@digimuza/saas-core 0.0.2
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/CLAUDE.md +1165 -0
- package/LICENSE +21 -0
- package/README.md +234 -0
- package/dist/api-keys/index.d.ts +9 -0
- package/dist/api-keys/index.d.ts.map +1 -0
- package/dist/api-keys/index.js +37 -0
- package/dist/api-keys/index.js.map +1 -0
- package/dist/api-keys/keys.d.ts +55 -0
- package/dist/api-keys/keys.d.ts.map +1 -0
- package/dist/api-keys/keys.js +92 -0
- package/dist/api-keys/keys.js.map +1 -0
- package/dist/api-keys/service.d.ts +111 -0
- package/dist/api-keys/service.d.ts.map +1 -0
- package/dist/api-keys/service.js +210 -0
- package/dist/api-keys/service.js.map +1 -0
- package/dist/audit/index.d.ts +5 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +44 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/service.d.ts +88 -0
- package/dist/audit/service.d.ts.map +1 -0
- package/dist/audit/service.js +128 -0
- package/dist/audit/service.js.map +1 -0
- package/dist/auth/client.d.ts +5 -0
- package/dist/auth/client.d.ts.map +1 -0
- package/dist/auth/client.js +26 -0
- package/dist/auth/client.js.map +1 -0
- package/dist/auth/cookies.d.ts +51 -0
- package/dist/auth/cookies.d.ts.map +1 -0
- package/dist/auth/cookies.js +58 -0
- package/dist/auth/cookies.js.map +1 -0
- package/dist/auth/index.d.ts +14 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +46 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/service.d.ts +67 -0
- package/dist/auth/service.d.ts.map +1 -0
- package/dist/auth/service.js +275 -0
- package/dist/auth/service.js.map +1 -0
- package/dist/auth/types.d.ts +56 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/authorization/index.d.ts +4 -0
- package/dist/authorization/index.d.ts.map +1 -0
- package/dist/authorization/index.js +24 -0
- package/dist/authorization/index.js.map +1 -0
- package/dist/authorization/service.d.ts +92 -0
- package/dist/authorization/service.d.ts.map +1 -0
- package/dist/authorization/service.js +254 -0
- package/dist/authorization/service.js.map +1 -0
- package/dist/billing/index.d.ts +16 -0
- package/dist/billing/index.d.ts.map +1 -0
- package/dist/billing/index.js +47 -0
- package/dist/billing/index.js.map +1 -0
- package/dist/billing/provider.d.ts +114 -0
- package/dist/billing/provider.d.ts.map +1 -0
- package/dist/billing/provider.js +2 -0
- package/dist/billing/provider.js.map +1 -0
- package/dist/billing/revolut.d.ts +68 -0
- package/dist/billing/revolut.d.ts.map +1 -0
- package/dist/billing/revolut.js +181 -0
- package/dist/billing/revolut.js.map +1 -0
- package/dist/billing/service.d.ts +124 -0
- package/dist/billing/service.d.ts.map +1 -0
- package/dist/billing/service.js +470 -0
- package/dist/billing/service.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +43 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +212 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/project.d.ts +12 -0
- package/dist/cli/commands/project.d.ts.map +1 -0
- package/dist/cli/commands/project.js +49 -0
- package/dist/cli/commands/project.js.map +1 -0
- package/dist/cli/docker.d.ts +50 -0
- package/dist/cli/docker.d.ts.map +1 -0
- package/dist/cli/docker.js +84 -0
- package/dist/cli/docker.js.map +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +198 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/output.d.ts +30 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +66 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/templates.d.ts +52 -0
- package/dist/cli/templates.d.ts.map +1 -0
- package/dist/cli/templates.js +225 -0
- package/dist/cli/templates.js.map +1 -0
- package/dist/config/client.d.ts +60 -0
- package/dist/config/client.d.ts.map +1 -0
- package/dist/config/client.js +94 -0
- package/dist/config/client.js.map +1 -0
- package/dist/config/config-loader.d.ts +2 -0
- package/dist/config/config-loader.d.ts.map +1 -0
- package/dist/config/config-loader.js +2 -0
- package/dist/config/config-loader.js.map +1 -0
- package/dist/config/env.d.ts +24 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +40 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/errors.d.ts +17 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/errors.js +26 -0
- package/dist/config/errors.js.map +1 -0
- package/dist/config/index.d.ts +72 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +106 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/runtime.d.ts +15 -0
- package/dist/config/runtime.d.ts.map +1 -0
- package/dist/config/runtime.js +22 -0
- package/dist/config/runtime.js.map +1 -0
- package/dist/config/sections.d.ts +226 -0
- package/dist/config/sections.d.ts.map +1 -0
- package/dist/config/sections.js +141 -0
- package/dist/config/sections.js.map +1 -0
- package/dist/db/client.d.ts +43 -0
- package/dist/db/client.d.ts.map +1 -0
- package/dist/db/client.js +77 -0
- package/dist/db/client.js.map +1 -0
- package/dist/db/errors.d.ts +39 -0
- package/dist/db/errors.d.ts.map +1 -0
- package/dist/db/errors.js +67 -0
- package/dist/db/errors.js.map +1 -0
- package/dist/db/index.d.ts +10 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +10 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrate.d.ts +21 -0
- package/dist/db/migrate.d.ts.map +1 -0
- package/dist/db/migrate.js +28 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/schema/_shared.d.ts +30 -0
- package/dist/db/schema/_shared.d.ts.map +1 -0
- package/dist/db/schema/_shared.js +36 -0
- package/dist/db/schema/_shared.js.map +1 -0
- package/dist/db/schema/apikeys.d.ts +263 -0
- package/dist/db/schema/apikeys.d.ts.map +1 -0
- package/dist/db/schema/apikeys.js +70 -0
- package/dist/db/schema/apikeys.js.map +1 -0
- package/dist/db/schema/authorization.d.ts +465 -0
- package/dist/db/schema/authorization.d.ts.map +1 -0
- package/dist/db/schema/authorization.js +134 -0
- package/dist/db/schema/authorization.js.map +1 -0
- package/dist/db/schema/billing.d.ts +1100 -0
- package/dist/db/schema/billing.d.ts.map +1 -0
- package/dist/db/schema/billing.js +223 -0
- package/dist/db/schema/billing.js.map +1 -0
- package/dist/db/schema/events.d.ts +161 -0
- package/dist/db/schema/events.d.ts.map +1 -0
- package/dist/db/schema/events.js +66 -0
- package/dist/db/schema/events.js.map +1 -0
- package/dist/db/schema/features.d.ts +243 -0
- package/dist/db/schema/features.d.ts.map +1 -0
- package/dist/db/schema/features.js +87 -0
- package/dist/db/schema/features.js.map +1 -0
- package/dist/db/schema/files.d.ts +253 -0
- package/dist/db/schema/files.d.ts.map +1 -0
- package/dist/db/schema/files.js +74 -0
- package/dist/db/schema/files.js.map +1 -0
- package/dist/db/schema/index.d.ts +38 -0
- package/dist/db/schema/index.d.ts.map +1 -0
- package/dist/db/schema/index.js +38 -0
- package/dist/db/schema/index.js.map +1 -0
- package/dist/db/schema/invitations.d.ts +214 -0
- package/dist/db/schema/invitations.d.ts.map +1 -0
- package/dist/db/schema/invitations.js +80 -0
- package/dist/db/schema/invitations.js.map +1 -0
- package/dist/db/schema/messaging.d.ts +442 -0
- package/dist/db/schema/messaging.d.ts.map +1 -0
- package/dist/db/schema/messaging.js +120 -0
- package/dist/db/schema/messaging.js.map +1 -0
- package/dist/db/schema/users.d.ts +246 -0
- package/dist/db/schema/users.d.ts.map +1 -0
- package/dist/db/schema/users.js +60 -0
- package/dist/db/schema/users.js.map +1 -0
- package/dist/db/schema/webhooks.d.ts +693 -0
- package/dist/db/schema/webhooks.d.ts.map +1 -0
- package/dist/db/schema/webhooks.js +147 -0
- package/dist/db/schema/webhooks.js.map +1 -0
- package/dist/db/schema/workspaces.d.ts +196 -0
- package/dist/db/schema/workspaces.d.ts.map +1 -0
- package/dist/db/schema/workspaces.js +57 -0
- package/dist/db/schema/workspaces.js.map +1 -0
- package/dist/db/types.d.ts +18 -0
- package/dist/db/types.d.ts.map +1 -0
- package/dist/db/types.js +2 -0
- package/dist/db/types.js.map +1 -0
- package/dist/email/adapters.d.ts +29 -0
- package/dist/email/adapters.d.ts.map +1 -0
- package/dist/email/adapters.js +60 -0
- package/dist/email/adapters.js.map +1 -0
- package/dist/email/index.d.ts +20 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +79 -0
- package/dist/email/index.js.map +1 -0
- package/dist/email/provider.d.ts +47 -0
- package/dist/email/provider.d.ts.map +1 -0
- package/dist/email/provider.js +7 -0
- package/dist/email/provider.js.map +1 -0
- package/dist/email/resend.d.ts +22 -0
- package/dist/email/resend.d.ts.map +1 -0
- package/dist/email/resend.js +51 -0
- package/dist/email/resend.js.map +1 -0
- package/dist/email/service.d.ts +55 -0
- package/dist/email/service.d.ts.map +1 -0
- package/dist/email/service.js +87 -0
- package/dist/email/service.js.map +1 -0
- package/dist/email/templates.d.ts +81 -0
- package/dist/email/templates.d.ts.map +1 -0
- package/dist/email/templates.js +84 -0
- package/dist/email/templates.js.map +1 -0
- package/dist/entitlements/index.d.ts +10 -0
- package/dist/entitlements/index.d.ts.map +1 -0
- package/dist/entitlements/index.js +28 -0
- package/dist/entitlements/index.js.map +1 -0
- package/dist/entitlements/service.d.ts +83 -0
- package/dist/entitlements/service.d.ts.map +1 -0
- package/dist/entitlements/service.js +158 -0
- package/dist/entitlements/service.js.map +1 -0
- package/dist/errors.d.ts +85 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +95 -0
- package/dist/errors.js.map +1 -0
- package/dist/events/core-events.d.ts +120 -0
- package/dist/events/core-events.d.ts.map +1 -0
- package/dist/events/core-events.js +109 -0
- package/dist/events/core-events.js.map +1 -0
- package/dist/events/index.d.ts +15 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +58 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/publisher.d.ts +46 -0
- package/dist/events/publisher.d.ts.map +1 -0
- package/dist/events/publisher.js +54 -0
- package/dist/events/publisher.js.map +1 -0
- package/dist/events/registry.d.ts +87 -0
- package/dist/events/registry.d.ts.map +1 -0
- package/dist/events/registry.js +34 -0
- package/dist/events/registry.js.map +1 -0
- package/dist/events/service.d.ts +78 -0
- package/dist/events/service.d.ts.map +1 -0
- package/dist/events/service.js +182 -0
- package/dist/events/service.js.map +1 -0
- package/dist/features/bucket.d.ts +22 -0
- package/dist/features/bucket.d.ts.map +1 -0
- package/dist/features/bucket.js +42 -0
- package/dist/features/bucket.js.map +1 -0
- package/dist/features/index.d.ts +15 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +78 -0
- package/dist/features/index.js.map +1 -0
- package/dist/features/registry.d.ts +51 -0
- package/dist/features/registry.d.ts.map +1 -0
- package/dist/features/registry.js +52 -0
- package/dist/features/registry.js.map +1 -0
- package/dist/features/service.d.ts +78 -0
- package/dist/features/service.d.ts.map +1 -0
- package/dist/features/service.js +207 -0
- package/dist/features/service.js.map +1 -0
- package/dist/files/index.d.ts +6 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +64 -0
- package/dist/files/index.js.map +1 -0
- package/dist/files/service.d.ts +92 -0
- package/dist/files/service.d.ts.map +1 -0
- package/dist/files/service.js +303 -0
- package/dist/files/service.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs/backend.d.ts +23 -0
- package/dist/jobs/backend.d.ts.map +1 -0
- package/dist/jobs/backend.js +23 -0
- package/dist/jobs/backend.js.map +1 -0
- package/dist/jobs/index.d.ts +29 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +114 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/jobs/service.d.ts +67 -0
- package/dist/jobs/service.d.ts.map +1 -0
- package/dist/jobs/service.js +139 -0
- package/dist/jobs/service.js.map +1 -0
- package/dist/jobs/types.d.ts +78 -0
- package/dist/jobs/types.d.ts.map +1 -0
- package/dist/jobs/types.js +4 -0
- package/dist/jobs/types.js.map +1 -0
- package/dist/logger/default.d.ts +7 -0
- package/dist/logger/default.d.ts.map +1 -0
- package/dist/logger/default.js +28 -0
- package/dist/logger/default.js.map +1 -0
- package/dist/logger/index.d.ts +24 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +90 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/redact.d.ts +40 -0
- package/dist/logger/redact.d.ts.map +1 -0
- package/dist/logger/redact.js +178 -0
- package/dist/logger/redact.js.map +1 -0
- package/dist/logger/service.d.ts +14 -0
- package/dist/logger/service.d.ts.map +1 -0
- package/dist/logger/service.js +56 -0
- package/dist/logger/service.js.map +1 -0
- package/dist/logger/sinks.d.ts +37 -0
- package/dist/logger/sinks.d.ts.map +1 -0
- package/dist/logger/sinks.js +135 -0
- package/dist/logger/sinks.js.map +1 -0
- package/dist/logger/types.d.ts +47 -0
- package/dist/logger/types.d.ts.map +1 -0
- package/dist/logger/types.js +8 -0
- package/dist/logger/types.js.map +1 -0
- package/dist/notifications/index.d.ts +8 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +72 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/service.d.ts +79 -0
- package/dist/notifications/service.d.ts.map +1 -0
- package/dist/notifications/service.js +164 -0
- package/dist/notifications/service.js.map +1 -0
- package/dist/realtime/channels.d.ts +37 -0
- package/dist/realtime/channels.d.ts.map +1 -0
- package/dist/realtime/channels.js +58 -0
- package/dist/realtime/channels.js.map +1 -0
- package/dist/realtime/index.d.ts +19 -0
- package/dist/realtime/index.d.ts.map +1 -0
- package/dist/realtime/index.js +118 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/realtime/postgres.d.ts +48 -0
- package/dist/realtime/postgres.d.ts.map +1 -0
- package/dist/realtime/postgres.js +151 -0
- package/dist/realtime/postgres.js.map +1 -0
- package/dist/realtime/service.d.ts +75 -0
- package/dist/realtime/service.d.ts.map +1 -0
- package/dist/realtime/service.js +103 -0
- package/dist/realtime/service.js.map +1 -0
- package/dist/realtime/sse.d.ts +55 -0
- package/dist/realtime/sse.d.ts.map +1 -0
- package/dist/realtime/sse.js +123 -0
- package/dist/realtime/sse.js.map +1 -0
- package/dist/realtime/transport.d.ts +52 -0
- package/dist/realtime/transport.d.ts.map +1 -0
- package/dist/realtime/transport.js +2 -0
- package/dist/realtime/transport.js.map +1 -0
- package/dist/storage/index.d.ts +18 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +89 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/keys.d.ts +50 -0
- package/dist/storage/keys.d.ts.map +1 -0
- package/dist/storage/keys.js +120 -0
- package/dist/storage/keys.js.map +1 -0
- package/dist/storage/memory.d.ts +32 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +84 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/storage/provider.d.ts +93 -0
- package/dist/storage/provider.d.ts.map +1 -0
- package/dist/storage/provider.js +2 -0
- package/dist/storage/provider.js.map +1 -0
- package/dist/storage/s3.d.ts +34 -0
- package/dist/storage/s3.d.ts.map +1 -0
- package/dist/storage/s3.js +175 -0
- package/dist/storage/s3.js.map +1 -0
- package/dist/storage/service.d.ts +86 -0
- package/dist/storage/service.d.ts.map +1 -0
- package/dist/storage/service.js +107 -0
- package/dist/storage/service.js.map +1 -0
- package/dist/storage/sigv4.d.ts +58 -0
- package/dist/storage/sigv4.d.ts.map +1 -0
- package/dist/storage/sigv4.js +114 -0
- package/dist/storage/sigv4.js.map +1 -0
- package/dist/streams/client.d.ts +91 -0
- package/dist/streams/client.d.ts.map +1 -0
- package/dist/streams/client.js +128 -0
- package/dist/streams/client.js.map +1 -0
- package/dist/streams/events.d.ts +68 -0
- package/dist/streams/events.d.ts.map +1 -0
- package/dist/streams/events.js +81 -0
- package/dist/streams/events.js.map +1 -0
- package/dist/streams/index.d.ts +47 -0
- package/dist/streams/index.d.ts.map +1 -0
- package/dist/streams/index.js +47 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams/realtime.d.ts +27 -0
- package/dist/streams/realtime.d.ts.map +1 -0
- package/dist/streams/realtime.js +50 -0
- package/dist/streams/realtime.js.map +1 -0
- package/dist/streams/state.d.ts +26 -0
- package/dist/streams/state.d.ts.map +1 -0
- package/dist/streams/state.js +34 -0
- package/dist/streams/state.js.map +1 -0
- package/dist/users/index.d.ts +9 -0
- package/dist/users/index.d.ts.map +1 -0
- package/dist/users/index.js +23 -0
- package/dist/users/index.js.map +1 -0
- package/dist/users/service.d.ts +43 -0
- package/dist/users/service.d.ts.map +1 -0
- package/dist/users/service.js +113 -0
- package/dist/users/service.js.map +1 -0
- package/dist/webhooks/inbound.d.ts +70 -0
- package/dist/webhooks/inbound.d.ts.map +1 -0
- package/dist/webhooks/inbound.js +146 -0
- package/dist/webhooks/inbound.js.map +1 -0
- package/dist/webhooks/index.d.ts +12 -0
- package/dist/webhooks/index.d.ts.map +1 -0
- package/dist/webhooks/index.js +65 -0
- package/dist/webhooks/index.js.map +1 -0
- package/dist/webhooks/outgoing.d.ts +89 -0
- package/dist/webhooks/outgoing.d.ts.map +1 -0
- package/dist/webhooks/outgoing.js +375 -0
- package/dist/webhooks/outgoing.js.map +1 -0
- package/dist/webhooks/signature.d.ts +40 -0
- package/dist/webhooks/signature.d.ts.map +1 -0
- package/dist/webhooks/signature.js +77 -0
- package/dist/webhooks/signature.js.map +1 -0
- package/dist/workspaces/context.d.ts +30 -0
- package/dist/workspaces/context.d.ts.map +1 -0
- package/dist/workspaces/context.js +12 -0
- package/dist/workspaces/context.js.map +1 -0
- package/dist/workspaces/index.d.ts +5 -0
- package/dist/workspaces/index.d.ts.map +1 -0
- package/dist/workspaces/index.js +21 -0
- package/dist/workspaces/index.js.map +1 -0
- package/dist/workspaces/service.d.ts +126 -0
- package/dist/workspaces/service.d.ts.map +1 -0
- package/dist/workspaces/service.js +495 -0
- package/dist/workspaces/service.js.map +1 -0
- package/docs/README.md +71 -0
- package/docs/api-keys.md +184 -0
- package/docs/audit.md +198 -0
- package/docs/auth.md +196 -0
- package/docs/authorization.md +225 -0
- package/docs/billing.md +293 -0
- package/docs/cli.md +238 -0
- package/docs/config.md +163 -0
- package/docs/db.md +219 -0
- package/docs/email.md +222 -0
- package/docs/entitlements.md +194 -0
- package/docs/errors.md +158 -0
- package/docs/events.md +279 -0
- package/docs/feature-flags.md +237 -0
- package/docs/files.md +229 -0
- package/docs/jobs.md +288 -0
- package/docs/logger.md +269 -0
- package/docs/notifications.md +179 -0
- package/docs/publishing.md +128 -0
- package/docs/realtime.md +360 -0
- package/docs/storage.md +279 -0
- package/docs/streams.md +296 -0
- package/docs/users.md +117 -0
- package/docs/webhooks.md +272 -0
- package/docs/workspaces.md +269 -0
- package/drizzle/0000_init_users.sql +21 -0
- package/drizzle/0001_workspaces_roles_permissions.sql +86 -0
- package/drizzle/0002_billing_entitlements.sql +97 -0
- package/drizzle/0003_events.sql +17 -0
- package/drizzle/0004_notifications_audit.sql +64 -0
- package/drizzle/0005_api_keys_webhooks.sql +77 -0
- package/drizzle/0006_feature_flags.sql +22 -0
- package/drizzle/0007_files.sql +23 -0
- package/drizzle/meta/0000_snapshot.json +160 -0
- package/drizzle/meta/0001_snapshot.json +837 -0
- package/drizzle/meta/0002_snapshot.json +1580 -0
- package/drizzle/meta/0003_snapshot.json +1751 -0
- package/drizzle/meta/0004_snapshot.json +2109 -0
- package/drizzle/meta/0005_snapshot.json +2719 -0
- package/drizzle/meta/0006_snapshot.json +2909 -0
- package/drizzle/meta/0007_snapshot.json +3113 -0
- package/drizzle/meta/_journal.json +62 -0
- package/package.json +180 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public events surface: `@digimuza/saas-core/events`. Server-only.
|
|
3
|
+
*
|
|
4
|
+
* One durable record of everything that happens, typed at compile time and
|
|
5
|
+
* validated at runtime. Events are the substrate the later phases build on:
|
|
6
|
+
* realtime delivery, RxJS streams, notifications, audit logs, outgoing webhooks
|
|
7
|
+
* and durable jobs all read from here rather than inventing their own channel.
|
|
8
|
+
*
|
|
9
|
+
* Applications declare their own events and merge them with the core catalogue:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { z } from "zod";
|
|
13
|
+
* import {
|
|
14
|
+
* coreEvents, createEventsService, defineEvents, mergeEventRegistries,
|
|
15
|
+
* } from "@digimuza/saas-core/events";
|
|
16
|
+
* import { getDatabase } from "@digimuza/saas-core/db";
|
|
17
|
+
*
|
|
18
|
+
* const registry = mergeEventRegistries(
|
|
19
|
+
* coreEvents,
|
|
20
|
+
* defineEvents({ "project.created": z.object({ projectId: z.uuid() }) }),
|
|
21
|
+
* );
|
|
22
|
+
*
|
|
23
|
+
* export const events = createEventsService({ db: getDatabase(), registry });
|
|
24
|
+
*
|
|
25
|
+
* await events.publish({
|
|
26
|
+
* type: "project.created", // autocompleted
|
|
27
|
+
* workspaceId: context.workspaceId,
|
|
28
|
+
* actorId: user.id,
|
|
29
|
+
* data: { projectId }, // narrowed to this event's schema
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Subscribers registered with `on()` run **in process** and are not durable: a
|
|
34
|
+
* crash between the commit and the handler loses the reaction, not the event.
|
|
35
|
+
* That is deliberate — durable reactions belong in Trigger.dev, reading the
|
|
36
|
+
* persisted row. Never keep state that must survive a restart in a handler.
|
|
37
|
+
*/
|
|
38
|
+
import { getDatabase } from "../db/client.js";
|
|
39
|
+
import { coreEvents } from "./core-events.js";
|
|
40
|
+
import { createEventsService } from "./service.js";
|
|
41
|
+
export { coreEvents } from "./core-events.js";
|
|
42
|
+
export { defineEvents, mergeEventRegistries, } from "./registry.js";
|
|
43
|
+
export { createEventsService, } from "./service.js";
|
|
44
|
+
/**
|
|
45
|
+
* Default service over the core catalogue only.
|
|
46
|
+
*
|
|
47
|
+
* An application with its own events should build its own service from a merged
|
|
48
|
+
* registry — this one cannot publish types it does not know about. It exists so
|
|
49
|
+
* the core's own services have somewhere to emit without every consumer having
|
|
50
|
+
* to wire a registry first.
|
|
51
|
+
*/
|
|
52
|
+
export const events = createEventsService({
|
|
53
|
+
get db() {
|
|
54
|
+
return getDatabase();
|
|
55
|
+
},
|
|
56
|
+
registry: coreEvents,
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/events/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAyB,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAsB,MAAM,cAAc,CAAC;AAEvE,OAAO,EAAE,UAAU,EAAyB,MAAM,kBAAkB,CAAC;AACrE,OAAO,EACL,YAAY,EACZ,oBAAoB,GAWrB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,mBAAmB,GAMpB,MAAM,cAAc,CAAC;AAEtB;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,MAAM,GAAoC,mBAAmB,CAAC;IACzE,IAAI,EAAE;QACJ,OAAO,WAAW,EAAE,CAAC;IACvB,CAAC;IACD,QAAQ,EAAE,UAAU;CACrB,CAAC,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { CoreDatabase } from "../db/types.ts";
|
|
2
|
+
import type { CoreEventSchemas } from "./core-events.ts";
|
|
3
|
+
import type { PublishInput } from "./registry.ts";
|
|
4
|
+
import type { EventsService } from "./service.ts";
|
|
5
|
+
/**
|
|
6
|
+
* The slice of the events service that core services need.
|
|
7
|
+
*
|
|
8
|
+
* Narrowed to publishing and dispatch so that `workspaces`, `authorization` and
|
|
9
|
+
* `billing` do not depend on the full generic service — and so an application
|
|
10
|
+
* can pass a service built from a *merged* registry, which is a supertype of the
|
|
11
|
+
* core catalogue, without any of them knowing about the extra event types.
|
|
12
|
+
*/
|
|
13
|
+
export type CoreEventPublisher = Pick<EventsService<CoreEventSchemas>, "record" | "dispatch">;
|
|
14
|
+
/**
|
|
15
|
+
* Collects events written inside a transaction, to be fanned out after it
|
|
16
|
+
* commits.
|
|
17
|
+
*
|
|
18
|
+
* This is the transactional outbox pattern. Writing the event in the same
|
|
19
|
+
* transaction as the state change means the two can never disagree: no state
|
|
20
|
+
* change without its event, and no event for a change that rolled back.
|
|
21
|
+
* Dispatching is deferred because in-process handlers would otherwise observe
|
|
22
|
+
* uncommitted work.
|
|
23
|
+
*/
|
|
24
|
+
export declare class EventOutbox {
|
|
25
|
+
private readonly publisher;
|
|
26
|
+
private readonly pending;
|
|
27
|
+
constructor(publisher: CoreEventPublisher | undefined);
|
|
28
|
+
/** True when no publisher is configured, so callers can skip the work. */
|
|
29
|
+
get enabled(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Record an event inside `tx`. No-op when events are not configured.
|
|
32
|
+
*
|
|
33
|
+
* Pass the transaction the state change is using, not the pooled handle — the
|
|
34
|
+
* whole point is that the two commit together.
|
|
35
|
+
*/
|
|
36
|
+
record(input: PublishInput<CoreEventSchemas>, tx: CoreDatabase): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Fan out everything recorded. Call only after the transaction has committed.
|
|
39
|
+
*
|
|
40
|
+
* Never throws: dispatch failures are reported through the events service's
|
|
41
|
+
* own error sink, because the state change and its event are already durable
|
|
42
|
+
* and a subscriber problem must not surface as a failed operation.
|
|
43
|
+
*/
|
|
44
|
+
flush(): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=publisher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publisher.d.ts","sourceRoot":"","sources":["../../src/events/publisher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAa,MAAM,eAAe,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC,CAAC;AAE9F;;;;;;;;;GASG;AACH,qBAAa,WAAW;IAGV,OAAO,CAAC,QAAQ,CAAC,SAAS;IAFtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAE7D,YAA6B,SAAS,EAAE,kBAAkB,GAAG,SAAS,EAAI;IAE1E,0EAA0E;IAC1E,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;;;OAKG;IACG,MAAM,CAAC,KAAK,EAAE,YAAY,CAAC,gBAAgB,CAAC,EAAE,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAWnF;IAED;;;;;;OAMG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAS3B;CACF"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collects events written inside a transaction, to be fanned out after it
|
|
3
|
+
* commits.
|
|
4
|
+
*
|
|
5
|
+
* This is the transactional outbox pattern. Writing the event in the same
|
|
6
|
+
* transaction as the state change means the two can never disagree: no state
|
|
7
|
+
* change without its event, and no event for a change that rolled back.
|
|
8
|
+
* Dispatching is deferred because in-process handlers would otherwise observe
|
|
9
|
+
* uncommitted work.
|
|
10
|
+
*/
|
|
11
|
+
export class EventOutbox {
|
|
12
|
+
publisher;
|
|
13
|
+
pending = [];
|
|
14
|
+
constructor(publisher) {
|
|
15
|
+
this.publisher = publisher;
|
|
16
|
+
}
|
|
17
|
+
/** True when no publisher is configured, so callers can skip the work. */
|
|
18
|
+
get enabled() {
|
|
19
|
+
return this.publisher !== undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Record an event inside `tx`. No-op when events are not configured.
|
|
23
|
+
*
|
|
24
|
+
* Pass the transaction the state change is using, not the pooled handle — the
|
|
25
|
+
* whole point is that the two commit together.
|
|
26
|
+
*/
|
|
27
|
+
async record(input, tx) {
|
|
28
|
+
if (this.publisher === undefined) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const { event, created } = await this.publisher.record(input, tx);
|
|
32
|
+
// A deduplicated event was already dispatched when first recorded.
|
|
33
|
+
if (created) {
|
|
34
|
+
this.pending.push(event);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Fan out everything recorded. Call only after the transaction has committed.
|
|
39
|
+
*
|
|
40
|
+
* Never throws: dispatch failures are reported through the events service's
|
|
41
|
+
* own error sink, because the state change and its event are already durable
|
|
42
|
+
* and a subscriber problem must not surface as a failed operation.
|
|
43
|
+
*/
|
|
44
|
+
async flush() {
|
|
45
|
+
if (this.publisher === undefined || this.pending.length === 0) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const events = this.pending.splice(0, this.pending.length);
|
|
49
|
+
for (const event of events) {
|
|
50
|
+
await this.publisher.dispatch(event);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=publisher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publisher.js","sourceRoot":"","sources":["../../src/events/publisher.ts"],"names":[],"mappings":"AAeA;;;;;;;;;GASG;AACH,MAAM,OAAO,WAAW;IAGO,SAAS;IAFrB,OAAO,GAAkC,EAAE,CAAC;IAE7D,YAA6B,SAAyC;yBAAzC,SAAS;IAAmC,CAAC;IAE1E,0EAA0E;IAC1E,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC;IACtC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,MAAM,CAAC,KAAqC,EAAE,EAAgB;QAClE,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YACjC,OAAO;QACT,CAAC;QAED,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAElE,mEAAmE;QACnE,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9D,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC3D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* A typed event catalogue.
|
|
4
|
+
*
|
|
5
|
+
* Each event type is paired with a Zod schema, which does double duty: `z.infer`
|
|
6
|
+
* gives the compile-time payload type, and the same schema validates at runtime
|
|
7
|
+
* before anything is written. A generic `Record<string, unknown>` payload would
|
|
8
|
+
* give neither, and a hand-written TypeScript interface would give only the
|
|
9
|
+
* first — leaving the database to accept whatever a caller happened to pass.
|
|
10
|
+
*/
|
|
11
|
+
export type EventSchemaMap = Record<string, z.ZodType>;
|
|
12
|
+
export interface EventRegistry<S extends EventSchemaMap = EventSchemaMap> {
|
|
13
|
+
readonly schemas: S;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Declare a set of event types.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* export const appEvents = defineEvents({
|
|
20
|
+
* "project.created": z.object({ projectId: z.uuid() }),
|
|
21
|
+
* "project.deleted": z.object({ projectId: z.uuid() }),
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* The `const` type parameter preserves the literal keys, which is what makes
|
|
26
|
+
* `publish({ type: "project.created", ... })` autocomplete and narrow `data`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function defineEvents<const S extends EventSchemaMap>(schemas: S): EventRegistry<S>;
|
|
29
|
+
/**
|
|
30
|
+
* Combine the core catalogue with an application's own.
|
|
31
|
+
*
|
|
32
|
+
* Duplicate keys throw rather than silently overriding. An application that
|
|
33
|
+
* redefined `workspace.member.created` with a different payload would make every
|
|
34
|
+
* core consumer of that event wrong, and the failure would surface far from the
|
|
35
|
+
* cause.
|
|
36
|
+
*/
|
|
37
|
+
export declare function mergeEventRegistries<A extends EventSchemaMap, B extends EventSchemaMap>(first: EventRegistry<A>, second: EventRegistry<B>): EventRegistry<A & B>;
|
|
38
|
+
/** Every event type in a registry, as a string literal union. */
|
|
39
|
+
export type EventTypeOf<S extends EventSchemaMap> = keyof S & string;
|
|
40
|
+
/** The validated payload for one event type. */
|
|
41
|
+
export type EventDataOf<S extends EventSchemaMap, K extends EventTypeOf<S>> = z.output<S[K]>;
|
|
42
|
+
/**
|
|
43
|
+
* What a caller passes to `publish`.
|
|
44
|
+
*
|
|
45
|
+
* A union of one variant per event type rather than `{ type: string; data:
|
|
46
|
+
* unknown }`, so `data` is checked against the schema for the `type` given in
|
|
47
|
+
* the same object literal.
|
|
48
|
+
*/
|
|
49
|
+
export type PublishInput<S extends EventSchemaMap> = {
|
|
50
|
+
[K in EventTypeOf<S>]: {
|
|
51
|
+
readonly type: K;
|
|
52
|
+
readonly data: z.input<S[K]>;
|
|
53
|
+
/**
|
|
54
|
+
* Tenant the event belongs to.
|
|
55
|
+
*
|
|
56
|
+
* Must originate from a verified {@link WorkspaceContext}; prefer
|
|
57
|
+
* `events.scoped(context)`, which binds it for you.
|
|
58
|
+
*/
|
|
59
|
+
readonly workspaceId?: string | null;
|
|
60
|
+
/** The acting user. Resolve from the session, never from a request body. */
|
|
61
|
+
readonly actorId?: string | null;
|
|
62
|
+
/** Deduplication key, scoped to this event type. */
|
|
63
|
+
readonly idempotencyKey?: string;
|
|
64
|
+
/** When it happened, if that differs from now. */
|
|
65
|
+
readonly occurredAt?: Date;
|
|
66
|
+
};
|
|
67
|
+
}[EventTypeOf<S>];
|
|
68
|
+
/** A persisted event, narrowed by `type`. */
|
|
69
|
+
export type SaasEvent<S extends EventSchemaMap = EventSchemaMap> = {
|
|
70
|
+
[K in EventTypeOf<S>]: {
|
|
71
|
+
readonly id: string;
|
|
72
|
+
readonly type: K;
|
|
73
|
+
readonly data: z.output<S[K]>;
|
|
74
|
+
readonly workspaceId: string | null;
|
|
75
|
+
readonly actorId: string | null;
|
|
76
|
+
readonly occurredAt: Date;
|
|
77
|
+
};
|
|
78
|
+
}[EventTypeOf<S>];
|
|
79
|
+
/** A single event type's variant of {@link SaasEvent}. */
|
|
80
|
+
export type SaasEventOf<S extends EventSchemaMap, K extends EventTypeOf<S>> = Extract<SaasEvent<S>, {
|
|
81
|
+
type: K;
|
|
82
|
+
}>;
|
|
83
|
+
export type EventHandler<S extends EventSchemaMap, K extends EventTypeOf<S>> = (event: SaasEventOf<S, K>) => void | Promise<void>;
|
|
84
|
+
export type AnyEventHandler<S extends EventSchemaMap> = (event: SaasEvent<S>) => void | Promise<void>;
|
|
85
|
+
/** Removes a subscription. Every `on`/`onAny` call returns one. */
|
|
86
|
+
export type Unsubscribe = () => void;
|
|
87
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/events/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { InvalidInputError } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Declare a set of event types.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* export const appEvents = defineEvents({
|
|
7
|
+
* "project.created": z.object({ projectId: z.uuid() }),
|
|
8
|
+
* "project.deleted": z.object({ projectId: z.uuid() }),
|
|
9
|
+
* });
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* The `const` type parameter preserves the literal keys, which is what makes
|
|
13
|
+
* `publish({ type: "project.created", ... })` autocomplete and narrow `data`.
|
|
14
|
+
*/
|
|
15
|
+
export function defineEvents(schemas) {
|
|
16
|
+
return { schemas };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Combine the core catalogue with an application's own.
|
|
20
|
+
*
|
|
21
|
+
* Duplicate keys throw rather than silently overriding. An application that
|
|
22
|
+
* redefined `workspace.member.created` with a different payload would make every
|
|
23
|
+
* core consumer of that event wrong, and the failure would surface far from the
|
|
24
|
+
* cause.
|
|
25
|
+
*/
|
|
26
|
+
export function mergeEventRegistries(first, second) {
|
|
27
|
+
const collisions = Object.keys(second.schemas).filter((key) => key in first.schemas);
|
|
28
|
+
if (collisions.length > 0) {
|
|
29
|
+
throw new InvalidInputError(`Event types already defined: ${collisions.join(", ")}. ` +
|
|
30
|
+
`Rename the application's events rather than redefining core ones.`);
|
|
31
|
+
}
|
|
32
|
+
return { schemas: { ...first.schemas, ...second.schemas } };
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/events/registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAiBjD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAiC,OAAU;IACrE,OAAO,EAAE,OAAO,EAAE,CAAC;AACrB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAuB,EACvB,MAAwB;IAExB,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAErF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,iBAAiB,CACzB,gCAAgC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YACvD,mEAAmE,CACtE,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { isUniqueViolation } from "../db/errors.ts";
|
|
2
|
+
import type { CoreDatabase } from "../db/types.ts";
|
|
3
|
+
import type { WorkspaceContext } from "../workspaces/context.ts";
|
|
4
|
+
import type { AnyEventHandler, EventHandler, EventRegistry, EventSchemaMap, EventTypeOf, PublishInput, SaasEvent, Unsubscribe } from "./registry.ts";
|
|
5
|
+
/** Outcome of persisting an event. */
|
|
6
|
+
export interface RecordResult<S extends EventSchemaMap> {
|
|
7
|
+
readonly event: SaasEvent<S>;
|
|
8
|
+
/**
|
|
9
|
+
* False when an event with the same type and idempotency key already existed.
|
|
10
|
+
*
|
|
11
|
+
* Callers must not dispatch a non-created event: it was already fanned out
|
|
12
|
+
* when it was first recorded, and doing it again would make every handler run
|
|
13
|
+
* twice for one real occurrence.
|
|
14
|
+
*/
|
|
15
|
+
readonly created: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface ListEventsOptions {
|
|
18
|
+
readonly types?: readonly string[];
|
|
19
|
+
readonly limit?: number;
|
|
20
|
+
/** Return events strictly older than this, for keyset pagination. */
|
|
21
|
+
readonly before?: Date;
|
|
22
|
+
readonly actorId?: string;
|
|
23
|
+
}
|
|
24
|
+
/** A publisher with the workspace already bound. */
|
|
25
|
+
export interface ScopedPublisher<S extends EventSchemaMap> {
|
|
26
|
+
publish(input: Omit<PublishInput<S>, "workspaceId"> & {
|
|
27
|
+
readonly actorId?: string | null;
|
|
28
|
+
}): Promise<SaasEvent<S>>;
|
|
29
|
+
record(input: Omit<PublishInput<S>, "workspaceId"> & {
|
|
30
|
+
readonly actorId?: string | null;
|
|
31
|
+
}, tx?: CoreDatabase): Promise<RecordResult<S>>;
|
|
32
|
+
}
|
|
33
|
+
export interface EventsService<S extends EventSchemaMap> {
|
|
34
|
+
/**
|
|
35
|
+
* Validate, persist and fan out an event.
|
|
36
|
+
*
|
|
37
|
+
* The row is committed before handlers run, so an event is never lost because
|
|
38
|
+
* a subscriber failed. A deduplicated event is returned without being
|
|
39
|
+
* dispatched again.
|
|
40
|
+
*/
|
|
41
|
+
publish(input: PublishInput<S>): Promise<SaasEvent<S>>;
|
|
42
|
+
/**
|
|
43
|
+
* Validate and persist without dispatching.
|
|
44
|
+
*
|
|
45
|
+
* Pass the transaction your state change is using, so the event and the change
|
|
46
|
+
* commit together — no state change without its event, and no event for a
|
|
47
|
+
* change that rolled back. Dispatch after the commit, and only when `created`
|
|
48
|
+
* is true.
|
|
49
|
+
*/
|
|
50
|
+
record(input: PublishInput<S>, tx?: CoreDatabase): Promise<RecordResult<S>>;
|
|
51
|
+
/** Fan out an already-persisted event. Use after committing a transaction. */
|
|
52
|
+
dispatch(event: SaasEvent<S>): Promise<void>;
|
|
53
|
+
/** Subscribe to one event type. */
|
|
54
|
+
on<K extends EventTypeOf<S>>(type: K, handler: EventHandler<S, K>): Unsubscribe;
|
|
55
|
+
/** Subscribe to every event. */
|
|
56
|
+
onAny(handler: AnyEventHandler<S>): Unsubscribe;
|
|
57
|
+
/** Recent events for a workspace, newest first. */
|
|
58
|
+
list(context: WorkspaceContext, options?: ListEventsOptions): Promise<SaasEvent<S>[]>;
|
|
59
|
+
/** Bind a verified workspace so callers cannot pass a different one. */
|
|
60
|
+
scoped(context: WorkspaceContext): ScopedPublisher<S>;
|
|
61
|
+
}
|
|
62
|
+
export interface EventsServiceDeps<S extends EventSchemaMap> {
|
|
63
|
+
readonly db: CoreDatabase;
|
|
64
|
+
readonly registry: EventRegistry<S>;
|
|
65
|
+
/**
|
|
66
|
+
* Where subscriber failures go.
|
|
67
|
+
*
|
|
68
|
+
* Handlers must not be able to fail a publish: the event has happened and is
|
|
69
|
+
* committed, so a broken subscriber is a separate operational problem. Without
|
|
70
|
+
* a sink those failures would vanish, so one is provided by default that logs.
|
|
71
|
+
*/
|
|
72
|
+
readonly onHandlerError?: (error: unknown, event: SaasEvent<S>) => void;
|
|
73
|
+
readonly now?: () => Date;
|
|
74
|
+
}
|
|
75
|
+
export declare function createEventsService<S extends EventSchemaMap>(deps: EventsServiceDeps<S>): EventsService<S>;
|
|
76
|
+
/** Re-exported so callers can narrow a caught publish failure. */
|
|
77
|
+
export { isUniqueViolation };
|
|
78
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/events/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { and, desc, eq, inArray, isNotNull, lt } from "drizzle-orm";
|
|
2
|
+
import { InvalidInputError } from "../errors.js";
|
|
3
|
+
import { isUniqueViolation } from "../db/errors.js";
|
|
4
|
+
import { getDefaultLogger } from "../logger/default.js";
|
|
5
|
+
import { events as eventsTable } from "../db/schema/index.js";
|
|
6
|
+
const DEFAULT_LIST_LIMIT = 50;
|
|
7
|
+
const MAX_LIST_LIMIT = 500;
|
|
8
|
+
export function createEventsService(deps) {
|
|
9
|
+
const now = deps.now ?? (() => new Date());
|
|
10
|
+
const typed = new Map();
|
|
11
|
+
const wildcard = new Set();
|
|
12
|
+
const reportHandlerError = deps.onHandlerError ??
|
|
13
|
+
((error, event) => {
|
|
14
|
+
getDefaultLogger().error("event handler failed", {
|
|
15
|
+
eventType: event.type,
|
|
16
|
+
eventId: event.id,
|
|
17
|
+
workspaceId: event.workspaceId,
|
|
18
|
+
error,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
function validate(input) {
|
|
22
|
+
const schema = deps.registry.schemas[input.type];
|
|
23
|
+
if (schema === undefined) {
|
|
24
|
+
/**
|
|
25
|
+
* Reachable only from untyped JavaScript, but worth catching: an unknown
|
|
26
|
+
* type would otherwise be persisted with an unvalidated payload and reach
|
|
27
|
+
* consumers that cannot interpret it.
|
|
28
|
+
*/
|
|
29
|
+
throw new InvalidInputError(`Unknown event type "${String(input.type)}".`);
|
|
30
|
+
}
|
|
31
|
+
const parsed = schema.safeParse(input.data);
|
|
32
|
+
if (!parsed.success) {
|
|
33
|
+
const problems = parsed.error.issues
|
|
34
|
+
.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`)
|
|
35
|
+
.join("; ");
|
|
36
|
+
throw new InvalidInputError(`Invalid payload for event "${input.type}": ${problems}`);
|
|
37
|
+
}
|
|
38
|
+
return parsed.data;
|
|
39
|
+
}
|
|
40
|
+
const service = {
|
|
41
|
+
async publish(input) {
|
|
42
|
+
const { event, created } = await service.record(input);
|
|
43
|
+
/**
|
|
44
|
+
* Already recorded means already dispatched. Fanning out again would make
|
|
45
|
+
* every handler run twice for a single real occurrence.
|
|
46
|
+
*/
|
|
47
|
+
if (created) {
|
|
48
|
+
await service.dispatch(event);
|
|
49
|
+
}
|
|
50
|
+
return event;
|
|
51
|
+
},
|
|
52
|
+
async record(input, tx) {
|
|
53
|
+
const data = validate(input);
|
|
54
|
+
const db = tx ?? deps.db;
|
|
55
|
+
const occurredAt = input.occurredAt ?? now();
|
|
56
|
+
const inserted = await db
|
|
57
|
+
.insert(eventsTable)
|
|
58
|
+
.values({
|
|
59
|
+
type: input.type,
|
|
60
|
+
workspaceId: input.workspaceId ?? null,
|
|
61
|
+
actorUserId: input.actorId ?? null,
|
|
62
|
+
data: data,
|
|
63
|
+
occurredAt,
|
|
64
|
+
idempotencyKey: input.idempotencyKey ?? null,
|
|
65
|
+
})
|
|
66
|
+
/**
|
|
67
|
+
* A retried operation reuses its idempotency key. Returning nothing on
|
|
68
|
+
* conflict lets us fetch and return the original event, so the caller
|
|
69
|
+
* sees the same identity either way and no duplicate is dispatched.
|
|
70
|
+
*
|
|
71
|
+
* The `where` predicate is required, not decorative: `events_idempotency_idx` is
|
|
72
|
+
* a *partial* unique index, and PostgreSQL refuses to infer a partial
|
|
73
|
+
* index as the conflict arbiter unless the predicate matches. Without it
|
|
74
|
+
* every insert fails with "no unique or exclusion constraint matching
|
|
75
|
+
* the ON CONFLICT specification".
|
|
76
|
+
*/
|
|
77
|
+
.onConflictDoNothing({
|
|
78
|
+
target: [eventsTable.type, eventsTable.idempotencyKey],
|
|
79
|
+
where: isNotNull(eventsTable.idempotencyKey),
|
|
80
|
+
})
|
|
81
|
+
.returning();
|
|
82
|
+
let row = inserted[0];
|
|
83
|
+
if (row === undefined) {
|
|
84
|
+
if (input.idempotencyKey === undefined) {
|
|
85
|
+
// No key means no conflict was possible; something else went wrong.
|
|
86
|
+
throw new Error(`Failed to record event "${input.type}".`);
|
|
87
|
+
}
|
|
88
|
+
const existing = await db
|
|
89
|
+
.select()
|
|
90
|
+
.from(eventsTable)
|
|
91
|
+
.where(and(eq(eventsTable.type, input.type), eq(eventsTable.idempotencyKey, input.idempotencyKey)))
|
|
92
|
+
.limit(1);
|
|
93
|
+
row = existing[0];
|
|
94
|
+
if (row === undefined) {
|
|
95
|
+
throw new Error(`Failed to record event "${input.type}".`);
|
|
96
|
+
}
|
|
97
|
+
return { event: toEvent(row), created: false };
|
|
98
|
+
}
|
|
99
|
+
return { event: toEvent(row), created: true };
|
|
100
|
+
},
|
|
101
|
+
async dispatch(event) {
|
|
102
|
+
const handlers = [...(typed.get(event.type) ?? []), ...wildcard];
|
|
103
|
+
if (handlers.length === 0) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Run concurrently and settle every handler. Sequential dispatch would let
|
|
108
|
+
* a slow subscriber delay the rest, and a rejection would skip those after
|
|
109
|
+
* it — so each failure is reported and the others still run.
|
|
110
|
+
*/
|
|
111
|
+
const results = await Promise.allSettled(handlers.map(async (handler) => handler(event)));
|
|
112
|
+
for (const result of results) {
|
|
113
|
+
if (result.status === "rejected") {
|
|
114
|
+
reportHandlerError(result.reason, event);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
on(type, handler) {
|
|
119
|
+
let handlers = typed.get(type);
|
|
120
|
+
if (handlers === undefined) {
|
|
121
|
+
handlers = new Set();
|
|
122
|
+
typed.set(type, handlers);
|
|
123
|
+
}
|
|
124
|
+
const erased = handler;
|
|
125
|
+
handlers.add(erased);
|
|
126
|
+
return () => {
|
|
127
|
+
handlers.delete(erased);
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
onAny(handler) {
|
|
131
|
+
wildcard.add(handler);
|
|
132
|
+
return () => {
|
|
133
|
+
wildcard.delete(handler);
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
async list(context, options = {}) {
|
|
137
|
+
const limit = Math.min(options.limit ?? DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT);
|
|
138
|
+
const filters = [
|
|
139
|
+
// Always scoped to the verified workspace; an event feed is tenant data.
|
|
140
|
+
eq(eventsTable.workspaceId, context.workspaceId),
|
|
141
|
+
];
|
|
142
|
+
if (options.types !== undefined && options.types.length > 0) {
|
|
143
|
+
filters.push(inArray(eventsTable.type, [...options.types]));
|
|
144
|
+
}
|
|
145
|
+
if (options.before !== undefined) {
|
|
146
|
+
filters.push(lt(eventsTable.occurredAt, options.before));
|
|
147
|
+
}
|
|
148
|
+
if (options.actorId !== undefined) {
|
|
149
|
+
filters.push(eq(eventsTable.actorUserId, options.actorId));
|
|
150
|
+
}
|
|
151
|
+
const rows = await deps.db
|
|
152
|
+
.select()
|
|
153
|
+
.from(eventsTable)
|
|
154
|
+
.where(and(...filters))
|
|
155
|
+
// Tie-break on id so keyset pagination cannot loop on equal timestamps.
|
|
156
|
+
.orderBy(desc(eventsTable.occurredAt), desc(eventsTable.id))
|
|
157
|
+
.limit(limit);
|
|
158
|
+
return rows.map((row) => toEvent(row));
|
|
159
|
+
},
|
|
160
|
+
scoped(context) {
|
|
161
|
+
const bind = (input) => ({ ...input, workspaceId: context.workspaceId });
|
|
162
|
+
return {
|
|
163
|
+
publish: (input) => service.publish(bind(input)),
|
|
164
|
+
record: (input, tx) => service.record(bind(input), tx),
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
return service;
|
|
169
|
+
}
|
|
170
|
+
function toEvent(row) {
|
|
171
|
+
return {
|
|
172
|
+
id: row.id,
|
|
173
|
+
type: row.type,
|
|
174
|
+
data: row.data,
|
|
175
|
+
workspaceId: row.workspaceId,
|
|
176
|
+
actorId: row.actorUserId,
|
|
177
|
+
occurredAt: row.occurredAt,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/** Re-exported so callers can narrow a caught publish failure. */
|
|
181
|
+
export { isUniqueViolation };
|
|
182
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/events/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic bucketing for percentage rollouts.
|
|
3
|
+
*
|
|
4
|
+
* A flag that flickered between requests would be worse than no flag — a user
|
|
5
|
+
* would see the new editor, reload, and lose their work. So the bucket is a pure
|
|
6
|
+
* function of the flag key and the subject: stable across requests, processes and
|
|
7
|
+
* deploys, with no state to store.
|
|
8
|
+
*
|
|
9
|
+
* Salted with the key so a subject in the first 10% of one flag is not
|
|
10
|
+
* automatically in the first 10% of every other — otherwise the same unlucky
|
|
11
|
+
* cohort would receive every experiment.
|
|
12
|
+
*/
|
|
13
|
+
export declare function bucketOf(key: string, subjectId: string): number;
|
|
14
|
+
/**
|
|
15
|
+
* Whether a subject falls inside a rollout.
|
|
16
|
+
*
|
|
17
|
+
* With no subject there is nothing stable to hash, so anything short of 100 is
|
|
18
|
+
* off: guessing would make the flag flicker, which is the one behaviour worth
|
|
19
|
+
* ruling out.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isInRollout(key: string, percentage: number, subjectId: string | undefined): boolean;
|
|
22
|
+
//# sourceMappingURL=bucket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bucket.d.ts","sourceRoot":"","sources":["../../src/features/bucket.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAS/D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAYT"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic bucketing for percentage rollouts.
|
|
4
|
+
*
|
|
5
|
+
* A flag that flickered between requests would be worse than no flag — a user
|
|
6
|
+
* would see the new editor, reload, and lose their work. So the bucket is a pure
|
|
7
|
+
* function of the flag key and the subject: stable across requests, processes and
|
|
8
|
+
* deploys, with no state to store.
|
|
9
|
+
*
|
|
10
|
+
* Salted with the key so a subject in the first 10% of one flag is not
|
|
11
|
+
* automatically in the first 10% of every other — otherwise the same unlucky
|
|
12
|
+
* cohort would receive every experiment.
|
|
13
|
+
*/
|
|
14
|
+
export function bucketOf(key, subjectId) {
|
|
15
|
+
const digest = createHash("sha256").update(`${key}:${subjectId}`).digest();
|
|
16
|
+
/**
|
|
17
|
+
* 32 bits mapped onto 0-99. Modulo bias over 100 buckets from a 2^32 range is
|
|
18
|
+
* about one part in forty million — irrelevant next to the sample sizes any
|
|
19
|
+
* rollout actually involves.
|
|
20
|
+
*/
|
|
21
|
+
return digest.readUInt32BE(0) % 100;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Whether a subject falls inside a rollout.
|
|
25
|
+
*
|
|
26
|
+
* With no subject there is nothing stable to hash, so anything short of 100 is
|
|
27
|
+
* off: guessing would make the flag flicker, which is the one behaviour worth
|
|
28
|
+
* ruling out.
|
|
29
|
+
*/
|
|
30
|
+
export function isInRollout(key, percentage, subjectId) {
|
|
31
|
+
if (percentage >= 100) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
if (percentage <= 0) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (subjectId === undefined) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return bucketOf(key, subjectId) < percentage;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=bucket.js.map
|