@cosmicdrift/kumiko-samples 0.365.0 → 0.366.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 (180) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +1 -1
  3. package/packages/bundled-features/src/admin-shell/feature.ts +9 -9
  4. package/packages/bundled-features/src/agent-tools/changes.json +6 -0
  5. package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +87 -5
  6. package/packages/bundled-features/src/audit/feature.ts +1 -1
  7. package/packages/bundled-features/src/auth-email-password/changes.json +13 -0
  8. package/packages/bundled-features/src/auth-email-password/constants.ts +0 -2
  9. package/packages/bundled-features/src/auth-email-password/feature.ts +3 -3
  10. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +1 -0
  11. package/packages/bundled-features/src/auth-mfa/feature.ts +4 -4
  12. package/packages/bundled-features/src/auth-mfa-user-data/index.ts +1 -1
  13. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +1 -1
  14. package/packages/bundled-features/src/billing-foundation/feature.ts +1 -1
  15. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +89 -28
  16. package/packages/bundled-features/src/cap-counter/changes.json +7 -0
  17. package/packages/bundled-features/src/cap-counter/constants.ts +3 -0
  18. package/packages/bundled-features/src/cap-counter/feature.ts +2 -2
  19. package/packages/bundled-features/src/cap-counter/tables.ts +5 -2
  20. package/packages/bundled-features/src/cap-overview/feature.ts +3 -3
  21. package/packages/bundled-features/src/channel-discord/feature.ts +2 -2
  22. package/packages/bundled-features/src/channel-email/feature.ts +1 -1
  23. package/packages/bundled-features/src/channel-in-app/feature.ts +1 -1
  24. package/packages/bundled-features/src/channel-push/feature.ts +1 -1
  25. package/packages/bundled-features/src/channel-slack/feature.ts +2 -2
  26. package/packages/bundled-features/src/channel-teams/feature.ts +2 -2
  27. package/packages/bundled-features/src/channel-telegram/feature.ts +2 -2
  28. package/packages/bundled-features/src/compliance-profiles/feature.ts +1 -1
  29. package/packages/bundled-features/src/compliance-profiles-ops/index.ts +3 -3
  30. package/packages/bundled-features/src/config/feature.ts +1 -1
  31. package/packages/bundled-features/src/crypto-shredding/changes.json +13 -0
  32. package/packages/bundled-features/src/custom-fields/feature.ts +2 -2
  33. package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +17 -13
  34. package/packages/bundled-features/src/data-retention/feature.ts +1 -1
  35. package/packages/bundled-features/src/data-retention/presets.ts +4 -4
  36. package/packages/bundled-features/src/delivery/changes.json +6 -0
  37. package/packages/bundled-features/src/delivery/db/queries/address-opt-outs.ts +6 -2
  38. package/packages/bundled-features/src/delivery/db/queries/preferences.ts +4 -4
  39. package/packages/bundled-features/src/delivery/delivery-service.ts +17 -2
  40. package/packages/bundled-features/src/delivery/feature.ts +9 -5
  41. package/packages/bundled-features/src/delivery/handlers/resubscribe-address.write.ts +12 -8
  42. package/packages/bundled-features/src/delivery/handlers/resubscribe-user.write.ts +3 -1
  43. package/packages/bundled-features/src/delivery/handlers/set-preference.write.ts +3 -1
  44. package/packages/bundled-features/src/delivery/handlers/unsubscribe-address.write.ts +3 -1
  45. package/packages/bundled-features/src/delivery/handlers/unsubscribe-user.write.ts +3 -1
  46. package/packages/bundled-features/src/delivery/notification-type-aliases.ts +13 -0
  47. package/packages/bundled-features/src/derivatives-sharp/feature.ts +1 -1
  48. package/packages/bundled-features/src/document-ingest-foundation/feature.ts +2 -2
  49. package/packages/bundled-features/src/file-derivatives/feature.ts +3 -3
  50. package/packages/bundled-features/src/file-foundation/feature.ts +1 -1
  51. package/packages/bundled-features/src/file-provider-inmemory/feature.ts +2 -2
  52. package/packages/bundled-features/src/file-provider-s3/feature.ts +3 -3
  53. package/packages/bundled-features/src/file-provider-s3-env/feature.ts +2 -2
  54. package/packages/bundled-features/src/files/README.md +1 -1
  55. package/packages/bundled-features/src/files-tenant-data/index.ts +1 -1
  56. package/packages/bundled-features/src/folders/feature.ts +2 -2
  57. package/packages/bundled-features/src/folders-user-data/index.ts +1 -1
  58. package/packages/bundled-features/src/form-draft/feature.ts +2 -2
  59. package/packages/bundled-features/src/form-draft-user-data/index.ts +1 -1
  60. package/packages/bundled-features/src/inbound-mail-foundation/changes.json +13 -0
  61. package/packages/bundled-features/src/inbound-mail-foundation/connect-routes.ts +9 -1
  62. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -1
  63. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +19 -7
  64. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +21 -2
  65. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +2 -2
  66. package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +1 -1
  67. package/packages/bundled-features/src/ledger/feature.ts +5 -5
  68. package/packages/bundled-features/src/legal-pages/feature.ts +1 -1
  69. package/packages/bundled-features/src/mail-foundation/feature.ts +1 -1
  70. package/packages/bundled-features/src/mail-transport-inmemory/feature.ts +2 -2
  71. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +3 -3
  72. package/packages/bundled-features/src/managed-pages/css-gate.ts +1 -1
  73. package/packages/bundled-features/src/managed-pages/feature.ts +2 -2
  74. package/packages/bundled-features/src/metrics/feature.ts +2 -2
  75. package/packages/bundled-features/src/notes-history/feature.ts +2 -2
  76. package/packages/bundled-features/src/notes-history-user-data/index.ts +1 -1
  77. package/packages/bundled-features/src/personal-access-tokens/feature.ts +15 -11
  78. package/packages/bundled-features/src/readiness/feature.ts +2 -2
  79. package/packages/bundled-features/src/renderer-foundation/README.md +1 -1
  80. package/packages/bundled-features/src/renderer-foundation/feature.ts +1 -1
  81. package/packages/bundled-features/src/renderer-pdf/feature.ts +1 -1
  82. package/packages/bundled-features/src/secrets/feature.ts +1 -1
  83. package/packages/bundled-features/src/seo/feature.ts +1 -1
  84. package/packages/bundled-features/src/sessions/feature.ts +17 -13
  85. package/packages/bundled-features/src/step-dispatcher/changes.json +8 -1
  86. package/packages/bundled-features/src/step-dispatcher/constants.ts +22 -0
  87. package/packages/bundled-features/src/step-dispatcher/deliver-dispatch-request.ts +236 -0
  88. package/packages/bundled-features/src/step-dispatcher/feature.ts +66 -119
  89. package/packages/bundled-features/src/step-dispatcher/webhook-runner.ts +40 -14
  90. package/packages/bundled-features/src/subscription-mollie/feature.ts +1 -1
  91. package/packages/bundled-features/src/subscription-stripe/feature.ts +3 -3
  92. package/packages/bundled-features/src/tags/feature.ts +2 -2
  93. package/packages/bundled-features/src/template-resolver/feature.ts +2 -2
  94. package/packages/bundled-features/src/template-resolver-user-data/index.ts +1 -1
  95. package/packages/bundled-features/src/tenant/feature.ts +4 -4
  96. package/packages/bundled-features/src/tenant/seeding.ts +1 -1
  97. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +2 -3
  98. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +6 -1
  99. package/packages/bundled-features/src/tenant-settings/feature.ts +1 -1
  100. package/packages/bundled-features/src/tier-engine/feature.ts +36 -27
  101. package/packages/bundled-features/src/user/feature.ts +1 -1
  102. package/packages/bundled-features/src/user-data-rights/README.md +1 -1
  103. package/packages/bundled-features/src/user-data-rights/feature.ts +4 -4
  104. package/packages/bundled-features/src/user-data-rights-defaults/feature.ts +2 -2
  105. package/packages/bundled-features/src/user-profile/feature.ts +4 -4
  106. package/packages/bundled-features/src/waitlist/feature.ts +2 -2
  107. package/samples/apps/cap-billing-demo/README.md +1 -1
  108. package/samples/apps/cap-billing-demo/src/feature.ts +4 -4
  109. package/samples/apps/cap-billing-demo/src/run-config.ts +1 -1
  110. package/samples/apps/config-demo/src/features/demo/feature.ts +2 -2
  111. package/samples/apps/styleguide/src/features/ai-settings/feature.ts +43 -28
  112. package/samples/apps/styleguide/src/features/demo/feature.ts +18 -18
  113. package/samples/apps/styleguide/src/features/widgets/ai-text-demo.ts +10 -12
  114. package/samples/apps/styleguide/src/features/widgets/feature.ts +90 -108
  115. package/samples/apps/ui-walkthrough/src/features/rental/feature.ts +36 -36
  116. package/samples/apps/ui-walkthrough/src/features/vehicles/feature.ts +6 -6
  117. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +2 -2
  118. package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
  119. package/samples/apps/use-all-bundled/scripts/gen-sample-index.ts +7 -7
  120. package/samples/apps/use-all-bundled/src/app/notes-feature.ts +3 -3
  121. package/samples/apps/use-all-bundled/src/app/screens-feature.ts +1 -1
  122. package/samples/apps/user-data-rights-demo/src/feature.ts +5 -5
  123. package/samples/recipes/access-control/src/feature.ts +21 -25
  124. package/samples/recipes/anonymous-access/src/feature.ts +36 -43
  125. package/samples/recipes/anonymous-access-multitenant/src/feature.ts +14 -16
  126. package/samples/recipes/auth-foundation-providers/src/feature.ts +1 -1
  127. package/samples/recipes/basic-entity/README.md +1 -1
  128. package/samples/recipes/boot-check/README.md +1 -1
  129. package/samples/recipes/boot-check/src/feature.ts +2 -2
  130. package/samples/recipes/chat-channels/src/feature.ts +1 -1
  131. package/samples/recipes/cross-feature-events/src/feature.ts +7 -7
  132. package/samples/recipes/currencies-global/src/feature.ts +3 -3
  133. package/samples/recipes/currencies-per-tenant/src/feature.ts +2 -2
  134. package/samples/recipes/custom-fields-basic/README.md +1 -1
  135. package/samples/recipes/custom-fields-basic/src/feature.ts +3 -2
  136. package/samples/recipes/custom-handlers/README.md +3 -3
  137. package/samples/recipes/custom-handlers/src/feature.ts +37 -41
  138. package/samples/recipes/dashboard-stats/src/feature.ts +6 -6
  139. package/samples/recipes/delivery-notifications/README.md +1 -1
  140. package/samples/recipes/delivery-notifications/src/feature.ts +8 -8
  141. package/samples/recipes/designer-demo/src/feature.ts +2 -1
  142. package/samples/recipes/embedded/src/feature.ts +1 -1
  143. package/samples/recipes/embedded-entity-form/src/feature.ts +13 -13
  144. package/samples/recipes/encrypted-tenant-config/src/feature.ts +1 -1
  145. package/samples/recipes/error-contract/src/feature.ts +25 -25
  146. package/samples/recipes/event-sourcing/src/feature.ts +70 -76
  147. package/samples/recipes/feature-toggles/src/feature.ts +28 -20
  148. package/samples/recipes/files-post-processing/src/feature.ts +22 -26
  149. package/samples/recipes/idempotency/src/feature.ts +16 -18
  150. package/samples/recipes/inbound-mail-triage/src/feature.ts +1 -1
  151. package/samples/recipes/lane-routing/src/feature.ts +10 -12
  152. package/samples/recipes/legal-pages/src/feature.ts +1 -1
  153. package/samples/recipes/lifecycle-hooks/README.md +4 -4
  154. package/samples/recipes/lifecycle-hooks/src/feature.ts +55 -35
  155. package/samples/recipes/managed-config/src/feature.ts +8 -8
  156. package/samples/recipes/managed-pages/src/feature.ts +1 -1
  157. package/samples/recipes/notes-history-basic/README.md +1 -1
  158. package/samples/recipes/notes-history-basic/src/feature.ts +2 -2
  159. package/samples/recipes/pdf-audit-export/src/feature.ts +7 -7
  160. package/samples/recipes/pipeline-basics/README.md +1 -1
  161. package/samples/recipes/pipeline-basics/src/feature.ts +7 -7
  162. package/samples/recipes/public-share-token/src/feature.ts +1 -1
  163. package/samples/recipes/rate-limiting/src/feature.ts +7 -9
  164. package/samples/recipes/record-detail-layout/src/feature.ts +18 -18
  165. package/samples/recipes/reference-data/src/feature.ts +1 -1
  166. package/samples/recipes/relations/src/feature.ts +2 -2
  167. package/samples/recipes/screens-nav/src/feature.ts +1 -1
  168. package/samples/recipes/search/src/feature.ts +17 -19
  169. package/samples/recipes/security-baseline/README.md +1 -1
  170. package/samples/recipes/state-machine/src/feature.ts +1 -1
  171. package/samples/recipes/tags-basic/README.md +1 -1
  172. package/samples/recipes/tags-basic/src/feature.ts +2 -2
  173. package/samples/recipes/tenant-settings/README.md +1 -1
  174. package/samples/recipes/tenant-settings/src/feature.ts +1 -1
  175. package/samples/recipes/timezones/src/feature.ts +12 -12
  176. package/samples/recipes/user-data-rights/README.md +2 -2
  177. package/samples/recipes/user-data-rights/src/feature.ts +1 -1
  178. package/samples/recipes/user-profile/src/feature.ts +1 -1
  179. package/samples/recipes/webhook-step/src/feature.ts +1 -1
  180. package/samples/recipes/writeform-section/src/feature.ts +12 -12
