@cosmicdrift/kumiko-framework 0.361.0 → 0.363.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 (126) hide show
  1. package/dist/api/auth-middleware.js +7 -5
  2. package/dist/api/redis-sse-broker.js +10 -8
  3. package/dist/api/routes.js +2 -2
  4. package/dist/api/server.js +3 -0
  5. package/dist/bun-db/query.d.ts +6 -4
  6. package/dist/bun-db/query.js +66 -92
  7. package/dist/crypto/kek-source.js +5 -3
  8. package/dist/crypto/pii-field-encryption.d.ts +4 -1
  9. package/dist/crypto/pii-field-encryption.js +2 -2
  10. package/dist/db/apply-entity-event.js +2 -2
  11. package/dist/db/cursor.js +2 -1
  12. package/dist/db/dialect.d.ts +1 -0
  13. package/dist/db/dialect.js +28 -13
  14. package/dist/db/entity-table-from-registry.js +1 -1
  15. package/dist/db/entity-table-meta.js +5 -13
  16. package/dist/db/event-store-executor-context.js +6 -6
  17. package/dist/db/event-store-executor-read.js +9 -9
  18. package/dist/db/migrate-generator.js +5 -3
  19. package/dist/db/migrate-runner.js +31 -35
  20. package/dist/db/parent-ref-clause.js +2 -2
  21. package/dist/db/queries/backfill-pii.js +11 -9
  22. package/dist/db/queries/es-ops.js +1 -1
  23. package/dist/db/queries/event-consumer.d.ts +6 -6
  24. package/dist/db/queries/event-consumer.js +17 -17
  25. package/dist/db/queries/event-store-admin.js +6 -6
  26. package/dist/db/queries/event-store-transfer.js +2 -2
  27. package/dist/db/queries/event-store.d.ts +1 -1
  28. package/dist/db/queries/event-store.js +2 -2
  29. package/dist/db/queries/pii-backfill-state.js +2 -2
  30. package/dist/db/queries/projection-rebuild.d.ts +1 -1
  31. package/dist/db/queries/raw-sql.js +1 -1
  32. package/dist/db/queries/test-stack.js +1 -1
  33. package/dist/db/reference-data.js +3 -5
  34. package/dist/db/table-builder.js +6 -4
  35. package/dist/db/table-introspection.d.ts +16 -0
  36. package/dist/db/table-introspection.js +76 -0
  37. package/dist/db/tenant-db.d.ts +3 -2
  38. package/dist/db/tenant-db.js +4 -10
  39. package/dist/derivatives/variant-key.js +2 -1
  40. package/dist/engine/boot-validator/access-declarations.js +29 -18
  41. package/dist/engine/boot-validator/card-meta-overflow.js +5 -2
  42. package/dist/engine/boot-validator/entity-list-screens.js +1 -1
  43. package/dist/engine/boot-validator/pii-retention.js +1 -1
  44. package/dist/engine/boot-validator/screens.js +2 -1
  45. package/dist/engine/build-app-schema.js +1 -0
  46. package/dist/engine/changeset-changes.js +3 -3
  47. package/dist/engine/define-handler.js +3 -6
  48. package/dist/engine/embedded-derived.js +10 -6
  49. package/dist/engine/extensions/escape-hatch-usage.js +2 -1
  50. package/dist/engine/factories.js +5 -6
  51. package/dist/engine/feature-ast/extractors/events.js +43 -33
  52. package/dist/engine/feature-ast/extractors/round3.d.ts +2 -2
  53. package/dist/engine/feature-ast/extractors/round3.js +4 -4
  54. package/dist/engine/feature-ast/patcher.d.ts +3 -3
  55. package/dist/engine/feature-ast/patcher.js +3 -3
  56. package/dist/engine/feature-ast/pattern-change-schema.d.ts +8 -8
  57. package/dist/engine/feature-ast/pattern-change-schema.js +3 -3
  58. package/dist/engine/feature-ast/patterns.d.ts +3 -3
  59. package/dist/engine/feature-ast/render.js +6 -3
  60. package/dist/engine/feature-changelog.d.ts +2 -1
  61. package/dist/engine/feature-changelog.js +8 -6
  62. package/dist/engine/feature-config-events-jobs.d.ts +17 -7
  63. package/dist/engine/feature-config-events-jobs.js +20 -5
  64. package/dist/engine/feature-ui-extensions.js +2 -3
  65. package/dist/engine/index.d.ts +2 -1
  66. package/dist/engine/index.js +1 -0
  67. package/dist/engine/ownership.js +7 -20
  68. package/dist/engine/pattern-library/mixed-schemas.js +0 -1
  69. package/dist/engine/registry-ingest.d.ts +2 -1
  70. package/dist/engine/registry-ingest.js +85 -2
  71. package/dist/engine/registry-validate.d.ts +1 -0
  72. package/dist/engine/registry-validate.js +38 -8
  73. package/dist/engine/registry.js +2 -1
  74. package/dist/engine/schema-builder.js +1 -3
  75. package/dist/engine/soft-delete-cleanup.js +2 -1
  76. package/dist/engine/steps/call-feature.js +1 -3
  77. package/dist/engine/steps/unsafe-projection-upsert.js +2 -13
  78. package/dist/engine/template-variables.d.ts +29 -0
  79. package/dist/engine/template-variables.js +77 -0
  80. package/dist/engine/tier-resolver-extension.js +2 -3
  81. package/dist/engine/types/index.d.ts +2 -2
  82. package/dist/engine/validate-projection-allowlist.js +12 -13
  83. package/dist/engine/where-rule-lint.js +8 -7
  84. package/dist/errors/reasons.d.ts +1 -0
  85. package/dist/errors/reasons.js +2 -0
  86. package/dist/files/bind-referenced-files.write.d.ts +16 -0
  87. package/dist/files/bind-referenced-files.write.js +109 -0
  88. package/dist/files/feature.d.ts +1 -0
  89. package/dist/files/feature.js +2 -0
  90. package/dist/files/file-routes.d.ts +2 -1
  91. package/dist/files/file-routes.js +10 -3
  92. package/dist/files/local-provider.js +2 -1
  93. package/dist/files/record-write-check.d.ts +1 -0
  94. package/dist/files/record-write-check.js +9 -9
  95. package/dist/i18n/index.d.ts +1 -1
  96. package/dist/i18n/index.js +1 -1
  97. package/dist/i18n/mail-registry.d.ts +2 -0
  98. package/dist/i18n/mail-registry.js +7 -0
  99. package/dist/jobs/job-runner.js +5 -5
  100. package/dist/pipeline/dispatch-query.js +2 -3
  101. package/dist/pipeline/dispatch-shared.js +45 -14
  102. package/dist/pipeline/event-dispatcher-delivery.js +2 -3
  103. package/dist/pipeline/index.d.ts +1 -0
  104. package/dist/pipeline/index.js +1 -0
  105. package/dist/pipeline/member-read-only-transaction.js +1 -1
  106. package/dist/pipeline/notify-scope.d.ts +13 -0
  107. package/dist/pipeline/notify-scope.js +32 -0
  108. package/dist/pipeline/system-hooks.js +2 -1
  109. package/dist/pipeline/system-identity-switch.js +2 -2
  110. package/dist/pipeline/write-origin.js +2 -2
  111. package/dist/search/meilisearch-adapter.js +3 -4
  112. package/dist/secrets/stored-envelope.js +7 -7
  113. package/dist/stack/push-entity-projection-tables.js +2 -3
  114. package/dist/stack/table-helpers.js +10 -26
  115. package/dist/stack/test-stack.js +2 -5
  116. package/dist/testing/multipart-helper.d.ts +0 -6
  117. package/dist/testing/multipart-helper.js +5 -5
  118. package/dist/testing/without-ambient-temporal.js +4 -4
  119. package/dist/ui-types/card-column-roles.d.ts +5 -0
  120. package/dist/ui-types/card-column-roles.js +22 -4
  121. package/dist/ui-types/index.d.ts +1 -1
  122. package/dist/ui-types/index.js +1 -1
  123. package/dist/utils/is-plain-object.d.ts +1 -2
  124. package/dist/utils/is-plain-object.js +1 -4
  125. package/package.json +4 -4
  126. package/src/changes.json +88 -0
