@kelpie/server 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/index.d.ts +17 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -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 +44 -6
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +9 -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 +123 -0
- package/dist/lib/kelpieConfigFile.d.ts.map +1 -0
- package/dist/lib/kelpieConfigFile.js +111 -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/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/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/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/service.d.ts +5 -0
- package/dist/modules/import-export/service.d.ts.map +1 -1
- package/dist/modules/import-export/service.js +53 -14
- package/dist/modules/import-export/service.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.map +1 -1
- package/dist/testing/app.js +11 -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/package.json +5 -2
- package/src/index.ts +44 -9
- package/src/lib/actor.ts +15 -1
- package/src/lib/config.ts +56 -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 +283 -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/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/events.ts +25 -0
- package/src/modules/import-export/index.ts +2 -0
- package/src/modules/import-export/service.ts +83 -15
- 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 +11 -3
- package/src/testing/database.ts +6 -3
- package/src/testing/services.ts +34 -5
|
@@ -9,7 +9,9 @@ import type { EntitlementRegistry } from '../../runtime/entitlements.ts'
|
|
|
9
9
|
import { limitFor } from '../../runtime/entitlements.ts'
|
|
10
10
|
import { moduleCapabilityName } from '../../runtime/moduleConfig.ts'
|
|
11
11
|
import type { TransactionScope } from '../../runtime/transaction.ts'
|
|
12
|
+
import { toEventActor } from '../../lib/actor.ts'
|
|
12
13
|
import type { Actor, SessionActor } from '../auth/actor.ts'
|
|
14
|
+
import './events.ts'
|
|
13
15
|
import * as authRepository from '../auth/repository.ts'
|
|
14
16
|
import { SEATS_LIMIT } from './capabilities.ts'
|
|
15
17
|
import * as repository from './repository.ts'
|
|
@@ -460,11 +462,19 @@ export function createWorkspaceService(dependencies: WorkspaceDependencies): Wor
|
|
|
460
462
|
// The session that created the workspace should land in it.
|
|
461
463
|
await authRepository.setActiveWorkspace(tx, actor.sessionId, workspaceId)
|
|
462
464
|
|
|
463
|
-
events.emit(
|
|
464
|
-
|
|
465
|
+
events.emit(
|
|
466
|
+
'workspace.workspace.created',
|
|
467
|
+
{ type: 'workspace', id: workspaceId },
|
|
468
|
+
{ slug: workspace.slug },
|
|
469
|
+
)
|
|
470
|
+
events.emit(
|
|
471
|
+
'workspace.member.joined',
|
|
472
|
+
{ type: 'member', id: memberId },
|
|
473
|
+
{ userId: actor.userId },
|
|
474
|
+
)
|
|
465
475
|
|
|
466
476
|
return toWorkspaceView(workspace)
|
|
467
|
-
})
|
|
477
|
+
}, { workspaceId, actor: toEventActor(actor) })
|
|
468
478
|
},
|
|
469
479
|
|
|
470
480
|
async get(actor, workspaceId) {
|
|
@@ -526,8 +536,12 @@ export function createWorkspaceService(dependencies: WorkspaceDependencies): Wor
|
|
|
526
536
|
await dependencies.transaction(async ({ tx, events }) => {
|
|
527
537
|
await repository.deleteWorkspace(tx, workspaceId)
|
|
528
538
|
|
|
529
|
-
events.emit(
|
|
530
|
-
|
|
539
|
+
events.emit(
|
|
540
|
+
'workspace.workspace.deleted',
|
|
541
|
+
{ type: 'workspace', id: workspaceId },
|
|
542
|
+
{ slug: workspace.slug },
|
|
543
|
+
)
|
|
544
|
+
}, { workspaceId, actor: toEventActor(actor) })
|
|
531
545
|
},
|
|
532
546
|
|
|
533
547
|
async listMembers(actor, workspaceId) {
|
|
@@ -602,8 +616,12 @@ export function createWorkspaceService(dependencies: WorkspaceDependencies): Wor
|
|
|
602
616
|
await dependencies.transaction(async ({ tx, events }) => {
|
|
603
617
|
await repository.deleteMember(tx, target.id)
|
|
604
618
|
|
|
605
|
-
events.emit(
|
|
606
|
-
|
|
619
|
+
events.emit(
|
|
620
|
+
'workspace.member.removed',
|
|
621
|
+
{ type: 'member', id: target.id },
|
|
622
|
+
{ userId: target.userId },
|
|
623
|
+
)
|
|
624
|
+
}, { workspaceId, actor: toEventActor(actor) })
|
|
607
625
|
} catch (error: unknown) {
|
|
608
626
|
// A record assigned to them between the count and this delete. The
|
|
609
627
|
// constraint answers the same question the count did, so the caller gets
|
|
@@ -658,10 +676,14 @@ export function createWorkspaceService(dependencies: WorkspaceDependencies): Wor
|
|
|
658
676
|
expiresAt: new Date(now.getTime() + INVITE_LIFETIME_MS),
|
|
659
677
|
})
|
|
660
678
|
|
|
661
|
-
events.emit(
|
|
679
|
+
events.emit(
|
|
680
|
+
'workspace.member.invited',
|
|
681
|
+
{ type: 'invite', id: created.id },
|
|
682
|
+
{ inviteId: created.id, email: created.email },
|
|
683
|
+
)
|
|
662
684
|
|
|
663
685
|
return created
|
|
664
|
-
})
|
|
686
|
+
}, { workspaceId, actor: toEventActor(actor) })
|
|
665
687
|
|
|
666
688
|
// Sent after commit, so a rolled-back invite never reaches an inbox.
|
|
667
689
|
await sendInviteEmail(invite.email, token)
|
|
@@ -790,14 +812,14 @@ export function createWorkspaceService(dependencies: WorkspaceDependencies): Wor
|
|
|
790
812
|
throw AppError.notFound('Workspace not found')
|
|
791
813
|
}
|
|
792
814
|
|
|
793
|
-
events.emit(
|
|
794
|
-
|
|
795
|
-
memberId,
|
|
796
|
-
userId: actor.userId,
|
|
797
|
-
|
|
815
|
+
events.emit(
|
|
816
|
+
'workspace.member.joined',
|
|
817
|
+
{ type: 'member', id: memberId },
|
|
818
|
+
{ userId: actor.userId },
|
|
819
|
+
)
|
|
798
820
|
|
|
799
821
|
return toWorkspaceView(workspace)
|
|
800
|
-
})
|
|
822
|
+
}, { workspaceId: invite.workspaceId, actor: toEventActor(actor) })
|
|
801
823
|
},
|
|
802
824
|
|
|
803
825
|
async listModuleSettings(actor, workspaceId) {
|
package/src/runtime/events.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
|
+
|
|
3
|
+
import type { EventTarget, KelpieEvent } from '@kelpie/schemas'
|
|
4
|
+
import Emittery from 'emittery'
|
|
5
|
+
import type { ZodType } from 'zod'
|
|
6
|
+
|
|
1
7
|
import { describeThrown } from '../lib/errors.ts'
|
|
2
8
|
import type { Logger } from '../lib/logger.ts'
|
|
3
9
|
|
|
@@ -7,15 +13,16 @@ import type { Logger } from '../lib/logger.ts'
|
|
|
7
13
|
* Delivery is at-least-once within the process, with no durable queue. A crash
|
|
8
14
|
* between commit and dispatch loses the event. A durable outbox is a known
|
|
9
15
|
* follow-up; do not build it before a consumer cannot tolerate the loss.
|
|
16
|
+
*
|
|
17
|
+
* Every event carries the envelope defined in `@kelpie/schemas`
|
|
18
|
+
* (`KelpieEvent<Name, Data>`). Names and payload schemas live in per-module
|
|
19
|
+
* catalogs the bus registers at boot; two modules cannot declare the same name.
|
|
10
20
|
*/
|
|
11
21
|
|
|
12
22
|
/**
|
|
13
|
-
* CRM object types
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* Wider than `EXTENSIBLE_RECORD_TYPES` in `@kelpie/schemas`, which is the
|
|
17
|
-
* shorter list of detail pages a UI module can contribute to. A Position, a
|
|
18
|
-
* Form, and a Handbook page each publish events but have no page to extend.
|
|
23
|
+
* CRM object types the webhooks bridge treats as deliverable records. Kept
|
|
24
|
+
* here (rather than folded into a module) because import and webhooks both
|
|
25
|
+
* read it, and neither owns the list.
|
|
19
26
|
*/
|
|
20
27
|
export const RECORD_OBJECT_TYPES = [
|
|
21
28
|
'person',
|
|
@@ -33,87 +40,134 @@ export const RECORD_OBJECT_TYPES = [
|
|
|
33
40
|
|
|
34
41
|
export type RecordObjectType = (typeof RECORD_OBJECT_TYPES)[number]
|
|
35
42
|
|
|
36
|
-
/** Objects whose stage or status moves through a pipeline. */
|
|
37
|
-
export type StagedObjectType = 'deal' | 'opportunity' | 'raise' | 'partnership'
|
|
38
|
-
|
|
39
43
|
/**
|
|
40
|
-
* The event catalog.
|
|
41
|
-
* have to look up which tenant an event belongs to.
|
|
44
|
+
* The compile-time event catalog.
|
|
42
45
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
46
|
+
* Modules extend this interface via TypeScript declaration merging, one
|
|
47
|
+
* declaration per module. The runtime catalog (see `registerCatalog`) is the
|
|
48
|
+
* authority; this interface is what gives autocomplete and payload typing at
|
|
49
|
+
* every `publish` and `subscribe` call site.
|
|
45
50
|
*/
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
52
|
+
export interface KelpieEventMap {}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* When `KelpieEventMap` is empty (no module has declared an event yet) `keyof`
|
|
56
|
+
* is `never`, which would make every `EventHandler` signature unusable. The
|
|
57
|
+
* fallback keeps types working during boot and in tests that build the bus
|
|
58
|
+
* without any modules registered.
|
|
59
|
+
*/
|
|
60
|
+
type EventMapWithFallback = keyof KelpieEventMap extends never
|
|
61
|
+
? Readonly<Record<string, unknown>>
|
|
62
|
+
: KelpieEventMap
|
|
63
|
+
|
|
64
|
+
export type EventName = string & keyof EventMapWithFallback
|
|
65
|
+
|
|
66
|
+
/** Handlers are async and must be idempotent: the same event may arrive twice. */
|
|
67
|
+
export type EventHandler<Name extends EventName> = (
|
|
68
|
+
event: KelpieEvent<Name, EventMapWithFallback[Name]>,
|
|
69
|
+
) => Promise<void> | void
|
|
70
|
+
|
|
71
|
+
/** Prefix subscribers see events they cannot name statically. */
|
|
72
|
+
export type AnyEventHandler = (event: KelpieEvent<string, unknown>) => Promise<void> | void
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* What a module hands the runtime at registration: the module's id and the Zod
|
|
76
|
+
* schema for every event name the module publishes.
|
|
77
|
+
*/
|
|
78
|
+
export interface EventCatalog {
|
|
79
|
+
readonly moduleId: string
|
|
80
|
+
readonly events: Readonly<Record<string, ZodType>>
|
|
75
81
|
}
|
|
76
82
|
|
|
77
|
-
|
|
83
|
+
/** One frame in the causation chain that produced an event. */
|
|
84
|
+
export interface EventChainEntry {
|
|
85
|
+
readonly id: string
|
|
86
|
+
readonly name: string
|
|
87
|
+
readonly targetType: string
|
|
88
|
+
readonly targetId: string
|
|
89
|
+
}
|
|
78
90
|
|
|
79
91
|
/**
|
|
80
|
-
* The
|
|
81
|
-
*
|
|
92
|
+
* The chain of ancestor events currently being handled, keyed against the async
|
|
93
|
+
* context that started them. `undefined` outside any handler.
|
|
82
94
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
95
|
+
* The bus wraps every `publish` in `run(child, ...)`, so a handler that opens a
|
|
96
|
+
* transaction and calls `emit(...)` inherits the chain. The transaction scope
|
|
97
|
+
* reads this to stamp `causedBy` and to run the cycle guard.
|
|
85
98
|
*/
|
|
86
|
-
|
|
87
|
-
'workspace.created',
|
|
88
|
-
'workspace.deleted',
|
|
89
|
-
'member.invited',
|
|
90
|
-
'member.joined',
|
|
91
|
-
'member.removed',
|
|
92
|
-
'record.created',
|
|
93
|
-
'record.updated',
|
|
94
|
-
'record.deleted',
|
|
95
|
-
'stage.changed',
|
|
96
|
-
'note.added',
|
|
97
|
-
'decision.added',
|
|
98
|
-
'plan.completed',
|
|
99
|
-
'form.submitted',
|
|
100
|
-
'import.completed',
|
|
101
|
-
'agent_run.finished',
|
|
102
|
-
] as const satisfies readonly DomainEventName[]
|
|
99
|
+
const chainStorage = new AsyncLocalStorage<readonly EventChainEntry[]>()
|
|
103
100
|
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
export function currentEventChain(): readonly EventChainEntry[] {
|
|
102
|
+
return chainStorage.getStore() ?? []
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Runs the cycle guard against the current chain. Called from the transaction
|
|
107
|
+
* scope's `emit` before it buffers the event.
|
|
108
|
+
*/
|
|
109
|
+
export function checkEventCycle(
|
|
110
|
+
chain: readonly EventChainEntry[],
|
|
111
|
+
name: string,
|
|
112
|
+
target: EventTarget,
|
|
113
|
+
maxDepth: number,
|
|
114
|
+
): CycleGuardOutcome {
|
|
115
|
+
if (chain.length >= maxDepth) {
|
|
116
|
+
return { kind: 'depth' }
|
|
117
|
+
}
|
|
118
|
+
for (const entry of chain) {
|
|
119
|
+
if (entry.name === name && entry.targetType === target.type && entry.targetId === target.id) {
|
|
120
|
+
return { kind: 'repeat' }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { kind: 'ok' }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export type CycleGuardOutcome =
|
|
127
|
+
| { readonly kind: 'ok' }
|
|
128
|
+
| { readonly kind: 'depth' }
|
|
129
|
+
| { readonly kind: 'repeat' }
|
|
130
|
+
|
|
131
|
+
export interface SubscribeOptions {
|
|
132
|
+
/** Shown in logs when the handler fails or times out. Defaults to the event name. */
|
|
133
|
+
readonly label?: string
|
|
134
|
+
/** Overrides the bus-wide default. */
|
|
135
|
+
readonly timeoutMs?: number
|
|
136
|
+
}
|
|
106
137
|
|
|
107
138
|
export interface EventBus {
|
|
108
|
-
/**
|
|
109
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Registers a module's event catalog. Called by `registerModules` at boot,
|
|
141
|
+
* once per module that declares events. Two modules cannot declare the same
|
|
142
|
+
* event name; the second call throws.
|
|
143
|
+
*/
|
|
144
|
+
registerCatalog(catalog: EventCatalog): void
|
|
145
|
+
/** True if any module has declared this event name. */
|
|
146
|
+
hasEvent(name: string): boolean
|
|
147
|
+
/** The payload schema registered for this event, or `undefined` if unknown. */
|
|
148
|
+
getSchema(name: string): ZodType | undefined
|
|
149
|
+
/**
|
|
150
|
+
* Subscribe to an event by exact name. Handlers for one event run
|
|
151
|
+
* concurrently, each in its own error and timeout boundary.
|
|
152
|
+
*/
|
|
153
|
+
subscribe<Name extends EventName>(
|
|
154
|
+
name: Name,
|
|
155
|
+
handler: EventHandler<Name>,
|
|
156
|
+
options?: SubscribeOptions,
|
|
157
|
+
): void
|
|
158
|
+
/**
|
|
159
|
+
* Subscribe to every event whose name starts with `prefix`. Runs concurrently
|
|
160
|
+
* with exact-name subscribers. Match is a plain `startsWith`; there is no
|
|
161
|
+
* pattern language.
|
|
162
|
+
*/
|
|
163
|
+
subscribePrefix(prefix: string, handler: AnyEventHandler, options?: SubscribeOptions): void
|
|
110
164
|
/**
|
|
111
165
|
* Dispatches immediately. Services should not call this directly: they emit
|
|
112
166
|
* through the transaction scope so nothing fires before the commit.
|
|
113
|
-
*
|
|
114
|
-
* @returns A promise that settles when every handler for this event has settled.
|
|
115
167
|
*/
|
|
116
|
-
publish<Name extends
|
|
168
|
+
publish<Name extends EventName>(
|
|
169
|
+
event: KelpieEvent<Name, EventMapWithFallback[Name]>,
|
|
170
|
+
): Promise<void>
|
|
117
171
|
/**
|
|
118
172
|
* Settles once every in-flight publication has finished, including any a
|
|
119
173
|
* handler started. Used by tests, and by shutdown to flush before exit.
|
|
@@ -121,57 +175,164 @@ export interface EventBus {
|
|
|
121
175
|
drain(): Promise<void>
|
|
122
176
|
}
|
|
123
177
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
178
|
+
export interface EventBusOptions {
|
|
179
|
+
/** Default per-handler timeout in ms. Defaults to 5000. */
|
|
180
|
+
readonly defaultHandlerTimeoutMs?: number
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const DEFAULT_HANDLER_TIMEOUT_MS = 5000
|
|
184
|
+
|
|
185
|
+
interface PrefixHandler {
|
|
186
|
+
readonly prefix: string
|
|
187
|
+
readonly label: string
|
|
188
|
+
readonly run: (event: KelpieEvent<string, unknown>) => Promise<void>
|
|
189
|
+
}
|
|
130
190
|
|
|
131
191
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
192
|
+
* Emittery v2 passes each listener an `{ name, data }` object rather than the
|
|
193
|
+
* raw payload. This shape stays internal so nothing outside this file sees the
|
|
194
|
+
* extra envelope on top of ours.
|
|
135
195
|
*/
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
registry[name] = []
|
|
141
|
-
}
|
|
196
|
+
interface EmitteryListenerEvent {
|
|
197
|
+
readonly name: string
|
|
198
|
+
readonly data: unknown
|
|
199
|
+
}
|
|
142
200
|
|
|
143
|
-
|
|
201
|
+
interface EmitterySignature {
|
|
202
|
+
readonly on: (
|
|
203
|
+
name: string,
|
|
204
|
+
listener: (event: EmitteryListenerEvent) => Promise<void> | void,
|
|
205
|
+
) => () => void
|
|
206
|
+
readonly emit: (name: string, data: unknown) => Promise<void>
|
|
144
207
|
}
|
|
145
208
|
|
|
146
|
-
export function createEventBus(logger: Logger): EventBus {
|
|
147
|
-
const
|
|
148
|
-
const
|
|
209
|
+
export function createEventBus(logger: Logger, options: EventBusOptions = {}): EventBus {
|
|
210
|
+
const emittery = new Emittery() as unknown as EmitterySignature
|
|
211
|
+
const schemas = new Map<string, ZodType>()
|
|
212
|
+
const owners = new Map<string, string>()
|
|
213
|
+
const prefixHandlers: PrefixHandler[] = []
|
|
214
|
+
const inFlight = new Set<Promise<unknown>>()
|
|
215
|
+
const defaultTimeoutMs = options.defaultHandlerTimeoutMs ?? DEFAULT_HANDLER_TIMEOUT_MS
|
|
216
|
+
|
|
217
|
+
function wrap<Value>(
|
|
218
|
+
label: string,
|
|
219
|
+
timeoutMs: number,
|
|
220
|
+
handler: (event: Value) => Promise<void> | void,
|
|
221
|
+
context: (event: Value) => Readonly<Record<string, unknown>>,
|
|
222
|
+
): (event: Value) => Promise<void> {
|
|
223
|
+
return async (event) => {
|
|
224
|
+
let timerHandle: NodeJS.Timeout | undefined
|
|
225
|
+
const timedOut = Symbol('timeout')
|
|
226
|
+
const timer = new Promise<typeof timedOut>((resolve) => {
|
|
227
|
+
timerHandle = setTimeout(() => resolve(timedOut), timeoutMs)
|
|
228
|
+
timerHandle.unref?.()
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const outcome = await Promise.race([
|
|
233
|
+
Promise.resolve().then(() => handler(event)),
|
|
234
|
+
timer,
|
|
235
|
+
])
|
|
236
|
+
if (outcome === timedOut) {
|
|
237
|
+
logger.error('event handler timed out', { label, timeoutMs, ...context(event) })
|
|
238
|
+
}
|
|
239
|
+
} catch (error: unknown) {
|
|
240
|
+
logger.error('event handler failed', {
|
|
241
|
+
label,
|
|
242
|
+
error: describeThrown(error),
|
|
243
|
+
...context(event),
|
|
244
|
+
})
|
|
245
|
+
} finally {
|
|
246
|
+
if (timerHandle !== undefined) {
|
|
247
|
+
clearTimeout(timerHandle)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
149
252
|
|
|
150
253
|
return {
|
|
151
|
-
|
|
152
|
-
|
|
254
|
+
registerCatalog(catalog) {
|
|
255
|
+
for (const [name, schema] of Object.entries(catalog.events)) {
|
|
256
|
+
const existing = owners.get(name)
|
|
257
|
+
if (existing !== undefined) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`event "${name}" is declared by both module "${existing}" and module "${catalog.moduleId}"`,
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
owners.set(name, catalog.moduleId)
|
|
263
|
+
schemas.set(name, schema)
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
hasEvent(name) {
|
|
268
|
+
return schemas.has(name)
|
|
153
269
|
},
|
|
154
270
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
271
|
+
getSchema(name) {
|
|
272
|
+
return schemas.get(name)
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
subscribe(name, handler, subscribeOptions) {
|
|
276
|
+
const label = subscribeOptions?.label ?? name
|
|
277
|
+
const timeoutMs = subscribeOptions?.timeoutMs ?? defaultTimeoutMs
|
|
278
|
+
const wrapped = wrap<EmitteryListenerEvent>(
|
|
279
|
+
label,
|
|
280
|
+
timeoutMs,
|
|
281
|
+
(event) => {
|
|
282
|
+
const envelope = event.data as KelpieEvent<string, unknown>
|
|
283
|
+
return handler(envelope as never)
|
|
168
284
|
},
|
|
285
|
+
(event) => {
|
|
286
|
+
const envelope = event.data as KelpieEvent<string, unknown>
|
|
287
|
+
return { event: envelope.name, eventId: envelope.id }
|
|
288
|
+
},
|
|
289
|
+
)
|
|
290
|
+
emittery.on(name, wrapped)
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
subscribePrefix(prefix, handler, subscribeOptions) {
|
|
294
|
+
const label = subscribeOptions?.label ?? `${prefix}*`
|
|
295
|
+
const timeoutMs = subscribeOptions?.timeoutMs ?? defaultTimeoutMs
|
|
296
|
+
prefixHandlers.push({
|
|
297
|
+
prefix,
|
|
298
|
+
label,
|
|
299
|
+
run: wrap<KelpieEvent<string, unknown>>(
|
|
300
|
+
label,
|
|
301
|
+
timeoutMs,
|
|
302
|
+
handler,
|
|
303
|
+
(event) => ({ event: event.name, eventId: event.id }),
|
|
304
|
+
),
|
|
305
|
+
})
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
publish(event) {
|
|
309
|
+
const parent = chainStorage.getStore() ?? []
|
|
310
|
+
const child: EventChainEntry[] = [
|
|
311
|
+
...parent,
|
|
312
|
+
{
|
|
313
|
+
id: event.id,
|
|
314
|
+
name: event.name,
|
|
315
|
+
targetType: event.target.type,
|
|
316
|
+
targetId: event.target.id,
|
|
317
|
+
},
|
|
318
|
+
]
|
|
319
|
+
|
|
320
|
+
const matching = prefixHandlers.filter((entry) => event.name.startsWith(entry.prefix))
|
|
321
|
+
const typedEvent = event as KelpieEvent<string, unknown>
|
|
322
|
+
|
|
323
|
+
const settled = chainStorage.run(child, () =>
|
|
324
|
+
Promise.allSettled([
|
|
325
|
+
emittery.emit(event.name, typedEvent),
|
|
326
|
+
...matching.map((entry) => entry.run(typedEvent)),
|
|
327
|
+
]),
|
|
169
328
|
)
|
|
170
329
|
|
|
171
330
|
inFlight.add(settled)
|
|
172
|
-
void settled.finally(() =>
|
|
331
|
+
void settled.finally(() => {
|
|
332
|
+
inFlight.delete(settled)
|
|
333
|
+
})
|
|
173
334
|
|
|
174
|
-
return settled
|
|
335
|
+
return settled.then(() => undefined)
|
|
175
336
|
},
|
|
176
337
|
|
|
177
338
|
async drain() {
|
package/src/runtime/module.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { Database } from '../lib/database.ts'
|
|
|
6
6
|
import type { EmailSender } from '../lib/email.ts'
|
|
7
7
|
import type { IdFactory } from '../lib/ids.ts'
|
|
8
8
|
import type { Logger } from '../lib/logger.ts'
|
|
9
|
+
import type { SecretEncryptionConfig } from '../lib/secrets.ts'
|
|
9
10
|
import type { EntitlementRegistry } from './entitlements.ts'
|
|
10
11
|
import type { EventBus } from './events.ts'
|
|
11
12
|
import type { TransactionScope } from './transaction.ts'
|
|
@@ -69,17 +70,55 @@ export interface ModuleServices {
|
|
|
69
70
|
readonly db: Database
|
|
70
71
|
/** Runs work in one transaction and publishes its events after commit. */
|
|
71
72
|
readonly transaction: TransactionScope
|
|
72
|
-
/** Transactional mail only: invites and password resets. */
|
|
73
|
-
readonly email: EmailSender
|
|
74
73
|
/** Generates `<prefix>_<ulid>` ids. Injected so tests can pin them. */
|
|
75
74
|
readonly createId: IdFactory
|
|
76
75
|
/** The current time, injected so expiry logic is testable. */
|
|
77
76
|
readonly now: () => Date
|
|
77
|
+
/**
|
|
78
|
+
* The deployment's base URL, when the assembly declares `appBaseUrl` in
|
|
79
|
+
* `kelpie.config.ts`. Modules that need it prefer this over
|
|
80
|
+
* `context.config(appUrlConfigSchema)`, which they fall back to when this is
|
|
81
|
+
* undefined (older assemblies).
|
|
82
|
+
*/
|
|
83
|
+
readonly appBaseUrl?: string | undefined
|
|
84
|
+
/**
|
|
85
|
+
* The keys that seal stored secrets, when the assembly declares
|
|
86
|
+
* `secretEncryption`. Same fallback pattern as `appBaseUrl`.
|
|
87
|
+
*/
|
|
88
|
+
readonly secretEncryption?: SecretEncryptionConfig | undefined
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
export interface ModuleContext extends ModuleServices {
|
|
81
92
|
/** Registers routes. They mount under `/v1` and are public API like any other. */
|
|
82
93
|
routes(mount: (router: Hono) => void): void
|
|
94
|
+
/**
|
|
95
|
+
* Registers a named transactional-mail provider. `name` is what the assembly
|
|
96
|
+
* puts in `kelpie.config.ts`'s `email.provider`; the runtime resolves that
|
|
97
|
+
* field against the registry after every module has registered.
|
|
98
|
+
*
|
|
99
|
+
* `build` is a factory the runtime calls exactly once, and only for the
|
|
100
|
+
* provider `email.provider` picked. A provider that is registered but never
|
|
101
|
+
* picked never has its factory called, so a module can register itself
|
|
102
|
+
* without demanding its own env up front: a self-hoster who leaves the
|
|
103
|
+
* built-in `smtp-email` core module in `coreModules` while running on
|
|
104
|
+
* `EMAIL_PROVIDER=log` will not be asked for SMTP credentials at boot.
|
|
105
|
+
* The factory may throw; the runtime wraps the error with the module id.
|
|
106
|
+
*
|
|
107
|
+
* Any number of modules may register different names side by side. Two
|
|
108
|
+
* modules registering the same name fails boot. `'log'` is registered by
|
|
109
|
+
* the runtime itself, so a module that tries to shadow it fails boot too.
|
|
110
|
+
*
|
|
111
|
+
* `context.email` is a proxy: every module receives the same object, and
|
|
112
|
+
* its `send` delegates to whichever provider `email.provider` picked.
|
|
113
|
+
* Consumer modules capture the proxy at register time; registration order
|
|
114
|
+
* does not matter.
|
|
115
|
+
*/
|
|
116
|
+
provideEmailSender(name: string, build: () => EmailSender): void
|
|
117
|
+
/**
|
|
118
|
+
* Transactional mail. Every module receives the same proxy; `.send()`
|
|
119
|
+
* delegates to the provider `email.provider` picked.
|
|
120
|
+
*/
|
|
121
|
+
readonly email: EmailSender
|
|
83
122
|
/**
|
|
84
123
|
* Registers routes that mount under `/v1/public`, take no credentials, and
|
|
85
124
|
* answer cross-origin requests from any site.
|
|
@@ -153,6 +192,15 @@ export interface ModuleCatalogEntry {
|
|
|
153
192
|
readonly structural: boolean
|
|
154
193
|
}
|
|
155
194
|
|
|
195
|
+
/**
|
|
196
|
+
* A module's event catalog: the Zod schema for every event name the module
|
|
197
|
+
* publishes. Registered with the bus at boot; two modules cannot claim the same
|
|
198
|
+
* name.
|
|
199
|
+
*/
|
|
200
|
+
export interface ModuleEventCatalog {
|
|
201
|
+
readonly [eventName: string]: ZodType
|
|
202
|
+
}
|
|
203
|
+
|
|
156
204
|
export interface KelpieModule {
|
|
157
205
|
readonly id: string
|
|
158
206
|
/** Ids of modules that must register first. Missing ones fail boot. */
|
|
@@ -167,5 +215,10 @@ export interface KelpieModule {
|
|
|
167
215
|
* without its author having to opt in.
|
|
168
216
|
*/
|
|
169
217
|
readonly structural?: boolean
|
|
218
|
+
/**
|
|
219
|
+
* The events this module publishes, as a runtime catalog. Optional: a module
|
|
220
|
+
* that only consumes events, or contributes none, may omit it.
|
|
221
|
+
*/
|
|
222
|
+
readonly events?: ModuleEventCatalog
|
|
170
223
|
register(context: ModuleContext): Promise<void>
|
|
171
224
|
}
|