@happyvertical/smrt-core 0.51.10 → 0.51.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/agents/change-feed.md +82 -0
  2. package/agents/generators.md +3 -1
  3. package/agents/schema-paths.md +23 -6
  4. package/dist/change-feed-sensitivity.d.ts +155 -0
  5. package/dist/change-feed-sensitivity.d.ts.map +1 -0
  6. package/dist/change-feed-sensitivity.js +139 -0
  7. package/dist/change-feed-sensitivity.js.map +1 -0
  8. package/dist/change-feed.d.ts +37 -10
  9. package/dist/change-feed.d.ts.map +1 -1
  10. package/dist/change-feed.js +128 -12
  11. package/dist/change-feed.js.map +1 -1
  12. package/dist/change-signals.d.ts.map +1 -1
  13. package/dist/change-signals.js +3 -0
  14. package/dist/change-signals.js.map +1 -1
  15. package/dist/class.d.ts +1 -1
  16. package/dist/class.d.ts.map +1 -1
  17. package/dist/class.js +4 -2
  18. package/dist/class.js.map +1 -1
  19. package/dist/decorators/index.d.ts +19 -0
  20. package/dist/decorators/index.d.ts.map +1 -1
  21. package/dist/decorators/index.js.map +1 -1
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +3 -2
  25. package/dist/manifest/static-manifest.js +1 -1
  26. package/dist/manifest/static-manifest.js.map +1 -1
  27. package/dist/manifest/store.js +1 -1
  28. package/dist/manifest.json +1 -1
  29. package/dist/migrations/differ.d.ts +64 -5
  30. package/dist/migrations/differ.d.ts.map +1 -1
  31. package/dist/migrations/differ.js +274 -37
  32. package/dist/migrations/differ.js.map +1 -1
  33. package/dist/migrations/index.d.ts +1 -1
  34. package/dist/migrations/index.d.ts.map +1 -1
  35. package/dist/migrations/index.js +2 -2
  36. package/dist/migrations/sqlite-rebuild.d.ts +41 -0
  37. package/dist/migrations/sqlite-rebuild.d.ts.map +1 -1
  38. package/dist/migrations/sqlite-rebuild.js +68 -13
  39. package/dist/migrations/sqlite-rebuild.js.map +1 -1
  40. package/dist/migrations.js +2 -2
  41. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/assert-valid-pattern.js +1 -1
  42. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -1
  43. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/ast.js +1 -1
  44. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/ast.js.map +1 -1
  45. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/brace-expressions.js +1 -1
  46. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
  47. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/escape.js +1 -1
  48. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/escape.js.map +1 -1
  49. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/index.js +1 -1
  50. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/index.js.map +1 -1
  51. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/unescape.js +1 -1
  52. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
  53. package/dist/registry/class-registration.d.ts +1 -1
  54. package/dist/registry/class-registration.d.ts.map +1 -1
  55. package/dist/registry/class-registration.js +50 -4
  56. package/dist/registry/class-registration.js.map +1 -1
  57. package/dist/registry/db-cache-key.d.ts +47 -0
  58. package/dist/registry/db-cache-key.d.ts.map +1 -0
  59. package/dist/registry/db-cache-key.js +201 -0
  60. package/dist/registry/db-cache-key.js.map +1 -0
  61. package/dist/registry/schema-builder.d.ts.map +1 -1
  62. package/dist/registry/schema-builder.js +6 -1
  63. package/dist/registry/schema-builder.js.map +1 -1
  64. package/dist/registry/types.d.ts +43 -0
  65. package/dist/registry/types.d.ts.map +1 -1
  66. package/dist/registry.d.ts +17 -1
  67. package/dist/registry.d.ts.map +1 -1
  68. package/dist/registry.js +20 -8
  69. package/dist/registry.js.map +1 -1
  70. package/dist/scanner/types.d.ts +2 -0
  71. package/dist/scanner/types.d.ts.map +1 -1
  72. package/dist/scanner/types.js.map +1 -1
  73. package/dist/schema/backfill.d.ts +16 -0
  74. package/dist/schema/backfill.d.ts.map +1 -0
  75. package/dist/schema/backfill.js +26 -0
  76. package/dist/schema/backfill.js.map +1 -0
  77. package/dist/schema/generator.d.ts.map +1 -1
  78. package/dist/schema/generator.js +3 -0
  79. package/dist/schema/generator.js.map +1 -1
  80. package/dist/schema/types.d.ts +20 -1
  81. package/dist/schema/types.d.ts.map +1 -1
  82. package/dist/smrt-knowledge.json +21 -21
  83. package/dist/vite-plugin/dev-plane-route.js +1 -1
  84. package/dist/vite-plugin/dev-plane-route.js.map +1 -1
  85. package/dist/vite-plugin/index.js +1 -1
  86. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  87. package/dist/vite-plugin/sveltekit-generator.js +54 -7
  88. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  89. package/dist/vite-plugin/sync-apply-route.d.ts +7 -0
  90. package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
  91. package/dist/vite-plugin/sync-apply-route.js +50 -3
  92. package/dist/vite-plugin/sync-apply-route.js.map +1 -1
  93. package/package.json +23 -23
