@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
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,1165 @@
|
|
|
1
|
+
# @digimuza/saas-core
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
@digimuza/saas-core is a reusable SaaS foundation for building multiple SaaS applications.
|
|
6
|
+
|
|
7
|
+
The goal is to eliminate repeatedly rebuilding SaaS infrastructure while keeping product-specific business logic outside the core.
|
|
8
|
+
|
|
9
|
+
The package must provide a simple, strongly typed foundation for:
|
|
10
|
+
|
|
11
|
+
- Authentication
|
|
12
|
+
- Users and profiles
|
|
13
|
+
- Workspaces / multi-tenancy
|
|
14
|
+
- Workspace memberships
|
|
15
|
+
- Customizable roles
|
|
16
|
+
- Multiple roles per user
|
|
17
|
+
- Permissions
|
|
18
|
+
- Environment configuration
|
|
19
|
+
- Runtime configuration
|
|
20
|
+
- Billing
|
|
21
|
+
- Entitlements
|
|
22
|
+
- Background jobs
|
|
23
|
+
- Durable workflows
|
|
24
|
+
- Application events
|
|
25
|
+
- Realtime communication
|
|
26
|
+
- RxJS-based reactive behaviors
|
|
27
|
+
- Email
|
|
28
|
+
- Notifications
|
|
29
|
+
- Audit logs
|
|
30
|
+
- API keys
|
|
31
|
+
- Webhooks
|
|
32
|
+
- Feature flags
|
|
33
|
+
- Storage
|
|
34
|
+
|
|
35
|
+
The primary design principle is:
|
|
36
|
+
|
|
37
|
+
"Use the simplest infrastructure that reliably solves the problem."
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
# Technology
|
|
42
|
+
|
|
43
|
+
Use:
|
|
44
|
+
|
|
45
|
+
- TypeScript
|
|
46
|
+
- Next.js
|
|
47
|
+
- PostgreSQL
|
|
48
|
+
- Drizzle ORM
|
|
49
|
+
- Zod
|
|
50
|
+
- RxJS
|
|
51
|
+
- Vitest
|
|
52
|
+
- Playwright when UI testing is required
|
|
53
|
+
|
|
54
|
+
External services:
|
|
55
|
+
|
|
56
|
+
- WorkOS -> authentication and identity
|
|
57
|
+
- Infisical -> environment configuration and secrets
|
|
58
|
+
- Revolut -> billing/payments
|
|
59
|
+
- Trigger.dev -> durable background execution
|
|
60
|
+
- Open-source realtime infrastructure -> realtime transport when required
|
|
61
|
+
|
|
62
|
+
Do not introduce additional infrastructure without a concrete requirement.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
# Core Philosophy
|
|
67
|
+
|
|
68
|
+
## Simplicity
|
|
69
|
+
|
|
70
|
+
Prefer:
|
|
71
|
+
|
|
72
|
+
PostgreSQL over Redis when PostgreSQL is sufficient.
|
|
73
|
+
|
|
74
|
+
Next.js over a separate backend when Next.js is sufficient.
|
|
75
|
+
|
|
76
|
+
Managed infrastructure over infrastructure that must be manually operated.
|
|
77
|
+
|
|
78
|
+
Explicit APIs over magic.
|
|
79
|
+
|
|
80
|
+
Small abstractions over generic frameworks.
|
|
81
|
+
|
|
82
|
+
Do not introduce microservices.
|
|
83
|
+
|
|
84
|
+
Do not build custom infrastructure when a mature service already solves the problem.
|
|
85
|
+
|
|
86
|
+
Do not optimize for hypothetical scale.
|
|
87
|
+
|
|
88
|
+
Optimize for:
|
|
89
|
+
|
|
90
|
+
- simplicity
|
|
91
|
+
- maintainability
|
|
92
|
+
- security
|
|
93
|
+
- developer experience
|
|
94
|
+
- reliability
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
# Package
|
|
99
|
+
|
|
100
|
+
Everything belongs to ONE npm package:
|
|
101
|
+
|
|
102
|
+
@digimuza/saas-core
|
|
103
|
+
|
|
104
|
+
Use subpath exports:
|
|
105
|
+
|
|
106
|
+
@digimuza/saas-core/auth
|
|
107
|
+
@digimuza/saas-core/db
|
|
108
|
+
@digimuza/saas-core/users
|
|
109
|
+
@digimuza/saas-core/workspaces
|
|
110
|
+
@digimuza/saas-core/authorization
|
|
111
|
+
@digimuza/saas-core/config
|
|
112
|
+
@digimuza/saas-core/billing
|
|
113
|
+
@digimuza/saas-core/entitlements
|
|
114
|
+
@digimuza/saas-core/jobs
|
|
115
|
+
@digimuza/saas-core/events
|
|
116
|
+
@digimuza/saas-core/realtime
|
|
117
|
+
@digimuza/saas-core/email
|
|
118
|
+
@digimuza/saas-core/notifications
|
|
119
|
+
@digimuza/saas-core/audit
|
|
120
|
+
@digimuza/saas-core/api-keys
|
|
121
|
+
@digimuza/saas-core/webhooks
|
|
122
|
+
@digimuza/saas-core/feature-flags
|
|
123
|
+
@digimuza/saas-core/storage
|
|
124
|
+
|
|
125
|
+
Do NOT create separate npm packages.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
# Database
|
|
130
|
+
|
|
131
|
+
Use PostgreSQL + Drizzle ORM.
|
|
132
|
+
|
|
133
|
+
Do NOT use Prisma.
|
|
134
|
+
|
|
135
|
+
Database schema definitions are part of the public package.
|
|
136
|
+
|
|
137
|
+
Export them from:
|
|
138
|
+
|
|
139
|
+
@digimuza/saas-core/db/schema
|
|
140
|
+
|
|
141
|
+
Applications must be able to import core tables and reference them from product-specific tables.
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
|
|
145
|
+
import { workspaces } from "@digimuza/saas-core/db/schema";
|
|
146
|
+
|
|
147
|
+
export const projects = pgTable("projects", {
|
|
148
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
149
|
+
|
|
150
|
+
workspaceId: uuid("workspace_id")
|
|
151
|
+
.notNull()
|
|
152
|
+
.references(() => workspaces.id),
|
|
153
|
+
|
|
154
|
+
name: text("name").notNull(),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
The core owns platform tables.
|
|
158
|
+
|
|
159
|
+
Individual SaaS applications own product-specific tables.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
# Database Ownership
|
|
164
|
+
|
|
165
|
+
Core-owned tables include:
|
|
166
|
+
|
|
167
|
+
users
|
|
168
|
+
user_profiles
|
|
169
|
+
workspaces
|
|
170
|
+
workspace_members
|
|
171
|
+
roles
|
|
172
|
+
permissions
|
|
173
|
+
workspace_member_roles
|
|
174
|
+
role_permissions
|
|
175
|
+
plans
|
|
176
|
+
billing_customers
|
|
177
|
+
subscriptions
|
|
178
|
+
billing_events
|
|
179
|
+
entitlements
|
|
180
|
+
audit_logs
|
|
181
|
+
notifications
|
|
182
|
+
api_keys
|
|
183
|
+
webhooks
|
|
184
|
+
feature_flags
|
|
185
|
+
events
|
|
186
|
+
usage_records where appropriate
|
|
187
|
+
|
|
188
|
+
Product applications own:
|
|
189
|
+
|
|
190
|
+
projects
|
|
191
|
+
documents
|
|
192
|
+
messages
|
|
193
|
+
AI-specific entities
|
|
194
|
+
product-specific configuration
|
|
195
|
+
product-specific business logic
|
|
196
|
+
|
|
197
|
+
Never put product-specific domain models into the core unless they clearly apply to multiple SaaS products.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
# Users
|
|
202
|
+
|
|
203
|
+
Separate authentication identity from user profile.
|
|
204
|
+
|
|
205
|
+
users:
|
|
206
|
+
|
|
207
|
+
- id
|
|
208
|
+
- workos_user_id
|
|
209
|
+
- created_at
|
|
210
|
+
- updated_at
|
|
211
|
+
|
|
212
|
+
user_profiles:
|
|
213
|
+
|
|
214
|
+
- user_id
|
|
215
|
+
- email
|
|
216
|
+
- first_name
|
|
217
|
+
- last_name
|
|
218
|
+
- display_name
|
|
219
|
+
- avatar_url
|
|
220
|
+
- created_at
|
|
221
|
+
- updated_at
|
|
222
|
+
|
|
223
|
+
Email belongs to user_profiles.
|
|
224
|
+
|
|
225
|
+
Do not duplicate email in users.
|
|
226
|
+
|
|
227
|
+
Do not store passwords.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
# Authentication
|
|
232
|
+
|
|
233
|
+
Use WorkOS.
|
|
234
|
+
|
|
235
|
+
Do not implement OAuth, SAML or OIDC manually.
|
|
236
|
+
|
|
237
|
+
Application code should use:
|
|
238
|
+
|
|
239
|
+
auth.getCurrentUser()
|
|
240
|
+
auth.requireUser()
|
|
241
|
+
auth.getSession()
|
|
242
|
+
auth.signOut()
|
|
243
|
+
|
|
244
|
+
Do not expose WorkOS implementation details throughout application code.
|
|
245
|
+
|
|
246
|
+
Support WorkOS capabilities where applicable:
|
|
247
|
+
|
|
248
|
+
- Google/social login
|
|
249
|
+
- Email authentication
|
|
250
|
+
- Passkeys
|
|
251
|
+
- MFA
|
|
252
|
+
- SAML
|
|
253
|
+
- OIDC
|
|
254
|
+
- Account linking
|
|
255
|
+
- Sessions
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
# Workspaces
|
|
260
|
+
|
|
261
|
+
Users can belong to multiple workspaces.
|
|
262
|
+
|
|
263
|
+
Never put workspace_id on users.
|
|
264
|
+
|
|
265
|
+
Use:
|
|
266
|
+
|
|
267
|
+
users
|
|
268
|
+
-> workspace_members
|
|
269
|
+
-> workspaces
|
|
270
|
+
|
|
271
|
+
A workspace membership represents the user's relationship with a workspace.
|
|
272
|
+
|
|
273
|
+
Expose:
|
|
274
|
+
|
|
275
|
+
workspaces.create()
|
|
276
|
+
workspaces.get()
|
|
277
|
+
workspaces.update()
|
|
278
|
+
workspaces.delete()
|
|
279
|
+
workspaces.getCurrent()
|
|
280
|
+
workspaces.members.list()
|
|
281
|
+
workspaces.members.invite()
|
|
282
|
+
workspaces.members.remove()
|
|
283
|
+
|
|
284
|
+
All tenant-sensitive operations must verify workspace membership.
|
|
285
|
+
|
|
286
|
+
Never trust workspace_id supplied by a client.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
# Roles
|
|
291
|
+
|
|
292
|
+
Roles are workspace-scoped.
|
|
293
|
+
|
|
294
|
+
roles:
|
|
295
|
+
|
|
296
|
+
- id
|
|
297
|
+
- workspace_id
|
|
298
|
+
- name
|
|
299
|
+
- description
|
|
300
|
+
- created_at
|
|
301
|
+
- updated_at
|
|
302
|
+
|
|
303
|
+
Users may have multiple roles.
|
|
304
|
+
|
|
305
|
+
workspace_member_roles:
|
|
306
|
+
|
|
307
|
+
- workspace_id
|
|
308
|
+
- user_id
|
|
309
|
+
- role_id
|
|
310
|
+
|
|
311
|
+
PostgreSQL constraints must prevent cross-workspace role assignments.
|
|
312
|
+
|
|
313
|
+
Support:
|
|
314
|
+
|
|
315
|
+
- system roles
|
|
316
|
+
- default roles
|
|
317
|
+
- custom roles
|
|
318
|
+
|
|
319
|
+
Do not hard-code role names into authorization logic.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
# Permissions
|
|
324
|
+
|
|
325
|
+
Permissions are application-defined.
|
|
326
|
+
|
|
327
|
+
Examples:
|
|
328
|
+
|
|
329
|
+
workspace.read
|
|
330
|
+
workspace.update
|
|
331
|
+
members.read
|
|
332
|
+
members.invite
|
|
333
|
+
members.remove
|
|
334
|
+
projects.read
|
|
335
|
+
projects.create
|
|
336
|
+
projects.update
|
|
337
|
+
projects.delete
|
|
338
|
+
billing.read
|
|
339
|
+
billing.update
|
|
340
|
+
|
|
341
|
+
Roles receive permissions through:
|
|
342
|
+
|
|
343
|
+
role_permissions
|
|
344
|
+
|
|
345
|
+
Effective permissions are the UNION of permissions from all roles assigned to the user within the workspace.
|
|
346
|
+
|
|
347
|
+
Expose:
|
|
348
|
+
|
|
349
|
+
authorization.can()
|
|
350
|
+
authorization.require()
|
|
351
|
+
authorization.hasRole()
|
|
352
|
+
|
|
353
|
+
Authorization always requires workspace context.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
# Configuration
|
|
358
|
+
|
|
359
|
+
Do NOT use .env files as the project's configuration management strategy.
|
|
360
|
+
|
|
361
|
+
Use Infisical.
|
|
362
|
+
|
|
363
|
+
Infisical is responsible for injecting secrets and environment configuration into the application runtime.
|
|
364
|
+
|
|
365
|
+
The core must not depend directly on the Infisical API during normal application execution.
|
|
366
|
+
|
|
367
|
+
Only the configuration module may access process.env.
|
|
368
|
+
|
|
369
|
+
Application code must never access process.env directly.
|
|
370
|
+
|
|
371
|
+
Provide typed configuration:
|
|
372
|
+
|
|
373
|
+
config.app
|
|
374
|
+
config.database
|
|
375
|
+
config.auth
|
|
376
|
+
config.billing
|
|
377
|
+
config.email
|
|
378
|
+
config.storage
|
|
379
|
+
config.realtime
|
|
380
|
+
config.jobs
|
|
381
|
+
|
|
382
|
+
Configuration must:
|
|
383
|
+
|
|
384
|
+
- be strongly typed
|
|
385
|
+
- be validated with Zod
|
|
386
|
+
- fail fast
|
|
387
|
+
- separate server and client configuration
|
|
388
|
+
- prevent server secrets from entering client bundles
|
|
389
|
+
|
|
390
|
+
Infrastructure secrets must never be stored in PostgreSQL.
|
|
391
|
+
|
|
392
|
+
Runtime application configuration may be stored in PostgreSQL.
|
|
393
|
+
|
|
394
|
+
Examples:
|
|
395
|
+
|
|
396
|
+
- workspace timezone
|
|
397
|
+
- locale
|
|
398
|
+
- branding
|
|
399
|
+
- workspace settings
|
|
400
|
+
- product configuration
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
# Billing
|
|
405
|
+
|
|
406
|
+
Use Revolut initially.
|
|
407
|
+
|
|
408
|
+
Do not expose Revolut-specific APIs throughout the application.
|
|
409
|
+
|
|
410
|
+
Expose:
|
|
411
|
+
|
|
412
|
+
billing.getCustomer()
|
|
413
|
+
billing.createCheckout()
|
|
414
|
+
billing.getSubscription()
|
|
415
|
+
billing.cancelSubscription()
|
|
416
|
+
billing.getPortal()
|
|
417
|
+
|
|
418
|
+
Database entities:
|
|
419
|
+
|
|
420
|
+
plans
|
|
421
|
+
billing_customers
|
|
422
|
+
subscriptions
|
|
423
|
+
billing_events
|
|
424
|
+
|
|
425
|
+
Billing state is updated from verified webhook events.
|
|
426
|
+
|
|
427
|
+
Keep the billing abstraction provider-independent enough to support another provider later.
|
|
428
|
+
|
|
429
|
+
Do not implement payment processing.
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
# Entitlements
|
|
434
|
+
|
|
435
|
+
Billing and authorization are separate.
|
|
436
|
+
|
|
437
|
+
Model:
|
|
438
|
+
|
|
439
|
+
Subscription
|
|
440
|
+
-> Plan
|
|
441
|
+
-> Entitlements
|
|
442
|
+
-> Features and limits
|
|
443
|
+
|
|
444
|
+
Examples:
|
|
445
|
+
|
|
446
|
+
advanced_analytics
|
|
447
|
+
ai_generation
|
|
448
|
+
api_access
|
|
449
|
+
custom_domains
|
|
450
|
+
|
|
451
|
+
Limits:
|
|
452
|
+
|
|
453
|
+
ai_generations
|
|
454
|
+
team_members
|
|
455
|
+
storage_bytes
|
|
456
|
+
api_requests
|
|
457
|
+
|
|
458
|
+
Expose:
|
|
459
|
+
|
|
460
|
+
entitlements.has()
|
|
461
|
+
entitlements.require()
|
|
462
|
+
entitlements.getLimit()
|
|
463
|
+
entitlements.checkLimit()
|
|
464
|
+
|
|
465
|
+
Product code must not inspect subscription plan names directly.
|
|
466
|
+
|
|
467
|
+
Use entitlements.
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
# Background Jobs
|
|
472
|
+
|
|
473
|
+
Use Trigger.dev for durable execution.
|
|
474
|
+
|
|
475
|
+
Do not implement a custom durable execution engine.
|
|
476
|
+
|
|
477
|
+
Use Trigger.dev for:
|
|
478
|
+
|
|
479
|
+
- long-running jobs
|
|
480
|
+
- retries
|
|
481
|
+
- scheduling
|
|
482
|
+
- cron
|
|
483
|
+
- durable workflows
|
|
484
|
+
- multi-step workflows
|
|
485
|
+
- waits
|
|
486
|
+
- concurrency
|
|
487
|
+
- task observability
|
|
488
|
+
|
|
489
|
+
Expose a thin integration:
|
|
490
|
+
|
|
491
|
+
jobs.trigger()
|
|
492
|
+
|
|
493
|
+
Do not unnecessarily hide Trigger.dev functionality.
|
|
494
|
+
|
|
495
|
+
The core should allow advanced applications to access Trigger.dev capabilities when required.
|
|
496
|
+
|
|
497
|
+
Do not introduce Redis solely for background jobs.
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
# Events
|
|
502
|
+
|
|
503
|
+
Create a unified application event model.
|
|
504
|
+
|
|
505
|
+
An event represents something that happened in the application.
|
|
506
|
+
|
|
507
|
+
Example:
|
|
508
|
+
|
|
509
|
+
events.publish({
|
|
510
|
+
type: "project.created",
|
|
511
|
+
workspaceId,
|
|
512
|
+
actorId,
|
|
513
|
+
data: {
|
|
514
|
+
projectId
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
Events must contain:
|
|
519
|
+
|
|
520
|
+
- id
|
|
521
|
+
- type
|
|
522
|
+
- timestamp
|
|
523
|
+
- workspace context when applicable
|
|
524
|
+
- actor context when applicable
|
|
525
|
+
- typed payload
|
|
526
|
+
|
|
527
|
+
Events may feed:
|
|
528
|
+
|
|
529
|
+
- realtime
|
|
530
|
+
- RxJS streams
|
|
531
|
+
- notifications
|
|
532
|
+
- audit logs
|
|
533
|
+
- webhooks
|
|
534
|
+
- Trigger.dev tasks
|
|
535
|
+
|
|
536
|
+
Events should be strongly typed.
|
|
537
|
+
|
|
538
|
+
Avoid a generic "anything JSON" event API when a typed event registry can be used.
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
# Realtime
|
|
543
|
+
|
|
544
|
+
Realtime is responsible for delivering events to connected clients.
|
|
545
|
+
|
|
546
|
+
It is NOT the durable source of truth.
|
|
547
|
+
|
|
548
|
+
The architecture is:
|
|
549
|
+
|
|
550
|
+
PostgreSQL
|
|
551
|
+
-> Application events
|
|
552
|
+
-> Realtime transport
|
|
553
|
+
-> Browser
|
|
554
|
+
|
|
555
|
+
The realtime layer must support:
|
|
556
|
+
|
|
557
|
+
- channels
|
|
558
|
+
- workspace-scoped channels
|
|
559
|
+
- user-scoped channels
|
|
560
|
+
- authentication
|
|
561
|
+
- authorization
|
|
562
|
+
- reconnect
|
|
563
|
+
- event delivery
|
|
564
|
+
|
|
565
|
+
Clients must never be able to subscribe to unauthorized workspace channels.
|
|
566
|
+
|
|
567
|
+
Do not expose PostgreSQL LISTEN/NOTIFY directly to browsers.
|
|
568
|
+
|
|
569
|
+
PostgreSQL may be used internally as part of the event pipeline.
|
|
570
|
+
|
|
571
|
+
The realtime implementation should be replaceable.
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
# RxJS Reactive Behavior Layer
|
|
576
|
+
|
|
577
|
+
RxJS is a first-class dependency.
|
|
578
|
+
|
|
579
|
+
RxJS is used for application-level reactive behavior.
|
|
580
|
+
|
|
581
|
+
It is NOT the database.
|
|
582
|
+
|
|
583
|
+
It is NOT the durable job system.
|
|
584
|
+
|
|
585
|
+
It is NOT the realtime transport.
|
|
586
|
+
|
|
587
|
+
Its responsibility is to:
|
|
588
|
+
|
|
589
|
+
- consume realtime events
|
|
590
|
+
- combine event streams
|
|
591
|
+
- filter events
|
|
592
|
+
- transform events
|
|
593
|
+
- debounce events
|
|
594
|
+
- throttle events
|
|
595
|
+
- aggregate events
|
|
596
|
+
- maintain reactive state
|
|
597
|
+
- coordinate reactive behavior
|
|
598
|
+
|
|
599
|
+
The conceptual architecture is:
|
|
600
|
+
|
|
601
|
+
Event sources
|
|
602
|
+
-> Realtime / application events
|
|
603
|
+
-> RxJS Observable
|
|
604
|
+
-> Reactor
|
|
605
|
+
-> Application / client
|
|
606
|
+
|
|
607
|
+
Expose a typed stream API.
|
|
608
|
+
|
|
609
|
+
Example:
|
|
610
|
+
|
|
611
|
+
const events$ = events.stream({
|
|
612
|
+
workspaceId
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
events$ returns:
|
|
616
|
+
|
|
617
|
+
Observable<SaaSEvent>
|
|
618
|
+
|
|
619
|
+
Example:
|
|
620
|
+
|
|
621
|
+
const projectUpdates$ = events$
|
|
622
|
+
.pipe(
|
|
623
|
+
filter(event => event.type === "project.updated"),
|
|
624
|
+
map(event => event.data),
|
|
625
|
+
distinctUntilChanged()
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
# RxJS Rules
|
|
631
|
+
|
|
632
|
+
Do not expose RxJS unnecessarily.
|
|
633
|
+
|
|
634
|
+
Use Observable where streaming/reactive behavior provides clear value.
|
|
635
|
+
|
|
636
|
+
Do not convert every synchronous API into an Observable.
|
|
637
|
+
|
|
638
|
+
Prefer:
|
|
639
|
+
|
|
640
|
+
Promise -> one-time asynchronous operation.
|
|
641
|
+
|
|
642
|
+
Observable -> stream of values/events.
|
|
643
|
+
|
|
644
|
+
Do not use RxJS for database access merely because RxJS exists.
|
|
645
|
+
|
|
646
|
+
Do not use RxJS as a replacement for Trigger.dev.
|
|
647
|
+
|
|
648
|
+
Do not use RxJS as a replacement for PostgreSQL persistence.
|
|
649
|
+
|
|
650
|
+
Do not create infinite subscriptions without lifecycle management.
|
|
651
|
+
|
|
652
|
+
Every subscription must have a clear lifecycle.
|
|
653
|
+
|
|
654
|
+
Prefer operators such as:
|
|
655
|
+
|
|
656
|
+
- map
|
|
657
|
+
- filter
|
|
658
|
+
- merge
|
|
659
|
+
- switchMap
|
|
660
|
+
- concatMap
|
|
661
|
+
- exhaustMap
|
|
662
|
+
- debounceTime
|
|
663
|
+
- throttleTime
|
|
664
|
+
- distinctUntilChanged
|
|
665
|
+
- scan
|
|
666
|
+
- share
|
|
667
|
+
- shareReplay
|
|
668
|
+
- takeUntil
|
|
669
|
+
|
|
670
|
+
Use operators intentionally.
|
|
671
|
+
|
|
672
|
+
Avoid deeply nested RxJS pipelines that become difficult to understand.
|
|
673
|
+
|
|
674
|
+
---
|
|
675
|
+
|
|
676
|
+
# Realtime + RxJS
|
|
677
|
+
|
|
678
|
+
Realtime transport should feed RxJS.
|
|
679
|
+
|
|
680
|
+
Example:
|
|
681
|
+
|
|
682
|
+
realtime.subscribe(...)
|
|
683
|
+
-> Observable<SaaSEvent>
|
|
684
|
+
-> RxJS operators
|
|
685
|
+
-> Application behavior
|
|
686
|
+
|
|
687
|
+
Do not make every UI component independently implement WebSocket behavior.
|
|
688
|
+
|
|
689
|
+
Prefer a shared event abstraction.
|
|
690
|
+
|
|
691
|
+
Example:
|
|
692
|
+
|
|
693
|
+
const workspaceEvents$ =
|
|
694
|
+
realtime.events.workspace(workspaceId);
|
|
695
|
+
|
|
696
|
+
Then:
|
|
697
|
+
|
|
698
|
+
workspaceEvents$
|
|
699
|
+
.pipe(
|
|
700
|
+
filter(event => event.type === "notification.created")
|
|
701
|
+
)
|
|
702
|
+
.subscribe(...)
|
|
703
|
+
|
|
704
|
+
The transport should be replaceable without changing the RxJS application API.
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
# Trigger.dev + RxJS
|
|
709
|
+
|
|
710
|
+
Trigger.dev owns durable execution.
|
|
711
|
+
|
|
712
|
+
RxJS owns reactive observation and behavior.
|
|
713
|
+
|
|
714
|
+
Example:
|
|
715
|
+
|
|
716
|
+
Trigger.dev task
|
|
717
|
+
-> job progress event
|
|
718
|
+
-> application event
|
|
719
|
+
-> realtime
|
|
720
|
+
-> RxJS Observable
|
|
721
|
+
-> browser UI
|
|
722
|
+
|
|
723
|
+
Do not attempt to make RxJS durable.
|
|
724
|
+
|
|
725
|
+
Do not keep state only in an Observable.
|
|
726
|
+
|
|
727
|
+
If state must survive process restart, persist it in PostgreSQL.
|
|
728
|
+
|
|
729
|
+
---
|
|
730
|
+
|
|
731
|
+
# Realtime Event Examples
|
|
732
|
+
|
|
733
|
+
Support patterns such as:
|
|
734
|
+
|
|
735
|
+
job.started
|
|
736
|
+
job.progress
|
|
737
|
+
job.completed
|
|
738
|
+
job.failed
|
|
739
|
+
|
|
740
|
+
project.created
|
|
741
|
+
project.updated
|
|
742
|
+
project.deleted
|
|
743
|
+
|
|
744
|
+
notification.created
|
|
745
|
+
|
|
746
|
+
workspace.member.created
|
|
747
|
+
workspace.member.removed
|
|
748
|
+
workspace.member.role.updated
|
|
749
|
+
|
|
750
|
+
billing.subscription.updated
|
|
751
|
+
|
|
752
|
+
The event registry should provide compile-time type safety.
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
# Email
|
|
757
|
+
|
|
758
|
+
Provide:
|
|
759
|
+
|
|
760
|
+
email.send()
|
|
761
|
+
email.sendTemplate()
|
|
762
|
+
|
|
763
|
+
Keep provider-specific implementation behind an adapter.
|
|
764
|
+
|
|
765
|
+
Templates may include:
|
|
766
|
+
|
|
767
|
+
- welcome
|
|
768
|
+
- invitation
|
|
769
|
+
- security
|
|
770
|
+
- billing
|
|
771
|
+
- notifications
|
|
772
|
+
|
|
773
|
+
---
|
|
774
|
+
|
|
775
|
+
# Notifications
|
|
776
|
+
|
|
777
|
+
Provide:
|
|
778
|
+
|
|
779
|
+
notifications.create()
|
|
780
|
+
notifications.list()
|
|
781
|
+
notifications.markRead()
|
|
782
|
+
|
|
783
|
+
Support:
|
|
784
|
+
|
|
785
|
+
- in-app notifications
|
|
786
|
+
- email notifications
|
|
787
|
+
|
|
788
|
+
Notifications may consume application events.
|
|
789
|
+
|
|
790
|
+
---
|
|
791
|
+
|
|
792
|
+
# Audit
|
|
793
|
+
|
|
794
|
+
Audit logs are durable and immutable.
|
|
795
|
+
|
|
796
|
+
Example:
|
|
797
|
+
|
|
798
|
+
audit.log({
|
|
799
|
+
action: "member.role.updated",
|
|
800
|
+
actorId,
|
|
801
|
+
workspaceId,
|
|
802
|
+
targetId,
|
|
803
|
+
metadata
|
|
804
|
+
})
|
|
805
|
+
|
|
806
|
+
Audit records include:
|
|
807
|
+
|
|
808
|
+
- actor
|
|
809
|
+
- workspace
|
|
810
|
+
- action
|
|
811
|
+
- target
|
|
812
|
+
- metadata
|
|
813
|
+
- timestamp
|
|
814
|
+
- request ID where available
|
|
815
|
+
|
|
816
|
+
Audit data must not be dependent on realtime delivery.
|
|
817
|
+
|
|
818
|
+
If an audit event is important, it must be persisted.
|
|
819
|
+
|
|
820
|
+
---
|
|
821
|
+
|
|
822
|
+
# API Keys
|
|
823
|
+
|
|
824
|
+
Workspace-scoped API keys.
|
|
825
|
+
|
|
826
|
+
Requirements:
|
|
827
|
+
|
|
828
|
+
- secure random generation
|
|
829
|
+
- hash secrets
|
|
830
|
+
- reveal secret only once
|
|
831
|
+
- revoke
|
|
832
|
+
- expiration
|
|
833
|
+
- scopes
|
|
834
|
+
- last-used timestamp
|
|
835
|
+
|
|
836
|
+
Expose:
|
|
837
|
+
|
|
838
|
+
apiKeys.create()
|
|
839
|
+
apiKeys.revoke()
|
|
840
|
+
apiKeys.validate()
|
|
841
|
+
|
|
842
|
+
API key validation must resolve the workspace securely.
|
|
843
|
+
|
|
844
|
+
---
|
|
845
|
+
|
|
846
|
+
# Webhooks
|
|
847
|
+
|
|
848
|
+
Support:
|
|
849
|
+
|
|
850
|
+
- incoming webhooks
|
|
851
|
+
- outgoing webhooks
|
|
852
|
+
|
|
853
|
+
Outgoing webhook infrastructure should support:
|
|
854
|
+
|
|
855
|
+
- endpoints
|
|
856
|
+
- secrets
|
|
857
|
+
- event subscriptions
|
|
858
|
+
- delivery records
|
|
859
|
+
- retries
|
|
860
|
+
- failures
|
|
861
|
+
|
|
862
|
+
Incoming webhooks must support:
|
|
863
|
+
|
|
864
|
+
- signature verification
|
|
865
|
+
- idempotency
|
|
866
|
+
- event IDs
|
|
867
|
+
- processing status
|
|
868
|
+
|
|
869
|
+
---
|
|
870
|
+
|
|
871
|
+
# Feature Flags
|
|
872
|
+
|
|
873
|
+
Provide simple feature flags.
|
|
874
|
+
|
|
875
|
+
Scopes:
|
|
876
|
+
|
|
877
|
+
- global
|
|
878
|
+
- application
|
|
879
|
+
- workspace
|
|
880
|
+
- user
|
|
881
|
+
|
|
882
|
+
Expose:
|
|
883
|
+
|
|
884
|
+
features.isEnabled()
|
|
885
|
+
features.getVariant()
|
|
886
|
+
|
|
887
|
+
Do not implement a complete feature management platform.
|
|
888
|
+
|
|
889
|
+
---
|
|
890
|
+
|
|
891
|
+
# Storage
|
|
892
|
+
|
|
893
|
+
Provide:
|
|
894
|
+
|
|
895
|
+
storage.upload()
|
|
896
|
+
storage.delete()
|
|
897
|
+
storage.getUrl()
|
|
898
|
+
|
|
899
|
+
Use adapters.
|
|
900
|
+
|
|
901
|
+
Do not couple product code to a specific storage provider.
|
|
902
|
+
|
|
903
|
+
---
|
|
904
|
+
|
|
905
|
+
# Security
|
|
906
|
+
|
|
907
|
+
Security is a first-class requirement.
|
|
908
|
+
|
|
909
|
+
Always enforce:
|
|
910
|
+
|
|
911
|
+
- tenant isolation
|
|
912
|
+
- authorization
|
|
913
|
+
- input validation
|
|
914
|
+
- API key security
|
|
915
|
+
- webhook signatures
|
|
916
|
+
- secret isolation
|
|
917
|
+
- session security
|
|
918
|
+
- realtime channel authorization
|
|
919
|
+
- idempotency
|
|
920
|
+
- auditability
|
|
921
|
+
|
|
922
|
+
Never trust client-provided:
|
|
923
|
+
|
|
924
|
+
userId
|
|
925
|
+
workspaceId
|
|
926
|
+
roleId
|
|
927
|
+
permission information
|
|
928
|
+
|
|
929
|
+
Resolve identity from authenticated context.
|
|
930
|
+
|
|
931
|
+
---
|
|
932
|
+
|
|
933
|
+
# Next.js
|
|
934
|
+
|
|
935
|
+
First-class Next.js support.
|
|
936
|
+
|
|
937
|
+
Support:
|
|
938
|
+
|
|
939
|
+
- App Router
|
|
940
|
+
- Server Components
|
|
941
|
+
- Server Actions
|
|
942
|
+
- Route Handlers
|
|
943
|
+
- Middleware where appropriate
|
|
944
|
+
|
|
945
|
+
Server-only modules must not be imported into client components.
|
|
946
|
+
|
|
947
|
+
Never expose server secrets to the client.
|
|
948
|
+
|
|
949
|
+
Prefer server-side operations.
|
|
950
|
+
|
|
951
|
+
---
|
|
952
|
+
|
|
953
|
+
# Testing
|
|
954
|
+
|
|
955
|
+
Use Vitest for unit and integration tests.
|
|
956
|
+
|
|
957
|
+
Use Playwright for end-to-end browser tests.
|
|
958
|
+
|
|
959
|
+
Test:
|
|
960
|
+
|
|
961
|
+
- workspace isolation
|
|
962
|
+
- role assignment
|
|
963
|
+
- multiple roles
|
|
964
|
+
- permission resolution
|
|
965
|
+
- authorization
|
|
966
|
+
- billing webhook idempotency
|
|
967
|
+
- API key validation
|
|
968
|
+
- realtime authorization
|
|
969
|
+
- event typing
|
|
970
|
+
- RxJS stream behavior
|
|
971
|
+
- RxJS subscription cleanup
|
|
972
|
+
- reconnect behavior
|
|
973
|
+
- Trigger.dev integration
|
|
974
|
+
- entitlement checks
|
|
975
|
+
|
|
976
|
+
Realtime tests must verify that users cannot receive events from unauthorized workspaces.
|
|
977
|
+
|
|
978
|
+
RxJS tests should verify:
|
|
979
|
+
|
|
980
|
+
- filtering
|
|
981
|
+
- transformation
|
|
982
|
+
- aggregation
|
|
983
|
+
- debouncing/throttling
|
|
984
|
+
- completion
|
|
985
|
+
- error behavior
|
|
986
|
+
- subscription cleanup
|
|
987
|
+
|
|
988
|
+
---
|
|
989
|
+
|
|
990
|
+
# Database Migration
|
|
991
|
+
|
|
992
|
+
Use Drizzle migrations.
|
|
993
|
+
|
|
994
|
+
Core schema definitions are exported publicly.
|
|
995
|
+
|
|
996
|
+
Product applications may reference core tables.
|
|
997
|
+
|
|
998
|
+
Core migrations must be versioned.
|
|
999
|
+
|
|
1000
|
+
Never modify production schemas manually.
|
|
1001
|
+
|
|
1002
|
+
Destructive changes require a migration strategy.
|
|
1003
|
+
|
|
1004
|
+
---
|
|
1005
|
+
|
|
1006
|
+
# Dependency Rules
|
|
1007
|
+
|
|
1008
|
+
Do not add a dependency unless:
|
|
1009
|
+
|
|
1010
|
+
1. It solves a real problem.
|
|
1011
|
+
2. The problem cannot be solved simply with existing infrastructure.
|
|
1012
|
+
3. The dependency is actively maintained.
|
|
1013
|
+
4. The operational complexity is justified.
|
|
1014
|
+
|
|
1015
|
+
Current infrastructure:
|
|
1016
|
+
|
|
1017
|
+
PostgreSQL -> durable state
|
|
1018
|
+
|
|
1019
|
+
Drizzle -> database access/schema
|
|
1020
|
+
|
|
1021
|
+
WorkOS -> authentication
|
|
1022
|
+
|
|
1023
|
+
Infisical -> secrets/configuration
|
|
1024
|
+
|
|
1025
|
+
Revolut -> payments
|
|
1026
|
+
|
|
1027
|
+
Trigger.dev -> durable execution
|
|
1028
|
+
|
|
1029
|
+
RxJS -> reactive behavior
|
|
1030
|
+
|
|
1031
|
+
Realtime transport -> event delivery
|
|
1032
|
+
|
|
1033
|
+
Next.js -> application runtime
|
|
1034
|
+
|
|
1035
|
+
Do not add Redis by default.
|
|
1036
|
+
|
|
1037
|
+
---
|
|
1038
|
+
|
|
1039
|
+
# Implementation Strategy
|
|
1040
|
+
|
|
1041
|
+
Implement incrementally.
|
|
1042
|
+
|
|
1043
|
+
For every feature:
|
|
1044
|
+
|
|
1045
|
+
1. Define the problem.
|
|
1046
|
+
2. Determine whether it belongs in the core.
|
|
1047
|
+
3. Define the public API.
|
|
1048
|
+
4. Define the database schema.
|
|
1049
|
+
5. Define security boundaries.
|
|
1050
|
+
6. Define failure modes.
|
|
1051
|
+
7. Write tests.
|
|
1052
|
+
8. Implement the smallest useful solution.
|
|
1053
|
+
9. Document the API.
|
|
1054
|
+
|
|
1055
|
+
Do not implement future features speculatively.
|
|
1056
|
+
|
|
1057
|
+
---
|
|
1058
|
+
|
|
1059
|
+
# Recommended Implementation Order
|
|
1060
|
+
|
|
1061
|
+
Phase 1:
|
|
1062
|
+
- Drizzle
|
|
1063
|
+
- PostgreSQL
|
|
1064
|
+
- schema architecture
|
|
1065
|
+
- migrations
|
|
1066
|
+
- configuration
|
|
1067
|
+
- package exports
|
|
1068
|
+
|
|
1069
|
+
Phase 2:
|
|
1070
|
+
- WorkOS
|
|
1071
|
+
- users
|
|
1072
|
+
- profiles
|
|
1073
|
+
|
|
1074
|
+
Phase 3:
|
|
1075
|
+
- workspaces
|
|
1076
|
+
- memberships
|
|
1077
|
+
- invitations
|
|
1078
|
+
|
|
1079
|
+
Phase 4:
|
|
1080
|
+
- roles
|
|
1081
|
+
- permissions
|
|
1082
|
+
- authorization
|
|
1083
|
+
|
|
1084
|
+
Phase 5:
|
|
1085
|
+
- billing
|
|
1086
|
+
- plans
|
|
1087
|
+
- subscriptions
|
|
1088
|
+
- entitlements
|
|
1089
|
+
|
|
1090
|
+
Phase 6:
|
|
1091
|
+
- application events
|
|
1092
|
+
- typed event registry
|
|
1093
|
+
|
|
1094
|
+
Phase 7:
|
|
1095
|
+
- Trigger.dev
|
|
1096
|
+
- durable jobs
|
|
1097
|
+
- scheduled jobs
|
|
1098
|
+
|
|
1099
|
+
Phase 8:
|
|
1100
|
+
- realtime transport
|
|
1101
|
+
- authenticated channels
|
|
1102
|
+
|
|
1103
|
+
Phase 9:
|
|
1104
|
+
- RxJS reactive event streams
|
|
1105
|
+
- client subscriptions
|
|
1106
|
+
- event composition
|
|
1107
|
+
|
|
1108
|
+
Phase 10:
|
|
1109
|
+
- email
|
|
1110
|
+
- notifications
|
|
1111
|
+
- audit logs
|
|
1112
|
+
|
|
1113
|
+
Phase 11:
|
|
1114
|
+
- API keys
|
|
1115
|
+
- webhooks
|
|
1116
|
+
|
|
1117
|
+
Phase 12:
|
|
1118
|
+
- feature flags
|
|
1119
|
+
- storage
|
|
1120
|
+
- observability
|
|
1121
|
+
|
|
1122
|
+
---
|
|
1123
|
+
|
|
1124
|
+
# Definition of Done
|
|
1125
|
+
|
|
1126
|
+
A feature is not complete until:
|
|
1127
|
+
|
|
1128
|
+
- TypeScript passes
|
|
1129
|
+
- tests pass
|
|
1130
|
+
- database migrations are included
|
|
1131
|
+
- security boundaries are tested
|
|
1132
|
+
- public APIs are documented
|
|
1133
|
+
- server/client boundaries are correct
|
|
1134
|
+
- no unnecessary infrastructure was introduced
|
|
1135
|
+
- the implementation is reusable across multiple SaaS applications
|
|
1136
|
+
|
|
1137
|
+
---
|
|
1138
|
+
|
|
1139
|
+
# Golden Rule
|
|
1140
|
+
|
|
1141
|
+
@digimuza/saas-core should answer:
|
|
1142
|
+
|
|
1143
|
+
Who is this user?
|
|
1144
|
+
|
|
1145
|
+
What workspace are they in?
|
|
1146
|
+
|
|
1147
|
+
What can they do?
|
|
1148
|
+
|
|
1149
|
+
What are they entitled to?
|
|
1150
|
+
|
|
1151
|
+
What have they paid for?
|
|
1152
|
+
|
|
1153
|
+
What is happening?
|
|
1154
|
+
|
|
1155
|
+
How do we react to what is happening?
|
|
1156
|
+
|
|
1157
|
+
How do we notify the user?
|
|
1158
|
+
|
|
1159
|
+
How do we run long-running work?
|
|
1160
|
+
|
|
1161
|
+
How do we deliver realtime events?
|
|
1162
|
+
|
|
1163
|
+
How do we audit important actions?
|
|
1164
|
+
|
|
1165
|
+
Everything else belongs to the individual SaaS application.
|