@cosmicdrift/kumiko-framework 0.387.0 → 0.388.0

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 (240) hide show
  1. package/AGENTS.md +6 -1
  2. package/agents/examples/anonymous-access.md +1 -1
  3. package/agents/examples/apex-landing.md +204 -0
  4. package/agents/examples/api-worker-split.md +64 -0
  5. package/agents/examples/app-config.md +14 -0
  6. package/agents/examples/auth-mfa.md +115 -0
  7. package/agents/examples/env-composition.md +28 -1
  8. package/agents/examples/legal-pages.md +1 -1
  9. package/agents/examples/prod-boot-options.md +16 -0
  10. package/agents/examples/rate-limiting.md +48 -0
  11. package/agents/examples/record-detail-layout.md +692 -0
  12. package/agents/examples/seed-migration.md +212 -0
  13. package/agents/examples/wizard-form.md +941 -0
  14. package/agents/examples/writeform-section.md +548 -0
  15. package/agents/features.md +19 -2
  16. package/agents/widgets.md +1 -0
  17. package/dist/api/client-error-route.js +8 -2
  18. package/dist/api/index.d.ts +3 -1
  19. package/dist/api/index.js +3 -1
  20. package/dist/api/request-context.d.ts +1 -1
  21. package/dist/api/request-context.js +1 -1
  22. package/dist/api/server-boot-checks.d.ts +10 -0
  23. package/dist/api/server-boot-checks.js +157 -0
  24. package/dist/api/server-event-dispatcher.d.ts +26 -0
  25. package/dist/api/server-event-dispatcher.js +208 -0
  26. package/dist/api/server-extra-routes.d.ts +11 -0
  27. package/dist/api/server-extra-routes.js +203 -0
  28. package/dist/api/server-middleware.d.ts +20 -0
  29. package/dist/api/server-middleware.js +211 -0
  30. package/dist/api/server-routes.d.ts +19 -0
  31. package/dist/api/server-routes.js +293 -0
  32. package/dist/api/server-websocket-routes.d.ts +4 -0
  33. package/dist/api/server-websocket-routes.js +108 -0
  34. package/dist/api/server-wiring.d.ts +32 -0
  35. package/dist/api/server-wiring.js +236 -0
  36. package/dist/api/server.d.ts +10 -15
  37. package/dist/api/server.js +48 -1251
  38. package/dist/api/session-cookies.js +4 -4
  39. package/dist/api/sse-route.js +16 -12
  40. package/dist/crypto/owner-transition-rekey.d.ts +5 -1
  41. package/dist/crypto/owner-transition-rekey.js +1 -1
  42. package/dist/crypto/pii-field-encryption.d.ts +5 -1
  43. package/dist/crypto/pii-field-encryption.js +3 -1
  44. package/dist/db/aggregate-sql.d.ts +21 -0
  45. package/dist/db/aggregate-sql.js +155 -0
  46. package/dist/db/closed-connection.d.ts +9 -0
  47. package/dist/db/closed-connection.js +95 -0
  48. package/dist/db/dialect.js +1 -1
  49. package/dist/db/distinct-on.d.ts +1 -1
  50. package/dist/db/driver-row-coercion.d.ts +2 -0
  51. package/dist/db/driver-row-coercion.js +186 -0
  52. package/dist/db/driver-value-preparation.d.ts +16 -0
  53. package/dist/db/driver-value-preparation.js +99 -0
  54. package/dist/db/entity-field-encryption.d.ts +1 -0
  55. package/dist/db/entity-field-encryption.js +1 -1
  56. package/dist/db/entity-table-meta.js +103 -110
  57. package/dist/db/event-store-executor-context.d.ts +2 -1
  58. package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
  59. package/dist/db/event-store-executor-read-aggregate.js +112 -0
  60. package/dist/db/event-store-executor-read-filter.d.ts +31 -0
  61. package/dist/db/event-store-executor-read-filter.js +203 -0
  62. package/dist/db/event-store-executor-read-paging.d.ts +14 -0
  63. package/dist/db/event-store-executor-read-paging.js +99 -0
  64. package/dist/db/event-store-executor-read-rows.d.ts +6 -0
  65. package/dist/db/event-store-executor-read-rows.js +32 -0
  66. package/dist/db/event-store-executor-read-search.d.ts +28 -0
  67. package/dist/db/event-store-executor-read-search.js +221 -0
  68. package/dist/db/event-store-executor-read-sort.d.ts +41 -0
  69. package/dist/db/event-store-executor-read-sort.js +130 -0
  70. package/dist/db/event-store-executor-read-where.d.ts +24 -0
  71. package/dist/db/event-store-executor-read-where.js +81 -0
  72. package/dist/db/event-store-executor-read.d.ts +2 -9
  73. package/dist/db/event-store-executor-read.js +48 -701
  74. package/dist/db/event-store-executor-write-steps.d.ts +20 -0
  75. package/dist/db/event-store-executor-write-steps.js +100 -0
  76. package/dist/db/event-store-executor-write.js +395 -499
  77. package/dist/db/index.d.ts +6 -2
  78. package/dist/db/index.js +5 -1
  79. package/dist/db/list-aggregate-labels.d.ts +14 -0
  80. package/dist/db/list-aggregate-labels.js +26 -0
  81. package/dist/db/migrate-generator.js +71 -86
  82. package/dist/db/migrate-runner.js +4 -1
  83. package/dist/db/query.d.ts +2 -15
  84. package/dist/db/query.js +8 -761
  85. package/dist/db/reference-data.js +6 -1
  86. package/dist/db/table-builder.js +113 -166
  87. package/dist/db/table-info.d.ts +10 -0
  88. package/dist/db/table-info.js +45 -0
  89. package/dist/db/temporal-value-guards.d.ts +6 -0
  90. package/dist/db/temporal-value-guards.js +15 -0
  91. package/dist/db/tenant-db-runner.d.ts +1 -0
  92. package/dist/db/tenant-db-runner.js +3 -0
  93. package/dist/db/tenant-db.js +39 -9
  94. package/dist/db/where-clause.d.ts +6 -0
  95. package/dist/db/where-clause.js +236 -0
  96. package/dist/derivatives/delete-stored-file.js +2 -1
  97. package/dist/engine/boot-validator/boot-check.js +1 -1
  98. package/dist/engine/boot-validator/detail-screens.js +2 -2
  99. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  100. package/dist/engine/boot-validator/entity-handler.js +17 -0
  101. package/dist/engine/boot-validator/form-derivations.js +35 -1
  102. package/dist/engine/boot-validator/i18n-keys.js +5 -3
  103. package/dist/engine/boot-validator/index.js +2 -0
  104. package/dist/engine/boot-validator/pii-retention.js +233 -155
  105. package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
  106. package/dist/engine/boot-validator/screens-actions.js +8 -1
  107. package/dist/engine/boot-validator/screens-dashboard.js +2 -5
  108. package/dist/engine/boot-validator/screens-forms.js +2 -6
  109. package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
  110. package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
  111. package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
  112. package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
  113. package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
  114. package/dist/engine/boot-validator/screens-kind-entity-list.js +228 -0
  115. package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
  116. package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
  117. package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
  118. package/dist/engine/boot-validator/screens-kind-projection-list.js +52 -0
  119. package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
  120. package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
  121. package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
  122. package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
  123. package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
  124. package/dist/engine/boot-validator/screens-lists.js +12 -9
  125. package/dist/engine/boot-validator/screens.d.ts +0 -2
  126. package/dist/engine/boot-validator/screens.js +99 -860
  127. package/dist/engine/build-app-schema.js +56 -96
  128. package/dist/engine/define-feature.js +9 -2
  129. package/dist/engine/define-handler.js +1 -0
  130. package/dist/engine/entity-handlers.d.ts +13 -0
  131. package/dist/engine/entity-handlers.js +10 -21
  132. package/dist/engine/entity-list-runtime-options.d.ts +8 -0
  133. package/dist/engine/entity-list-runtime-options.js +18 -0
  134. package/dist/engine/feature-ast/extractors/handlers.js +1 -0
  135. package/dist/engine/feature-ast/extractors/round2.js +25 -6
  136. package/dist/engine/feature-ast/patch.js +4 -182
  137. package/dist/engine/feature-ast/pattern-change-schema.d.ts +3 -0
  138. package/dist/engine/feature-ast/pattern-change-schema.js +1 -0
  139. package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
  140. package/dist/engine/feature-ast/pattern-id-matchers.js +210 -0
  141. package/dist/engine/feature-ast/patterns.d.ts +9 -0
  142. package/dist/engine/feature-ast/patterns.js +43 -51
  143. package/dist/engine/feature-ast/render.js +48 -87
  144. package/dist/engine/feature-entity-handlers.js +3 -0
  145. package/dist/engine/form-derivations.js +18 -7
  146. package/dist/engine/handler-field-references.js +1 -1
  147. package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
  148. package/dist/engine/project-app-schema-for-roles.js +6 -4
  149. package/dist/engine/registry-ingest.js +1 -1
  150. package/dist/engine/schema-builder.js +228 -212
  151. package/dist/engine/screen-helpers.js +1 -1
  152. package/dist/engine/screen-kind.d.ts +7 -0
  153. package/dist/engine/screen-kind.js +1 -0
  154. package/dist/engine/strip-disabled-features-from-app-schema.js +51 -14
  155. package/dist/engine/validate-projection-allowlist.js +2 -1
  156. package/dist/entrypoint/index.d.ts +4 -1
  157. package/dist/entrypoint/index.js +7 -1
  158. package/dist/errors/docs-url.d.ts +1 -1
  159. package/dist/errors/docs-url.js +22 -2
  160. package/dist/es-ops/context.js +1 -1
  161. package/dist/event-store/admin-api.js +1 -1
  162. package/dist/event-store/event-store.js +10 -4
  163. package/dist/files/bind-pending-file-refs.js +3 -3
  164. package/dist/files/create-file-ref-from-bytes.js +4 -1
  165. package/dist/files/file-handle.js +2 -3
  166. package/dist/files/file-routes.js +11 -8
  167. package/dist/files/resolve-content-type.js +43 -0
  168. package/dist/files/upload-ledger.js +7 -0
  169. package/dist/i18n/required-surface-keys.js +182 -206
  170. package/dist/internal/stack-support.d.ts +2 -1
  171. package/dist/internal/stack-support.js +1 -1
  172. package/dist/jobs/job-runner-context.d.ts +32 -0
  173. package/dist/jobs/job-runner-context.js +305 -0
  174. package/dist/jobs/job-runner-execution.d.ts +7 -0
  175. package/dist/jobs/job-runner-execution.js +160 -0
  176. package/dist/jobs/job-runner-gates.d.ts +19 -0
  177. package/dist/jobs/job-runner-gates.js +198 -0
  178. package/dist/jobs/job-runner-helpers.d.ts +17 -3
  179. package/dist/jobs/job-runner-helpers.js +61 -2
  180. package/dist/jobs/job-runner-scope.d.ts +44 -0
  181. package/dist/jobs/job-runner-scope.js +102 -0
  182. package/dist/jobs/job-runner-types.d.ts +12 -0
  183. package/dist/jobs/job-runner.d.ts +0 -1
  184. package/dist/jobs/job-runner.js +47 -657
  185. package/dist/observability/escape-hatch-report.d.ts +3 -0
  186. package/dist/observability/escape-hatch-report.js +12 -1
  187. package/dist/pipeline/cascade-handler.js +165 -138
  188. package/dist/pipeline/dispatch-query.js +10 -3
  189. package/dist/pipeline/dispatch-shared.d.ts +13 -3
  190. package/dist/pipeline/dispatch-shared.js +58 -710
  191. package/dist/pipeline/dispatch-write.js +296 -189
  192. package/dist/pipeline/event-dispatcher-admin.js +2 -1
  193. package/dist/pipeline/event-dispatcher-delivery.js +3 -5
  194. package/dist/pipeline/handler-context-auth.d.ts +9 -0
  195. package/dist/pipeline/handler-context-auth.js +54 -0
  196. package/dist/pipeline/handler-context-config.d.ts +14 -0
  197. package/dist/pipeline/handler-context-config.js +113 -0
  198. package/dist/pipeline/handler-context-db.d.ts +24 -0
  199. package/dist/pipeline/handler-context-db.js +149 -0
  200. package/dist/pipeline/handler-context-dispatch.d.ts +21 -0
  201. package/dist/pipeline/handler-context-dispatch.js +89 -0
  202. package/dist/pipeline/handler-context-events.d.ts +43 -0
  203. package/dist/pipeline/handler-context-events.js +237 -0
  204. package/dist/pipeline/handler-context-notify.d.ts +12 -0
  205. package/dist/pipeline/handler-context-notify.js +55 -0
  206. package/dist/pipeline/handler-context-observability.d.ts +7 -0
  207. package/dist/pipeline/handler-context-observability.js +21 -0
  208. package/dist/pipeline/handler-context-scope.d.ts +48 -0
  209. package/dist/pipeline/handler-context-scope.js +88 -0
  210. package/dist/pipeline/notify-scope.js +12 -1
  211. package/dist/pipeline/pre-handler-gates.js +20 -4
  212. package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
  213. package/dist/pipeline/pre-transaction-reservations.js +9 -3
  214. package/dist/pipeline/projection-rebuild.js +2 -1
  215. package/dist/schema-cli/apply-command.d.ts +2 -0
  216. package/dist/schema-cli/apply-command.js +97 -0
  217. package/dist/schema-cli/baseline-command.d.ts +2 -0
  218. package/dist/schema-cli/baseline-command.js +33 -0
  219. package/dist/schema-cli/generate-command.d.ts +2 -0
  220. package/dist/schema-cli/generate-command.js +90 -0
  221. package/dist/schema-cli/shared.d.ts +39 -0
  222. package/dist/schema-cli/shared.js +95 -0
  223. package/dist/schema-cli/status-command.d.ts +2 -0
  224. package/dist/schema-cli/status-command.js +73 -0
  225. package/dist/schema-cli/validate-boot-input.d.ts +5 -0
  226. package/dist/schema-cli/validate-boot-input.js +130 -0
  227. package/dist/schema-cli/validate-command.d.ts +2 -0
  228. package/dist/schema-cli/validate-command.js +130 -0
  229. package/dist/schema-cli.d.ts +2 -18
  230. package/dist/schema-cli.js +47 -604
  231. package/dist/search/meilisearch-adapter.js +2 -1
  232. package/dist/upgrade-cli.js +149 -121
  233. package/package.json +4 -4
  234. package/src/changes.json +310 -0
  235. package/src/scripts/codemod/feature-layout.ts +10 -2
  236. package/src/scripts/codemod/object-form.ts +41 -4
  237. package/src/scripts/codemod/package-merge.ts +58 -8
  238. package/src/scripts/codemod/pii-personal-migration.ts +175 -145
  239. package/src/scripts/codemod/testing-injection.ts +122 -42
  240. package/src/scripts/codemod/testing-move.ts +29 -7
