@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 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/email/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AAE3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,GAG1B,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,aAAa,GAKd,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,oBAAoB,EAA6B,MAAM,aAAa,CAAC;AAE9E,OAAO,EACL,kBAAkB,GAKnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,sBAAsB,GAMvB,MAAM,gBAAgB,CAAC;AAExB;;;;;;GAMG;AACH,MAAM,UAAU,6BAA6B;IAC3C,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC;IAEjC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,IAAI,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACzE,OAAO,0BAA0B,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,OAAO,oBAAoB,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,qCAAqC;AACrC,MAAM,UAAU,gBAAgB;IAC9B,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC;IACjC,OAAO;QACL,OAAO,EAAE,WAAW,CAAC,WAAW;QAChC,GAAG,CAAC,WAAW,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9E,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seam between the core and an email provider.
|
|
3
|
+
*
|
|
4
|
+
* One method, because sending is all the core needs. Bounce handling, suppression
|
|
5
|
+
* lists and analytics are the provider's dashboard, not this interface — wrapping
|
|
6
|
+
* them would mean maintaining a lowest common denominator of every provider's
|
|
7
|
+
* feature set.
|
|
8
|
+
*/
|
|
9
|
+
export interface EmailProvider {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
send(message: OutgoingEmail): Promise<SentEmail>;
|
|
12
|
+
}
|
|
13
|
+
export interface EmailAddress {
|
|
14
|
+
readonly address: string;
|
|
15
|
+
readonly name?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface OutgoingEmail {
|
|
18
|
+
readonly to: readonly EmailAddress[];
|
|
19
|
+
readonly from: EmailAddress;
|
|
20
|
+
readonly subject: string;
|
|
21
|
+
/**
|
|
22
|
+
* Always present. HTML is optional, but a text part is not: some clients render
|
|
23
|
+
* only text, and a text-less message scores worse with spam filters.
|
|
24
|
+
*/
|
|
25
|
+
readonly text: string;
|
|
26
|
+
readonly html?: string;
|
|
27
|
+
readonly replyTo?: EmailAddress;
|
|
28
|
+
readonly cc?: readonly EmailAddress[];
|
|
29
|
+
readonly bcc?: readonly EmailAddress[];
|
|
30
|
+
/**
|
|
31
|
+
* Provider-side deduplication key, where supported.
|
|
32
|
+
*
|
|
33
|
+
* Sending is not transactional with the database, so a retried request can send
|
|
34
|
+
* twice. This is the only defence against that, and providers that ignore it
|
|
35
|
+
* will simply deliver twice.
|
|
36
|
+
*/
|
|
37
|
+
readonly idempotencyKey?: string;
|
|
38
|
+
/** Custom headers, e.g. `List-Unsubscribe`. */
|
|
39
|
+
readonly headers?: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
export interface SentEmail {
|
|
42
|
+
/** Provider's message id, for correlating with their logs. */
|
|
43
|
+
readonly id: string;
|
|
44
|
+
readonly provider: string;
|
|
45
|
+
}
|
|
46
|
+
export declare function formatAddress(address: EmailAddress): string;
|
|
47
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/email/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,SAAS,YAAY,EAAE,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACtC,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,SAAS;IACxB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAK3D"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function formatAddress(address) {
|
|
2
|
+
// Quoted so a display name containing a comma cannot split the header.
|
|
3
|
+
return address.name === undefined
|
|
4
|
+
? address.address
|
|
5
|
+
: `"${address.name.replace(/"/g, "")}" <${address.address}>`;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/email/provider.ts"],"names":[],"mappings":"AAiDA,MAAM,UAAU,aAAa,CAAC,OAAqB;IACjD,uEAAuE;IACvE,OAAO,OAAO,CAAC,IAAI,KAAK,SAAS;QAC/B,CAAC,CAAC,OAAO,CAAC,OAAO;QACjB,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,OAAO,CAAC,OAAO,GAAG,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { EmailProvider } from "./provider.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Resend adapter.
|
|
4
|
+
*
|
|
5
|
+
* ## Verification status
|
|
6
|
+
*
|
|
7
|
+
* Written against Resend's documented `POST /emails` endpoint but **not exercised
|
|
8
|
+
* against live credentials**. Treat it as unverified until it has run against a
|
|
9
|
+
* real account.
|
|
10
|
+
*
|
|
11
|
+
* Resend is the default because its API is a single endpoint with no SDK required
|
|
12
|
+
* — which keeps this file small enough to audit and leaves nothing to go stale.
|
|
13
|
+
* Any other provider is a ~20-line adapter; see `docs/email.md`.
|
|
14
|
+
*/
|
|
15
|
+
export interface ResendAdapterOptions {
|
|
16
|
+
readonly apiKey: string;
|
|
17
|
+
/** Override for a proxy or a regional endpoint. */
|
|
18
|
+
readonly apiUrl?: string;
|
|
19
|
+
readonly fetchImpl?: typeof fetch;
|
|
20
|
+
}
|
|
21
|
+
export declare function createResendProvider(options: ResendAdapterOptions): EmailProvider;
|
|
22
|
+
//# sourceMappingURL=resend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resend.d.ts","sourceRoot":"","sources":["../../src/email/resend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAA4B,MAAM,eAAe,CAAC;AAG7E;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CACnC;AAID,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAsDjF"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { formatAddress } from "./provider.js";
|
|
2
|
+
const DEFAULT_API_URL = "https://api.resend.com";
|
|
3
|
+
export function createResendProvider(options) {
|
|
4
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
5
|
+
const baseUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/+$/, "");
|
|
6
|
+
return {
|
|
7
|
+
name: "resend",
|
|
8
|
+
async send(message) {
|
|
9
|
+
const headers = {
|
|
10
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
11
|
+
"Content-Type": "application/json",
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Resend reads idempotency from a header rather than the body, and honours
|
|
15
|
+
* it for 24 hours.
|
|
16
|
+
*/
|
|
17
|
+
if (message.idempotencyKey !== undefined) {
|
|
18
|
+
headers["Idempotency-Key"] = message.idempotencyKey;
|
|
19
|
+
}
|
|
20
|
+
const response = await fetchImpl(`${baseUrl}/emails`, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers,
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
from: formatAddress(message.from),
|
|
25
|
+
to: message.to.map(formatAddress),
|
|
26
|
+
subject: message.subject,
|
|
27
|
+
text: message.text,
|
|
28
|
+
...(message.html !== undefined ? { html: message.html } : {}),
|
|
29
|
+
...(message.replyTo !== undefined ? { reply_to: formatAddress(message.replyTo) } : {}),
|
|
30
|
+
...(message.cc !== undefined ? { cc: message.cc.map(formatAddress) } : {}),
|
|
31
|
+
...(message.bcc !== undefined ? { bcc: message.bcc.map(formatAddress) } : {}),
|
|
32
|
+
...(message.headers !== undefined ? { headers: message.headers } : {}),
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
/**
|
|
37
|
+
* The response body is not included. Provider errors echo request
|
|
38
|
+
* contents, and this message may reach logs — a bounced address is
|
|
39
|
+
* personal data and a rejected template could contain a token.
|
|
40
|
+
*/
|
|
41
|
+
throw new Error(`Resend rejected the message with status ${response.status}.`);
|
|
42
|
+
}
|
|
43
|
+
const body = (await response.json());
|
|
44
|
+
if (typeof body.id !== "string") {
|
|
45
|
+
throw new Error("Resend did not return a message id.");
|
|
46
|
+
}
|
|
47
|
+
return { id: body.id, provider: "resend" };
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=resend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resend.js","sourceRoot":"","sources":["../../src/email/resend.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAsB9C,MAAM,eAAe,GAAG,wBAAwB,CAAC;AAEjD,MAAM,UAAU,oBAAoB,CAAC,OAA6B;IAChE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,UAAU,CAAC,KAAK,CAAC;IACxD,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,eAAe,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAExE,OAAO;QACL,IAAI,EAAE,QAAQ;QAEd,KAAK,CAAC,IAAI,CAAC,OAAsB;YAC/B,MAAM,OAAO,GAA2B;gBACtC,aAAa,EAAE,UAAU,OAAO,CAAC,MAAM,EAAE;gBACzC,cAAc,EAAE,kBAAkB;aACnC,CAAC;YAEF;;;eAGG;YACH,IAAI,OAAO,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;gBACzC,OAAO,CAAC,iBAAiB,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC;YACtD,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,OAAO,SAAS,EAAE;gBACpD,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC;oBACjC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC;oBACjC,OAAO,EAAE,OAAO,CAAC,OAAO;oBACxB,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC7D,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACtF,GAAG,CAAC,OAAO,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC1E,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC7E,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACvE,CAAC;aACH,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB;;;;mBAIG;gBACH,MAAM,IAAI,KAAK,CAAC,2CAA2C,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YACjF,CAAC;YAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAqB,CAAC;YACzD,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;gBAChC,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;YACzD,CAAC;YAED,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;QAC7C,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type { EmailAddress, EmailProvider, SentEmail } from "./provider.ts";
|
|
3
|
+
import type { EmailTemplateMap, EmailTemplateName, EmailTemplateRegistry } from "./templates.ts";
|
|
4
|
+
export interface SendEmailInput {
|
|
5
|
+
readonly to: string | EmailAddress | readonly (string | EmailAddress)[];
|
|
6
|
+
readonly subject: string;
|
|
7
|
+
readonly text: string;
|
|
8
|
+
readonly html?: string;
|
|
9
|
+
/** Overrides the configured sender. */
|
|
10
|
+
readonly from?: EmailAddress;
|
|
11
|
+
readonly replyTo?: string | EmailAddress;
|
|
12
|
+
readonly cc?: readonly (string | EmailAddress)[];
|
|
13
|
+
readonly bcc?: readonly (string | EmailAddress)[];
|
|
14
|
+
readonly idempotencyKey?: string;
|
|
15
|
+
readonly headers?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
/** A send request for one of the registered templates, narrowed by name. */
|
|
18
|
+
export type SendTemplateInput<T extends EmailTemplateMap> = {
|
|
19
|
+
[K in EmailTemplateName<T>]: {
|
|
20
|
+
readonly to: string | EmailAddress | readonly (string | EmailAddress)[];
|
|
21
|
+
readonly template: K;
|
|
22
|
+
readonly data: z.input<T[K]["schema"]>;
|
|
23
|
+
readonly from?: EmailAddress;
|
|
24
|
+
readonly replyTo?: string | EmailAddress;
|
|
25
|
+
readonly idempotencyKey?: string;
|
|
26
|
+
readonly headers?: Record<string, string>;
|
|
27
|
+
};
|
|
28
|
+
}[EmailTemplateName<T>];
|
|
29
|
+
export interface EmailService<T extends EmailTemplateMap> {
|
|
30
|
+
/** Send an ad-hoc message. */
|
|
31
|
+
send(input: SendEmailInput): Promise<SentEmail>;
|
|
32
|
+
/**
|
|
33
|
+
* Render a registered template and send it.
|
|
34
|
+
*
|
|
35
|
+
* `data` is validated against the template's schema first, so a missing field
|
|
36
|
+
* fails before a message is built rather than producing an email containing
|
|
37
|
+
* "undefined".
|
|
38
|
+
*/
|
|
39
|
+
sendTemplate(input: SendTemplateInput<T>): Promise<SentEmail>;
|
|
40
|
+
/** Render without sending. For previews, and for asserting copy in tests. */
|
|
41
|
+
render<K extends EmailTemplateName<T>>(template: K, data: z.input<T[K]["schema"]>): {
|
|
42
|
+
subject: string;
|
|
43
|
+
text: string;
|
|
44
|
+
html?: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface EmailServiceDeps<T extends EmailTemplateMap> {
|
|
48
|
+
readonly provider: EmailProvider;
|
|
49
|
+
readonly templates: EmailTemplateRegistry<T>;
|
|
50
|
+
/** Default sender. Read per call, so configuration can resolve lazily. */
|
|
51
|
+
readonly from: EmailAddress;
|
|
52
|
+
readonly replyTo?: EmailAddress;
|
|
53
|
+
}
|
|
54
|
+
export declare function createEmailService<T extends EmailTemplateMap>(deps: EmailServiceDeps<T>): EmailService<T>;
|
|
55
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/email/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAiB,SAAS,EAAE,MAAM,eAAe,CAAC;AAC3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEjG,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC,MAAM,GAAG,YAAY,CAAC,EAAE,CAAC;IACxE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IACzC,QAAQ,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,YAAY,CAAC,EAAE,CAAC;IACjD,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,YAAY,CAAC,EAAE,CAAC;IAClD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAED,4EAA4E;AAC5E,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,gBAAgB,IAAI;KACzD,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC,GAAG;QAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC,MAAM,GAAG,YAAY,CAAC,EAAE,CAAC;QACxE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QACrB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC;QAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;QACzC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAC3C;CACF,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;AAExB,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,gBAAgB;IACtD,8BAA8B;IAC9B,IAAI,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhD;;;;;;OAMG;IACH,YAAY,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAE9D,6EAA6E;IAC7E,MAAM,CAAC,CAAC,SAAS,iBAAiB,CAAC,CAAC,CAAC,EACnC,QAAQ,EAAE,CAAC,EACX,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAC5B;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,gBAAgB;IAC1D,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC7C,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC;CACjC;AA2BD,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,gBAAgB,EAC3D,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,GACxB,YAAY,CAAC,CAAC,CAAC,CAyEjB"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { InvalidInputError } from "../errors.js";
|
|
2
|
+
function toAddress(value) {
|
|
3
|
+
return typeof value === "string" ? { address: value } : value;
|
|
4
|
+
}
|
|
5
|
+
function toAddresses(value) {
|
|
6
|
+
const list = Array.isArray(value) ? value : [value];
|
|
7
|
+
return list.map(toAddress);
|
|
8
|
+
}
|
|
9
|
+
/** Rudimentary shape check — the provider is authoritative about deliverability. */
|
|
10
|
+
function assertRecipients(recipients) {
|
|
11
|
+
if (recipients.length === 0) {
|
|
12
|
+
throw new InvalidInputError("At least one recipient is required.");
|
|
13
|
+
}
|
|
14
|
+
for (const recipient of recipients) {
|
|
15
|
+
if (!recipient.address.includes("@") || /\s/.test(recipient.address)) {
|
|
16
|
+
// The address is echoed because it came from the caller, not from a secret.
|
|
17
|
+
throw new InvalidInputError(`"${recipient.address}" is not a valid email address.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function createEmailService(deps) {
|
|
22
|
+
function lookup(name) {
|
|
23
|
+
const template = deps.templates.templates[name];
|
|
24
|
+
if (template === undefined) {
|
|
25
|
+
throw new InvalidInputError(`Unknown email template "${name}".`);
|
|
26
|
+
}
|
|
27
|
+
return template;
|
|
28
|
+
}
|
|
29
|
+
function renderTemplate(name, data) {
|
|
30
|
+
const template = lookup(name);
|
|
31
|
+
const parsed = template.schema.safeParse(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 data for email template "${name}": ${problems}`);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
subject: template.subject(parsed.data),
|
|
40
|
+
text: template.text(parsed.data),
|
|
41
|
+
...(template.html !== undefined ? { html: template.html(parsed.data) } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async function dispatch(message) {
|
|
45
|
+
assertRecipients(message.to);
|
|
46
|
+
return deps.provider.send(message);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
async send(input) {
|
|
50
|
+
return dispatch({
|
|
51
|
+
to: toAddresses(input.to),
|
|
52
|
+
from: input.from ?? deps.from,
|
|
53
|
+
subject: input.subject,
|
|
54
|
+
text: input.text,
|
|
55
|
+
...(input.html !== undefined ? { html: input.html } : {}),
|
|
56
|
+
...(input.replyTo !== undefined
|
|
57
|
+
? { replyTo: toAddress(input.replyTo) }
|
|
58
|
+
: deps.replyTo !== undefined
|
|
59
|
+
? { replyTo: deps.replyTo }
|
|
60
|
+
: {}),
|
|
61
|
+
...(input.cc !== undefined ? { cc: toAddresses(input.cc) } : {}),
|
|
62
|
+
...(input.bcc !== undefined ? { bcc: toAddresses(input.bcc) } : {}),
|
|
63
|
+
...(input.idempotencyKey !== undefined ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
64
|
+
...(input.headers !== undefined ? { headers: input.headers } : {}),
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
async sendTemplate(input) {
|
|
68
|
+
const rendered = renderTemplate(input.template, input.data);
|
|
69
|
+
return dispatch({
|
|
70
|
+
to: toAddresses(input.to),
|
|
71
|
+
from: input.from ?? deps.from,
|
|
72
|
+
...rendered,
|
|
73
|
+
...(input.replyTo !== undefined
|
|
74
|
+
? { replyTo: toAddress(input.replyTo) }
|
|
75
|
+
: deps.replyTo !== undefined
|
|
76
|
+
? { replyTo: deps.replyTo }
|
|
77
|
+
: {}),
|
|
78
|
+
...(input.idempotencyKey !== undefined ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
79
|
+
...(input.headers !== undefined ? { headers: input.headers } : {}),
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
render(template, data) {
|
|
83
|
+
return renderTemplate(template, data);
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/email/service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AA2DjD,SAAS,SAAS,CAAC,KAA4B;IAC7C,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AAChE,CAAC;AAED,SAAS,WAAW,CAClB,KAAiE;IAEjE,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAA8B,CAAC,CAAC;IAC7E,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAC7B,CAAC;AAED,oFAAoF;AACpF,SAAS,gBAAgB,CAAC,UAAmC;IAC3D,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,iBAAiB,CAAC,qCAAqC,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;YACrE,4EAA4E;YAC5E,MAAM,IAAI,iBAAiB,CAAC,IAAI,SAAS,CAAC,OAAO,iCAAiC,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,IAAyB;IAEzB,SAAS,MAAM,CAAC,IAAY;QAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAChD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,iBAAiB,CAAC,2BAA2B,IAAI,IAAI,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,SAAS,cAAc,CAAC,IAAY,EAAE,IAAa;QACjD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAE/C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;iBACjC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;iBACvE,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,MAAM,IAAI,iBAAiB,CAAC,oCAAoC,IAAI,MAAM,QAAQ,EAAE,CAAC,CAAC;QACxF,CAAC;QAED,OAAO;YACL,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;YACtC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YAChC,GAAG,CAAC,QAAQ,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7E,CAAC;IACJ,CAAC;IAED,KAAK,UAAU,QAAQ,CAAC,OAAsB;QAC5C,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC7B,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,KAAK;YACd,OAAO,QAAQ,CAAC;gBACd,EAAE,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI;gBAC7B,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzD,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS;oBAC7B,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE;oBACvC,CAAC,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS;wBAC1B,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;wBAC3B,CAAC,CAAC,EAAE,CAAC;gBACT,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChE,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACnE,GAAG,CAAC,KAAK,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvF,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnE,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,YAAY,CAAC,KAAK;YACtB,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAE5D,OAAO,QAAQ,CAAC;gBACd,EAAE,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI;gBAC7B,GAAG,QAAQ;gBACX,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS;oBAC7B,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE;oBACvC,CAAC,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS;wBAC1B,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;wBAC3B,CAAC,CAAC,EAAE,CAAC;gBACT,GAAG,CAAC,KAAK,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvF,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnE,CAAC,CAAC;QACL,CAAC;QAED,MAAM,CAAC,QAAQ,EAAE,IAAI;YACnB,OAAO,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACxC,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* A typed email template.
|
|
4
|
+
*
|
|
5
|
+
* Same shape as the event registry, for the same reason: the Zod schema gives the
|
|
6
|
+
* compile-time payload type *and* validates at runtime, so a template rendered
|
|
7
|
+
* with the wrong data fails before a message is built rather than producing an
|
|
8
|
+
* email with `undefined` in it.
|
|
9
|
+
*/
|
|
10
|
+
export interface EmailTemplate<TSchema extends z.ZodType = z.ZodType> {
|
|
11
|
+
readonly schema: TSchema;
|
|
12
|
+
readonly subject: (data: z.output<TSchema>) => string;
|
|
13
|
+
readonly text: (data: z.output<TSchema>) => string;
|
|
14
|
+
/** Optional. A text part is mandatory; HTML is an enhancement. */
|
|
15
|
+
readonly html?: (data: z.output<TSchema>) => string;
|
|
16
|
+
}
|
|
17
|
+
export type EmailTemplateMap = Record<string, EmailTemplate>;
|
|
18
|
+
/**
|
|
19
|
+
* Build one template, inferring the payload type from its own schema.
|
|
20
|
+
*
|
|
21
|
+
* The wrapper is load-bearing. TypeScript cannot infer a callback's parameter
|
|
22
|
+
* from a *sibling* property of the same object literal, so writing the template
|
|
23
|
+
* inline would leave `data` as `unknown` in `subject` and `text`. Binding the
|
|
24
|
+
* schema generic inside this call is what types them.
|
|
25
|
+
*/
|
|
26
|
+
export declare function emailTemplate<S extends z.ZodType>(template: {
|
|
27
|
+
readonly schema: S;
|
|
28
|
+
readonly subject: (data: z.output<S>) => string;
|
|
29
|
+
readonly text: (data: z.output<S>) => string;
|
|
30
|
+
readonly html?: (data: z.output<S>) => string;
|
|
31
|
+
}): EmailTemplate<S>;
|
|
32
|
+
export interface EmailTemplateRegistry<T extends EmailTemplateMap = EmailTemplateMap> {
|
|
33
|
+
readonly templates: T;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Declare a set of templates.
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* export const appTemplates = defineEmailTemplates({
|
|
40
|
+
* "project.shared": emailTemplate({
|
|
41
|
+
* schema: z.object({ projectName: z.string(), sharedBy: z.string() }),
|
|
42
|
+
* subject: (data) => `${data.sharedBy} shared "${data.projectName}" with you`,
|
|
43
|
+
* text: (data) => `${data.sharedBy} shared ${data.projectName}.`,
|
|
44
|
+
* }),
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function defineEmailTemplates<const T extends EmailTemplateMap>(templates: T): EmailTemplateRegistry<T>;
|
|
49
|
+
/** Combine the core templates with an application's own. Duplicates throw. */
|
|
50
|
+
export declare function mergeEmailTemplates<A extends EmailTemplateMap, B extends EmailTemplateMap>(first: EmailTemplateRegistry<A>, second: EmailTemplateRegistry<B>): EmailTemplateRegistry<A & B>;
|
|
51
|
+
/**
|
|
52
|
+
* Replace core templates with your own.
|
|
53
|
+
*
|
|
54
|
+
* Separate from `mergeEmailTemplates` so that overriding the core's copy is a
|
|
55
|
+
* visible decision rather than a silent shadowing — the copy and branding of an
|
|
56
|
+
* invitation email is exactly the sort of thing an application *should* replace.
|
|
57
|
+
*/
|
|
58
|
+
export declare function overrideEmailTemplates<A extends EmailTemplateMap, B extends EmailTemplateMap>(first: EmailTemplateRegistry<A>, second: EmailTemplateRegistry<B>): EmailTemplateRegistry<Omit<A, keyof B> & B>;
|
|
59
|
+
export type EmailTemplateName<T extends EmailTemplateMap> = keyof T & string;
|
|
60
|
+
/**
|
|
61
|
+
* Templates the core needs in order to function.
|
|
62
|
+
*
|
|
63
|
+
* Only one, deliberately. The core *creates* workspace invitations, and there is
|
|
64
|
+
* no way to deliver the single-use token without an email — so this one must
|
|
65
|
+
* exist. Welcome, security and billing emails are product copy: shipping a
|
|
66
|
+
* default would mean shipping branding decisions, and every application would
|
|
67
|
+
* override it anyway.
|
|
68
|
+
*
|
|
69
|
+
* The wording here is intentionally plain. Override it with
|
|
70
|
+
* `overrideEmailTemplates` for anything customer-facing.
|
|
71
|
+
*/
|
|
72
|
+
export declare const coreEmailTemplates: EmailTemplateRegistry<{
|
|
73
|
+
readonly "workspace.invitation": EmailTemplate<z.ZodObject<{
|
|
74
|
+
workspaceName: z.ZodString;
|
|
75
|
+
inviterName: z.ZodOptional<z.ZodString>;
|
|
76
|
+
acceptUrl: z.ZodURL;
|
|
77
|
+
expiresAt: z.ZodDate;
|
|
78
|
+
}, z.core.$strip>>;
|
|
79
|
+
}>;
|
|
80
|
+
export type CoreEmailTemplates = (typeof coreEmailTemplates)["templates"];
|
|
81
|
+
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/email/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa,CAAC,OAAO,SAAS,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO;IAClE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC;IACtD,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC;IACnD,kEAAkE;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC;CACrD;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE7D;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE;IAC3D,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC;IAChD,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC;IAC7C,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC;CAC/C,GAAG,aAAa,CAAC,CAAC,CAAC,CAEnB;AAED,MAAM,WAAW,qBAAqB,CAAC,CAAC,SAAS,gBAAgB,GAAG,gBAAgB;IAClF,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,CAAC,CAAC,SAAS,gBAAgB,EACnE,SAAS,EAAE,CAAC,GACX,qBAAqB,CAAC,CAAC,CAAC,CAE1B;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,gBAAgB,EAAE,CAAC,SAAS,gBAAgB,EACxF,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAC/B,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAC/B,qBAAqB,CAAC,CAAC,GAAG,CAAC,CAAC,CAY9B;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,gBAAgB,EAAE,CAAC,SAAS,gBAAgB,EAC3F,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAC/B,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAC/B,qBAAqB,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAE7C;AAED,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,gBAAgB,IAAI,MAAM,CAAC,GAAG,MAAM,CAAC;AAE7E;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB;;;;;;;EAsB7B,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,WAAW,CAAC,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { InvalidInputError } from "../errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build one template, inferring the payload type from its own schema.
|
|
5
|
+
*
|
|
6
|
+
* The wrapper is load-bearing. TypeScript cannot infer a callback's parameter
|
|
7
|
+
* from a *sibling* property of the same object literal, so writing the template
|
|
8
|
+
* inline would leave `data` as `unknown` in `subject` and `text`. Binding the
|
|
9
|
+
* schema generic inside this call is what types them.
|
|
10
|
+
*/
|
|
11
|
+
export function emailTemplate(template) {
|
|
12
|
+
return template;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Declare a set of templates.
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* export const appTemplates = defineEmailTemplates({
|
|
19
|
+
* "project.shared": emailTemplate({
|
|
20
|
+
* schema: z.object({ projectName: z.string(), sharedBy: z.string() }),
|
|
21
|
+
* subject: (data) => `${data.sharedBy} shared "${data.projectName}" with you`,
|
|
22
|
+
* text: (data) => `${data.sharedBy} shared ${data.projectName}.`,
|
|
23
|
+
* }),
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function defineEmailTemplates(templates) {
|
|
28
|
+
return { templates };
|
|
29
|
+
}
|
|
30
|
+
/** Combine the core templates with an application's own. Duplicates throw. */
|
|
31
|
+
export function mergeEmailTemplates(first, second) {
|
|
32
|
+
const collisions = Object.keys(second.templates).filter((key) => key in first.templates);
|
|
33
|
+
if (collisions.length > 0) {
|
|
34
|
+
throw new InvalidInputError(`Email templates already defined: ${collisions.join(", ")}. ` +
|
|
35
|
+
`Rename the application's templates, or override deliberately with ` +
|
|
36
|
+
`overrideEmailTemplates().`);
|
|
37
|
+
}
|
|
38
|
+
return { templates: { ...first.templates, ...second.templates } };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Replace core templates with your own.
|
|
42
|
+
*
|
|
43
|
+
* Separate from `mergeEmailTemplates` so that overriding the core's copy is a
|
|
44
|
+
* visible decision rather than a silent shadowing — the copy and branding of an
|
|
45
|
+
* invitation email is exactly the sort of thing an application *should* replace.
|
|
46
|
+
*/
|
|
47
|
+
export function overrideEmailTemplates(first, second) {
|
|
48
|
+
return { templates: { ...first.templates, ...second.templates } };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Templates the core needs in order to function.
|
|
52
|
+
*
|
|
53
|
+
* Only one, deliberately. The core *creates* workspace invitations, and there is
|
|
54
|
+
* no way to deliver the single-use token without an email — so this one must
|
|
55
|
+
* exist. Welcome, security and billing emails are product copy: shipping a
|
|
56
|
+
* default would mean shipping branding decisions, and every application would
|
|
57
|
+
* override it anyway.
|
|
58
|
+
*
|
|
59
|
+
* The wording here is intentionally plain. Override it with
|
|
60
|
+
* `overrideEmailTemplates` for anything customer-facing.
|
|
61
|
+
*/
|
|
62
|
+
export const coreEmailTemplates = defineEmailTemplates({
|
|
63
|
+
"workspace.invitation": emailTemplate({
|
|
64
|
+
schema: z.object({
|
|
65
|
+
workspaceName: z.string().min(1),
|
|
66
|
+
inviterName: z.string().min(1).optional(),
|
|
67
|
+
/** Absolute URL carrying the single-use token. */
|
|
68
|
+
acceptUrl: z.url(),
|
|
69
|
+
expiresAt: z.date(),
|
|
70
|
+
}),
|
|
71
|
+
subject: (data) => `You have been invited to join ${data.workspaceName}`,
|
|
72
|
+
text: (data) => [
|
|
73
|
+
data.inviterName === undefined
|
|
74
|
+
? `You have been invited to join ${data.workspaceName}.`
|
|
75
|
+
: `${data.inviterName} has invited you to join ${data.workspaceName}.`,
|
|
76
|
+
"",
|
|
77
|
+
"Accept the invitation:",
|
|
78
|
+
data.acceptUrl,
|
|
79
|
+
"",
|
|
80
|
+
`This link expires on ${data.expiresAt.toISOString()} and can be used once.`,
|
|
81
|
+
].join("\n"),
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/email/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAoBjD;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAsB,QAKlD;IACC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAMD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAAY;IAEZ,OAAO,EAAE,SAAS,EAAE,CAAC;AACvB,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,mBAAmB,CACjC,KAA+B,EAC/B,MAAgC;IAEhC,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;IAEzF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,iBAAiB,CACzB,oCAAoC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YAC3D,oEAAoE;YACpE,2BAA2B,CAC9B,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,GAAG,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC;AACpE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,KAA+B,EAC/B,MAAgC;IAEhC,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,GAAG,MAAM,CAAC,SAAS,EAA0B,EAAE,CAAC;AAC5F,CAAC;AAID;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAC;IACrD,sBAAsB,EAAE,aAAa,CAAC;QACpC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;YACf,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAChC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;YACzC,kDAAkD;YAClD,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE;YAClB,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE;SACpB,CAAC;QACF,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,iCAAiC,IAAI,CAAC,aAAa,EAAE;QACxE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CACb;YACE,IAAI,CAAC,WAAW,KAAK,SAAS;gBAC5B,CAAC,CAAC,iCAAiC,IAAI,CAAC,aAAa,GAAG;gBACxD,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,4BAA4B,IAAI,CAAC,aAAa,GAAG;YACxE,EAAE;YACF,wBAAwB;YACxB,IAAI,CAAC,SAAS;YACd,EAAE;YACF,wBAAwB,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,wBAAwB;SAC7E,CAAC,IAAI,CAAC,IAAI,CAAC;KACf,CAAC;CACH,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { createEntitlementsService, type EntitlementsService, type EntitlementsServiceDeps, type LimitCheck, type RecordUsageOptions, type ResolvedEntitlements, type UsageWindow, } from "./service.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Default service, bound to the configuration-driven database handle.
|
|
4
|
+
*
|
|
5
|
+
* No fallback plan is configured, so a workspace without a live subscription has
|
|
6
|
+
* no entitlements. Build your own with `createEntitlementsService` to grant a
|
|
7
|
+
* free tier.
|
|
8
|
+
*/
|
|
9
|
+
export declare const entitlements: import("./service.ts").EntitlementsService;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/entitlements/index.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AAEtB;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,4CAIvB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entitlements surface: `@digimuza/saas-core/entitlements`. Server-only.
|
|
3
|
+
*
|
|
4
|
+
* Billing and authorization answer different questions. Authorization asks "may
|
|
5
|
+
* this user do it?"; entitlements ask "has this workspace paid for it?" Both must
|
|
6
|
+
* pass, and product code should never inspect a plan name to decide either:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* await authorization.require(context, "projects.create");
|
|
10
|
+
* await entitlements.require(context, "ai_generation");
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
import { getDatabase } from "../db/client.js";
|
|
14
|
+
import { createEntitlementsService } from "./service.js";
|
|
15
|
+
export { createEntitlementsService, } from "./service.js";
|
|
16
|
+
/**
|
|
17
|
+
* Default service, bound to the configuration-driven database handle.
|
|
18
|
+
*
|
|
19
|
+
* No fallback plan is configured, so a workspace without a live subscription has
|
|
20
|
+
* no entitlements. Build your own with `createEntitlementsService` to grant a
|
|
21
|
+
* free tier.
|
|
22
|
+
*/
|
|
23
|
+
export const entitlements = createEntitlementsService({
|
|
24
|
+
get db() {
|
|
25
|
+
return getDatabase();
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/entitlements/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EACL,yBAAyB,GAO1B,MAAM,cAAc,CAAC;AAEtB;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,yBAAyB,CAAC;IACpD,IAAI,EAAE;QACJ,OAAO,WAAW,EAAE,CAAC;IACvB,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { CoreDatabase } from "../db/types.ts";
|
|
2
|
+
import type { Entitlement, Plan } from "../db/schema/index.ts";
|
|
3
|
+
import type { WorkspaceContext } from "../workspaces/context.ts";
|
|
4
|
+
/**
|
|
5
|
+
* The window over which metered usage is counted.
|
|
6
|
+
*
|
|
7
|
+
* Aligned to the subscription's billing period when there is one, because that
|
|
8
|
+
* is what a customer is paying for. Workspaces on a fallback plan have no
|
|
9
|
+
* period, so the current UTC calendar month is used — an all-time total would
|
|
10
|
+
* make a monthly quota permanently exhausted.
|
|
11
|
+
*/
|
|
12
|
+
export interface UsageWindow {
|
|
13
|
+
readonly start: Date;
|
|
14
|
+
readonly end: Date;
|
|
15
|
+
}
|
|
16
|
+
export interface LimitCheck {
|
|
17
|
+
/** Whether the requested quantity fits within the remaining quota. */
|
|
18
|
+
readonly allowed: boolean;
|
|
19
|
+
/** Null when unlimited. */
|
|
20
|
+
readonly limit: number | null;
|
|
21
|
+
readonly used: number;
|
|
22
|
+
/** Null when unlimited. */
|
|
23
|
+
readonly remaining: number | null;
|
|
24
|
+
readonly window: UsageWindow;
|
|
25
|
+
}
|
|
26
|
+
export interface RecordUsageOptions {
|
|
27
|
+
readonly quantity?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Deduplication key. A retried request reusing it is counted once, so a
|
|
30
|
+
* network retry does not consume the customer's quota twice.
|
|
31
|
+
*/
|
|
32
|
+
readonly idempotencyKey?: string;
|
|
33
|
+
readonly occurredAt?: Date;
|
|
34
|
+
}
|
|
35
|
+
export interface ResolvedEntitlements {
|
|
36
|
+
readonly plan: Plan | undefined;
|
|
37
|
+
readonly byKey: ReadonlyMap<string, Entitlement>;
|
|
38
|
+
}
|
|
39
|
+
export interface EntitlementsService {
|
|
40
|
+
/** The plan whose entitlements apply, via the live subscription or the fallback. */
|
|
41
|
+
resolvePlan(context: WorkspaceContext): Promise<Plan | undefined>;
|
|
42
|
+
/** Everything the workspace is entitled to, keyed by entitlement key. */
|
|
43
|
+
resolve(context: WorkspaceContext): Promise<ResolvedEntitlements>;
|
|
44
|
+
/** Whether the workspace's plan includes this feature or limit at all. */
|
|
45
|
+
has(context: WorkspaceContext, key: string): Promise<boolean>;
|
|
46
|
+
/** As {@link has}, but throws {@link EntitlementRequiredError}. */
|
|
47
|
+
require(context: WorkspaceContext, key: string): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* The quota for a limit entitlement.
|
|
50
|
+
*
|
|
51
|
+
* `null` means unlimited; `0` means explicitly none. A key the plan does not
|
|
52
|
+
* include also yields `0`, so an unconfigured limit fails closed.
|
|
53
|
+
*/
|
|
54
|
+
getLimit(context: WorkspaceContext, key: string): Promise<number | null>;
|
|
55
|
+
/** Whether `quantity` more units fit within the quota for the current period. */
|
|
56
|
+
checkLimit(context: WorkspaceContext, key: string, quantity?: number): Promise<LimitCheck>;
|
|
57
|
+
/** As {@link checkLimit}, but throws {@link LimitExceededError}. */
|
|
58
|
+
requireLimit(context: WorkspaceContext, key: string, quantity?: number): Promise<LimitCheck>;
|
|
59
|
+
/** Consumption in the current period. */
|
|
60
|
+
getUsage(context: WorkspaceContext, key: string): Promise<number>;
|
|
61
|
+
/** Append a usage record. Does not itself enforce the limit. */
|
|
62
|
+
recordUsage(context: WorkspaceContext, key: string, options?: RecordUsageOptions): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Check the quota and record the consumption together.
|
|
65
|
+
*
|
|
66
|
+
* @throws LimitExceededError without recording anything.
|
|
67
|
+
*/
|
|
68
|
+
consume(context: WorkspaceContext, key: string, options?: RecordUsageOptions): Promise<LimitCheck>;
|
|
69
|
+
}
|
|
70
|
+
export interface EntitlementsServiceDeps {
|
|
71
|
+
readonly db: CoreDatabase;
|
|
72
|
+
/**
|
|
73
|
+
* Plan applied to workspaces with no live subscription — a free tier.
|
|
74
|
+
*
|
|
75
|
+
* Omit it and such workspaces have no entitlements at all, which is the safe
|
|
76
|
+
* default but leaves no room for a free plan.
|
|
77
|
+
*/
|
|
78
|
+
readonly fallbackPlanKey?: string;
|
|
79
|
+
/** Injected for deterministic tests. */
|
|
80
|
+
readonly now?: () => Date;
|
|
81
|
+
}
|
|
82
|
+
export declare function createEntitlementsService(deps: EntitlementsServiceDeps): EntitlementsService;
|
|
83
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/entitlements/service.ts"],"names":[],"mappings":"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"}
|