@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,31 +1,24 @@
1
1
  import { getTemporal } from "../time/index.ts";
2
2
  export interface ZipEntry {
3
- /** Pfad im ZIP, slash-separated. ASCII (kein UTF-8-Flag gesetzt). */
3
+ /** Path inside the ZIP, slash-separated and UTF-8 encoded (general-purpose flag bit 11). */
4
4
  readonly path: string;
5
- /** Body als AsyncIterable — kann lazy generated sein (kein Upfront-Memory). */
5
+ /** Body as an AsyncIterable; it may be generated lazily to avoid upfront memory use. */
6
6
  readonly data: AsyncIterable<Uint8Array>;
7
7
  /**
8
- * Optional Modification-Time. Default = Now. ZIP nutzt MS-DOS-Format
9
- * (2-Sek-Aufloesung). Wer Audit-Trail-Zeitpunkte haben will, setzt
10
- * den Generation-Timestamp ueber alle Entries.
8
+ * Optional modification time. Defaults to the current time and uses the ZIP
9
+ * MS-DOS format with two-second precision. Set the generation timestamp on
10
+ * every entry when consistent audit-trail timestamps are required.
11
11
  */
12
12
  readonly mtime?: InstanceType<ReturnType<typeof getTemporal>["Instant"]>;
13
13
  }
14
14
  /**
15
- * Erzeugt einen ZIP-Stream als `AsyncIterable<Uint8Array>`. Caller (z.B.
16
- * `FileStorageProvider.writeStream`) konsumiert chunk-fuer-chunk, kein
17
- * Entry haengt im Memory bis fertig.
15
+ * Creates a ZIP stream as `AsyncIterable<Uint8Array>`. The caller, such as
16
+ * `FileStorageProvider.writeStream`, consumes the output chunk by chunk.
18
17
  *
19
- * Streaming-Lifecycle pro Entry:
20
- * 1. Local File Header (mit CRC32+size=0 als Placeholder; wir nutzen
21
- * KEINE Streaming-Data-Descriptor weil Storage-Provider Random-
22
- * Access nicht garantiert — wir collecten den Body in Memory pro
23
- * Entry um CRC + size **vor** dem Header zu kennen.
24
- * Trade-off: 1 Entry-Body im Memory at-a-time; bei <100 MB Files
25
- * OK, bei >1 GB single-Files braeuchte ZIP64+Streaming-Descriptor).
26
- * 2. File Body (raw bytes)
27
- * Am Ende:
28
- * 3. Central Directory (eine Liste-Entry pro File mit CRC + size)
29
- * 4. EOCD (End Of Central Directory)
18
+ * Each entry's body is collected in memory before its local file header is
19
+ * emitted because non-seekable storage providers cannot use a streaming data
20
+ * descriptor, and the header must contain the CRC32 and size. This limits
21
+ * memory use to one entry body at a time; ZIP64 plus streaming descriptors
22
+ * would be needed for larger single files.
30
23
  */
31
24
  export declare function createZipStream(entries: AsyncIterable<ZipEntry>): AsyncIterable<Uint8Array>;
@@ -1,27 +1,22 @@
1
1
  import { getTemporal } from "../time/index.js";
2
- // Streaming-ZIP-Builder (S2.U3 Atom 3a) — pure-JS, dependency-frei.
2
+ // Pure-JS streaming ZIP builder without dependencies.
3
3
  //
4
- // **Format-Wahl: STORE (kein DEFLATE).** Compression-frei + minimal —
5
- // fuer User-Data-Export-Bundles akzeptabel weil:
6
- // - Storage-Provider (S3/R2) komprimiert eh on-the-wire (HTTP gzip)
7
- // - JSON-Snippets sind klein, File-Binaries sind oft schon komprimiert
8
- // (PNG/JPG/PDF/MP4/...)
9
- // - DEFLATE braucht zlib + buffering pro Entry, killt das Streaming-
10
- // Property
4
+ // **Format: STORE (no DEFLATE).** Avoiding compression keeps the implementation minimal:
5
+ // - Storage providers (S3/R2) compress data on the wire (HTTP gzip).
6
+ // - JSON snippets are small, and file binaries are often already compressed
7
+ // (PNG/JPG/PDF/MP4/...).
8
+ // - DEFLATE requires zlib and per-entry buffering, which defeats streaming.
11
9
  //
12
- // **Streaming-Property:** Caller gibt `AsyncIterable<ZipEntry>`, jeder
13
- // Entry hat `data: AsyncIterable<Uint8Array>`. ZIP-Bytes werden
14
- // chunk-fuer-chunk yieldet — kein Entry haengt im Memory bis er fertig
15
- // ist. Storage-Provider's writeStream konsumiert direkt durch.
10
+ // **Streaming:** The caller provides `AsyncIterable<ZipEntry>`, and each entry
11
+ // has `data: AsyncIterable<Uint8Array>`. ZIP bytes are yielded chunk by chunk.
12
+ // The storage provider's writeStream consumes them directly.
16
13
  //
