@cosmicdrift/kumiko-framework 0.386.0 → 0.387.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 (288) hide show
  1. package/LICENSE +21 -57
  2. package/README.md +2 -2
  3. package/agents/widgets.md +39 -39
  4. package/dist/api/api-constants.js +2 -2
  5. package/dist/api/auth-middleware.js +7 -7
  6. package/dist/api/auth-rate-limiters.js +7 -3
  7. package/dist/api/auth-routes-mfa.js +1 -1
  8. package/dist/api/auth-routes-session.js +1 -1
  9. package/dist/api/auth-routes-shared.js +7 -3
  10. package/dist/api/auth-routes-signup.js +1 -1
  11. package/dist/api/client-ip.js +3 -1
  12. package/dist/api/extra-route.js +1 -1
  13. package/dist/api/pii-leak-guard.js +4 -2
  14. package/dist/api/readiness.js +5 -5
  15. package/dist/api/redis-sse-broker.js +8 -6
  16. package/dist/api/routes.js +2 -2
  17. package/dist/api/server.js +34 -33
  18. package/dist/api/sse-broker.js +1 -1
  19. package/dist/api/sse-route.d.ts +13 -13
  20. package/dist/api/sse-route.js +15 -15
  21. package/dist/auth/index.js +4 -4
  22. package/dist/auth/roles.d.ts +4 -4
  23. package/dist/auth/roles.js +24 -24
  24. package/dist/compliance/duration-spec.js +18 -20
  25. package/dist/compliance/index.js +2 -3
  26. package/dist/compliance/override-schema.d.ts +4 -4
  27. package/dist/compliance/override-schema.js +19 -22
  28. package/dist/compliance/profiles.d.ts +26 -29
  29. package/dist/compliance/profiles.js +62 -69
  30. package/dist/compliance/sub-processors.d.ts +28 -30
  31. package/dist/compliance/sub-processors.js +17 -21
  32. package/dist/consumer-cli.js +4 -4
  33. package/dist/crypto/blind-index.js +18 -17
  34. package/dist/crypto/ciphertext-pattern.js +1 -1
  35. package/dist/crypto/event-pii.js +3 -3
  36. package/dist/crypto/kek-source.d.ts +1 -1
  37. package/dist/crypto/kek-source.js +3 -2
  38. package/dist/crypto/kms-adapter.d.ts +1 -1
  39. package/dist/crypto/kms-adapter.js +4 -6
  40. package/dist/crypto/kms-wiring.d.ts +1 -2
  41. package/dist/crypto/kms-wiring.js +2 -2
  42. package/dist/crypto/pg-kms-adapter.js +2 -6
  43. package/dist/crypto/pii-field-encryption.js +4 -4
  44. package/dist/crypto/subject-resolver.d.ts +1 -1
  45. package/dist/crypto/subject-resolver.js +2 -2
  46. package/dist/db/apply-entity-event.d.ts +6 -6
  47. package/dist/db/apply-entity-event.js +55 -55
  48. package/dist/db/blind-index-cleanup.js +7 -7
  49. package/dist/db/collect-table-metas.js +10 -11
  50. package/dist/db/compound-types.d.ts +1 -1
  51. package/dist/db/compound-types.js +12 -12
  52. package/dist/db/cursor.js +3 -4
  53. package/dist/db/dialect.d.ts +2 -2
  54. package/dist/db/dialect.js +2 -2
  55. package/dist/db/eagerload.d.ts +3 -3
  56. package/dist/db/eagerload.js +21 -12
  57. package/dist/db/entity-table-meta.d.ts +2 -2
  58. package/dist/db/entity-table-meta.js +4 -4
  59. package/dist/db/event-store-executor-context.d.ts +1 -1
  60. package/dist/db/event-store-executor-context.js +13 -13
  61. package/dist/db/event-store-executor-read.js +27 -28
  62. package/dist/db/event-store-executor-write.js +65 -70
  63. package/dist/db/located-timestamp.d.ts +11 -11
  64. package/dist/db/located-timestamp.js +29 -30
  65. package/dist/db/migrate-generator.d.ts +1 -1
  66. package/dist/db/migrate-generator.js +26 -19
  67. package/dist/db/migrate-runner.js +25 -25
  68. package/dist/db/money.d.ts +6 -6
  69. package/dist/db/money.js +29 -27
  70. package/dist/db/queries/backfill-pii.js +5 -5
  71. package/dist/db/queries/ddl.js +1 -1
  72. package/dist/db/queries/es-ops.d.ts +1 -1
  73. package/dist/db/queries/es-ops.js +1 -1
  74. package/dist/db/queries/event-consumer.js +3 -3
  75. package/dist/db/queries/event-store.js +4 -2
  76. package/dist/db/queries/projection-rebuild.js +3 -3
  77. package/dist/db/queries/raw-sql.d.ts +2 -2
  78. package/dist/db/queries/raw-sql.js +2 -2
  79. package/dist/db/queries/shadow-swap.js +19 -19
  80. package/dist/db/query.d.ts +1 -1
  81. package/dist/db/query.js +6 -6
  82. package/dist/db/rebuild-marker.js +20 -21
  83. package/dist/db/replay-migration-sql.js +2 -2
  84. package/dist/db/schema-inspection.js +4 -5
  85. package/dist/db/table-builder.d.ts +1 -2
  86. package/dist/db/table-builder.js +82 -82
  87. package/dist/db/tenant-db.d.ts +2 -2
  88. package/dist/db/tenant-db.js +9 -2
  89. package/dist/derivatives/delete-stored-file.js +5 -2
  90. package/dist/derivatives/derivatives-context.js +1 -1
  91. package/dist/engine/boot-validator/access-roles.js +4 -3
  92. package/dist/engine/boot-validator/api-ext.js +14 -14
  93. package/dist/engine/boot-validator/config-deps.js +7 -6
  94. package/dist/engine/boot-validator/detail-screens.js +3 -3
  95. package/dist/engine/boot-validator/entity-handler.js +60 -61
  96. package/dist/engine/boot-validator/gdpr-storage.js +4 -4
  97. package/dist/engine/boot-validator/i18n-keys.js +1 -1
  98. package/dist/engine/boot-validator/index.d.ts +2 -2
  99. package/dist/engine/boot-validator/index.js +20 -20
  100. package/dist/engine/boot-validator/nav.js +5 -5
  101. package/dist/engine/boot-validator/ownership.js +12 -9
  102. package/dist/engine/boot-validator/parent-ref.js +1 -1
  103. package/dist/engine/boot-validator/pii-retention.js +50 -57
  104. package/dist/engine/boot-validator/projection-list-screens.js +7 -7
  105. package/dist/engine/boot-validator/query-refs.js +1 -1
  106. package/dist/engine/boot-validator/screens-actions.js +15 -16
  107. package/dist/engine/boot-validator/screens-forms.js +10 -11
  108. package/dist/engine/boot-validator/screens-lists.js +14 -15
  109. package/dist/engine/boot-validator/screens.js +34 -40
  110. package/dist/engine/boot-validator/security-baseline.js +3 -2
  111. package/dist/engine/boot-validator/transfer-graph.js +2 -2
  112. package/dist/engine/boot-validator/workspaces.js +3 -3
  113. package/dist/engine/bridge-stub.js +3 -4
  114. package/dist/engine/build-app-schema.js +75 -75
  115. package/dist/engine/build-config-feature-schema.js +50 -50
  116. package/dist/engine/build-target.js +20 -23
  117. package/dist/engine/codemod/pipeline-codemod.js +20 -13
  118. package/dist/engine/config-helpers.js +4 -4
  119. package/dist/engine/constants.js +2 -2
  120. package/dist/engine/create-app.js +2 -2
  121. package/dist/engine/define-feature.js +12 -11
  122. package/dist/engine/define-handler.js +12 -11
  123. package/dist/engine/define-step.js +1 -1
  124. package/dist/engine/define-workflow.d.ts +5 -5
  125. package/dist/engine/define-workflow.js +6 -6
  126. package/dist/engine/entity-handlers.js +13 -14
  127. package/dist/engine/event-def.js +2 -2
  128. package/dist/engine/extension-names.d.ts +16 -17
  129. package/dist/engine/extension-names.js +37 -38
  130. package/dist/engine/extensions/escape-hatch-usage.js +1 -1
  131. package/dist/engine/extensions/tenant-data.js +2 -2
  132. package/dist/engine/extensions/user-data.d.ts +23 -24
  133. package/dist/engine/factories.d.ts +14 -13
  134. package/dist/engine/factories.js +38 -37
  135. package/dist/engine/feature-ast/entity-field-types.js +1 -1
  136. package/dist/engine/feature-ast/extractors/handlers.js +1 -1
  137. package/dist/engine/feature-ast/extractors/hooks.js +1 -1
  138. package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
  139. package/dist/engine/feature-ast/extractors/shared.js +2 -3
  140. package/dist/engine/feature-ast/parse.js +13 -13
  141. package/dist/engine/feature-ast/patch.js +1 -1
  142. package/dist/engine/feature-ast/pattern-change-schema.js +1 -1
  143. package/dist/engine/feature-ast/patterns.d.ts +2 -2
  144. package/dist/engine/feature-ast/render.js +2 -2
  145. package/dist/engine/feature-config-events-jobs.js +15 -12
  146. package/dist/engine/feature-entity-handlers.js +9 -5
  147. package/dist/engine/feature-manifest.d.ts +9 -9
  148. package/dist/engine/feature-manifest.js +6 -7
  149. package/dist/engine/feature-ui-extensions.d.ts +3 -3
  150. package/dist/engine/feature-ui-extensions.js +30 -22
  151. package/dist/engine/field-access.js +5 -6
  152. package/dist/engine/index.d.ts +1 -0
  153. package/dist/engine/index.js +4 -4
  154. package/dist/engine/object-form.d.ts +1 -0
  155. package/dist/engine/object-form.js +14 -4
  156. package/dist/engine/ownership.js +3 -3
  157. package/dist/engine/parse-ref-target.js +5 -7
  158. package/dist/engine/pattern-library/index.js +2 -2
  159. package/dist/engine/pattern-library/library.js +2 -2
  160. package/dist/engine/pattern-library/types.js +3 -3
  161. package/dist/engine/pipeline.js +2 -2
  162. package/dist/engine/project-app-schema-for-roles.js +1 -1
  163. package/dist/engine/projection-helpers.js +9 -9
  164. package/dist/engine/qualified-name.js +2 -2
  165. package/dist/engine/registry-facade.js +1 -1
  166. package/dist/engine/registry-ingest.js +12 -12
  167. package/dist/engine/registry-state.js +17 -19
  168. package/dist/engine/registry-validate.js +12 -12
  169. package/dist/engine/run-in.js +2 -2
  170. package/dist/engine/run-pipeline.js +7 -0
  171. package/dist/engine/schema-builder.js +40 -38
  172. package/dist/engine/screen-filter-ops.js +17 -20
  173. package/dist/engine/screen-helpers.js +4 -3
  174. package/dist/engine/state-machine.d.ts +12 -12
  175. package/dist/engine/state-machine.js +7 -7
  176. package/dist/engine/steps/_resolver-utils.js +4 -3
  177. package/dist/engine/steps/for-each.js +4 -4
  178. package/dist/engine/steps/unsafe-projection-delete.js +1 -1
  179. package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
  180. package/dist/engine/steps/wait-for-event.js +4 -4
  181. package/dist/engine/steps/workflow-system-events.js +3 -3
  182. package/dist/engine/tier-resolver-extension.d.ts +29 -27
  183. package/dist/engine/tier-resolver-extension.js +26 -26
  184. package/dist/engine/transfer-adjacency.js +2 -2
  185. package/dist/engine/validate-projection-allowlist.js +7 -27
  186. package/dist/engine/walk-step-instances.d.ts +2 -0
  187. package/dist/engine/walk-step-instances.js +38 -0
  188. package/dist/engine/where-rule-lint.js +1 -1
  189. package/dist/engine/write-handler-qn-extract.js +1 -1
  190. package/dist/entrypoint/index.js +3 -3
  191. package/dist/env/index.d.ts +7 -6
  192. package/dist/env/index.js +10 -9
  193. package/dist/errors/transition-details.js +7 -7
  194. package/dist/errors/write-error-info.d.ts +6 -6
  195. package/dist/errors/write-error-info.js +6 -6
  196. package/dist/es-ops/context.d.ts +9 -9
  197. package/dist/es-ops/context.js +28 -28
  198. package/dist/es-ops/index.js +7 -9
  199. package/dist/es-ops/operations-schema.js +4 -10
  200. package/dist/es-ops/runner.d.ts +11 -11
  201. package/dist/es-ops/runner.js +49 -53
  202. package/dist/es-ops/types.d.ts +55 -55
  203. package/dist/es-ops/types.js +11 -11
  204. package/dist/event-store/admin-api.js +6 -7
  205. package/dist/event-store/errors.js +1 -1
  206. package/dist/event-store/event-store.js +2 -2
  207. package/dist/event-store/rebuild-dead-letter.js +1 -1
  208. package/dist/files/feature.js +9 -10
  209. package/dist/files/file-handle.js +5 -6
  210. package/dist/files/file-ref-entity.js +15 -15
  211. package/dist/files/file-ref-table.js +7 -8
  212. package/dist/files/file-routes.d.ts +7 -0
  213. package/dist/files/file-routes.js +11 -5
  214. package/dist/files/in-memory-provider.js +8 -9
  215. package/dist/files/local-provider.js +18 -25
  216. package/dist/files/provider-resolver.d.ts +7 -7
  217. package/dist/files/storage-tracking.js +2 -2
  218. package/dist/files/zip-stream.d.ts +12 -19
  219. package/dist/files/zip-stream.js +44 -63
  220. package/dist/i18n/required-surface-keys.d.ts +1 -1
  221. package/dist/i18n/required-surface-keys.js +8 -8
  222. package/dist/jobs/job-runner-helpers.js +8 -2
  223. package/dist/jobs/job-runner-schedulers.d.ts +1 -1
  224. package/dist/jobs/job-runner-schedulers.js +3 -4
  225. package/dist/jobs/job-runner.js +13 -15
  226. package/dist/lifecycle/lifecycle.js +3 -5
  227. package/dist/lifecycle/signal-handlers.js +5 -4
  228. package/dist/logging/utils.d.ts +2 -1
  229. package/dist/logging/utils.js +38 -10
  230. package/dist/migrations/index.js +1 -1
  231. package/dist/migrations/kumiko-drift.js +14 -13
  232. package/dist/migrations/pending-rebuilds.d.ts +21 -20
  233. package/dist/migrations/pending-rebuilds.js +27 -27
  234. package/dist/migrations/projection-table-index.d.ts +3 -3
  235. package/dist/migrations/projection-table-index.js +8 -8
  236. package/dist/observability/metric-validator.js +1 -1
  237. package/dist/observability/metrics-handle.js +3 -3
  238. package/dist/pipeline/append-event-core.js +11 -2
  239. package/dist/pipeline/cascade-handler.d.ts +1 -1
  240. package/dist/pipeline/dispatch-batch.js +2 -2
  241. package/dist/pipeline/dispatch-query.js +10 -10
  242. package/dist/pipeline/dispatch-shared.d.ts +1 -0
  243. package/dist/pipeline/dispatch-shared.js +15 -17
  244. package/dist/pipeline/dispatch-stream.js +1 -1
  245. package/dist/pipeline/dispatch-write.js +3 -3
  246. package/dist/pipeline/dispatcher-utils.d.ts +1 -1
  247. package/dist/pipeline/dispatcher-utils.js +1 -1
  248. package/dist/pipeline/event-consumer-state.js +2 -2
  249. package/dist/pipeline/event-dispatcher-delivery.js +1 -1
  250. package/dist/pipeline/event-dispatcher.js +17 -28
  251. package/dist/pipeline/irreversible-operation-gate.js +1 -1
  252. package/dist/pipeline/lifecycle-pipeline.js +9 -9
  253. package/dist/pipeline/member-read-only-transaction.js +1 -3
  254. package/dist/pipeline/msp-rebuild.js +1 -1
  255. package/dist/pipeline/projection-rebuild.js +13 -12
  256. package/dist/pipeline/system-hooks.js +39 -41
  257. package/dist/pipeline/system-identity-switch.js +1 -3
  258. package/dist/pipeline/tenant-timezone-cache.js +1 -1
  259. package/dist/random/generate.d.ts +29 -29
  260. package/dist/random/generate.js +30 -30
  261. package/dist/random/words.js +20 -20
  262. package/dist/rate-limit/middleware.js +9 -5
  263. package/dist/redis/client.js +2 -2
  264. package/dist/redis/pubsub-signal.js +14 -17
  265. package/dist/schema-cli.js +116 -22
  266. package/dist/search/index.js +4 -4
  267. package/dist/search/purge-subject.js +4 -5
  268. package/dist/search/reindex-entity.js +6 -9
  269. package/dist/time/boot-tz-warning.d.ts +2 -2
  270. package/dist/time/boot-tz-warning.js +8 -8
  271. package/dist/time/iana.js +10 -10
  272. package/dist/time/index.js +9 -9
  273. package/dist/time/legacy-date.d.ts +2 -2
  274. package/dist/time/legacy-date.js +6 -8
  275. package/dist/time/polyfill.js +1 -1
  276. package/dist/time/tz-context.d.ts +1 -1
  277. package/dist/time/tz-context.js +1 -1
  278. package/dist/ui-types/app-schema.js +7 -7
  279. package/dist/ui-types/list-row-meta.js +4 -4
  280. package/dist/upgrade-cli.js +10 -9
  281. package/dist/utils/compare.js +1 -1
  282. package/package.json +4 -4
  283. package/src/changes.json +48 -0
  284. package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
  285. package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
  286. package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
  287. package/src/scripts/codemod/pii-personal-migration.ts +17 -8
  288. package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