@@ -7,6 +7,7 @@ import type { RelationDefinition } from "../types/relations.ts";
7
7
  import type { ScreenDefinition } from "../types/screen.ts";
8
8
  import type { TreeActionDef } from "../types/tree-node.ts";
9
9
  import type { WorkspaceDefinition } from "../types/workspace.ts";
10
+ import type { RawRefSentinel } from "./extractors/shared.ts";
10
11
  import type { PatternChange } from "./patch.ts";
11
12
  import type { SourceLocation } from "./source-location.ts";
12
13
  export type PatternChangeIssue = {
@@ -26,6 +27,7 @@ export declare const PATTERN_SCHEMAS_BY_KIND: {
26
27
  source: z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>;
27
28
  entityName: z.ZodString;
28
29
  definition: z.ZodType<EntityDefinition, unknown, z.core.$ZodTypeInternals<EntityDefinition, unknown>>;
30
+ backingTable: z.ZodOptional<z.ZodType<RawRefSentinel, unknown, z.core.$ZodTypeInternals<RawRefSentinel, unknown>>>;
29
31
  }, z.core.$strict>;
30
32
  relation: z.ZodObject<{
31
33
  kind: z.ZodLiteral<"relation">;
@@ -648,6 +650,7 @@ export declare const patternSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
648
650
  source: z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>;
649
651
  entityName: z.ZodString;
650
652
  definition: z.ZodType<EntityDefinition, unknown, z.core.$ZodTypeInternals<EntityDefinition, unknown>>;
653
+ backingTable: z.ZodOptional<z.ZodType<RawRefSentinel, unknown, z.core.$ZodTypeInternals<RawRefSentinel, unknown>>>;
651
654
  }, z.core.$strict>, z.ZodObject<{
652
655
  kind: z.ZodLiteral<"relation">;
653
656
  source: z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>;
@@ -332,6 +332,7 @@ const entitySchema = z
332
332
  source: sourceFieldSchema,
333
333
  entityName: z.string(),
334
334
  definition: entityDefinitionSchema,
335
+ backingTable: passthroughSchema().optional(),
335
336
  })
