@cosmicdrift/kumiko-framework 0.1.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 (388) hide show
  1. package/README.md +159 -0
  2. package/package.json +91 -0
  3. package/src/__tests__/anonymous-access.integration.ts +325 -0
  4. package/src/__tests__/error-contract.integration.ts +435 -0
  5. package/src/__tests__/field-access.integration.ts +269 -0
  6. package/src/__tests__/full-stack.integration.ts +914 -0
  7. package/src/__tests__/ownership.integration.ts +449 -0
  8. package/src/__tests__/reference-data.integration.ts +198 -0
  9. package/src/__tests__/transition-guard.integration.ts +340 -0
  10. package/src/api/__tests__/api.test.ts +337 -0
  11. package/src/api/__tests__/auth-middleware-transport.test.ts +80 -0
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +179 -0
  13. package/src/api/__tests__/batch.integration.ts +404 -0
  14. package/src/api/__tests__/body-limit.test.ts +88 -0
  15. package/src/api/__tests__/csrf-middleware.test.ts +97 -0
  16. package/src/api/__tests__/dispatcher-live.integration.ts +216 -0
  17. package/src/api/__tests__/metrics-endpoint.test.ts +126 -0
  18. package/src/api/__tests__/nested-write.integration.ts +213 -0
  19. package/src/api/__tests__/readiness.test.ts +76 -0
  20. package/src/api/__tests__/request-id-middleware.test.ts +72 -0
  21. package/src/api/__tests__/sse-broker.test.ts +58 -0
  22. package/src/api/__tests__/sse-route.test.ts +112 -0
  23. package/src/api/anonymous-cookie.ts +60 -0
  24. package/src/api/api-constants.ts +64 -0
  25. package/src/api/auth-middleware.ts +418 -0
  26. package/src/api/auth-routes.ts +982 -0
  27. package/src/api/csrf-middleware.ts +77 -0
  28. package/src/api/index.ts +31 -0
  29. package/src/api/jwt.ts +66 -0
  30. package/src/api/observability-middleware.ts +89 -0
  31. package/src/api/readiness.ts +132 -0
  32. package/src/api/request-context.ts +49 -0
  33. package/src/api/request-id-middleware.ts +50 -0
  34. package/src/api/route-registrars.ts +195 -0
  35. package/src/api/routes.ts +135 -0
  36. package/src/api/server.ts +640 -0
  37. package/src/api/sse-broker.ts +71 -0
  38. package/src/api/sse-route.ts +62 -0
  39. package/src/api/tokens.ts +16 -0
  40. package/src/db/__tests__/apply-entity-event-tenant.integration.ts +159 -0
  41. package/src/db/__tests__/compound-types.test.ts +114 -0
  42. package/src/db/__tests__/connection-options.test.ts +68 -0
  43. package/src/db/__tests__/cursor.test.ts +41 -0
  44. package/src/db/__tests__/db-helpers.test.ts +369 -0
  45. package/src/db/__tests__/dialect-instant.test.ts +50 -0
  46. package/src/db/__tests__/drizzle-helpers.integration.ts +186 -0
  47. package/src/db/__tests__/drizzle-table-types.test.ts +162 -0
  48. package/src/db/__tests__/encryption.test.ts +39 -0
  49. package/src/db/__tests__/event-store-executor-list.integration.ts +313 -0
  50. package/src/db/__tests__/event-store-executor.integration.ts +235 -0
  51. package/src/db/__tests__/implicit-projection-equivalence.integration.ts +304 -0
  52. package/src/db/__tests__/located-timestamp.test.ts +184 -0
  53. package/src/db/__tests__/money.test.ts +199 -0
  54. package/src/db/__tests__/multi-row-insert.integration.ts +76 -0
  55. package/src/db/__tests__/parse-auto-verb.test.ts +70 -0
  56. package/src/db/__tests__/required-not-null-migration-safety.integration.ts +105 -0
  57. package/src/db/__tests__/row-helpers.test.ts +59 -0
  58. package/src/db/__tests__/schema-migration.integration.ts +273 -0
  59. package/src/db/__tests__/table-builder-indexes.test.ts +153 -0
  60. package/src/db/__tests__/table-builder-required.test.ts +216 -0
  61. package/src/db/__tests__/tenant-db.integration.ts +606 -0
  62. package/src/db/__tests__/unique-violation-mapping.integration.ts +166 -0
  63. package/src/db/apply-entity-event.ts +188 -0
  64. package/src/db/assert-exists-in.ts +59 -0
  65. package/src/db/compound-types.ts +47 -0
  66. package/src/db/connection.ts +104 -0
  67. package/src/db/cursor.ts +83 -0
  68. package/src/db/dialect.ts +109 -0
  69. package/src/db/eagerload.ts +174 -0
  70. package/src/db/encryption.ts +39 -0
  71. package/src/db/event-store-executor.ts +906 -0
  72. package/src/db/index.ts +55 -0
  73. package/src/db/located-timestamp.ts +114 -0
  74. package/src/db/money.ts +120 -0
  75. package/src/db/pg-error.ts +46 -0
  76. package/src/db/reference-data.ts +77 -0
  77. package/src/db/row-helpers.ts +53 -0
  78. package/src/db/schema-inspection.ts +25 -0
  79. package/src/db/table-builder.ts +475 -0
  80. package/src/db/tenant-db.ts +434 -0
  81. package/src/engine/__tests__/auth-claims-registrar.test.ts +74 -0
  82. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +108 -0
  83. package/src/engine/__tests__/boot-validator.test.ts +1865 -0
  84. package/src/engine/__tests__/build-app-schema.test.ts +154 -0
  85. package/src/engine/__tests__/claim-keys.test.ts +274 -0
  86. package/src/engine/__tests__/config-helpers.test.ts +236 -0
  87. package/src/engine/__tests__/effective-features.test.ts +86 -0
  88. package/src/engine/__tests__/engine.test.ts +1461 -0
  89. package/src/engine/__tests__/entity-handlers.test.ts +274 -0
  90. package/src/engine/__tests__/event-helpers.test.ts +68 -0
  91. package/src/engine/__tests__/extends-registrar.test.ts +159 -0
  92. package/src/engine/__tests__/factories-long-text.test.ts +84 -0
  93. package/src/engine/__tests__/factories-time.test.ts +158 -0
  94. package/src/engine/__tests__/field-predicates.test.ts +48 -0
  95. package/src/engine/__tests__/hook-phases.test.ts +132 -0
  96. package/src/engine/__tests__/identifiers.test.ts +35 -0
  97. package/src/engine/__tests__/lifecycle-hooks.test.ts +237 -0
  98. package/src/engine/__tests__/nav.test.ts +267 -0
  99. package/src/engine/__tests__/ownership.test.ts +421 -0
  100. package/src/engine/__tests__/parse-ref-target.test.ts +43 -0
  101. package/src/engine/__tests__/projection-helpers.test.ts +62 -0
  102. package/src/engine/__tests__/projection.test.ts +191 -0
  103. package/src/engine/__tests__/qualified-name.test.ts +264 -0
  104. package/src/engine/__tests__/resolve-config-or-param.test.ts +315 -0
  105. package/src/engine/__tests__/run-in.test.ts +38 -0
  106. package/src/engine/__tests__/schema-builder.test.ts +380 -0
  107. package/src/engine/__tests__/screen.test.ts +408 -0
  108. package/src/engine/__tests__/state-machine.test.ts +148 -0
  109. package/src/engine/__tests__/system-user.test.ts +57 -0
  110. package/src/engine/__tests__/validation-hooks.test.ts +71 -0
  111. package/src/engine/access.ts +23 -0
  112. package/src/engine/boot-validator.ts +1528 -0
  113. package/src/engine/build-app-schema.ts +125 -0
  114. package/src/engine/config-helpers.ts +115 -0
  115. package/src/engine/constants.ts +85 -0
  116. package/src/engine/create-app.ts +98 -0
  117. package/src/engine/define-feature.ts +702 -0
  118. package/src/engine/define-handler.ts +78 -0
  119. package/src/engine/define-roles.ts +19 -0
  120. package/src/engine/effective-features.ts +87 -0
  121. package/src/engine/entity-handlers.ts +364 -0
  122. package/src/engine/event-helpers.ts +73 -0
  123. package/src/engine/factories.ts +328 -0
  124. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +416 -0
  125. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +197 -0
  126. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +128 -0
  127. package/src/engine/feature-ast/__tests__/parse.test.ts +888 -0
  128. package/src/engine/feature-ast/__tests__/patch.test.ts +360 -0
  129. package/src/engine/feature-ast/__tests__/patcher.test.ts +469 -0
  130. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +287 -0
  131. package/src/engine/feature-ast/extractors.ts +2562 -0
  132. package/src/engine/feature-ast/index.ts +105 -0
  133. package/src/engine/feature-ast/parse.ts +369 -0
  134. package/src/engine/feature-ast/patch.ts +525 -0
  135. package/src/engine/feature-ast/patcher.ts +518 -0
  136. package/src/engine/feature-ast/patterns.ts +434 -0
  137. package/src/engine/feature-ast/render.ts +602 -0
  138. package/src/engine/feature-ast/source-location.ts +45 -0
  139. package/src/engine/field-access.ts +120 -0
  140. package/src/engine/index.ts +254 -0
  141. package/src/engine/ownership.ts +337 -0
  142. package/src/engine/parse-ref-target.ts +22 -0
  143. package/src/engine/pattern-library/__tests__/library.test.ts +351 -0
  144. package/src/engine/pattern-library/index.ts +24 -0
  145. package/src/engine/pattern-library/library.ts +1117 -0
  146. package/src/engine/pattern-library/types.ts +255 -0
  147. package/src/engine/projection-helpers.ts +85 -0
  148. package/src/engine/qualified-name.ts +122 -0
  149. package/src/engine/read-claim.ts +31 -0
  150. package/src/engine/registry.ts +1325 -0
  151. package/src/engine/resolve-config-or-param.ts +153 -0
  152. package/src/engine/run-in.ts +29 -0
  153. package/src/engine/schema-builder.ts +175 -0
  154. package/src/engine/screen-filter-ops.ts +51 -0
  155. package/src/engine/state-machine.ts +70 -0
  156. package/src/engine/system-user.ts +32 -0
  157. package/src/engine/types/config.ts +306 -0
  158. package/src/engine/types/event-type-map.ts +37 -0
  159. package/src/engine/types/feature.ts +574 -0
  160. package/src/engine/types/fields.ts +422 -0
  161. package/src/engine/types/handlers.ts +742 -0
  162. package/src/engine/types/hooks.ts +142 -0
  163. package/src/engine/types/http-route.ts +54 -0
  164. package/src/engine/types/identifiers.ts +47 -0
  165. package/src/engine/types/index.ts +208 -0
  166. package/src/engine/types/nav.ts +46 -0
  167. package/src/engine/types/projection.ts +132 -0
  168. package/src/engine/types/relations.ts +51 -0
  169. package/src/engine/types/screen.ts +452 -0
  170. package/src/engine/types/workspace.ts +42 -0
  171. package/src/engine/validation.ts +33 -0
  172. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.ts +173 -0
  173. package/src/entrypoint/__tests__/split-deploy.integration.ts +297 -0
  174. package/src/entrypoint/index.ts +442 -0
  175. package/src/errors/__tests__/classes.test.ts +371 -0
  176. package/src/errors/__tests__/write-failures.test.ts +109 -0
  177. package/src/errors/classes.ts +249 -0
  178. package/src/errors/i18n/de.yaml +83 -0
  179. package/src/errors/i18n/en.yaml +80 -0
  180. package/src/errors/index.ts +41 -0
  181. package/src/errors/kumiko-error.ts +67 -0
  182. package/src/errors/reasons.ts +36 -0
  183. package/src/errors/serialize.ts +136 -0
  184. package/src/errors/transition-details.ts +30 -0
  185. package/src/errors/write-error-info.ts +123 -0
  186. package/src/errors/zod-bridge.ts +49 -0
  187. package/src/event-store/__tests__/admin-api.integration.ts +361 -0
  188. package/src/event-store/__tests__/event-store.integration.ts +584 -0
  189. package/src/event-store/__tests__/get-stream-version-perf.integration.ts +83 -0
  190. package/src/event-store/__tests__/perf.integration.ts +255 -0
  191. package/src/event-store/__tests__/snapshot.integration.ts +267 -0
  192. package/src/event-store/__tests__/upcaster-dead-letter.integration.ts +204 -0
  193. package/src/event-store/__tests__/upcaster.integration.ts +460 -0
  194. package/src/event-store/admin-api.ts +257 -0
  195. package/src/event-store/archive.ts +106 -0
  196. package/src/event-store/errors.ts +35 -0
  197. package/src/event-store/event-store.ts +405 -0
  198. package/src/event-store/events-schema.ts +90 -0
  199. package/src/event-store/index.ts +50 -0
  200. package/src/event-store/snapshot.ts +210 -0
  201. package/src/event-store/upcaster-dead-letter.ts +119 -0
  202. package/src/event-store/upcaster.ts +147 -0
  203. package/src/files/__tests__/content-disposition.test.ts +123 -0
  204. package/src/files/__tests__/file-field-column.integration.ts +103 -0
  205. package/src/files/__tests__/file-field-pipeline.integration.ts +211 -0
  206. package/src/files/__tests__/file-handle.test.ts +122 -0
  207. package/src/files/__tests__/files.integration.ts +830 -0
  208. package/src/files/__tests__/storage-tracking.integration.ts +153 -0
  209. package/src/files/content-disposition.ts +55 -0
  210. package/src/files/file-handle.ts +63 -0
  211. package/src/files/file-ref-table.ts +22 -0
  212. package/src/files/file-routes.ts +353 -0
  213. package/src/files/in-memory-provider.ts +62 -0
  214. package/src/files/index.ts +29 -0
  215. package/src/files/local-provider.ts +35 -0
  216. package/src/files/storage-tracking.ts +60 -0
  217. package/src/files/types.ts +118 -0
  218. package/src/i18n/__tests__/i18n.test.ts +72 -0
  219. package/src/i18n/index.ts +29 -0
  220. package/src/jobs/__tests__/job-event-trigger.integration.ts +172 -0
  221. package/src/jobs/__tests__/job-multi-trigger.integration.ts +144 -0
  222. package/src/jobs/__tests__/jobs.integration.ts +566 -0
  223. package/src/jobs/index.ts +2 -0
  224. package/src/jobs/job-runner.ts +574 -0
  225. package/src/lifecycle/__tests__/create-test-lifecycle.ts +19 -0
  226. package/src/lifecycle/__tests__/lifecycle-server.integration.ts +108 -0
  227. package/src/lifecycle/__tests__/lifecycle.test.ts +212 -0
  228. package/src/lifecycle/__tests__/signal-handlers.test.ts +106 -0
  229. package/src/lifecycle/index.ts +13 -0
  230. package/src/lifecycle/lifecycle.ts +160 -0
  231. package/src/lifecycle/signal-handlers.ts +62 -0
  232. package/src/logging/__tests__/pino-trace-bridge.test.ts +50 -0
  233. package/src/logging/index.ts +3 -0
  234. package/src/logging/pino-logger.ts +64 -0
  235. package/src/logging/types.ts +7 -0
  236. package/src/migrations/__tests__/compare-snapshots.test.ts +150 -0
  237. package/src/migrations/__tests__/detect-drift.integration.ts +320 -0
  238. package/src/migrations/__tests__/detect-projections-to-rebuild.integration.ts +134 -0
  239. package/src/migrations/__tests__/rebuild-marker.test.ts +79 -0
  240. package/src/migrations/index.ts +28 -0
  241. package/src/migrations/projection-detection.ts +149 -0
  242. package/src/migrations/rebuild-marker.ts +64 -0
  243. package/src/migrations/schema-drift.ts +395 -0
  244. package/src/observability/__tests__/console-provider.test.ts +67 -0
  245. package/src/observability/__tests__/metric-validator.test.ts +87 -0
  246. package/src/observability/__tests__/noop-provider.test.ts +82 -0
  247. package/src/observability/__tests__/observability.integration.ts +559 -0
  248. package/src/observability/__tests__/prometheus-meter.test.ts +144 -0
  249. package/src/observability/__tests__/recording-meter.test.ts +101 -0
  250. package/src/observability/__tests__/recording-tracer.test.ts +110 -0
  251. package/src/observability/__tests__/sensitive-filter.test.ts +98 -0
  252. package/src/observability/console-provider.ts +130 -0
  253. package/src/observability/context.ts +26 -0
  254. package/src/observability/fallback.ts +34 -0
  255. package/src/observability/ids.ts +25 -0
  256. package/src/observability/index.ts +79 -0
  257. package/src/observability/metric-validator.ts +86 -0
  258. package/src/observability/metrics-handle.ts +56 -0
  259. package/src/observability/noop-provider.ts +146 -0
  260. package/src/observability/prometheus-meter.ts +284 -0
  261. package/src/observability/recording-meter.ts +156 -0
  262. package/src/observability/recording-tracer.ts +198 -0
  263. package/src/observability/redis-wrapper.ts +132 -0
  264. package/src/observability/sensitive-filter.ts +108 -0
  265. package/src/observability/standard-metrics.ts +213 -0
  266. package/src/observability/types/index.ts +29 -0
  267. package/src/observability/types/metric.ts +56 -0
  268. package/src/observability/types/provider.ts +32 -0
  269. package/src/observability/types/span.ts +64 -0
  270. package/src/pipeline/__tests__/archive-stream.integration.ts +220 -0
  271. package/src/pipeline/__tests__/auth-claims-resolver.test.ts +279 -0
  272. package/src/pipeline/__tests__/cascade-handler.integration.ts +419 -0
  273. package/src/pipeline/__tests__/cascade-handler.test.ts +52 -0
  274. package/src/pipeline/__tests__/causation-chain.integration.ts +206 -0
  275. package/src/pipeline/__tests__/ctx-bridge.integration.ts +234 -0
  276. package/src/pipeline/__tests__/dispatcher.test.ts +379 -0
  277. package/src/pipeline/__tests__/distributed-lock.integration.ts +67 -0
  278. package/src/pipeline/__tests__/domain-events-projections.integration.ts +323 -0
  279. package/src/pipeline/__tests__/event-dedup.integration.ts +153 -0
  280. package/src/pipeline/__tests__/event-define-event-strict.integration.ts +202 -0
  281. package/src/pipeline/__tests__/event-dispatcher-lifecycle.integration.ts +220 -0
  282. package/src/pipeline/__tests__/event-dispatcher-multi-instance.integration.ts +423 -0
  283. package/src/pipeline/__tests__/event-dispatcher-pg-listen.integration.ts +123 -0
  284. package/src/pipeline/__tests__/event-dispatcher-recovery.integration.ts +202 -0
  285. package/src/pipeline/__tests__/event-dispatcher-second-audit.integration.ts +290 -0
  286. package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +65 -0
  287. package/src/pipeline/__tests__/event-dispatcher.integration.ts +287 -0
  288. package/src/pipeline/__tests__/event-retention.integration.ts +239 -0
  289. package/src/pipeline/__tests__/fetch-for-writing.integration.ts +281 -0
  290. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +430 -0
  291. package/src/pipeline/__tests__/load-aggregate-query.integration.ts +266 -0
  292. package/src/pipeline/__tests__/msp-error-mode.integration.ts +149 -0
  293. package/src/pipeline/__tests__/msp-multi-hop.integration.ts +228 -0
  294. package/src/pipeline/__tests__/msp-rebuild.integration.ts +368 -0
  295. package/src/pipeline/__tests__/multi-stream-projection.integration.ts +341 -0
  296. package/src/pipeline/__tests__/perf-rebuild.integration.ts +147 -0
  297. package/src/pipeline/__tests__/projection-rebuild.integration.ts +551 -0
  298. package/src/pipeline/__tests__/query-projection.integration.ts +201 -0
  299. package/src/pipeline/__tests__/redis-pipeline.integration.ts +306 -0
  300. package/src/pipeline/append-event-core.ts +117 -0
  301. package/src/pipeline/auth-claims-resolver.ts +103 -0
  302. package/src/pipeline/cascade-handler.ts +113 -0
  303. package/src/pipeline/dispatcher.ts +1585 -0
  304. package/src/pipeline/distributed-lock.ts +37 -0
  305. package/src/pipeline/entity-cache.ts +113 -0
  306. package/src/pipeline/event-consumer-state.ts +108 -0
  307. package/src/pipeline/event-dedup.ts +23 -0
  308. package/src/pipeline/event-dispatcher.ts +1016 -0
  309. package/src/pipeline/event-retention.ts +154 -0
  310. package/src/pipeline/idempotency.ts +76 -0
  311. package/src/pipeline/index.ts +66 -0
  312. package/src/pipeline/lifecycle-pipeline.ts +409 -0
  313. package/src/pipeline/msp-rebuild.ts +242 -0
  314. package/src/pipeline/multi-stream-apply-context.ts +115 -0
  315. package/src/pipeline/projection-rebuild.ts +334 -0
  316. package/src/pipeline/projection-state.ts +72 -0
  317. package/src/pipeline/projections-runner.ts +56 -0
  318. package/src/pipeline/redis-keys.ts +11 -0
  319. package/src/pipeline/system-hooks.ts +190 -0
  320. package/src/random/__tests__/generate.test.ts +149 -0
  321. package/src/random/generate.ts +141 -0
  322. package/src/random/index.ts +8 -0
  323. package/src/random/words.ts +392 -0
  324. package/src/rate-limit/__tests__/dispatcher-l3.integration.ts +111 -0
  325. package/src/rate-limit/__tests__/middleware.integration.ts +189 -0
  326. package/src/rate-limit/__tests__/resolver.integration.ts +189 -0
  327. package/src/rate-limit/bucket.ts +36 -0
  328. package/src/rate-limit/index.ts +14 -0
  329. package/src/rate-limit/middleware.ts +152 -0
  330. package/src/rate-limit/resolver.ts +267 -0
  331. package/src/redis/__tests__/redis-options.test.ts +54 -0
  332. package/src/redis/index.ts +74 -0
  333. package/src/search/__tests__/meilisearch-adapter.integration.ts +236 -0
  334. package/src/search/__tests__/search-adapter.test.ts +256 -0
  335. package/src/search/in-memory-adapter.ts +123 -0
  336. package/src/search/index.ts +12 -0
  337. package/src/search/meilisearch-adapter.ts +106 -0
  338. package/src/search/types.ts +39 -0
  339. package/src/secrets/__tests__/dek-cache.test.ts +213 -0
  340. package/src/secrets/__tests__/env-master-key-provider.test.ts +119 -0
  341. package/src/secrets/__tests__/envelope.test.ts +74 -0
  342. package/src/secrets/__tests__/leak-guard.test.ts +92 -0
  343. package/src/secrets/__tests__/rotation.test.ts +149 -0
  344. package/src/secrets/dek-cache.ts +116 -0
  345. package/src/secrets/env-master-key-provider.ts +162 -0
  346. package/src/secrets/envelope.ts +55 -0
  347. package/src/secrets/index.ts +19 -0
  348. package/src/secrets/leak-guard.ts +87 -0
  349. package/src/secrets/rotation.ts +34 -0
  350. package/src/secrets/types.ts +107 -0
  351. package/src/stack/db.ts +104 -0
  352. package/src/stack/event-collector.ts +23 -0
  353. package/src/stack/index.ts +32 -0
  354. package/src/stack/redis.ts +44 -0
  355. package/src/stack/request-helper.ts +168 -0
  356. package/src/stack/table-helpers.ts +104 -0
  357. package/src/stack/test-stack.ts +357 -0
  358. package/src/stack/test-users.ts +37 -0
  359. package/src/testing/__tests__/e2e-generator.test.ts +230 -0
  360. package/src/testing/__tests__/ensure-entity-table.integration.ts +54 -0
  361. package/src/testing/access-assertions.ts +15 -0
  362. package/src/testing/assertions.ts +35 -0
  363. package/src/testing/e2e-generator.ts +465 -0
  364. package/src/testing/expect-error.ts +25 -0
  365. package/src/testing/handler-context.ts +125 -0
  366. package/src/testing/http-cookies.ts +52 -0
  367. package/src/testing/index.ts +41 -0
  368. package/src/testing/late-bound.ts +39 -0
  369. package/src/testing/mutable-master-key-provider.ts +31 -0
  370. package/src/testing/observability-recorder.ts +54 -0
  371. package/src/testing/shared-entities.ts +49 -0
  372. package/src/testing/utils.ts +1 -0
  373. package/src/testing/wait-for.ts +31 -0
  374. package/src/time/__tests__/polyfill.test.ts +73 -0
  375. package/src/time/__tests__/tz-context.test.ts +121 -0
  376. package/src/time/index.ts +21 -0
  377. package/src/time/polyfill.ts +70 -0
  378. package/src/time/tz-context.ts +107 -0
  379. package/src/ui-types/app-schema.ts +57 -0
  380. package/src/ui-types/index.ts +65 -0
  381. package/src/utils/__tests__/assert.test.ts +17 -0
  382. package/src/utils/__tests__/env-parse.test.ts +54 -0
  383. package/src/utils/assert.ts +18 -0
  384. package/src/utils/env-parse.ts +16 -0
  385. package/src/utils/ids.ts +16 -0
  386. package/src/utils/index.ts +5 -0
  387. package/src/utils/safe-json.ts +30 -0
  388. package/src/utils/serialization.ts +7 -0