@@ -1,13 +1,13 @@
1
1
  import type { EntityDefinition } from "../engine/types/index.ts";
2
2
  /**
3
- * API → DB: locatedTimestamp-Felder zu zwei flachen Spalten flatten.
3
+ * API → DB: flatten locatedTimestamp fields into two flat columns.
4
4
  *
5
- * - `{ at, tz }` (UI-Form) → `{ <name>Utc, <name>Tz }` (utc via Temporal berechnet)
6
- * - `{ utc, tz }` (Server-Form) → utc gewinnt direkt
7
- * - `{ at, tz, utc }` → utc gewinnt; at wird ignoriert (Konsistenz-Check ist
8
- * Caller-Verantwortung)
5
+ * - `{ at, tz }` (UI form) → `{ <name>Utc, <name>Tz }` (`utc` calculated via Temporal)
6
+ * - `{ utc, tz }` (server form) → `utc` takes precedence
7
+ * - `{ at, tz, utc }` → `utc` takes precedence; `at` is ignored (the caller is
8
+ * responsible for the consistency check)
9
9
  *
10
- * Pure — mutiert nicht.
10
+ * Pure — does not mutate.
11
11
  */
12
12
  export declare function flattenLocatedTimestamp(payload: Record<string, unknown>, entity: EntityDefinition): Record<string, unknown>;
