@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":"bucket.js","sourceRoot":"","sources":["../../src/features/bucket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,SAAiB;IACrD,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,GAAG,IAAI,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;IAE3E;;;;OAIG;IACH,OAAO,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;AACtC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CACzB,GAAW,EACX,UAAkB,EAClB,SAA6B;IAE7B,IAAI,UAAU,IAAI,GAAG,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,UAAU,IAAI,CAAC,EAAE,CAAC;QACpB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,UAAU,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FeatureFlagMap, FeatureFlagRegistry } from "./registry.ts";
|
|
2
|
+
import { type FeaturesService } from "./service.ts";
|
|
3
|
+
export { bucketOf, isInRollout } from "./bucket.ts";
|
|
4
|
+
export { defineFeatureFlags, mergeFeatureFlags, type FeatureFlagDefinition, type FeatureFlagKey, type FeatureFlagMap, type FeatureFlagRegistry, type FeatureFlagVariant, } from "./registry.ts";
|
|
5
|
+
export { createFeaturesService, type FeaturesService, type FeaturesServiceDeps, type FlagSubject, type ResolvedFlag, type SetFlagInput, type UnsetFlagInput, } from "./service.ts";
|
|
6
|
+
export { FLAG_SCOPE_PRECEDENCE, type FeatureFlagScope } from "../db/schema/features.ts";
|
|
7
|
+
/**
|
|
8
|
+
* Build a service from configuration, using `APP_NAME` as the application scope.
|
|
9
|
+
*
|
|
10
|
+
* There is no default `features` export: flags are application-defined, so a
|
|
11
|
+
* service with an empty registry could not answer anything. Construct your own
|
|
12
|
+
* with your registry.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createConfiguredFeaturesService<T extends FeatureFlagMap>(registry: FeatureFlagRegistry<T>): FeaturesService<T>;
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/features/index.ts"],"names":[],"mappings":"AAiDA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAE3E,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,qBAAqB,EAAE,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAExF;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAAC,CAAC,SAAS,cAAc,EACtE,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC/B,eAAe,CAAC,CAAC,CAAC,CAepB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public feature flags surface: `@digimuza/saas-core/feature-flags`. Server-only.
|
|
3
|
+
*
|
|
4
|
+
* Simple flags with four scopes, resolved most-specific-first:
|
|
5
|
+
*
|
|
6
|
+
* ```
|
|
7
|
+
* global < application < workspace < user
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* Deliberately **not** a feature management platform. No segments, no rule
|
|
11
|
+
* engines, no audience builders — a flag is on or off, optionally for a
|
|
12
|
+
* percentage, optionally with a variant. Anything more belongs in a product that
|
|
13
|
+
* does only that.
|
|
14
|
+
*
|
|
15
|
+
* ## Declare flags in code
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { defineFeatureFlags, createFeaturesService } from "@digimuza/saas-core/feature-flags";
|
|
19
|
+
* import { getDatabase } from "@digimuza/saas-core/db";
|
|
20
|
+
*
|
|
21
|
+
* export const flags = defineFeatureFlags({
|
|
22
|
+
* "new-editor": { default: false, description: "Rebuilt document editor" },
|
|
23
|
+
* "ranking": { default: true, variants: ["control", "v2"], defaultVariant: "control" },
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* export const features = createFeaturesService({
|
|
27
|
+
* db: getDatabase(),
|
|
28
|
+
* registry: flags,
|
|
29
|
+
* application: "docs-app",
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Definitions live in code so that a fresh database behaves the same as a
|
|
34
|
+
* configured one, a typo is a compile error, and deleting a flag from the code
|
|
35
|
+
* deletes it from the product. Database rows are only *overrides*.
|
|
36
|
+
*
|
|
37
|
+
* ## Read them
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* const subject = { workspaceId: context.workspaceId, userId: user.id };
|
|
41
|
+
*
|
|
42
|
+
* if (await features.isEnabled("new-editor", subject)) { ... }
|
|
43
|
+
*
|
|
44
|
+
* // Better: one query per request, then pass it to the client.
|
|
45
|
+
* const resolved = await features.all(subject);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
import { config } from "../config/index.js";
|
|
49
|
+
import { getDatabase } from "../db/client.js";
|
|
50
|
+
import { createFeaturesService } from "./service.js";
|
|
51
|
+
export { bucketOf, isInRollout } from "./bucket.js";
|
|
52
|
+
export { defineFeatureFlags, mergeFeatureFlags, } from "./registry.js";
|
|
53
|
+
export { createFeaturesService, } from "./service.js";
|
|
54
|
+
export { FLAG_SCOPE_PRECEDENCE } from "../db/schema/features.js";
|
|
55
|
+
/**
|
|
56
|
+
* Build a service from configuration, using `APP_NAME` as the application scope.
|
|
57
|
+
*
|
|
58
|
+
* There is no default `features` export: flags are application-defined, so a
|
|
59
|
+
* service with an empty registry could not answer anything. Construct your own
|
|
60
|
+
* with your registry.
|
|
61
|
+
*/
|
|
62
|
+
export function createConfiguredFeaturesService(registry) {
|
|
63
|
+
return createFeaturesService({
|
|
64
|
+
/**
|
|
65
|
+
* Both dependencies are getters, so the `app` and `database` configuration
|
|
66
|
+
* sections resolve on first use rather than at import — constructing the
|
|
67
|
+
* service never requires configuration to be present.
|
|
68
|
+
*/
|
|
69
|
+
get db() {
|
|
70
|
+
return getDatabase();
|
|
71
|
+
},
|
|
72
|
+
registry,
|
|
73
|
+
get application() {
|
|
74
|
+
return config.app.name;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/features/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAE,qBAAqB,EAAwB,MAAM,cAAc,CAAC;AAE3E,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EACL,kBAAkB,EAClB,iBAAiB,GAMlB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,qBAAqB,GAOtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,qBAAqB,EAAyB,MAAM,0BAA0B,CAAC;AAExF;;;;;;GAMG;AACH,MAAM,UAAU,+BAA+B,CAC7C,QAAgC;IAEhC,OAAO,qBAAqB,CAAC;QAC3B;;;;WAIG;QACH,IAAI,EAAE;YACJ,OAAO,WAAW,EAAE,CAAC;QACvB,CAAC;QACD,QAAQ;QACR,IAAI,WAAW;YACb,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;QACzB,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A flag, declared in code.
|
|
3
|
+
*
|
|
4
|
+
* The definition lives here rather than in the database so that a fresh database
|
|
5
|
+
* behaves the same as a configured one, a typo in a key is a compile error, and
|
|
6
|
+
* deleting the flag from the code deletes it from the product. Database rows are
|
|
7
|
+
* only *overrides* — deviations from these defaults.
|
|
8
|
+
*/
|
|
9
|
+
export interface FeatureFlagDefinition {
|
|
10
|
+
/** Value when no override matches. */
|
|
11
|
+
readonly default: boolean;
|
|
12
|
+
readonly description?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Permitted variant values.
|
|
15
|
+
*
|
|
16
|
+
* Declaring them makes `set` reject a typo at write time — the alternative is
|
|
17
|
+
* discovering it when a variant silently matches nothing in a switch.
|
|
18
|
+
*/
|
|
19
|
+
readonly variants?: readonly string[];
|
|
20
|
+
/** Variant when no override supplies one. */
|
|
21
|
+
readonly defaultVariant?: string;
|
|
22
|
+
}
|
|
23
|
+
export type FeatureFlagMap = Record<string, FeatureFlagDefinition>;
|
|
24
|
+
export interface FeatureFlagRegistry<T extends FeatureFlagMap = FeatureFlagMap> {
|
|
25
|
+
readonly flags: T;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Declare an application's flags.
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* export const appFlags = defineFeatureFlags({
|
|
32
|
+
* "new-editor": { default: false, description: "Rebuilt document editor" },
|
|
33
|
+
* "ranking": { default: true, variants: ["control", "v2"], defaultVariant: "control" },
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* The `const` type parameter preserves the literal keys, which is what makes
|
|
38
|
+
* `isEnabled("new-editor")` autocomplete and a misspelling fail to compile.
|
|
39
|
+
*/
|
|
40
|
+
export declare function defineFeatureFlags<const T extends FeatureFlagMap>(flags: T): FeatureFlagRegistry<T>;
|
|
41
|
+
/**
|
|
42
|
+
* Combine registries. Duplicate keys throw.
|
|
43
|
+
*
|
|
44
|
+
* Two modules each defining `"new-editor"` with different defaults would make the
|
|
45
|
+
* flag's behaviour depend on import order.
|
|
46
|
+
*/
|
|
47
|
+
export declare function mergeFeatureFlags<A extends FeatureFlagMap, B extends FeatureFlagMap>(first: FeatureFlagRegistry<A>, second: FeatureFlagRegistry<B>): FeatureFlagRegistry<A & B>;
|
|
48
|
+
export type FeatureFlagKey<T extends FeatureFlagMap> = keyof T & string;
|
|
49
|
+
/** The variant type for one flag: its declared union, or `string`. */
|
|
50
|
+
export type FeatureFlagVariant<T extends FeatureFlagMap, K extends FeatureFlagKey<T>> = T[K]["variants"] extends readonly (infer V)[] ? V : string;
|
|
51
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/features/registry.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAqB;IACpC,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,6CAA6C;IAC7C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;AAEnE,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc;IAC5E,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,CAAC,SAAS,cAAc,EAC/D,KAAK,EAAE,CAAC,GACP,mBAAmB,CAAC,CAAC,CAAC,CAgCxB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,cAAc,EAAE,CAAC,SAAS,cAAc,EAClF,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAC7B,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC7B,mBAAmB,CAAC,CAAC,GAAG,CAAC,CAAC,CAQ5B;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,cAAc,IAAI,MAAM,CAAC,GAAG,MAAM,CAAC;AAExE,sEAAsE;AACtE,MAAM,MAAM,kBAAkB,CAC5B,CAAC,SAAS,cAAc,EACxB,CAAC,SAAS,cAAc,CAAC,CAAC,CAAC,IACzB,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { InvalidInputError } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Declare an application's flags.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* export const appFlags = defineFeatureFlags({
|
|
7
|
+
* "new-editor": { default: false, description: "Rebuilt document editor" },
|
|
8
|
+
* "ranking": { default: true, variants: ["control", "v2"], defaultVariant: "control" },
|
|
9
|
+
* });
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* The `const` type parameter preserves the literal keys, which is what makes
|
|
13
|
+
* `isEnabled("new-editor")` autocomplete and a misspelling fail to compile.
|
|
14
|
+
*/
|
|
15
|
+
export function defineFeatureFlags(flags) {
|
|
16
|
+
for (const [key, definition] of Object.entries(flags)) {
|
|
17
|
+
if (!/^[a-z0-9][a-z0-9._-]{0,62}[a-z0-9]$/.test(key)) {
|
|
18
|
+
/**
|
|
19
|
+
* Keys reach URLs, log lines and client payloads. Constraining them here
|
|
20
|
+
* avoids having to escape them everywhere downstream.
|
|
21
|
+
*/
|
|
22
|
+
throw new InvalidInputError(`"${key}" is not a valid flag key: use lowercase alphanumerics, dots, dashes or underscores.`);
|
|
23
|
+
}
|
|
24
|
+
if (definition.variants !== undefined) {
|
|
25
|
+
if (definition.variants.length === 0) {
|
|
26
|
+
throw new InvalidInputError(`Flag "${key}" declares an empty variant list.`);
|
|
27
|
+
}
|
|
28
|
+
if (definition.defaultVariant !== undefined &&
|
|
29
|
+
!definition.variants.includes(definition.defaultVariant)) {
|
|
30
|
+
throw new InvalidInputError(`Flag "${key}" has a defaultVariant that is not among its variants.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (definition.defaultVariant !== undefined) {
|
|
34
|
+
throw new InvalidInputError(`Flag "${key}" declares a defaultVariant without declaring variants.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { flags };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Combine registries. Duplicate keys throw.
|
|
41
|
+
*
|
|
42
|
+
* Two modules each defining `"new-editor"` with different defaults would make the
|
|
43
|
+
* flag's behaviour depend on import order.
|
|
44
|
+
*/
|
|
45
|
+
export function mergeFeatureFlags(first, second) {
|
|
46
|
+
const collisions = Object.keys(second.flags).filter((key) => key in first.flags);
|
|
47
|
+
if (collisions.length > 0) {
|
|
48
|
+
throw new InvalidInputError(`Feature flags already defined: ${collisions.join(", ")}.`);
|
|
49
|
+
}
|
|
50
|
+
return { flags: { ...first.flags, ...second.flags } };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/features/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AA+BjD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAQ;IAER,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACtD,IAAI,CAAC,qCAAqC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACrD;;;eAGG;YACH,MAAM,IAAI,iBAAiB,CACzB,IAAI,GAAG,sFAAsF,CAC9F,CAAC;QACJ,CAAC;QAED,IAAI,UAAU,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YACtC,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACrC,MAAM,IAAI,iBAAiB,CAAC,SAAS,GAAG,mCAAmC,CAAC,CAAC;YAC/E,CAAC;YACD,IACE,UAAU,CAAC,cAAc,KAAK,SAAS;gBACvC,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,cAAc,CAAC,EACxD,CAAC;gBACD,MAAM,IAAI,iBAAiB,CACzB,SAAS,GAAG,wDAAwD,CACrE,CAAC;YACJ,CAAC;QACH,CAAC;aAAM,IAAI,UAAU,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;YACnD,MAAM,IAAI,iBAAiB,CACzB,SAAS,GAAG,yDAAyD,CACtE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAA6B,EAC7B,MAA8B;IAE9B,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC;IAEjF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,iBAAiB,CAAC,kCAAkC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1F,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { FeatureFlagRow, FeatureFlagScope } from "../db/schema/index.ts";
|
|
2
|
+
import type { CoreDatabase } from "../db/types.ts";
|
|
3
|
+
import type { FeatureFlagKey, FeatureFlagMap, FeatureFlagRegistry, FeatureFlagVariant } from "./registry.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Who a flag is being evaluated for.
|
|
6
|
+
*
|
|
7
|
+
* Plain ids, deliberately, rather than a `WorkspaceContext`. Flags are read many
|
|
8
|
+
* times per request — every conditional render — and a membership check per read
|
|
9
|
+
* would make them expensive enough that people would cache them badly. A flag
|
|
10
|
+
* state is configuration, not tenant data.
|
|
11
|
+
*
|
|
12
|
+
* Pass ids that came from a verified context. The service will not check for you.
|
|
13
|
+
*/
|
|
14
|
+
export interface FlagSubject {
|
|
15
|
+
readonly workspaceId?: string;
|
|
16
|
+
readonly userId?: string;
|
|
17
|
+
}
|
|
18
|
+
/** A resolved flag, with enough detail to explain itself. */
|
|
19
|
+
export interface ResolvedFlag {
|
|
20
|
+
readonly key: string;
|
|
21
|
+
readonly enabled: boolean;
|
|
22
|
+
readonly variant: string | undefined;
|
|
23
|
+
/** Which scope decided it. `"default"` means no override matched. */
|
|
24
|
+
readonly source: FeatureFlagScope | "default";
|
|
25
|
+
}
|
|
26
|
+
export interface SetFlagInput {
|
|
27
|
+
readonly key: string;
|
|
28
|
+
readonly scope: FeatureFlagScope;
|
|
29
|
+
/** Required for `scope: "application"`. */
|
|
30
|
+
readonly application?: string;
|
|
31
|
+
/** Required for `scope: "workspace"`. */
|
|
32
|
+
readonly workspaceId?: string;
|
|
33
|
+
/** Required for `scope: "user"`. */
|
|
34
|
+
readonly userId?: string;
|
|
35
|
+
readonly enabled: boolean;
|
|
36
|
+
readonly variant?: string | null;
|
|
37
|
+
/** 0-100. Null or omitted means everyone the override applies to. */
|
|
38
|
+
readonly rolloutPercentage?: number | null;
|
|
39
|
+
readonly description?: string;
|
|
40
|
+
}
|
|
41
|
+
export type UnsetFlagInput = Pick<SetFlagInput, "key" | "scope" | "application" | "workspaceId" | "userId">;
|
|
42
|
+
export interface FeaturesService<T extends FeatureFlagMap> {
|
|
43
|
+
/** Whether a flag is on for this subject. */
|
|
44
|
+
isEnabled(key: FeatureFlagKey<T>, subject?: FlagSubject): Promise<boolean>;
|
|
45
|
+
/**
|
|
46
|
+
* The active variant, or `undefined`.
|
|
47
|
+
*
|
|
48
|
+
* A disabled flag has no variant: returning one would invite code that reads
|
|
49
|
+
* the variant without checking `isEnabled` first.
|
|
50
|
+
*/
|
|
51
|
+
getVariant<K extends FeatureFlagKey<T>>(key: K, subject?: FlagSubject): Promise<FeatureFlagVariant<T, K> | undefined>;
|
|
52
|
+
/** One flag, with the scope that decided it. For a debug view. */
|
|
53
|
+
resolve(key: FeatureFlagKey<T>, subject?: FlagSubject): Promise<ResolvedFlag>;
|
|
54
|
+
/**
|
|
55
|
+
* Every flag, resolved in **one** query.
|
|
56
|
+
*
|
|
57
|
+
* The recommended path: resolve once per request and pass the result to the
|
|
58
|
+
* client, rather than querying per conditional.
|
|
59
|
+
*/
|
|
60
|
+
all(subject?: FlagSubject): Promise<Map<FeatureFlagKey<T>, ResolvedFlag>>;
|
|
61
|
+
/** Create or replace an override. Idempotent. */
|
|
62
|
+
set(input: SetFlagInput): Promise<FeatureFlagRow>;
|
|
63
|
+
/** Remove an override, falling back to the next-broadest scope. */
|
|
64
|
+
unset(input: UnsetFlagInput): Promise<void>;
|
|
65
|
+
/** Every stored override for a key, broadest first. */
|
|
66
|
+
overrides(key: FeatureFlagKey<T>): Promise<FeatureFlagRow[]>;
|
|
67
|
+
}
|
|
68
|
+
export interface FeaturesServiceDeps<T extends FeatureFlagMap> {
|
|
69
|
+
readonly db: CoreDatabase;
|
|
70
|
+
readonly registry: FeatureFlagRegistry<T>;
|
|
71
|
+
/**
|
|
72
|
+
* Which application this process is. Required for `application`-scoped
|
|
73
|
+
* overrides to match — without it they are ignored rather than guessed at.
|
|
74
|
+
*/
|
|
75
|
+
readonly application?: string;
|
|
76
|
+
}
|
|
77
|
+
export declare function createFeaturesService<T extends FeatureFlagMap>(deps: FeaturesServiceDeps<T>): FeaturesService<T>;
|
|
78
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/features/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { and, eq, inArray, isNull, or } from "drizzle-orm";
|
|
2
|
+
import { featureFlags, FLAG_SCOPE_PRECEDENCE } from "../db/schema/index.js";
|
|
3
|
+
import { InvalidInputError, NotFoundError } from "../errors.js";
|
|
4
|
+
import { isInRollout } from "./bucket.js";
|
|
5
|
+
export function createFeaturesService(deps) {
|
|
6
|
+
function definition(key) {
|
|
7
|
+
const found = deps.registry.flags[key];
|
|
8
|
+
if (found === undefined) {
|
|
9
|
+
/**
|
|
10
|
+
* Reachable only from untyped JavaScript. Throwing beats defaulting to
|
|
11
|
+
* `false`: a flag that silently never turns on is far harder to diagnose
|
|
12
|
+
* than one that fails loudly at the call site.
|
|
13
|
+
*/
|
|
14
|
+
throw new InvalidInputError(`Unknown feature flag "${key}".`);
|
|
15
|
+
}
|
|
16
|
+
return found;
|
|
17
|
+
}
|
|
18
|
+
/** Overrides that could apply to this subject, for these keys. */
|
|
19
|
+
async function loadOverrides(keys, subject) {
|
|
20
|
+
const targets = [
|
|
21
|
+
// Global always applies.
|
|
22
|
+
and(eq(featureFlags.scope, "global")),
|
|
23
|
+
...(deps.application !== undefined
|
|
24
|
+
? [and(eq(featureFlags.scope, "application"), eq(featureFlags.application, deps.application))]
|
|
25
|
+
: []),
|
|
26
|
+
...(subject.workspaceId !== undefined
|
|
27
|
+
? [and(eq(featureFlags.scope, "workspace"), eq(featureFlags.workspaceId, subject.workspaceId))]
|
|
28
|
+
: []),
|
|
29
|
+
...(subject.userId !== undefined
|
|
30
|
+
? [and(eq(featureFlags.scope, "user"), eq(featureFlags.userId, subject.userId))]
|
|
31
|
+
: []),
|
|
32
|
+
];
|
|
33
|
+
return deps.db
|
|
34
|
+
.select()
|
|
35
|
+
.from(featureFlags)
|
|
36
|
+
.where(and(inArray(featureFlags.key, [...keys]), or(...targets)));
|
|
37
|
+
}
|
|
38
|
+
/** Apply precedence: the most specific matching override wins. */
|
|
39
|
+
function decide(key, rows, subject) {
|
|
40
|
+
const flag = definition(key);
|
|
41
|
+
const relevant = rows.filter((row) => row.key === key);
|
|
42
|
+
let winner;
|
|
43
|
+
let winnerRank = -1;
|
|
44
|
+
for (const row of relevant) {
|
|
45
|
+
const rank = FLAG_SCOPE_PRECEDENCE.indexOf(row.scope);
|
|
46
|
+
if (rank > winnerRank) {
|
|
47
|
+
winner = row;
|
|
48
|
+
winnerRank = rank;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (winner === undefined) {
|
|
52
|
+
return {
|
|
53
|
+
key,
|
|
54
|
+
enabled: flag.default,
|
|
55
|
+
variant: flag.default ? flag.defaultVariant : undefined,
|
|
56
|
+
source: "default",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Rollout narrows an override; it never turns one on. So a disabled override
|
|
61
|
+
* stays disabled regardless of percentage, and `rollout: 20` on an enabled
|
|
62
|
+
* override means "on for 20% of the subjects this override covers".
|
|
63
|
+
*/
|
|
64
|
+
let enabled = winner.enabled;
|
|
65
|
+
if (enabled && winner.rolloutPercentage !== null) {
|
|
66
|
+
// Bucket on the most specific subject available, so a per-user rollout is
|
|
67
|
+
// stable per user and a per-workspace one is stable per workspace.
|
|
68
|
+
const subjectId = subject.userId ?? subject.workspaceId;
|
|
69
|
+
enabled = isInRollout(key, winner.rolloutPercentage, subjectId);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
key,
|
|
73
|
+
enabled,
|
|
74
|
+
// A disabled flag has no variant; see getVariant.
|
|
75
|
+
variant: enabled ? (winner.variant ?? flag.defaultVariant) : undefined,
|
|
76
|
+
source: winner.scope,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function assertTarget(input) {
|
|
80
|
+
const expectations = {
|
|
81
|
+
global: undefined,
|
|
82
|
+
application: "application",
|
|
83
|
+
workspace: "workspaceId",
|
|
84
|
+
user: "userId",
|
|
85
|
+
};
|
|
86
|
+
const required = expectations[input.scope];
|
|
87
|
+
if (required !== undefined && input[required] === undefined) {
|
|
88
|
+
throw new InvalidInputError(`A "${input.scope}" override requires ${String(required)}.`);
|
|
89
|
+
}
|
|
90
|
+
// Reject a target that contradicts the scope, rather than silently ignoring
|
|
91
|
+
// it — `{ scope: "global", workspaceId }` is always a mistake.
|
|
92
|
+
for (const [scope, field] of Object.entries(expectations)) {
|
|
93
|
+
if (field !== undefined && scope !== input.scope && input[field] !== undefined) {
|
|
94
|
+
throw new InvalidInputError(`A "${input.scope}" override must not set ${String(field)}.`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Match exactly one target row, treating absent columns as null. */
|
|
99
|
+
function targetFilter(input) {
|
|
100
|
+
return and(eq(featureFlags.key, input.key), eq(featureFlags.scope, input.scope), input.application === undefined
|
|
101
|
+
? isNull(featureFlags.application)
|
|
102
|
+
: eq(featureFlags.application, input.application), input.workspaceId === undefined
|
|
103
|
+
? isNull(featureFlags.workspaceId)
|
|
104
|
+
: eq(featureFlags.workspaceId, input.workspaceId), input.userId === undefined
|
|
105
|
+
? isNull(featureFlags.userId)
|
|
106
|
+
: eq(featureFlags.userId, input.userId));
|
|
107
|
+
}
|
|
108
|
+
const service = {
|
|
109
|
+
async resolve(key, subject = {}) {
|
|
110
|
+
const rows = await loadOverrides([key], subject);
|
|
111
|
+
return decide(key, rows, subject);
|
|
112
|
+
},
|
|
113
|
+
async isEnabled(key, subject = {}) {
|
|
114
|
+
return (await service.resolve(key, subject)).enabled;
|
|
115
|
+
},
|
|
116
|
+
async getVariant(key, subject = {}) {
|
|
117
|
+
const resolved = await service.resolve(key, subject);
|
|
118
|
+
return resolved.variant;
|
|
119
|
+
},
|
|
120
|
+
async all(subject = {}) {
|
|
121
|
+
const keys = Object.keys(deps.registry.flags);
|
|
122
|
+
const rows = keys.length === 0 ? [] : await loadOverrides(keys, subject);
|
|
123
|
+
const result = new Map();
|
|
124
|
+
for (const key of keys) {
|
|
125
|
+
result.set(key, decide(key, rows, subject));
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
},
|
|
129
|
+
async set(input) {
|
|
130
|
+
const flag = definition(input.key);
|
|
131
|
+
assertTarget(input);
|
|
132
|
+
if (input.variant != null) {
|
|
133
|
+
if (flag.variants === undefined) {
|
|
134
|
+
throw new InvalidInputError(`Flag "${input.key}" does not declare variants.`);
|
|
135
|
+
}
|
|
136
|
+
if (!flag.variants.includes(input.variant)) {
|
|
137
|
+
throw new InvalidInputError(`"${input.variant}" is not a declared variant of "${input.key}".`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (input.rolloutPercentage != null) {
|
|
141
|
+
if (!Number.isInteger(input.rolloutPercentage) ||
|
|
142
|
+
input.rolloutPercentage < 0 ||
|
|
143
|
+
input.rolloutPercentage > 100) {
|
|
144
|
+
throw new InvalidInputError("rolloutPercentage must be an integer between 0 and 100.");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const values = {
|
|
148
|
+
key: input.key,
|
|
149
|
+
scope: input.scope,
|
|
150
|
+
application: input.application ?? null,
|
|
151
|
+
workspaceId: input.workspaceId ?? null,
|
|
152
|
+
userId: input.userId ?? null,
|
|
153
|
+
enabled: input.enabled,
|
|
154
|
+
variant: input.variant ?? null,
|
|
155
|
+
rolloutPercentage: input.rolloutPercentage ?? null,
|
|
156
|
+
description: input.description ?? null,
|
|
157
|
+
};
|
|
158
|
+
const [row] = await deps.db
|
|
159
|
+
.insert(featureFlags)
|
|
160
|
+
.values(values)
|
|
161
|
+
/**
|
|
162
|
+
* Idempotent by target. Setting the same override twice updates it rather
|
|
163
|
+
* than failing, which is what an admin UI and a seeding script both want.
|
|
164
|
+
*/
|
|
165
|
+
.onConflictDoUpdate({
|
|
166
|
+
target: [
|
|
167
|
+
featureFlags.key,
|
|
168
|
+
featureFlags.scope,
|
|
169
|
+
featureFlags.application,
|
|
170
|
+
featureFlags.workspaceId,
|
|
171
|
+
featureFlags.userId,
|
|
172
|
+
],
|
|
173
|
+
set: {
|
|
174
|
+
enabled: values.enabled,
|
|
175
|
+
variant: values.variant,
|
|
176
|
+
rolloutPercentage: values.rolloutPercentage,
|
|
177
|
+
description: values.description,
|
|
178
|
+
updatedAt: new Date(),
|
|
179
|
+
},
|
|
180
|
+
})
|
|
181
|
+
.returning();
|
|
182
|
+
return row;
|
|
183
|
+
},
|
|
184
|
+
async unset(input) {
|
|
185
|
+
definition(input.key);
|
|
186
|
+
assertTarget(input);
|
|
187
|
+
const deleted = await deps.db
|
|
188
|
+
.delete(featureFlags)
|
|
189
|
+
.where(targetFilter(input))
|
|
190
|
+
.returning({ id: featureFlags.id });
|
|
191
|
+
if (deleted.length === 0) {
|
|
192
|
+
throw new NotFoundError("Feature flag override not found.");
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
async overrides(key) {
|
|
196
|
+
definition(key);
|
|
197
|
+
const rows = await deps.db
|
|
198
|
+
.select()
|
|
199
|
+
.from(featureFlags)
|
|
200
|
+
.where(eq(featureFlags.key, key));
|
|
201
|
+
// Broadest first, matching the order they are applied in.
|
|
202
|
+
return rows.sort((a, b) => FLAG_SCOPE_PRECEDENCE.indexOf(a.scope) - FLAG_SCOPE_PRECEDENCE.indexOf(b.scope));
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
return service;
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/features/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type FilesService } from "./service.ts";
|
|
2
|
+
export { createFilesService, fileStoragePath, sanitizeFilename, type CreateUploadInput, type DirectUpload, type FilesService, type FilesServiceDeps, type ListFilesOptions, type ServerUploadInput, } from "./service.ts";
|
|
3
|
+
export type { FileRecord, FileStatus } from "../db/schema/files.ts";
|
|
4
|
+
/** Default service, bound to the configured database and object store. */
|
|
5
|
+
export declare const files: FilesService;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/files/index.ts"],"names":[],"mappings":"AAsDA,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAErE,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,GACvB,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEpE,0EAA0E;AAC1E,eAAO,MAAM,KAAK,EAAE,YAKlB,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public files surface: `@digimuza/saas-core/files`. Server-only.
|
|
3
|
+
*
|
|
4
|
+
* The durable record of a stored object: where the bytes are, what they are, who
|
|
5
|
+
* uploaded them, and whether the upload actually finished.
|
|
6
|
+
*
|
|
7
|
+
* It pairs with [`storage`](../storage/README.md), which moves bytes and knows
|
|
8
|
+
* nothing about rows. The split matters for the direct-upload flow: the server
|
|
9
|
+
* hands the browser a presigned URL and the bytes travel out-of-band, so there has
|
|
10
|
+
* to be a row *before* anything is stored and a way to confirm afterwards.
|
|
11
|
+
*
|
|
12
|
+
* The core owns the **record**, not the **association**. A product table
|
|
13
|
+
* references `files.id` to say what a file is *for* — an avatar, an import, an
|
|
14
|
+
* attachment — because only the product knows.
|
|
15
|
+
*
|
|
16
|
+
* ## Direct browser upload
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* // 1. Reserve. Row is `pending`; nothing treats the file as real yet.
|
|
20
|
+
* const { file, uploadUrl } = await files.createUpload(context, {
|
|
21
|
+
* filename: "quarterly.csv",
|
|
22
|
+
* contentType: "text/csv",
|
|
23
|
+
* sizeBytes: declaredSize,
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // 2. The browser PUTs the bytes to `uploadUrl` directly.
|
|
27
|
+
*
|
|
28
|
+
* // 3. Confirm. Asks the store how big the object really is.
|
|
29
|
+
* const ready = await files.confirmUpload(context, file.id);
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* ## Server-side upload
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* const file = await files.upload(context, {
|
|
36
|
+
* filename: "report.pdf",
|
|
37
|
+
* body: bytes,
|
|
38
|
+
* contentType: "application/pdf",
|
|
39
|
+
* });
|
|
40
|
+
* // Already `ready`, with a checksum — the server saw the bytes.
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* ## Reading and cleanup
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* const url = await files.getUrl(context, file.id); // named, attachment
|
|
47
|
+
* await files.delete(context, file.id); // object, then row
|
|
48
|
+
*
|
|
49
|
+
* // From a scheduled job: reservations whose upload never happened.
|
|
50
|
+
* await files.purgeAbandoned();
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
import { getDatabase } from "../db/client.js";
|
|
54
|
+
import { storage } from "../storage/index.js";
|
|
55
|
+
import { createFilesService } from "./service.js";
|
|
56
|
+
export { createFilesService, fileStoragePath, sanitizeFilename, } from "./service.js";
|
|
57
|
+
/** Default service, bound to the configured database and object store. */
|
|
58
|
+
export const files = createFilesService({
|
|
59
|
+
get db() {
|
|
60
|
+
return getDatabase();
|
|
61
|
+
},
|
|
62
|
+
storage,
|
|
63
|
+
});
|
|
64
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/files/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAqB,MAAM,cAAc,CAAC;AAErE,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,gBAAgB,GAOjB,MAAM,cAAc,CAAC;AAItB,0EAA0E;AAC1E,MAAM,CAAC,MAAM,KAAK,GAAiB,kBAAkB,CAAC;IACpD,IAAI,EAAE;QACJ,OAAO,WAAW,EAAE,CAAC;IACvB,CAAC;IACD,OAAO;CACR,CAAC,CAAC"}
|