@kelpie/server 0.5.2 → 0.7.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/README.md +5 -2
- package/dist/app.d.ts +13 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +9 -0
- package/dist/app.js.map +1 -1
- package/dist/boot.d.ts +42 -0
- package/dist/boot.d.ts.map +1 -0
- package/dist/boot.js +57 -0
- package/dist/boot.js.map +1 -0
- package/dist/index.d.ts +20 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/actor.d.ts +9 -1
- package/dist/lib/actor.d.ts.map +1 -1
- package/dist/lib/actor.js +13 -0
- package/dist/lib/actor.js.map +1 -1
- package/dist/lib/config.d.ts +51 -6
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +11 -5
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/email.d.ts +15 -63
- package/dist/lib/email.d.ts.map +1 -1
- package/dist/lib/email.js +13 -65
- package/dist/lib/email.js.map +1 -1
- package/dist/lib/fromEnv.d.ts +60 -0
- package/dist/lib/fromEnv.d.ts.map +1 -0
- package/dist/lib/fromEnv.js +94 -0
- package/dist/lib/fromEnv.js.map +1 -0
- package/dist/lib/ids.d.ts +6 -0
- package/dist/lib/ids.d.ts.map +1 -1
- package/dist/lib/ids.js +6 -0
- package/dist/lib/ids.js.map +1 -1
- package/dist/lib/kelpieConfigFile.d.ts +129 -0
- package/dist/lib/kelpieConfigFile.d.ts.map +1 -0
- package/dist/lib/kelpieConfigFile.js +112 -0
- package/dist/lib/kelpieConfigFile.js.map +1 -0
- package/dist/lib/logger.d.ts +38 -7
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +58 -20
- package/dist/lib/logger.js.map +1 -1
- package/dist/modules/agent-tasks/index.d.ts.map +1 -1
- package/dist/modules/agent-tasks/index.js +4 -1
- package/dist/modules/agent-tasks/index.js.map +1 -1
- package/dist/modules/agent-tasks/prompt.d.ts +36 -6
- package/dist/modules/agent-tasks/prompt.d.ts.map +1 -1
- package/dist/modules/agent-tasks/prompt.js +33 -4
- package/dist/modules/agent-tasks/prompt.js.map +1 -1
- package/dist/modules/agent-tasks/service.d.ts.map +1 -1
- package/dist/modules/agent-tasks/service.js +3 -2
- package/dist/modules/agent-tasks/service.js.map +1 -1
- package/dist/modules/agent-tasks/wire.d.ts +11 -0
- package/dist/modules/agent-tasks/wire.d.ts.map +1 -1
- package/dist/modules/agent-tasks/wire.js +1 -0
- package/dist/modules/agent-tasks/wire.js.map +1 -1
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/auth/index.js +8 -7
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/companies/events.d.ts +22 -0
- package/dist/modules/companies/events.d.ts.map +1 -0
- package/dist/modules/companies/events.js +8 -0
- package/dist/modules/companies/events.js.map +1 -0
- package/dist/modules/companies/index.d.ts.map +1 -1
- package/dist/modules/companies/index.js +2 -0
- package/dist/modules/companies/index.js.map +1 -1
- package/dist/modules/companies/service.d.ts +1 -0
- package/dist/modules/companies/service.d.ts.map +1 -1
- package/dist/modules/companies/service.js +8 -11
- package/dist/modules/companies/service.js.map +1 -1
- package/dist/modules/core.d.ts.map +1 -1
- package/dist/modules/core.js +4 -0
- package/dist/modules/core.js.map +1 -1
- package/dist/modules/deals/events.d.ts +31 -0
- package/dist/modules/deals/events.d.ts.map +1 -0
- package/dist/modules/deals/events.js +12 -0
- package/dist/modules/deals/events.js.map +1 -0
- package/dist/modules/deals/index.d.ts.map +1 -1
- package/dist/modules/deals/index.js +2 -0
- package/dist/modules/deals/index.js.map +1 -1
- package/dist/modules/deals/service.d.ts +1 -0
- package/dist/modules/deals/service.d.ts.map +1 -1
- package/dist/modules/deals/service.js +9 -18
- package/dist/modules/deals/service.js.map +1 -1
- package/dist/modules/decisions/events.d.ts +21 -0
- package/dist/modules/decisions/events.d.ts.map +1 -0
- package/dist/modules/decisions/events.js +11 -0
- package/dist/modules/decisions/events.js.map +1 -0
- package/dist/modules/decisions/index.d.ts.map +1 -1
- package/dist/modules/decisions/index.js +2 -0
- package/dist/modules/decisions/index.js.map +1 -1
- package/dist/modules/decisions/service.d.ts +1 -0
- package/dist/modules/decisions/service.d.ts.map +1 -1
- package/dist/modules/decisions/service.js +4 -7
- package/dist/modules/decisions/service.js.map +1 -1
- package/dist/modules/eventCatalogs.d.ts +35 -0
- package/dist/modules/eventCatalogs.d.ts.map +1 -0
- package/dist/modules/eventCatalogs.js +35 -0
- package/dist/modules/eventCatalogs.js.map +1 -0
- package/dist/modules/forms/events.d.ts +37 -0
- package/dist/modules/forms/events.d.ts.map +1 -0
- package/dist/modules/forms/events.js +15 -0
- package/dist/modules/forms/events.js.map +1 -0
- package/dist/modules/forms/index.d.ts.map +1 -1
- package/dist/modules/forms/index.js +2 -0
- package/dist/modules/forms/index.js.map +1 -1
- package/dist/modules/forms/service.d.ts +1 -0
- package/dist/modules/forms/service.d.ts.map +1 -1
- package/dist/modules/forms/service.js +8 -11
- package/dist/modules/forms/service.js.map +1 -1
- package/dist/modules/forms/submission.d.ts +5 -0
- package/dist/modules/forms/submission.d.ts.map +1 -1
- package/dist/modules/forms/submission.js +54 -28
- package/dist/modules/forms/submission.js.map +1 -1
- package/dist/modules/handbook/events.d.ts +28 -0
- package/dist/modules/handbook/events.d.ts.map +1 -0
- package/dist/modules/handbook/events.js +14 -0
- package/dist/modules/handbook/events.js.map +1 -0
- package/dist/modules/handbook/index.d.ts.map +1 -1
- package/dist/modules/handbook/index.js +2 -0
- package/dist/modules/handbook/index.js.map +1 -1
- package/dist/modules/handbook/service.d.ts +1 -0
- package/dist/modules/handbook/service.d.ts.map +1 -1
- package/dist/modules/handbook/service.js +8 -15
- package/dist/modules/handbook/service.js.map +1 -1
- package/dist/modules/hiring/candidates.d.ts +1 -0
- package/dist/modules/hiring/candidates.d.ts.map +1 -1
- package/dist/modules/hiring/candidates.js +8 -11
- package/dist/modules/hiring/candidates.js.map +1 -1
- package/dist/modules/hiring/events.d.ts +35 -0
- package/dist/modules/hiring/events.d.ts.map +1 -0
- package/dist/modules/hiring/events.js +11 -0
- package/dist/modules/hiring/events.js.map +1 -0
- package/dist/modules/hiring/index.d.ts.map +1 -1
- package/dist/modules/hiring/index.js +2 -0
- package/dist/modules/hiring/index.js.map +1 -1
- package/dist/modules/hiring/roles.d.ts +1 -0
- package/dist/modules/hiring/roles.d.ts.map +1 -1
- package/dist/modules/hiring/roles.js +8 -11
- package/dist/modules/hiring/roles.js.map +1 -1
- package/dist/modules/import-export/drafts.d.ts +9 -0
- package/dist/modules/import-export/drafts.d.ts.map +1 -1
- package/dist/modules/import-export/drafts.js +25 -0
- package/dist/modules/import-export/drafts.js.map +1 -1
- package/dist/modules/import-export/events.d.ts +22 -0
- package/dist/modules/import-export/events.d.ts.map +1 -0
- package/dist/modules/import-export/events.js +12 -0
- package/dist/modules/import-export/events.js.map +1 -0
- package/dist/modules/import-export/exportRows.d.ts +9 -0
- package/dist/modules/import-export/exportRows.d.ts.map +1 -1
- package/dist/modules/import-export/exportRows.js +10 -1
- package/dist/modules/import-export/exportRows.js.map +1 -1
- package/dist/modules/import-export/index.d.ts.map +1 -1
- package/dist/modules/import-export/index.js +2 -0
- package/dist/modules/import-export/index.js.map +1 -1
- package/dist/modules/import-export/plan.d.ts +23 -1
- package/dist/modules/import-export/plan.d.ts.map +1 -1
- package/dist/modules/import-export/plan.js +62 -3
- package/dist/modules/import-export/plan.js.map +1 -1
- package/dist/modules/import-export/presets.d.ts.map +1 -1
- package/dist/modules/import-export/presets.js +25 -0
- package/dist/modules/import-export/presets.js.map +1 -1
- package/dist/modules/import-export/repository.d.ts +17 -0
- package/dist/modules/import-export/repository.d.ts.map +1 -1
- package/dist/modules/import-export/repository.js +21 -1
- package/dist/modules/import-export/repository.js.map +1 -1
- package/dist/modules/import-export/routes.d.ts.map +1 -1
- package/dist/modules/import-export/routes.js +5 -1
- package/dist/modules/import-export/routes.js.map +1 -1
- package/dist/modules/import-export/schema.d.ts +55 -0
- package/dist/modules/import-export/schema.d.ts.map +1 -1
- package/dist/modules/import-export/schema.js +13 -1
- package/dist/modules/import-export/schema.js.map +1 -1
- package/dist/modules/import-export/service.d.ts +9 -1
- package/dist/modules/import-export/service.d.ts.map +1 -1
- package/dist/modules/import-export/service.js +104 -17
- package/dist/modules/import-export/service.js.map +1 -1
- package/dist/modules/import-export/tools.d.ts.map +1 -1
- package/dist/modules/import-export/tools.js +7 -1
- package/dist/modules/import-export/tools.js.map +1 -1
- package/dist/modules/import-export/writes.d.ts +16 -0
- package/dist/modules/import-export/writes.d.ts.map +1 -1
- package/dist/modules/import-export/writes.js +77 -2
- package/dist/modules/import-export/writes.js.map +1 -1
- package/dist/modules/notes/events.d.ts +22 -0
- package/dist/modules/notes/events.d.ts.map +1 -0
- package/dist/modules/notes/events.js +12 -0
- package/dist/modules/notes/events.js.map +1 -0
- package/dist/modules/notes/index.d.ts.map +1 -1
- package/dist/modules/notes/index.js +2 -0
- package/dist/modules/notes/index.js.map +1 -1
- package/dist/modules/notes/service.d.ts +1 -0
- package/dist/modules/notes/service.d.ts.map +1 -1
- package/dist/modules/notes/service.js +4 -7
- package/dist/modules/notes/service.js.map +1 -1
- package/dist/modules/opportunities/events.d.ts +31 -0
- package/dist/modules/opportunities/events.d.ts.map +1 -0
- package/dist/modules/opportunities/events.js +12 -0
- package/dist/modules/opportunities/events.js.map +1 -0
- package/dist/modules/opportunities/index.d.ts.map +1 -1
- package/dist/modules/opportunities/index.js +2 -0
- package/dist/modules/opportunities/index.js.map +1 -1
- package/dist/modules/opportunities/service.d.ts +1 -0
- package/dist/modules/opportunities/service.d.ts.map +1 -1
- package/dist/modules/opportunities/service.js +9 -18
- package/dist/modules/opportunities/service.js.map +1 -1
- package/dist/modules/partnerships/events.d.ts +31 -0
- package/dist/modules/partnerships/events.d.ts.map +1 -0
- package/dist/modules/partnerships/events.js +12 -0
- package/dist/modules/partnerships/events.js.map +1 -0
- package/dist/modules/partnerships/index.d.ts.map +1 -1
- package/dist/modules/partnerships/index.js +2 -0
- package/dist/modules/partnerships/index.js.map +1 -1
- package/dist/modules/partnerships/service.d.ts +1 -0
- package/dist/modules/partnerships/service.d.ts.map +1 -1
- package/dist/modules/partnerships/service.js +9 -18
- package/dist/modules/partnerships/service.js.map +1 -1
- package/dist/modules/people/events.d.ts +28 -0
- package/dist/modules/people/events.d.ts.map +1 -0
- package/dist/modules/people/events.js +18 -0
- package/dist/modules/people/events.js.map +1 -0
- package/dist/modules/people/index.d.ts.map +1 -1
- package/dist/modules/people/index.js +2 -0
- package/dist/modules/people/index.js.map +1 -1
- package/dist/modules/people/service.d.ts +1 -0
- package/dist/modules/people/service.d.ts.map +1 -1
- package/dist/modules/people/service.js +8 -11
- package/dist/modules/people/service.js.map +1 -1
- package/dist/modules/pipelines/service.d.ts +4 -0
- package/dist/modules/pipelines/service.d.ts.map +1 -1
- package/dist/modules/pipelines/service.js +31 -8
- package/dist/modules/pipelines/service.js.map +1 -1
- package/dist/modules/plans/events.d.ts +21 -0
- package/dist/modules/plans/events.d.ts.map +1 -0
- package/dist/modules/plans/events.js +11 -0
- package/dist/modules/plans/events.js.map +1 -0
- package/dist/modules/plans/index.d.ts.map +1 -1
- package/dist/modules/plans/index.js +2 -0
- package/dist/modules/plans/index.js.map +1 -1
- package/dist/modules/plans/service.d.ts +1 -0
- package/dist/modules/plans/service.d.ts.map +1 -1
- package/dist/modules/plans/service.js +6 -14
- package/dist/modules/plans/service.js.map +1 -1
- package/dist/modules/positions/events.d.ts +22 -0
- package/dist/modules/positions/events.d.ts.map +1 -0
- package/dist/modules/positions/events.js +8 -0
- package/dist/modules/positions/events.js.map +1 -0
- package/dist/modules/positions/index.d.ts.map +1 -1
- package/dist/modules/positions/index.js +2 -0
- package/dist/modules/positions/index.js.map +1 -1
- package/dist/modules/positions/service.d.ts +1 -0
- package/dist/modules/positions/service.d.ts.map +1 -1
- package/dist/modules/positions/service.js +8 -11
- package/dist/modules/positions/service.js.map +1 -1
- package/dist/modules/raises/events.d.ts +31 -0
- package/dist/modules/raises/events.d.ts.map +1 -0
- package/dist/modules/raises/events.js +12 -0
- package/dist/modules/raises/events.js.map +1 -0
- package/dist/modules/raises/index.d.ts.map +1 -1
- package/dist/modules/raises/index.js +2 -0
- package/dist/modules/raises/index.js.map +1 -1
- package/dist/modules/raises/service.d.ts +1 -0
- package/dist/modules/raises/service.d.ts.map +1 -1
- package/dist/modules/raises/service.js +9 -18
- package/dist/modules/raises/service.js.map +1 -1
- package/dist/modules/reseal.d.ts +55 -0
- package/dist/modules/reseal.d.ts.map +1 -1
- package/dist/modules/reseal.js +90 -1
- package/dist/modules/reseal.js.map +1 -1
- package/dist/modules/smtp-email/index.d.ts +69 -0
- package/dist/modules/smtp-email/index.d.ts.map +1 -0
- package/dist/modules/smtp-email/index.js +82 -0
- package/dist/modules/smtp-email/index.js.map +1 -0
- package/dist/modules/webhooks/index.d.ts.map +1 -1
- package/dist/modules/webhooks/index.js +5 -1
- package/dist/modules/webhooks/index.js.map +1 -1
- package/dist/modules/webhooks/payloads.d.ts +22 -7
- package/dist/modules/webhooks/payloads.d.ts.map +1 -1
- package/dist/modules/webhooks/payloads.js +63 -44
- package/dist/modules/webhooks/payloads.js.map +1 -1
- package/dist/modules/workspace/events.d.ts +52 -0
- package/dist/modules/workspace/events.d.ts.map +1 -0
- package/dist/modules/workspace/events.js +16 -0
- package/dist/modules/workspace/events.js.map +1 -0
- package/dist/modules/workspace/index.d.ts.map +1 -1
- package/dist/modules/workspace/index.js +6 -2
- package/dist/modules/workspace/index.js.map +1 -1
- package/dist/modules/workspace/service.d.ts +1 -0
- package/dist/modules/workspace/service.d.ts.map +1 -1
- package/dist/modules/workspace/service.js +13 -15
- package/dist/modules/workspace/service.js.map +1 -1
- package/dist/runtime/events.d.ts +86 -108
- package/dist/runtime/events.d.ts.map +1 -1
- package/dist/runtime/events.js +123 -52
- package/dist/runtime/events.js.map +1 -1
- package/dist/runtime/module.d.ts +54 -2
- package/dist/runtime/module.d.ts.map +1 -1
- package/dist/runtime/registry.d.ts +23 -0
- package/dist/runtime/registry.d.ts.map +1 -1
- package/dist/runtime/registry.js +102 -2
- package/dist/runtime/registry.js.map +1 -1
- package/dist/runtime/transaction.d.ts +29 -4
- package/dist/runtime/transaction.d.ts.map +1 -1
- package/dist/runtime/transaction.js +48 -7
- package/dist/runtime/transaction.js.map +1 -1
- package/dist/testing/app.d.ts +5 -1
- package/dist/testing/app.d.ts.map +1 -1
- package/dist/testing/app.js +13 -3
- package/dist/testing/app.js.map +1 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +6 -3
- package/dist/testing/database.js.map +1 -1
- package/dist/testing/services.d.ts +21 -1
- package/dist/testing/services.d.ts.map +1 -1
- package/dist/testing/services.js +20 -5
- package/dist/testing/services.js.map +1 -1
- package/migrations/0019_sharp_red_skull.sql +4 -0
- package/migrations/0020_early_nemesis.sql +2 -0
- package/migrations/meta/0019_snapshot.json +5138 -0
- package/migrations/meta/0020_snapshot.json +5138 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +5 -2
- package/src/app.ts +26 -0
- package/src/boot.ts +89 -0
- package/src/index.ts +52 -9
- package/src/lib/actor.ts +15 -1
- package/src/lib/config.ts +65 -7
- package/src/lib/email.ts +22 -92
- package/src/lib/fromEnv.ts +143 -0
- package/src/lib/ids.ts +6 -0
- package/src/lib/kelpieConfigFile.ts +291 -0
- package/src/lib/logger.ts +92 -34
- package/src/modules/agent-tasks/index.ts +4 -1
- package/src/modules/agent-tasks/prompt.ts +57 -9
- package/src/modules/agent-tasks/service.ts +3 -2
- package/src/modules/agent-tasks/wire.ts +12 -0
- package/src/modules/auth/index.ts +11 -9
- package/src/modules/companies/events.ts +25 -0
- package/src/modules/companies/index.ts +2 -0
- package/src/modules/companies/service.ts +8 -11
- package/src/modules/core.ts +4 -0
- package/src/modules/deals/events.ts +34 -0
- package/src/modules/deals/index.ts +2 -0
- package/src/modules/deals/service.ts +17 -18
- package/src/modules/decisions/events.ts +24 -0
- package/src/modules/decisions/index.ts +2 -0
- package/src/modules/decisions/service.ts +8 -7
- package/src/modules/eventCatalogs.ts +35 -0
- package/src/modules/forms/events.ts +37 -0
- package/src/modules/forms/index.ts +2 -0
- package/src/modules/forms/service.ts +12 -11
- package/src/modules/forms/submission.ts +80 -29
- package/src/modules/handbook/events.ts +31 -0
- package/src/modules/handbook/index.ts +2 -0
- package/src/modules/handbook/service.ts +8 -15
- package/src/modules/hiring/candidates.ts +8 -11
- package/src/modules/hiring/events.ts +36 -0
- package/src/modules/hiring/index.ts +2 -0
- package/src/modules/hiring/roles.ts +8 -11
- package/src/modules/import-export/drafts.ts +27 -0
- package/src/modules/import-export/events.ts +25 -0
- package/src/modules/import-export/exportRows.ts +10 -1
- package/src/modules/import-export/index.ts +2 -0
- package/src/modules/import-export/plan.ts +107 -5
- package/src/modules/import-export/presets.ts +25 -0
- package/src/modules/import-export/repository.ts +35 -1
- package/src/modules/import-export/routes.ts +5 -0
- package/src/modules/import-export/schema.ts +13 -0
- package/src/modules/import-export/service.ts +153 -19
- package/src/modules/import-export/tools.ts +9 -0
- package/src/modules/import-export/writes.ts +116 -3
- package/src/modules/notes/events.ts +25 -0
- package/src/modules/notes/index.ts +2 -0
- package/src/modules/notes/service.ts +8 -7
- package/src/modules/opportunities/events.ts +34 -0
- package/src/modules/opportunities/index.ts +2 -0
- package/src/modules/opportunities/service.ts +17 -18
- package/src/modules/partnerships/events.ts +34 -0
- package/src/modules/partnerships/index.ts +2 -0
- package/src/modules/partnerships/service.ts +17 -18
- package/src/modules/people/events.ts +36 -0
- package/src/modules/people/index.ts +2 -0
- package/src/modules/people/service.ts +97 -87
- package/src/modules/pipelines/service.ts +47 -8
- package/src/modules/plans/events.ts +24 -0
- package/src/modules/plans/index.ts +2 -0
- package/src/modules/plans/service.ts +14 -14
- package/src/modules/positions/events.ts +25 -0
- package/src/modules/positions/index.ts +2 -0
- package/src/modules/positions/service.ts +8 -11
- package/src/modules/raises/events.ts +34 -0
- package/src/modules/raises/index.ts +2 -0
- package/src/modules/raises/service.ts +17 -18
- package/src/modules/reseal.ts +144 -1
- package/src/modules/smtp-email/index.ts +119 -0
- package/src/modules/webhooks/index.ts +5 -1
- package/src/modules/webhooks/payloads.ts +94 -48
- package/src/modules/workspace/events.ts +46 -0
- package/src/modules/workspace/index.ts +6 -2
- package/src/modules/workspace/service.ts +37 -15
- package/src/runtime/events.ts +268 -107
- package/src/runtime/module.ts +55 -2
- package/src/runtime/registry.ts +169 -1
- package/src/runtime/transaction.ts +94 -17
- package/src/testing/app.ts +18 -4
- package/src/testing/database.ts +6 -3
- package/src/testing/services.ts +34 -5
|
@@ -134,6 +134,20 @@
|
|
|
134
134
|
"when": 1786656430307,
|
|
135
135
|
"tag": "0018_hard_mystique",
|
|
136
136
|
"breakpoints": true
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"idx": 19,
|
|
140
|
+
"version": "7",
|
|
141
|
+
"when": 1787446086593,
|
|
142
|
+
"tag": "0019_sharp_red_skull",
|
|
143
|
+
"breakpoints": true
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"idx": 20,
|
|
147
|
+
"version": "7",
|
|
148
|
+
"when": 1787447911296,
|
|
149
|
+
"tag": "0020_early_nemesis",
|
|
150
|
+
"breakpoints": true
|
|
137
151
|
}
|
|
138
152
|
]
|
|
139
153
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kelpie/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "The Kelpie service as a library: module runtime, core CRM modules, REST API, MCP surface, and the shared migration pipeline.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kelpie",
|
|
@@ -52,13 +52,16 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@hono/node-server": "^2.0.12",
|
|
55
|
-
"@kelpie/schemas": "^0.
|
|
55
|
+
"@kelpie/schemas": "^0.7.0",
|
|
56
56
|
"@node-rs/argon2": "^2.0.2",
|
|
57
57
|
"drizzle-orm": "^0.45.2",
|
|
58
|
+
"emittery": "^2.0.0",
|
|
58
59
|
"hono": "^4.12.32",
|
|
59
60
|
"nodemailer": "^9.0.5",
|
|
60
61
|
"postgres": "^3.4.9",
|
|
61
62
|
"ulid": "^3.0.2",
|
|
63
|
+
"winston": "^3.19.0",
|
|
64
|
+
"winston-transport": "^4.9.0",
|
|
62
65
|
"zod": "^4.4.3"
|
|
63
66
|
},
|
|
64
67
|
"devDependencies": {
|
package/src/app.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Hono } from 'hono'
|
|
|
2
2
|
import type { Context } from 'hono'
|
|
3
3
|
import { cors } from 'hono/cors'
|
|
4
4
|
|
|
5
|
+
import type { RuntimeMode } from './lib/config.ts'
|
|
5
6
|
import type { DatabaseProbe } from './lib/database.ts'
|
|
6
7
|
import { AppError, internalErrorBody, toErrorBody } from './lib/errors.ts'
|
|
7
8
|
import { PUBLIC_ROUTE_PREFIX } from './lib/http.ts'
|
|
@@ -51,6 +52,18 @@ export interface AppDependencies {
|
|
|
51
52
|
* control (`testing/app.ts`).
|
|
52
53
|
*/
|
|
53
54
|
readonly resolveClientIp: (context: Context) => string
|
|
55
|
+
/**
|
|
56
|
+
* Reported through `GET /v1/public/config` so the browser knows which
|
|
57
|
+
* runtime it is talking to. Defaults to `'production'` so a caller that has
|
|
58
|
+
* not thought about it fails safe: no banner shows.
|
|
59
|
+
*/
|
|
60
|
+
readonly runtimeMode?: RuntimeMode
|
|
61
|
+
/**
|
|
62
|
+
* Reported through `GET /v1/public/config` so a non-production UI can name
|
|
63
|
+
* the site it is on. Undefined is fine and means "this deployment did not
|
|
64
|
+
* name itself".
|
|
65
|
+
*/
|
|
66
|
+
readonly siteName?: string | undefined
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
/** Per-request values the middleware chain sets and handlers read. */
|
|
@@ -161,6 +174,19 @@ export function createApp(dependencies: AppDependencies): Hono<AppBindings> {
|
|
|
161
174
|
app.route(PUBLIC_ROUTE_PREFIX, router)
|
|
162
175
|
}
|
|
163
176
|
|
|
177
|
+
// Public deployment metadata the browser reads once at boot. Sits under the
|
|
178
|
+
// public prefix so the CORS and rate-limit layers above cover it, and takes
|
|
179
|
+
// no credentials for the same reason /healthz does not: the pages that read
|
|
180
|
+
// it include the sign-in page, which runs before any session exists. The
|
|
181
|
+
// runtime mode is already visible in error messages, and the site name
|
|
182
|
+
// exists to be visible; nothing here is sensitive.
|
|
183
|
+
const runtimeMode: RuntimeMode = dependencies.runtimeMode ?? 'production'
|
|
184
|
+
const siteName = dependencies.siteName ?? null
|
|
185
|
+
|
|
186
|
+
app.get(`${PUBLIC_ROUTE_PREFIX}/config`, (context) =>
|
|
187
|
+
context.json({ runtime_mode: runtimeMode, site_name: siteName }, 200),
|
|
188
|
+
)
|
|
189
|
+
|
|
164
190
|
for (const { router } of dependencies.contributions.routers) {
|
|
165
191
|
app.route('/v1', router)
|
|
166
192
|
}
|
package/src/boot.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Environment, KelpieConfig } from './lib/config.ts'
|
|
2
|
+
import { connectDatabase } from './lib/database.ts'
|
|
3
|
+
import type { DatabaseConnection } from './lib/database.ts'
|
|
4
|
+
import { createIdFactory } from './lib/ids.ts'
|
|
5
|
+
import type { IdFactory } from './lib/ids.ts'
|
|
6
|
+
import { resolveKelpieConfig } from './lib/kelpieConfigFile.ts'
|
|
7
|
+
import type { KelpieConfigInput } from './lib/kelpieConfigFile.ts'
|
|
8
|
+
import { createLogger, createTransportForDestination } from './lib/logger.ts'
|
|
9
|
+
import type { Logger } from './lib/logger.ts'
|
|
10
|
+
import { readModuleConfigFile } from './lib/moduleConfig.ts'
|
|
11
|
+
import { resolveActorFrom } from './modules/auth/credentials.ts'
|
|
12
|
+
import type { CredentialDependencies } from './modules/auth/credentials.ts'
|
|
13
|
+
import { createEventBus } from './runtime/events.ts'
|
|
14
|
+
import { registerModules } from './runtime/registry.ts'
|
|
15
|
+
import type { ModuleContributions } from './runtime/registry.ts'
|
|
16
|
+
import { createTransactionScope } from './runtime/transaction.ts'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A resolved assembly: config parsed, database connected, modules registered.
|
|
20
|
+
*
|
|
21
|
+
* This is the common prelude every entry point needs before it does its own
|
|
22
|
+
* work. `server.ts` goes on to build the app and serve; `migrate.ts` goes on to
|
|
23
|
+
* apply migrations and exit. Both start here, so the wiring lives in one place
|
|
24
|
+
* rather than drifting between two copies.
|
|
25
|
+
*
|
|
26
|
+
* The database is connected but not opened: postgres.js connects lazily, and
|
|
27
|
+
* registration issues no query, so a caller that only needs the contributions
|
|
28
|
+
* pays for no connection. The caller owns `database.close()`.
|
|
29
|
+
*/
|
|
30
|
+
export interface AssemblyBoot {
|
|
31
|
+
readonly config: KelpieConfig
|
|
32
|
+
readonly logger: Logger
|
|
33
|
+
readonly database: DatabaseConnection
|
|
34
|
+
readonly createId: IdFactory
|
|
35
|
+
readonly credentials: CredentialDependencies
|
|
36
|
+
readonly contributions: ModuleContributions
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolves an assembly's config, connects the database, and runs the module
|
|
41
|
+
* registration pass.
|
|
42
|
+
*
|
|
43
|
+
* Registration touches no database. It only collects what the modules
|
|
44
|
+
* contribute, migrations directories included, which is why an entry point can
|
|
45
|
+
* call this and then decide whether to serve, migrate, or both.
|
|
46
|
+
*
|
|
47
|
+
* @throws ConfigurationError when a required variable is missing or malformed.
|
|
48
|
+
* @throws ModuleConfigFileError when the module override file cannot be read.
|
|
49
|
+
* @throws ModuleBootError when the module list is invalid or a module fails to
|
|
50
|
+
* register. The caller maps these to a fix hint and an exit code.
|
|
51
|
+
*/
|
|
52
|
+
export async function bootAssembly(
|
|
53
|
+
kelpieConfig: KelpieConfigInput,
|
|
54
|
+
environment: Environment,
|
|
55
|
+
): Promise<AssemblyBoot> {
|
|
56
|
+
const config = resolveKelpieConfig(kelpieConfig, environment)
|
|
57
|
+
const logger = createLogger({
|
|
58
|
+
level: config.logging.level,
|
|
59
|
+
transports: config.logging.destinations.map(createTransportForDestination),
|
|
60
|
+
})
|
|
61
|
+
const database = connectDatabase(config.databaseUrl, logger)
|
|
62
|
+
const events = createEventBus(logger)
|
|
63
|
+
const createId = createIdFactory()
|
|
64
|
+
const credentials: CredentialDependencies = { db: database.db, now: () => new Date() }
|
|
65
|
+
const moduleConfig = readModuleConfigFile(config.moduleConfigPath)
|
|
66
|
+
const contributions = await registerModules({
|
|
67
|
+
modules: kelpieConfig.modules,
|
|
68
|
+
environment: config.env,
|
|
69
|
+
logger,
|
|
70
|
+
events,
|
|
71
|
+
moduleConfig,
|
|
72
|
+
resolveActor: (context) => resolveActorFrom(credentials, context),
|
|
73
|
+
services: {
|
|
74
|
+
db: database.db,
|
|
75
|
+
transaction: createTransactionScope({ db: database.db, bus: events, logger, createId }),
|
|
76
|
+
createId,
|
|
77
|
+
now: () => new Date(),
|
|
78
|
+
appBaseUrl: config.appBaseUrl,
|
|
79
|
+
secretEncryption: config.secretEncryption,
|
|
80
|
+
},
|
|
81
|
+
// `provider` picks a named sender from the runtime's registry. `'log'` is
|
|
82
|
+
// built in; `'smtp'` is registered by the built-in `smtp-email` core
|
|
83
|
+
// module; other names come from third-party provider modules listed in
|
|
84
|
+
// `kelpie.config.ts`. `from` is the address on every outgoing message.
|
|
85
|
+
email: { provider: config.email.EMAIL_PROVIDER, from: config.email.EMAIL_FROM },
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return { config, logger, database, createId, credentials, contributions }
|
|
89
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
|
+
// Carries each module's `KelpieEventMap` augmentation into the emitted
|
|
2
|
+
// `index.d.ts` so consumers installed from npm get a typed `event.data`. See
|
|
3
|
+
// `modules/eventCatalogs.ts` for why the entry point has to reference them.
|
|
4
|
+
import './modules/eventCatalogs.ts'
|
|
5
|
+
|
|
1
6
|
export { createApp } from './app.ts'
|
|
2
7
|
export type { AppDependencies, AppBindings } from './app.ts'
|
|
3
8
|
|
|
9
|
+
export { bootAssembly } from './boot.ts'
|
|
10
|
+
export type { AssemblyBoot } from './boot.ts'
|
|
11
|
+
|
|
4
12
|
export { WebBundleError, serveWebBundle } from './webBundle.ts'
|
|
5
13
|
export type { WebBundleOptions } from './webBundle.ts'
|
|
6
14
|
|
|
7
15
|
export { ConfigurationError, loadConfig } from './lib/config.ts'
|
|
8
16
|
export type { Environment, KelpieConfig, LogLevel, RuntimeMode } from './lib/config.ts'
|
|
9
17
|
|
|
18
|
+
export { fromEnv, isFromEnvMarker, resolveMarker, resolveMarkers } from './lib/fromEnv.ts'
|
|
19
|
+
export type { ConfigValue, FromEnvMarker, FromEnvProblem, ResolveResult } from './lib/fromEnv.ts'
|
|
20
|
+
|
|
21
|
+
export { defineKelpieConfig, resolveKelpieConfig } from './lib/kelpieConfigFile.ts'
|
|
22
|
+
export type {
|
|
23
|
+
EmailInput,
|
|
24
|
+
KelpieConfigInput,
|
|
25
|
+
RateLimitBudgetInput,
|
|
26
|
+
RateLimitInput,
|
|
27
|
+
SecretEncryptionInput,
|
|
28
|
+
} from './lib/kelpieConfigFile.ts'
|
|
29
|
+
|
|
30
|
+
export { appUrlConfigSchema } from './lib/appUrl.ts'
|
|
31
|
+
export type { AppUrlConfig } from './lib/appUrl.ts'
|
|
32
|
+
|
|
10
33
|
export { resolveClientIpFrom } from './lib/clientIp.ts'
|
|
11
34
|
|
|
12
35
|
export { ModuleConfigFileError, readModuleConfigFile } from './lib/moduleConfig.ts'
|
|
@@ -23,9 +46,12 @@ export type { Database, DatabaseConnection, DatabaseProbe } from './lib/database
|
|
|
23
46
|
|
|
24
47
|
export * as schema from './schema/index.ts'
|
|
25
48
|
|
|
26
|
-
export {
|
|
49
|
+
export { createLogEmailSender, emailConfigSchema } from './lib/email.ts'
|
|
27
50
|
export type { EmailConfig, EmailMessage, EmailSender } from './lib/email.ts'
|
|
28
51
|
|
|
52
|
+
export { SMTP_EMAIL_PROVIDER, createSmtpEmailModule, createSmtpEmailSender } from './modules/smtp-email/index.ts'
|
|
53
|
+
export type { SmtpEmailConfig, SmtpEmailModuleOptions, SmtpTransport } from './modules/smtp-email/index.ts'
|
|
54
|
+
|
|
29
55
|
export { MINIMUM_PASSWORD_LENGTH, hashPassword, isPasswordStrongEnough, verifyPassword } from './lib/passwords.ts'
|
|
30
56
|
export { generateToken, hashToken, tokenHashesMatch } from './lib/tokens.ts'
|
|
31
57
|
|
|
@@ -49,8 +75,13 @@ export type { ErrorBody, ErrorCode, ErrorDetail, ErrorStatus, ValidationIssue }
|
|
|
49
75
|
export { createIdFactory, idPrefixes } from './lib/ids.ts'
|
|
50
76
|
export type { IdFactory, ObjectKind } from './lib/ids.ts'
|
|
51
77
|
|
|
52
|
-
export {
|
|
53
|
-
|
|
78
|
+
export {
|
|
79
|
+
createCaptureTransport,
|
|
80
|
+
createLogger,
|
|
81
|
+
createStdoutTransport,
|
|
82
|
+
createTransportForDestination,
|
|
83
|
+
} from './lib/logger.ts'
|
|
84
|
+
export type { CreateLoggerOptions, LogFields, Logger, LoggingDestination } from './lib/logger.ts'
|
|
54
85
|
|
|
55
86
|
export type {
|
|
56
87
|
KelpieModule,
|
|
@@ -76,21 +107,33 @@ export type {
|
|
|
76
107
|
|
|
77
108
|
export { SEATS_LIMIT } from './modules/workspace/capabilities.ts'
|
|
78
109
|
|
|
79
|
-
export {
|
|
110
|
+
export {
|
|
111
|
+
RECORD_OBJECT_TYPES,
|
|
112
|
+
checkEventCycle,
|
|
113
|
+
createEventBus,
|
|
114
|
+
currentEventChain,
|
|
115
|
+
} from './runtime/events.ts'
|
|
80
116
|
export type {
|
|
81
|
-
|
|
82
|
-
|
|
117
|
+
AnyEventHandler,
|
|
118
|
+
CycleGuardOutcome,
|
|
83
119
|
EventBus,
|
|
120
|
+
EventBusOptions,
|
|
121
|
+
EventCatalog,
|
|
122
|
+
EventChainEntry,
|
|
84
123
|
EventHandler,
|
|
124
|
+
EventName,
|
|
125
|
+
KelpieEventMap,
|
|
85
126
|
RecordObjectType,
|
|
86
|
-
|
|
127
|
+
SubscribeOptions,
|
|
87
128
|
} from './runtime/events.ts'
|
|
129
|
+
export type { ModuleEventCatalog } from './runtime/module.ts'
|
|
88
130
|
|
|
89
131
|
export { createTransactionScope } from './runtime/transaction.ts'
|
|
90
132
|
export type {
|
|
91
133
|
BufferedEvents,
|
|
92
134
|
Transaction,
|
|
93
135
|
TransactionContext,
|
|
136
|
+
TransactionOptions,
|
|
94
137
|
TransactionScope,
|
|
95
138
|
TransactionScopeDependencies,
|
|
96
139
|
} from './runtime/transaction.ts'
|
|
@@ -100,8 +143,8 @@ export type { MigrationPlanStep } from './runtime/migrate.ts'
|
|
|
100
143
|
|
|
101
144
|
export { coreMigrationsDirectory, coreModules } from './modules/core.ts'
|
|
102
145
|
|
|
103
|
-
export { resealStoredSecrets } from './modules/reseal.ts'
|
|
104
|
-
export type { ResealColumnOutcome, ResealOutcome } from './modules/reseal.ts'
|
|
146
|
+
export { resealStoredSecrets, runReseal } from './modules/reseal.ts'
|
|
147
|
+
export type { ResealColumnOutcome, ResealOutcome, ResealPass, RunResealOptions } from './modules/reseal.ts'
|
|
105
148
|
|
|
106
149
|
export { KEY_KINDS, kindOfSecret, mintKey, parseKeyKind, readBearerToken } from './modules/api-keys/keys.ts'
|
|
107
150
|
export type { KeyKind, MintedKey } from './modules/api-keys/keys.ts'
|
package/src/lib/actor.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MemberRole } from '@kelpie/schemas'
|
|
1
|
+
import type { EventActor, MemberRole } from '@kelpie/schemas'
|
|
2
2
|
|
|
3
3
|
import { AppError } from './errors.ts'
|
|
4
4
|
|
|
@@ -97,3 +97,17 @@ export function actorUserId(actor: Actor): string | null {
|
|
|
97
97
|
export function actorMemberId(actor: Actor): string | null {
|
|
98
98
|
return actor.memberId
|
|
99
99
|
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Maps the request actor to the actor stamped on domain events.
|
|
103
|
+
*
|
|
104
|
+
* A signed-in user becomes `user`. A workspace key (no user behind it) becomes
|
|
105
|
+
* `system`, because the event was caused by the workspace itself rather than
|
|
106
|
+
* any one person.
|
|
107
|
+
*/
|
|
108
|
+
export function toEventActor(actor: Actor): EventActor {
|
|
109
|
+
if (actor.userId !== null) {
|
|
110
|
+
return { kind: 'user', id: actor.userId }
|
|
111
|
+
}
|
|
112
|
+
return { kind: 'system' }
|
|
113
|
+
}
|
package/src/lib/config.ts
CHANGED
|
@@ -3,8 +3,14 @@ import { z } from 'zod'
|
|
|
3
3
|
import { emailConfigSchema } from './email.ts'
|
|
4
4
|
import type { EmailConfig } from './email.ts'
|
|
5
5
|
import { describeValidationIssue } from './errors.ts'
|
|
6
|
+
// `EmailConfig` is `{ EMAIL_PROVIDER, EMAIL_FROM }`. `EMAIL_PROVIDER` names one
|
|
7
|
+
// of the registered senders; `'log'` is always available, other names come
|
|
8
|
+
// from provider modules. Provider-specific config (SMTP settings, an API key)
|
|
9
|
+
// belongs to the module that reads it through `context.config(...)`.
|
|
10
|
+
import type { LoggingDestination } from './logger.ts'
|
|
6
11
|
import { rateLimitConfigFrom, rateLimitConfigSchema } from './rateLimit.ts'
|
|
7
12
|
import type { RateLimitConfig } from './rateLimit.ts'
|
|
13
|
+
import type { SecretEncryptionConfig } from './secrets.ts'
|
|
8
14
|
|
|
9
15
|
/**
|
|
10
16
|
* The single place the service reads environment variables. Every other module
|
|
@@ -18,12 +24,36 @@ export type RuntimeMode = 'development' | 'test' | 'production'
|
|
|
18
24
|
|
|
19
25
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error'
|
|
20
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Everything the logger needs to boot: the minimum severity to emit, and the
|
|
29
|
+
* ordered list of destinations each line is written to. Stdout is one such
|
|
30
|
+
* destination; adding a file or a hosted collector means adding an entry to
|
|
31
|
+
* `destinations` and extending `LoggingDestination` in `lib/logger.ts`.
|
|
32
|
+
*/
|
|
33
|
+
export interface LoggingConfig {
|
|
34
|
+
readonly level: LogLevel
|
|
35
|
+
readonly destinations: readonly LoggingDestination[]
|
|
36
|
+
}
|
|
37
|
+
|
|
21
38
|
export interface KelpieConfig {
|
|
22
39
|
readonly runtimeMode: RuntimeMode
|
|
23
40
|
readonly port: number
|
|
24
41
|
readonly databaseUrl: string
|
|
25
|
-
|
|
26
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Human-readable name of this deployment ("dev", "demo", "cloud", or
|
|
44
|
+
* whatever the self-hoster picks). Undefined is normal. Exposed to the
|
|
45
|
+
* browser through `GET /v1/public/config` so a non-production UI can name
|
|
46
|
+
* the site it is on. Ignored when `runtimeMode` is `production`.
|
|
47
|
+
*/
|
|
48
|
+
readonly siteName: string | undefined
|
|
49
|
+
readonly logging: LoggingConfig
|
|
50
|
+
/**
|
|
51
|
+
* The provider name the module runtime looks up in its registry, and the
|
|
52
|
+
* from address every outgoing message uses. See `lib/email.ts` for the
|
|
53
|
+
* shape and rules; `'log'` is always available, `'smtp'` is registered by
|
|
54
|
+
* the built-in `smtp-email` core module, and other names come from
|
|
55
|
+
* third-party provider modules.
|
|
56
|
+
*/
|
|
27
57
|
readonly email: EmailConfig
|
|
28
58
|
/**
|
|
29
59
|
* Path to the deploy-time module override file (`lib/moduleConfig.ts`).
|
|
@@ -44,6 +74,28 @@ export interface KelpieConfig {
|
|
|
44
74
|
* client IP is read from `X-Forwarded-For` (`lib/clientIp.ts`).
|
|
45
75
|
*/
|
|
46
76
|
readonly trustedProxyHopCount: number
|
|
77
|
+
/**
|
|
78
|
+
* What modules see through `context.config(schema)`. `resolveKelpieConfig`
|
|
79
|
+
* produces this by merging any `env` section in `kelpie.config.ts` over
|
|
80
|
+
* `process.env`, so a key in the config file wins for that key and unset
|
|
81
|
+
* keys pass through. `loadConfig` sets it to the environment it was given,
|
|
82
|
+
* so an old caller keeps the same behaviour.
|
|
83
|
+
*/
|
|
84
|
+
readonly env: Environment
|
|
85
|
+
/**
|
|
86
|
+
* The deployment's base URL, source of every emailed link. Optional so an
|
|
87
|
+
* older assembly that omits it from `kelpie.config.ts` still boots: modules
|
|
88
|
+
* that need it fall back to `context.config(appUrlConfigSchema)` when unset.
|
|
89
|
+
* A new assembly declares it as a first-class field.
|
|
90
|
+
*/
|
|
91
|
+
readonly appBaseUrl: string | undefined
|
|
92
|
+
/**
|
|
93
|
+
* The key(s) that seal stored secrets. Optional for the same reason
|
|
94
|
+
* `appBaseUrl` is: modules that need it fall back to
|
|
95
|
+
* `context.config(secretEncryptionConfigSchema)`. The base assembly declares
|
|
96
|
+
* it, so both webhooks and agent-tasks receive it through `services`.
|
|
97
|
+
*/
|
|
98
|
+
readonly secretEncryption: SecretEncryptionConfig | undefined
|
|
47
99
|
}
|
|
48
100
|
|
|
49
101
|
/** Thrown at boot when the environment cannot produce a valid configuration. */
|
|
@@ -74,6 +126,7 @@ const environmentSchema = z.object({
|
|
|
74
126
|
.refine(isPostgresUrl, { message: 'must be a postgres:// or postgresql:// connection string' }),
|
|
75
127
|
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']),
|
|
76
128
|
KELPIE_MODULE_CONFIG_PATH: z.string().min(1).optional(),
|
|
129
|
+
KELPIE_SITE_NAME: z.string().min(1).optional(),
|
|
77
130
|
WEB_BUNDLE_DIR: z.string().min(1).optional(),
|
|
78
131
|
TRUSTED_PROXY_HOP_COUNT: z.coerce.number().int().nonnegative().default(0),
|
|
79
132
|
...rateLimitConfigSchema.shape,
|
|
@@ -82,10 +135,8 @@ const environmentSchema = z.object({
|
|
|
82
135
|
/**
|
|
83
136
|
* Parses an environment into a validated config.
|
|
84
137
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* only its own `EMAIL_PROVIDER`-keyed branches. Problems from both parses are
|
|
88
|
-
* combined into one error, preserving the "every missing variable at once" rule.
|
|
138
|
+
* The email variables are parsed separately so a caller sees every missing
|
|
139
|
+
* variable at once, rather than one at a time.
|
|
89
140
|
*
|
|
90
141
|
* @param environment Raw variables, normally `process.env`.
|
|
91
142
|
* @throws ConfigurationError listing every invalid or missing variable.
|
|
@@ -107,11 +158,18 @@ export function loadConfig(environment: Environment): KelpieConfig {
|
|
|
107
158
|
runtimeMode: environmentResult.data.NODE_ENV,
|
|
108
159
|
port: environmentResult.data.PORT,
|
|
109
160
|
databaseUrl: environmentResult.data.DATABASE_URL,
|
|
110
|
-
|
|
161
|
+
logging: {
|
|
162
|
+
level: environmentResult.data.LOG_LEVEL,
|
|
163
|
+
destinations: [{ kind: 'stdout' }],
|
|
164
|
+
},
|
|
111
165
|
email: emailResult.data,
|
|
112
166
|
moduleConfigPath: environmentResult.data.KELPIE_MODULE_CONFIG_PATH,
|
|
167
|
+
siteName: environmentResult.data.KELPIE_SITE_NAME,
|
|
113
168
|
webBundleDirectory: environmentResult.data.WEB_BUNDLE_DIR,
|
|
114
169
|
rateLimit: rateLimitConfigFrom(environmentResult.data),
|
|
115
170
|
trustedProxyHopCount: environmentResult.data.TRUSTED_PROXY_HOP_COUNT,
|
|
171
|
+
env: environment,
|
|
172
|
+
appBaseUrl: undefined,
|
|
173
|
+
secretEncryption: undefined,
|
|
116
174
|
}
|
|
117
175
|
}
|
package/src/lib/email.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import nodemailer from 'nodemailer'
|
|
2
1
|
import { z } from 'zod'
|
|
3
2
|
|
|
4
|
-
import { describeThrown } from './errors.ts'
|
|
5
3
|
import type { Logger } from './logger.ts'
|
|
6
4
|
|
|
7
5
|
/**
|
|
@@ -9,15 +7,15 @@ import type { Logger } from './logger.ts'
|
|
|
9
7
|
* and account-change notifications, nothing else. Kelpie never sends outreach
|
|
10
8
|
* email.
|
|
11
9
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* a
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
10
|
+
* The assembly names one named provider in `kelpie.config.ts`'s
|
|
11
|
+
* `email.provider`. The module runtime resolves the name against a registry
|
|
12
|
+
* every module can contribute to via `context.provideEmailSender(name, sender)`.
|
|
13
|
+
* `'log'` is a built-in name the runtime always registers, so a bare install
|
|
14
|
+
* has something to point at. `'smtp'` is registered by the built-in
|
|
15
|
+
* `smtp-email` core module. Other names come from third-party provider modules
|
|
16
|
+
* (Resend, Postmark, SES) that follow the same registry pattern. Transport is
|
|
17
|
+
* arbitrary: the module builds one behind the `EmailSender` interface and
|
|
18
|
+
* hands it to core, which knows only how to `send`.
|
|
21
19
|
*/
|
|
22
20
|
|
|
23
21
|
export interface EmailMessage {
|
|
@@ -30,40 +28,29 @@ export interface EmailSender {
|
|
|
30
28
|
send(message: EmailMessage): Promise<void>
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
const
|
|
34
|
-
|
|
31
|
+
export const emailConfigSchema = z.object({
|
|
32
|
+
/**
|
|
33
|
+
* The named provider `registerModules` resolves against its registry. `'log'`
|
|
34
|
+
* is a built-in, always present. Any other name needs a module that
|
|
35
|
+
* registered it via `context.provideEmailSender(name, sender)`. Free-string
|
|
36
|
+
* so a self-hoster can add a module core has never heard of.
|
|
37
|
+
*/
|
|
38
|
+
EMAIL_PROVIDER: z.string().min(1),
|
|
35
39
|
EMAIL_FROM: z.string().min(1),
|
|
36
40
|
})
|
|
37
41
|
|
|
38
|
-
const smtpEmailConfigSchema = z.object({
|
|
39
|
-
EMAIL_PROVIDER: z.literal('smtp'),
|
|
40
|
-
EMAIL_FROM: z.string().min(1),
|
|
41
|
-
SMTP_HOST: z.string().min(1),
|
|
42
|
-
SMTP_PORT: z.coerce.number().int().positive().max(65535),
|
|
43
|
-
SMTP_SECURE: z.enum(['true', 'false']).transform((value) => value === 'true'),
|
|
44
|
-
SMTP_USER: z.string().min(1),
|
|
45
|
-
SMTP_PASSWORD: z.string().min(1),
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
export const emailConfigSchema = z.discriminatedUnion('EMAIL_PROVIDER', [
|
|
49
|
-
logEmailConfigSchema,
|
|
50
|
-
smtpEmailConfigSchema,
|
|
51
|
-
])
|
|
52
|
-
|
|
53
42
|
export type EmailConfig = z.infer<typeof emailConfigSchema>
|
|
54
|
-
export type SmtpEmailConfig = z.infer<typeof smtpEmailConfigSchema>
|
|
55
43
|
|
|
56
44
|
/**
|
|
57
|
-
* Writes the message to the log instead of sending it.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* so nobody reaches this provider without naming it.
|
|
45
|
+
* Writes the message to the log instead of sending it. The registry default,
|
|
46
|
+
* swapped out for a real sender by picking a different provider name in
|
|
47
|
+
* `email.provider` (`'smtp'` for the built-in SMTP module, or any name a
|
|
48
|
+
* third-party provider module registers).
|
|
62
49
|
*/
|
|
63
50
|
export function createLogEmailSender(logger: Logger, from: string): EmailSender {
|
|
64
51
|
return {
|
|
65
52
|
send(message) {
|
|
66
|
-
logger.info('email not sent: provider
|
|
53
|
+
logger.info('email not sent: no provider module registered', {
|
|
67
54
|
from,
|
|
68
55
|
to: message.to,
|
|
69
56
|
subject: message.subject,
|
|
@@ -74,60 +61,3 @@ export function createLogEmailSender(logger: Logger, from: string): EmailSender
|
|
|
74
61
|
},
|
|
75
62
|
}
|
|
76
63
|
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* What `createSmtpEmailSender` sends a message through. A real deployment gets
|
|
80
|
-
* one backed by `nodemailer`; a test injects one that records calls, so the
|
|
81
|
-
* sender is verifiable without a container running a real SMTP conversation.
|
|
82
|
-
*/
|
|
83
|
-
export interface SmtpTransport {
|
|
84
|
-
sendMail(message: { from: string; to: string; subject: string; text: string }): Promise<unknown>
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function createNodemailerTransport(config: SmtpEmailConfig): SmtpTransport {
|
|
88
|
-
return nodemailer.createTransport({
|
|
89
|
-
host: config.SMTP_HOST,
|
|
90
|
-
port: config.SMTP_PORT,
|
|
91
|
-
secure: config.SMTP_SECURE,
|
|
92
|
-
auth: { user: config.SMTP_USER, pass: config.SMTP_PASSWORD },
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Sends over SMTP. `transport` defaults to a real connection built from `config`. */
|
|
97
|
-
export function createSmtpEmailSender(
|
|
98
|
-
config: SmtpEmailConfig,
|
|
99
|
-
logger: Logger,
|
|
100
|
-
transport: SmtpTransport = createNodemailerTransport(config),
|
|
101
|
-
): EmailSender {
|
|
102
|
-
return {
|
|
103
|
-
async send(message) {
|
|
104
|
-
try {
|
|
105
|
-
await transport.sendMail({
|
|
106
|
-
from: config.EMAIL_FROM,
|
|
107
|
-
to: message.to,
|
|
108
|
-
subject: message.subject,
|
|
109
|
-
text: message.body,
|
|
110
|
-
})
|
|
111
|
-
} catch (cause) {
|
|
112
|
-
const reason = describeThrown(cause)
|
|
113
|
-
logger.error('smtp send failed', { to: message.to, reason })
|
|
114
|
-
throw new Error(`Failed to send email to ${message.to} over SMTP: ${reason}`, { cause })
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Builds the configured sender.
|
|
122
|
-
*
|
|
123
|
-
* @throws Never. An unknown provider cannot reach here: the config schema is a
|
|
124
|
-
* discriminated union, so boot rejects it first.
|
|
125
|
-
*/
|
|
126
|
-
export function createEmailSender(config: EmailConfig, logger: Logger): EmailSender {
|
|
127
|
-
switch (config.EMAIL_PROVIDER) {
|
|
128
|
-
case 'log':
|
|
129
|
-
return createLogEmailSender(logger, config.EMAIL_FROM)
|
|
130
|
-
case 'smtp':
|
|
131
|
-
return createSmtpEmailSender(config, logger)
|
|
132
|
-
}
|
|
133
|
-
}
|