13
13
  /** Shape of a single rehydrated locatedTimestamp field. `at` is the wall clock in `tz`
@@ -18,12 +18,12 @@ export type LocatedTimestampRead = {
18
18
  readonly utc: string;
19
19
  };
20
20
  /**
21
- * DB → API: zwei flache Spalten zu combined { at, tz, utc } rehydraten.
21
+ * DB → API: rehydrate two flat columns into combined { at, tz, utc }.
22
22
  *
23
- * `at` ist immer Wall-Clock in der gespeicherten `tz` (Pickup-Ort-lokal).
24
- * Wer User-Sicht braucht, leitet aus `utc` selbst ab — der Server kennt
25
- * keine User-TZ.
23
+ * `at` is always the wall-clock time in the stored `tz` (local to the pickup
24
+ * location). To obtain a user-specific view, derive it from `utc` yourself —
25
+ * the server does not know the user's time zone.
26
26
  *
27
- * Pure — mutiert nicht.
27
+ * Pure — does not mutate.
28
28
  */
29
29
  export declare function rehydrateLocatedTimestamp(row: Record<string, unknown>, entity: EntityDefinition): Record<string, unknown>;
@@ -1,23 +1,22 @@
1
- // Auto-Convert für locatedTimestamp-Felder im DB-Layer.
1
+ // Automatically converts locatedTimestamp fields in the DB layer.
2
2
  //
3
- // Vertrag (siehe auch db/money.ts — gleicher Compound-Type-Pattern):
4
- // API-Form: { at, tz } | { utc, tz }
5
- // DB-Form: <name>Utc TIMESTAMPTZ + <name>Tz TEXT
6
- // Read-Form: { at, tz, utc }
3
+ // Contract (see also db/money.ts — the same compound-type pattern):
4
+ // API form: { at, tz } | { utc, tz }
5
+ // DB form: <name>Utc TIMESTAMPTZ + <name>Tz TEXT
6
+ // Read form: { at, tz, utc }
7
7
  //
8
- // `at`-Default-Sicht beim Read: Pickup-Ort-lokal (utc projiziert in
9
- // gespeicherter tz). Server kennt User-TZ nicht — User-spezifische
10
- // Anzeige passiert client-seitig aus utc.
8
+ // Default `at` view on read: local to the pickup location (`utc` projected into
9
+ // the stored `tz`). The server does not know the user's time zone, so
10
+ // user-specific display is derived from `utc` on the client.
11
11
  // Static import, not the ambient global: Bun doesn't expose Temporal on