17
- // **ZIP-Standard:** APPNOTE.TXT v6.3.10 (April 2022). STORE-Method,
18
- // kein ZIP64 (max 4 GB pro Entry, max 65535 Entries — fuer User-Daten-
19
- // Exports ausreichend; ZIP64-Support kommt wenn ein realer User mit
20
- // >4 GB single-File auftaucht). Kein UTF-8-Flag (filenames sind ASCII
21
- // in unserem Use-Case: "profile.json", "files/<id>.<ext>").
14
+ // **ZIP standard:** APPNOTE.TXT v6.3.10 (April 2022). STORE method, no ZIP64
15
+ // (maximum 4 GB per entry and 65535 entries). ZIP64 support is needed before
16
+ // exporting a single file larger than 4 GB. The UTF-8 flag is enabled because
17
+ // filenames may contain non-ASCII characters.
22
18
  //
23
- // **CRC32:** klassisches IEEE-802.3-Polynom (0xEDB88320). Lookup-Table
24
- // einmal initialisiert + cached.
19
+ // **CRC32:** Classic IEEE-802.3 polynomial (0xEDB88320), with a cached lookup table.
25
20
  const ZIP_VERSION_NEEDED = 20; // 2.0 = STORE
26
21
  const ZIP_METHOD_STORE = 0;
27
22
  // Local file header signature: 0x04034b50 = "PK\x03\x04"
@@ -31,50 +26,38 @@ const CENTRAL_DIR_HEADER_SIG = 0x02014b50;
31
26
  // End of central directory record signature: 0x06054b50 = "PK\x05\x06"
32
27
  const EOCD_SIG = 0x06054b50;
33
28
  const VERSION_MADE_BY = 0x031e; // 0x03 = UNIX, 0x1e = ZIP 3.0
34
- // General Purpose Flag Bit 11 (0x0800) = UTF-8 filename + comment encoding.
35
- // Wird default-on gesetzt: ASCII ist gueltiges UTF-8, also Win-Win.
36
- // Ohne Flag interpretieren aeltere ZIP-Tools filenames als CP437 — bei
37
- // Umlauten ("Bügel.pdf") wird das Mojibake.
29
+ // General Purpose Flag Bit 11 (0x0800) enables UTF-8 encoding for filenames and comments.
30
+ // ASCII is valid UTF-8, while omitting the flag makes older ZIP tools interpret
31
+ // filenames as CP437, producing mojibake for non-ASCII characters such as umlauts.
38
32
  const GENERAL_PURPOSE_FLAGS = 0x0800;
39
- // External-Attrs (UNIX-Mode in High-Bytes) fuer regulaere Dateien mit
40
- // 0644-Permissions: `(S_IFREG | 0644) << 16 = 0o100644 << 16 = 0x81a40000`.
41
- // S_IFREG = 0o100000 (regular file), 0o644 = rw-r--r--. Ohne diese
42
- // Bits entpackt Info-ZIP mit Mode 0000 (EACCES beim Read).
33
+ // Store UNIX file permissions in the high bytes of the external attributes.
34
+ // Without `(S_IFREG | 0644) << 16 = 0o100644 << 16 = 0x81a40000`, Info-ZIP
35
+ // extracts regular files with mode 0000, causing EACCES when they are read.
43
36
  const UNIX_REGULAR_FILE_0644 = 0o100644 << 16; // 0x81a40000
44
- // ZIP-Format-Limits ohne ZIP64-Extension. Atom 3a-Scope ist STORE-only;
45
- // ZIP64 kommt wenn ein realer User-Export diese Caps reisst.
37
+ // STORE without ZIP64 is limited to 4 GB per entry and 65535 entries.
38
+ // ZIP64 support is required before these limits can be exceeded.
46
39
  const ZIP_MAX_ENTRY_SIZE = 0xffffffff; // uint32 → 4 GB
47
40
  const ZIP_MAX_ENTRIES = 0xffff; // uint16 → 65535