@@ -13,6 +13,13 @@ interface SyncTargetSpec {
13
13
  * simple name.
14
14
  */
15
15
  registryKey: string;
16
+ /**
17
+ * `<collection>` prefix of the `<collection>.<action>` operation permission
18
+ * the mirrored generated write routes require (#2977) — the same
19
+ * `decoratorConfig.collection ?? collection` resolution as
20
+ * sveltekit-generator.ts `resolvePermissionCollection`.
21
+ */
22
+ permissionCollection: string;
16
23
  ops: MutatingAction[];
17
24
  readonlyFields: string[];
18
25
  writableAllowlist: string[] | null;
@@ -1 +1 @@
1
- {"version":3,"file":"sync-apply-route.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/sync-apply-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,QAAA,MAAM,gBAAgB,yCAA0C,CAAC;AAEjE,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,wDAAwD;AACxD,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,cAAc,EAAE,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,iBAAiB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACnC,YAAY,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B,YAAY,EAAE,OAAO,CAAC;CACvB;AAmED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,mBAAmB,GAC5B,cAAc,EAAE,CAgClB;AAgBD;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,cAAc,EAAE,EACzB,YAAY,EAAE,MAAM,GACnB,MAAM,CA0GR;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAiCT"}
1
+ {"version":3,"file":"sync-apply-route.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/sync-apply-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,QAAA,MAAM,gBAAgB,yCAA0C,CAAC;AAEjE,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,wDAAwD;AACxD,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAC7B,GAAG,EAAE,cAAc,EAAE,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,iBAAiB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACnC,YAAY,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B,YAAY,EAAE,OAAO,CAAC;CACvB;AA6ED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,mBAAmB,GAC5B,cAAc,EAAE,CAiClB;AAiBD;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,cAAc,EAAE,EACzB,YAAY,EAAE,MAAM,GACnB,MAAM,CAkJR;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAiCT"}
@@ -68,6 +68,11 @@ function getApiWritableAllowlist(apiConfig) {
68
68
  const config = getApiConfigObject(apiConfig);
69
69
  return Array.isArray(config?.writable) ? config.writable : null;
70
70
  }
71
+ /** Mirror of sveltekit-generator.ts `resolvePermissionCollection` (#2977). */
72
+ function resolvePermissionCollection(objectDef) {
73
+ const configured = objectDef.decoratorConfig?.collection;
74
+ return typeof configured === "string" && configured.length > 0 ? configured : objectDef.collection;
75
+ }
71
76
  /**
72
77
  * Collect the syncable targets from a manifest: non-collection objects whose
73
78
  * API config is enabled and exposes at least one mutating action.
@@ -85,6 +90,7 @@ function collectSyncApplyTargets(manifest) {
85
90
  targets.push({
86
91
  segment: objectDef.collection,
87
92
  registryKey: className,
93
+ permissionCollection: resolvePermissionCollection(objectDef),
88
94
  ops,
89
95
  readonlyFields: collectReadonlyFieldNames(objectDef),
90
96
  writableAllowlist: getApiWritableAllowlist(apiConfig),
@@ -98,6 +104,7 @@ function serializeTargets(targets) {
98
104
  return `{\n${targets.map((target) => {
99
105
  const fields = [
100
106
  `registryKey: ${JSON.stringify(target.registryKey)}`,
107
+ `permissionCollection: ${JSON.stringify(target.permissionCollection)}`,
101
108
  `ops: ${JSON.stringify(target.ops)}`,
102
109
  `readonlyFields: ${JSON.stringify(target.readonlyFields)}`,
103
110
  `writableAllowlist: ${JSON.stringify(target.writableAllowlist)}`,
@@ -146,6 +153,8 @@ function establishTenantContext(locals: unknown): void {
146
153
  interface SyncTargetConfig {
147
154
  /** Manifest registry key (may be package-qualified), as CRUD routes use. */
148
155
  registryKey: string;
156
+ /** \`<collection>\` prefix of the required \`<collection>.<op>\` permission. */
157
+ permissionCollection: string;
149
158
  ops: SyncApplyOp[];
150
159
  readonlyFields: string[];
151
160
  writableAllowlist: string[] | null;
@@ -170,6 +179,39 @@ function hasAuthenticatedPrincipal(locals: unknown): boolean {
170
179
  );
171
180
  }
172
181
 