@@ -48,7 +48,7 @@ function validateWriteOnlyField(featureName, entityName, fieldName, field) {
48
48
  const where = `[Feature ${featureName}] Field "${fieldName}" on entity "${entityName}"`;
49
49
  if (field.type === "embedded") {
50
50
  for (const [subName, subField] of Object.entries(field.schema)) {
51
- if (subField.writeOnly === true) {
51
+ if ("writeOnly" in subField && subField.writeOnly === true) {
52
52
  // @cast-boundary schema-walk
53
53
  throw new Error(`${where} declares writeOnly on embedded sub-field "${subName}" — writeOnly is only supported on top-level text fields.`);
54
54
  }
@@ -6,6 +6,7 @@
6
6
  import { resolveActionIcon } from "@cosmicdrift/kumiko-types/action-icon";
7
7
  import { NO_WIDGET_FIELD_TYPES, optionsQueryFieldRefs, SELECT_OPTION_TONES, } from "@cosmicdrift/kumiko-types/fields";
8
8
  import { NAV_ICON_KEYS } from "@cosmicdrift/kumiko-types/nav-icon";
9
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
9
10
  import { rowMetaFieldNames } from "../../db/table-builder.js";
10
11
  import { REFERENCE_LOOKUP_SOURCES } from "../../ui-types/list-row-meta.js";
11
12
  import { isEncryptedAtRest } from "../config-helpers.js";
@@ -275,7 +276,7 @@ function validateWizardLayout(featureName, screenId, screenType, layout, feature
275
276
  // for an unregistered key instead of crashing — this check makes the same
276
277
  // gap fail loudly at boot instead of silently at render.
277
278
  function isRegisteredIcon(icon) {
278
- return NAV_ICON_KEYS.includes(icon);
279
+ return isOneOf(NAV_ICON_KEYS, icon);
279
280
  }
280
281
  export function validateActionHasIcon(featureName, screenId, screenType, context, action) {
281
282
  const resolved = resolveActionIcon(action.id, action.icon);
@@ -409,6 +409,7 @@ function projectEntity(entityName, entity) {
409
409
  ...(typeof entity.defaultCurrency === "string" && {
410
410
  defaultCurrency: entity.defaultCurrency,
411
411
  }),
412
+ ...(entity.lockedWhen !== undefined && { lockedWhen: entity.lockedWhen }),
412
413
  };
413
414
  }
414
415
  // Nur valueType durch — die derive-fn ist Server-only und NICHT JSON-safe
@@ -1,6 +1,6 @@
1
- import { validateChangelog } from "./feature-changelog.js";
1
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
2
+ import { CHANGELOG_TYPES, validateChangelog } from "./feature-changelog.js";
2
3
  const BLOCK_RE = /<!--\s*kumiko-changes\s*\n([\s\S]*?)\n\s*-->/g;
3
- const TYPE_VALUES = new Set(["breaking", "improvement", "fix"]);
4
4
  const KEY_RE = /^(feature|type|title|detail|migration|codemod|manualAfterCodemod):(?:\s(.*))?$/;
5
5
  function requiredField(fields, field, source) {
6
6
  const value = fields.get(field)?.trim();
@@ -95,7 +95,7 @@ function parseManualAfterCodemod(fields, source) {
95
95
  function buildChange(fields, prose, source) {
96
96
  const feature = requiredField(fields, "feature", source);
97
97
  const rawType = requiredField(fields, "type", source);
98
- if (!TYPE_VALUES.has(rawType)) {
98
+ if (!isOneOf(CHANGELOG_TYPES, rawType)) {
99
99
  throw new Error(`${source}: kumiko-changes type must be breaking, improvement, or fix`);
100
100
  }
101
101
  const { title, detail } = resolveTitleAndDetail(fields, prose, source);
@@ -1,4 +1,5 @@
1
1
  import { ENTITY_CONVENTION_QUERY_BRAND } from "@cosmicdrift/kumiko-types/handlers";
2
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
2
3
  import { runPipeline } from "./run-pipeline.js";
3
4
  export function defineWriteHandler(def,
4
5
  // R6: a phantom rest-param. When the inferred response `TData` carries a
@@ -88,14 +89,10 @@ export function defineQueryHandler(def,
88
89
  // brand through into the stored registry entry.
89
90
  export const PAGED_QUERY_HANDLER_BRAND = "__kumikoPagedQueryHandler";
90
91
  export function isPagedQueryHandler(def) {
91
- // @cast-boundary brand-probe — reading an internal marker key off an
92
- // otherwise-typed handler definition; the property may legitimately be
93
- // absent, which is exactly the case this function distinguishes.
94
- return def[PAGED_QUERY_HANDLER_BRAND] === true;
92
+ return propertyOf(def, PAGED_QUERY_HANDLER_BRAND) === true;
95
93
  }
96
94
  export function isEntityConventionQueryHandler(def) {
97
- // @cast-boundary brand-probe — same shape as isPagedQueryHandler above.
98
- return def[ENTITY_CONVENTION_QUERY_BRAND] === true;
95
+ return propertyOf(def, ENTITY_CONVENTION_QUERY_BRAND) === true;
99
96
  }
100
97
  // A projectionList screen's query must resolve to { rows, nextCursor, total? }
101
98
  // (CursorResult<T>) — the renderer used to read rowsQuery.data?.rows and
@@ -1,3 +1,4 @@
1
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
1
2
  /** Computes a derived cell from its source values. Missing/non-numeric
2
3
  * sources: "multiply" with any missing source → undefined; "sum"/"subtract"
3
4
  * treat missing as 0 only when at least one source is present — if every
@@ -6,9 +7,13 @@
6
7
  * to the target sub-field's declared precision afterward. */
7
8
  export function computeDerivedCellValue(op, values) {
8
9
  if (op === "multiply") {
9
- if (values.some((value) => value === undefined))
10
- return undefined;
11
- return values.reduce((product, value) => product * value, 1);
10
+ let product = 1;
11
+ for (const value of values) {
12
+ if (value === undefined)
13
+ return undefined;
14
+ product *= value;
15
+ }
16
+ return product;
12
17
  }
13
18
  if (values.every((value) => value === undefined))
14
19
  return undefined;
@@ -58,10 +63,9 @@ function roundHalfAwayFromZero(value, decimals) {
58
63
  * it's written back, so a fractional product lands on a value the target
59
64
  * type can actually represent. */
60
65
  export function withDerivedCells(row, derived, schema) {
61
- if (typeof row !== "object" || row === null || Array.isArray(row))
66
+ if (!isPlainObject(row))
62
67
  return row;
63
- const source = row;
64
- const copy = { ...source };
68
+ const copy = { ...row };
65
69
  for (const [cellName, def] of Object.entries(derived)) {
66
70
  const sourceValues = def.from.map((sourceField) => {
67
71
  const value = copy[sourceField];
@@ -3,9 +3,10 @@
3
3
  // validation (feature-ui-extensions.ts's useExtension) already rejects an
4
4
  // empty reason, but options stays `Record<string, unknown>`, so orchestrators
5
5
  // re-narrow at read time instead of trusting the shape.
6
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
6
7
  export function extensionUsageEscapeHatchReason(usage) {
7
8
  const escapeHatch = usage.options?.["escapeHatch"];
8
- if (typeof escapeHatch !== "object" || escapeHatch === null)
9
+ if (!isPlainObject(escapeHatch))
9
10
  return undefined;
10
11
  const { reason, grants } = escapeHatch;
11
12
  if (Array.isArray(grants) && !grants.includes("unsafeRaw"))
@@ -1,3 +1,4 @@
1
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
1
2
  // `personal: { of: "id" }` means the row itself is the subject, not a
2
3
  // referenced user — resolves to `recordOwned` instead of `userOwned`.
3
4
  const RECORD_OWNER_FIELD = "id";
@@ -75,15 +76,13 @@ function isNonEmptyString(value) {
75
76
  // Fail-closed stance gate (kumiko-framework#2921) for the two text-shaped
76
77
  // factories — runs before expandPersonalAnnotations so an untyped JS caller
77
78
  // (no compiler to stop it) can't slip an unannotated text field past the
78
- // type layer either. `overrides` is `unknown` here on purpose: the typed
79
- // signature above already rejects a missing/malformed shape at compile
80
- // time, this re-checks the same shape at the runtime boundary.
79
+ // type layer either. The typed signature above already rejects a
80
+ // missing/malformed shape at compile time; this re-checks the same shape at
81
+ // runtime, hence the nullable parameter.
81
82
  function assertTextFieldStance(factoryName, overrides) {
82
83
  if (overrides === null || typeof overrides !== "object") {
83
84
  throw missingTextFieldStanceError(factoryName);
84
85
  }
85
- // @cast-boundary runtime-guard — re-validated field by field below before
86
- // any value is trusted.
87
86
  const { personal, find, reason } = overrides;
88
87
  if (personal === undefined || personal === null) {
89
88
  throw missingTextFieldStanceError(factoryName);
@@ -106,7 +105,7 @@ function assertTextFieldStance(factoryName, overrides) {
106
105
  throw missingTextFieldStanceError(factoryName);
107
106
  }
108
107
  const validFinds = TEXT_FIELD_STANCE_FIND_VALUES[factoryName];
109
- if (typeof find !== "string" || !validFinds.includes(find)) {
108
+ if (typeof find !== "string" || !isOneOf(validFinds, find)) {
110
109
  throw missingTextFieldStanceError(factoryName);
111
110
  }
112
111
  }
@@ -200,37 +200,45 @@ export function extractNotification(call, sourceFile) {
200
200
  return fail("notification", sourceLocationFromNode(call, sourceFile), "second argument must be an inline definition object");
201
201
  }
202
202
  }
203
- const triggerObj = defObj
204
- .getProperty("trigger")
205
- ?.asKind(SyntaxKind.PropertyAssignment)
206
- ?.getInitializer()
207
- ?.asKind(SyntaxKind.ObjectLiteralExpression);
208
- if (!triggerObj) {
209
- return fail("notification", sourceLocationFromNode(call, sourceFile), "missing or non-object `trigger` property");
210
- }
211
- const onInit = triggerObj
212
- .getProperty("on")
213
- ?.asKind(SyntaxKind.PropertyAssignment)
214
- ?.getInitializer();
215
- const onName = onInit ? readNameOrRef(onInit) : undefined;
216
- if (!onName) {
217
- return fail("notification", sourceLocationFromNode(call, sourceFile), "trigger.on must be a string literal or inline { name } object");
203
+ const triggerProp = defObj.getProperty("trigger");
204
+ if (!triggerProp && (defObj.getProperty("recipient") || defObj.getProperty("data"))) {
205
+ return fail("notification", sourceLocationFromNode(call, sourceFile), "recipient and data require a `trigger`");
218
206
  }
219
- const recipientInit = defObj
220
- .getProperty("recipient")
221
- ?.asKind(SyntaxKind.PropertyAssignment)
222
- ?.getInitializer();
223
- const recipientFn = recipientInit ? findFunctionLiteral(recipientInit) : undefined;
224
- if (!recipientFn) {
225
- return fail("notification", sourceLocationFromNode(call, sourceFile), "recipient must be an inline arrow function or function expression");
226
- }
227
- const dataInit = defObj
228
- .getProperty("data")
229
- ?.asKind(SyntaxKind.PropertyAssignment)
230
- ?.getInitializer();
231
- const dataFn = dataInit ? findFunctionLiteral(dataInit) : undefined;
232
- if (!dataFn) {
233
- return fail("notification", sourceLocationFromNode(call, sourceFile), "data must be an inline arrow function or function expression");
207
+ let onName;
208
+ let recipientFn;
209
+ let dataFn;
210
+ if (triggerProp) {
211
+ const triggerObj = triggerProp
212
+ .asKind(SyntaxKind.PropertyAssignment)
213
+ ?.getInitializer()
214
+ ?.asKind(SyntaxKind.ObjectLiteralExpression);
215
+ if (!triggerObj) {
216
+ return fail("notification", sourceLocationFromNode(call, sourceFile), "non-object `trigger` property");
217
+ }
218
+ const onInit = triggerObj
219
+ .getProperty("on")
220
+ ?.asKind(SyntaxKind.PropertyAssignment)
221
+ ?.getInitializer();
222
+ onName = onInit ? readNameOrRef(onInit) : undefined;
223
+ if (!onName) {
224
+ return fail("notification", sourceLocationFromNode(call, sourceFile), "trigger.on must be a string literal or inline { name } object");
225
+ }
226
+ const recipientInit = defObj
227
+ .getProperty("recipient")
228
+ ?.asKind(SyntaxKind.PropertyAssignment)
229
+ ?.getInitializer();
230
+ recipientFn = recipientInit ? findFunctionLiteral(recipientInit) : undefined;
231
+ if (!recipientFn) {
232
+ return fail("notification", sourceLocationFromNode(call, sourceFile), "recipient must be an inline arrow function or function expression");
233
+ }
234
+ const dataInit = defObj
235
+ .getProperty("data")
236
+ ?.asKind(SyntaxKind.PropertyAssignment)
237
+ ?.getInitializer();
238
+ dataFn = dataInit ? findFunctionLiteral(dataInit) : undefined;
239
+ if (!dataFn) {
240
+ return fail("notification", sourceLocationFromNode(call, sourceFile), "data must be an inline arrow function or function expression");
241
+ }
234
242
  }
235
243
  let templates;
236
244
  const templatesObj = defObj
@@ -257,9 +265,11 @@ export function extractNotification(call, sourceFile) {
257
265
  kind: "notification",
258
266
  source: sourceLocationFromNode(call, sourceFile),
259
267
  notificationName,
260
- trigger: { on: onName },
261
- recipientBody: sourceLocationFromNode(recipientFn, sourceFile),
262
- dataBody: sourceLocationFromNode(dataFn, sourceFile),
268
+ ...(onName !== undefined && { trigger: { on: onName } }),
269
+ ...(recipientFn !== undefined && {
270
+ recipientBody: sourceLocationFromNode(recipientFn, sourceFile),
271
+ }),
272
+ ...(dataFn !== undefined && { dataBody: sourceLocationFromNode(dataFn, sourceFile) }),
263
273
  ...(templates !== undefined && { templates }),
264
274
  });
265
275
  }
@@ -2,7 +2,7 @@ import type { CallExpression, SourceFile } from "ts-morph";
2
2
  import type { ClaimKeyType } from "../../types/handlers.js";
3
3
  import type { ParseError } from "../parse.js";
4
4
  import type { ClaimKeyPattern, ConfigPattern, MetricPattern, ReferenceDataPattern, SecretNamespacePattern, SecretPattern, TranslationsPattern, UseExtensionPattern } from "../patterns.js";
5
- import { type ExtractOutput } from "./shared.js";
5
+ import { type ExtractOutput, type ReadDataLiteralOptions } from "./shared.js";
6
6
  export type NamedOptionsResult = {
7
7
  readonly kind: "ok";
8
8
  readonly name: string;
@@ -11,7 +11,7 @@ export type NamedOptionsResult = {
11
11
  readonly kind: "error";
12
12
  readonly error: ParseError;
13
13
  };
14
- export declare function readNamedOptions(call: CallExpression, sourceFile: SourceFile, methodName: string): NamedOptionsResult;
14
+ export declare function readNamedOptions(call: CallExpression, sourceFile: SourceFile, methodName: string, readOptions?: ReadDataLiteralOptions): NamedOptionsResult;
15
15
  export declare function extractConfig(call: CallExpression, sourceFile: SourceFile): ExtractOutput<ConfigPattern>;
16
16
  export declare function extractTranslations(call: CallExpression, sourceFile: SourceFile): ExtractOutput<TranslationsPattern>;
17
17
  export declare function extractMetric(call: CallExpression, sourceFile: SourceFile): ExtractOutput<MetricPattern>;
@@ -1,7 +1,7 @@
1
1
  import { SyntaxKind } from "ts-morph";
2
2
  import { sourceLocationFromNode } from "../source-location.js";
3
3
  import { fail, isPlainObject, ok, readDataLiteralNode, readNameLiteral, readNameLiteralRef, readNameOrRef, } from "./shared.js";
4
- export function readNamedOptions(call, sourceFile, methodName) {
4
+ export function readNamedOptions(call, sourceFile, methodName, readOptions = {}) {
5
5
  const args = call.getArguments();
6
6
  const first = args[0];
7
7
  if (!first) {
@@ -17,7 +17,7 @@ export function readNamedOptions(call, sourceFile, methodName) {
17
17
  if (!nameInit) {
18
18
  return fail(methodName, sourceLocationFromNode(call, sourceFile), "object form requires a string-literal `name` property");
19
19
  }
20
- const data = readDataLiteralNode(obj);
20
+ const data = readDataLiteralNode(obj, readOptions);
21
21
  if (!isPlainObject(data)) {
22
22
  return fail(methodName, sourceLocationFromNode(call, sourceFile), "argument could not be read as a plain object");
23
23
  }
@@ -32,7 +32,7 @@ export function readNamedOptions(call, sourceFile, methodName) {
32
32
  if (!optionsArg) {
33
33
  return fail(methodName, sourceLocationFromNode(call, sourceFile), "expected an options object as second argument");
34
34
  }
35
- const options = readDataLiteralNode(optionsArg);
35
+ const options = readDataLiteralNode(optionsArg, readOptions);
36
36
  if (!isPlainObject(options)) {
37
37
  return fail(methodName, sourceLocationFromNode(call, sourceFile), "options could not be read as a plain object");
38
38
  }
@@ -88,7 +88,7 @@ export function extractMetric(call, sourceFile) {
88
88
  });
89
89
  }
90
90
  export function extractSecret(call, sourceFile) {
91
- const parsed = readNamedOptions(call, sourceFile, "secret");
91
+ const parsed = readNamedOptions(call, sourceFile, "secret", { functionValuesAsRaw: true });
92
92
  if (parsed.kind === "error")
93
93
  return parsed;
94
94
  return ok({
@@ -69,11 +69,11 @@ export type AddJobArgs = {
69
69
  };
70
70
  export type AddNotificationArgs = {
71
71
  readonly name: string;
72
- readonly trigger: {
72
+ readonly trigger?: {
73
73
  readonly on: string;
74
74
  };
75
- readonly recipientSource: string;
76
- readonly dataSource: string;
75
+ readonly recipientSource?: string;
76
+ readonly dataSource?: string;
77
77
  readonly templates?: Readonly<Record<string, string>>;
78
78
  };
79
79
  export type AddHttpRouteArgs = {
@@ -205,9 +205,9 @@ export function createFeaturePatcher(sourceFile) {
205
205
  kind: "notification",
206
206
  source: SYNTHETIC_LOC,
207
207
  notificationName: name,
208
- trigger,
209
- recipientBody: rawLoc(recipientSource),
210
- dataBody: rawLoc(dataSource),
208
+ ...(trigger !== undefined && { trigger }),
209
+ ...(recipientSource !== undefined && { recipientBody: rawLoc(recipientSource) }),
210
+ ...(dataSource !== undefined && { dataBody: rawLoc(dataSource) }),
211
211
  ...(templateLocs !== undefined && { templates: templateLocs }),
212
212
  });
213
213
  },
@@ -366,11 +366,11 @@ export declare const PATTERN_SCHEMAS_BY_KIND: {
366
366
  kind: z.ZodLiteral<"notification">;
367
367
  source: z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>;
368
368
  notificationName: z.ZodString;
369
- trigger: z.ZodObject<{
369
+ trigger: z.ZodOptional<z.ZodObject<{
370
370
  on: z.ZodString;
371
- }, z.core.$strict>;
372
- recipientBody: z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>;
373
- dataBody: z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>;
371
+ }, z.core.$strict>>;
372
+ recipientBody: z.ZodOptional<z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>>;
373
+ dataBody: z.ZodOptional<z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>>;
374
374
  templates: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>>>;
375
375
  }, z.core.$strict>;
376
376
  authClaims: z.ZodObject<{
@@ -883,11 +883,11 @@ export declare const patternSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
883
883
  kind: z.ZodLiteral<"notification">;
884
884
  source: z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>;
885
885
  notificationName: z.ZodString;
886
- trigger: z.ZodObject<{
886
+ trigger: z.ZodOptional<z.ZodObject<{
887
887
  on: z.ZodString;
888
- }, z.core.$strict>;
889
- recipientBody: z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>;
890
- dataBody: z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>;
888
+ }, z.core.$strict>>;
889
+ recipientBody: z.ZodOptional<z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>>;
890
+ dataBody: z.ZodOptional<z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>>;
891
891
  templates: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<SourceLocation, unknown, z.core.$ZodTypeInternals<SourceLocation, unknown>>>>;
892
892
  }, z.core.$strict>, z.ZodObject<{
893
893
  kind: z.ZodLiteral<"authClaims">;
@@ -562,9 +562,9 @@ const notificationSchema = z
562
562
  kind: z.literal("notification"),
563
563
  source: sourceFieldSchema,
564
564
  notificationName: z.string(),
565
- trigger: z.object({ on: z.string() }).strict(),
566
- recipientBody: sourceBodySchema,
567
- dataBody: sourceBodySchema,
565
+ trigger: z.object({ on: z.string() }).strict().optional(),
566
+ recipientBody: sourceBodySchema.optional(),
567
+ dataBody: sourceBodySchema.optional(),
568
568
  templates: z.record(z.string(), sourceBodySchema).optional(),
569
569
  })
570
570
  .strict();
@@ -190,11 +190,11 @@ export type NotificationPattern = {
190
190
  readonly kind: "notification";
191
191
  readonly source: SourceLocation;
192
192
  readonly notificationName: string;
193
- readonly trigger: {
193
+ readonly trigger?: {
194
194
  readonly on: string;
195
195
  };
196
- readonly recipientBody: SourceLocation;
197
- readonly dataBody: SourceLocation;
196
+ readonly recipientBody?: SourceLocation;
197
+ readonly dataBody?: SourceLocation;
198
198
  readonly templates?: Readonly<Record<string, SourceLocation>>;
199
199
  };
200
200
  export type AuthClaimsPattern = {
@@ -497,9 +497,12 @@ function renderJob(p) {
497
497
  function renderNotification(p) {
498
498
  const lines = ["r.notification({"];
499
499
  lines.push(` name: ${JSON.stringify(p.notificationName)},`);
500
- lines.push(` trigger: { on: ${JSON.stringify(p.trigger.on)} },`);
501
- lines.push(` recipient: ${p.recipientBody.raw},`);
502
- lines.push(` data: ${p.dataBody.raw},`);
500
+ if (p.trigger)
501
+ lines.push(` trigger: { on: ${JSON.stringify(p.trigger.on)} },`);
502
+ if (p.recipientBody)
503
+ lines.push(` recipient: ${p.recipientBody.raw},`);
504
+ if (p.dataBody)
505
+ lines.push(` data: ${p.dataBody.raw},`);
503
506
  if (p.templates && Object.keys(p.templates).length > 0) {
504
507
  lines.push(" templates: {");
505
508
  for (const [k, loc] of Object.entries(p.templates)) {
@@ -1,4 +1,5 @@
1
- export type ChangelogType = "breaking" | "improvement" | "fix";
1
+ export declare const CHANGELOG_TYPES: readonly ["breaking", "improvement", "fix"];
2
+ export type ChangelogType = (typeof CHANGELOG_TYPES)[number];
2
3
  export type ChangelogEntry = {
3
4
  readonly version: string;
4
5
  readonly type: ChangelogType;
@@ -5,6 +5,8 @@
5
5
  //
6
6
  // File I/O stays in the CLI (`bin/commands/upgrade.ts`) — this module is
7
7
  // pure parse/validate so engine stays off the node:fs allowlist.
8
+ import { isOneOf, isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
9
+ export const CHANGELOG_TYPES = ["breaking", "improvement", "fix"];
8
10
  /** Parse a changes.json body. Callers own file I/O. */
9
11
  export function parseFeatureChangelog(raw, featureName) {
10
12
  try {
@@ -24,16 +26,16 @@ export function parseFeatureChangelog(raw, featureName) {
24
26
  }
25
27
  }
26
28
  function isChangelogEntry(value) {
27
- if (typeof value !== "object" || value === null)
29
+ if (!isPlainObject(value))
28
30
  return false;
29
- const obj = value;
30
- if (typeof obj["version"] !== "string")
31
+ if (typeof value["version"] !== "string")
31
32
  return false;
32
- if (!["breaking", "improvement", "fix"].includes(obj["type"]))
33
+ if (!isOneOf(CHANGELOG_TYPES, value["type"]))
33
34
  return false;
34
- if (typeof obj["title"] !== "string")
35
+ if (typeof value["title"] !== "string")
35
36
  return false;
36
- if (obj["manualAfterCodemod"] !== undefined && typeof obj["manualAfterCodemod"] !== "boolean") {
37
+ if (value["manualAfterCodemod"] !== undefined &&
38
+ typeof value["manualAfterCodemod"] !== "boolean") {
37
39
  return false;
38
40
  }
39
41
  return true;
@@ -1,7 +1,7 @@
1
1
  import type * as z from "zod";
2
2
  import { type ZodType } from "zod";
3
3
  import type { FeatureBuilderState } from "./feature-builder-state.js";
4
- import type { AuthClaimsFn, ClaimKeyHandle, ClaimKeyType, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigSeedDef, DeclarativeEventMigration, EventDef, EventPiiStance, EventUpcastFn, JobDefinition, JobHandlerFn, MetricOptions, NameOrRef, NotificationDataFn, NotificationRecipientFn, NotificationTemplateFn, QualifiedEventName, SecretKeyHandle, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, TranslationsDef } from "./types/index.js";
4
+ import type { AuthClaimsFn, ClaimKeyHandle, ClaimKeyType, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigSeedDef, DeclarativeEventMigration, EventDef, EventPiiStance, EventUpcastFn, JobDefinition, JobHandlerFn, MetricOptions, NameOrRef, NotificationDataFn, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, QualifiedEventName, SecretKeyHandle, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, TemplateVariables, TranslationsDef } from "./types/index.js";
5
5
  export declare function buildConfigEventsJobsMethods<TName extends string>(state: FeatureBuilderState, name: TName): {
6
6
  config: {
7
7
  <T extends ConfigKeyType>(keyName: string, def: ConfigKeyDefinition<T>): ConfigKeyHandle<T>;
@@ -14,19 +14,29 @@ export declare function buildConfigEventsJobsMethods<TName extends string>(state
14
14
  notification(notificationNameOrDefinition: string | ({
15
15
  readonly name: string;
16
16
  } & {
17
- readonly trigger: {
17
+ readonly trigger?: {
18
18
  readonly on: NameOrRef;
19
19
  };
20
- readonly recipient: NotificationRecipientFn;
21
- readonly data: NotificationDataFn;
20
+ readonly recipient?: NotificationRecipientFn;
21
+ readonly data?: NotificationDataFn;
22
22
  readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
23
+ readonly variables?: TemplateVariables;
24
+ readonly record?: NotificationRecordMapping;
25
+ readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
26
+ readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
27
+ readonly localizedTemplate?: () => NotificationLocalizedTemplate;
23
28
  }), definition?: {
24
- readonly trigger: {
29
+ readonly trigger?: {
25
30
  readonly on: NameOrRef;
26
31
  };
27
- readonly recipient: NotificationRecipientFn;
28
- readonly data: NotificationDataFn;
32
+ readonly recipient?: NotificationRecipientFn;
33
+ readonly data?: NotificationDataFn;
29
34
  readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
35
+ readonly variables?: TemplateVariables;
36
+ readonly record?: NotificationRecordMapping;
37
+ readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
38
+ readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
39
+ readonly localizedTemplate?: () => NotificationLocalizedTemplate;
30
40
  }): void;
31
41
  translations(def: TranslationsDef): void;
32
42
  defineEvent: <const TInner extends string, TPayload>(eventName: TInner, schema: ZodType<TPayload>, options: {
@@ -1,4 +1,5 @@
1
1
  import { escapeHatchGrantsProblem, normalizeEventPiiSubject, } from "@cosmicdrift/kumiko-types/handlers";
2
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
2
3
  import { ZodObject } from "zod";
3
4
  import { isValidIanaTimeZone } from "../time/iana.js";
4
5
  import { resolveName } from "./handler-helpers.js";
@@ -197,10 +198,25 @@ export function buildConfigEventsJobsMethods(state, name) {
197
198
  : splitNamedDefinition(notificationNameOrDefinition);
198
199
  state.notifications[notificationName] = {
199
200
  name: notificationName,
200
- trigger: { on: resolveName(resolvedDefinition.trigger.on) },
201
- recipient: resolvedDefinition.recipient,
202
- data: resolvedDefinition.data,
201
+ ...(resolvedDefinition.trigger !== undefined
202
+ ? { trigger: { on: resolveName(resolvedDefinition.trigger.on) } }
203
+ : {}),
204
+ ...(resolvedDefinition.recipient !== undefined
205
+ ? { recipient: resolvedDefinition.recipient }
206
+ : {}),
207
+ ...(resolvedDefinition.data !== undefined ? { data: resolvedDefinition.data } : {}),
203
208
  templates: resolvedDefinition.templates,
209
+ ...(resolvedDefinition.variables !== undefined
210
+ ? { variables: resolvedDefinition.variables }
211
+ : {}),
212
+ ...(resolvedDefinition.record !== undefined ? { record: resolvedDefinition.record } : {}),
213
+ ...(resolvedDefinition.template !== undefined
214
+ ? { template: resolvedDefinition.template }
215
+ : {}),
216
+ ...(resolvedDefinition.meta !== undefined ? { meta: resolvedDefinition.meta } : {}),
217
+ ...(resolvedDefinition.localizedTemplate !== undefined
218
+ ? { localizedTemplate: resolvedDefinition.localizedTemplate }
219
+ : {}),
204
220
  };
205
221
  },
206
222
  translations(def) {
@@ -366,10 +382,9 @@ function compileEventMigration(spec) {
366
382
  renameTargets.set(to, from);
367
383
  }
368
384
  return (payload) => {
369
- if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
385
+ if (!isPlainObject(payload)) {
370
386
  throw new Error("Declarative event migration expects an object payload");
371
387
  }
372
- // @cast-boundary parse — payload is guarded as a plain object above
373
388
  const next = { ...payload };
374
389
  for (const [from, to] of Object.entries(spec.rename ?? {})) {
375
390
  if (from in next) {
@@ -1,4 +1,5 @@
1
1
  import { escapeHatchGrantsProblem } from "@cosmicdrift/kumiko-types/handlers";
2
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
2
3
  import { WEBSOCKET_MAX_CONNECTIONS_PER_USER_LIMIT, WEBSOCKET_MAX_PAYLOAD_BYTES, WEBSOCKET_ROUTE_PATH_PREFIX, } from "@cosmicdrift/kumiko-types/websocket-route";
3
4
  import { bindHookEscapeHatchGrant } from "../pipeline/system-identity-switch.js";
4
5
  import { LifecycleHookTypes } from "./constants.js";
@@ -209,9 +210,7 @@ export function buildUiExtensionsMethods(state, name) {
209
210
  // for every extension regardless of its typed hook shape.
210
211
  const escapeHatch = optionsBag?.["escapeHatch"];
211
212
  if (escapeHatch !== undefined) {
212
- const reason = typeof escapeHatch === "object" && escapeHatch !== null
213
- ? escapeHatch.reason
214
- : undefined;
213
+ const reason = isPlainObject(escapeHatch) ? escapeHatch["reason"] : undefined;
215
214
  if (typeof reason !== "string" || reason.trim().length === 0) {
216
215
  throw new Error(`[Feature ${name}] r.useExtension("${extensionName}", "${resolvedEntityName}", ...) declares an invalid { escapeHatch } — ` +
217
216
  `must be { reason: "<non-empty string>" } explaining why this usage needs unfiltered db access.`);
@@ -77,10 +77,11 @@ export { describeWorkflowStepError } from "./steps/describe-workflow-step-error.
77
77
  export { unwrapWorkflowStepError, WorkflowStepError } from "./steps/workflow-step-error.js";
78
78
  export { WORKFLOW_SYSTEM_EVENT_DEFS } from "./steps/workflow-system-events.js";
79
79
  export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUser, SYSTEM_ROLE, SYSTEM_USER_ID, } from "./system-user.js";
80
+ export { assertNotificationVariables, templateVariable, templateVariablesToZod, } from "./template-variables.js";
80
81
  export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.js";
81
82
  export { findOverDeepTransferChain, resolveTransferAdjacency as resolveEntityTransferAdjacency, type TransferAdjacency, type TransferEdge, type TransferEdgeLink, } from "./transfer-adjacency.js";
82
83
  export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
83
- export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, DateFieldDef, DecimalFieldDef, DeleteContext, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EmbeddedListEditSpec, EntityDefinition, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, NotifyDelivery, NotifyDeliveryStatus, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TenantId, TenantWriteTarget, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
84
+ export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, DateFieldDef, DecimalFieldDef, DeleteContext, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EmbeddedListEditSpec, EntityDefinition, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TemplateListVariableDef, TemplateScalarVariableDef, TemplateVariableDef, TemplateVariables, TenantId, TenantWriteTarget, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
84
85
  export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HookPhases, HTTP_ERROR_CODE_PATTERN, isAgentVisibleScreen, isDeliveryErrorCode, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.js";
85
86
  export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.js";
86
87
  export { runValidation } from "./validation.js";
@@ -70,6 +70,7 @@ export { describeWorkflowStepError } from "./steps/describe-workflow-step-error.
70
70
  export { unwrapWorkflowStepError, WorkflowStepError } from "./steps/workflow-step-error.js";
71
71
  export { WORKFLOW_SYSTEM_EVENT_DEFS } from "./steps/workflow-system-events.js";
72
72
  export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUser, SYSTEM_ROLE, SYSTEM_USER_ID, } from "./system-user.js";
73
+ export { assertNotificationVariables, templateVariable, templateVariablesToZod, } from "./template-variables.js";
73
74
  export { findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, } from "./tier-resolver-extension.js";
74
75
  export { findOverDeepTransferChain, resolveTransferAdjacency as resolveEntityTransferAdjacency, } from "./transfer-adjacency.js";
75
76
  export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";