12
12
  // globalThis, so this crashed with "Temporal is not defined" outside boot
13
- // paths that install it (#1480).
13
+ // paths that install it.
14
14
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
15
- // Sprint F: <name>Utc-Spalte ist jetzt instant() (siehe dialect.ts) —
16
- // Drizzle gibt direkt Temporal.Instant zurück. Vor Sprint F kam ein PG-
17
- // Wire-Format-String "2026-04-15 09:00:00+00" rein der via String-Massage
18
- // zu ISO-8601 gemacht werden musste. Heute übernimmt der customType die
19
- // Konversion DB↔Instant — diese Funktion ist nur noch defensive Glue für
20
- // Legacy-Code-Pfade die noch Strings durchreichen (z.B. raw SQL).
15
+ // The <name>Utc column uses instant() (see dialect.ts), so Drizzle returns Temporal.Instant.
16
+ // Previously, a PostgreSQL wire-format string "2026-04-15 09:00:00+00" had to be
17
+ // converted to ISO 8601 through string manipulation. The customType now handles
18
+ // DB↔Instant conversion, so this function is only defensive glue for legacy code
19
+ // paths that still pass strings (for example, raw SQL).
21
20
  function toInstant(value) {
22
21
  if (value instanceof Temporal.Instant)
23
22
  return value;
@@ -27,14 +26,14 @@ function toInstant(value) {
27
26
  return Temporal.Instant.from(iso);
28
27
  }
29
28
  /**
30
- * API → DB: locatedTimestamp-Felder zu zwei flachen Spalten flatten.
29
+ * API → DB: flatten locatedTimestamp fields into two flat columns.
31
30
  *
32
- * - `{ at, tz }` (UI-Form) → `{ <name>Utc, <name>Tz }` (utc via Temporal berechnet)
33
- * - `{ utc, tz }` (Server-Form) → utc gewinnt direkt
34
- * - `{ at, tz, utc }` → utc gewinnt; at wird ignoriert (Konsistenz-Check ist
35
- * Caller-Verantwortung)
31
+ * - `{ at, tz }` (UI form) → `{ <name>Utc, <name>Tz }` (`utc` calculated via Temporal)
32
+ * - `{ utc, tz }` (server form) → `utc` takes precedence
33
+ * - `{ at, tz, utc }` → `utc` takes precedence; `at` is ignored (the caller is
34
+ * responsible for the consistency check)
36
35
  *
37
- * Pure — mutiert nicht.
36
+ * Pure — does not mutate.
38
37
  */
39
38
  export function flattenLocatedTimestamp(payload, entity) {
40
39
  const T = Temporal;
@@ -53,10 +52,10 @@ export function flattenLocatedTimestamp(payload, entity) {
53
52
  if (pair.tz === undefined)
54
53
  continue;
55
54
  const tz = pair.tz;
56
- // Sprint F: <name>Utc-Spalte ist instant() — Drizzle erwartet
57
- // Temporal.Instant. Konvertierung pair.utc-string → Instant geht via
58
- // toInstant() (kennt String + Instant); pair.at + tz → Instant via
59
- // Temporal-Math.
55
+ // The <name>Utc column uses instant(); Drizzle expects
56
+ // Temporal.Instant. Conversion from pair.utc string → Instant goes through
57
+ // toInstant() (which handles strings and Instant values); pair.at + tz →
58
+ // Instant uses Temporal math.
60
59
  const instant = pair.utc !== undefined
61
60
  ? toInstant(pair.utc)
62
61
  : pair.at !== undefined
@@ -70,13 +69,13 @@ export function flattenLocatedTimestamp(payload, entity) {
70
69
  return result;
71
70
  }
72
71
  /**
73
- * DB → API: zwei flache Spalten zu combined { at, tz, utc } rehydraten.
72
+ * DB → API: rehydrate two flat columns into combined { at, tz, utc }.
74
73
  *
75
- * `at` ist immer Wall-Clock in der gespeicherten `tz` (Pickup-Ort-lokal).
76
- * Wer User-Sicht braucht, leitet aus `utc` selbst ab — der Server kennt
77
- * keine User-TZ.
74
+ * `at` is always the wall-clock time in the stored `tz` (local to the pickup
75
+ * location). To obtain a user-specific view, derive it from `utc` yourself —
76
+ * the server does not know the user's time zone.
78
77
  *
79
- * Pure — mutiert nicht.
78
+ * Pure — does not mutate.
80
79
  */
81
80
  export function rehydrateLocatedTimestamp(row, entity) {
82
81
  const result = { ...row };
@@ -3,7 +3,7 @@ import { type RetiredFrameworkTable } from "./retired-framework-tables.ts";
3
3
  declare const SNAPSHOT_VERSION: 1;
4
4
  export type Snapshot = {
5
5
  readonly version: typeof SNAPSHOT_VERSION;
6
- readonly generatedAt: string;
6
+ readonly generatedAt?: string;
7
7
  readonly tables: readonly EntityTableMeta[];
8
8
  };
9
9
  export type ColumnChange = {
@@ -1,31 +1,36 @@
1
- // Build-time Generator: EntityTableMeta[] → reviewbares SQL-Migration-File.
2
- // Ersatz für `drizzle-kit generate`.
1
+ // Build-time generator: EntityTableMeta[] → reviewable SQL migration file.
2
+ // Replacement for `drizzle-kit generate`.
3
3
  //
4
- // **NO-MAGIC-ON-DATA Kernprinzip** (siehe drizzle-replacement.md):
5
- // - Generator emittiert SQL-Files für PR-Review/Edit
6
- // - Generator wird NIE zur App-Runtime aufgerufen
7
- // - Output ist Start-Form; App-Author darf das SQL hand-editieren
8
- // (BRIN-Index, partial-Index, performance-tuning) bevor committed
9
- // - Append-only: bestehende Migration-Files werden NIE überschrieben.
10
- // Neue Schema-Diffs landen in einem NEUEN File mit incrementierter
11
- // Sequence-Number.
4
+ // **NO-MAGIC-ON-DATA core principle** (see drizzle-replacement.md):
5
+ // - The generator emits SQL files for PR review/editing.
6
+ // - The generator is NEVER called at app runtime.
7
+ // - The output is a starting point; app authors may hand-edit the SQL
8
+ // (BRIN index, partial index, performance tuning) before committing.
9
+ // - Append-only: existing migration files are NEVER overwritten.
10
+ // New schema diffs go into a NEW file with an incremented sequence number.
12
11
  //
13
- // **DESTRUCTIVE-ops** (DROP TABLE/COLUMN, INDEX-removal) werden als
14
- // AUSKOMMENTIERTE statements emittiert — Reviewer muss explizit
15
- // uncommenten + ggf. Data-Backup-Schritt vorlauf einfügen. Default:
16
- // niemals automatisch droppen.
12
+ // **DESTRUCTIVE operations** (DROP TABLE/COLUMN, index removal) are emitted
13
+ // as commented-out statements — reviewers must explicitly uncomment them
14
+ // and may need to add a data-backup step beforehand. Default:
15
+ // never drop automatically.
17
16
  import { readFileSync, writeFileSync } from "node:fs";
18
17
  import { errorCodeOf, isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
19
18
  import { compareByCodepoint } from "../utils/index.js";
20
19
  import { renderIndex, renderTableDdl } from "./render-ddl.js";
21
20
  import { findRetiredTableDrops } from "./retired-framework-tables.js";
22
21
  const SNAPSHOT_VERSION = 1;
22
+ function isSnapshot(value) {
23
+ return (isPlainObject(value) &&
24
+ value["version"] === SNAPSHOT_VERSION &&
25
+ (value["generatedAt"] === undefined || typeof value["generatedAt"] === "string") &&
26
+ Array.isArray(value["tables"]));
27
+ }
23
28
  export function snapshotFromMetas(metas) {
24
29
  // Stable ordering by tableName so the snapshot.json diff in PRs is
25
30
  // meaningful (table-add isn't a noisy re-sort of everything). Codepoint, not
26
31
  // localeCompare: the snapshot is serialized to byte-exact JSON and the table
27
32
  // order carries into the generated migration SQL — locale-dependent ordering
28
- // would drift between macOS-dev and Linux-CI (#367).
33
+ // would drift between macOS-dev and Linux-CI.
29
34
  const sorted = [...metas].sort((a, b) => compareByCodepoint(a.tableName, b.tableName));
30
35
  return {
31
36
  version: SNAPSHOT_VERSION,
@@ -41,6 +46,9 @@ export function loadSnapshotJson(path) {
41
46
  if (version !== SNAPSHOT_VERSION) {
42
47
  throw new Error(`Snapshot at ${path} has version ${String(version)}, expected ${SNAPSHOT_VERSION}`);
43
48
  }
49
+ if (!isSnapshot(parsed)) {
50
+ throw new Error(`Snapshot at ${path} is malformed: expected a tables list`);
51
+ }
44
52
  return parsed;
45
53
  }
46
54
  catch (err) {
@@ -53,7 +61,7 @@ export function writeSnapshotJson(path, snapshot) {
53
61
  writeFileSync(path, `${JSON.stringify(snapshot, null, 2)}\n`);
54
62
  }
55
63
  function indexMetaKey(idx) {
56
- // Index-Identität via Name (PG-Constraint: unique innerhalb der DB)
64
+ // Index identity is based on its name (PostgreSQL constraint: unique within the database).
57
65
  return idx.name;
58
66
  }
59
67
  function indexColumnsEqual(a, b) {
@@ -273,8 +281,7 @@ function renderColumnChange(tableName, change) {
273
281
  // timestamptz → date: PG's implicit ::date cast (what a bare ALTER ...
274
282
  // TYPE without USING falls back to) reads the session TimeZone, so the
275
283
  // same row migrates to a different calendar day depending on who/where
276
- // runs it — exactly the non-determinism kumiko-framework#1924 fixes.
277
- // Anchor explicitly at UTC instead of trusting the session.
284
+ // runs it. Anchor explicitly at UTC instead of trusting the session.
278
285
  if ((from === "timestamptz" || from === "timestamptz(3)") && to === "date") {
279
286
  out.push(`-- ${from} → date: explicit UTC anchor — a bare cast would use the session TimeZone (non-deterministic).`);
280
287
  out.push(`ALTER TABLE ${tbl} ALTER COLUMN ${col} TYPE date USING (${col} AT TIME ZONE 'UTC')::date;`);
@@ -383,7 +390,7 @@ export function renderMigrationSql(diff, options) {
383
390
  // needsManualWhere renders CREATE as a commented WARN (renderIndex),
384
391
  // since the generator can't express the new WHERE — the DROP has to
385
392
  // stay commented too, or the live index gets dropped with no
386
- // executable replacement (kumiko-framework#2492 review).
393
+ // executable replacement.
387
394
  lines.push(nextIdxMeta.needsManualWhere === true
388
395
  ? `-- (review) DROP INDEX IF EXISTS ${quoteIdent(idxChange.name)};`
389
396
  : `DROP INDEX IF EXISTS ${quoteIdent(idxChange.name)};`);
@@ -1,26 +1,26 @@
1
- // Migrate-Runner: applied checked-in SQL-Files gegen eine DB.
2
- // Ersatz für `drizzle-kit migrate` als Runtime-Komponente.
1
+ // Migration runner: applies checked-in SQL files to a database.
2
+ // Replaces `drizzle-kit migrate` as a runtime component.
3
3
  //
4
- // **NO-MAGIC-ON-DATA Kernprinzip** (siehe drizzle-replacement.md):
5
- // - Runner liest NUR checked-in `<dir>/*.sql` Files
6
- // - Kein Reach-back zu EntityDefinition zur Runtime
7
- // - Kein Auto-Apply-on-App-Boot — Deploy-Skript oder Init-Container ruft
8
- // `kumiko migrate apply` als eigenen Schritt
9
- // - Schema-Drift wird durch Checksum-Mismatch fail-loud detektiert
4
+ // **NO-MAGIC-ON-DATA core principle** (see drizzle-replacement.md):
5
+ // - The runner reads ONLY checked-in `<dir>/*.sql` files.
6
+ // - It does not reach back to EntityDefinition at runtime.
7
+ // - It does not auto-apply on app boot — a deploy script or init container
8
+ // invokes `kumiko migrate apply` as a separate step.
9
+ // - Schema drift is detected fail-loudly through checksum mismatches.
10
10
  //
11
- // Tracking-Table `_kumiko_migrations(id, applied_at, checksum)`. id = filename
12
- // ohne .sql-Endung. Checksum = sha256 vom File-Content beim Apply. Wenn ein
13
- // schon-appliedes File nachträglich editiert wird, schlägt der nächste Apply
14
- // fehl mit klarer Fehlermeldung (production-DBs haben das alte SQL drin —
15
- // edit nachträglich = production-state inkonsistent mit committed-state).
11
+ // Tracking table: `_kumiko_migrations(id, applied_at, checksum)`. `id` is the
12
+ // filename without the `.sql` suffix. The checksum is the sha256 hash of the
13
+ // file content at apply time. If an already-applied file is edited, the next
14
+ // apply fails with a clear error (production databases contain the old SQL —
15
+ // editing it makes production state inconsistent with committed state).
16
16
  import { createHash } from "node:crypto";
17
17
  import { existsSync, readdirSync, readFileSync } from "node:fs";
18
18
  import { join } from "node:path";
19
19
  import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
20
20
  import { asRawClient, hasBegin, } from "./query.js";
21
- // Advisory-lock key (random 32-bit int, stable). Verhindert dass zwei
22
- // gleichzeitig bootende Pods beide migrate apply laufen lassen (zweiter
23
- // blockiert bis erster fertig).
21
+ // Advisory-lock key (random 32-bit integer, stable). Prevents two Pods
22
+ // booting at the same time from both running migrate apply (the second
23
+ // blocks until the first finishes).
24
24
  export const MIGRATION_ADVISORY_LOCK_KEY = 0x6b756d69; // "kumi" in ASCII
25
25
  const MIGRATIONS_TABLE_DDL = `
26
26
  CREATE TABLE IF NOT EXISTS "_kumiko_migrations" (
@@ -147,7 +147,7 @@ function sha256Hex(content) {
147
147
  // returns Migration[] with id + checksum + statements. `preprocess` is an
148
148
  // optional hook applied to each file's raw content before splitting/hashing
149
149
  // — used by replayMigrationsDir to expand its DESTRUCTIVE-marker comments
150
- // without a second, drifting copy of this file-discovery logic (#1522/9).
150
+ // without a second, drifting copy of this file-discovery logic.
151
151
  export function loadMigrationsFromDir(dir, preprocess) {
152
152
  const files = readdirSync(dir)
153
153
  .filter((f) => f.endsWith(".sql"))
@@ -230,9 +230,9 @@ export class MigrationChecksumMismatchError extends Error {
230
230
  this.name = "MigrationChecksumMismatchError";
231
231
  }
232
232
  }
233
- // Apply pending migrations. Idempotent: schon applied migrations werden via
234
- // _kumiko_migrations-Lookup übersprungen. Checksum-Mismatch wirft fail-loud.
235
- // Advisory-Lock verhindert concurrent-apply-races.
233
+ // Applies pending migrations. Idempotent: already-applied migrations are
234
+ // skipped via the _kumiko_migrations lookup. Checksum mismatches fail loudly.
235
+ // The advisory lock prevents concurrent-apply races.
236
236
  export async function runMigrations(db, migrations) {
237
237
  // Session-level advisory lock and per-migration BEGIN/COMMIT must share one
238
238
  // session. Pool calls may land on different connections (the pool round-robins),
@@ -256,9 +256,9 @@ export async function runMigrations(db, migrations) {
256
256
  skippedIds.push(m.id);
257
257
  continue;
258
258
  }
259
- // Apply file content + INSERT tracking-row in einer TX. Wenn ein
260
- // Statement bricht, Rollback inkl. Tracking-Row → kein partial-apply
261
- // stuck in den books.
259
+ // Applies the file content and INSERT tracking row in one transaction. If a
260
+ // statement fails, the rollback includes the tracking row, so no partial
261
+ // apply remains stuck in the books.
262
262
  await conn.unsafe("BEGIN");
263
263
  try {
264
264
  for (const stmt of m.statements) {
@@ -289,8 +289,8 @@ export async function runMigrations(db, migrations) {
289
289
  await conn.release();
290
290
  }
291
291
  }
292
- // Convenience: load + run in einem Call. Wird vom `kumiko migrate apply`
293
- // CLI-Command + von Test-Setup-Helfern verwendet.
292
+ // Convenience: loads and runs migrations in one call. Used by the
293
+ // `kumiko migrate apply` CLI command and by test setup helpers.
294
294
  export async function runMigrationsFromDir(db, dir) {
295
295
  const migrations = loadMigrationsFromDir(dir);
296
296
  return runMigrations(db, migrations);
@@ -15,17 +15,17 @@ export type MoneyRead = {
15
15
  * @deprecated Removed in 0.390.0. Use `amountScaled` — this name implies ISO-4217 minor units
16
16
  * (cents), which is wrong once a currency needing a different scale than
17
17
  * the current flat MINOR_UNIT_SCALE=100 lands (e.g. JPY, 0 decimals).
18
- * Alias of `amountScaled`, kept until #1976/4 and #1976/5 migrate their
19
- * consumers off it.
18
+ * Alias of `amountScaled`, kept until the consumers migrate off it.
20
19
  */
21
20
  readonly amountMinor: number;
22
21
  };
23
22
  /**
24
- * DB → API: zwei flache Spalten zu combined { amount, currency } rehydraten.
23
+ * Rehydrate two flat DB columns into a combined `{ amount, currency }` value.
25
24
  *
26
- * Wirft loud bei korrupter DB-Form (string das nicht zur Zahl wird) — silent
27
- * data-loss wäre Bug-Vektor. NULL/undefined amount → field aus Output entfernt.
25
+ * Throws loudly for corrupt DB values (for example, a string that cannot be
26
+ * converted to a number), because silent data loss would hide a bug. A
27
+ * NULL/undefined amount removes the field from the output.
28
28
  *
29
- * Pure — mutiert nicht.
29
+ * Pure — does not mutate its input.
30
30
  */
31
31
  export declare function rehydrateMoney(row: Record<string, unknown>, entity: EntityDefinition): Record<string, unknown>;
package/dist/db/money.js CHANGED
@@ -1,17 +1,17 @@
1
- // Auto-Convert für money-Felder im DB-Layer.
1
+ // Automatically convert money fields in the DB layer.
2
2
  //
3
- // Vertrag (siehe auch db/located-timestamp.ts — gleicher Compound-Type-Pattern):
4
- // API-Form: { amount, currency } | number — amount in MAJOR units (56799.16 EUR)
5
- // DB-Form: <name> BIGINT (minor units, e.g. cents) + <name>Currency TEXT
6
- // Read-Form: { amount, currency, amountScaled } — amount in MAJOR units again,
7
- // amountScaled sits alongside as the exact integer value in
8
- // MINOR_UNIT_SCALE units (fw#1830) for callers that need
9
- // scale-exact comparisons (e.g. invoice sums) instead of
10
- // round-tripping the float amount through /100·*100.
3
+ // Contract (see also db/located-timestamp.ts — the same compound-type pattern):
4
+ // API form: { amount, currency } | number — amount in MAJOR units (56799.16 EUR)
5
+ // DB form: <name> BIGINT (minor units, e.g. cents) + <name>Currency TEXT
6
+ // Read form: { amount, currency, amountScaled } — amount in MAJOR units again,
7
+ // amountScaled sits alongside as the exact integer value in
8
+ // MINOR_UNIT_SCALE units for callers that need scale-exact
9
+ // comparisons (e.g. invoice sums) instead of round-tripping the
10
+ // float amount through /100·*100.
11
11
  // `amountMinor` stays as a @deprecated alias of the same
12
- // value until #1976/4 and #1976/5 migrate their remaining
13
- // consumers (renderer/web/primitives/index.tsx,
14
- // renderer/components/render-field.tsx) off the old name.
12
+ // value until the remaining consumers
13
+ // (renderer/web/primitives/index.tsx,
14
+ // renderer/components/render-field.tsx) migrate off the old name.
15
15
  //
16
16
  // table-builder.ts's moneyAmount column has always documented BIGINT as
17
17
  // "the integer minor unit" — this file used to just pass the API amount
@@ -23,13 +23,13 @@
23
23
  // pass/receive ordinary decimal amounts; DB storage stays exact-integer
24
24
  // cents (no float drift in SUM()/aggregate queries).
25
25
  //
26
- // Permissiv-Insert: primitive number wird als amount akzeptiert (Legacy aus
27
- // pre-Stufe-3-Samples). Currency fällt dann auf entity.defaultCurrency
28
- // zurück (oder DEFAULT_CURRENCIES[0] = "EUR" als Framework-Fallback).
26
+ // Permissive insert: a primitive number is accepted as amount (legacy from
27
+ // pre-Stufe-3 samples). Currency then falls back to entity.defaultCurrency
28
+ // (or DEFAULT_CURRENCIES[0] = "EUR" as the framework fallback).
29
29
  //
30
- // Anders als locatedTimestamp behalten wir den Field-Namen `<name>` als
31
- // amount-Spalte (Legacy DB-Convention für Money — `SUM(buying_price)` bleibt
32
- // idiomatisch). `<name>Currency` ist die zusätzliche Spalte.
30
+ // Unlike locatedTimestamp, we retain the field name `<name>` as the
31
+ // amount column (legacy DB convention for money — `SUM(buying_price)` remains
32
+ // idiomatic). `<name>Currency` is the additional column.
33
33
  import { MINOR_UNIT_SCALE, toMinorUnits } from "../engine/minor-units.js";
34
34
  import { DEFAULT_CURRENCIES } from "../engine/types/index.js";
35
35
  const FRAMEWORK_DEFAULT_CURRENCY = DEFAULT_CURRENCIES[0]; // "EUR"
@@ -46,9 +46,9 @@ function toMajorUnits(amountScaled) {
46
46
  // scaled the same way. `applyTotalsMatchRefinements` (schema-builder.ts)
47
47
  // uses this internally for `EmbeddedFieldDef.totalsMatch`; exported so a
48
48
  // custom write-handler doing its own total check doesn't have to re-derive
49
- // this unwrap-and-scale step by hand (kumiko-framework#1972 — a hand-rolled
50
- // comparison of a raw `{amount}` against a raw minor-unit row sum is exactly
51
- // what silently fails 100x off).
49
+ // this unwrap-and-scale step by hand. A hand-rolled comparison of a raw
50
+ // `{amount}` against a raw minor-unit row sum is exactly what silently fails
51
+ // 100× off.
52
52
  export function moneyPayloadToMinorUnits(raw) {
53
53
  if (typeof raw === "number")
54
54
  return toMinorUnits(raw);
@@ -81,7 +81,7 @@ export function flattenMoney(payload, entity) {
81
81
  }
82
82
  else if (typeof raw === "number") {
83
83
  amount = raw;
84
- // Expliziter currency-key im Payload überschreibt den Default-Fallback.
84
+ // An explicit currency key in the payload overrides the default fallback.
85
85
  const explicitCurrency = result[`${name}Currency`];
86
86
  currency = typeof explicitCurrency === "string" ? explicitCurrency : fallbackCurrency;
87
87
  }
@@ -95,12 +95,13 @@ export function flattenMoney(payload, entity) {
95
95
  return result;
96
96
  }
97
97
  /**
98
- * DB → API: zwei flache Spalten zu combined { amount, currency } rehydraten.
98
+ * Rehydrate two flat DB columns into a combined `{ amount, currency }` value.
99
99
  *
100
- * Wirft loud bei korrupter DB-Form (string das nicht zur Zahl wird) — silent
101
- * data-loss wäre Bug-Vektor. NULL/undefined amount → field aus Output entfernt.
100
+ * Throws loudly for corrupt DB values (for example, a string that cannot be
101
+ * converted to a number), because silent data loss would hide a bug. A
102
+ * NULL/undefined amount removes the field from the output.
102
103
  *
103
- * Pure — mutiert nicht.
104
+ * Pure — does not mutate its input.
104
105
  */
105
106
  export function rehydrateMoney(row, entity) {
106
107
  const result = { ...row };
@@ -126,7 +127,8 @@ export function rehydrateMoney(row, entity) {
126
127
  }
127
128
  }
128
129
  else if (typeof amountRaw === "string" && amountRaw !== "") {
129
- // PG-driver liefert BIGINT manchmal als String (>2^53 sicher).
130
+ // The PG driver sometimes returns BIGINT as a string (when it exceeds the
131
+ // safe integer range of 2^53).
130
132
  amountScaled = Number(amountRaw);
131
133
  if (!Number.isSafeInteger(amountScaled)) {
132
134
  throw new Error(`rehydrateMoney: field "${name}" amount string "${amountRaw}" is not a safe integer — DB corruption?`);
@@ -1,18 +1,18 @@
1
- // One-time backfill for pre-KMS plaintext PII in kumiko_events (#799).
1
+ // One-time backfill for pre-KMS plaintext PII in kumiko_events.
2
2
  //
3
- // Crypto-shredding (#724/#818) only covers NEW writes — events appended
3
+ // Crypto-shredding only covers NEW writes — events appended
4
4
  // before a KMS was configured still carry plaintext (user.created email,
5
5
  // delivery attempt recipientAddress, job payloads). This tool re-encrypts
6
6
  // them in place, per field, under the owning subject's DEK:
7
7
  //
8
8
  // - entity lifecycle events (<entity>.created/updated/deleted/forgotten/
9
9
  // restored) for every entity with PII subject annotations — generic over
10
- // subject kind (user/tenant/record, fw#2790), since it goes through the
10
+ // subject kind (user/tenant/record), since it goes through the
11
11
  // same collectPiiEncryptedFields/resolveSubjectForField the live write
12
12
  // path uses
13
13
  // - custom events from the event-PII catalog (r.defineEvent piiFields) —
14
14
  // user/tenant/self subjects, via the same resolveEventSubject the live
15
- // append path uses (fw#2801)
15
+ // append path uses
16
16
  //
17
17
  // Already-forgotten subjects must NOT get a fresh key minted for their old
18
18
  // plaintext — three erased-detection layers write [[erased]] instead:
@@ -28,7 +28,7 @@
28
28
  // Owner resolution for entity-lifecycle events. The live write path
29
29
  // (event-store-executor-write.ts) resolves the same chain via
30
30
  // subject-resolver.ts: row owner, then the owner the same event sets, then
31
- // the field's whenAbsent (fw#3654). Backfill adds two opt-in stages and
31
+ // the field's whenAbsent. Backfill adds two opt-in stages and
32
32
  // delays whenAbsent behind the projection so a known owner is never shadowed:
33
33
  // 1. payload — the event section (plus the owner the same event sets) names
34
34
  // the owner field; whenAbsent is not applied yet.
@@ -12,7 +12,7 @@ export async function alterTableAddColumn(db, tableName, columnName, columnType,
12
12
  // throwing when a column already exists with a different shape than
13
13
  // EntityTableMeta expects — that's how it surfaces drift. Callers that
14
14
  // just need an idempotent, race-safe backfill (e.g. event-consumer-state's
15
- // multi-instance boot path, #1362) opt in explicitly.
15
+ // multi-instance boot path) opt in explicitly.
16
16
  ifNotExists = false) {
17
17
  await asRawClient(db).unsafe(`ALTER TABLE ${quoteTableIdent(tableName)} ADD COLUMN ${ifNotExists ? "IF NOT EXISTS " : ""}${quoteTableIdent(columnName)} ${columnType}${defaultClause}${notNull}`);
18
18
  }
@@ -1,5 +1,5 @@
1
1
  import type { AnyDb } from "../query.ts";
2
- /** Stabiler Lock-Key für pg_advisory_xact_lock — multi-replica seed boots. */
2
+ /** Stable lock key for pg_advisory_xact_lock across multi-replica seed boots. */
3
3
  export declare const ES_OPS_ADVISORY_LOCK_KEY = 1702063984;
4
4
  export declare function acquireEsOpsAdvisoryLock(db: AnyDb): Promise<void>;
5
5
  export declare function esOperationExists(db: AnyDb, operationId: string): Promise<boolean>;
@@ -1,5 +1,5 @@
1
1
  import { asRawClient } from "../query.js";
2
- /** Stabiler Lock-Key für pg_advisory_xact_lock — multi-replica seed boots. */
2
+ /** Stable lock key for pg_advisory_xact_lock across multi-replica seed boots. */
3
3
  export const ES_OPS_ADVISORY_LOCK_KEY = 0x65_73_6f_70; // 'esop'
4
4
  export async function acquireEsOpsAdvisoryLock(db) {
5
5
  await asRawClient(db).unsafe(`SELECT pg_advisory_xact_lock($1)`, [ES_OPS_ADVISORY_LOCK_KEY]);
@@ -24,7 +24,7 @@ export async function lockEventConsumersShareMode(db) {
24
24
  await asRawClient(db).unsafe(`LOCK TABLE "kumiko_event_consumers" IN SHARE MODE`);
25
25
  }
26
26
  // The MAX(id) horizon plus every currently-open id gap below it, read from a
27
- // single statement snapshot. Used both to seed startFrom "now" (#3231) and to
27
+ // single statement snapshot. Used both to seed startFrom "now" and to
28
28
  // seed an MSP rebuild's handoff to the live dispatcher (msp-rebuild.ts).
29
29
  //
30
30
  // Scan lower bound: the consumer with the highest cursor already tracks every
@@ -84,7 +84,7 @@ export async function selectEventIdHorizonWithMissingRanges(db) {
84
84
  // WithMissingRanges): a transaction holding a lower id can still be
85
85
  // in-flight at boot, and without seeding those gaps its event would commit
86
86
  // after the cursor already sits above it, so `id > cursor` would never
87
- // deliver it (#3231). The cheap existence check skips the scan entirely for
87
+ // deliver it. The cheap existence check skips the scan entirely for
88
88
  // the (overwhelmingly common) case of an already-registered consumer.
89
89
  // ON CONFLICT DO NOTHING keeps both branches safe for an existing row: the
90
90
  // subquery only ever affects the row this statement inserts.
@@ -144,7 +144,7 @@ export async function markConsumerProcessing(db, name, instanceId) {
144
144
  // repeated infra blips (the exact case this backoff exists for) dead-letter
145
145
  // the consumer on the next real handler failure instead of after
146
146
  // maxAttempts. Visibility is covered by last_error, updated_at, and the
147
- // caller's console.error — status and the cursor are left as-is too, since
147
+ // caller's error log — status and the cursor are left as-is too, since
148
148
  // we don't know at this point whether the consumer should be "dead" (that's
149
149
  // the deliverEvents/maxAttempts contract, which never ran this pass).
150
150
  //
@@ -1,7 +1,9 @@
1
+ import { createFallbackLogger } from "../../logging/utils.js";
1
2
  import { constraintOf, isLockNotAvailable, isTableAlreadyExists, isUniqueViolation, } from "../pg-error.js";
2
3
  import { asRawClient, runInNewTransaction, runnerCanBeginTransaction, unsafeReadRetrying, } from "../query.js";
3
4
  import { withoutStatementTimeout } from "../statement-timeout.js";
4
5
  import { toPostgresArrayLiteral } from "./postgres-array-literal.js";
6
+ const log = createFallbackLogger("event-store");
5
7
  // Gap-finality (event-dispatcher pending_gaps) needs every holder of an
6
8
  // event id to already have a *real* xact id by the time it inserts — Postgres
7
9
  // assigns those lazily on first write, so a bare INSERT alone doesn't
@@ -81,9 +83,9 @@ async function buildIdempotencyKeyIndex(db) {
81
83
  // skip: sibling pod already built a valid index — this race loser is done.
82
84
  if ((await indexValidity(client)) === true)
83
85
  return;
84
- console.warn(`ensureIdempotencyKeyIndex: backed off on a "benign" race (${String(e)}) but ` +
86
+ log.warn(`ensureIdempotencyKeyIndex: backed off on a "benign" race but ` +
85
87
  `"events_idempotency_uq" is still missing/invalid afterward — likely a ` +
86
- `lock_timeout during CREATE INDEX CONCURRENTLY, not an actual winner/loser race.`);
88
+ `lock_timeout during CREATE INDEX CONCURRENTLY, not an actual winner/loser race.`, { err: e });
87
89
  throw e;
88
90
  }
89
91
  throw duplicateIdempotencyKeyErrorOr(e);
@@ -14,7 +14,7 @@ export async function markProjectionRebuilding(db, projectionName) {
14
14
  export async function selectEventsForProjectionRebuildBatch(db, aggregateTypes, eventTypes, afterId, limit) {
15
15
  // Archived streams don't replay (Marten-aligned): their aggregates are
16
16
  // frozen ops-tombstones — replaying them would resurrect rows (or, for
17
- // stranded duplicate-aggregates like fw#832, collide on unique indexes).
17
+ // stranded duplicate-aggregates, collide on unique indexes).
18
18
  return unsafeReadRetrying(db, `SELECT * FROM "kumiko_events" e
19
19
  WHERE e."aggregate_type" = ANY($1::text[])
20
20
  AND e."type" = ANY($2::text[])
@@ -29,9 +29,9 @@ export async function selectEventsForProjectionRebuildBatch(db, aggregateTypes,
29
29
  // Total subscribed events in the log — same source/type filter as
30
30
  // selectEventsForProjectionRebuildBatch, no id cursor. Under the cutover fence
31
31
  // this is the ground-truth count the rebuild must have applied; a shortfall
32
- // means a lower-id event committed late and the id-cursor leapt past it (#443).
32
+ // means a lower-id event committed late and the id-cursor leapt past it.
33
33
  export async function countSubscribedEvents(db, aggregateTypes, eventTypes) {
34
- // Same archived-streams exclusion as the batch query — the #443 recompute
34
+ // Same archived-streams exclusion as the batch query — the recompute
35
35
  // compares this count against applied events; a filter mismatch would make
36
36
  // every rebuild with an archived stream loop the full-re-replay forever.
37
37
  const rows = await unsafeReadRetrying(db, `SELECT count(*)::bigint AS n FROM "kumiko_events" e
@@ -1,9 +1,9 @@
1
1
  import type { AnyDb } from "../query.ts";
2
2
  /** Escape hatch for caller-built SQL that may write (or lock). No closed-connection retry —
3
- * retrying an ambiguous write risks double-apply (#1358). Prefer {@link executeRawQueryRead}
3
+ * retrying an ambiguous write risks double-apply. Prefer {@link executeRawQueryRead}
4
4
  * for SELECT-only paths. */
5
5
  export declare function executeRawQuery<T = Record<string, unknown>>(db: AnyDb, sqlText: string, params?: readonly unknown[]): Promise<readonly T[]>;
6
- /** SELECT-only escape hatch with the #1163 closed-connection retry. Do not pass
6
+ /** SELECT-only escape hatch with the closed-connection retry. Do not pass
7
7
  * INSERT/UPDATE/DELETE — retry re-executes the statement. */
8
8
  export declare function executeRawQueryRead<T = Record<string, unknown>>(db: AnyDb, sqlText: string, params?: readonly unknown[]): Promise<readonly T[]>;
9
9
  export declare function pingDatabase(db: AnyDb): Promise<void>;