336
337
  .strict();
337
338
  const relationSchema = z
@@ -0,0 +1,7 @@
1
+ import { type CallExpression } from "ts-morph";
2
+ import type { PatternId } from "./patch.ts";
3
+ import type { MembersByKind } from "./patterns.ts";
4
+ type PatternIdKind = PatternId["kind"];
5
+ type PatternIdOfKind = MembersByKind<PatternId>;
6
+ export declare function callMatchesId<K extends PatternIdKind>(call: CallExpression, kind: K, id: PatternIdOfKind[K]): boolean;
7
+ export {};
@@ -0,0 +1,210 @@
1
+ import { SyntaxKind } from "ts-morph";
2
+ import { readNameLiteral, readNameOrRef, resolveSameFileObjectLiteral, } from "./extractors/shared.js";
3
+ const PATTERN_ID_MATCHERS = {
4
+ requires: matchSingletonCall,
5
+ optionalRequires: matchSingletonCall,
6
+ readsConfig: matchSingletonCall,
7
+ systemScope: matchSingletonCall,
8
+ toggleable: matchSingletonCall,
9
+ describe: matchSingletonCall,
10
+ uiHints: matchSingletonCall,
11
+ config: matchSingletonCall,
12
+ translations: matchSingletonCall,
13
+ authClaims: matchSingletonCall,
14
+ treeActions: matchSingletonCall,
15
+ entity: matchEntityCall,
16
+ relation: matchRelationCall,
17
+ nav: matchIdPropertyCall,
18
+ workspace: matchIdPropertyCall,
19
+ screen: matchIdPropertyCall,
20
+ writeHandler: matchHandlerCall,
21
+ queryHandler: matchHandlerCall,
22
+ streamHandler: matchHandlerCall,
23
+ hook: matchHookCall,
24
+ metric: matchShortNameCall,
25
+ secret: matchShortNameCall,
26
+ secretNamespace: matchShortNameCall,
27
+ claimKey: matchShortNameCall,
28
+ referenceData: matchReferenceDataCall,
29
+ useExtension: matchUseExtensionCall,
30
+ job: matchJobCall,
31
+ notification: matchNotificationCall,
32
+ httpRoute: matchHttpRouteCall,
33
+ projection: matchProjectionCall,
34
+ multiStreamProjection: matchProjectionCall,
35
+ defineEvent: matchDefineEventCall,
36
+ extendsRegistrar: matchExtendsRegistrarCall,
37
+ "ai.generate": matchAiStepCall,
38
+ "ai.extract": matchAiStepCall,
39
+ "ai.classify": matchAiStepCall,
40
+ };
41
+ export function callMatchesId(call, kind, id) {
42
+ return PATTERN_ID_MATCHERS[kind](call, id);
43
+ }
44
+ // Singletons: kind alone identifies the call.
45
+ function matchSingletonCall() {
46
+ return true;
47
+ }
48
+ function matchEntityCall(call, id) {
49
+ return (matchFirstArgString(call, id.entityName) || matchObjectProperty(call, "name", id.entityName));
50
+ }
51
+ // Positional: r.relation(entity, name, def) | Object: { entity, name, ... }
52
+ function matchRelationCall(call, id) {
53
+ if (matchFirstArgString(call, id.entityName)) {
54
+ return matchArgString(call, 1, id.relationName);
55
+ }
56
+ return (matchObjectProperty(call, "entity", id.entityName) &&
57
+ matchObjectProperty(call, "name", id.relationName));
58
+ }
59
+ function matchIdPropertyCall(call, id) {
60
+ return matchObjectProperty(call, "id", id.id);
61
+ }
62
+ function matchHandlerCall(call, id) {
63
+ return (matchFirstArgString(call, id.handlerName) || matchObjectProperty(call, "name", id.handlerName));
64
+ }
65
+ // Positional: r.hook(type, target, fn) | Object: { type, target }
66
+ function matchHookCall(call, id) {
67
+ if (typeof id.target === "object") {
68
+ return matchHookAllOfCall(call, id.hookType, id.target.allOf);
69
+ }
70
+ if (matchFirstArgString(call, id.hookType)) {
71
+ return matchArgNameOrRef(call, 1, id.target);
72
+ }
73
+ return (matchObjectProperty(call, "type", id.hookType) && matchObjectProperty(call, "target", id.target));
74
+ }
75
+ // Entity-wide { allOf } target — only ever appears as the object
76
+ // literal { allOf: entity } (positional or nested in the object
77
+ // form), never as a bare string, so check both call shapes.
78
+ function matchHookAllOfCall(call, hookType, allOfEntity) {
79
+ if (matchFirstArgString(call, hookType)) {
80
+ return matchAllOfArg(call.getArguments()[1], allOfEntity);
81
+ }
82
+ return (matchObjectProperty(call, "type", hookType) &&
83
+ matchObjectAllOfProperty(call, "target", allOfEntity));
84
+ }
85
+ function matchShortNameCall(call, id) {
86
+ return matchFirstArgString(call, id.shortName) || matchObjectProperty(call, "name", id.shortName);
87
+ }
88
+ function matchReferenceDataCall(call, id) {
89
+ return (matchFirstArgString(call, id.entityName) || matchObjectProperty(call, "entity", id.entityName));
90
+ }
91
+ // Positional: r.useExtension(name, entity) | Object: { name, entity }
92
+ function matchUseExtensionCall(call, id) {
93
+ if (matchFirstArgString(call, id.extensionName)) {
94
+ return matchArgNameOrRef(call, 1, id.entityName);
95
+ }
96
+ return (matchObjectProperty(call, "name", id.extensionName) &&
97
+ matchObjectProperty(call, "entity", id.entityName));
98
+ }
99
+ function matchJobCall(call, id) {
100
+ return matchFirstArgString(call, id.jobName) || matchObjectProperty(call, "name", id.jobName);
101
+ }
102
+ function matchNotificationCall(call, id) {
103
+ return (matchFirstArgString(call, id.notificationName) ||
104
+ matchObjectProperty(call, "name", id.notificationName));
105
+ }
106
+ // Object form only; positional doesn't apply.
107
+ function matchHttpRouteCall(call, id) {
108
+ return (matchObjectProperty(call, "method", id.method) && matchObjectProperty(call, "path", id.path));
109
+ }
110
+ function matchProjectionCall(call, id) {
111
+ return matchObjectProperty(call, "name", id.name);
112
+ }
113
+ function matchDefineEventCall(call, id) {
114
+ return matchFirstArgString(call, id.eventName) || matchObjectProperty(call, "name", id.eventName);
115
+ }
116
+ function matchExtendsRegistrarCall(call, id) {
117
+ return matchFirstArgString(call, id.extensionName);
118
+ }
119
+ function matchAiStepCall(call, id) {
120
+ return readAiStepKey(call) === id.stepKey;
121
+ }
122
+ function readAiStepKey(call) {
123
+ const arg = call.getArguments()[0];
124
+ if (!arg)
125
+ return undefined;
126
+ const obj = resolveSameFileObjectLiteral(arg);
127
+ if (!obj)
128
+ return undefined;
129
+ const prop = obj.getProperty("stepKey");
130
+ if (!prop)
131
+ return undefined;
132
+ const assign = prop.asKind(SyntaxKind.PropertyAssignment);
133
+ if (assign) {
134
+ const init = assign.getInitializer();
135
+ if (!init)
136
+ return undefined;
137
+ return readNameLiteral(init);
138
+ }
139
+ const shorthand = prop.asKind(SyntaxKind.ShorthandPropertyAssignment);
140
+ if (shorthand)
141
+ return readNameLiteral(shorthand.getNameNode());
142
+ return undefined;
143
+ }
144
+ // Resolves an argument the same way the extractors do (readNameLiteral):
145
+ // a string literal directly, or a bare identifier following its
146
+ // declaration (same-file or imported) to a string-literal initializer —
147
+ // the dominant naming style in the framework's own bundled-features
148
+ // (`r.entity(ENTITY, ...)`, `r.useExtension(EXT_X, ...)`).
149
+ //
150
+ // Narrow on purpose: every kind's arg-0 (and relation's arg-1) is parsed
151
+ // via readNameLiteral, never readNameOrRef (see round2.ts/round3.ts) —
152
+ // widening this shared helper to readNameOrRef would let an object-form
153
+ // call's first argument (an ObjectLiteralExpression) match here too,
154
+ // short-circuiting the object-form branch in callMatchesId. Positions
155
+ // where the parser itself accepts an inline `{ name: "..." }` ref use
156
+ // `matchArgNameOrRef` below instead.
157
+ function matchArgString(call, index, expected) {
158
+ const arg = call.getArguments()[index];
159
+ if (!arg)
160
+ return false;
161
+ return readNameLiteral(arg) === expected;
162
+ }
163
+ // @wrapper-known semantic-alias
164
+ function matchFirstArgString(call, expected) {
165
+ return matchArgString(call, 0, expected);
166
+ }
167
+ // Like matchArgString, but via readNameOrRef — for the specific positional
168
+ // slots where the parser accepts an inline `{ name: "..." }` object ref in
169
+ // addition to a literal/identifier (useExtension's entity arg, hook's
170
+ // target arg; see round3.ts:445 / hooks.ts:75). Not a drop-in replacement
171
+ // for matchArgString: applying it to an arg-0 position would match an
172
+ // object-form call's first (and only) argument, see the comment above.
173
+ function matchArgNameOrRef(call, index, expected) {
174
+ const arg = call.getArguments()[index];
175
+ if (!arg)
176
+ return false;
177
+ return readNameOrRef(arg) === expected;
178
+ }
179
+ // Object-form property values are resolved via readNameOrRef, not the
180
+ // narrower readNameLiteral — some properties (useExtension's `entity`,
181
+ // hook's `target`/`allOf`) accept an inline `{ name: "..." }` ref in the
182
+ // parser (see extractors/round3.ts, extractors/hooks.ts), not just a
183
+ // literal or identifier. readNameOrRef is a superset of readNameLiteral,
184
+ // so this is safe for every other property too.
185
+ function matchObjectProperty(call, propName, expected) {
186
+ const obj = call.getArguments()[0]?.asKind(SyntaxKind.ObjectLiteralExpression);
187
+ if (!obj)
188
+ return false;
189
+ const init = obj.getProperty(propName)?.asKind(SyntaxKind.PropertyAssignment)?.getInitializer();
190
+ if (!init)
191
+ return false;
192
+ return readNameOrRef(init) === expected;
193
+ }
194
+ // Matches an r.hook `{ allOf: entity }` target — as the arg node directly
195
+ // (positional call form) or as a nested object property (object call form).
196
+ function matchAllOfArg(node, expectedEntity) {
197
+ const obj = node?.asKind(SyntaxKind.ObjectLiteralExpression);
198
+ const init = obj?.getProperty("allOf")?.asKind(SyntaxKind.PropertyAssignment)?.getInitializer();
199
+ if (!init)
200
+ return false;
201
+ return readNameOrRef(init) === expectedEntity;
202
+ }
203
+ function matchObjectAllOfProperty(call, propName, expectedEntity) {
204
+ const obj = call.getArguments()[0]?.asKind(SyntaxKind.ObjectLiteralExpression);
205
+ const propInit = obj
206
+ ?.getProperty(propName)
207
+ ?.asKind(SyntaxKind.PropertyAssignment)
208
+ ?.getInitializer();
209
+ return matchAllOfArg(propInit, expectedEntity);
210
+ }
@@ -18,6 +18,7 @@ export type EntityPattern = {
18
18
  readonly source: SourceLocation;
19
19
  readonly entityName: string;
20
20
  readonly definition: EntityDefinition;
21
+ readonly backingTable?: RawRefSentinel;
21
22
  };
