@kelpie/server 0.12.0 → 0.14.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/app.d.ts +13 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +8 -1
- package/dist/app.js.map +1 -1
- package/dist/boot.d.ts +9 -0
- package/dist/boot.d.ts.map +1 -1
- package/dist/boot.js +12 -2
- package/dist/boot.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/apiKeyScopes.d.ts.map +1 -1
- package/dist/lib/apiKeyScopes.js +6 -0
- package/dist/lib/apiKeyScopes.js.map +1 -1
- package/dist/lib/columns.d.ts +6 -0
- package/dist/lib/columns.d.ts.map +1 -1
- package/dist/lib/columns.js +5 -3
- package/dist/lib/columns.js.map +1 -1
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +3 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/egress.d.ts.map +1 -1
- package/dist/lib/egress.js +8 -4
- package/dist/lib/egress.js.map +1 -1
- package/dist/lib/errors.d.ts +3 -3
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +13 -5
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/ids.d.ts +11 -0
- package/dist/lib/ids.d.ts.map +1 -1
- package/dist/lib/ids.js +11 -0
- package/dist/lib/ids.js.map +1 -1
- package/dist/lib/jobs.d.ts +110 -0
- package/dist/lib/jobs.d.ts.map +1 -0
- package/dist/lib/jobs.js +5 -0
- package/dist/lib/jobs.js.map +1 -0
- package/dist/lib/kelpieConfigFile.d.ts +15 -1
- package/dist/lib/kelpieConfigFile.d.ts.map +1 -1
- package/dist/lib/kelpieConfigFile.js +33 -1
- package/dist/lib/kelpieConfigFile.js.map +1 -1
- package/dist/modules/agent-tasks/catalog.d.ts.map +1 -1
- package/dist/modules/agent-tasks/catalog.js +32 -1
- package/dist/modules/agent-tasks/catalog.js.map +1 -1
- package/dist/modules/agent-tasks/index.d.ts.map +1 -1
- package/dist/modules/agent-tasks/index.js +1 -0
- package/dist/modules/agent-tasks/index.js.map +1 -1
- package/dist/modules/agent-tasks/resolve.d.ts.map +1 -1
- package/dist/modules/agent-tasks/resolve.js +5 -0
- package/dist/modules/agent-tasks/resolve.js.map +1 -1
- package/dist/modules/agent-tasks/routes.d.ts +2 -0
- package/dist/modules/agent-tasks/routes.d.ts.map +1 -1
- package/dist/modules/attachedRecords.d.ts +22 -8
- package/dist/modules/attachedRecords.d.ts.map +1 -1
- package/dist/modules/attachedRecords.js +42 -5
- package/dist/modules/attachedRecords.js.map +1 -1
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/auth/index.js +1 -0
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/auth/repository.d.ts +14 -0
- package/dist/modules/auth/repository.d.ts.map +1 -1
- package/dist/modules/auth/repository.js +20 -2
- package/dist/modules/auth/repository.js.map +1 -1
- package/dist/modules/auth/routes.d.ts.map +1 -1
- package/dist/modules/auth/routes.js +30 -0
- package/dist/modules/auth/routes.js.map +1 -1
- package/dist/modules/auth/service.d.ts +28 -0
- package/dist/modules/auth/service.d.ts.map +1 -1
- package/dist/modules/auth/service.js +72 -4
- package/dist/modules/auth/service.js.map +1 -1
- package/dist/modules/companies/repository.d.ts.map +1 -1
- package/dist/modules/companies/repository.js +1 -1
- package/dist/modules/companies/repository.js.map +1 -1
- package/dist/modules/companies/routes.d.ts +2 -4
- package/dist/modules/companies/routes.d.ts.map +1 -1
- package/dist/modules/companies/routes.js +6 -11
- package/dist/modules/companies/routes.js.map +1 -1
- package/dist/modules/companies/schema.d.ts +13 -28
- package/dist/modules/companies/schema.d.ts.map +1 -1
- package/dist/modules/companies/schema.js +2 -2
- package/dist/modules/companies/schema.js.map +1 -1
- package/dist/modules/companies/service.d.ts +2 -3
- package/dist/modules/companies/service.d.ts.map +1 -1
- package/dist/modules/companies/service.js +4 -7
- package/dist/modules/companies/service.js.map +1 -1
- package/dist/modules/core.d.ts.map +1 -1
- package/dist/modules/core.js +2 -0
- package/dist/modules/core.js.map +1 -1
- package/dist/modules/custom-fields/repository.d.ts.map +1 -1
- package/dist/modules/custom-fields/repository.js +2 -0
- package/dist/modules/custom-fields/repository.js.map +1 -1
- package/dist/modules/custom-fields/routes.d.ts +1 -0
- package/dist/modules/custom-fields/routes.d.ts.map +1 -1
- package/dist/modules/dashboard/index.d.ts.map +1 -1
- package/dist/modules/dashboard/index.js +1 -0
- package/dist/modules/dashboard/index.js.map +1 -1
- package/dist/modules/dashboard/repository.d.ts +13 -0
- package/dist/modules/dashboard/repository.d.ts.map +1 -1
- package/dist/modules/dashboard/repository.js +36 -0
- package/dist/modules/dashboard/repository.js.map +1 -1
- package/dist/modules/dashboard/routes.d.ts.map +1 -1
- package/dist/modules/dashboard/routes.js +13 -0
- package/dist/modules/dashboard/routes.js.map +1 -1
- package/dist/modules/dashboard/service.d.ts +11 -0
- package/dist/modules/dashboard/service.d.ts.map +1 -1
- package/dist/modules/dashboard/service.js +24 -1
- package/dist/modules/dashboard/service.js.map +1 -1
- package/dist/modules/decisions/repository.d.ts.map +1 -1
- package/dist/modules/decisions/repository.js +2 -0
- package/dist/modules/decisions/repository.js.map +1 -1
- package/dist/modules/decisions/routes.d.ts +2 -0
- package/dist/modules/decisions/routes.d.ts.map +1 -1
- package/dist/modules/events/attendances.d.ts +35 -0
- package/dist/modules/events/attendances.d.ts.map +1 -0
- package/dist/modules/events/attendances.js +157 -0
- package/dist/modules/events/attendances.js.map +1 -0
- package/dist/modules/events/catalog.d.ts +35 -0
- package/dist/modules/events/catalog.d.ts.map +1 -0
- package/dist/modules/events/catalog.js +11 -0
- package/dist/modules/events/catalog.js.map +1 -0
- package/dist/modules/events/index.d.ts +10 -0
- package/dist/modules/events/index.d.ts.map +1 -0
- package/dist/modules/events/index.js +47 -0
- package/dist/modules/events/index.js.map +1 -0
- package/dist/modules/events/repository.d.ts +67 -0
- package/dist/modules/events/repository.d.ts.map +1 -0
- package/dist/modules/events/repository.js +212 -0
- package/dist/modules/events/repository.js.map +1 -0
- package/dist/modules/events/routes.d.ts +136 -0
- package/dist/modules/events/routes.d.ts.map +1 -0
- package/dist/modules/events/routes.js +280 -0
- package/dist/modules/events/routes.js.map +1 -0
- package/dist/modules/events/schema.d.ts +626 -0
- package/dist/modules/events/schema.d.ts.map +1 -0
- package/dist/modules/events/schema.js +93 -0
- package/dist/modules/events/schema.js.map +1 -0
- package/dist/modules/events/service.d.ts +68 -0
- package/dist/modules/events/service.d.ts.map +1 -0
- package/dist/modules/events/service.js +262 -0
- package/dist/modules/events/service.js.map +1 -0
- package/dist/modules/events/tools.d.ts +8 -0
- package/dist/modules/events/tools.d.ts.map +1 -0
- package/dist/modules/events/tools.js +80 -0
- package/dist/modules/events/tools.js.map +1 -0
- package/dist/modules/forms/applySubmissionFields.d.ts.map +1 -1
- package/dist/modules/forms/applySubmissionFields.js +7 -5
- package/dist/modules/forms/applySubmissionFields.js.map +1 -1
- package/dist/modules/forms/fillAddresses.d.ts +4 -0
- package/dist/modules/forms/fillAddresses.d.ts.map +1 -0
- package/dist/modules/forms/fillAddresses.js +93 -0
- package/dist/modules/forms/fillAddresses.js.map +1 -0
- package/dist/modules/forms/repository.d.ts +2 -2
- package/dist/modules/forms/repository.d.ts.map +1 -1
- package/dist/modules/forms/repository.js.map +1 -1
- package/dist/modules/forms/routes.d.ts +2 -0
- package/dist/modules/forms/routes.d.ts.map +1 -1
- package/dist/modules/forms/routes.js +2 -2
- package/dist/modules/forms/routes.js.map +1 -1
- package/dist/modules/forms/schema.js +2 -2
- package/dist/modules/forms/schema.js.map +1 -1
- package/dist/modules/forms/service.js +2 -2
- package/dist/modules/forms/service.js.map +1 -1
- package/dist/modules/forms/submission.d.ts +1 -0
- package/dist/modules/forms/submission.d.ts.map +1 -1
- package/dist/modules/forms/submission.js +27 -4
- package/dist/modules/forms/submission.js.map +1 -1
- package/dist/modules/hiring/roles.d.ts.map +1 -1
- package/dist/modules/hiring/roles.js +2 -1
- package/dist/modules/hiring/roles.js.map +1 -1
- package/dist/modules/import-export/addresses.d.ts +22 -0
- package/dist/modules/import-export/addresses.d.ts.map +1 -0
- package/dist/modules/import-export/addresses.js +83 -0
- package/dist/modules/import-export/addresses.js.map +1 -0
- package/dist/modules/import-export/drafts.d.ts +3 -4
- package/dist/modules/import-export/drafts.d.ts.map +1 -1
- package/dist/modules/import-export/drafts.js +4 -5
- package/dist/modules/import-export/drafts.js.map +1 -1
- package/dist/modules/import-export/exportRows.d.ts.map +1 -1
- package/dist/modules/import-export/exportRows.js +3 -3
- package/dist/modules/import-export/exportRows.js.map +1 -1
- package/dist/modules/import-export/plan.d.ts.map +1 -1
- package/dist/modules/import-export/plan.js +17 -2
- 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 +52 -7
- package/dist/modules/import-export/presets.js.map +1 -1
- package/dist/modules/import-export/repository.d.ts +3 -4
- package/dist/modules/import-export/repository.d.ts.map +1 -1
- package/dist/modules/import-export/repository.js +2 -3
- package/dist/modules/import-export/repository.js.map +1 -1
- package/dist/modules/import-export/writes.d.ts.map +1 -1
- package/dist/modules/import-export/writes.js +8 -4
- package/dist/modules/import-export/writes.js.map +1 -1
- package/dist/modules/lists/routes.d.ts +4 -0
- package/dist/modules/lists/routes.d.ts.map +1 -1
- package/dist/modules/notes/routes.d.ts +2 -0
- package/dist/modules/notes/routes.d.ts.map +1 -1
- package/dist/modules/people/repository.d.ts.map +1 -1
- package/dist/modules/people/repository.js +1 -1
- package/dist/modules/people/repository.js.map +1 -1
- package/dist/modules/people/routes.d.ts +2 -2
- package/dist/modules/people/routes.js +6 -6
- package/dist/modules/people/routes.js.map +1 -1
- package/dist/modules/people/schema.d.ts +17 -14
- package/dist/modules/people/schema.d.ts.map +1 -1
- package/dist/modules/people/schema.js +6 -4
- package/dist/modules/people/schema.js.map +1 -1
- package/dist/modules/people/service.d.ts +2 -2
- package/dist/modules/people/service.d.ts.map +1 -1
- package/dist/modules/people/service.js +5 -4
- package/dist/modules/people/service.js.map +1 -1
- package/dist/modules/plans/repository.d.ts +2 -2
- package/dist/modules/plans/repository.d.ts.map +1 -1
- package/dist/modules/plans/routes.d.ts +1 -0
- package/dist/modules/plans/routes.d.ts.map +1 -1
- package/dist/modules/plans/routes.js +4 -4
- package/dist/modules/plans/routes.js.map +1 -1
- package/dist/modules/plans/schema.d.ts +2 -2
- package/dist/modules/plans/schema.d.ts.map +1 -1
- package/dist/modules/plans/schema.js +3 -3
- package/dist/modules/plans/schema.js.map +1 -1
- package/dist/modules/plans/service.d.ts +2 -2
- package/dist/modules/plans/service.d.ts.map +1 -1
- package/dist/modules/plans/service.js.map +1 -1
- package/dist/modules/plans/tools.js +2 -2
- package/dist/modules/plans/tools.js.map +1 -1
- package/dist/modules/recordTargets.d.ts.map +1 -1
- package/dist/modules/recordTargets.js +11 -0
- package/dist/modules/recordTargets.js.map +1 -1
- package/dist/modules/references.d.ts.map +1 -1
- package/dist/modules/references.js +2 -0
- package/dist/modules/references.js.map +1 -1
- package/dist/modules/sample-data/fixture.d.ts +26 -4
- package/dist/modules/sample-data/fixture.d.ts.map +1 -1
- package/dist/modules/sample-data/fixture.js +157 -23
- package/dist/modules/sample-data/fixture.js.map +1 -1
- package/dist/modules/sample-data/index.d.ts +5 -2
- package/dist/modules/sample-data/index.d.ts.map +1 -1
- package/dist/modules/sample-data/index.js +7 -2
- package/dist/modules/sample-data/index.js.map +1 -1
- package/dist/modules/sample-data/routes.d.ts.map +1 -1
- package/dist/modules/sample-data/routes.js +2 -0
- package/dist/modules/sample-data/routes.js.map +1 -1
- package/dist/modules/sample-data/service.d.ts +11 -3
- package/dist/modules/sample-data/service.d.ts.map +1 -1
- package/dist/modules/sample-data/service.js +452 -363
- package/dist/modules/sample-data/service.js.map +1 -1
- package/dist/modules/sample-data/tools.d.ts.map +1 -1
- package/dist/modules/sample-data/tools.js +4 -3
- package/dist/modules/sample-data/tools.js.map +1 -1
- package/dist/modules/search/index.d.ts +1 -1
- package/dist/modules/search/index.d.ts.map +1 -1
- package/dist/modules/search/index.js +2 -1
- package/dist/modules/search/index.js.map +1 -1
- package/dist/modules/search/repository.d.ts +1 -0
- package/dist/modules/search/repository.d.ts.map +1 -1
- package/dist/modules/search/repository.js +22 -0
- package/dist/modules/search/repository.js.map +1 -1
- package/dist/modules/search/service.d.ts +2 -2
- package/dist/modules/search/service.d.ts.map +1 -1
- package/dist/modules/search/service.js +4 -3
- package/dist/modules/search/service.js.map +1 -1
- package/dist/modules/smtp-email/index.d.ts +12 -4
- package/dist/modules/smtp-email/index.d.ts.map +1 -1
- package/dist/modules/smtp-email/index.js +25 -5
- package/dist/modules/smtp-email/index.js.map +1 -1
- package/dist/modules/workspace/repository.d.ts +2 -0
- package/dist/modules/workspace/repository.d.ts.map +1 -1
- package/dist/modules/workspace/repository.js +12 -0
- package/dist/modules/workspace/repository.js.map +1 -1
- package/dist/modules/workspace/routes.d.ts.map +1 -1
- package/dist/modules/workspace/routes.js +13 -0
- package/dist/modules/workspace/routes.js.map +1 -1
- package/dist/modules/workspace/service.d.ts +10 -0
- package/dist/modules/workspace/service.d.ts.map +1 -1
- package/dist/modules/workspace/service.js +34 -10
- package/dist/modules/workspace/service.js.map +1 -1
- package/dist/modules/workspace/starters.d.ts +17 -2
- package/dist/modules/workspace/starters.d.ts.map +1 -1
- package/dist/modules/workspace/starters.js +141 -14
- package/dist/modules/workspace/starters.js.map +1 -1
- package/dist/modules/workspace/workspaceAccessMiddleware.d.ts.map +1 -1
- package/dist/modules/workspace/workspaceAccessMiddleware.js +3 -4
- package/dist/modules/workspace/workspaceAccessMiddleware.js.map +1 -1
- package/dist/runtime/events.d.ts +1 -1
- package/dist/runtime/events.d.ts.map +1 -1
- package/dist/runtime/events.js +2 -0
- package/dist/runtime/events.js.map +1 -1
- package/dist/runtime/jobs.d.ts +63 -0
- package/dist/runtime/jobs.d.ts.map +1 -0
- package/dist/runtime/jobs.js +231 -0
- package/dist/runtime/jobs.js.map +1 -0
- package/dist/runtime/module.d.ts +20 -0
- package/dist/runtime/module.d.ts.map +1 -1
- package/dist/runtime/registry.d.ts +8 -0
- package/dist/runtime/registry.d.ts.map +1 -1
- package/dist/runtime/registry.js +117 -7
- package/dist/runtime/registry.js.map +1 -1
- package/dist/runtime/transaction.d.ts +19 -0
- package/dist/runtime/transaction.d.ts.map +1 -1
- package/dist/runtime/transaction.js +7 -0
- package/dist/runtime/transaction.js.map +1 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/testing/app.d.ts +15 -0
- package/dist/testing/app.d.ts.map +1 -1
- package/dist/testing/app.js +7 -1
- package/dist/testing/app.js.map +1 -1
- package/dist/testing/client.d.ts.map +1 -1
- package/dist/testing/client.js +9 -1
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/database.d.ts +7 -0
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +26 -2
- package/dist/testing/database.js.map +1 -1
- package/dist/testing/services.d.ts +7 -0
- package/dist/testing/services.d.ts.map +1 -1
- package/dist/testing/services.js +8 -1
- package/dist/testing/services.js.map +1 -1
- package/dist/worker.d.ts +15 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +19 -0
- package/dist/worker.js.map +1 -0
- package/migrations/0044_events.sql +79 -0
- package/migrations/0045_addresses.sql +66 -0
- package/migrations/0046_drop_company_website.sql +66 -0
- package/migrations/meta/_journal.json +21 -0
- package/package.json +4 -3
- package/src/app.ts +25 -1
- package/src/boot.ts +21 -2
- package/src/index.ts +17 -0
- package/src/lib/apiKeyScopes.ts +6 -0
- package/src/lib/columns.ts +12 -3
- package/src/lib/config.ts +21 -0
- package/src/lib/egress.ts +8 -4
- package/src/lib/errors.ts +16 -9
- package/src/lib/ids.ts +11 -0
- package/src/lib/jobs.ts +126 -0
- package/src/lib/kelpieConfigFile.ts +69 -1
- package/src/modules/agent-tasks/catalog.ts +33 -1
- package/src/modules/agent-tasks/index.ts +1 -0
- package/src/modules/agent-tasks/resolve.ts +11 -0
- package/src/modules/attachedRecords.ts +85 -13
- package/src/modules/auth/index.ts +1 -0
- package/src/modules/auth/repository.ts +34 -3
- package/src/modules/auth/routes.ts +44 -1
- package/src/modules/auth/service.ts +118 -4
- package/src/modules/companies/repository.ts +1 -0
- package/src/modules/companies/routes.ts +6 -11
- package/src/modules/companies/schema.ts +3 -3
- package/src/modules/companies/service.ts +6 -10
- package/src/modules/core.ts +2 -0
- package/src/modules/custom-fields/repository.ts +2 -0
- package/src/modules/dashboard/index.ts +1 -0
- package/src/modules/dashboard/repository.ts +64 -0
- package/src/modules/dashboard/routes.ts +15 -0
- package/src/modules/dashboard/service.ts +48 -0
- package/src/modules/decisions/repository.ts +2 -0
- package/src/modules/events/attendances.ts +245 -0
- package/src/modules/events/catalog.ts +36 -0
- package/src/modules/events/index.ts +54 -0
- package/src/modules/events/repository.ts +396 -0
- package/src/modules/events/routes.ts +405 -0
- package/src/modules/events/schema.ts +148 -0
- package/src/modules/events/service.ts +443 -0
- package/src/modules/events/tools.ts +118 -0
- package/src/modules/forms/applySubmissionFields.ts +7 -5
- package/src/modules/forms/fillAddresses.ts +185 -0
- package/src/modules/forms/repository.ts +3 -3
- package/src/modules/forms/routes.ts +2 -2
- package/src/modules/forms/schema.ts +2 -2
- package/src/modules/forms/service.ts +2 -2
- package/src/modules/forms/submission.ts +34 -4
- package/src/modules/hiring/roles.ts +2 -1
- package/src/modules/import-export/addresses.ts +150 -0
- package/src/modules/import-export/drafts.ts +7 -9
- package/src/modules/import-export/exportRows.ts +3 -3
- package/src/modules/import-export/plan.ts +30 -2
- package/src/modules/import-export/presets.ts +55 -7
- package/src/modules/import-export/repository.ts +5 -7
- package/src/modules/import-export/writes.ts +8 -4
- package/src/modules/people/repository.ts +1 -0
- package/src/modules/people/routes.ts +6 -6
- package/src/modules/people/schema.ts +7 -5
- package/src/modules/people/service.ts +7 -5
- package/src/modules/plans/repository.ts +2 -2
- package/src/modules/plans/routes.ts +6 -6
- package/src/modules/plans/schema.ts +4 -4
- package/src/modules/plans/service.ts +3 -3
- package/src/modules/plans/tools.ts +2 -2
- package/src/modules/recordTargets.ts +14 -0
- package/src/modules/references.ts +2 -0
- package/src/modules/sample-data/fixture.ts +208 -27
- package/src/modules/sample-data/index.ts +7 -2
- package/src/modules/sample-data/routes.ts +2 -0
- package/src/modules/sample-data/service.ts +166 -49
- package/src/modules/sample-data/tools.ts +4 -3
- package/src/modules/search/index.ts +2 -1
- package/src/modules/search/repository.ts +32 -1
- package/src/modules/search/service.ts +6 -4
- package/src/modules/smtp-email/index.ts +32 -10
- package/src/modules/workspace/repository.ts +18 -0
- package/src/modules/workspace/routes.ts +16 -0
- package/src/modules/workspace/service.ts +59 -14
- package/src/modules/workspace/starters.ts +275 -14
- package/src/modules/workspace/workspaceAccessMiddleware.ts +3 -4
- package/src/runtime/events.ts +2 -0
- package/src/runtime/jobs.ts +358 -0
- package/src/runtime/module.ts +20 -0
- package/src/runtime/registry.ts +146 -8
- package/src/runtime/transaction.ts +43 -0
- package/src/schema/index.ts +1 -0
- package/src/testing/app.ts +22 -1
- package/src/testing/client.ts +11 -1
- package/src/testing/database.ts +35 -2
- package/src/testing/services.ts +15 -1
- package/src/worker.ts +20 -0
package/src/lib/jobs.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { ZodType } from 'zod'
|
|
2
|
+
|
|
3
|
+
import type { Logger } from './logger.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The port core runs background work through.
|
|
7
|
+
*
|
|
8
|
+
* A module declares a handle at register time with `context.jobs.define(...)`
|
|
9
|
+
* and enqueues against it from a service. The insert lives inside the same
|
|
10
|
+
* Drizzle transaction as the write it belongs to, so a rolled-back write
|
|
11
|
+
* never leaves an orphan job behind. Handlers run out of process — on the
|
|
12
|
+
* worker entry point, or inline when the API runs its own worker — and see
|
|
13
|
+
* the same payload the caller passed, validated against the module's Zod
|
|
14
|
+
* schema before it reaches user code.
|
|
15
|
+
*
|
|
16
|
+
* The port itself knows nothing about pg-boss. `runtime/jobs.ts` binds it to
|
|
17
|
+
* a real provider at boot; tests bind it to a stub. Nothing outside the
|
|
18
|
+
* runtime imports the provider directly.
|
|
19
|
+
*
|
|
20
|
+
* Why a handle rather than a global name map: consumers of `@kelpie/server`
|
|
21
|
+
* installed from npm lost the `KelpieEventMap` augmentation until every
|
|
22
|
+
* catalog was pulled in from the entry point (see
|
|
23
|
+
* `modules/eventCatalogs.ts`). A handle keeps the payload type on the value
|
|
24
|
+
* the module exports, so `enqueue(handle, data)` typechecks without
|
|
25
|
+
* declaration merging across the package boundary.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Marker property carrying the handle's payload type. Never assigned at
|
|
30
|
+
* runtime; the `?` on the property makes an untagged plain object satisfy the
|
|
31
|
+
* shape at read time, so a stub handle in a test can be built without
|
|
32
|
+
* `unsafeCast`.
|
|
33
|
+
*/
|
|
34
|
+
declare const JobHandleDataMarker: unique symbol
|
|
35
|
+
|
|
36
|
+
/** A typed reference a module exports after `define()`. */
|
|
37
|
+
export interface JobHandle<Data> {
|
|
38
|
+
readonly name: string
|
|
39
|
+
readonly [JobHandleDataMarker]?: Data
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Defaults every enqueue on this queue inherits, unless the caller overrides
|
|
44
|
+
* on a per-`enqueue` basis. `localConcurrency` sizes the work loop on the
|
|
45
|
+
* worker; the rest match pg-boss's `QueueOptions` names.
|
|
46
|
+
*
|
|
47
|
+
* `retryDelay` is in seconds. pg-boss's default is 0 (immediate retry).
|
|
48
|
+
* `retryLimit` counts the retries pg-boss does after the first run; a job
|
|
49
|
+
* with `retryLimit: 2` runs at most three times.
|
|
50
|
+
*/
|
|
51
|
+
export interface JobDefaults {
|
|
52
|
+
readonly retryLimit?: number
|
|
53
|
+
readonly retryDelay?: number
|
|
54
|
+
readonly retryBackoff?: boolean
|
|
55
|
+
readonly retryDelayMax?: number
|
|
56
|
+
readonly expireInSeconds?: number
|
|
57
|
+
readonly localConcurrency?: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Passed to a job handler when a worker picks the job up. */
|
|
61
|
+
export interface JobContext<Data> {
|
|
62
|
+
readonly id: string
|
|
63
|
+
readonly data: Data
|
|
64
|
+
readonly log: Logger
|
|
65
|
+
/** Fires when pg-boss decides the job has expired. Long-running handlers should honour it. */
|
|
66
|
+
readonly signal: AbortSignal
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type JobHandler<Data> = (context: JobContext<Data>) => Promise<void>
|
|
70
|
+
|
|
71
|
+
export interface JobDefinition<Data> {
|
|
72
|
+
/**
|
|
73
|
+
* The queue name pg-boss stores against. Must be unique across the
|
|
74
|
+
* assembly; a second `define()` with the same name fails boot.
|
|
75
|
+
*
|
|
76
|
+
* Prefix with the module id — `webhooks.deliver`, `agent-tasks.dispatch` —
|
|
77
|
+
* so operators reading `pgboss.job` see who owns it.
|
|
78
|
+
*/
|
|
79
|
+
readonly name: string
|
|
80
|
+
/**
|
|
81
|
+
* Parses the payload at handle time before it reaches user code. A job
|
|
82
|
+
* whose payload no longer matches fails, retries, and eventually
|
|
83
|
+
* dead-letters like any other failure.
|
|
84
|
+
*/
|
|
85
|
+
readonly schema: ZodType<Data>
|
|
86
|
+
readonly handler: JobHandler<Data>
|
|
87
|
+
readonly defaults?: JobDefaults
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The registration surface exposed to a module through `context.jobs`. Modules
|
|
92
|
+
* hand a definition in and receive a typed handle to enqueue against.
|
|
93
|
+
*/
|
|
94
|
+
export interface JobRegistry {
|
|
95
|
+
define<Data>(definition: JobDefinition<Data>): JobHandle<Data>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Options a caller may override per `enqueue`. Deliberately narrow: features
|
|
100
|
+
* beyond retries, a singleton key, and a deferred start are out of scope until
|
|
101
|
+
* a consumer needs them (`crm-brief.md` cost/maintainability trade-offs).
|
|
102
|
+
*/
|
|
103
|
+
export interface EnqueueOptions {
|
|
104
|
+
readonly retryLimit?: number
|
|
105
|
+
readonly retryBackoff?: boolean
|
|
106
|
+
readonly singletonKey?: string
|
|
107
|
+
readonly startAfter?: number | string | Date
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Exposed on the transaction context next to `events`. `enqueue` runs its
|
|
112
|
+
* insert inside the caller's transaction via pg-boss's `fromDrizzle` adapter,
|
|
113
|
+
* so a rollback discards the job with the write.
|
|
114
|
+
*/
|
|
115
|
+
export interface TransactionJobs {
|
|
116
|
+
enqueue<Data>(
|
|
117
|
+
handle: JobHandle<Data>,
|
|
118
|
+
data: Data,
|
|
119
|
+
options?: EnqueueOptions,
|
|
120
|
+
): Promise<string | null>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The name pg-boss stores the dead-letter queue under, given a handle name. */
|
|
124
|
+
export function deadLetterQueueName(jobName: string): string {
|
|
125
|
+
return `${jobName}.dead`
|
|
126
|
+
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
import type { PublicRegion } from '@kelpie/schemas'
|
|
2
|
+
import { publicRegionSchema } from '@kelpie/schemas'
|
|
3
|
+
|
|
1
4
|
import type { KelpieModule } from '../runtime/module.ts'
|
|
2
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ConfigurationError,
|
|
7
|
+
type Environment,
|
|
8
|
+
type KelpieConfig,
|
|
9
|
+
type LogLevel,
|
|
10
|
+
type RuntimeMode,
|
|
11
|
+
type SignupsMode,
|
|
12
|
+
} from './config.ts'
|
|
3
13
|
import type { EmailConfig } from './email.ts'
|
|
14
|
+
import { describeValidationIssue } from './errors.ts'
|
|
4
15
|
import { type ConfigValue, resolveMarkers } from './fromEnv.ts'
|
|
5
16
|
import type { LoggingDestination } from './logger.ts'
|
|
6
17
|
import type { RateLimitConfig } from './rateLimit.ts'
|
|
@@ -35,6 +46,19 @@ export interface KelpieConfigInput {
|
|
|
35
46
|
readonly siteName?: ConfigValue<string | undefined>
|
|
36
47
|
/** Optional; defaults to 0 (no proxy in front). */
|
|
37
48
|
readonly trustedProxyHopCount?: ConfigValue<number>
|
|
49
|
+
/**
|
|
50
|
+
* Whether the instance accepts a brand-new account. Optional; defaults to
|
|
51
|
+
* `'open'`, matching `loadConfig`'s default, so an assembly that never sets
|
|
52
|
+
* this sees no change in behaviour.
|
|
53
|
+
*/
|
|
54
|
+
readonly signups?: ConfigValue<SignupsMode>
|
|
55
|
+
/**
|
|
56
|
+
* Origins a multi-region assembly advertises on signed-out pages. Optional;
|
|
57
|
+
* omitted or empty hides the switcher, matching a self-hosted install.
|
|
58
|
+
* Two or more draws it. Each `id` and `origin` must be unique, and each
|
|
59
|
+
* `origin` must be an absolute `http:` or `https:` origin.
|
|
60
|
+
*/
|
|
61
|
+
readonly regions?: ConfigValue<readonly PublicRegion[]>
|
|
38
62
|
/**
|
|
39
63
|
* The deployment's base URL. Every emailed link (password reset, email
|
|
40
64
|
* verification, invite) is built from it. Required by the workspace and auth
|
|
@@ -164,6 +188,9 @@ export function resolveKelpieConfig(input: KelpieConfigInput, environment: Envir
|
|
|
164
188
|
const rateLimit = buildRateLimitConfig(resolved.rateLimit)
|
|
165
189
|
const env = mergeEnv(environment, resolved.env)
|
|
166
190
|
const secretEncryption = buildSecretEncryptionConfig(resolved.secretEncryption)
|
|
191
|
+
const regions = resolved.regions ?? []
|
|
192
|
+
|
|
193
|
+
problems.push(...describeRegionProblems(regions))
|
|
167
194
|
|
|
168
195
|
if (problems.length > 0) {
|
|
169
196
|
throw new ConfigurationError(problems)
|
|
@@ -186,6 +213,8 @@ export function resolveKelpieConfig(input: KelpieConfigInput, environment: Envir
|
|
|
186
213
|
env,
|
|
187
214
|
appBaseUrl: resolved.appBaseUrl,
|
|
188
215
|
secretEncryption,
|
|
216
|
+
signups: resolved.signups ?? 'open',
|
|
217
|
+
regions,
|
|
189
218
|
}
|
|
190
219
|
}
|
|
191
220
|
|
|
@@ -217,6 +246,8 @@ interface ResolvedInput {
|
|
|
217
246
|
readonly moduleConfigPath?: string | undefined
|
|
218
247
|
readonly siteName?: string | undefined
|
|
219
248
|
readonly trustedProxyHopCount?: number
|
|
249
|
+
readonly signups?: SignupsMode
|
|
250
|
+
readonly regions?: readonly PublicRegion[]
|
|
220
251
|
readonly appBaseUrl?: string
|
|
221
252
|
readonly secretEncryption?: ResolvedSecretEncryption
|
|
222
253
|
readonly email: ResolvedEmail
|
|
@@ -242,6 +273,43 @@ interface ResolvedRateLimit {
|
|
|
242
273
|
readonly api?: { readonly limit?: number; readonly windowSeconds?: number }
|
|
243
274
|
}
|
|
244
275
|
|
|
276
|
+
function describeRegionProblems(regions: readonly unknown[]): string[] {
|
|
277
|
+
const problems: string[] = []
|
|
278
|
+
const ids = new Set<string>()
|
|
279
|
+
const origins = new Set<string>()
|
|
280
|
+
|
|
281
|
+
for (const [index, region] of regions.entries()) {
|
|
282
|
+
const parsed = publicRegionSchema.safeParse(region)
|
|
283
|
+
|
|
284
|
+
if (!parsed.success) {
|
|
285
|
+
for (const issue of parsed.error.issues) {
|
|
286
|
+
problems.push(
|
|
287
|
+
describeValidationIssue({
|
|
288
|
+
path: ['regions', index, ...issue.path],
|
|
289
|
+
message: issue.message,
|
|
290
|
+
}),
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
continue
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (ids.has(parsed.data.id)) {
|
|
298
|
+
problems.push(`regions.${String(index)}.id: duplicate id '${parsed.data.id}'`)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
ids.add(parsed.data.id)
|
|
302
|
+
|
|
303
|
+
if (origins.has(parsed.data.origin)) {
|
|
304
|
+
problems.push(`regions.${String(index)}.origin: duplicate origin '${parsed.data.origin}'`)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
origins.add(parsed.data.origin)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return problems
|
|
311
|
+
}
|
|
312
|
+
|
|
245
313
|
function buildEmailConfig(resolved: ResolvedEmail | undefined): EmailConfig {
|
|
246
314
|
// Missing `email.provider` or `email.from` is already recorded by the
|
|
247
315
|
// walker; return a placeholder so `resolveKelpieConfig` reaches its throw
|
|
@@ -128,7 +128,7 @@ Append a Note, update summary only when clearly supported, propose Plan items on
|
|
|
128
128
|
targetTypes: ['company'],
|
|
129
129
|
placement: 'primary',
|
|
130
130
|
handbookSlugs: ['ideal-customer-profile', 'agent-faq'],
|
|
131
|
-
instructions: `Research this Company. Update description, stage, sizeBand, techStack,
|
|
131
|
+
instructions: `Research this Company. Update description, stage, sizeBand, techStack, domain when evidenced, tags, and summary.
|
|
132
132
|
Append a Note listing sources. Prefer notes over guesses for uncertain facts.`,
|
|
133
133
|
}),
|
|
134
134
|
task({
|
|
@@ -623,6 +623,28 @@ Ask before setting stage to passed.`,
|
|
|
623
623
|
instructions: `The human will paste notes after this prompt. Append a Note; propose Plan items; extract Decisions.`,
|
|
624
624
|
}),
|
|
625
625
|
|
|
626
|
+
// Event
|
|
627
|
+
task({
|
|
628
|
+
id: 'event.brief',
|
|
629
|
+
label: 'Event brief',
|
|
630
|
+
description: 'Agenda, who is registered, linked records, open Plans.',
|
|
631
|
+
targetTypes: ['event'],
|
|
632
|
+
placement: 'primary',
|
|
633
|
+
handbookSlugs: ['agent-faq'],
|
|
634
|
+
instructions: `Prepare a brief for this Event: agenda from details, who is registered, linked records, and open Plan items.
|
|
635
|
+
Do not invent attendees. Prefer pinned notes and Decisions.`,
|
|
636
|
+
}),
|
|
637
|
+
task({
|
|
638
|
+
id: 'event.follow_up',
|
|
639
|
+
label: 'Follow up',
|
|
640
|
+
description: 'Draft follow-up from attended vs no-show Attendances.',
|
|
641
|
+
targetTypes: ['event'],
|
|
642
|
+
placement: 'primary',
|
|
643
|
+
handbookSlugs: ['voice-and-tone', 'agent-faq'],
|
|
644
|
+
instructions: `Draft follow-up from this Event's Attendances. Separate attended from no-show.
|
|
645
|
+
Save drafts as Notes. Do not send email.`,
|
|
646
|
+
}),
|
|
647
|
+
|
|
626
648
|
// Candidate
|
|
627
649
|
task({
|
|
628
650
|
id: 'candidate.score',
|
|
@@ -753,6 +775,16 @@ Suggest Draft outreach or Plan items. Do not spam everyone.`,
|
|
|
753
775
|
handbookSlugs: ['how-we-sell', 'agent-faq'],
|
|
754
776
|
instructions: `Propose a week of Plan items across open Deals, Opportunities, Raises, and Partnerships.
|
|
755
777
|
Create Plan items only when clearly useful; otherwise list proposals for confirmation.`,
|
|
778
|
+
}),
|
|
779
|
+
task({
|
|
780
|
+
id: 'workspace.upcoming_events',
|
|
781
|
+
label: 'Upcoming events',
|
|
782
|
+
description: 'Scheduled Events in the next week and their attendee counts.',
|
|
783
|
+
targetTypes: ['workspace'],
|
|
784
|
+
placement: 'primary',
|
|
785
|
+
handbookSlugs: ['agent-faq'],
|
|
786
|
+
instructions: `List scheduled Events in the next week with attendee counts.
|
|
787
|
+
Use GET /v1/dashboard upcoming_events. Suggest follow-up or brief tasks where useful.`,
|
|
756
788
|
}),
|
|
757
789
|
task({
|
|
758
790
|
id: 'workspace.empty_field_sweep',
|
|
@@ -9,6 +9,7 @@ import { companies } from '../companies/schema.ts'
|
|
|
9
9
|
import { deals } from '../deals/schema.ts'
|
|
10
10
|
import { decisions } from '../decisions/schema.ts'
|
|
11
11
|
import { enquiries } from '../enquiries/schema.ts'
|
|
12
|
+
import { attendances } from '../events/schema.ts'
|
|
12
13
|
import { handbookPages } from '../handbook/schema.ts'
|
|
13
14
|
import { candidates, roles } from '../hiring/schema.ts'
|
|
14
15
|
import { notes } from '../notes/schema.ts'
|
|
@@ -443,6 +444,16 @@ async function collectRelated(
|
|
|
443
444
|
),
|
|
444
445
|
}
|
|
445
446
|
|
|
447
|
+
case 'event':
|
|
448
|
+
return {
|
|
449
|
+
person_ids: await relatedFrom(
|
|
450
|
+
db,
|
|
451
|
+
attendances,
|
|
452
|
+
attendances.personId,
|
|
453
|
+
and(eq(attendances.workspaceId, workspaceId), eq(attendances.eventId, targetId)) as SQL,
|
|
454
|
+
),
|
|
455
|
+
}
|
|
456
|
+
|
|
446
457
|
case 'handbook':
|
|
447
458
|
case 'workspace':
|
|
448
459
|
return {}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
EVENT_ASSOCIATION_TARGET_TYPES,
|
|
3
|
+
FORM_ATTACH_TARGET_TYPES,
|
|
4
|
+
PIPELINE_KINDS,
|
|
5
|
+
PLAN_ITEM_TARGET_TYPES,
|
|
6
|
+
} from '@kelpie/schemas'
|
|
2
7
|
import type { PipelineKind } from '@kelpie/schemas'
|
|
3
8
|
import { and, eq } from 'drizzle-orm'
|
|
4
9
|
|
|
5
10
|
import type { Queryable } from '../runtime/transaction.ts'
|
|
6
11
|
import * as activityRepository from './activities/repository.ts'
|
|
7
12
|
import * as decisionRepository from './decisions/repository.ts'
|
|
13
|
+
import { attendances, eventAssociations } from './events/schema.ts'
|
|
8
14
|
import { formAttachTargets } from './forms/schema.ts'
|
|
9
15
|
import { candidates } from './hiring/schema.ts'
|
|
10
16
|
import * as listRepository from './lists/repository.ts'
|
|
@@ -20,19 +26,26 @@ import * as planRepository from './plans/repository.ts'
|
|
|
20
26
|
* the target deletes these, in the same transaction, or the rows outlive the
|
|
21
27
|
* record they described and reappear the day an id is reused.
|
|
22
28
|
*
|
|
23
|
-
* `plan_items` attach the same way but their check constraint allows
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* is the same shape: forms pinning submitters into a pre-existing pipeline
|
|
29
|
-
* record shed the mapping when the target goes.
|
|
29
|
+
* `plan_items` attach the same way but their check constraint allows the
|
|
30
|
+
* pipeline types plus Event, so they are removed only for those targets.
|
|
31
|
+
* `person_links` stays pipeline-only. `form_attach_targets` follows the plan
|
|
32
|
+
* set (pipelines plus Event). `event_associations` is Event-only: deleting the
|
|
33
|
+
* other record strips rows that pointed at it.
|
|
30
34
|
*/
|
|
31
35
|
|
|
32
36
|
/** Target types that can own attached records here. */
|
|
33
|
-
export type AttachableTargetType =
|
|
37
|
+
export type AttachableTargetType =
|
|
38
|
+
| 'person'
|
|
39
|
+
| 'company'
|
|
40
|
+
| 'candidate'
|
|
41
|
+
| 'event'
|
|
42
|
+
| 'attendance'
|
|
43
|
+
| PipelineKind
|
|
34
44
|
|
|
35
|
-
const PLAN_TARGET_TYPES: ReadonlySet<string> = new Set(
|
|
45
|
+
const PLAN_TARGET_TYPES: ReadonlySet<string> = new Set(PLAN_ITEM_TARGET_TYPES)
|
|
46
|
+
const LINK_TARGET_TYPES: ReadonlySet<string> = new Set(PIPELINE_KINDS)
|
|
47
|
+
const FORM_ATTACH_TYPES: ReadonlySet<string> = new Set(FORM_ATTACH_TARGET_TYPES)
|
|
48
|
+
const ASSOCIATION_TARGET_TYPES: ReadonlySet<string> = new Set(EVENT_ASSOCIATION_TARGET_TYPES)
|
|
36
49
|
|
|
37
50
|
/**
|
|
38
51
|
* @param db Must be the caller's transaction. Called on its own, this deletes the
|
|
@@ -53,13 +66,13 @@ export async function deleteRecordsAttachedTo(
|
|
|
53
66
|
const plans = PLAN_TARGET_TYPES.has(targetType)
|
|
54
67
|
? await planRepository.deleteForTarget(db, workspaceId, targetType, targetId)
|
|
55
68
|
: 0
|
|
56
|
-
const links =
|
|
69
|
+
const links = LINK_TARGET_TYPES.has(targetType)
|
|
57
70
|
? await personLinks.deleteLinksForTarget(db, workspaceId, {
|
|
58
71
|
targetType: targetType as PipelineKind,
|
|
59
72
|
targetId,
|
|
60
73
|
})
|
|
61
74
|
: 0
|
|
62
|
-
const attachTargets =
|
|
75
|
+
const attachTargets = FORM_ATTACH_TYPES.has(targetType)
|
|
63
76
|
? (
|
|
64
77
|
await db
|
|
65
78
|
.delete(formAttachTargets)
|
|
@@ -79,8 +92,36 @@ export async function deleteRecordsAttachedTo(
|
|
|
79
92
|
targetType,
|
|
80
93
|
targetId,
|
|
81
94
|
)
|
|
95
|
+
const associations = ASSOCIATION_TARGET_TYPES.has(targetType)
|
|
96
|
+
? await deleteEventAssociationsForTarget(db, workspaceId, targetType, targetId)
|
|
97
|
+
: 0
|
|
98
|
+
|
|
99
|
+
return notes + activities + decisions + plans + links + attachTargets + memberships + associations
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Event↔record rows have no FK to the target, so the target's delete must strip
|
|
104
|
+
* them. Event delete itself cascades `event_id`. Role is not an attachable
|
|
105
|
+
* notes target, so its service calls this helper directly.
|
|
106
|
+
*/
|
|
107
|
+
export async function deleteEventAssociationsForTarget(
|
|
108
|
+
db: Queryable,
|
|
109
|
+
workspaceId: string,
|
|
110
|
+
targetType: string,
|
|
111
|
+
targetId: string,
|
|
112
|
+
): Promise<number> {
|
|
113
|
+
const deleted = await db
|
|
114
|
+
.delete(eventAssociations)
|
|
115
|
+
.where(
|
|
116
|
+
and(
|
|
117
|
+
eq(eventAssociations.workspaceId, workspaceId),
|
|
118
|
+
eq(eventAssociations.targetType, targetType),
|
|
119
|
+
eq(eventAssociations.targetId, targetId),
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
.returning({ id: eventAssociations.id })
|
|
82
123
|
|
|
83
|
-
return
|
|
124
|
+
return deleted.length
|
|
84
125
|
}
|
|
85
126
|
|
|
86
127
|
/**
|
|
@@ -117,3 +158,34 @@ export async function deleteRecordsAttachedToCandidaciesOf(
|
|
|
117
158
|
|
|
118
159
|
return removed
|
|
119
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The same cleanup for Attendances a person's (or Event's) delete takes with
|
|
164
|
+
* them. `attendances.person_id` / `event_id` cascade, so notes on those rows
|
|
165
|
+
* would otherwise outlive the Attendance.
|
|
166
|
+
*/
|
|
167
|
+
export async function deleteRecordsAttachedToAttendancesOf(
|
|
168
|
+
db: Queryable,
|
|
169
|
+
workspaceId: string,
|
|
170
|
+
filter: { readonly personId: string } | { readonly eventId: string },
|
|
171
|
+
): Promise<number> {
|
|
172
|
+
const held = await db
|
|
173
|
+
.select({ id: attendances.id })
|
|
174
|
+
.from(attendances)
|
|
175
|
+
.where(
|
|
176
|
+
and(
|
|
177
|
+
eq(attendances.workspaceId, workspaceId),
|
|
178
|
+
'personId' in filter
|
|
179
|
+
? eq(attendances.personId, filter.personId)
|
|
180
|
+
: eq(attendances.eventId, filter.eventId),
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
let removed = 0
|
|
185
|
+
|
|
186
|
+
for (const attendance of held) {
|
|
187
|
+
removed += await deleteRecordsAttachedTo(db, workspaceId, 'attendance', attendance.id)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return removed
|
|
191
|
+
}
|
|
@@ -37,6 +37,7 @@ export function createAuthModule(migrationsDirectory: string): KelpieModule {
|
|
|
37
37
|
createId: context.createId,
|
|
38
38
|
now: context.now,
|
|
39
39
|
appBaseUrl,
|
|
40
|
+
signupsEnabled: context.signupsEnabled ?? true,
|
|
40
41
|
})
|
|
41
42
|
|
|
42
43
|
// `Secure` everywhere except development. A test host reaches the API
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { and, desc, eq, gt, isNull, ne } from 'drizzle-orm'
|
|
1
|
+
import { and, asc, desc, eq, gt, isNull, ne } from 'drizzle-orm'
|
|
2
2
|
|
|
3
3
|
import type { Database } from '../../lib/database.ts'
|
|
4
4
|
import type { Transaction } from '../../runtime/transaction.ts'
|
|
5
|
-
import { workspaceMembers } from '../workspace/schema.ts'
|
|
5
|
+
import { workspaceMembers, workspaces } from '../workspace/schema.ts'
|
|
6
6
|
import {
|
|
7
7
|
emailVerificationTokens,
|
|
8
8
|
passwordResetTokens,
|
|
@@ -64,11 +64,15 @@ export async function updateUserPassword(
|
|
|
64
64
|
/**
|
|
65
65
|
* Writes name and email. Not the password: that one has its own function because
|
|
66
66
|
* it also ends sessions, and the two must not be reachable through one call.
|
|
67
|
+
*
|
|
68
|
+
* `emailVerifiedAt` rides along with an email change: the service resets it to
|
|
69
|
+
* null in the same call rather than a second write, so there is no window where
|
|
70
|
+
* the row holds a new address and the old address's verification.
|
|
67
71
|
*/
|
|
68
72
|
export async function updateUserProfile(
|
|
69
73
|
db: Queryable,
|
|
70
74
|
userId: string,
|
|
71
|
-
changes: { readonly name?: string; readonly email?: string },
|
|
75
|
+
changes: { readonly name?: string; readonly email?: string; readonly emailVerifiedAt?: Date | null },
|
|
72
76
|
now: Date,
|
|
73
77
|
): Promise<UserRecord | undefined> {
|
|
74
78
|
const [updated] = await db
|
|
@@ -294,3 +298,30 @@ export async function findFirstMembership(
|
|
|
294
298
|
|
|
295
299
|
return found
|
|
296
300
|
}
|
|
301
|
+
|
|
302
|
+
/** Every workspace this account belongs to, oldest membership first. */
|
|
303
|
+
export interface AccountWorkspaceRecord {
|
|
304
|
+
readonly id: string
|
|
305
|
+
readonly name: string
|
|
306
|
+
readonly slug: string
|
|
307
|
+
readonly timezone: string
|
|
308
|
+
readonly role: string
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export async function listWorkspacesForUser(
|
|
312
|
+
db: Queryable,
|
|
313
|
+
userId: string,
|
|
314
|
+
): Promise<AccountWorkspaceRecord[]> {
|
|
315
|
+
return db
|
|
316
|
+
.select({
|
|
317
|
+
id: workspaces.id,
|
|
318
|
+
name: workspaces.name,
|
|
319
|
+
slug: workspaces.slug,
|
|
320
|
+
timezone: workspaces.timezone,
|
|
321
|
+
role: workspaceMembers.role,
|
|
322
|
+
})
|
|
323
|
+
.from(workspaceMembers)
|
|
324
|
+
.innerJoin(workspaces, eq(workspaces.id, workspaceMembers.workspaceId))
|
|
325
|
+
.where(eq(workspaceMembers.userId, userId))
|
|
326
|
+
.orderBy(asc(workspaceMembers.joinedAt))
|
|
327
|
+
}
|
|
@@ -10,7 +10,7 @@ import type { SessionActor } from './actor.ts'
|
|
|
10
10
|
import { resolveActorFrom } from './credentials.ts'
|
|
11
11
|
import type { CredentialDependencies } from './credentials.ts'
|
|
12
12
|
import type { PreferenceValues } from './preferences.ts'
|
|
13
|
-
import type { AccountView, AuthService, IssuedSession, SessionView } from './service.ts'
|
|
13
|
+
import type { AccountView, AuthService, IssuedSession, MeView, SessionView } from './service.ts'
|
|
14
14
|
import { clearSessionCookie, describeClient, writeSessionCookie } from './session.ts'
|
|
15
15
|
import type { SessionCookieOptions } from './session.ts'
|
|
16
16
|
|
|
@@ -44,6 +44,10 @@ const changePasswordBody = z.object({
|
|
|
44
44
|
new_password: z.string().min(1),
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
+
const switchWorkspaceBodySchema = z.object({
|
|
48
|
+
workspace_id: z.string().min(1),
|
|
49
|
+
})
|
|
50
|
+
|
|
47
51
|
const verifyEmailConfirmBody = z.object({
|
|
48
52
|
token: z.string().min(1),
|
|
49
53
|
})
|
|
@@ -135,6 +139,32 @@ function preferencesResponse(preferences: PreferenceValues): Record<string, unkn
|
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
|
|
142
|
+
function sessionMeResponse(session: MeView): Record<string, unknown> {
|
|
143
|
+
return {
|
|
144
|
+
user_id: session.userId,
|
|
145
|
+
session_id: session.sessionId,
|
|
146
|
+
workspace_id: session.workspaceId,
|
|
147
|
+
role: session.role,
|
|
148
|
+
email_verified: session.emailVerified,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function accountWorkspaceResponse(workspace: {
|
|
153
|
+
readonly id: string
|
|
154
|
+
readonly name: string
|
|
155
|
+
readonly slug: string
|
|
156
|
+
readonly timezone: string
|
|
157
|
+
readonly role: string
|
|
158
|
+
}): Record<string, unknown> {
|
|
159
|
+
return {
|
|
160
|
+
id: workspace.id,
|
|
161
|
+
name: workspace.name,
|
|
162
|
+
slug: workspace.slug,
|
|
163
|
+
timezone: workspace.timezone,
|
|
164
|
+
role: workspace.role,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
138
168
|
function sessionResponse(session: SessionView): Record<string, unknown> {
|
|
139
169
|
return {
|
|
140
170
|
id: session.id,
|
|
@@ -194,6 +224,19 @@ export function mountAuthRoutes(router: Hono, dependencies: AuthRoutesDependenci
|
|
|
194
224
|
})
|
|
195
225
|
})
|
|
196
226
|
|
|
227
|
+
router.get('/auth/workspaces', async (context) => {
|
|
228
|
+
const workspaces = await dependencies.service.listWorkspaces(await requireActor(context))
|
|
229
|
+
|
|
230
|
+
return context.json({ data: workspaces.map(accountWorkspaceResponse), next_cursor: null })
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
router.post('/auth/workspace', async (context) => {
|
|
234
|
+
const body = await readBody(context, switchWorkspaceBodySchema)
|
|
235
|
+
const session = await dependencies.service.switchWorkspace(await requireActor(context), body.workspace_id)
|
|
236
|
+
|
|
237
|
+
return context.json(sessionMeResponse(session))
|
|
238
|
+
})
|
|
239
|
+
|
|
197
240
|
/**
|
|
198
241
|
* `/account` rather than `/auth/account`: `/auth/*` is how a browser gets a
|
|
199
242
|
* session, and this is the person that session belongs to. The auth module
|