182
+ function readRecord(value: unknown): Record<string, unknown> {
183
+ return value && typeof value === 'object'
184
+ ? (value as Record<string, unknown>)
185
+ : {};
186
+ }
187
+
188
+ function hasGrantedPermission(value: unknown, permission: string): boolean {
189
+ if (Array.isArray(value)) return value.includes(permission);
190
+ if (value instanceof Set) return value.has(permission);
191
+ return false;
192
+ }
193
+
194
+ // Fail-closed operation permission (#2977, #3011): each applied op requires
195
+ // the same \`<collection>.<op>\` permission the mirrored generated write route
196
+ // enforces, read from the principal's session permission snapshot. A missing
197
+ // snapshot denies; only an explicit tenancy super-admin bypass skips it.
198
+ function hasOperationPermission(
199
+ locals: unknown,
200
+ permissionCollection: string,
201
+ op: SyncApplyOp,
202
+ ): boolean {
203
+ const l = readRecord(locals);
204
+ const tenantContext = readRecord(l.tenantContext);
205
+ if (tenantContext.superAdminBypass === true) return true;
206
+ const permission = \`\${permissionCollection}.\${op}\`;
207
+ return (
208
+ hasGrantedPermission(l.permissions, permission) ||
209
+ hasGrantedPermission(l.permissionSet, permission) ||
210
+ hasGrantedPermission(l.smrtPermissions, permission) ||
211
+ hasGrantedPermission(tenantContext.permissions, permission)
212
+ );
213
+ }
214
+
173
215
  export const POST: RequestHandler = async ({ locals, request }) => {
174
216
  const authenticated = hasAuthenticatedPrincipal(locals);
175
217
  ${anyTenantScoped ? " establishTenantContext(locals);\n" : ""}
@@ -195,10 +237,15 @@ ${anyTenantScoped ? " establishTenantContext(locals);\n" : ""}
195
237
  objectName: target.registryKey,
196
238
  collection,
197
239
  isOpAllowed: (op: SyncApplyOp) => target.ops.includes(op),
198
- authorize: () =>
199
- authenticated || target.publicAccess === true
240
+ authorize: (op: SyncApplyOp) => {
241
+ // \`public: true\` keeps its unauthenticated write semantics, as on
242
+ // the generated CRUD routes.
243
+ if (target.publicAccess === true) return 'ok' as const;
244
+ if (!authenticated) return 'auth_required' as const;
245
+ return hasOperationPermission(locals, target.permissionCollection, op)
200
246
  ? ('ok' as const)
201
- : ('auth_required' as const),
247
+ : ('forbidden' as const);
248
+ },
202
249
  prepare: (payload: Record<string, unknown>) =>
203
250
  applySyncWritablePolicy(payload, {
204
251
  readonlyFields: target.readonlyFields,
@@ -1 +1 @@
1
- {"version":3,"file":"sync-apply-route.js","names":[],"sources":["../../src/vite-plugin/sync-apply-route.ts"],"sourcesContent":["/**\n * SvelteKit generator for the sync-apply batch write route (#1759).\n *\n * Emits ONE route — `{routesDir}/sync/apply/+server.ts` — implementing the\n * shared web/mobile write contract for every syncable model in the manifest\n * (models whose API config exposes at least one of create/update/delete). The\n * generated file is deliberately thin: it maps `object` segments to\n * collections plus per-model policy data, and delegates all batch processing\n * to `processSyncApplyBatch` from `@happyvertical/smrt-core` — the same\n * engine the runtime REST generator uses, so the two transports cannot drift.\n *\n * This lives in its own module (rather than sveltekit-generator.ts) so the\n * shared generator file only needs a one-line registration hook; sibling\n * work (#1757, #1758) touches the same generator files in parallel.\n *\n * Contract documentation: docs/content/architecture/sync-apply-contract.md\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { isFrameworkBaseClass } from '../registry/framework-base-classes.js';\nimport type { ApiConfig } from '../registry/types.js';\nimport type {\n SmartObjectDefinition,\n SmartObjectManifest,\n} from '../scanner/types';\nimport { AUTO_GENERATED_ROUTE_HEADER } from './route-header.js';\nimport { resolveSvelteKitConfigImport } from './sveltekit-config-import.js';\nimport type { SvelteKitOptions } from './sveltekit-generator.js';\nimport { isCollectionManifestClass } from './web-collections.js';\n\nconst MUTATING_ACTIONS = ['create', 'update', 'delete'] as const;\n\ntype MutatingAction = (typeof MUTATING_ACTIONS)[number];\n\n/** Per-model data embedded into the generated route. */\ninterface SyncTargetSpec {\n segment: string;\n /**\n * The manifest registry key, passed VERBATIM to `getCollection()` exactly\n * as the generated CRUD routes do. May be package-qualified\n * (`@happyvertical/smrt-ledgers:Account`) — collapsing it to a simple class\n * name would resolve ambiguously when two loaded packages declare the same\n * simple name.\n */\n registryKey: string;\n ops: MutatingAction[];\n readonlyFields: string[];\n writableAllowlist: string[] | null;\n publicAccess: boolean | 'read';\n tenantScoped: boolean;\n}\n\nfunction getApiConfigObject(apiConfig: unknown): ApiConfig | null {\n if (!apiConfig || typeof apiConfig !== 'object') {\n return null;\n }\n return apiConfig as ApiConfig;\n}\n\n/**\n * Mirror of sveltekit-generator.ts `resolveStandardCrudActions`, narrowed to\n * the mutating subset the sync contract can carry. Kept local so this module\n * needs no exports added to the (parallel-edited) shared generator file.\n */\nfunction resolveMutatingActions(apiConfig: unknown): MutatingAction[] {\n if (apiConfig === false) return [];\n if (\n apiConfig === true ||\n apiConfig === undefined ||\n typeof apiConfig !== 'object'\n ) {\n return [...MUTATING_ACTIONS];\n }\n\n const config = apiConfig as { include?: string[]; exclude?: string[] };\n let actions: MutatingAction[] = Array.isArray(config.include)\n ? MUTATING_ACTIONS.filter((action) => config.include?.includes(action))\n : [...MUTATING_ACTIONS];\n if (Array.isArray(config.exclude)) {\n const excluded = config.exclude;\n actions = actions.filter((action) => !excluded.includes(action));\n }\n return actions;\n}\n\n/** Mirror of sveltekit-generator.ts `getApiPublicAccess` (fail-closed). */\nfunction getApiPublicAccess(apiConfig: unknown): boolean | 'read' {\n const config = getApiConfigObject(apiConfig);\n const value = config?.public;\n if (value === true || value === 'read') {\n return value;\n }\n return false;\n}\n\n/** Mirror of sveltekit-generator.ts `collectReadonlyFieldNames` (#1540, 2b). */\nfunction collectReadonlyFieldNames(objectDef: SmartObjectDefinition): string[] {\n const fields = objectDef.fields || {};\n const names: string[] = [];\n for (const [name, def] of Object.entries(fields)) {\n const meta = (def as { _meta?: Record<string, unknown> })._meta;\n if (\n (def as { readonly?: boolean }).readonly === true ||\n meta?.readonly === true\n ) {\n names.push(name);\n }\n }\n return names;\n}\n\n/** Mirror of sveltekit-generator.ts `getApiWritableAllowlist`. */\nfunction getApiWritableAllowlist(apiConfig: unknown): string[] | null {\n const config = getApiConfigObject(apiConfig);\n return Array.isArray(config?.writable) ? config.writable : null;\n}\n\n/**\n * Collect the syncable targets from a manifest: non-collection objects whose\n * API config is enabled and exposes at least one mutating action.\n */\nexport function collectSyncApplyTargets(\n manifest: SmartObjectManifest,\n): SyncTargetSpec[] {\n const targets: SyncTargetSpec[] = [];\n\n for (const [className, objectDef] of Object.entries(manifest.objects)) {\n if (isCollectionManifestClass(manifest, objectDef)) continue;\n // The framework's own abstract base classes are scaffolding, not\n // resources — never make them a sync-apply target, regardless of\n // config (#2642).\n if (isFrameworkBaseClass(objectDef.className, objectDef.packageName)) {\n continue;\n }\n\n const apiConfig = objectDef.decoratorConfig?.api;\n if (apiConfig === false) continue;\n\n const ops = resolveMutatingActions(apiConfig);\n if (ops.length === 0) continue;\n\n if (!objectDef.collection) continue;\n\n targets.push({\n segment: objectDef.collection,\n registryKey: className,\n ops,\n readonlyFields: collectReadonlyFieldNames(objectDef),\n writableAllowlist: getApiWritableAllowlist(apiConfig),\n publicAccess: getApiPublicAccess(apiConfig),\n tenantScoped: !!objectDef.decoratorConfig?.tenantScoped,\n });\n }\n\n return targets.sort((a, b) => a.segment.localeCompare(b.segment));\n}\n\nfunction serializeTargets(targets: SyncTargetSpec[]): string {\n const entries = targets.map((target) => {\n const fields = [\n `registryKey: ${JSON.stringify(target.registryKey)}`,\n `ops: ${JSON.stringify(target.ops)}`,\n `readonlyFields: ${JSON.stringify(target.readonlyFields)}`,\n `writableAllowlist: ${JSON.stringify(target.writableAllowlist)}`,\n `publicAccess: ${JSON.stringify(target.publicAccess)}`,\n ];\n return ` ${JSON.stringify(target.segment)}: {\\n ${fields.join(',\\n ')},\\n },`;\n });\n return `{\\n${entries.join('\\n')}\\n}`;\n}\n\n/**\n * Render the generated `+server.ts` content for the sync-apply route.\n * Exported for tests.\n */\nexport function generateSyncApplyRouteTemplate(\n targets: SyncTargetSpec[],\n configImport: string,\n): string {\n const anyTenantScoped = targets.some((target) => target.tenantScoped);\n\n const tenantHelper = anyTenantScoped\n ? `\nimport { enterTenantContext, hasTenantContext } from '@happyvertical/smrt-tenancy';\n\nfunction establishTenantContext(locals: unknown): void {\n if (hasTenantContext()) return;\n if (!locals || typeof locals !== 'object') return;\n const l = locals as Record<string, unknown>;\n const user = l.user as Record<string, unknown> | undefined;\n const session = l.session as Record<string, unknown> | undefined;\n const tenantId = l.tenantId ?? user?.tenantId ?? session?.tenantId;\n if (typeof tenantId === 'string' && tenantId) {\n enterTenantContext({ tenantId });\n }\n}\n`\n : '';\n\n return `${AUTO_GENERATED_ROUTE_HEADER}\n// DO NOT EDIT - changes will be overwritten\n//\n// Idempotent sync-apply batch write contract (#1759) — the shared write path\n// for the web offline outbox (#1762) and the KMP mobile write queue (#1739).\n// Contract: docs/content/architecture/sync-apply-contract.md\n\nimport {\n applySyncWritablePolicy,\n processSyncApplyBatch,\n type SyncApplyOp,\n type SyncApplyTarget,\n} from '@happyvertical/smrt-core';\nimport { json } from '@sveltejs/kit';\nimport { getCollection } from '${configImport}';\nimport type { RequestHandler } from './$types';\n${tenantHelper}\ninterface SyncTargetConfig {\n /** Manifest registry key (may be package-qualified), as CRUD routes use. */\n registryKey: string;\n ops: SyncApplyOp[];\n readonlyFields: string[];\n writableAllowlist: string[] | null;\n publicAccess: boolean | 'read';\n}\n\n// One entry per syncable model, keyed by the collection route segment its\n// generated CRUD routes use.\nconst SYNC_TARGETS: Record<string, SyncTargetConfig> = ${serializeTargets(targets)};\n\n// Fail-closed authorization (#1540): sync mutations require an authenticated\n// principal on \\`locals\\` unless the target model is \\`api: { public: true }\\`.\nfunction hasAuthenticatedPrincipal(locals: unknown): boolean {\n if (!locals || typeof locals !== 'object') return false;\n const l = locals as Record<string, unknown>;\n const isResolvedPrincipal = (v: unknown) =>\n typeof v === 'object' && v !== null;\n return (\n isResolvedPrincipal(l.user) ||\n isResolvedPrincipal(l.session) ||\n l.smrtAuth === true\n );\n}\n\nexport const POST: RequestHandler = async ({ locals, request }) => {\n const authenticated = hasAuthenticatedPrincipal(locals);\n${anyTenantScoped ? ' establishTenantContext(locals);\\n' : ''}\n let body: unknown;\n try {\n body = await request.json();\n } catch {\n return json(\n { error: { code: 'invalid_batch', message: 'Invalid JSON body' } },\n { status: 400 },\n );\n }\n\n const outcome = await processSyncApplyBatch(body, {\n resolveTarget: async (segment: string): Promise<SyncApplyTarget | null> => {\n const target = SYNC_TARGETS[segment];\n if (!target) return null;\n // The verbatim manifest key, exactly as the generated CRUD routes pass\n // it — package-qualified keys stay qualified so same-simple-name\n // classes across packages resolve unambiguously.\n const collection = await getCollection(target.registryKey);\n return {\n objectName: target.registryKey,\n collection,\n isOpAllowed: (op: SyncApplyOp) => target.ops.includes(op),\n authorize: () =>\n authenticated || target.publicAccess === true\n ? ('ok' as const)\n : ('auth_required' as const),\n prepare: (payload: Record<string, unknown>) =>\n applySyncWritablePolicy(payload, {\n readonlyFields: target.readonlyFields,\n writableAllowlist: target.writableAllowlist,\n }),\n };\n },\n });\n\n return json(outcome.body, { status: outcome.status });\n};\n`;\n}\n\n/**\n * Generate `{routesDir}/sync/apply/+server.ts` for the manifest's syncable\n * models. Returns true when a route was written. Skips generation (with a\n * warning) if a non-generated file already occupies the path, and writes\n * nothing when the manifest has no syncable models.\n */\nexport function generateSyncApplyRoute(\n projectRoot: string,\n manifest: SmartObjectManifest,\n options: SvelteKitOptions,\n): boolean {\n const targets = collectSyncApplyTargets(manifest);\n if (targets.length === 0) {\n return false;\n }\n\n const routeDir = join(projectRoot, options.routesDir, 'sync', 'apply');\n const routePath = join(routeDir, '+server.ts');\n\n if (existsSync(routePath)) {\n const existing = readFileSync(routePath, 'utf-8');\n if (!existing.startsWith(AUTO_GENERATED_ROUTE_HEADER)) {\n console.warn(\n `[smrt] Skipping sync-apply route - ${routePath} exists and is not generated`,\n );\n return false;\n }\n }\n\n if (!existsSync(routeDir)) {\n mkdirSync(routeDir, { recursive: true });\n }\n\n writeFileSync(\n routePath,\n generateSyncApplyRouteTemplate(\n targets,\n resolveSvelteKitConfigImport(projectRoot, routeDir, options),\n ),\n 'utf-8',\n );\n console.log(`[smrt] Generated: ${routePath}`);\n return true;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sync-apply-route.js","names":[],"sources":["../../src/vite-plugin/sync-apply-route.ts"],"sourcesContent":["/**\n * SvelteKit generator for the sync-apply batch write route (#1759).\n *\n * Emits ONE route — `{routesDir}/sync/apply/+server.ts` — implementing the\n * shared web/mobile write contract for every syncable model in the manifest\n * (models whose API config exposes at least one of create/update/delete). The\n * generated file is deliberately thin: it maps `object` segments to\n * collections plus per-model policy data, and delegates all batch processing\n * to `processSyncApplyBatch` from `@happyvertical/smrt-core` — the same\n * engine the runtime REST generator uses, so the two transports cannot drift.\n *\n * This lives in its own module (rather than sveltekit-generator.ts) so the\n * shared generator file only needs a one-line registration hook; sibling\n * work (#1757, #1758) touches the same generator files in parallel.\n *\n * Contract documentation: docs/content/architecture/sync-apply-contract.md\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { isFrameworkBaseClass } from '../registry/framework-base-classes.js';\nimport type { ApiConfig } from '../registry/types.js';\nimport type {\n SmartObjectDefinition,\n SmartObjectManifest,\n} from '../scanner/types';\nimport { AUTO_GENERATED_ROUTE_HEADER } from './route-header.js';\nimport { resolveSvelteKitConfigImport } from './sveltekit-config-import.js';\nimport type { SvelteKitOptions } from './sveltekit-generator.js';\nimport { isCollectionManifestClass } from './web-collections.js';\n\nconst MUTATING_ACTIONS = ['create', 'update', 'delete'] as const;\n\ntype MutatingAction = (typeof MUTATING_ACTIONS)[number];\n\n/** Per-model data embedded into the generated route. */\ninterface SyncTargetSpec {\n segment: string;\n /**\n * The manifest registry key, passed VERBATIM to `getCollection()` exactly\n * as the generated CRUD routes do. May be package-qualified\n * (`@happyvertical/smrt-ledgers:Account`) — collapsing it to a simple class\n * name would resolve ambiguously when two loaded packages declare the same\n * simple name.\n */\n registryKey: string;\n /**\n * `<collection>` prefix of the `<collection>.<action>` operation permission\n * the mirrored generated write routes require (#2977) — the same\n * `decoratorConfig.collection ?? collection` resolution as\n * sveltekit-generator.ts `resolvePermissionCollection`.\n */\n permissionCollection: string;\n ops: MutatingAction[];\n readonlyFields: string[];\n writableAllowlist: string[] | null;\n publicAccess: boolean | 'read';\n tenantScoped: boolean;\n}\n\nfunction getApiConfigObject(apiConfig: unknown): ApiConfig | null {\n if (!apiConfig || typeof apiConfig !== 'object') {\n return null;\n }\n return apiConfig as ApiConfig;\n}\n\n/**\n * Mirror of sveltekit-generator.ts `resolveStandardCrudActions`, narrowed to\n * the mutating subset the sync contract can carry. Kept local so this module\n * needs no exports added to the (parallel-edited) shared generator file.\n */\nfunction resolveMutatingActions(apiConfig: unknown): MutatingAction[] {\n if (apiConfig === false) return [];\n if (\n apiConfig === true ||\n apiConfig === undefined ||\n typeof apiConfig !== 'object'\n ) {\n return [...MUTATING_ACTIONS];\n }\n\n const config = apiConfig as { include?: string[]; exclude?: string[] };\n let actions: MutatingAction[] = Array.isArray(config.include)\n ? MUTATING_ACTIONS.filter((action) => config.include?.includes(action))\n : [...MUTATING_ACTIONS];\n if (Array.isArray(config.exclude)) {\n const excluded = config.exclude;\n actions = actions.filter((action) => !excluded.includes(action));\n }\n return actions;\n}\n\n/** Mirror of sveltekit-generator.ts `getApiPublicAccess` (fail-closed). */\nfunction getApiPublicAccess(apiConfig: unknown): boolean | 'read' {\n const config = getApiConfigObject(apiConfig);\n const value = config?.public;\n if (value === true || value === 'read') {\n return value;\n }\n return false;\n}\n\n/** Mirror of sveltekit-generator.ts `collectReadonlyFieldNames` (#1540, 2b). */\nfunction collectReadonlyFieldNames(objectDef: SmartObjectDefinition): string[] {\n const fields = objectDef.fields || {};\n const names: string[] = [];\n for (const [name, def] of Object.entries(fields)) {\n const meta = (def as { _meta?: Record<string, unknown> })._meta;\n if (\n (def as { readonly?: boolean }).readonly === true ||\n meta?.readonly === true\n ) {\n names.push(name);\n }\n }\n return names;\n}\n\n/** Mirror of sveltekit-generator.ts `getApiWritableAllowlist`. */\nfunction getApiWritableAllowlist(apiConfig: unknown): string[] | null {\n const config = getApiConfigObject(apiConfig);\n return Array.isArray(config?.writable) ? config.writable : null;\n}\n\n/** Mirror of sveltekit-generator.ts `resolvePermissionCollection` (#2977). */\nfunction resolvePermissionCollection(objectDef: SmartObjectDefinition): string {\n const configured = (\n objectDef.decoratorConfig as { collection?: unknown } | undefined\n )?.collection;\n return typeof configured === 'string' && configured.length > 0\n ? configured\n : objectDef.collection;\n}\n\n/**\n * Collect the syncable targets from a manifest: non-collection objects whose\n * API config is enabled and exposes at least one mutating action.\n */\nexport function collectSyncApplyTargets(\n manifest: SmartObjectManifest,\n): SyncTargetSpec[] {\n const targets: SyncTargetSpec[] = [];\n\n for (const [className, objectDef] of Object.entries(manifest.objects)) {\n if (isCollectionManifestClass(manifest, objectDef)) continue;\n // The framework's own abstract base classes are scaffolding, not\n // resources — never make them a sync-apply target, regardless of\n // config (#2642).\n if (isFrameworkBaseClass(objectDef.className, objectDef.packageName)) {\n continue;\n }\n\n const apiConfig = objectDef.decoratorConfig?.api;\n if (apiConfig === false) continue;\n\n const ops = resolveMutatingActions(apiConfig);\n if (ops.length === 0) continue;\n\n if (!objectDef.collection) continue;\n\n targets.push({\n segment: objectDef.collection,\n registryKey: className,\n permissionCollection: resolvePermissionCollection(objectDef),\n ops,\n readonlyFields: collectReadonlyFieldNames(objectDef),\n writableAllowlist: getApiWritableAllowlist(apiConfig),\n publicAccess: getApiPublicAccess(apiConfig),\n tenantScoped: !!objectDef.decoratorConfig?.tenantScoped,\n });\n }\n\n return targets.sort((a, b) => a.segment.localeCompare(b.segment));\n}\n\nfunction serializeTargets(targets: SyncTargetSpec[]): string {\n const entries = targets.map((target) => {\n const fields = [\n `registryKey: ${JSON.stringify(target.registryKey)}`,\n `permissionCollection: ${JSON.stringify(target.permissionCollection)}`,\n `ops: ${JSON.stringify(target.ops)}`,\n `readonlyFields: ${JSON.stringify(target.readonlyFields)}`,\n `writableAllowlist: ${JSON.stringify(target.writableAllowlist)}`,\n `publicAccess: ${JSON.stringify(target.publicAccess)}`,\n ];\n return ` ${JSON.stringify(target.segment)}: {\\n ${fields.join(',\\n ')},\\n },`;\n });\n return `{\\n${entries.join('\\n')}\\n}`;\n}\n\n/**\n * Render the generated `+server.ts` content for the sync-apply route.\n * Exported for tests.\n */\nexport function generateSyncApplyRouteTemplate(\n targets: SyncTargetSpec[],\n configImport: string,\n): string {\n const anyTenantScoped = targets.some((target) => target.tenantScoped);\n\n const tenantHelper = anyTenantScoped\n ? `\nimport { enterTenantContext, hasTenantContext } from '@happyvertical/smrt-tenancy';\n\nfunction establishTenantContext(locals: unknown): void {\n if (hasTenantContext()) return;\n if (!locals || typeof locals !== 'object') return;\n const l = locals as Record<string, unknown>;\n const user = l.user as Record<string, unknown> | undefined;\n const session = l.session as Record<string, unknown> | undefined;\n const tenantId = l.tenantId ?? user?.tenantId ?? session?.tenantId;\n if (typeof tenantId === 'string' && tenantId) {\n enterTenantContext({ tenantId });\n }\n}\n`\n : '';\n\n return `${AUTO_GENERATED_ROUTE_HEADER}\n// DO NOT EDIT - changes will be overwritten\n//\n// Idempotent sync-apply batch write contract (#1759) — the shared write path\n// for the web offline outbox (#1762) and the KMP mobile write queue (#1739).\n// Contract: docs/content/architecture/sync-apply-contract.md\n\nimport {\n applySyncWritablePolicy,\n processSyncApplyBatch,\n type SyncApplyOp,\n type SyncApplyTarget,\n} from '@happyvertical/smrt-core';\nimport { json } from '@sveltejs/kit';\nimport { getCollection } from '${configImport}';\nimport type { RequestHandler } from './$types';\n${tenantHelper}\ninterface SyncTargetConfig {\n /** Manifest registry key (may be package-qualified), as CRUD routes use. */\n registryKey: string;\n /** \\`<collection>\\` prefix of the required \\`<collection>.<op>\\` permission. */\n permissionCollection: string;\n ops: SyncApplyOp[];\n readonlyFields: string[];\n writableAllowlist: string[] | null;\n publicAccess: boolean | 'read';\n}\n\n// One entry per syncable model, keyed by the collection route segment its\n// generated CRUD routes use.\nconst SYNC_TARGETS: Record<string, SyncTargetConfig> = ${serializeTargets(targets)};\n\n// Fail-closed authorization (#1540): sync mutations require an authenticated\n// principal on \\`locals\\` unless the target model is \\`api: { public: true }\\`.\nfunction hasAuthenticatedPrincipal(locals: unknown): boolean {\n if (!locals || typeof locals !== 'object') return false;\n const l = locals as Record<string, unknown>;\n const isResolvedPrincipal = (v: unknown) =>\n typeof v === 'object' && v !== null;\n return (\n isResolvedPrincipal(l.user) ||\n isResolvedPrincipal(l.session) ||\n l.smrtAuth === true\n );\n}\n\nfunction readRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object'\n ? (value as Record<string, unknown>)\n : {};\n}\n\nfunction hasGrantedPermission(value: unknown, permission: string): boolean {\n if (Array.isArray(value)) return value.includes(permission);\n if (value instanceof Set) return value.has(permission);\n return false;\n}\n\n// Fail-closed operation permission (#2977, #3011): each applied op requires\n// the same \\`<collection>.<op>\\` permission the mirrored generated write route\n// enforces, read from the principal's session permission snapshot. A missing\n// snapshot denies; only an explicit tenancy super-admin bypass skips it.\nfunction hasOperationPermission(\n locals: unknown,\n permissionCollection: string,\n op: SyncApplyOp,\n): boolean {\n const l = readRecord(locals);\n const tenantContext = readRecord(l.tenantContext);\n if (tenantContext.superAdminBypass === true) return true;\n const permission = \\`\\${permissionCollection}.\\${op}\\`;\n return (\n hasGrantedPermission(l.permissions, permission) ||\n hasGrantedPermission(l.permissionSet, permission) ||\n hasGrantedPermission(l.smrtPermissions, permission) ||\n hasGrantedPermission(tenantContext.permissions, permission)\n );\n}\n\nexport const POST: RequestHandler = async ({ locals, request }) => {\n const authenticated = hasAuthenticatedPrincipal(locals);\n${anyTenantScoped ? ' establishTenantContext(locals);\\n' : ''}\n let body: unknown;\n try {\n body = await request.json();\n } catch {\n return json(\n { error: { code: 'invalid_batch', message: 'Invalid JSON body' } },\n { status: 400 },\n );\n }\n\n const outcome = await processSyncApplyBatch(body, {\n resolveTarget: async (segment: string): Promise<SyncApplyTarget | null> => {\n const target = SYNC_TARGETS[segment];\n if (!target) return null;\n // The verbatim manifest key, exactly as the generated CRUD routes pass\n // it — package-qualified keys stay qualified so same-simple-name\n // classes across packages resolve unambiguously.\n const collection = await getCollection(target.registryKey);\n return {\n objectName: target.registryKey,\n collection,\n isOpAllowed: (op: SyncApplyOp) => target.ops.includes(op),\n authorize: (op: SyncApplyOp) => {\n // \\`public: true\\` keeps its unauthenticated write semantics, as on\n // the generated CRUD routes.\n if (target.publicAccess === true) return 'ok' as const;\n if (!authenticated) return 'auth_required' as const;\n return hasOperationPermission(locals, target.permissionCollection, op)\n ? ('ok' as const)\n : ('forbidden' as const);\n },\n prepare: (payload: Record<string, unknown>) =>\n applySyncWritablePolicy(payload, {\n readonlyFields: target.readonlyFields,\n writableAllowlist: target.writableAllowlist,\n }),\n };\n },\n });\n\n return json(outcome.body, { status: outcome.status });\n};\n`;\n}\n\n/**\n * Generate `{routesDir}/sync/apply/+server.ts` for the manifest's syncable\n * models. Returns true when a route was written. Skips generation (with a\n * warning) if a non-generated file already occupies the path, and writes\n * nothing when the manifest has no syncable models.\n */\nexport function generateSyncApplyRoute(\n projectRoot: string,\n manifest: SmartObjectManifest,\n options: SvelteKitOptions,\n): boolean {\n const targets = collectSyncApplyTargets(manifest);\n if (targets.length === 0) {\n return false;\n }\n\n const routeDir = join(projectRoot, options.routesDir, 'sync', 'apply');\n const routePath = join(routeDir, '+server.ts');\n\n if (existsSync(routePath)) {\n const existing = readFileSync(routePath, 'utf-8');\n if (!existing.startsWith(AUTO_GENERATED_ROUTE_HEADER)) {\n console.warn(\n `[smrt] Skipping sync-apply route - ${routePath} exists and is not generated`,\n );\n return false;\n }\n }\n\n if (!existsSync(routeDir)) {\n mkdirSync(routeDir, { recursive: true });\n }\n\n writeFileSync(\n routePath,\n generateSyncApplyRouteTemplate(\n targets,\n resolveSvelteKitConfigImport(projectRoot, routeDir, options),\n ),\n 'utf-8',\n );\n console.log(`[smrt] Generated: ${routePath}`);\n return true;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-core",
3
- "version": "0.51.10",
3
+ "version": "0.51.14",
4
4
  "description": "Core AI agent framework with standardized collections, object-relational mapping, and code generators",
5
5
  "author": "HappyVertical",
6
6
  "type": "module",
@@ -150,26 +150,26 @@
150
150
  }