@@ -80,7 +80,7 @@ export function createTenantFeature(options?: TenantFeatureOptions): FeatureDefi
80
80
  recommended: true,
81
81
  });
82
82
  r.systemScope();
83
- r.requires("config");
83
+ r.requires({ features: ["config"] });
84
84
  r.extendsRegistrar(EXT_ASSIGNABLE_ROLE, {});
85
85
  // The elevation guards read the registry, the screens only these options: a role the
86
86
  // server accepts but the screen omits is silently stripped on the next member-roles save.
@@ -92,9 +92,9 @@ export function createTenantFeature(options?: TenantFeatureOptions): FeatureDefi
92
92
  );
93
93
  }
94
94
  });
95
- r.entity("tenant", tenantEntity);
96
- r.entity("tenant-membership", tenantMembershipEntity);
97
- r.entity("tenant-invitation", tenantInvitationEntity);
95
+ r.entity({ name: "tenant", ...tenantEntity });
96
+ r.entity({ name: "tenant-membership", ...tenantMembershipEntity });
97
+ r.entity({ name: "tenant-invitation", ...tenantInvitationEntity });
98
98
 
99
99
  r.config({
100
100
  keys: {
@@ -292,7 +292,7 @@ export async function seedTenant(
292
292
 
293
293
  // "tenant:seed" matches no handler-scoped hook, only entity-scoped ones
294
294
  // (keyed by result.entityName === "tenant") — that's exactly what
295
- // tier-engine's `r.hook("postSave", { allOf: "tenant" }, ...)` needs.
295
+ // tier-engine's `r.hook({ type: "postSave", target: { allOf: "tenant" }, ... })` needs.
296
296
  await fireEntityPostSave(hooks, "tenant:seed", result.data, { tenantId: options.id, db });
297
297
 
298
298
  return { id: options.id };
@@ -63,7 +63,7 @@ export function createTenantLifecycleFeature(): FeatureDefinition {
63
63
  category: "compliance",
64
64
  recommended: false,
65
65
  });
66
- r.requires("tenant", "compliance-profiles");
66
+ r.requires({ features: ["tenant", "compliance-profiles"] });
67
67
  r.optionalRequires("sessions");
68
68
  r.usesApi("compliance.forTenant");
69
69
 
@@ -140,8 +140,7 @@ export function createTenantLifecycleFeature(): FeatureDefinition {
140
140
  if (!ctx.registry) {
141
141
  throw new Error("run-tenant-destruction: ctx.registry required (JobContext incomplete)");
142
142
  }
143
- const T = (await import("@cosmicdrift/kumiko-framework/time")).getTemporal();
144
- const now = T.Now.instant();
143
+ const now = ctx.tz.now();
145
144
  // Without crossTenantReads (undeclared hatch) fall through to the sweep.
146
145
  if (ctx.crossTenantReads && !(await hasTenantDestructionWork(ctx.crossTenantReads, now))) {
147
146
  // skip: no tenant is due, so the sweep would not touch anything
@@ -224,7 +224,7 @@ async function tombstoneTenantRow(ctx: DestructionStageCtx): Promise<StageRunOut
224
224
  );
225
225
  }
226
226
  }
227
- await tenantCrud.update(
227
+ const tombstone = await tenantCrud.update(
228
228
  {
229
229
  id: ctx.tenantId,
230
230
  changes: {
@@ -237,6 +237,11 @@ async function tombstoneTenantRow(ctx: DestructionStageCtx): Promise<StageRunOut
237
237
  db,
238
238
  { skipOptimisticLock: true, writePurpose: WritePurposes.erasure },
239
239
  );
240
+ if (!tombstone.isSuccess) {
241
+ throw new Error(
242
+ `tenant-lifecycle: failed to mark tenant ${ctx.tenantId} destroyed: ${tombstone.error.message}`,
243
+ );
244
+ }
240
245
  invalidateTenantLifecycleGate(ctx.tenantId);
241
246
  return STAGE_DONE;
242
247
  }
@@ -22,7 +22,7 @@ export function createTenantSettingsFeature(
22
22
  opts: TenantSettingsFeatureOptions = {},
23
23
  ): FeatureDefinition {
24
24
  return defineFeature(TENANT_SETTINGS_FEATURE_NAME, (r) => {
25
- r.requires("config");
25
+ r.requires({ features: ["config"] });
26
26
  r.describe(
27
27
  "Per-tenant Currency + Locale defaults, exposed as two config keys (tenant-settings:config:currency, tenant-settings:config:locale) that surface in the self-populating Settings-Hub via `mask`. Pair with defineCreateWithTenantDefaults to auto-fill a money field's currency or a locale field on create instead of hard-coding a literal per entity.",
28
28
  );
@@ -206,10 +206,10 @@ export function createTierEngineFeature<
206
206
  category: "billing",
207
207
  recommended: false,
208
208
  });
209
- r.requires("config");
210
- r.requires("tenant");
209
+ r.requires({ features: ["config"] });
210
+ r.requires({ features: ["tenant"] });
211
211
 
212
- r.entity("tier-assignment", tierAssignmentEntity);
212
+ r.entity({ name: "tier-assignment", ...tierAssignmentEntity });
213
213
 
214
214
  // Standard-CRUD via Helper.
215
215
  r.writeHandler(
@@ -369,25 +369,33 @@ export function createTierEngineFeature<
369
369
  onTierAssigned.fn = applyAssignment;
370
370
 
371
371
  // Invalidation: tier-assignment CRUD events (e.g. the billing webhook) update the cache.
372
- r.hook("postSave", { allOf: "tier-assignment" }, async (result) => {
373
- // result.data has tenantId + tier (after entity-update merge)
374
- const data = result.data as { tenantId?: unknown; tier?: unknown }; // @cast-boundary engine-payload
375
- // skip: defensive type-guard auf payload-shape. Bei korrekt gerenderten
376
- // entity-events sind beide fields immer strings; ein malformed-payload
377
- // (custom-handler-bug) würde hier silent zum cache-skip führen statt
378
- // throwing — der lifecycle-pipeline darf nicht durch hook-fehler
379
- // blocken (afterCommit-pattern, side-effect-best-effort).
380
- if (typeof data.tenantId !== "string" || typeof data.tier !== "string") return;
381
- applyAssignment(data.tenantId as TenantId, data.tier);
372
+ r.hook({
373
+ type: "postSave",
374
+ target: { allOf: "tier-assignment" },
375
+ handler: async (result) => {
376
+ // result.data has tenantId + tier (after entity-update merge)
377
+ const data = result.data as { tenantId?: unknown; tier?: unknown }; // @cast-boundary engine-payload
378
+ // skip: defensive guard on the payload shape. Correct entity events
379
+ // always carry both fields as strings; a malformed payload (a custom
380
+ // handler bug) skips the cache update instead of throwing, because a
381
+ // hook error must not block the lifecycle pipeline (afterCommit,
382
+ // best-effort side effect).
383
+ if (typeof data.tenantId !== "string" || typeof data.tier !== "string") return;
384
+ applyAssignment(data.tenantId as TenantId, data.tier);
385
+ },
382
386
  });
383
- r.hook("postDelete", { allOf: "tier-assignment" }, async (payload) => {
384
- const data = payload.data as { tenantId?: unknown }; // @cast-boundary engine-payload
385
- // skip: gleiche type-guard semantik wie postSave-hook oben.
386
- if (typeof data.tenantId !== "string") return;
387
- const tenantId = data.tenantId as TenantId;
388
- takeTicket(tenantId);
389
- cache.delete(tenantId);
390
- publishAssignmentChange(tenantId);
387
+ r.hook({
388
+ type: "postDelete",
389
+ target: { allOf: "tier-assignment" },
390
+ handler: async (payload) => {
391
+ const data = payload.data as { tenantId?: unknown }; // @cast-boundary engine-payload
392
+ // skip: gleiche type-guard semantik wie postSave-hook oben.
393
+ if (typeof data.tenantId !== "string") return;
394
+ const tenantId = data.tenantId as TenantId;
395
+ takeTicket(tenantId);
396
+ cache.delete(tenantId);
397
+ publishAssignmentChange(tenantId);
398
+ },
391
399
  });
392
400
 
393
401
  // Auto-default-tier-on-tenant-signup: hook fires inTransaction (atomic
@@ -406,10 +414,10 @@ export function createTierEngineFeature<
406
414
  const defaultTier = opts.defaultTier;
407
415
  const autoDefaultTierHookReason =
408
416
  "seeds the default tier-assignment stream of the tenant just created";
409
- r.hook(
410
- "postSave",
411
- { allOf: "tenant" },
412
- async (result, ctx) => {
417
+ r.hook({
418
+ type: "postSave",
419
+ target: { allOf: "tenant" },
420
+ handler: async (result, ctx) => {
413
421
  // result-shape: kumiko-framework's SaveContext mit isNew + data
414
422
  const saveResult = result as { isNew?: unknown; data?: unknown }; // @cast-boundary engine-payload
415
423
  // skip: nur bei tenant-create (initial) — tenant-updates feuern
@@ -462,8 +470,9 @@ export function createTierEngineFeature<
462
470
  tdb,
463
471
  );
464
472
  },
465
- { phase: HookPhases.inTransaction, escapeHatch: { reason: autoDefaultTierHookReason } },
466
- );
473
+ phase: HookPhases.inTransaction,
474
+ escapeHatch: { reason: autoDefaultTierHookReason },
475
+ });
467
476
  }
468
477
 
469
478
  // Extension-point declaration + self-registration. Pattern analog
@@ -28,7 +28,7 @@ export function createUserFeature(): FeatureDefinition {
28
28
  recommended: true,
29
29
  });
30
30
  r.systemScope();
31
- r.entity("user", userEntity);
31
+ r.entity({ name: "user", ...userEntity });
32
32
 
33
33
  // Self-extension: `user` declares AND fulfils principalStatus (precedent:
34
34
  // tier-engine/feature.ts) — wires the blocked-principal check into every stack that mounts `user`.
@@ -14,7 +14,7 @@ an. user-data-rights orchestriert Export- und Forget-Pipeline:
14
14
 
15
15
  ```ts
16
16
  defineFeature("tasks", (r) => {
17
- r.requires("user-data-rights");
17
+ r.requires({ features: ["user-data-rights"] });
18
18
  r.useExtension(EXT_USER_DATA, "task", {
19
19
  export: async (ctx) => ({
20
20
  entity: "task",
@@ -197,7 +197,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
197
197
  category: "compliance",
198
198
  recommended: false,
199
199
  });
200
- r.requires("user", "data-retention", "compliance-profiles", "sessions");
200
+ r.requires({ features: ["user", "data-retention", "compliance-profiles", "sessions"] });
201
201
  r.usesApi("compliance.forTenant");
202
202
  r.usesApi("retention.policyFor");
203
203
  // S2.U6 — restrict-account ruft sessions.revokeAllForUser cross-feature.
@@ -242,15 +242,15 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
242
242
  );
243
243
 
244
244
  // S2.U3 Atom 1b — ExportJob-Lifecycle-Entity.
245
- r.entity("export-job", exportJobEntity);
245
+ r.entity({ name: "export-job", ...exportJobEntity });
246
246
 
247
247
  // S2.U3 Atom 4a — Download-Token-Entity. Worker generiert Token beim
248
248
  // Flip auf done (siehe run-export-jobs.ts). Hash in DB, plain im
249
249
  // RunExportJobsResult fuer Atom 5 (Notification per Email).
250
- r.entity("export-download-token", exportDownloadTokenEntity);
250
+ r.entity({ name: "export-download-token", ...exportDownloadTokenEntity });
251
251
 
252
252
  // S2.U7 — Audit-Trail invalid Download-Attempts (DPO Brute-Force-Detection).
253
- r.entity("download-attempt", downloadAttemptEntity);
253
+ r.entity({ name: "download-attempt", ...downloadAttemptEntity });
254
254
 
255
255
  // S2.U6 — DSGVO Art. 18 Account-Freeze (Verarbeitungs-Pause).
256
256
  // Endpoints fuer Restrict + Lift. Login-Block fuer Restricted Users
@@ -40,7 +40,7 @@ import {
40
40
  // `user` (S2.H1) und `fileRef` (S2.H2).
41
41
  //
42
42
  // Architektur-Entscheidung (S2.H1+H2): user-data-rights selbst kann
43
- // nicht r.requires("user", "files") + r.useExtension(EXT_USER_DATA, ...)
43
+ // nicht r.requires({ features: ["user", "files"] }) + r.useExtension(EXT_USER_DATA, ...)
44
44
  // machen weil es selbst Provider von EXT_USER_DATA ist (Boot-Validator
45
45
  // lehnt self-extension ab). Lösung: drittes optional-mountbares Feature
46
46
  // das requires beide Sources + die useExtension-Calls macht.
@@ -68,7 +68,7 @@ export function createUserDataRightsDefaultsFeature(): FeatureDefinition {
68
68
  category: "compliance",
69
69
  recommended: false,
70
70
  });
71
- r.requires("user", "files", "user-data-rights");
71
+ r.requires({ features: ["user", "files", "user-data-rights"] });
72
72
  // Optional sources: hooks are registered unconditionally but each one
73
73
  // no-ops at runtime when its source feature isn't mounted (see
74
74
  // hooks/feature-mounted.ts) — the export runner has no per-hook
@@ -41,10 +41,10 @@ export function createUserProfileFeature(options?: UserProfileFeatureOptions): F
41
41
  category: "identity",
42
42
  recommended: true,
43
43
  });
44
- r.requires("user");
45
- r.requires("auth-email-password");
46
- r.requires("user-data-rights");
47
- r.requires("user-data-rights-defaults");
44
+ r.requires({ features: ["user"] });
45
+ r.requires({ features: ["auth-email-password"] });
46
+ r.requires({ features: ["user-data-rights"] });
47
+ r.requires({ features: ["user-data-rights-defaults"] });
48
48
 
49
49
  const handlers = allowEmailChange ? { changeEmail: r.writeHandler(changeEmailWrite) } : {};
50
50
 
@@ -30,7 +30,7 @@ export function createWaitlistFeature(opts: WaitlistOptions = {}): FeatureDefini
30
30
  );
31
31
  r.uiHints({ displayLabel: "Waitlist", category: "data", recommended: false });
32
32
  r.systemScope();
33
- r.requires("tenant", "auth-email-password", "user-data-rights");
33
+ r.requires({ features: ["tenant", "auth-email-password", "user-data-rights"] });
34
34
  r.translations({ keys: WAITLIST_I18N });
35
35
 
36
36
  r.notification("confirmation", {
@@ -43,7 +43,7 @@ export function createWaitlistFeature(opts: WaitlistOptions = {}): FeatureDefini
43
43
  localizedTemplate: waitlistAdminNoticeTemplate,
44
44
  });
45
45
 
46
- r.entity("waitlistEntry", waitlistEntryEntity);
46
+ r.entity({ name: "waitlistEntry", ...waitlistEntryEntity });
47
47
  r.writeHandler(createSubmitHandler(opts));
48
48
  r.writeHandler(createInviteHandler(opts.invite));
49
49
  r.writeHandler(rejectHandler);
@@ -102,7 +102,7 @@ In the browser, use the Designer/Admin UI to set the config key
102
102
  there's no HTTP endpoint for it because the sample shows the
103
103
  architecture, not an inbox UI.
104
104
 
105
- If you want clickable: write a small `r.queryHandler("inbox:list")`
105
+ If you want clickable: write a small `r.queryHandler({ name: "inbox:list", … })`
106
106
  that returns `getInbox(ctx.user.tenantId)`. ~20 LOC, deliberately
107
107
  omitted to keep the focus on cap+tier.
108
108
 
@@ -183,10 +183,10 @@ export const newsletterFeature = defineFeature(FEATURE_NAME, (r) => {
183
183
  // Hard-deps: mail-foundation für Plugin-API + config für tier-Wahl +
184
184
  // cap-counter (transitiv via withCapEnforcement, aber explicit ist
185
185
  // klarer für boot-Validator-Errors).
186
- r.requires("config");
187
- r.requires("cap-counter");
188
- r.requires(mailFoundationFeature.name);
189
- r.requires(BILLING_FOUNDATION_FEATURE);
186
+ r.requires({ features: ["config"] });
187
+ r.requires({ features: ["cap-counter"] });
188
+ r.requires({ features: [mailFoundationFeature.name] });
189
+ r.requires({ features: [BILLING_FOUNDATION_FEATURE] });
190
190
 
191
191
  // Tier-config-key. Tenant-Admin setzt's; default "free".
192
192
  r.config(
@@ -1,7 +1,7 @@
1
1
  // Single source of truth für die Feature-Komposition der Demo-App.
2
2
  //
3
3
  // **Mount-Order matters:** mail-foundation MUSS vor mail-transport-
4
- // inmemory stehen (`r.requires("mail-foundation")` im Plugin); das
4
+ // inmemory stehen (`r.requires({ features: ["mail-foundation"] })` im Plugin); das
5
5
  // newsletter-feature kommt danach (es nutzt mail-foundation +
6
6
  // cap-counter). composeFeatures (auth-mode) ergänzt config + secrets +
7
7
  // auth-email-password automatisch.
@@ -37,8 +37,8 @@ const REQUIRED_I18N = {
37
37
  // mask.title ist der i18n-Key des Feld-Labels, mask.order die Reihenfolge.
38
38
  export const configDemoFeature = defineFeature("config-demo", (r) => {
39
39
  r.translations({ keys: REQUIRED_I18N });
40
- r.requires("config");
41
- r.requires("secrets");
40
+ r.requires({ features: ["config"] });
41
+ r.requires({ features: ["secrets"] });
42
42
 
43
43
  r.config({
44
44
  keys: {
@@ -53,14 +53,14 @@ function isProvider(value: unknown): value is Provider {
53
53
  }
54
54
 
55
55
  export const aiSettingsFeature = defineFeature("ai-settings", (r) => {
56
- r.requires("config");
56
+ r.requires({ features: ["config"] });
57
57
  r.translations({ keys: AI_SETTINGS_I18N });
58
58
 
59
59
  r.crud("connection", connectionEntity, { write: open, read: openRead });
60
- r.queryHandler(
61
- "connection-options",
62
- noPayload,
63
- async (query, ctx) => {
60
+ r.queryHandler({
61
+ name: "connection-options",
62
+ schema: noPayload,
63
+ handler: async (query, ctx) => {
64
64
  const page = await executor.list({ limit: 100 }, query.user, ctx.db);
65
65
  return {
66
66
  rows: page.rows.map((row) => ({
@@ -69,28 +69,33 @@ export const aiSettingsFeature = defineFeature("ai-settings", (r) => {
69
69
  })),
70
70
  };
71
71
  },
72
- openRead,
73
- );
72
+ ...openRead,
73
+ });
74
74
 
75
- r.queryHandler(
76
- "model-options",
77
- connectionIdPayload,
78
- async (query, ctx) => {
75
+ r.queryHandler({
76
+ name: "model-options",
77
+ schema: connectionIdPayload,
78
+ handler: async (query, ctx) => {
79
79
  const { connectionId } = query.payload;
80
80
  if (connectionId === undefined) return { rows: [] };
81
81
  const row = await executor.detail({ id: connectionId }, query.user, ctx.db);
82
82
  const provider = row?.["provider"];
83
83
  return { rows: isProvider(provider) ? MODELS_BY_PROVIDER[provider] : [] };
84
84
  },
85
- openRead,
86
- );
85
+ ...openRead,
86
+ });
87
87
 
88
- r.queryHandler("step-options", noPayload, async () => ({ rows: AI_STEPS }), openRead);
88
+ r.queryHandler({
89
+ name: "step-options",
90
+ schema: noPayload,
91
+ handler: async () => ({ rows: AI_STEPS }),
92
+ ...openRead,
93
+ });
89
94
 
90
- r.queryHandler(
91
- "all-model-options",
92
- noPayload,
93
- async () => ({
95
+ r.queryHandler({
96
+ name: "all-model-options",
97
+ schema: noPayload,
98
+ handler: async () => ({
94
99
  rows: PROVIDERS.flatMap((provider) =>
95
100
  MODELS_BY_PROVIDER[provider].map((model) => ({
96
101
  ...model,
@@ -98,13 +103,13 @@ export const aiSettingsFeature = defineFeature("ai-settings", (r) => {
98
103
  })),
99
104
  ),
100
105
  }),
101
- openRead,
102
- );
106
+ ...openRead,
107
+ });
103
108
 
104
- r.queryHandler(
105
- "connection:summary",
106
- idPayload,
107
- async (query, ctx) => {
109
+ r.queryHandler({
110
+ name: "connection:summary",
111
+ schema: idPayload,
112
+ handler: async (query, ctx) => {
108
113
  const row = await executor.detail({ id: query.payload.id }, query.user, ctx.db);
109
114
  return {
110
115
  id: query.payload.id,
@@ -112,12 +117,22 @@ export const aiSettingsFeature = defineFeature("ai-settings", (r) => {
112
117
  provider: String(row?.["provider"] ?? ""),
113
118
  };
114
119
  },
115
- openRead,
116
- );
120
+ ...openRead,
121
+ });
117
122
 
118
123
  const acknowledged = async () => ({ isSuccess: true as const, data: null });
119
- r.writeHandler("step:assign", z.object({}).passthrough(), acknowledged, open);
120
- r.writeHandler("probe:run", z.object({}).passthrough(), acknowledged, open);
124
+ r.writeHandler({
125
+ name: "step:assign",
126
+ schema: z.object({}).passthrough(),
127
+ handler: acknowledged,
128
+ ...open,
129
+ });
130
+ r.writeHandler({
131
+ name: "probe:run",
132
+ schema: z.object({}).passthrough(),
133
+ handler: acknowledged,
134
+ ...open,
135
+ });
121
136
 
122
137
  r.config({
123
138
  keys: {
@@ -63,10 +63,10 @@ export const demoFeature = defineFeature("styleguide", (r) => {
63
63
  // triggers shouldRenderActionsIconOnly) and need their own write handlers.
64
64
  const { executor: itemExecutor } = createEntityExecutor("item", demoEntity);
65
65
 
66
- r.writeHandler(
67
- "item:archive",
68
- z.object({ id: z.uuid() }),
69
- async (event, ctx) =>
66
+ r.writeHandler({
67
+ name: "item:archive",
68
+ schema: z.object({ id: z.uuid() }),
69
+ handler: async (event, ctx) =>
70
70
  // Admin-style status flip from a list row: last-writer-wins is fine,
71
71
  // same as the tenant enable/disable toggle this mirrors.
72
72
  itemExecutor.update(
@@ -75,13 +75,13 @@ export const demoFeature = defineFeature("styleguide", (r) => {
75
75
  ctx.db,
76
76
  { skipOptimisticLock: true },
77
77
  ),
78
- { access: open.access },
79
- );
78
+ access: open.access,
79
+ });
80
80
 
81
- r.writeHandler(
82
- "item:publish",
83
- z.object({ id: z.uuid() }),
84
- async (event, ctx) =>
81
+ r.writeHandler({
82
+ name: "item:publish",
83
+ schema: z.object({ id: z.uuid() }),
84
+ handler: async (event, ctx) =>
85
85
  itemExecutor.update(
86
86
  {
87
87
  id: event.payload.id,
@@ -91,13 +91,13 @@ export const demoFeature = defineFeature("styleguide", (r) => {
91
91
  ctx.db,
92
92
  { skipOptimisticLock: true },
93
93
  ),
94
- { access: open.access },
95
- );
94
+ access: open.access,
95
+ });
96
96
 
97
- r.writeHandler(
98
- "item:duplicate",
99
- z.object({ id: z.uuid() }),
100
- async (event, ctx) => {
97
+ r.writeHandler({
98
+ name: "item:duplicate",
99
+ schema: z.object({ id: z.uuid() }),
100
+ handler: async (event, ctx) => {
101
101
  const source = await itemExecutor.detail({ id: event.payload.id }, event.user, ctx.db);
102
102
  if (!source) return failNotFound("item", event.payload.id);
103
103
  return itemExecutor.create(
@@ -115,8 +115,8 @@ export const demoFeature = defineFeature("styleguide", (r) => {
115
115
  ctx.db,
116
116
  );
117
117
  },
118
- { access: open.access },
119
- );
118
+ access: open.access,
119
+ });
120
120
 
121
121
  r.screen({
122
122
  id: "item-edit",
@@ -46,10 +46,10 @@ function cannedText(payload: RunInput): string {
46
46
  }
47
47
 
48
48
  export const aiTextDemoFeature = defineFeature("ai-text", (r) => {
49
- r.queryHandler(
50
- "run",
51
- runInput,
52
- async ({ payload }) => {
49
+ r.queryHandler({
50
+ name: "run",
51
+ schema: runInput,
52
+ handler: async ({ payload }) => {
53
53
  const text = cannedText(payload);
54
54
  return {
55
55
  type: "text" as const,
@@ -57,14 +57,12 @@ export const aiTextDemoFeature = defineFeature("ai-text", (r) => {
57
57
  usage: { inputTokens: payload.text.length, outputTokens: text.length },
58
58
  };
59
59
  },
60
- {
61
- access: {
62
- openToAll: {
63
- reason:
64
- "demo stand-in for a real LLM call: returns canned text transforms with no " +
65
- "side effects or per-user data; any signed-in user may query it",
66
- },
60
+ access: {
61
+ openToAll: {
62
+ reason:
63
+ "demo stand-in for a real LLM call: returns canned text transforms with no " +
64
+ "side effects or per-user data; any signed-in user may query it",
67
65
  },
68
66
  },
69
- );
67
+ });
70
68
  });