48
41
  /**
49
- * Erzeugt einen ZIP-Stream als `AsyncIterable<Uint8Array>`. Caller (z.B.
50
- * `FileStorageProvider.writeStream`) konsumiert chunk-fuer-chunk, kein
51
- * Entry haengt im Memory bis fertig.
42
+ * Creates a ZIP stream as `AsyncIterable<Uint8Array>`. The caller, such as
43
+ * `FileStorageProvider.writeStream`, consumes the output chunk by chunk.
52
44
  *
53
- * Streaming-Lifecycle pro Entry:
54
- * 1. Local File Header (mit CRC32+size=0 als Placeholder; wir nutzen
55
- * KEINE Streaming-Data-Descriptor weil Storage-Provider Random-
56
- * Access nicht garantiert — wir collecten den Body in Memory pro
57
- * Entry um CRC + size **vor** dem Header zu kennen.
58
- * Trade-off: 1 Entry-Body im Memory at-a-time; bei <100 MB Files
59
- * OK, bei >1 GB single-Files braeuchte ZIP64+Streaming-Descriptor).
60
- * 2. File Body (raw bytes)
61
- * Am Ende:
62
- * 3. Central Directory (eine Liste-Entry pro File mit CRC + size)
63
- * 4. EOCD (End Of Central Directory)
45
+ * Each entry's body is collected in memory before its local file header is
46
+ * emitted because non-seekable storage providers cannot use a streaming data
47
+ * descriptor, and the header must contain the CRC32 and size. This limits
48
+ * memory use to one entry body at a time; ZIP64 plus streaming descriptors
49
+ * would be needed for larger single files.
64
50
  */