151
151
  },
152
152
  "dependencies": {
153
- "@happyvertical/ai": "^0.89.11",
154
- "@happyvertical/files": "^0.89.11",
155
- "@happyvertical/json": "^0.89.11",
156
- "@happyvertical/logger": "^0.89.11",
157
- "@happyvertical/smrt-config": "0.51.10",
158
- "@happyvertical/smrt-scanner": "0.51.10",
159
- "@happyvertical/smrt-types": "0.51.10",
160
- "@happyvertical/sql": "^0.89.11",
161
- "@happyvertical/utils": "^0.89.11",
162
- "@oxc-project/runtime": "^0.138.0",
153
+ "@happyvertical/ai": "^0.89.12",
154
+ "@happyvertical/files": "^0.89.12",
155
+ "@happyvertical/json": "^0.89.12",
156
+ "@happyvertical/logger": "^0.89.12",
157
+ "@happyvertical/smrt-config": "0.51.14",
158
+ "@happyvertical/smrt-scanner": "0.51.14",
159
+ "@happyvertical/smrt-types": "0.51.14",
160
+ "@happyvertical/sql": "^0.89.12",
161
+ "@happyvertical/utils": "^0.89.12",
162
+ "@oxc-project/runtime": "^0.150.0",
163
163
  "cheerio": "^1.2.0",
164
164
  "fast-glob": "3.3.3",
165
- "minimatch": "10.2.5",
166
- "oxc-transform": "0.138.0",
165
+ "minimatch": "10.2.6",
166
+ "oxc-transform": "0.150.0",
167
167
  "pluralize": "^8.0.0",
168
- "tsx": "^4.23.0",
169
- "yaml": "^2.9.0"
168
+ "tsx": "^4.23.13",
169
+ "yaml": "^2.9.1"
170
170
  },
