appos 0.3.5-0 → 0.3.6-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.
- package/dist/bin/main.mjs +2 -2
- package/dist/exports/api/adapter-C2mMJKvG.mjs +1504 -0
- package/dist/exports/api/{auth-schema.mjs → auth-schema-CON4yFFY.mjs} +2 -3
- package/dist/exports/api/bun-sqlite-dialect-CPpPZa23.mjs +155 -0
- package/dist/exports/{cli/_virtual/rolldown_runtime.mjs → api/chunk-CyXqrcp_.mjs} +14 -1
- package/dist/exports/api/conditions-DjBAWfiK.mjs +116 -0
- package/dist/exports/api/dialect-CUUD24Ge.mjs +72 -0
- package/dist/exports/api/env-CwT3zhez.mjs +1 -0
- package/dist/exports/api/esm-_hkVMulx.mjs +15816 -0
- package/dist/exports/api/event-BHFSE6hY.mjs +20983 -0
- package/dist/exports/api/extract-blob-metadata-BMgUEPcW.mjs +4 -0
- package/dist/exports/api/extract-blob-metadata-_9RUEwoU.mjs +9818 -0
- package/dist/exports/api/generate-image-variant-DOIzj1wJ.mjs +4 -0
- package/dist/exports/api/generate-image-variant-DxOPoGAk.mjs +118 -0
- package/dist/exports/api/generate-preview-CvTFuq58.mjs +160 -0
- package/dist/exports/api/generate-preview-Dk3coswZ.mjs +4 -0
- package/dist/exports/api/index.d.mts +7347 -20
- package/dist/exports/api/index.mjs +146856 -18
- package/dist/exports/api/kysely-adapter-CmP2TbkS.mjs +296 -0
- package/dist/exports/api/memory-adapter-BzhRhLFK.mjs +212 -0
- package/dist/exports/api/node-sqlite-dialect-Bl5suBxl.mjs +155 -0
- package/dist/exports/api/orm-CMqufD21.mjs +153690 -0
- package/dist/exports/api/orm-DXqYuKvB.d.mts +11 -0
- package/dist/exports/api/orm.d.mts +2 -11
- package/dist/exports/api/orm.mjs +4 -42
- package/dist/exports/api/pdf-YxB2Hm1p.mjs +25822 -0
- package/dist/exports/api/purge-attachment-Cf6KH2Jv.mjs +34 -0
- package/dist/exports/api/purge-attachment-DIuil7ps.mjs +4 -0
- package/dist/exports/{cli/api/workflows/purge-audit-logs.mjs → api/purge-audit-logs-z6j_Pu47.mjs} +3 -3
- package/dist/exports/{cli/api/workflows/purge-unattached-blobs.mjs → api/purge-unattached-blobs-B0TfY5Hk.mjs} +1 -1
- package/dist/exports/api/react-BYhV5bYt.mjs +1131 -0
- package/dist/exports/api/server.node-DyVrQ6cz.mjs +19298 -0
- package/dist/exports/api/table-6bTIEqON.mjs +2636 -0
- package/dist/exports/api/{workflows/track-db-changes.mjs → track-db-changes-BysaV6nN.mjs} +14 -14
- package/dist/exports/api/{workflow.mjs → workflow-BuSWHcX-.mjs} +121 -10
- package/dist/exports/api/workflows/auth-schema-BFve3hgZ.mjs +2813 -0
- package/dist/exports/api/{_virtual/rolldown_runtime.mjs → workflows/chunk-B36mNPO4.mjs} +5 -1
- package/dist/exports/api/workflows/extract-blob-metadata-BcWKvY2K.mjs +9818 -0
- package/dist/exports/api/workflows/{generate-image-variant.mjs → generate-image-variant-BAFvL_zV.mjs} +20 -20
- package/dist/exports/{cli/api/workflows/generate-preview.mjs → api/workflows/generate-preview-BjlIVYYp.mjs} +6 -6
- package/dist/exports/api/workflows/index.d.mts +2011 -1
- package/dist/exports/api/workflows/index.mjs +2 -1
- package/dist/exports/api/workflows/pdf-DUB8zyIc.mjs +25822 -0
- package/dist/exports/{cli/api/workflows/purge-attachment.mjs → api/workflows/purge-attachment-k_sdxTPY.mjs} +3 -3
- package/dist/exports/api/workflows/{purge-audit-logs.mjs → purge-audit-logs-zCyWj4Mw.mjs} +12 -3
- package/dist/exports/api/workflows/{purge-unattached-blobs.mjs → purge-unattached-blobs-CdsuFAeW.mjs} +1 -1
- package/dist/exports/api/workflows/track-db-changes-CiKaI9gT.mjs +20943 -0
- package/dist/exports/api/workflows/track-db-changes-DEWQYryO.mjs +4 -0
- package/dist/exports/{cli/api/workflow.mjs → api/workflows/workflow-BjM2xCa6.mjs} +23 -3
- package/dist/exports/api/workflows/zod-Br0enFWK.mjs +12333 -0
- package/dist/exports/api/zod-CdrJdBtV.mjs +12571 -0
- package/dist/exports/cli/auth-schema-CNuOqPi-.mjs +2813 -0
- package/dist/exports/{api/workflows/_virtual/rolldown_runtime.mjs → cli/chunk-B36mNPO4.mjs} +5 -1
- package/dist/exports/cli/extract-blob-metadata-CV1Ke90d.mjs +9818 -0
- package/dist/exports/cli/{api/workflows/generate-image-variant.mjs → generate-image-variant-CM1BrVvZ.mjs} +20 -20
- package/dist/exports/{api/workflows/generate-preview.mjs → cli/generate-preview-DdHQ3ukz.mjs} +6 -6
- package/dist/exports/cli/index.d.mts +2142 -2
- package/dist/exports/cli/index.mjs +41 -1
- package/dist/exports/cli/pdf-CLUPEIdT.mjs +25822 -0
- package/dist/exports/{api/workflows/purge-attachment.mjs → cli/purge-attachment-Dc_J74dG.mjs} +3 -3
- package/dist/exports/cli/purge-audit-logs-DFhWh-Bx.mjs +56 -0
- package/dist/exports/cli/purge-unattached-blobs-C1MRlM_2.mjs +46 -0
- package/dist/exports/cli/track-db-changes-CfneOF2_.mjs +20943 -0
- package/dist/exports/{api/workflows/api/workflow.mjs → cli/workflow-DSbbXBMt.mjs} +23 -3
- package/dist/exports/cli/zod-7q0_Mtnn.mjs +12333 -0
- package/dist/exports/devtools/BaseTanStackRouterDevtoolsPanel-BBz1qLry-DUwdvyLv.js +2090 -0
- package/dist/exports/devtools/EIDV623S-B7f6114d.js +2497 -0
- package/dist/exports/devtools/FloatingTanStackRouterDevtools-DymJEvfG-BHuQHiRR.js +237 -0
- package/dist/exports/devtools/MIMHJGAX-Cb3wf11F.js +10903 -0
- package/dist/exports/devtools/Q7LWSL4U-BoEO3rNq.js +37 -0
- package/dist/exports/devtools/VLTTJS3N-CIyRc84e.js +44 -0
- package/dist/exports/devtools/index.js +1724 -3
- package/dist/exports/devtools/utils-YRTCpRgb.js +1670 -0
- package/dist/exports/tests/api.d.mts +7354 -5
- package/dist/exports/tests/api.mjs +79 -1
- package/dist/exports/tests/auth-schema-DUCJw-_2.mjs +1 -0
- package/dist/exports/tests/chunk-Cii4FAhs.mjs +1 -0
- package/dist/exports/tests/conditions-B0ffVJ5E.mjs +1 -0
- package/dist/exports/tests/constants-DHjjg05J.mjs +1 -0
- package/dist/exports/tests/dist-D6vgKv3t.mjs +7 -0
- package/dist/exports/tests/dist-EfrRkq5c.mjs +296 -0
- package/dist/exports/tests/extract-blob-metadata-Dv343Vcn.mjs +169 -0
- package/dist/exports/tests/generate-image-variant-cQc8q0kh.mjs +1 -0
- package/dist/exports/tests/generate-preview-HaTMd2hS.mjs +1 -0
- package/dist/exports/tests/magic-string.es-D6VRmdiF.mjs +14 -0
- package/dist/exports/tests/mock-BvkO5nlm.mjs +1 -0
- package/dist/exports/tests/mock.mjs +1 -1
- package/dist/exports/tests/pdf-BDsS3vjF.mjs +13 -0
- package/dist/exports/tests/purge-attachment-D4uOzHRi.mjs +1 -0
- package/dist/exports/tests/purge-audit-logs-DiPcc09d.mjs +1 -0
- package/dist/exports/tests/purge-unattached-blobs-CqW6tlIC.mjs +1 -0
- package/dist/exports/tests/react.mjs +6 -1
- package/dist/exports/tests/setup.d.mts +1 -1
- package/dist/exports/tests/setup.mjs +34 -1
- package/dist/exports/tests/table-CbU04119.mjs +1 -0
- package/dist/exports/tests/track-db-changes-Bd4W-P1q.mjs +1 -0
- package/dist/exports/tests/vi.2VT5v0um-Cme1b0Dl.mjs +348 -0
- package/dist/exports/tests/{api/workflow.mjs → workflow-DwZxTqdH.mjs} +1 -1
- package/dist/exports/tests/zod-DcpxsMPz.mjs +23 -0
- package/dist/exports/vendors/date.js +13236 -1
- package/dist/exports/vendors/toolkit.js +2206 -1
- package/dist/exports/vendors/zod.js +12344 -1
- package/dist/exports/vite/api-DGRU_RdM.mjs +27 -0
- package/dist/exports/vite/build-CxJAu2Bn.mjs +1 -0
- package/dist/exports/vite/build2-BMK0OFVt.mjs +16 -0
- package/dist/exports/vite/chunk-CxnlK1Zi.mjs +1 -0
- package/dist/exports/vite/chunk-PtveFMYu.mjs +1 -0
- package/dist/exports/vite/dist-Bxp8DqWh.mjs +114 -0
- package/dist/exports/vite/dist-CX51FKbX.mjs +1 -0
- package/dist/exports/vite/dist-DcyNr-KR.mjs +1 -0
- package/dist/exports/vite/dist-j3-Khlzt.mjs +1 -0
- package/dist/exports/vite/experimental-index-3KsfmUbz.mjs +1 -0
- package/dist/exports/vite/false-BNJbUKYT.mjs +1 -0
- package/dist/exports/vite/false-BvrPEDU6.mjs +1 -0
- package/dist/exports/vite/index.d.mts +1 -1
- package/dist/exports/vite/index.mjs +26 -1
- package/dist/exports/vite/internal-C5cVwRiK.mjs +1 -0
- package/dist/exports/vite/jiti-BjSPrFvg.mjs +9 -0
- package/dist/exports/vite/lexer-DQCqS3nf-C6xCDOEQ.mjs +3 -0
- package/dist/exports/vite/lib-CMv7Sfwa.mjs +1 -0
- package/dist/exports/vite/lib-D9TnS-7w.mjs +1 -0
- package/dist/exports/vite/lib-DuBRr9dH.mjs +382 -0
- package/dist/exports/vite/node-DI8AnY_i.mjs +437 -0
- package/dist/exports/vite/postcss-BtmDpj-c.mjs +32 -0
- package/dist/exports/vite/postcss-import-Bk_ZCd6c.mjs +5 -0
- package/dist/exports/vite/rolldown-build-CNW2eye_-CJNfyXdF.mjs +13 -0
- package/dist/exports/vitest/api-CPv6lnxG.mjs +27 -0
- package/dist/exports/vitest/build2-28i3OiJ3.mjs +16 -0
- package/dist/exports/vitest/chunk-_e2jlDPK.mjs +1 -0
- package/dist/exports/vitest/chunk-jwDkFoXW.mjs +1 -0
- package/dist/exports/vitest/config.mjs +77 -1
- package/dist/exports/vitest/dist-L-OpshPJ.mjs +114 -0
- package/dist/exports/vitest/index.mjs +348 -1
- package/dist/exports/vitest/jiti-eNGOyHIo.mjs +9 -0
- package/dist/exports/vitest/lexer-DQCqS3nf-TWLyIqlY.mjs +3 -0
- package/dist/exports/vitest/lib-C3G64csm.mjs +1 -0
- package/dist/exports/vitest/magic-string.es-CxM5Ubyl.mjs +14 -0
- package/dist/exports/vitest/module-runner-DsF4L04D.mjs +1 -0
- package/dist/exports/vitest/postcss-BxLp_Too.mjs +32 -0
- package/dist/exports/vitest/postcss-import-5pKj3f5q.mjs +5 -0
- package/dist/exports/web/browser-ponyfill-DxTJMXNq.js +443 -0
- package/dist/exports/web/chunk-DksrlJLg.js +49 -0
- package/dist/exports/web/index.d.ts +2546 -2
- package/dist/exports/web/index.js +9295 -6
- package/dist/exports/web/plugin-DqTFWG7p.js +250 -0
- package/package.json +1 -1
- package/dist/exports/api/app-context.d.mts +0 -115
- package/dist/exports/api/app-context.mjs +0 -24
- package/dist/exports/api/auth-schema.d.mts +0 -4248
- package/dist/exports/api/auth.d.mts +0 -402
- package/dist/exports/api/auth.mjs +0 -188
- package/dist/exports/api/cache.d.mts +0 -44
- package/dist/exports/api/cache.mjs +0 -28
- package/dist/exports/api/config.d.mts +0 -28
- package/dist/exports/api/config.mjs +0 -72
- package/dist/exports/api/constants.mjs +0 -92
- package/dist/exports/api/container.d.mts +0 -210
- package/dist/exports/api/container.mjs +0 -49
- package/dist/exports/api/database.d.mts +0 -101
- package/dist/exports/api/database.mjs +0 -219
- package/dist/exports/api/event.d.mts +0 -235
- package/dist/exports/api/event.mjs +0 -236
- package/dist/exports/api/i18n.d.mts +0 -34
- package/dist/exports/api/i18n.mjs +0 -45
- package/dist/exports/api/instrumentation.d.mts +0 -7
- package/dist/exports/api/instrumentation.mjs +0 -40
- package/dist/exports/api/logger.d.mts +0 -21
- package/dist/exports/api/logger.mjs +0 -26
- package/dist/exports/api/mailer.d.mts +0 -70
- package/dist/exports/api/mailer.mjs +0 -37
- package/dist/exports/api/middleware/request-logger.d.mts +0 -24
- package/dist/exports/api/middleware.d.mts +0 -39
- package/dist/exports/api/middleware.mjs +0 -73
- package/dist/exports/api/openapi.d.mts +0 -271
- package/dist/exports/api/openapi.mjs +0 -507
- package/dist/exports/api/otel.d.mts +0 -40
- package/dist/exports/api/otel.mjs +0 -56
- package/dist/exports/api/redis.d.mts +0 -34
- package/dist/exports/api/redis.mjs +0 -41
- package/dist/exports/api/storage-schema.d.mts +0 -707
- package/dist/exports/api/storage-schema.mjs +0 -72
- package/dist/exports/api/storage.d.mts +0 -506
- package/dist/exports/api/storage.mjs +0 -833
- package/dist/exports/api/web/auth.mjs +0 -17
- package/dist/exports/api/workflow.d.mts +0 -250
- package/dist/exports/api/workflows/api/auth-schema.mjs +0 -373
- package/dist/exports/api/workflows/api/auth.d.mts +0 -379
- package/dist/exports/api/workflows/api/cache.d.mts +0 -44
- package/dist/exports/api/workflows/api/config.d.mts +0 -18
- package/dist/exports/api/workflows/api/container.d.mts +0 -167
- package/dist/exports/api/workflows/api/database.d.mts +0 -47
- package/dist/exports/api/workflows/api/event.d.mts +0 -68
- package/dist/exports/api/workflows/api/event.mjs +0 -126
- package/dist/exports/api/workflows/api/logger.d.mts +0 -21
- package/dist/exports/api/workflows/api/mailer.d.mts +0 -70
- package/dist/exports/api/workflows/api/orm.d.mts +0 -13
- package/dist/exports/api/workflows/api/redis.mjs +0 -3
- package/dist/exports/api/workflows/api/storage-schema.d.mts +0 -699
- package/dist/exports/api/workflows/api/storage.d.mts +0 -396
- package/dist/exports/api/workflows/api/workflow.d.mts +0 -24
- package/dist/exports/api/workflows/constants.mjs +0 -23
- package/dist/exports/api/workflows/extract-blob-metadata.mjs +0 -132
- package/dist/exports/api/workflows/generate-image-variant.d.mts +0 -63
- package/dist/exports/api/workflows/track-db-changes.d.mts +0 -72
- package/dist/exports/cli/api/auth-schema.mjs +0 -373
- package/dist/exports/cli/api/auth.d.mts +0 -379
- package/dist/exports/cli/api/cache.d.mts +0 -44
- package/dist/exports/cli/api/config.d.mts +0 -18
- package/dist/exports/cli/api/container.d.mts +0 -167
- package/dist/exports/cli/api/database.d.mts +0 -47
- package/dist/exports/cli/api/event.d.mts +0 -68
- package/dist/exports/cli/api/event.mjs +0 -126
- package/dist/exports/cli/api/logger.d.mts +0 -21
- package/dist/exports/cli/api/mailer.d.mts +0 -70
- package/dist/exports/cli/api/orm.d.mts +0 -13
- package/dist/exports/cli/api/redis.mjs +0 -3
- package/dist/exports/cli/api/storage-schema.d.mts +0 -699
- package/dist/exports/cli/api/storage.d.mts +0 -396
- package/dist/exports/cli/api/workflow.d.mts +0 -2
- package/dist/exports/cli/api/workflows/extract-blob-metadata.mjs +0 -132
- package/dist/exports/cli/api/workflows/generate-image-variant.d.mts +0 -63
- package/dist/exports/cli/api/workflows/track-db-changes.mjs +0 -110
- package/dist/exports/cli/command.d.mts +0 -56
- package/dist/exports/cli/command.mjs +0 -43
- package/dist/exports/cli/constants.mjs +0 -23
- package/dist/exports/cli/context.d.mts +0 -170
- package/dist/exports/tests/_virtual/rolldown_runtime.mjs +0 -1
- package/dist/exports/tests/api/app-context.d.mts +0 -115
- package/dist/exports/tests/api/app-context.mjs +0 -1
- package/dist/exports/tests/api/auth-schema.d.mts +0 -4248
- package/dist/exports/tests/api/auth-schema.mjs +0 -1
- package/dist/exports/tests/api/auth.d.mts +0 -402
- package/dist/exports/tests/api/cache.d.mts +0 -44
- package/dist/exports/tests/api/config.d.mts +0 -28
- package/dist/exports/tests/api/container.d.mts +0 -210
- package/dist/exports/tests/api/database.d.mts +0 -101
- package/dist/exports/tests/api/database.mjs +0 -1
- package/dist/exports/tests/api/event.d.mts +0 -235
- package/dist/exports/tests/api/event.mjs +0 -1
- package/dist/exports/tests/api/i18n.d.mts +0 -34
- package/dist/exports/tests/api/index.d.mts +0 -26
- package/dist/exports/tests/api/logger.d.mts +0 -21
- package/dist/exports/tests/api/mailer.d.mts +0 -70
- package/dist/exports/tests/api/middleware/error-handler.mjs +0 -1
- package/dist/exports/tests/api/middleware/health.mjs +0 -1
- package/dist/exports/tests/api/middleware/i18n.mjs +0 -1
- package/dist/exports/tests/api/middleware/request-logger.d.mts +0 -24
- package/dist/exports/tests/api/middleware/request-logger.mjs +0 -1
- package/dist/exports/tests/api/middleware/shutdown.mjs +0 -1
- package/dist/exports/tests/api/middleware/timeout.mjs +0 -1
- package/dist/exports/tests/api/middleware.d.mts +0 -39
- package/dist/exports/tests/api/middleware.mjs +0 -1
- package/dist/exports/tests/api/openapi.d.mts +0 -271
- package/dist/exports/tests/api/openapi.mjs +0 -1
- package/dist/exports/tests/api/orm.d.mts +0 -13
- package/dist/exports/tests/api/otel.d.mts +0 -40
- package/dist/exports/tests/api/redis.d.mts +0 -34
- package/dist/exports/tests/api/redis.mjs +0 -1
- package/dist/exports/tests/api/server.mjs +0 -1
- package/dist/exports/tests/api/storage-schema.d.mts +0 -707
- package/dist/exports/tests/api/storage.d.mts +0 -506
- package/dist/exports/tests/api/workflow.d.mts +0 -250
- package/dist/exports/tests/api/workflows/extract-blob-metadata.mjs +0 -1
- package/dist/exports/tests/api/workflows/generate-image-variant.d.mts +0 -63
- package/dist/exports/tests/api/workflows/generate-image-variant.mjs +0 -1
- package/dist/exports/tests/api/workflows/generate-preview.mjs +0 -1
- package/dist/exports/tests/api/workflows/purge-attachment.mjs +0 -1
- package/dist/exports/tests/api/workflows/purge-audit-logs.mjs +0 -1
- package/dist/exports/tests/api/workflows/purge-unattached-blobs.mjs +0 -1
- package/dist/exports/tests/api/workflows/track-db-changes.mjs +0 -1
- package/dist/exports/tests/constants.mjs +0 -1
- package/dist/exports/tests/instrumentation.d.mts +0 -7
- package/dist/exports/tests/instrumentation.mjs +0 -1
- package/dist/exports/web/api/auth.d.ts +0 -125
- package/dist/exports/web/api/database.d.ts +0 -4
- package/dist/exports/web/api/logger.d.ts +0 -1
- package/dist/exports/web/auth.d.ts +0 -2388
- package/dist/exports/web/auth.js +0 -75
- package/dist/exports/web/i18n.d.ts +0 -42
- package/dist/exports/web/i18n.js +0 -45
- /package/dist/exports/tests/{api/middleware/youch-handler.mjs → youch-handler-Ch5yf6im.mjs} +0 -0
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import { APPOS_DIR, DATABASES_DIR } from "./constants.mjs";
|
|
2
|
-
import { getTableName, sql } from "drizzle-orm";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { drizzle } from "drizzle-orm/node-postgres";
|
|
5
|
-
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
|
6
|
-
import pg from "pg";
|
|
7
|
-
|
|
8
|
-
//#region src/api/database.ts
|
|
9
|
-
const { Client, Pool: Pool$1 } = pg;
|
|
10
|
-
/**
|
|
11
|
-
* The schema used for migrations.
|
|
12
|
-
*/
|
|
13
|
-
const migrationsSchema = "public";
|
|
14
|
-
/**
|
|
15
|
-
* Get the migration options for a specific database.
|
|
16
|
-
*
|
|
17
|
-
* @param name Name of the database to get migration options for
|
|
18
|
-
* @param type Type of migration (schema or data), defaults to "schema"
|
|
19
|
-
* @returns Migration configuration for drizzle-orm
|
|
20
|
-
*/
|
|
21
|
-
function defineMigrationOpts(name, type = "schema") {
|
|
22
|
-
const folder = type === "schema" ? "schema-migrations" : "data-migrations";
|
|
23
|
-
const table = type === "schema" ? "schema-migrations" : "data-migrations";
|
|
24
|
-
return {
|
|
25
|
-
migrationsFolder: `${join(APPOS_DIR, DATABASES_DIR)}/${name}/${folder}`,
|
|
26
|
-
migrationsSchema,
|
|
27
|
-
migrationsTable: table
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* The database logger.
|
|
32
|
-
*/
|
|
33
|
-
var DatabaseLogger = class {
|
|
34
|
-
#logger;
|
|
35
|
-
constructor(logger) {
|
|
36
|
-
this.#logger = logger;
|
|
37
|
-
}
|
|
38
|
-
logQuery(query, params) {
|
|
39
|
-
this.#logger.info(params, query);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* Generate old and new row JSONB representations for delete/insert/update queries.
|
|
44
|
-
* Uses PostgreSQL 18's OLD/NEW support in RETURNING clause.
|
|
45
|
-
*
|
|
46
|
-
* @param table The table to generate changes for.
|
|
47
|
-
* @returns Object containing `_table`, `old` and `new` JSONB columns.
|
|
48
|
-
*/
|
|
49
|
-
function dbChanges(table) {
|
|
50
|
-
const tableName = getTableName(table);
|
|
51
|
-
return {
|
|
52
|
-
_table: sql`${sql.raw(`'${tableName}'`)}`.as("_table"),
|
|
53
|
-
old: sql`to_jsonb(OLD.*)`.as("old"),
|
|
54
|
-
new: sql`to_jsonb(NEW.*)`.as("new")
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Define the database with the provided options.
|
|
59
|
-
*
|
|
60
|
-
* Algorithm:
|
|
61
|
-
* 1. Create a connection pool with sensible defaults
|
|
62
|
-
* 2. Initialize drizzle ORM with schema and relations
|
|
63
|
-
*
|
|
64
|
-
* @param opts The options for defining the database, including pool configuration, relations, and schema.
|
|
65
|
-
* @template TSchema The schema type for the database.
|
|
66
|
-
* @template TRelations The relations type for the database.
|
|
67
|
-
* @returns The defined database instance.
|
|
68
|
-
*/
|
|
69
|
-
async function defineDatabase(opts) {
|
|
70
|
-
return drizzle({
|
|
71
|
-
client: new Pool$1({
|
|
72
|
-
application_name: "appos",
|
|
73
|
-
max: 16,
|
|
74
|
-
...opts.poolConfig
|
|
75
|
-
}),
|
|
76
|
-
logger: new DatabaseLogger(opts.logger),
|
|
77
|
-
relations: opts.relations,
|
|
78
|
-
schema: opts.schema
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Create a single test database with isolated schema.
|
|
83
|
-
*
|
|
84
|
-
* @param connectionString - Database connection string
|
|
85
|
-
* @param name - Name of the test database
|
|
86
|
-
* @returns Test database instance and cleanup function
|
|
87
|
-
*/
|
|
88
|
-
async function defineTestDatabase(opts) {
|
|
89
|
-
const dbName = `test_w${process.env.VITEST_POOL_ID || "0"}_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
|
90
|
-
await ensureTemplateDatabase(opts.connectionString, opts.name, opts.logger);
|
|
91
|
-
const templateDbName = `${opts.name}_test_template`;
|
|
92
|
-
const maintenanceUrl = getMaintenanceDbUrl(opts.connectionString);
|
|
93
|
-
const maintenanceClient = new Client({ connectionString: maintenanceUrl });
|
|
94
|
-
await maintenanceClient.connect();
|
|
95
|
-
try {
|
|
96
|
-
await maintenanceClient.query(`CREATE DATABASE ${dbName} WITH TEMPLATE ${templateDbName}`);
|
|
97
|
-
} finally {
|
|
98
|
-
await maintenanceClient.end();
|
|
99
|
-
}
|
|
100
|
-
const testComponents = parsePostgresUrl(opts.connectionString);
|
|
101
|
-
testComponents.database = dbName;
|
|
102
|
-
const testPool = new Pool$1({ connectionString: buildPostgresUrl(testComponents) });
|
|
103
|
-
const db = drizzle({
|
|
104
|
-
client: testPool,
|
|
105
|
-
logger: opts.logger ? new DatabaseLogger(opts.logger) : void 0,
|
|
106
|
-
relations: opts.relations,
|
|
107
|
-
schema: opts.schema
|
|
108
|
-
});
|
|
109
|
-
const cleanUp = async () => {
|
|
110
|
-
await testPool.end();
|
|
111
|
-
const cleanupClient = new Client({ connectionString: maintenanceUrl });
|
|
112
|
-
await cleanupClient.connect();
|
|
113
|
-
try {
|
|
114
|
-
await cleanupClient.query(`SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1`, [dbName]);
|
|
115
|
-
await cleanupClient.query(`DROP DATABASE IF EXISTS ${dbName}`);
|
|
116
|
-
} catch (error) {
|
|
117
|
-
console.error("Could not drop test database:", error);
|
|
118
|
-
} finally {
|
|
119
|
-
await cleanupClient.end();
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
return {
|
|
123
|
-
cleanUp,
|
|
124
|
-
db,
|
|
125
|
-
dbName
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Parse PostgreSQL connection URL into components.
|
|
130
|
-
*
|
|
131
|
-
* @param connectionString - PostgreSQL connection URL
|
|
132
|
-
* @returns Parsed URL components
|
|
133
|
-
*/
|
|
134
|
-
function parsePostgresUrl(connectionString) {
|
|
135
|
-
const url = new URL(connectionString);
|
|
136
|
-
return {
|
|
137
|
-
database: url.pathname.slice(1) || "postgres",
|
|
138
|
-
host: url.hostname,
|
|
139
|
-
password: decodeURIComponent(url.password),
|
|
140
|
-
port: Number.parseInt(url.port || "5432", 10),
|
|
141
|
-
user: decodeURIComponent(url.username)
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Build PostgreSQL connection URL from components.
|
|
146
|
-
*
|
|
147
|
-
* @param components - URL components
|
|
148
|
-
* @returns PostgreSQL connection URL
|
|
149
|
-
*/
|
|
150
|
-
function buildPostgresUrl(components) {
|
|
151
|
-
const { user, password, host, port, database } = components;
|
|
152
|
-
return `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Get connection URL for maintenance database operations.
|
|
156
|
-
*
|
|
157
|
-
* @param connectionString - Original connection string
|
|
158
|
-
* @returns Connection string to maintenance database
|
|
159
|
-
*/
|
|
160
|
-
function getMaintenanceDbUrl(connectionString) {
|
|
161
|
-
const components = parsePostgresUrl(connectionString);
|
|
162
|
-
components.database = "postgres";
|
|
163
|
-
return buildPostgresUrl(components);
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Generate deterministic advisory lock ID from template database name.
|
|
167
|
-
*
|
|
168
|
-
* @param templateDbName - Template database name
|
|
169
|
-
* @returns 32-bit integer lock ID
|
|
170
|
-
*/
|
|
171
|
-
function getAdvisoryLockId(templateDbName) {
|
|
172
|
-
let hash = 0;
|
|
173
|
-
for (let i = 0; i < templateDbName.length; i++) {
|
|
174
|
-
const char = templateDbName.charCodeAt(i);
|
|
175
|
-
hash = (hash << 5) - hash + char;
|
|
176
|
-
hash = hash & hash;
|
|
177
|
-
}
|
|
178
|
-
return Math.abs(hash);
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Ensure template database exists with migrations applied.
|
|
182
|
-
*
|
|
183
|
-
* Uses PostgreSQL advisory locks to prevent race conditions when multiple
|
|
184
|
-
* test workers try to create the same template database concurrently.
|
|
185
|
-
*
|
|
186
|
-
* @param connectionString - Database connection string
|
|
187
|
-
* @param name - Database name (e.g., "platform")
|
|
188
|
-
* @param rootFolder - Root folder for migrations
|
|
189
|
-
* @param logger - Optional logger
|
|
190
|
-
*/
|
|
191
|
-
async function ensureTemplateDatabase(connectionString, name, logger) {
|
|
192
|
-
const templateDbName = `${name}_test_template`;
|
|
193
|
-
const maintenanceClient = new Client({ connectionString: getMaintenanceDbUrl(connectionString) });
|
|
194
|
-
await maintenanceClient.connect();
|
|
195
|
-
const lockId = getAdvisoryLockId(templateDbName);
|
|
196
|
-
try {
|
|
197
|
-
await maintenanceClient.query("SELECT pg_advisory_lock($1)", [lockId]);
|
|
198
|
-
if ((await maintenanceClient.query("SELECT 1 FROM pg_database WHERE datname = $1", [templateDbName])).rowCount === 0) {
|
|
199
|
-
await maintenanceClient.query(`CREATE DATABASE ${templateDbName} WITH IS_TEMPLATE = true`);
|
|
200
|
-
const components = parsePostgresUrl(connectionString);
|
|
201
|
-
components.database = templateDbName;
|
|
202
|
-
const templatePool = new Pool$1({ connectionString: buildPostgresUrl(components) });
|
|
203
|
-
try {
|
|
204
|
-
await migrate(drizzle({
|
|
205
|
-
client: templatePool,
|
|
206
|
-
logger: logger ? new DatabaseLogger(logger) : void 0
|
|
207
|
-
}), defineMigrationOpts(name));
|
|
208
|
-
} finally {
|
|
209
|
-
await templatePool.end();
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
} finally {
|
|
213
|
-
await maintenanceClient.query("SELECT pg_advisory_unlock($1)", [lockId]);
|
|
214
|
-
await maintenanceClient.end();
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
//#endregion
|
|
219
|
-
export { dbChanges, defineDatabase, defineMigrationOpts, defineTestDatabase, migrationsSchema };
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { Logger } from "./logger.mjs";
|
|
2
|
-
import { Container } from "./container.mjs";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
|
|
5
|
-
//#region src/api/event.d.ts
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Context available to event handlers.
|
|
9
|
-
*/
|
|
10
|
-
interface EventContext<TInput> {
|
|
11
|
-
/**
|
|
12
|
-
* The application container with access to db, mailer, logger, etc.
|
|
13
|
-
*/
|
|
14
|
-
container: Container;
|
|
15
|
-
/**
|
|
16
|
-
* The validated input passed to this event.
|
|
17
|
-
*/
|
|
18
|
-
input: TInput;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Options for defining an event bus.
|
|
22
|
-
*/
|
|
23
|
-
interface DefineEventBusOptions {
|
|
24
|
-
/**
|
|
25
|
-
* Redis URL for pub/sub.
|
|
26
|
-
*/
|
|
27
|
-
dbUrl: string;
|
|
28
|
-
/**
|
|
29
|
-
* Logger instance for error reporting.
|
|
30
|
-
*/
|
|
31
|
-
logger: Logger;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* The event bus type for container.
|
|
35
|
-
*/
|
|
36
|
-
type EventBus = ReturnType<typeof defineEventBus>;
|
|
37
|
-
/**
|
|
38
|
-
* Defines the event bus for pub/sub messaging.
|
|
39
|
-
* Uses Redis for cross-server communication.
|
|
40
|
-
*
|
|
41
|
-
* Algorithm:
|
|
42
|
-
* 1. Create lazy Redis publisher client
|
|
43
|
-
* 2. Create lazy Redis subscriber client
|
|
44
|
-
* 3. Manage subscriptions via callback registry
|
|
45
|
-
* 4. Provide publish/subscribe/close methods
|
|
46
|
-
*
|
|
47
|
-
* @param opts - Event bus configuration
|
|
48
|
-
* @returns Event bus instance
|
|
49
|
-
*/
|
|
50
|
-
declare function defineEventBus(opts: DefineEventBusOptions): {
|
|
51
|
-
/**
|
|
52
|
-
* Publish a message to a channel.
|
|
53
|
-
* Auto-connects on first call if not already connected.
|
|
54
|
-
*
|
|
55
|
-
* @param channel - Channel name
|
|
56
|
-
* @param message - Message payload (will be JSON stringified)
|
|
57
|
-
*/
|
|
58
|
-
publish(channel: string, message: unknown): Promise<void>;
|
|
59
|
-
/**
|
|
60
|
-
* Subscribe to a channel. Returns unsubscribe function.
|
|
61
|
-
* Uses single connection with callback management.
|
|
62
|
-
*
|
|
63
|
-
* @param channel - Channel name
|
|
64
|
-
* @param callback - Callback for received messages
|
|
65
|
-
* @returns Unsubscribe function
|
|
66
|
-
*/
|
|
67
|
-
subscribe(channel: string, callback: (message: unknown) => void): Promise<() => void>;
|
|
68
|
-
/**
|
|
69
|
-
* Check if a channel has any subscribers.
|
|
70
|
-
*
|
|
71
|
-
* @param channel - Channel name
|
|
72
|
-
* @returns true if channel has subscribers
|
|
73
|
-
*/
|
|
74
|
-
hasSubscribers(channel: string): boolean;
|
|
75
|
-
/**
|
|
76
|
-
* Close the Redis connections.
|
|
77
|
-
*/
|
|
78
|
-
close(): Promise<void>;
|
|
79
|
-
};
|
|
80
|
-
/**
|
|
81
|
-
* Options for defining an event.
|
|
82
|
-
*/
|
|
83
|
-
interface DefineEventOptions<TInput extends z.ZodType> {
|
|
84
|
-
/**
|
|
85
|
-
* Zod schema for validating event input.
|
|
86
|
-
*/
|
|
87
|
-
input: TInput;
|
|
88
|
-
/**
|
|
89
|
-
* In-memory handler that runs only on the emitting server.
|
|
90
|
-
*
|
|
91
|
-
* @param ctx - Event context with container and input
|
|
92
|
-
*/
|
|
93
|
-
run: (ctx: EventContext<z.infer<TInput>>) => Promise<void>;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Event object returned by defineEvent.
|
|
97
|
-
*/
|
|
98
|
-
interface Event<TInput> {
|
|
99
|
-
/**
|
|
100
|
-
* The event name (set during registration from filename).
|
|
101
|
-
*/
|
|
102
|
-
readonly name: string | null;
|
|
103
|
-
/**
|
|
104
|
-
* Emit this event with the given input.
|
|
105
|
-
* Runs in-memory handler, then publishes to Redis (fire-and-forget).
|
|
106
|
-
*
|
|
107
|
-
* @param input - Input matching the event's input schema.
|
|
108
|
-
*/
|
|
109
|
-
emit(input: TInput): Promise<void>;
|
|
110
|
-
/**
|
|
111
|
-
* The Zod schema for validating input.
|
|
112
|
-
*/
|
|
113
|
-
inputSchema: z.ZodType<TInput>;
|
|
114
|
-
/**
|
|
115
|
-
* Register this event with the container. Called by the event loader.
|
|
116
|
-
*
|
|
117
|
-
* @param container - The application container
|
|
118
|
-
* @param name - The event name (derived from filename)
|
|
119
|
-
*/
|
|
120
|
-
register(container: Container, name: string): void;
|
|
121
|
-
/**
|
|
122
|
-
* Subscribe to this event via Redis pub/sub.
|
|
123
|
-
* Use for tRPC subscriptions, SSE, WebSockets.
|
|
124
|
-
*
|
|
125
|
-
* @param handler - Handler called when event is emitted on any server
|
|
126
|
-
* @returns Unsubscribe function to cleanup the subscription
|
|
127
|
-
*/
|
|
128
|
-
subscribe(handler: (ctx: EventContext<TInput>) => Promise<void>): Promise<() => void>;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Defines a type-safe event with in-memory and Redis pub/sub support.
|
|
132
|
-
*
|
|
133
|
-
* Algorithm:
|
|
134
|
-
* 1. Define event with input schema and in-memory run handler
|
|
135
|
-
* 2. On emit(): validate input, run in-memory handler, publish to Redis (fire-and-forget)
|
|
136
|
-
* 3. .subscribe() creates Redis subscription for tRPC/SSE/WebSocket handlers
|
|
137
|
-
*
|
|
138
|
-
* @example
|
|
139
|
-
* ```typescript
|
|
140
|
-
* // api/events/order-status.ts
|
|
141
|
-
* export default defineEvent({
|
|
142
|
-
* input: z.object({
|
|
143
|
-
* orderId: z.string(),
|
|
144
|
-
* status: z.enum(["pending", "shipped", "delivered"]),
|
|
145
|
-
* }),
|
|
146
|
-
* async run(ctx) {
|
|
147
|
-
* ctx.container.logger.info(`Order ${ctx.input.orderId} is ${ctx.input.status}`);
|
|
148
|
-
* },
|
|
149
|
-
* });
|
|
150
|
-
*
|
|
151
|
-
* // Emit from anywhere
|
|
152
|
-
* await orderStatus.emit({ orderId: "123", status: "shipped" });
|
|
153
|
-
*
|
|
154
|
-
* // Subscribe (e.g., in tRPC router)
|
|
155
|
-
* const unsubscribe = await orderStatus.subscribe(async (ctx) => {
|
|
156
|
-
* // Push to client via SSE/WebSocket
|
|
157
|
-
* });
|
|
158
|
-
* // Cleanup when client disconnects
|
|
159
|
-
* unsubscribe();
|
|
160
|
-
* ```
|
|
161
|
-
*/
|
|
162
|
-
declare function defineEvent<TInput extends z.ZodType>(options: DefineEventOptions<TInput>): Event<z.infer<TInput>>;
|
|
163
|
-
/**
|
|
164
|
-
* Input schema for dbChangesEvent.
|
|
165
|
-
*/
|
|
166
|
-
declare const dbChangeInputSchema: z.ZodObject<{
|
|
167
|
-
action: z.ZodEnum<{
|
|
168
|
-
DELETE: "DELETE";
|
|
169
|
-
INSERT: "INSERT";
|
|
170
|
-
UPDATE: "UPDATE";
|
|
171
|
-
}>;
|
|
172
|
-
newData: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
173
|
-
oldData: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
174
|
-
organizationId: z.ZodNullable<z.ZodString>;
|
|
175
|
-
tableName: z.ZodString;
|
|
176
|
-
timestamp: z.ZodString;
|
|
177
|
-
userId: z.ZodNullable<z.ZodString>;
|
|
178
|
-
}, z.core.$strip>;
|
|
179
|
-
/**
|
|
180
|
-
* Type for dbChangesEvent input.
|
|
181
|
-
*/
|
|
182
|
-
type DbChangeInput = z.infer<typeof dbChangeInputSchema>;
|
|
183
|
-
/**
|
|
184
|
-
* Built-in event for database changes.
|
|
185
|
-
* Emitted by trackDbChanges workflow.
|
|
186
|
-
*
|
|
187
|
-
* @example
|
|
188
|
-
* ```typescript
|
|
189
|
-
* // Subscribe to DB changes (e.g., in tRPC subscription)
|
|
190
|
-
* import { dbChangesEvent } from "appos/api";
|
|
191
|
-
*
|
|
192
|
-
* const unsubscribe = await dbChangesEvent.subscribe(async (ctx) => {
|
|
193
|
-
* wsServer.publish("db-changes", JSON.stringify(ctx.input));
|
|
194
|
-
* });
|
|
195
|
-
*
|
|
196
|
-
* // Cleanup when done
|
|
197
|
-
* unsubscribe();
|
|
198
|
-
* ```
|
|
199
|
-
*/
|
|
200
|
-
declare const dbChangesEvent: Event<{
|
|
201
|
-
action: "DELETE" | "INSERT" | "UPDATE";
|
|
202
|
-
newData: Record<string, unknown> | null;
|
|
203
|
-
oldData: Record<string, unknown> | null;
|
|
204
|
-
organizationId: string | null;
|
|
205
|
-
tableName: string;
|
|
206
|
-
timestamp: string;
|
|
207
|
-
userId: string | null;
|
|
208
|
-
}>;
|
|
209
|
-
/**
|
|
210
|
-
* Options for loading events.
|
|
211
|
-
*/
|
|
212
|
-
interface LoadEventsOptions {
|
|
213
|
-
/**
|
|
214
|
-
* The application container.
|
|
215
|
-
*/
|
|
216
|
-
container: Container;
|
|
217
|
-
/**
|
|
218
|
-
* Optional custom events directory path.
|
|
219
|
-
*/
|
|
220
|
-
eventsDir?: string;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Auto-discovers and registers all events from the given directory.
|
|
224
|
-
*
|
|
225
|
-
* Algorithm:
|
|
226
|
-
* 1. Register built-in dbChangesEvent
|
|
227
|
-
* 2. Glob all .ts files in the directory (excluding test files)
|
|
228
|
-
* 3. Import each module's default export
|
|
229
|
-
* 4. If it's an Event (has emit + subscribe), call register(container, name)
|
|
230
|
-
*
|
|
231
|
-
* @param opts - Load events options
|
|
232
|
-
*/
|
|
233
|
-
declare function loadEvents(opts: LoadEventsOptions): Promise<void>;
|
|
234
|
-
//#endregion
|
|
235
|
-
export { DbChangeInput, DefineEventBusOptions, Event, EventBus, EventContext, dbChangeInputSchema, dbChangesEvent, defineEvent, defineEventBus, loadEvents };
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import { defineRedisClient } from "./redis.mjs";
|
|
2
|
-
import { APPOS_DIR, EVENTS_DIR, FILE_EXT } from "./constants.mjs";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { basename, join } from "node:path";
|
|
5
|
-
import { glob } from "node:fs/promises";
|
|
6
|
-
import { camelCase } from "es-toolkit";
|
|
7
|
-
|
|
8
|
-
//#region src/api/event.ts
|
|
9
|
-
/**
|
|
10
|
-
* Defines the event bus for pub/sub messaging.
|
|
11
|
-
* Uses Redis for cross-server communication.
|
|
12
|
-
*
|
|
13
|
-
* Algorithm:
|
|
14
|
-
* 1. Create lazy Redis publisher client
|
|
15
|
-
* 2. Create lazy Redis subscriber client
|
|
16
|
-
* 3. Manage subscriptions via callback registry
|
|
17
|
-
* 4. Provide publish/subscribe/close methods
|
|
18
|
-
*
|
|
19
|
-
* @param opts - Event bus configuration
|
|
20
|
-
* @returns Event bus instance
|
|
21
|
-
*/
|
|
22
|
-
function defineEventBus(opts) {
|
|
23
|
-
const publisherClient = defineRedisClient({
|
|
24
|
-
logger: opts.logger,
|
|
25
|
-
url: opts.dbUrl
|
|
26
|
-
});
|
|
27
|
-
let subscriberClient = null;
|
|
28
|
-
const subscribers = /* @__PURE__ */ new Map();
|
|
29
|
-
return {
|
|
30
|
-
async publish(channel, message) {
|
|
31
|
-
await publisherClient.publish(channel, JSON.stringify(message));
|
|
32
|
-
},
|
|
33
|
-
async subscribe(channel, callback) {
|
|
34
|
-
if (!subscriberClient) {
|
|
35
|
-
subscriberClient = publisherClient.duplicate();
|
|
36
|
-
await subscriberClient.connect();
|
|
37
|
-
}
|
|
38
|
-
if (!subscribers.has(channel)) {
|
|
39
|
-
subscribers.set(channel, /* @__PURE__ */ new Set());
|
|
40
|
-
await subscriberClient.subscribe(channel, (msg) => {
|
|
41
|
-
const callbacks = subscribers.get(channel);
|
|
42
|
-
if (callbacks) {
|
|
43
|
-
let parsed;
|
|
44
|
-
try {
|
|
45
|
-
parsed = JSON.parse(msg);
|
|
46
|
-
} catch (err) {
|
|
47
|
-
opts.logger.error({
|
|
48
|
-
err,
|
|
49
|
-
channel,
|
|
50
|
-
msg
|
|
51
|
-
}, "Failed to parse event message");
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
for (const cb of callbacks) try {
|
|
55
|
-
cb(parsed);
|
|
56
|
-
} catch (err) {
|
|
57
|
-
opts.logger.error({
|
|
58
|
-
err,
|
|
59
|
-
channel
|
|
60
|
-
}, "Event handler error");
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
subscribers.get(channel).add(callback);
|
|
66
|
-
return () => {
|
|
67
|
-
const callbacks = subscribers.get(channel);
|
|
68
|
-
if (callbacks) callbacks.delete(callback);
|
|
69
|
-
};
|
|
70
|
-
},
|
|
71
|
-
hasSubscribers(channel) {
|
|
72
|
-
const callbacks = subscribers.get(channel);
|
|
73
|
-
return callbacks !== void 0 && callbacks.size > 0;
|
|
74
|
-
},
|
|
75
|
-
async close() {
|
|
76
|
-
if (subscriberClient?.isOpen) await subscriberClient.quit();
|
|
77
|
-
if (publisherClient.isOpen) await publisherClient.quit();
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Defines a type-safe event with in-memory and Redis pub/sub support.
|
|
83
|
-
*
|
|
84
|
-
* Algorithm:
|
|
85
|
-
* 1. Define event with input schema and in-memory run handler
|
|
86
|
-
* 2. On emit(): validate input, run in-memory handler, publish to Redis (fire-and-forget)
|
|
87
|
-
* 3. .subscribe() creates Redis subscription for tRPC/SSE/WebSocket handlers
|
|
88
|
-
*
|
|
89
|
-
* @example
|
|
90
|
-
* ```typescript
|
|
91
|
-
* // api/events/order-status.ts
|
|
92
|
-
* export default defineEvent({
|
|
93
|
-
* input: z.object({
|
|
94
|
-
* orderId: z.string(),
|
|
95
|
-
* status: z.enum(["pending", "shipped", "delivered"]),
|
|
96
|
-
* }),
|
|
97
|
-
* async run(ctx) {
|
|
98
|
-
* ctx.container.logger.info(`Order ${ctx.input.orderId} is ${ctx.input.status}`);
|
|
99
|
-
* },
|
|
100
|
-
* });
|
|
101
|
-
*
|
|
102
|
-
* // Emit from anywhere
|
|
103
|
-
* await orderStatus.emit({ orderId: "123", status: "shipped" });
|
|
104
|
-
*
|
|
105
|
-
* // Subscribe (e.g., in tRPC router)
|
|
106
|
-
* const unsubscribe = await orderStatus.subscribe(async (ctx) => {
|
|
107
|
-
* // Push to client via SSE/WebSocket
|
|
108
|
-
* });
|
|
109
|
-
* // Cleanup when client disconnects
|
|
110
|
-
* unsubscribe();
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
|
-
function defineEvent(options) {
|
|
114
|
-
let container = null;
|
|
115
|
-
let eventName = null;
|
|
116
|
-
return {
|
|
117
|
-
inputSchema: options.input,
|
|
118
|
-
get name() {
|
|
119
|
-
return eventName;
|
|
120
|
-
},
|
|
121
|
-
register(c, name) {
|
|
122
|
-
container = c;
|
|
123
|
-
eventName = name;
|
|
124
|
-
},
|
|
125
|
-
async emit(input) {
|
|
126
|
-
if (!container || !eventName) throw new Error("Event not registered. Ensure the worker is started before emitting events.");
|
|
127
|
-
const validated = options.input.parse(input);
|
|
128
|
-
const ctx = {
|
|
129
|
-
container,
|
|
130
|
-
input: validated
|
|
131
|
-
};
|
|
132
|
-
await options.run(ctx);
|
|
133
|
-
container.eventBus.publish(eventName, validated).catch((err) => {
|
|
134
|
-
container.logger.error({
|
|
135
|
-
err,
|
|
136
|
-
event: eventName
|
|
137
|
-
}, "Redis publish failed");
|
|
138
|
-
});
|
|
139
|
-
},
|
|
140
|
-
async subscribe(handler) {
|
|
141
|
-
if (!container || !eventName) throw new Error("Event not registered. Ensure the worker is started before subscribing.");
|
|
142
|
-
return container.eventBus.subscribe(eventName, async (message) => {
|
|
143
|
-
const validated = options.input.parse(message);
|
|
144
|
-
const ctx = {
|
|
145
|
-
container,
|
|
146
|
-
input: validated
|
|
147
|
-
};
|
|
148
|
-
try {
|
|
149
|
-
await handler(ctx);
|
|
150
|
-
} catch (err) {
|
|
151
|
-
container.logger.error({
|
|
152
|
-
err,
|
|
153
|
-
event: eventName
|
|
154
|
-
}, "Event subscription handler error");
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Input schema for dbChangesEvent.
|
|
162
|
-
*/
|
|
163
|
-
const dbChangeInputSchema = z.object({
|
|
164
|
-
action: z.enum([
|
|
165
|
-
"INSERT",
|
|
166
|
-
"UPDATE",
|
|
167
|
-
"DELETE"
|
|
168
|
-
]),
|
|
169
|
-
newData: z.record(z.string(), z.unknown()).nullable(),
|
|
170
|
-
oldData: z.record(z.string(), z.unknown()).nullable(),
|
|
171
|
-
organizationId: z.string().nullable(),
|
|
172
|
-
tableName: z.string(),
|
|
173
|
-
timestamp: z.string(),
|
|
174
|
-
userId: z.string().nullable()
|
|
175
|
-
});
|
|
176
|
-
/**
|
|
177
|
-
* Built-in event for database changes.
|
|
178
|
-
* Emitted by trackDbChanges workflow.
|
|
179
|
-
*
|
|
180
|
-
* @example
|
|
181
|
-
* ```typescript
|
|
182
|
-
* // Subscribe to DB changes (e.g., in tRPC subscription)
|
|
183
|
-
* import { dbChangesEvent } from "appos/api";
|
|
184
|
-
*
|
|
185
|
-
* const unsubscribe = await dbChangesEvent.subscribe(async (ctx) => {
|
|
186
|
-
* wsServer.publish("db-changes", JSON.stringify(ctx.input));
|
|
187
|
-
* });
|
|
188
|
-
*
|
|
189
|
-
* // Cleanup when done
|
|
190
|
-
* unsubscribe();
|
|
191
|
-
* ```
|
|
192
|
-
*/
|
|
193
|
-
const dbChangesEvent = defineEvent({
|
|
194
|
-
input: dbChangeInputSchema,
|
|
195
|
-
async run() {}
|
|
196
|
-
});
|
|
197
|
-
/**
|
|
198
|
-
* Extracts event name from filepath as camelCase.
|
|
199
|
-
*/
|
|
200
|
-
function getEventName(filepath) {
|
|
201
|
-
return camelCase(basename(filepath, ".ts"));
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Auto-discovers and registers all events from the given directory.
|
|
205
|
-
*
|
|
206
|
-
* Algorithm:
|
|
207
|
-
* 1. Register built-in dbChangesEvent
|
|
208
|
-
* 2. Glob all .ts files in the directory (excluding test files)
|
|
209
|
-
* 3. Import each module's default export
|
|
210
|
-
* 4. If it's an Event (has emit + subscribe), call register(container, name)
|
|
211
|
-
*
|
|
212
|
-
* @param opts - Load events options
|
|
213
|
-
*/
|
|
214
|
-
async function loadEvents(opts) {
|
|
215
|
-
const { container } = opts;
|
|
216
|
-
const eventsDir = opts.eventsDir ?? join(process.cwd(), APPOS_DIR, EVENTS_DIR);
|
|
217
|
-
dbChangesEvent.register(container, "dbChanges");
|
|
218
|
-
const files = await Array.fromAsync(glob(`${eventsDir}/**/*.${FILE_EXT}`, { exclude: [
|
|
219
|
-
"**/*.test.ts",
|
|
220
|
-
"**/*.spec.ts",
|
|
221
|
-
"**/*.test.js",
|
|
222
|
-
"**/*.spec.js"
|
|
223
|
-
] }));
|
|
224
|
-
for (const file of files) {
|
|
225
|
-
const mod = await import(file);
|
|
226
|
-
if (mod.default && typeof mod.default === "object") {
|
|
227
|
-
if ("emit" in mod.default && "subscribe" in mod.default) {
|
|
228
|
-
const name = getEventName(file);
|
|
229
|
-
mod.default.register(container, name);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
//#endregion
|
|
236
|
-
export { dbChangeInputSchema, dbChangesEvent, defineEvent, defineEventBus, loadEvents };
|