Skip to content

Commit 798cccc

Browse files
Copilothotlong
andcommitted
feat: add namespace property to ObjectSchema with tableName auto-derivation, extend SystemObjectName with all system objects, create sys-namespaced object definitions across plugin-auth/plugin-security/plugin-audit
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 319e53a commit 798cccc

29 files changed

Lines changed: 1448 additions & 379 deletions

packages/metadata/src/objects/sys-metadata.object.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';
1515
* @see MetadataRecordSchema in metadata-persistence.zod.ts
1616
*/
1717
export const SysMetadataObject = ObjectSchema.create({
18-
name: 'sys_metadata',
18+
namespace: 'sys',
19+
name: 'metadata',
1920
label: 'System Metadata',
2021
pluralLabel: 'System Metadata',
2122
icon: 'settings',
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "@objectstack/plugin-audit",
3+
"version": "3.2.5",
4+
"license": "Apache-2.0",
5+
"description": "Audit Plugin for ObjectStack — System audit log object and audit trail",
6+
"main": "dist/index.js",
7+
"types": "dist/index.d.ts",
8+
"exports": {
9+
".": {
10+
"types": "./dist/index.d.ts",
11+
"import": "./dist/index.mjs",
12+
"require": "./dist/index.js"
13+
}
14+
},
15+
"scripts": {
16+
"build": "tsup --config ../../../tsup.config.ts",
17+
"test": "vitest run"
18+
},
19+
"dependencies": {
20+
"@objectstack/spec": "workspace:*"
21+
},
22+
"devDependencies": {
23+
"@types/node": "^25.3.5",
24+
"typescript": "^5.0.0",
25+
"vitest": "^4.0.18"
26+
}
27+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* @objectstack/plugin-audit
5+
*
6+
* Audit Plugin for ObjectStack
7+
* Provides the sys_audit_log system object definition for immutable audit trails.
8+
*/
9+
10+
// System Object Definitions (sys namespace)
11+
export { SysAuditLog } from './objects/index.js';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* Audit Plugin — System Object Definitions (sys namespace)
5+
*
6+
* Canonical ObjectSchema definitions for audit-related system objects.
7+
*/
8+
9+
export { SysAuditLog } from './sys-audit-log.object.js';
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { ObjectSchema, Field } from '@objectstack/spec/data';
4+
5+
/**
6+
* sys_audit_log — System Audit Log Object
7+
*
8+
* Immutable audit trail for all significant platform events.
9+
* Records who did what, when, and the before/after state.
10+
*
11+
* @namespace sys
12+
*/
13+
export const SysAuditLog = ObjectSchema.create({
14+
namespace: 'sys',
15+
name: 'audit_log',
16+
label: 'Audit Log',
17+
pluralLabel: 'Audit Logs',
18+
icon: 'scroll-text',
19+
isSystem: true,
20+
description: 'Immutable audit trail for platform events',
21+
titleFormat: '{action} on {object_name} by {user_id}',
22+
compactLayout: ['action', 'object_name', 'user_id', 'created_at'],
23+
24+
fields: {
25+
id: Field.text({
26+
label: 'Audit Log ID',
27+
required: true,
28+
readonly: true,
29+
}),
30+
31+
created_at: Field.datetime({
32+
label: 'Timestamp',
33+
required: true,
34+
defaultValue: 'NOW()',
35+
readonly: true,
36+
}),
37+
38+
user_id: Field.text({
39+
label: 'User ID',
40+
required: false,
41+
description: 'User who performed the action (null for system actions)',
42+
}),
43+
44+
action: Field.select(['create', 'update', 'delete', 'restore', 'login', 'logout', 'permission_change', 'config_change', 'export', 'import'], {
45+
label: 'Action',
46+
required: true,
47+
}),
48+
49+
object_name: Field.text({
50+
label: 'Object Name',
51+
required: false,
52+
maxLength: 255,
53+
description: 'Target object (e.g. sys_user, project_task)',
54+
}),
55+
56+
record_id: Field.text({
57+
label: 'Record ID',
58+
required: false,
59+
description: 'ID of the affected record',
60+
}),
61+
62+
old_value: Field.textarea({
63+
label: 'Old Value',
64+
required: false,
65+
description: 'JSON-serialized previous state',
66+
}),
67+
68+
new_value: Field.textarea({
69+
label: 'New Value',
70+
required: false,
71+
description: 'JSON-serialized new state',
72+
}),
73+
74+
ip_address: Field.text({
75+
label: 'IP Address',
76+
required: false,
77+
maxLength: 45,
78+
}),
79+
80+
user_agent: Field.textarea({
81+
label: 'User Agent',
82+
required: false,
83+
}),
84+
85+
tenant_id: Field.text({
86+
label: 'Tenant ID',
87+
required: false,
88+
description: 'Tenant context for multi-tenant isolation',
89+
}),
90+
91+
metadata: Field.textarea({
92+
label: 'Metadata',
93+
required: false,
94+
description: 'JSON-serialized additional context',
95+
}),
96+
},
97+
98+
indexes: [
99+
{ fields: ['created_at'] },
100+
{ fields: ['user_id'] },
101+
{ fields: ['object_name', 'record_id'] },
102+
{ fields: ['action'] },
103+
{ fields: ['tenant_id'] },
104+
],
105+
106+
enable: {
107+
trackHistory: false, // Audit logs are themselves the audit trail
108+
searchable: true,
109+
apiEnabled: true,
110+
apiMethods: ['get', 'list'], // Read-only — audit logs are immutable
111+
trash: false, // Never soft-delete audit logs
112+
mru: false,
113+
clone: false,
114+
},
115+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "../../../tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "./dist",
5+
"rootDir": "./src"
6+
},
7+
"include": ["src/**/*"],
8+
"exclude": ["dist", "node_modules", "**/*.test.ts"]
9+
}
Lines changed: 3 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,7 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

3-
import { ObjectSchema, Field } from '@objectstack/spec/data';
4-
53
/**
6-
* Auth Account Object
7-
*
8-
* Uses better-auth's native schema for seamless migration:
9-
* - id: string
10-
* - created_at: Date
11-
* - updated_at: Date
12-
* - provider_id: string (e.g., 'google', 'github')
13-
* - account_id: string (provider's user ID)
14-
* - user_id: string (link to user table)
15-
* - access_token: string | null
16-
* - refresh_token: string | null
17-
* - id_token: string | null
18-
* - access_token_expires_at: Date | null
19-
* - refresh_token_expires_at: Date | null
20-
* - scope: string | null
21-
* - password: string | null (for email/password provider)
4+
* @deprecated Use `SysAccount` from `./sys-account.object` instead.
5+
* This re-export is kept for backward compatibility.
226
*/
23-
export const AuthAccount = ObjectSchema.create({
24-
name: 'sys_account',
25-
label: 'Account',
26-
pluralLabel: 'Accounts',
27-
icon: 'link',
28-
description: 'OAuth and authentication provider accounts',
29-
titleFormat: '{provider_id} - {account_id}',
30-
compactLayout: ['provider_id', 'user_id', 'account_id'],
31-
32-
fields: {
33-
id: Field.text({
34-
label: 'Account ID',
35-
required: true,
36-
readonly: true,
37-
}),
38-
39-
created_at: Field.datetime({
40-
label: 'Created At',
41-
defaultValue: 'NOW()',
42-
readonly: true,
43-
}),
44-
45-
updated_at: Field.datetime({
46-
label: 'Updated At',
47-
defaultValue: 'NOW()',
48-
readonly: true,
49-
}),
50-
51-
provider_id: Field.text({
52-
label: 'Provider ID',
53-
required: true,
54-
description: 'OAuth provider identifier (google, github, etc.)',
55-
}),
56-
57-
account_id: Field.text({
58-
label: 'Provider Account ID',
59-
required: true,
60-
description: "User's ID in the provider's system",
61-
}),
62-
63-
user_id: Field.text({
64-
label: 'User ID',
65-
required: true,
66-
description: 'Link to user table',
67-
}),
68-
69-
access_token: Field.textarea({
70-
label: 'Access Token',
71-
required: false,
72-
}),
73-
74-
refresh_token: Field.textarea({
75-
label: 'Refresh Token',
76-
required: false,
77-
}),
78-
79-
id_token: Field.textarea({
80-
label: 'ID Token',
81-
required: false,
82-
}),
83-
84-
access_token_expires_at: Field.datetime({
85-
label: 'Access Token Expires At',
86-
required: false,
87-
}),
88-
89-
refresh_token_expires_at: Field.datetime({
90-
label: 'Refresh Token Expires At',
91-
required: false,
92-
}),
93-
94-
scope: Field.text({
95-
label: 'OAuth Scope',
96-
required: false,
97-
}),
98-
99-
password: Field.text({
100-
label: 'Password Hash',
101-
required: false,
102-
description: 'Hashed password for email/password provider',
103-
}),
104-
},
105-
106-
// Database indexes for performance
107-
indexes: [
108-
{ fields: ['user_id'], unique: false },
109-
{ fields: ['provider_id', 'account_id'], unique: true },
110-
],
111-
112-
// Enable features
113-
enable: {
114-
trackHistory: false,
115-
searchable: false,
116-
apiEnabled: true,
117-
apiMethods: ['get', 'list', 'create', 'update', 'delete'],
118-
trash: true,
119-
mru: false,
120-
},
121-
});
7+
export { SysAccount as AuthAccount } from './sys-account.object.js';

0 commit comments

Comments
 (0)