171
171
  "peerDependencies": {
172
- "@huggingface/transformers": ">=3.0.0 <4.0.0",
172
+ "@huggingface/transformers": ">=3.0.0 <5.0.0",
173
173
  "@xenova/transformers": "^2.17.2"
174
174
  },
175
175
  "peerDependenciesMeta": {
@@ -181,18 +181,18 @@
181
181
  }
182
182
  },
183
183
  "devDependencies": {
184
- "@faker-js/faker": "^10.5.0",
185
- "@huggingface/transformers": "^3.8.1",
186
- "@sveltejs/kit": "^2.69.3",
187
- "@sveltejs/vite-plugin-svelte": "^7.1.2",
188
- "@types/node": "24.13.2",
184
+ "@faker-js/faker": "^10.6.0",
185
+ "@huggingface/transformers": "^4.3.0",
186
+ "@sveltejs/kit": "2.69.3",
187
+ "@sveltejs/vite-plugin-svelte": "^7.3.0",
188
+ "@types/node": "24.13.6",
189
189
  "@types/pluralize": "^0.0.33",
190
190
  "@xenova/transformers": "^2.17.2",
191
- "svelte": "^5.56.4",
191
+ "svelte": "^5.57.1",
192
192
  "svelte-check": "4.7.6",
193
193
  "typescript": "5.9.3",
194
194
  "vite": "8.1.4",
195
- "vite-plugin-dts": "4.5.4",
195
+ "vite-plugin-dts": "5.1.0",
196
196
  "vitest": "4.1.10"
197
197
  },
198
198
  "publishConfig": {