@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/docs/files.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Files
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import { files, createFilesService } from "@digimuza/saas-core/files";
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
> Orientation and recipes: [`src/files/README.md`](../src/files/README.md).
|
|
8
|
+
|
|
9
|
+
The durable record of a stored object. Pairs with [storage](./storage.md), which
|
|
10
|
+
moves bytes and knows nothing about rows.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Why the record is separate from the bytes
|
|
15
|
+
|
|
16
|
+
Direct browser uploads make it necessary. The server issues a presigned URL and the
|
|
17
|
+
client sends the bytes out-of-band, so:
|
|
18
|
+
|
|
19
|
+
1. A row must exist **before** anything is stored — to reserve an id, derive a key,
|
|
20
|
+
and record intent.
|
|
21
|
+
2. Something must **confirm** afterwards, because the server never sees the
|
|
22
|
+
transfer.
|
|
23
|
+
|
|
24
|
+
Hence `status`: `pending` → `ready`, or `failed`.
|
|
25
|
+
|
|
26
|
+
## The core owns the record, not the association
|
|
27
|
+
|
|
28
|
+
A product table references `files.id` to say what a file is *for*:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
export const documents = pgTable("documents", {
|
|
32
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
33
|
+
fileId: uuid("file_id").notNull().references(() => files.id, { onDelete: "cascade" }),
|
|
34
|
+
title: text("title").notNull(),
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
A core table could not know whether a file is an avatar, an import or an
|
|
39
|
+
attachment, so it does not try.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Direct browser upload
|
|
44
|
+
|
|
45
|
+
### `files.createUpload(context, input)`
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
function createUpload(context: WorkspaceContext, input: {
|
|
49
|
+
filename: string;
|
|
50
|
+
contentType?: string;
|
|
51
|
+
sizeBytes?: number; // advisory — see below
|
|
52
|
+
metadata?: Record<string, unknown>;
|
|
53
|
+
expiresInSeconds?: number; // default 900
|
|
54
|
+
maxBytes?: number;
|
|
55
|
+
}): Promise<{ file: FileRecord; uploadUrl: string }>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Creates a `pending` row and returns a URL the browser can `PUT` to.
|
|
59
|
+
|
|
60
|
+
**The storage key is derived from the row id**, which is why the row is inserted
|
|
61
|
+
first. So a filename never becomes part of a path:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const { file } = await files.createUpload(context, { filename: "../../etc/passwd" });
|
|
65
|
+
file.storageKey; // workspaces/<id>/files/<uuid>
|
|
66
|
+
file.filename; // "passwd"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`sizeBytes` only avoids issuing a URL for something obviously too big — a presigned
|
|
70
|
+
`PUT` cannot enforce a size.
|
|
71
|
+
|
|
72
|
+
`fileStoragePath(fileId)` is the mapping — `files/<id>` — exported so a migration
|
|
73
|
+
or a reconciliation script can compute a key from a row without going through the
|
|
74
|
+
service.
|
|
75
|
+
|
|
76
|
+
### `files.confirmUpload(context, fileId)`
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
function confirmUpload(context: WorkspaceContext, fileId: string): Promise<FileRecord>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Asks the store.** A client reporting success is not evidence: without this you
|
|
83
|
+
could mark a file ready that was never uploaded, and every consumer downstream
|
|
84
|
+
would fail on a missing object.
|
|
85
|
+
|
|
86
|
+
| Outcome | Result |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| Object present, within limit | `ready`, with the store's **true** size |
|
|
89
|
+
| No object | row `failed`, throws `ConflictError` |
|
|
90
|
+
| Object over the limit | object **deleted**, row `failed`, throws `InvalidInputError` |
|
|
91
|
+
| Already `ready` | returned unchanged — idempotent |
|
|
92
|
+
|
|
93
|
+
Deleting an oversized object matters: leaving it would keep billing for bytes
|
|
94
|
+
nothing can reach.
|
|
95
|
+
|
|
96
|
+
It also backfills `contentType` from the store when the client declared none.
|
|
97
|
+
|
|
98
|
+
Uses the provider's optional `stat()`. A provider without it falls back to
|
|
99
|
+
`exists()`, which confirms presence but cannot verify size.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Server-side upload
|
|
104
|
+
|
|
105
|
+
### `files.upload(context, input)`
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
function upload(context: WorkspaceContext, input: {
|
|
109
|
+
filename: string;
|
|
110
|
+
body: Uint8Array | string;
|
|
111
|
+
contentType?: string;
|
|
112
|
+
metadata?: Record<string, unknown>;
|
|
113
|
+
maxBytes?: number;
|
|
114
|
+
}): Promise<FileRecord>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
One step, `ready` immediately — and with a **checksum**, because the server saw the
|
|
118
|
+
bytes. A direct upload has no checksum for exactly that reason.
|
|
119
|
+
|
|
120
|
+
If the store rejects the write, the row is **deleted** rather than left `pending`:
|
|
121
|
+
a failed server-side upload leaves no object, so a reservation would be pure noise
|
|
122
|
+
in the sweep.
|
|
123
|
+
|
|
124
|
+
### `files.findByChecksum(context, checksum)`
|
|
125
|
+
|
|
126
|
+
Deduplication. Scoped to the workspace, and only matches `ready` files.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Reading, listing, deleting
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
files.get(context, fileId): Promise<FileRecord>
|
|
134
|
+
files.list(context, options?): Promise<FileRecord[]>
|
|
135
|
+
files.getUrl(context, fileId, options?): Promise<string>
|
|
136
|
+
files.delete(context, fileId): Promise<void>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`list` filters by `status` (one or several), paginates with `before`, defaults to 50
|
|
140
|
+
and caps at 200. Newest first, tie-broken on id so keyset pagination cannot loop.
|
|
141
|
+
|
|
142
|
+
`getUrl` defaults `downloadFilename` to the stored filename, so a download is named
|
|
143
|
+
sensibly and served as an **attachment** — which is what makes user uploads safe to
|
|
144
|
+
hand back. See [storage](./storage.md#urls). It throws `ConflictError` for a file
|
|
145
|
+
that is not `ready`, because there is no object behind it and the URL would 404.
|
|
146
|
+
|
|
147
|
+
`delete` removes the **object first, then the row**. The other order would orphan
|
|
148
|
+
the object on failure: unreachable, and still billed. There is a test asserting the
|
|
149
|
+
row survives when the store delete fails.
|
|
150
|
+
|
|
151
|
+
Every method takes a verified `WorkspaceContext` and scopes on it, so another
|
|
152
|
+
tenant's file id is simply not found.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## `files.purgeAbandoned(options?)`
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
function purgeAbandoned(options?: { olderThan?: Date; limit?: number }): Promise<number>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
A presigned URL that is never used leaves a `pending` row forever. Run this from a
|
|
163
|
+
scheduled job:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
export const sweepFiles = schedules.task({
|
|
167
|
+
id: "sweep-files",
|
|
168
|
+
cron: "0 * * * *",
|
|
169
|
+
run: () => files.purgeAbandoned(),
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Deletes the object unconditionally first — a reservation may have a half-finished
|
|
174
|
+
object behind it. If the store delete fails, the **row is kept** so the next sweep
|
|
175
|
+
retries rather than losing the record.
|
|
176
|
+
|
|
177
|
+
Default cutoff is 24 hours, comfortably beyond any upload URL's lifetime.
|
|
178
|
+
|
|
179
|
+
> **Two clocks.** `created_at` defaults to PostgreSQL's `now()`, while the default
|
|
180
|
+
> cutoff uses the service's injected clock. A day of slack makes any realistic skew
|
|
181
|
+
> between an application server and the database irrelevant — pass `olderThan` if
|
|
182
|
+
> you need it exact.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## `sanitizeFilename(filename)`
|
|
187
|
+
|
|
188
|
+
The filename never becomes part of a key, so this is **not** a traversal defence.
|
|
189
|
+
It exists because the value is displayed and put in a `Content-Disposition` header:
|
|
190
|
+
|
|
191
|
+
- keeps only the leaf, so `C:\Users\me\report.pdf` becomes `report.pdf`
|
|
192
|
+
- strips control characters, which can break a header
|
|
193
|
+
- strips leading dots
|
|
194
|
+
- caps at 255 characters, so it cannot break every UI that renders it
|
|
195
|
+
- throws when nothing usable remains
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Lifecycle
|
|
200
|
+
|
|
201
|
+
| Deleted | Effect |
|
|
202
|
+
| --- | --- |
|
|
203
|
+
| Workspace | file rows cascade away |
|
|
204
|
+
| Uploading user | `uploaded_by_user_id` set null; the file survives |
|
|
205
|
+
|
|
206
|
+
The file outliving its uploader is deliberate — a departing employee's uploads
|
|
207
|
+
belong to the workspace, not to them.
|
|
208
|
+
|
|
209
|
+
Note that deleting a *row* directly (a cascade, or your own SQL) does **not**
|
|
210
|
+
remove the object. Delete through `files.delete` where you can; for a cascade,
|
|
211
|
+
reconcile with `purgeAbandoned`-style sweeping of your own.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Testing
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
const provider = createMemoryStorageProvider();
|
|
219
|
+
const files = createFilesService({
|
|
220
|
+
db,
|
|
221
|
+
storage: createStorageService({ provider }),
|
|
222
|
+
maxBytes: 1024,
|
|
223
|
+
now: () => fixedClock,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const { file } = await files.createUpload(context, { filename: "a.csv" });
|
|
227
|
+
await provider.put({ key: file.storageKey, body: "a,b,c" }); // stand in for the browser
|
|
228
|
+
expect((await files.confirmUpload(context, file.id)).sizeBytes).toBe(5);
|
|
229
|
+
```
|
package/docs/jobs.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Background jobs
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import { jobs, task, configureJobs } from "@digimuza/saas-core/jobs";
|
|
5
|
+
import type { JobPayload } from "@digimuza/saas-core/jobs";
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
> Orientation and recipes: [`src/jobs/README.md`](../src/jobs/README.md).
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
Trigger.dev owns durable execution — retries, scheduling, cron, waits, concurrency,
|
|
12
|
+
multi-step workflows and run observability. The core reimplements none of it and
|
|
13
|
+
**stores no run state**: a `job_runs` table would be a second, always-slightly-wrong
|
|
14
|
+
copy of what Trigger.dev already knows.
|
|
15
|
+
|
|
16
|
+
What the core adds is exactly two things:
|
|
17
|
+
|
|
18
|
+
1. **Tenant attribution.** A job outlives the request that queued it, so the
|
|
19
|
+
workspace travels with it.
|
|
20
|
+
2. **Application events.** `job.started` / `.progress` / `.completed` / `.failed`,
|
|
21
|
+
so the rest of the system can react.
|
|
22
|
+
|
|
23
|
+
Everything else is Trigger.dev's, and the SDK is re-exported rather than wrapped,
|
|
24
|
+
so advanced use is never blocked.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Install and configure
|
|
29
|
+
|
|
30
|
+
`@trigger.dev/sdk` is an **optional peer dependency**. Install it in your
|
|
31
|
+
application — an app defines its tasks against the same SDK instance the core
|
|
32
|
+
triggers through, and two copies would not share task registries.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @trigger.dev/sdk
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { configureJobs } from "@digimuza/saas-core/jobs";
|
|
40
|
+
|
|
41
|
+
await configureJobs(); // once at startup
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This points the SDK at the Infisical-injected `TRIGGER_SECRET_KEY` and
|
|
45
|
+
`TRIGGER_API_URL`. Without it the SDK reads the environment itself, which works but
|
|
46
|
+
bypasses the validation every other config section gets.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## `jobs.trigger(taskId, data, options)`
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
function trigger<TData>(taskId: string, data: TData, options: {
|
|
54
|
+
workspaceId: string;
|
|
55
|
+
actorId?: string | null;
|
|
56
|
+
idempotencyKey?: string | string[];
|
|
57
|
+
idempotencyKeyTTL?: string;
|
|
58
|
+
maxAttempts?: number;
|
|
59
|
+
queue?: string;
|
|
60
|
+
concurrencyKey?: string;
|
|
61
|
+
delay?: string | Date;
|
|
62
|
+
ttl?: string | number;
|
|
63
|
+
priority?: number;
|
|
64
|
+
tags?: string[];
|
|
65
|
+
metadata?: Record<string, unknown>;
|
|
66
|
+
}): Promise<{ runId: string; taskId: string }>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const handle = await jobs.trigger("resize-image", { assetId }, {
|
|
71
|
+
workspaceId: context.workspaceId,
|
|
72
|
+
actorId: user.id,
|
|
73
|
+
idempotencyKey: requestId,
|
|
74
|
+
concurrencyKey: context.workspaceId, // one tenant cannot starve another
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Three things happen beyond a plain `tasks.trigger`:
|
|
79
|
+
|
|
80
|
+
- The payload is wrapped in a `JobPayload` envelope, so your data stays untouched
|
|
81
|
+
under `data`. A payload with its own `tenant` field keeps working.
|
|
82
|
+
- The run is tagged `workspace:<id>` and `actor:<id>`, so runs are filterable per
|
|
83
|
+
tenant in the Trigger.dev dashboard. Caller tags are merged and de-duplicated,
|
|
84
|
+
since Trigger.dev counts them against a per-run limit.
|
|
85
|
+
- Tenancy is mirrored into run **metadata**, which is what makes a production
|
|
86
|
+
incident searchable back to a tenant.
|
|
87
|
+
|
|
88
|
+
`workspaceId` and `actorId` are stripped from what reaches Trigger.dev — they are
|
|
89
|
+
core concepts, not SDK options.
|
|
90
|
+
|
|
91
|
+
Throws `InvalidInputError` for a blank task id or workspace id.
|
|
92
|
+
|
|
93
|
+
## `jobs.triggerSystem(taskId, data, options?)`
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
function triggerSystem<TData>(taskId: string, data: TData, options?: JobTriggerOptions):
|
|
97
|
+
Promise<{ runId: string; taskId: string }>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
For work that belongs to no tenant — a nightly sweep, a catalogue sync. Sends
|
|
101
|
+
`tenant: null` and no tenant tags.
|
|
102
|
+
|
|
103
|
+
A separate method rather than accepting a null workspace, so forgetting to
|
|
104
|
+
attribute tenant work is a deliberate choice and not a typo.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Writing a task
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { task } from "@digimuza/saas-core/jobs";
|
|
112
|
+
import type { JobPayload } from "@digimuza/saas-core/jobs";
|
|
113
|
+
import { jobs } from "@digimuza/saas-core/jobs";
|
|
114
|
+
import { workspaces } from "@digimuza/saas-core/workspaces";
|
|
115
|
+
|
|
116
|
+
export const resizeImage = task({
|
|
117
|
+
id: "resize-image",
|
|
118
|
+
run: async (payload: JobPayload<{ assetId: string }>, { ctx }) => {
|
|
119
|
+
const reporter = jobs.reporter(
|
|
120
|
+
{ runId: ctx.run.id, taskId: ctx.task.id },
|
|
121
|
+
payload.tenant,
|
|
122
|
+
);
|
|
123
|
+
await reporter.started();
|
|
124
|
+
|
|
125
|
+
// Re-verify. The payload is attribution, not authority.
|
|
126
|
+
const context = await workspaces.requireContext({
|
|
127
|
+
userId: payload.tenant.actorId!,
|
|
128
|
+
workspaceId: payload.tenant.workspaceId,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
await doWork(context, payload.data);
|
|
133
|
+
await reporter.completed();
|
|
134
|
+
} catch (error) {
|
|
135
|
+
await reporter.failed({ error, attempt: ctx.attempt.number });
|
|
136
|
+
throw error; // rethrow so Trigger.dev retries
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Always re-verify tenancy inside the task
|
|
143
|
+
|
|
144
|
+
`JobPayload.tenant` is **attribution, not authorisation**. Between queueing and
|
|
145
|
+
running, the workspace may have been deleted or the actor removed from it. Call
|
|
146
|
+
`workspaces.requireContext()` before touching tenant data — it is one indexed query
|
|
147
|
+
and it closes the gap.
|
|
148
|
+
|
|
149
|
+
For a system job (`tenant: null`) there is no membership to check; resolve whatever
|
|
150
|
+
scope the task needs explicitly.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## `jobs.reporter(run, tenant)`
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
function reporter(run: { runId: string; taskId: string }, tenant: JobTenant | null): JobReporter
|
|
158
|
+
|
|
159
|
+
interface JobReporter {
|
|
160
|
+
started(): Promise<void>;
|
|
161
|
+
progress(progress: {
|
|
162
|
+
percent?: number; // 0-100
|
|
163
|
+
message?: string; // max 500 chars
|
|
164
|
+
step?: number;
|
|
165
|
+
totalSteps?: number;
|
|
166
|
+
}): Promise<void>;
|
|
167
|
+
completed(result?: { durationMs?: number }): Promise<void>;
|
|
168
|
+
failed(input: { error: unknown; attempt?: number }): Promise<void>;
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Emits `job.*` application events so the application can react and a browser can
|
|
173
|
+
watch progress. These are **not** a second source of truth — Trigger.dev already
|
|
174
|
+
tracks run state, and losing one of these events does not lose the run.
|
|
175
|
+
|
|
176
|
+
Deduplication is chosen per method, deliberately:
|
|
177
|
+
|
|
178
|
+
| Method | Key | Why |
|
|
179
|
+
| --- | --- | --- |
|
|
180
|
+
| `started` | run id | A Trigger.dev retry re-executes the task body; the run has not restarted. |
|
|
181
|
+
| `completed` | run id | One completion per run. |
|
|
182
|
+
| `failed` | run id + attempt | Each failed attempt is a distinct fact; a run failing twice should say so twice. |
|
|
183
|
+
| `progress` | none | Every tick is news. |
|
|
184
|
+
|
|
185
|
+
`completed()` computes `durationMs` from reporter construction if you do not supply
|
|
186
|
+
one.
|
|
187
|
+
|
|
188
|
+
`failed()` reduces any throwable to a bounded string via `describeJobError`, capped
|
|
189
|
+
at 2000 characters so the event schema accepts it. **Never put a secret in a task
|
|
190
|
+
error** — the message is persisted and readable.
|
|
191
|
+
|
|
192
|
+
### Throttle progress
|
|
193
|
+
|
|
194
|
+
Each `progress()` call writes a row. A tight loop would turn the events table into
|
|
195
|
+
a firehose. Report on meaningful steps, or rate-limit to a few per second.
|
|
196
|
+
|
|
197
|
+
### Without an event publisher
|
|
198
|
+
|
|
199
|
+
`createJobsService({ backend })` — no `events` — returns a reporter whose methods do
|
|
200
|
+
nothing, so a task can call it unconditionally without branching.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## The Trigger.dev SDK, re-exported
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import {
|
|
208
|
+
task, tasks, schemaTask, schedules, wait, retry, queue, queues,
|
|
209
|
+
batch, runs, tags, metadata, logger, timeout, usage, idempotencyKeys,
|
|
210
|
+
configureTriggerClient,
|
|
211
|
+
} from "@digimuza/saas-core/jobs";
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Deliberately unwrapped. Wrapping `task`, `schedules` or `wait` would mean
|
|
215
|
+
re-documenting and re-typing a mature API for no gain, and would lag behind it.
|
|
216
|
+
|
|
217
|
+
So scheduled jobs, cron, waits, multi-step workflows and concurrency controls are
|
|
218
|
+
all used exactly as Trigger.dev documents them:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { schedules } from "@digimuza/saas-core/jobs";
|
|
222
|
+
|
|
223
|
+
export const nightly = schedules.task({
|
|
224
|
+
id: "nightly-sweep",
|
|
225
|
+
cron: "0 3 * * *",
|
|
226
|
+
run: async () => { /* ... */ },
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Importing this module does not require credentials — there is a test asserting the
|
|
231
|
+
entry point loads with none set.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Subscribing to job events
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import { events } from "@digimuza/saas-core/events";
|
|
239
|
+
|
|
240
|
+
events.on("job.progress", (event) => {
|
|
241
|
+
event.data.percent; // narrowed
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The intended architecture, end to end:
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
Trigger.dev task → reporter → events table → realtime → RxJS → browser UI
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Realtime is Phase 8 and RxJS Phase 9; today the events are persisted and
|
|
252
|
+
in-process subscribers fire.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Custom construction
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { createJobsService, createTriggerDevBackend } from "@digimuza/saas-core/jobs";
|
|
260
|
+
|
|
261
|
+
const jobs = createJobsService({
|
|
262
|
+
backend: createTriggerDevBackend(),
|
|
263
|
+
events,
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`JobBackend` is a one-method interface, so tests pass a recorder instead of needing
|
|
268
|
+
a Trigger.dev account:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
const calls: unknown[] = [];
|
|
272
|
+
const jobs = createJobsService({
|
|
273
|
+
backend: {
|
|
274
|
+
trigger(taskId, payload, options) {
|
|
275
|
+
calls.push({ taskId, payload, options });
|
|
276
|
+
return Promise.resolve({ id: "run_1" });
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## What is not here
|
|
285
|
+
|
|
286
|
+
No Redis. PostgreSQL and Trigger.dev cover durable state and durable execution
|
|
287
|
+
between them; adding Redis for background jobs would be operational complexity with
|
|
288
|
+
nothing to show for it.
|