22
23
  export type RelationPattern = {
23
24
  readonly kind: "relation";
@@ -304,6 +305,14 @@ export type UnknownPattern = {
304
305
  };
305
306
  export type FeaturePattern = EntityPattern | RelationPattern | NavPattern | WorkspacePattern | ConfigPattern | TranslationsPattern | RequiresPattern | OptionalRequiresPattern | SystemScopePattern | ToggleablePattern | DescribePattern | UiHintsPattern | MetricPattern | SecretPattern | SecretNamespacePattern | ClaimKeyPattern | ReferenceDataPattern | ReadsConfigPattern | UseExtensionPattern | UsesApiPattern | ExposesApiPattern | TreeActionsPattern | ScreenPattern | WriteHandlerPattern | QueryHandlerPattern | StreamHandlerPattern | HookPattern | JobPattern | NotificationPattern | AuthClaimsPattern | HttpRoutePattern | ProjectionPattern | MultiStreamProjectionPattern | DefineEventPattern | ExtendsRegistrarPattern | EnvSchemaPattern | AiGeneratePattern | AiExtractPattern | AiClassifyPattern | UnknownPattern;
306
307
  export type FeaturePatternKind = FeaturePattern["kind"];
308
+ export type MembersByKind<U extends {
309
+ readonly kind: string;
310
+ }> = {
311
+ readonly [K in U["kind"]]: Extract<U, {
312
+ readonly kind: K;
313
+ }>;
314
+ };
315
+ export type FeaturePatternOfKind = MembersByKind<FeaturePattern>;
307
316
  export type Editability = "static" | "mixed" | "opaque";
308
317
  export declare function getEditability(pattern: FeaturePattern): Editability;
309
318
  export {};
@@ -59,56 +59,48 @@
59
59
  // against this constant to decide "form vs read-only span" — the actual
60
60
  // SourceLocation lives at the matching path in `opaqueProps`.
61
61
  export const SCREEN_OPAQUE_MARKER = "$opaque";
62
+ const EDITABILITY_BY_KIND = {
63
+ entity: "static",
64
+ relation: "static",
65
+ nav: "static",
66
+ workspace: "static",
67
+ config: "static",
68
+ translations: "static",
69
+ requires: "static",
70
+ optionalRequires: "static",
71
+ systemScope: "static",
72
+ toggleable: "static",
73
+ describe: "static",
74
+ metric: "static",
75
+ secret: "static",
76
+ secretNamespace: "static",
77
+ claimKey: "static",
78
+ referenceData: "static",
79
+ readsConfig: "static",
80
+ useExtension: "static",
81
+ usesApi: "static",
82
+ exposesApi: "static",
83
+ treeActions: "static",
84
+ screen: "mixed",
85
+ writeHandler: "mixed",
86
+ queryHandler: "mixed",
87
+ streamHandler: "mixed",
88
+ hook: "mixed",
89
+ job: "mixed",
90
+ notification: "mixed",
91
+ httpRoute: "mixed",
92
+ projection: "mixed",
93
+ multiStreamProjection: "mixed",
94
+ defineEvent: "mixed",
95
+ "ai.generate": "mixed",
96
+ "ai.extract": "mixed",
97
+ "ai.classify": "mixed",
98
+ authClaims: "opaque",
99
+ extendsRegistrar: "opaque",
100
+ envSchema: "opaque",
101
+ uiHints: "opaque",
102
+ unknown: "opaque",
103
+ };
62
104
  export function getEditability(pattern) {
63
- switch (pattern.kind) {
64
- case "entity":
65
- case "relation":
66
- case "nav":
67
- case "workspace":
68
- case "config":
69
- case "translations":
70
- case "requires":
71
- case "optionalRequires":
72
- case "systemScope":
73
- case "toggleable":
74
- case "describe":
75
- case "metric":
76
- case "secret":
77
- case "secretNamespace":
78
- case "claimKey":
79
- case "referenceData":
80
- case "readsConfig":
81
- case "useExtension":
82
- case "usesApi":
83
- case "exposesApi":
84
- case "treeActions":
85
- return "static";
86
- case "screen":
87
- case "writeHandler":
88
- case "queryHandler":
89
- case "streamHandler":
90
- case "hook":
91
- case "job":
92
- case "notification":
93
- case "httpRoute":
94
- case "projection":
95
- case "multiStreamProjection":
96
- case "defineEvent":
97
- case "ai.generate":
98
- case "ai.extract":
99
- case "ai.classify":
100
- return "mixed";
101
- case "authClaims":
102
- case "extendsRegistrar":
103
- case "envSchema":
104
- case "uiHints":
105
- case "unknown":
106
- return "opaque";
107
- default: {
108
- // Exhaustiveness guard — adding a new pattern kind without
109
- // updating this switch produces a compile error here.
110
- const _exhaustive = pattern;
111
- return _exhaustive;
112
- }
113
- }
105
+ return EDITABILITY_BY_KIND[pattern.kind];
114
106
  }
@@ -30,92 +30,52 @@ export const VERSION_HEADER = `// kumiko-feature-version: ${FEATURE_FILE_VERSION
30
30
  * trailing newline; callers compose statements with their own joiner.
31
31
  */
32
32
  export function renderPattern(pattern) {
33
- switch (pattern.kind) {
34
- case "requires":
35
- return renderRequires(pattern);
36
- case "optionalRequires":
37
- return renderOptionalRequires(pattern);
38
- case "readsConfig":
39
- return renderReadsConfig(pattern);
40
- case "systemScope":
41
- return renderSystemScope(pattern);
42
- case "toggleable":
43
- return renderToggleable(pattern);
44
- case "describe":
45
- return renderDescribe(pattern);
46
- case "uiHints":
47
- return renderUiHints(pattern);
48
- case "entity":
49
- return renderEntity(pattern);
50
- case "relation":
51
- return renderRelation(pattern);
52
- case "nav":
53
- return renderNav(pattern);
54
- case "workspace":
55
- return renderWorkspace(pattern);
56
- case "config":
57
- return renderConfig(pattern);
58
- case "translations":
59
- return renderTranslations(pattern);
60
- case "metric":
61
- return renderMetric(pattern);
62
- case "secret":
63
- return renderSecret(pattern);
64
- case "secretNamespace":
65
- return renderSecretNamespace(pattern);
66
- case "claimKey":
67
- return renderClaimKey(pattern);
68
- case "referenceData":
69
- return renderReferenceData(pattern);
70
- case "useExtension":
71
- return renderUseExtension(pattern);
72
- case "screen":
73
- return renderScreen(pattern);
74
- case "writeHandler":
75
- return renderWriteHandler(pattern);
76
- case "queryHandler":
77
- return renderQueryHandler(pattern);
78
- case "streamHandler":
79
- return renderStreamHandler(pattern);
80
- case "hook":
81
- return renderHook(pattern);
82
- case "job":
83
- return renderJob(pattern);
84
- case "notification":
85
- return renderNotification(pattern);
86
- case "authClaims":
87
- return renderAuthClaims(pattern);
88
- case "httpRoute":
89
- return renderHttpRoute(pattern);
90
- case "projection":
91
- return renderProjection(pattern);
92
- case "multiStreamProjection":
93
- return renderMultiStreamProjection(pattern);
94
- case "defineEvent":
95
- return renderDefineEvent(pattern);
96
- case "extendsRegistrar":
97
- return renderExtendsRegistrar(pattern);
98
- case "usesApi":
99
- return renderUsesApi(pattern);
100
- case "exposesApi":
101
- return renderExposesApi(pattern);
102
- case "treeActions":
103
- return renderTreeActions(pattern);
104
- case "envSchema":
105
- return renderEnvSchema(pattern);
106
- case "ai.generate":
107
- return renderAiGenerate(pattern);
108
- case "ai.extract":
109
- return renderAiExtract(pattern);
110
- case "ai.classify":
111
- return renderAiClassify(pattern);
112
- case "unknown":
113
- return renderUnknown(pattern);
114
- default: {
115
- const _exhaustive = pattern;
116
- return _exhaustive;
117
- }
118
- }
33
+ return renderPatternOfKind(pattern.kind, pattern);
34
+ }
35
+ const PATTERN_RENDERERS = {
36
+ requires: renderRequires,
37
+ optionalRequires: renderOptionalRequires,
38
+ readsConfig: renderReadsConfig,
39
+ systemScope: renderSystemScope,
40
+ toggleable: renderToggleable,
41
+ describe: renderDescribe,
42
+ uiHints: renderUiHints,
43
+ entity: renderEntity,
44
+ relation: renderRelation,
45
+ nav: renderNav,
46
+ workspace: renderWorkspace,
47
+ config: renderConfig,
48
+ translations: renderTranslations,
49
+ metric: renderMetric,
50
+ secret: renderSecret,
51
+ secretNamespace: renderSecretNamespace,
52
+ claimKey: renderClaimKey,
53
+ referenceData: renderReferenceData,
54
+ useExtension: renderUseExtension,
55
+ screen: renderScreen,
56
+ writeHandler: renderWriteHandler,
57
+ queryHandler: renderQueryHandler,
58
+ streamHandler: renderStreamHandler,
59
+ hook: renderHook,
60
+ job: renderJob,
61
+ notification: renderNotification,
62
+ authClaims: renderAuthClaims,
63
+ httpRoute: renderHttpRoute,
64
+ projection: renderProjection,
65
+ multiStreamProjection: renderMultiStreamProjection,
66
+ defineEvent: renderDefineEvent,
67
+ extendsRegistrar: renderExtendsRegistrar,
68
+ usesApi: renderUsesApi,
69
+ exposesApi: renderExposesApi,
70
+ treeActions: renderTreeActions,
71
+ envSchema: renderEnvSchema,
72
+ "ai.generate": renderAiGenerate,
73
+ "ai.extract": renderAiExtract,
74
+ "ai.classify": renderAiClassify,
75
+ unknown: renderUnknown,
76
+ };
77
+ function renderPatternOfKind(kind, pattern) {
78
+ return PATTERN_RENDERERS[kind](pattern);
119
79
  }
120
80
  // =============================================================================
121
81
  // JSON-Like Value Renderer — emits TypeScript-source-compatible literals.
@@ -206,7 +166,8 @@ function renderEntity(p) {
206
166
  // losing `name` to the sentinel shortcut — spread the reference verbatim
207
167
  // next to the name instead.
208
168
  if (isRawRefSentinel(p.definition)) {
209
- return `r.entity({ name: ${JSON.stringify(p.entityName)}, ...${p.definition.__raw} });`;
169
+ const backingTable = p.backingTable ? `, backingTable: ${p.backingTable.__raw}` : "";
170
+ return `r.entity({ name: ${JSON.stringify(p.entityName)}, ...${p.definition.__raw}${backingTable} });`;
210
171
  }
211
172
  const merged = { name: p.entityName, ...p.definition };
212
173
  return `r.entity(${renderValue(merged)});`;
@@ -130,6 +130,9 @@ export function buildEntityHandlerMethods(state, name) {
130
130
  ...(def.unsafeSkipTransitionGuard && { unsafeSkipTransitionGuard: true }),
131
131
  ...(def.rateLimit && { rateLimit: def.rateLimit }),
132
132
  ...(def.additionalRateLimits && { additionalRateLimits: def.additionalRateLimits }),
133
+ ...(def.rateLimitCost && {
134
+ rateLimitCost: def.rateLimitCost, // @cast-boundary engine-bridge
135
+ }),
133
136
  ...(def.escapeHatch && { escapeHatch: def.escapeHatch }),
134
137
  ...transactionPhaseSlots(def),
135
138
  ...(def.tenantlessAnonymous && { tenantlessAnonymous: true }),
@@ -101,17 +101,30 @@ function sumListCell(rows, listField, cell) {
101
101
  return sum;
102
102
  }
103
103
  function targetCurrency(field, current, sourceCurrency) {
104
+ // The client value is last: it must never override what the field or its sources dictate.
105
+ if (field.type === "money" && field.currency.kind === "literal")
106
+ return field.currency.code;
107
+ if (sourceCurrency !== undefined)
108
+ return sourceCurrency;
104
109
  if (isPlainObject(current) &&
105
110
  typeof current["currency"] === "string" &&
106
111
  current["currency"] !== "") {
107
112
  return current["currency"];
108
113
  }
109
- if (sourceCurrency !== undefined)
110
- return sourceCurrency;
111
- if (field.type === "money" && field.currency.kind === "literal")
112
- return field.currency.code;
113
114
  return undefined;
114
115
  }
116
+ function computeOrdinary(derivation, values, fields) {
117
+ const operands = derivation.from.map((name) => readOperand(values, fields, name));
118
+ const currencies = new Set(operands.flatMap((operand) => operand.currency ?? []));
119
+ const sourceCurrency = currencies.values().next().value;
120
+ // Operands in different currencies have no meaningful sum.
121
+ if (currencies.size > 1)
122
+ return { computed: undefined, sourceCurrency };
123
+ return {
124
+ computed: computeDerivedCellValue(derivation.op, operands.map((operand) => operand.value)),
125
+ sourceCurrency,
126
+ };
127
+ }
115
128
  /** Recomputes every derived head field of an action form from its inputs.
116
129
  * Pure and client-safe: the renderer runs it live, the server runs it
117
130
  * again (`withFormDerivations`) and overwrites whatever the client sent.
@@ -148,9 +161,7 @@ export function applyFormDerivations(values, screen) {
148
161
  ordinary = derivation;
149
162
  }
150
163
  if (ordinary !== undefined) {
151
- const operands = ordinary.from.map((name) => readOperand(result, screen.fields, name));
152
- sourceCurrency = operands.find((operand) => operand.currency !== undefined)?.currency;
153
- computed = computeDerivedCellValue(ordinary.op, operands.map((operand) => operand.value));
164
+ ({ computed, sourceCurrency } = computeOrdinary(ordinary, result, screen.fields));
154
165
  }
155
166
  if (computed === undefined) {
156
167
  delete result[target];
@@ -5,7 +5,7 @@ const MAX_WRAPPER_DEPTH = 8;
5
5
  const MAX_NESTING_DEPTH = 8;
6
6
  const referenceMetaSchema = z.union([
7
7
  z.string().min(1),
8
- z.object({ entity: z.string().min(1), by: z.string().min(1).optional() }),
8
+ z.strictObject({ entity: z.string().min(1), by: z.string().min(1).optional() }),
9
9
  ]);
10
10
  function rawReferenceMeta(field) {
11
11
  const meta = zodMeta(field);
@@ -1,2 +1,4 @@
1
1
  import type { AppSchema } from "../ui-types/app-schema.ts";
2
+ import type { NavDefinition } from "./types/nav.ts";
2
3
  export declare function projectAppSchemaForRoles(schema: AppSchema, roles: readonly string[]): AppSchema;
4
+ export declare function isPureGroupingNav(nav: Pick<NavDefinition, "screen" | "target" | "provider">): boolean;
@@ -27,7 +27,7 @@
27
27
  // @cosmicdrift/kumiko-types/handlers) — an unset `access` here means every
28
28
  // signed-in user may see the entry, the opposite stance from `hasAccess`.
29
29
  //
30
- // Reference-site coverage (every place boot-validator/screens.ts resolves a
30
+ // Reference-site coverage (every place boot-validator/screens*.ts resolves a
31
31
  // screen target is a site this file also projects):
32
32
  // - nav.screen / nav.parent (author-registered AND settings-hub-generated
33
33
  // navs — the latter use short ids relative to the synthetic "config"
@@ -204,16 +204,18 @@ function isNavDeniedOrDead(ctx, screenAccessByQn, keptScreenQns, roles) {
204
204
  const effectiveAccess = ctx.nav.access ?? screenAccessByQn.get(ctx.resolvedScreenQn);
205
205
  return !isUiAccessGranted(effectiveAccess, roles);
206
206
  }
207
+ export function isPureGroupingNav(nav) {
208
+ return nav.screen === undefined && nav.target === undefined && nav.provider !== true;
209
+ }
207
210
  function isDroppedByPropagation(ctx, dropped, childrenByParent) {
208
211
  if (ctx.resolvedParentQn !== undefined && dropped.has(ctx.resolvedParentQn))
209
212
  return true;
210
- const isPureGroupingNode = ctx.nav.screen === undefined && ctx.nav.target === undefined && ctx.nav.provider !== true;
211
- if (!isPureGroupingNode)
213
+ if (!isPureGroupingNav(ctx.nav))
212
214
  return false;
213
215
  const children = childrenByParent.get(ctx.qn) ?? [];
214
216
  return children.length > 0 && children.every((child) => dropped.has(child.qn));
215
217
  }
216
- // --- screen-target resolution helpers (mirror boot-validator/screens.ts) ---
218
+ // --- screen-target resolution helpers (mirror boot-validator/screens*.ts) ---
217
219
  function isScreenTargetKept(featureName, target, keptScreenQns) {
218
220
  return keptScreenQns.has(resolveScreenTargetQn(featureName, target));
219
221
  }
@@ -16,7 +16,7 @@ export function populateFeatureCore(state, feature) {
16
16
  throw new Error(`Duplicate entity: "${name}" (registered by multiple features)`);
17
17
  }
18
18
  state.entityMap.set(name, entity);
19
- const physical = resolveTableName(name, entity, feature.name);
19
+ const physical = resolveTableName(name, entity, undefined);
20
20
  const clash = state.physicalTableOwners.get(physical);
21
21
  if (clash?.kind === "store") {
22
22
  throw new Error(`Entity "${name}" (feature "${feature.name}") has physical table "${physical}" which ` +