|
1 | 1 | // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. |
2 | 2 |
|
3 | | -import { ServiceObject, ObjectSchema, ObjectOwnership, provisionPrimary, resolveCrudAffordances, isTenancyDisabled, LEGACY_API_METHODS } from '@objectstack/spec/data'; |
| 3 | +import { ServiceObject, ObjectSchema, ObjectOwnership, provisionPrimary, resolveCrudAffordances, isTenancyDisabled, LEGACY_API_METHODS, AUDIT_PROVENANCE_FIELDS, type AuditProvenanceField } from '@objectstack/spec/data'; |
4 | 4 | import { resolveMultiOrgEnabled, resolveSearchPinyinEnabled } from '@objectstack/types'; |
5 | 5 | import { provisionSearchCompanion } from './search-companion.js'; |
6 | 6 | import { ObjectStackManifest, ManifestSchema, InstalledPackage, InstalledPackageSchema } from '@objectstack/spec/kernel'; |
@@ -233,6 +233,53 @@ export interface SchemaRegistryOptions { |
233 | 233 | * ADD ownership — the failure mode we are eliminating — silently breaks |
234 | 234 | * every owner-keyed feature. |
235 | 235 | */ |
| 236 | +/** |
| 237 | + * Column definitions for the audit-provenance family, keyed by the spec's |
| 238 | + * {@link AUDIT_PROVENANCE_FIELDS} tuple — the canonical declaration of WHICH |
| 239 | + * columns exist (#3786). This table owns only WHAT each column looks like. |
| 240 | + * |
| 241 | + * The `satisfies` clause is the sync mechanism: a name added to the spec tuple |
| 242 | + * without a definition here — or a definition for a name the spec dropped — is |
| 243 | + * a compile error, not a silently diverging copy. Same discipline as the |
| 244 | + * spec's `APPROVER_VALUE_BINDINGS`. |
| 245 | + */ |
| 246 | +const AUDIT_FIELD_DEFS = { |
| 247 | + created_at: { |
| 248 | + type: 'datetime', |
| 249 | + label: 'Created At', |
| 250 | + required: false, |
| 251 | + readonly: true, |
| 252 | + system: true, |
| 253 | + description: 'Timestamp when the record was created (auto-populated by the driver).', |
| 254 | + }, |
| 255 | + created_by: { |
| 256 | + type: 'lookup', |
| 257 | + reference: 'sys_user', |
| 258 | + label: 'Created By', |
| 259 | + required: false, |
| 260 | + readonly: true, |
| 261 | + system: true, |
| 262 | + description: 'User who created the record (populated when an authenticated session is present).', |
| 263 | + }, |
| 264 | + updated_at: { |
| 265 | + type: 'datetime', |
| 266 | + label: 'Last Modified At', |
| 267 | + required: false, |
| 268 | + readonly: true, |
| 269 | + system: true, |
| 270 | + description: 'Timestamp of the most recent modification (auto-populated by the driver).', |
| 271 | + }, |
| 272 | + updated_by: { |
| 273 | + type: 'lookup', |
| 274 | + reference: 'sys_user', |
| 275 | + label: 'Last Modified By', |
| 276 | + required: false, |
| 277 | + readonly: true, |
| 278 | + system: true, |
| 279 | + description: 'User who last modified the record (populated when an authenticated session is present).', |
| 280 | + }, |
| 281 | +} satisfies Record<AuditProvenanceField, Record<string, unknown>>; |
| 282 | + |
236 | 283 | export function applySystemFields( |
237 | 284 | schema: ServiceObject, |
238 | 285 | opts: { multiTenant: boolean } |
@@ -314,47 +361,8 @@ export function applySystemFields( |
314 | 361 | } |
315 | 362 |
|
316 | 363 | if (wantAudit) { |
317 | | - if (!schema.fields?.created_at) { |
318 | | - additions.created_at = { |
319 | | - type: 'datetime', |
320 | | - label: 'Created At', |
321 | | - required: false, |
322 | | - readonly: true, |
323 | | - system: true, |
324 | | - description: 'Timestamp when the record was created (auto-populated by the driver).', |
325 | | - }; |
326 | | - } |
327 | | - if (!schema.fields?.created_by) { |
328 | | - additions.created_by = { |
329 | | - type: 'lookup', |
330 | | - reference: 'sys_user', |
331 | | - label: 'Created By', |
332 | | - required: false, |
333 | | - readonly: true, |
334 | | - system: true, |
335 | | - description: 'User who created the record (populated when an authenticated session is present).', |
336 | | - }; |
337 | | - } |
338 | | - if (!schema.fields?.updated_at) { |
339 | | - additions.updated_at = { |
340 | | - type: 'datetime', |
341 | | - label: 'Last Modified At', |
342 | | - required: false, |
343 | | - readonly: true, |
344 | | - system: true, |
345 | | - description: 'Timestamp of the most recent modification (auto-populated by the driver).', |
346 | | - }; |
347 | | - } |
348 | | - if (!schema.fields?.updated_by) { |
349 | | - additions.updated_by = { |
350 | | - type: 'lookup', |
351 | | - reference: 'sys_user', |
352 | | - label: 'Last Modified By', |
353 | | - required: false, |
354 | | - readonly: true, |
355 | | - system: true, |
356 | | - description: 'User who last modified the record (populated when an authenticated session is present).', |
357 | | - }; |
| 364 | + for (const name of AUDIT_PROVENANCE_FIELDS) { |
| 365 | + if (!schema.fields?.[name]) additions[name] = AUDIT_FIELD_DEFS[name]; |
358 | 366 | } |
359 | 367 | } |
360 | 368 |
|
|
0 commit comments