65
51
  export async function* createZipStream(entries) {
66
52
  const centralDirRecords = [];
67
- let offset = 0; // Bytes-Counter fuer central-dir entry's local-header-offset
53
+ let offset = 0; // Tracks the local-header offset for each central-directory entry
68
54
  for await (const entry of entries) {
69
- // Body in Memory collecten — wir brauchen CRC32 + size VOR dem
70
- // local-file-header (kein streaming-data-descriptor weil
71
- // file-storage-providers nicht zwingend seek-able sind, aber wir
72
- // wollen vermeiden dass der konsumierende Storage zwei-pass laesst).
55
+ // Collect the body before writing the local file header: non-seekable storage
56
+ // providers cannot use a streaming data descriptor, and a two-pass read
57
+ // would be required otherwise.
73
58
  const body = await collectBody(entry.data);
74
- // Hard-Limit: STORE-Mode ohne ZIP64-Extension cappt bei uint32 (4 GB).
75
- // Bei Ueberschreitung wuerde der Integer-Wrap silent ein korruptes
76
- // ZIP produzieren (Decoder lesen Muell-Sizes). Lieber early-fail mit
77
- // klarer Begruendung — Worker (Atom 3b) fängt das + setzt Job=failed.
59
+ // STORE without ZIP64 uses uint32 sizes. Fail early instead of allowing integer
60
+ // wraparound to produce a corrupt ZIP with incorrect sizes.
78
61
  if (body.byteLength > ZIP_MAX_ENTRY_SIZE) {
79
62
  throw new Error(`ZIP-Entry "${entry.path}" exceeds 4 GB limit (${body.byteLength} bytes). ` +
80
63
  `STORE-mode without ZIP64-extension caps at uint32. ` +
@@ -105,8 +88,7 @@ export async function* createZipStream(entries) {
105
88
  lfh.set(filenameBytes, 30);
106
89
  yield lfh;
107
90
  yield body;
108
- // Central-Directory-Eintrag fuer diesen Entry — wir collecten ihn,
109
- // emittieren ihn am Ende.
91
+ // Keep central-directory entries until all file bodies have been emitted.
110
92
  const cdh = new Uint8Array(46 + filenameBytes.byteLength);
111
93
  const cdhView = new DataView(cdh.buffer);
112
94
  cdhView.setUint32(0, CENTRAL_DIR_HEADER_SIG, true);
@@ -130,7 +112,7 @@ export async function* createZipStream(entries) {
130
112
  centralDirRecords.push(cdh);
131
113
  offset += lfh.byteLength + body.byteLength;
132
114
  }
133
- // Central Directory: alle entries hintereinander
115
+ // Central directory: all entries back to back.
134
116
  const centralDirStart = offset;
135
117
  let centralDirSize = 0;
136
118
  for (const record of centralDirRecords) {
@@ -189,15 +171,14 @@ function crc32(data) {
189
171
  }
190
172
  return (crc ^ 0xffffffff) >>> 0;
191
173
  }
192
- // MS-DOS Date+Time encoding fuer ZIP-Header.
193
- // Date: bits 9-15=year-1980, 5-8=month, 0-4=day.
194
- // Time: bits 11-15=hour, 5-10=minute, 0-4=second/2.
174
+ // MS-DOS date and time encoding for ZIP headers.
175
+ // Date: bits 9-15 = year - 1980, 5-8 = month, 0-4 = day.
176
+ // Time: bits 11-15 = hour, 5-10 = minute, 0-4 = second / 2.
195
177
  //
196
- // **UTC** statt lokaler Zeitzone: in einem DSGVO-Kontext ist der
197
- // ZIP-mtime Teil des Auskunfts-Artefakts. Verschiedene Server-Zeitzonen
198
- // wuerden sonst verschiedene mtime-Werte fuer denselben Generation-
199
- // Instant produzieren — Audit-Drift. UTC ist der Standard fuer alle
200
- // server-side-Timestamps im Repo (Temporal.Instant ueberall).
178
+ // **UTC** instead of the local time zone: in a GDPR context the ZIP mtime is
179
+ // part of the data-access export. Different server time zones would otherwise produce
180
+ // different values for the same generation instant, causing audit drift. UTC is
181
+ // used for server-side timestamps throughout the repository.
201
182
  function toDosTime(i) {
202
183
  const dt = i.toZonedDateTimeISO("UTC");
203
184
  const year = dt.year;
@@ -16,7 +16,7 @@ export declare function booleanFacetOptionKeys(featureName: string, entityName:
16
16
  export declare function selectFacetOptionKey(featureName: string, entityName: string, fieldName: string, value: string): string;
17
17
  export declare function screenTitleKey(screenId: string): string;
18
18
  export type RequiredKeysOptions = {
19
- /** Bypass `isI18nKey`'s colon-only check for generated dot-form labels (fw#2260). */
19
+ /** Bypass `isI18nKey`'s colon-only check for generated dot-form labels. */
20
20
  readonly treatDotFormAsKey?: boolean;
21
21
  };
22
22
  export declare function requiredKeysFromScreen(featureName: string, screen: ScreenDefinition, options?: RequiredKeysOptions): readonly string[];
@@ -35,8 +35,8 @@ function isI18nKey(value) {
35
35
  /**
36
36
  * `treatAsKey` bypasses the colon-only `isI18nKey` check — used by the Settings-Hub
37
37
  * generator's dot-form labels (`${feature}.settings`, mask titles), which are
38
- * always i18n references by construction, never literal display text (fw#2260).
39
- * `isI18nKey` itself also accepts dot-form labels explicitly marked via `i18nKey()` (fw#2313).
38
+ * always i18n references by construction, never literal display text.
39
+ * `isI18nKey` itself also accepts dot-form labels explicitly marked via `i18nKey()`.
40
40
  */
41
41
  function pushKey(out, value, treatAsKey = false) {
42
42
  if (value !== undefined && (treatAsKey || isI18nKey(value)))
@@ -112,7 +112,7 @@ function pushDashboardScreenKeys(out, dashboard) {
112
112
  pushRowActionKeys(out, action);
113
113
  }
114
114
  function pushDashboardPanelKeys(out, panel) {
115
- // skip: custom-Panel übersetzt sich selbst, kein Key hier
115
+ // skip: custom panels translate themselves, so no key is collected here
116
116
  if (panel.kind === "custom")
117
117
  return;
118
118
  pushKey(out, panel.label);
@@ -251,7 +251,8 @@ export function requiredKeysFromScreen(featureName, screen, options = {}) {
251
251
  }
252
252
  case "projectionList": {
253
253
  const list = screen;
254
- // Keine Entity → keine field-label-Fallbacks; nur explizite Column-Labels.
254
+ // No entity means there are no field-label fallbacks; only explicit column labels are
255
+ // collected.
255
256
  for (const col of list.columns) {
256
257
  const normalized = normalizeListColumn(col);
257
258
  if (normalized.label !== undefined)
@@ -454,9 +455,8 @@ function collectEntityListFilterKeys(feature, out) {
454
455
  const entity = feature.entities?.[screen.entity];
455
456
  if (!entity)
456
457
  continue;
457
- for (const [fieldName, rawDef] of Object.entries(entity.fields)) {
458
- const def = rawDef;
459
- if (def.filterable !== true)
458
+ for (const [fieldName, def] of Object.entries(entity.fields)) {
459
+ if (!("filterable" in def) || def.filterable !== true)
460
460
  continue;
461
461
  if (def.type === "boolean") {
462
462
  for (const key of booleanFacetOptionKeys(feature.name, screen.entity, fieldName)) {
@@ -513,7 +513,7 @@ export function buildEffectiveTranslationKeys(features) {
513
513
  for (const key of Object.keys(feature.translations ?? {})) {
514
514
  out.add(`${feature.name}:${key}`);
515
515
  // Blank form too — Settings-Hub group namespaces (`group: "tenant-settings"`)
516
- // require `${group}.settings` which is not `${feature}:${key}` (fw#2314).
516
+ // require `${group}.settings` which is not `${feature}:${key}`.
517
517
  out.add(key);
518
518
  }
519
519
  }
@@ -66,6 +66,12 @@ export function serializeJobSubject(subject) {
66
66
  .sort()
67
67
  .map((field) => [field, subject[field] ?? null]));
68
68
  }
69
+ function isJobSubjectValue(value) {
70
+ return (value === null ||
71
+ typeof value === "string" ||
72
+ typeof value === "number" ||
73
+ typeof value === "boolean");
74
+ }
69
75
  // Stable identity for the declared payload fields: sorted keys, so two runs
70
76
  // with the same subject values produce the same string and the later one
71
77
  // replaces the earlier record. Non-primitives would serialize into something
@@ -76,7 +82,7 @@ export function jobSubjectKey(jobName, payload, fields, declaration) {
76
82
  const subject = {};
77
83
  for (const field of fields) {
78
84
  const value = payload[field] ?? null;
79
- if (value !== null && typeof value === "object") {
85
+ if (!isJobSubjectValue(value)) {
80
86
  throw new Error(`Job "${jobName}": ${declaration}.subjectFields["${field}"] must be a primitive, got ${typeof value}`);
81
87
  }
82
88
  subject[field] = value;
@@ -250,7 +256,7 @@ export function payloadMatchesTriggerFilter(where, payload) {
250
256
  // on the children still existing in Redis when a wrapper retry re-derives
251
257
  // their ids. The margin here is generous — queue wait time isn't bounded by
252
258
  // backoff — to cover realistic windows. Audit trail lives in read_job_runs,
253
- // not BullMQ (fw#3199).
259
+ // not BullMQ.
254
260
  export const COMPLETED_JOB_RETENTION_AGE_SEC = 86_400; // 24h
255
261
  export const FAILED_JOB_RETENTION_AGE_SEC = 604_800; // 7d
256
262
  // Total wall-clock time BullMQ can hold a perTenant wrapper across all its
@@ -7,7 +7,7 @@ export declare const ACTIVE_TENANT_IDS_QUERY_NAME = "tenant:query:active-tenant-
7
7
  * BullMQ job ids are `repeat:<schedulerId>:<millis>`. Colons inside the
8
8
  * scheduler id push the segment count to ≥5, which BullMQ's legacy heuristic
9
9
  * treated as old repeatables — spawning a new scheduler entry every tick and
10
- * leaking permanent `repeat:*` hashes (taskforcesh/bullmq#3828, fw#1603 /
10
+ * leaking permanent `repeat:*` hashes (taskforcesh/bullmq#3828,
11
11
  * publicstatus Redis OOM). Strip `.` and `:` from the job QN.
12
12
  */
13
13
  export declare function schedulerIdForJobName(jobName: string): string;
@@ -19,13 +19,13 @@ export const ACTIVE_TENANT_IDS_QUERY_NAME = "tenant:query:active-tenant-ids";
19
19
  * BullMQ job ids are `repeat:<schedulerId>:<millis>`. Colons inside the
20
20
  * scheduler id push the segment count to ≥5, which BullMQ's legacy heuristic
21
21
  * treated as old repeatables — spawning a new scheduler entry every tick and
22
- * leaking permanent `repeat:*` hashes (taskforcesh/bullmq#3828, fw#1603 /
22
+ * leaking permanent `repeat:*` hashes (taskforcesh/bullmq#3828,
23
23
  * publicstatus Redis OOM). Strip `.` and `:` from the job QN.
24
24
  */
25
25
  export function schedulerIdForJobName(jobName) {
26
26
  return `scheduler-${jobName.replace(/[.:]/g, "-")}`;
27
27
  }
28
- // Same colon-in-BullMQ-id hazard as schedulerIdForJobName (fw#1603/#1604) —
28
+ // Same colon-in-BullMQ-id hazard as schedulerIdForJobName —
29
29
  // a QN like "publicstatus:job:uptime-probe" must not leave ":" in the id.
30
30
  export function bootJobIdForJobName(jobName) {
31
31
  return `boot-${jobName.replace(/[.:]/g, "-")}`;
@@ -97,8 +97,7 @@ export async function removeOrphanJobSchedulers(queue, expectedSchedulerIds, lan
97
97
  // duplicates — safe only as long as the child job hash outlives the
98
98
  // wrapper's retry window, enforced at construction against
99
99
  // COMPLETED_JOB_RETENTION_AGE_SEC (see the invariant check in
100
- // createJobRunner). Same colon-in-BullMQ-id hazard as schedulerIdForJobName
101
- // (fw#1603/#1604) — the wrapper id already contains ":", so strip
100
+ // createJobRunner). The wrapper id already contains ":", so strip
102
101
  // separators from both halves before joining instead of interpolating raw.
103
102
  export function perTenantChildJobId(wrapperJobId, tenantId) {
104
103
  const sanitize = (value) => value.replace(/[.:]/g, "-");
@@ -139,8 +139,7 @@ export function createJobRunner(options) {
139
139
  lockRedis = new Redis(redisOpts);
140
140
  // Without a listener, a post-close 'error' (e.g. a teardown-race
141
141
  // "Connection is closed") is unhandled and crashes the process — in
142
- // bun:test it gets attributed to whichever test happens to run next
143
- // (fw#1805).
142
+ // bun:test it gets attributed to whichever test happens to run next.
144
143
  lockRedis.on("error", (err) => {
145
144
  const log = stopping ? errorLogger.debug : errorLogger.error;
146
145
  log("lock redis connection error", { error: err.message });
@@ -181,7 +180,7 @@ export function createJobRunner(options) {
181
180
  }),
182
181
  };
183
182
  // Same unhandled-'error'-crash hazard as lockRedis above, just via
184
- // BullMQ's internal ioredis client (fw#1805).
183
+ // BullMQ's internal ioredis client.
185
184
  for (const queue of Object.values(queues)) {
186
185
  queue.on("error", (err) => {
187
186
  const log = stopping ? errorLogger.debug : errorLogger.error;
@@ -557,7 +556,7 @@ export function createJobRunner(options) {
557
556
  // Same buildHandlerContext parity as ctx.files above: ctx.notify/ctx.config
558
557
  // must resolve for jobs the same way they do for write-handlers, or an
559
558
  // app-author job calling ctx.notify(...)/ctx.config(...) hits a TypeError
560
- // at runtime the write-handler path never would (framework#1532).
559
+ // at runtime the write-handler path never would.
561
560
  const notify = context._notifyFactory?.(jobSystemUser, tenantId, selfRunner, undefined, context._fileProviderResolver);
562
561
  const configDb = context.db; // @cast-boundary db-operator
563
562
  // Shared by the config accessor and ctx.derivatives below — both need the
@@ -739,7 +738,7 @@ export function createJobRunner(options) {
739
738
  const handlerResult = await requestContext.run({
740
739
  requestId: jobRequestId,
741
740
  correlationId: jobCorrelationId,
742
- // #3043 — events a job writes carry the job as their origin.
741
+ // Events a job writes carry the job as their origin.
743
742
  handler: jobName,
744
743
  feature: registry.getJobFeature(jobName) ?? qnScope(jobName),
745
744
  writeOrigin: jobOrigin,
@@ -794,7 +793,7 @@ export function createJobRunner(options) {
794
793
  "job.id": jobId,
795
794
  "job.attempt": attempt,
796
795
  "kumiko.tenant_id": tenantId,
797
- // Lane-routing attributes (Welle 2.6). `run_in` is the job's
796
+ // Lane-routing attributes. `run_in` is the job's
798
797
  // declared lane (explicit or default-"worker"); `consumer_lane`
799
798
  // is which runner actually executed it. They diverge in
800
799
  // all-in-one (both lanes live in one process) but must match
@@ -840,7 +839,7 @@ export function createJobRunner(options) {
840
839
  // Boot gates run inline and are awaited, so a throwing gate rejects start()
841
840
  // — and with it the entrypoint's start() and runProdApp's boot, before the
842
841
  // process reports ready. Enqueuing them (as runOnBoot does) would only fail
843
- // a queue job and let the deploy through (fw#2590).
842
+ // a queue job and let the deploy through.
844
843
  async function runBootGates(lane) {
845
844
  for (const [name, jobDef] of allJobs) {
846
845
  if (laneForJob(jobDef) !== lane)
@@ -978,7 +977,7 @@ export function createJobRunner(options) {
978
977
  // still-settling blocking connection: it rejects in-flight commands
979
978
  // via ioredis's flushQueue() during close(), which isn't a listenable
980
979
  // 'error' event — the only fix is to not return until both of the
981
- // worker's connections (main + blocking) are ready (fw#1805). This
980
+ // worker's connections (main + blocking) are ready. This
982
981
  // mirrors the wait BullMQ already does internally for
983
982
  // upsertJobScheduler()/add() below when the lane has a cron/boot job.
984
983
  // Racing a timeout against it keeps an unreachable Redis from hanging
@@ -1070,7 +1069,7 @@ export function createJobRunner(options) {
1070
1069
  }
1071
1070
  // BullMQ close() while a Queue's connection is still 'initializing'
1072
1071
  // rejects the in-flight INFO with "Connection is closed." on an emitter
1073
- // without listeners -> unhandled rejection (fw#1805). Wait for readiness
1072
+ // without listeners -> unhandled rejection. Wait for readiness
1074
1073
  // first; a failed/timed-out wait must not block the close itself.
1075
1074
  const stopReadyTimeoutMs = Math.min(bootRedisTimeoutMs, STOP_QUEUE_READY_TIMEOUT_MS);
1076
1075
  await settleAbandonedBootGates(stopReadyTimeoutMs);
@@ -1203,8 +1202,8 @@ export function createJobRunner(options) {
1203
1202
  for (const [name, jobDef] of allJobs) {
1204
1203
  if (!("on" in jobDef.trigger))
1205
1204
  continue;
1206
- // skip: andere Trigger-Formen (cron, manual) reagieren nicht auf
1207
- // Events. Nur "on"-Trigger werden hier matched.
1205
+ // skip: other trigger types (cron, manual) do not respond to events.
1206
+ // Only "on" triggers are matched here.
1208
1207
  const triggerOn = jobDef.trigger.on;
1209
1208
  const matches = Array.isArray(triggerOn)
1210
1209
  ? triggerOn.includes(eventName)
@@ -1218,10 +1217,9 @@ export function createJobRunner(options) {
1218
1217
  data["_tenantId"] = user.tenantId;
1219
1218
  data["_triggeredById"] = user.id;
1220
1219
  }
1221
- // Multi-Trigger: payload bekommt _triggerName damit der Handler
1222
- // weiß, welcher der N Trigger gefeuert hat. Bei Single-Trigger
1223
- // setzen wir es auch — kostet nichts und vereinfacht Handler-Code
1224
- // (kein "ist es Multi?"-Branch nötig).
1220
+ // Multi-trigger: add _triggerName to the payload so the handler knows which
1221
+ // of the N triggers fired. Set it for single triggers too; this costs
1222
+ // nothing and keeps handler code simple (no "is it multi?" branch).
1225
1223
  data["_triggerName"] = eventName;
1226
1224
  if (triggerEvent)
1227
1225
  data["_triggerEvent"] = triggerEvent;
@@ -1,5 +1,6 @@
1
1
  // Process lifecycle: 4-state machine + LIFO shutdown hooks.
2
2
  // Signal wiring lives in signal-handlers.ts; v1 scope in architecture/lifecycle.md.
3
+ import { createFallbackLogger } from "../logging/utils.js";
3
4
  const DEFAULT_DRAIN_TIMEOUT_MS = 40_000;
4
5
  export function createLifecycle(opts = {}) {
5
6
  const now = opts.now ?? (() => Date.now());
@@ -103,9 +104,6 @@ export function createLifecycle(opts = {}) {
103
104
  };
104
105
  }
105
106
  function makeErrorLogger(logger) {
106
- if (logger) {
107
- return (msg, err) => logger.error(`[lifecycle] ${msg}`, { err });
108
- }
109
- // biome-ignore lint/suspicious/noConsole: ops-visible fallback when no logger is wired
110
- return (msg, err) => console.error(`[lifecycle] ${msg}:`, err);
107
+ const log = createFallbackLogger("lifecycle", logger);
108
+ return (msg, err) => log.error(msg, { err });
111
109
  }
@@ -4,6 +4,9 @@
4
4
  export function attachSignalHandlers(lifecycle, opts = {}) {
5
5
  const signals = opts.signals ?? ["SIGTERM", "SIGINT"];
6
6
  const exitFn = opts.exit ?? ((code) => process.exit(code));
7
+ // bun-types vs @types/node: signal overload on process.on/off is not
8
+ // selectable via bind() under the merged types — EventEmitter seam is intentional.
9
+ const processEmitter = process;
7
10
  const listeners = new Map();
8
11
  // Guard so double-SIGTERM doesn't call exitFn twice. drain() is already
9
12
  // idempotent via its shared promise, but its .then/.catch chain would fire
@@ -24,15 +27,13 @@ export function attachSignalHandlers(lifecycle, opts = {}) {
24
27
  .then(() => exitFn(0))
25
28
  .catch(() => exitFn(1));
26
29
  };
27
- // bun-types vs @types/node: signal overload on process.on/off is not
28
- // selectable via bind() under the merged types — EventEmitter seam is intentional.
29
- process.on(sig, handler);
30
+ processEmitter.on(sig, handler);
30
31
  listeners.set(sig, handler);
31
32
  }
32
33
  return {
33
34
  detach: () => {
34
35
  for (const [sig, handler] of listeners) {
35
- process.off(sig, handler);
36
+ processEmitter.off(sig, handler);
36
37
  }
37
38
  listeners.clear();
38
39
  },
@@ -1,9 +1,10 @@
1
1
  import type { Logger } from "./types.ts";
2
2
  type FallbackLogger = {
3
+ info(msg: string, data?: Record<string, unknown>): void;
3
4
  error(msg: string, data?: Record<string, unknown>): void;
4
5
  warn(msg: string, data?: Record<string, unknown>): void;
5
6
  debug(msg: string, data?: Record<string, unknown>): void;
6
7
  };
7
8
  export declare function setProcessLogger(logger: Logger | null): void;
8
- export declare function createFallbackLogger(namespace: string, logger?: (Pick<Logger, "error"> & Partial<Pick<Logger, "warn" | "debug">>) | undefined): FallbackLogger;
9
+ export declare function createFallbackLogger(namespace: string, logger?: (Pick<Logger, "error"> & Partial<Pick<Logger, "info" | "warn" | "debug" | "child">>) | undefined): FallbackLogger;
9
10
  export {};
@@ -3,9 +3,33 @@ let processLogger = null;
3
3
  export function setProcessLogger(logger) {
4
4
  processLogger = logger;
5
5
  }
6
+ // Bun prints a trailing "undefined" when a second argument is passed explicitly as undefined.
7
+ function writeConsole(write, line, data) {
8
+ if (data === undefined)
9
+ write(line);
10
+ else
11
+ write(line, data);
12
+ }
6
13
  export function createFallbackLogger(namespace, logger) {
14
+ if (logger?.child) {
15
+ const child = logger.child({ component: namespace });
16
+ return {
17
+ info: (msg, data) => child.info(msg, data),
18
+ error: (msg, data) => child.error(msg, data),
19
+ warn: (msg, data) => child.warn(msg, data),
20
+ debug: (msg, data) => child.debug(msg, data),
21
+ };
22
+ }
7
23
  if (logger) {
8
24
  return {
25
+ info(msg, data) {
26
+ if (logger.info) {
27
+ logger.info(`[${namespace}] ${msg}`, data);
28
+ }
29
+ else {
30
+ writeConsole(console.info, `[${namespace}] ${msg}`, data);
31
+ }
32
+ },
9
33
  error(msg, data) {
10
34
  logger.error(`[${namespace}] ${msg}`, data);
11
35
  },
@@ -14,8 +38,7 @@ export function createFallbackLogger(namespace, logger) {
14
38
  logger.warn(`[${namespace}] ${msg}`, data);
15
39
  }
16
40
  else {
17
- // biome-ignore lint/suspicious/noConsole: ops-visible fallback when the wrapped logger has no warn method
18
- console.warn(`[${namespace}] ${msg}`, data);
41
+ writeConsole(console.warn, `[${namespace}] ${msg}`, data);
19
42
  }
20
43
  },
21
44
  debug(msg, data) {
@@ -23,8 +46,7 @@ export function createFallbackLogger(namespace, logger) {
23
46
  logger.debug(`[${namespace}] ${msg}`, data);
24
47
  }
25
48
  else {
26
- // biome-ignore lint/suspicious/noConsole: ops-visible fallback when the wrapped logger has no debug method
27
- console.debug(`[${namespace}] ${msg}`, data);
49
+ writeConsole(console.debug, `[${namespace}] ${msg}`, data);
28
50
  }
29
51
  },
30
52
  };
@@ -41,14 +63,22 @@ export function createFallbackLogger(namespace, logger) {
41
63
  return cachedChild;
42
64
  };
43
65
  return {
66
+ info(msg, data) {
67
+ const child = resolveProcessChild();
68
+ if (child) {
69
+ child.info(msg, data);
70
+ }
71
+ else {
72
+ writeConsole(console.info, `[${namespace}] ${msg}`, data);
73
+ }
74
+ },
44
75
  error(msg, data) {
45
76
  const child = resolveProcessChild();
46
77
  if (child) {
47
78
  child.error(msg, data);
48
79
  }
49
80
  else {
50
- // biome-ignore lint/suspicious/noConsole: ops-visible fallback when no logger is wired
51
- console.error(`[${namespace}] ${msg}`, data);
81
+ writeConsole(console.error, `[${namespace}] ${msg}`, data);
52
82
  }
53
83
  },
54
84
  warn(msg, data) {
@@ -57,8 +87,7 @@ export function createFallbackLogger(namespace, logger) {
57
87
  child.warn(msg, data);
58
88
  }
59
89
  else {
60
- // biome-ignore lint/suspicious/noConsole: ops-visible fallback when no logger is wired
61
- console.warn(`[${namespace}] ${msg}`, data);
90
+ writeConsole(console.warn, `[${namespace}] ${msg}`, data);
62
91
  }
63
92
  },
64
93
  debug(msg, data) {
@@ -67,8 +96,7 @@ export function createFallbackLogger(namespace, logger) {
67
96
  child.debug(msg, data);
68
97
  }
69
98
  else {
70
- // biome-ignore lint/suspicious/noConsole: ops-visible fallback when no logger is wired
71
- console.debug(`[${namespace}] ${msg}`, data);
99
+ writeConsole(console.debug, `[${namespace}] ${msg}`, data);
72
100
  }
73
101
  },
74
102
  };
@@ -4,5 +4,5 @@ export { assertKumikoSchemaCurrent, detectKumikoDrift, formatKumikoDriftReport,
4
4
  export { createPendingRebuildsTable, enqueueProjectionRebuild, listPendingRebuilds, PROJECTION_REBUILD_JOB, pendingRebuildsTable, queueRebuildsFromMarkers, rebuildProjectionOrMultiStream, reportPendingRebuildRun, runPendingRebuilds, } from "./pending-rebuilds.js";
5
5
  // Boot-time backfill: re-encrypts plaintext events after a PII annotation was added.
6
6
  export { createPiiBackfillStateTable, piiAnnotationFingerprint, piiBackfillStateTable, runPiiEventBackfill, } from "./pii-event-backfill.js";
7
- // tableName → projection-name, für den app-seitigen Projection-Rebuild.
7
+ // tableName → projection-name, for application-side projection rebuilds.
8
8
  export { buildProjectionTableIndex } from "./projection-table-index.js";