@@ -0,0 +1,190 @@
1
+ import type { SseBroker } from "../api/sse-broker";
2
+ import type { DbRow } from "../db/connection";
3
+ import { tenantChannel } from "../engine/constants";
4
+ import type { EntityId, Registry } from "../engine/types";
5
+ import type { SearchAdapter, SearchDocument } from "../search/types";
6
+ import type { EventConsumer } from "./event-dispatcher";
7
+
8
+ // --- Search Index Consumer (async, via event-dispatcher) ---
9
+ //
10
+ // Search-Indexierung läuft seit D.4 als async EventConsumer über den event-
11
+ // dispatcher, nicht mehr als synchroner postSave/postDelete-hook. Das
12
+ // spiegelt Marten's ISubscription-Pattern: ein einziger async Pfad für alle
13
+ // non-inline side-effects.
14
+ //
15
+ // Event → Search-Op Mapping:
16
+ //
17
+ // <entity>.created → index(tenantId, doc)
18
+ // <entity>.updated → index(tenantId, doc) // re-index mit neuem state
19
+ // <entity>.restored → index(tenantId, doc) // wiederbeleben
20
+ // <entity>.deleted → remove(tenantId, type, id)
21
+ //
22
+ // Der Document-State wird aus dem Event rekonstruiert (kein SaveContext
23
+ // mehr available). Regel:
24
+ //
25
+ // created: state = event.payload // ganze entity ist im payload
26
+ // updated: state = { ...previous, ...changes } // rekonstruiert neuen state
27
+ // restored: state = event.payload.previous // restored field-set
28
+ //
29
+ // Sensitive fields sind aus dem event log bereits gestrippt (event-store-
30
+ // executor.ts), also kriegt der Search-Index sie ebenfalls nicht — das ist
31
+ // die gleiche Garantie wie vorher beim postSave-hook.
32
+ //
33
+ // Batch-Variante gibt's aktuell nicht mehr — jeder Event triggert einen
34
+ // eigenen index()-call. Wenn Performance nach Scale-Messung das erfordert,
35
+ // kann der event-dispatcher später eine Batch-Handler-Variante bekommen.
36
+ export const SEARCH_CONSUMER_NAME = "system:consumer:search";
37
+
38
+ export function createSearchEventConsumer(
39
+ searchAdapter: SearchAdapter,
40
+ registry: Registry,
41
+ ): EventConsumer {
42
+ return {
43
+ name: SEARCH_CONSUMER_NAME,
44
+ handler: async (event) => {
45
+ const entityName = event.aggregateType;
46
+ const verb = event.type.split(".").pop();
47
+ const tenantId = event.tenantId;
48
+
49
+ // skip: delete takes an early-return after removing the index entry —
50
+ // the "reconstruct state" path below only makes sense for created/
51
+ // updated/restored, which carry field data in the payload.
52
+ if (verb === "deleted") {
53
+ await searchAdapter.remove(tenantId, entityName, event.aggregateId);
54
+ return;
55
+ }
56
+
57
+ if (verb !== "created" && verb !== "updated" && verb !== "restored") {
58
+ // skip: other event types (custom domain events, future verbs) don't
59
+ // carry a search-indexable payload shape. If a future feature needs
60
+ // them indexed, it registers its own multiStreamProjection.
61
+ return;
62
+ }
63
+
64
+ const state = reconstructStateForSearch(event.payload, verb);
65
+ const doc = buildSearchDocument(entityName, event.aggregateId, state, registry);
66
+ if (!doc) {
67
+ // skip: entity isn't searchable (no searchable fields declared)
68
+ return;
69
+ }
70
+ await searchAdapter.index(tenantId, doc);
71
+ },
72
+ };
73
+ }
74
+
75
+ // Rebuild the entity-state a search index needs from the event-payload alone.
76
+ // Three shapes to handle — see event-store-executor.ts for the emitter side.
77
+ function reconstructStateForSearch(
78
+ payload: Record<string, unknown>,
79
+ verb: "created" | "updated" | "restored",
80
+ ): Record<string, unknown> {
81
+ if (verb === "created") {
82
+ // create: payload IS the entity (minus sensitive fields, already
83
+ // stripped by event-store-executor)
84
+ return payload;
85
+ }
86
+ if (verb === "updated") {
87
+ // update: payload = { changes, previous }. Merge to get the new state
88
+ // the index should reflect. Sensitive fields already filtered out.
89
+ const previous = (payload["previous"] as Record<string, unknown> | undefined) ?? {}; // @cast-boundary engine-payload
90
+ const changes = (payload["changes"] as Record<string, unknown> | undefined) ?? {}; // @cast-boundary engine-payload
91
+ return { ...previous, ...changes };
92
+ }
93
+ // restored: payload = { previous }. The restored entity is whatever the
94
+ // field-values were at delete time — restore copies them back verbatim.
95
+ return (payload["previous"] as Record<string, unknown> | undefined) ?? {}; // @cast-boundary engine-payload
96
+ }
97
+
98
+ // Build a SearchDocument from raw field-state. Parallel to the old
99
+ // buildSearchDocument that took a SaveContext — same selector logic, just
100
+ // a different input shape.
101
+ function buildSearchDocument(
102
+ entityName: string,
103
+ entityId: EntityId,
104
+ state: Record<string, unknown>,
105
+ registry: Registry,
106
+ ): SearchDocument | null {
107
+ const entity = registry.getEntity(entityName);
108
+ if (!entity) return null;
109
+
110
+ const searchableFields = registry.getSearchableFields(entityName);
111
+ if (searchableFields.length === 0) return null;
112
+
113
+ const embeddedFields = new Set<string>();
114
+ for (const [fname, fdef] of Object.entries(entity.fields)) {
115
+ if (fdef.type === "embedded") embeddedFields.add(fname);
116
+ }
117
+
118
+ const fields: Record<string, unknown> = {};
119
+ for (const f of searchableFields) {
120
+ const underscoreIdx = f.indexOf("_");
121
+ if (underscoreIdx > 0) {
122
+ const parentKey = f.slice(0, underscoreIdx);
123
+ if (embeddedFields.has(parentKey)) {
124
+ const subKey = f.slice(underscoreIdx + 1);
125
+ const parent = state[parentKey];
126
+ if (parent && typeof parent === "object") {
127
+ const value = (parent as DbRow)[subKey];
128
+ if (value !== undefined) fields[f] = value;
129
+ }
130
+ continue;
131
+ }
132
+ }
133
+ if (state[f] !== undefined) {
134
+ fields[f] = state[f];
135
+ }
136
+ }
137
+
138
+ return {
139
+ entityType: entityName,
140
+ entityId,
141
+ weight: entity.searchWeight ?? 1,
142
+ fields,
143
+ };
144
+ }
145
+
146
+ // --- SSE Broadcast (async, via event-dispatcher) ---
147
+ //
148
+ // SSE-Broadcast läuft seit D.3 als async EventConsumer über den event-
149
+ // dispatcher, nicht mehr als synchroner postSave/postDelete-hook. Das hat
150
+ // zwei Konsequenzen:
151
+ //
152
+ // 1. **Event-native Payload-Shape.** Der SSE-event spiegelt den StoredEvent:
153
+ // `type` ist event.type ("user.created", "unit.updated"), `data` enthält
154
+ // id, aggregateType, version und die event-payload — keine künstliche
155
+ // "system:event:<entity>:<verb>" Hülle mehr. Clients haben direkten
156
+ // Zugriff auf `payload.changes` + `payload.previous` (wie im event-log).
157
+ // 2. **Eventual consistency statt Read-after-Write.** Ein SSE-Event kommt
158
+ // ~10–100ms nach dem HTTP-200 (abhängig von pollIntervalMs). UI-Clients
159
+ // die auf optimistic-update setzen merken das nicht; strictly-waiting
160
+ // Clients müssten poll-after-write.
161
+ //
162
+ // Tests drain deterministisch via `await stack.eventDispatcher.runOnce()`.
163
+ export const SSE_BROADCAST_CONSUMER_NAME = "system:consumer:sse-broadcast";
164
+
165
+ export function createSseBroadcastEventConsumer(sseBroker: SseBroker): EventConsumer {
166
+ return {
167
+ name: SSE_BROADCAST_CONSUMER_NAME,
168
+ // Per-instance delivery: each API process has its own pool of SSE
169
+ // clients and its own cursor. Every instance reads every event
170
+ // independently and pushes to its local clients. Without this, in a
171
+ // split-deploy (API-1/API-2 + Worker, Welle 2.5), only ONE API
172
+ // instance would pick up each event (shared-cursor SKIP LOCKED) and
173
+ // the other instance's clients would never see updates. SSE clients
174
+ // pin to a specific API process via the HTTP long-poll; cross-process
175
+ // delivery isn't solvable by sharing a cursor.
176
+ delivery: "per-instance",
177
+ handler: async (event) => {
178
+ sseBroker.pushToChannel(tenantChannel(event.tenantId), {
179
+ type: event.type,
180
+ data: {
181
+ id: event.aggregateId,
182
+ aggregateType: event.aggregateType,
183
+ version: event.version,
184
+ payload: event.payload,
185
+ createdAt: event.createdAt,
186
+ },
187
+ });
188
+ },
189
+ };
190
+ }
@@ -0,0 +1,149 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import {
3
+ ADJECTIVES,
4
+ generateAdjNounName,
5
+ generateNoConfusableId,
6
+ generateUniqueName,
7
+ NOUNS,
8
+ } from "../index";
9
+
10
+ describe("generateAdjNounName", () => {
11
+ test("default: <adj>-<noun> aus den Standard-Listen", () => {
12
+ const name = generateAdjNounName();
13
+ const [adj, noun, ...rest] = name.split("-");
14
+ expect(rest).toEqual([]);
15
+ expect(ADJECTIVES).toContain(adj);
16
+ expect(NOUNS).toContain(noun);
17
+ });
18
+
19
+ test("custom separator", () => {
20
+ const name = generateAdjNounName({ separator: "_" });
21
+ expect(name).toMatch(/^[a-z]+_[a-z]+$/);
22
+ const [adj, noun] = name.split("_");
23
+ expect(ADJECTIVES).toContain(adj);
24
+ expect(NOUNS).toContain(noun);
25
+ });
26
+
27
+ test("mit suffix: <adj>-<noun>-<suffix>", () => {
28
+ const name = generateAdjNounName({ suffix: { length: 3 } });
29
+ const parts = name.split("-");
30
+ expect(parts).toHaveLength(3);
31
+ const [adj, noun, suffix] = parts;
32
+ expect(ADJECTIVES).toContain(adj);
33
+ expect(NOUNS).toContain(noun);
34
+ expect(suffix).toMatch(/^[a-z2-9]{3}$/);
35
+ // No-confusable: keine 0/1/o/l/i
36
+ expect(suffix).not.toMatch(/[01oli]/);
37
+ });
38
+
39
+ test("custom adjectives + nouns Wahl", () => {
40
+ const name = generateAdjNounName({
41
+ adjectives: ["rapid"],
42
+ nouns: ["receiver"],
43
+ });
44
+ expect(name).toBe("rapid-receiver");
45
+ });
46
+
47
+ test("Statistical: 100 Generierungen → mindestens 5 verschiedene", () => {
48
+ const names = new Set<string>();
49
+ for (let i = 0; i < 100; i++) {
50
+ names.add(generateAdjNounName());
51
+ }
52
+ // 22500 combos, 100 picks → erwartete Diversität ist hoch.
53
+ // Lower bound 5 fängt nur einen kompletten RNG-Defekt.
54
+ expect(names.size).toBeGreaterThan(5);
55
+ });
56
+ });
57
+
58
+ describe("generateNoConfusableId", () => {
59
+ test("returns string der gewünschten Länge", () => {
60
+ expect(generateNoConfusableId(8)).toHaveLength(8);
61
+ expect(generateNoConfusableId(1)).toHaveLength(1);
62
+ });
63
+
64
+ test("nur Zeichen aus dem no-confusable-Alphabet", () => {
65
+ for (let i = 0; i < 50; i++) {
66
+ const id = generateNoConfusableId(10);
67
+ expect(id).toMatch(/^[a-z2-9]+$/);
68
+ expect(id).not.toMatch(/[01oli]/);
69
+ }
70
+ });
71
+
72
+ test("length < 1 wirft", () => {
73
+ expect(() => generateNoConfusableId(0)).toThrow(/length must be ≥ 1/);
74
+ expect(() => generateNoConfusableId(-3)).toThrow(/length must be ≥ 1/);
75
+ });
76
+
77
+ test("Statistical: 200 IDs der Länge 8 → alle unique", () => {
78
+ // 32^8 = 1 Trillion combos → 200 picks haben praktisch 0 % Kollision.
79
+ const ids = new Set<string>();
80
+ for (let i = 0; i < 200; i++) {
81
+ ids.add(generateNoConfusableId(8));
82
+ }
83
+ expect(ids.size).toBe(200);
84
+ });
85
+ });
86
+
87
+ describe("generateUniqueName", () => {
88
+ test("isAvailable=true beim ersten Wurf → returnt clean (kein Suffix)", async () => {
89
+ const seen: string[] = [];
90
+ const name = await generateUniqueName({
91
+ isAvailable: async (n) => {
92
+ seen.push(n);
93
+ return true;
94
+ },
95
+ });
96
+ expect(seen).toHaveLength(1);
97
+ expect(name).toBe(seen[0]);
98
+ // Clean = nur 2 Teile (adj + noun), kein suffix
99
+ expect(name.split("-")).toHaveLength(2);
100
+ });
101
+
102
+ test("nach 3 Kollisionen wechselt zu Suffix-Mode", async () => {
103
+ const tried: string[] = [];
104
+ const name = await generateUniqueName({
105
+ maxCleanAttempts: 3,
106
+ isAvailable: async (n) => {
107
+ tried.push(n);
108
+ // Erste 3 sind belegt, der 4. Versuch (suffix-mode) wins.
109
+ return tried.length === 4;
110
+ },
111
+ });
112
+ expect(tried).toHaveLength(4);
113
+ // Erste 3 ohne suffix
114
+ for (let i = 0; i < 3; i++) {
115
+ expect(tried[i]?.split("-")).toHaveLength(2);
116
+ }
117
+ // 4. Versuch hat suffix
118
+ expect(tried[3]?.split("-")).toHaveLength(3);
119
+ expect(name).toBe(tried[3]);
120
+ });
121
+
122
+ test("wirft wenn maxTotalAttempts erschöpft", async () => {
123
+ await expect(
124
+ generateUniqueName({
125
+ isAvailable: async () => false,
126
+ maxTotalAttempts: 5,
127
+ }),
128
+ ).rejects.toThrow(/failed to find available name after 5 attempts/);
129
+ });
130
+
131
+ test("respektiert custom Wortlisten", async () => {
132
+ const name = await generateUniqueName({
133
+ isAvailable: async () => true,
134
+ adjectives: ["bold"],
135
+ nouns: ["receiver"],
136
+ });
137
+ expect(name).toBe("bold-receiver");
138
+ });
139
+
140
+ test("config-Validierung: maxClean > maxTotal wirft", async () => {
141
+ await expect(
142
+ generateUniqueName({
143
+ isAvailable: async () => true,
144
+ maxCleanAttempts: 10,
145
+ maxTotalAttempts: 5,
146
+ }),
147
+ ).rejects.toThrow(/maxCleanAttempts.*must not exceed maxTotalAttempts/);
148
+ });
149
+ });
@@ -0,0 +1,141 @@
1
+ // Random-Generator für human-readable Resource-Identifier:
2
+ // - Tenant-Keys ("happy-cloud", "swift-river-k8x")
3
+ // - Webhook-Subscriber-Slugs ("bold-falcon-receiver" mit custom nouns)
4
+ // - API-Key-Display-Names (3 Vorschläge anbieten beim Create)
5
+ // - Tenant-Subdomain-Vorschläge im Subdomain-Setup-Screen
6
+ // - Test-Fixtures mit lesbaren Identifiern
7
+ //
8
+ // NICHT für Security-Token (CSRF, Session, API-Keys, OAuth-State,
9
+ // Reset-Token). Math.random() ist nicht kryptografisch unvorhersagbar.
10
+ // Authority-binding für Tenant-Keys läuft über JWT + DB-Lookup, nicht
11
+ // über Slug-Geheimhaltung — der Slug darf erratbar sein.
12
+ //
13
+ // Universal-safe: Math.random() läuft in Bun, Node, Metro/RN, Expo-Web.
14
+ // Keine node:crypto-Imports.
15
+
16
+ import { ADJECTIVES, NOUNS } from "./words";
17
+
18
+ // Alphabet ohne handgetippt verwechselbare Zeichen:
19
+ // - keine 0/O (Null vs Großbuchstabe O)
20
+ // - keine 1/l/I (Eins vs Kleinbuchstabe L vs Großbuchstabe I)
21
+ // Resultat: 32 Zeichen, sicher beim Telefon-Buchstabieren UND beim
22
+ // Mailtext-Copy-Paste in fremde Schriftarten.
23
+ const NO_CONFUSABLE_ALPHABET = "23456789abcdefghjkmnpqrstuvwxyz";
24
+ const NO_CONFUSABLE_CHARS: readonly string[] = Object.freeze(NO_CONFUSABLE_ALPHABET.split(""));
25
+
26
+ function pickRandom<T>(arr: readonly T[]): T {
27
+ if (arr.length === 0) {
28
+ throw new Error("pickRandom: cannot pick from empty array");
29
+ }
30
+ const value = arr[Math.floor(Math.random() * arr.length)];
31
+ // Length-Check oben garantiert dass index in-range ist.
32
+ if (value === undefined) {
33
+ throw new Error("pickRandom: indexed value undefined (sparse array?)");
34
+ }
35
+ return value;
36
+ }
37
+
38
+ export type AdjNounNameOptions = {
39
+ /** Trennzeichen zwischen adj/noun (und ggf. suffix). Default "-". */
40
+ readonly separator?: string;
41
+ /** Wenn gesetzt, wird ein random-suffix der Länge .length angehängt
42
+ * (no-confusable-Alphabet). Empfohlen 3 Zeichen = 32^3 = 32.768
43
+ * zusätzliche Combinations pro Wortpaar. */
44
+ readonly suffix?: { readonly length: number };
45
+ /** Custom Adjective-Liste — default ADJECTIVES (150 generic). */
46
+ readonly adjectives?: readonly string[];
47
+ /** Custom Noun-Liste — default NOUNS (150 generic). Apps die Domain-
48
+ * spezifische Slugs wollen (z.B. webhook-feature mit eigenen
49
+ * -receiver/-listener-Substantiven) reichen ihre eigene Liste. */
50
+ readonly nouns?: readonly string[];
51
+ };
52
+
53
+ /** Sync Generator: produziert "happy-cloud" oder "happy-cloud-k8x"
54
+ * (mit suffix). Kein Conflict-Check — Caller verantwortlich für
55
+ * Uniqueness, oder generateUniqueName() für die async Variante. */
56
+ export function generateAdjNounName(options: AdjNounNameOptions = {}): string {
57
+ const sep = options.separator ?? "-";
58
+ const adjs = options.adjectives ?? ADJECTIVES;
59
+ const nouns = options.nouns ?? NOUNS;
60
+ let name = `${pickRandom(adjs)}${sep}${pickRandom(nouns)}`;
61
+ if (options.suffix) {
62
+ name = `${name}${sep}${generateNoConfusableId(options.suffix.length)}`;
63
+ }
64
+ return name;
65
+ }
66
+
67
+ /** Random-String aus dem no-confusable-Alphabet. Für Suffix-bei-Kollision
68
+ * oder als standalone short-IDs (z.B. Webhook-Verifizierungs-Codes
69
+ * zum Vorlesen am Telefon). length ≥ 1. */
70
+ export function generateNoConfusableId(length: number): string {
71
+ if (length < 1) {
72
+ throw new Error(`generateNoConfusableId: length must be ≥ 1 (got ${length})`);
73
+ }
74
+ let id = "";
75
+ for (let i = 0; i < length; i++) {
76
+ id += pickRandom(NO_CONFUSABLE_CHARS);
77
+ }
78
+ return id;
79
+ }
80
+
81
+ export type GenerateUniqueNameOptions = {
82
+ /** Caller-Predicate. Returns true wenn der Name noch nicht vergeben
83
+ * ist (typisch: DB-Query "select where slug=$1" → row count === 0). */
84
+ readonly isAvailable: (name: string) => Promise<boolean>;
85
+ /** Max Versuche OHNE Suffix bevor wir auf suffix-mode wechseln.
86
+ * Default 3. Bei 22.500 Default-Combos und ~150 existierenden
87
+ * Tenants liegt p(Kollision) < 1% — 3 Versuche reichen weit. */
88
+ readonly maxCleanAttempts?: number;
89
+ /** Suffix-Länge bei Kollision-Mode. Default 3 (= 32.768 Combinations
90
+ * pro Wortpaar). */
91
+ readonly suffixLength?: number;
92
+ /** Hard-Cap an Total-Versuchen bevor wir aufgeben. Default 20.
93
+ * Praktisch nie erreicht — wenn doch, ist die Wortliste leer oder
94
+ * isAvailable() returnt durchgängig false (DB-Bug). */
95
+ readonly maxTotalAttempts?: number;
96
+ readonly separator?: string;
97
+ readonly adjectives?: readonly string[];
98
+ readonly nouns?: readonly string[];
99
+ };
100
+
101
+ /** Generiert einen unique Adj-Noun-Slug indem es bis zu maxCleanAttempts
102
+ * saubere Wortpaare versucht, danach mit random Suffix bis maxTotal-
103
+ * Attempts. Wirft wenn auch das nicht hilft (= caller hat ein Bug
104
+ * mit isAvailable, oder die Wortliste ist defekt).
105
+ *
106
+ * Beispiel:
107
+ * const slug = await generateUniqueName({
108
+ * isAvailable: async (s) =>
109
+ * !(await db.select().from(tenants).where(eq(tenants.tenantKey, s)).then(r => r.length > 0)),
110
+ * });
111
+ * // → "happy-cloud" oder "happy-cloud-k8x" bei Kollision
112
+ */
113
+ export async function generateUniqueName(options: GenerateUniqueNameOptions): Promise<string> {
114
+ const maxClean = options.maxCleanAttempts ?? 3;
115
+ const suffixLength = options.suffixLength ?? 3;
116
+ const maxTotal = options.maxTotalAttempts ?? 20;
117
+ if (maxClean > maxTotal) {
118
+ throw new Error(
119
+ `generateUniqueName: maxCleanAttempts (${maxClean}) must not exceed maxTotalAttempts (${maxTotal})`,
120
+ );
121
+ }
122
+
123
+ const baseOptions: AdjNounNameOptions = {
124
+ ...(options.separator !== undefined && { separator: options.separator }),
125
+ ...(options.adjectives !== undefined && { adjectives: options.adjectives }),
126
+ ...(options.nouns !== undefined && { nouns: options.nouns }),
127
+ };
128
+
129
+ for (let i = 0; i < maxTotal; i++) {
130
+ const useSuffix = i >= maxClean;
131
+ const name = generateAdjNounName(
132
+ useSuffix ? { ...baseOptions, suffix: { length: suffixLength } } : baseOptions,
133
+ );
134
+ if (await options.isAvailable(name)) return name;
135
+ }
136
+
137
+ throw new Error(
138
+ `generateUniqueName: failed to find available name after ${maxTotal} attempts. ` +
139
+ `Wordlists may be exhausted, or isAvailable() returns false unconditionally.`,
140
+ );
141
+ }
@@ -0,0 +1,8 @@
1
+ export {
2
+ type AdjNounNameOptions,
3
+ type GenerateUniqueNameOptions,
4
+ generateAdjNounName,
5
+ generateNoConfusableId,
6
+ generateUniqueName,
7
+ } from "./generate";
8
+ export { ADJECTIVES, NOUNS } from "./words";