workerstack 0.1.0-beta.0
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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +138 -0
- package/dist/access.d.ts +131 -0
- package/dist/access.d.ts.map +1 -0
- package/dist/access.js +333 -0
- package/dist/access.js.map +1 -0
- package/dist/api/builder.d.ts +457 -0
- package/dist/api/builder.d.ts.map +1 -0
- package/dist/api/builder.js +45 -0
- package/dist/api/builder.js.map +1 -0
- package/dist/api/catalog.d.ts +8 -0
- package/dist/api/catalog.d.ts.map +1 -0
- package/dist/api/catalog.js +56 -0
- package/dist/api/catalog.js.map +1 -0
- package/dist/api/context.d.ts +47 -0
- package/dist/api/context.d.ts.map +1 -0
- package/dist/api/context.js +40 -0
- package/dist/api/context.js.map +1 -0
- package/dist/api/crud-router.d.ts +476 -0
- package/dist/api/crud-router.d.ts.map +1 -0
- package/dist/api/crud-router.js +354 -0
- package/dist/api/crud-router.js.map +1 -0
- package/dist/api/list-input-schema.d.ts +37 -0
- package/dist/api/list-input-schema.d.ts.map +1 -0
- package/dist/api/list-input-schema.js +63 -0
- package/dist/api/list-input-schema.js.map +1 -0
- package/dist/api/list-spec.d.ts +44 -0
- package/dist/api/list-spec.d.ts.map +1 -0
- package/dist/api/list-spec.js +49 -0
- package/dist/api/list-spec.js.map +1 -0
- package/dist/api/live-view.d.ts +14 -0
- package/dist/api/live-view.d.ts.map +1 -0
- package/dist/api/live-view.js +42 -0
- package/dist/api/live-view.js.map +1 -0
- package/dist/api/live-window.d.ts +40 -0
- package/dist/api/live-window.d.ts.map +1 -0
- package/dist/api/live-window.js +93 -0
- package/dist/api/live-window.js.map +1 -0
- package/dist/api/openapi.d.ts +6 -0
- package/dist/api/openapi.d.ts.map +1 -0
- package/dist/api/openapi.js +146 -0
- package/dist/api/openapi.js.map +1 -0
- package/dist/api/realtime-router.d.ts +94 -0
- package/dist/api/realtime-router.d.ts.map +1 -0
- package/dist/api/realtime-router.js +45 -0
- package/dist/api/realtime-router.js.map +1 -0
- package/dist/api/registry.d.ts +30 -0
- package/dist/api/registry.d.ts.map +1 -0
- package/dist/api/registry.js +236 -0
- package/dist/api/registry.js.map +1 -0
- package/dist/api/router.d.ts +15 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +32 -0
- package/dist/api/router.js.map +1 -0
- package/dist/api/storage-router.d.ts +349 -0
- package/dist/api/storage-router.d.ts.map +1 -0
- package/dist/api/storage-router.js +155 -0
- package/dist/api/storage-router.js.map +1 -0
- package/dist/api/types.d.ts +55 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/auth-password.d.ts +27 -0
- package/dist/auth-password.d.ts.map +1 -0
- package/dist/auth-password.js +60 -0
- package/dist/auth-password.js.map +1 -0
- package/dist/auth.d.ts +59 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +146 -0
- package/dist/auth.js.map +1 -0
- package/dist/backend-internals.d.ts +35 -0
- package/dist/backend-internals.d.ts.map +1 -0
- package/dist/backend-internals.js +2 -0
- package/dist/backend-internals.js.map +1 -0
- package/dist/backend.d.ts +63 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +59 -0
- package/dist/backend.js.map +1 -0
- package/dist/blueprint-generator.d.ts +21 -0
- package/dist/blueprint-generator.d.ts.map +1 -0
- package/dist/blueprint-generator.js +199 -0
- package/dist/blueprint-generator.js.map +1 -0
- package/dist/blueprint.d.ts +99 -0
- package/dist/blueprint.d.ts.map +1 -0
- package/dist/blueprint.js +257 -0
- package/dist/blueprint.js.map +1 -0
- package/dist/cli-skills.d.ts +16 -0
- package/dist/cli-skills.d.ts.map +1 -0
- package/dist/cli-skills.js +108 -0
- package/dist/cli-skills.js.map +1 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +205 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/client.d.ts +53 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +111 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +4 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/live-view.d.ts +49 -0
- package/dist/client/live-view.d.ts.map +1 -0
- package/dist/client/live-view.js +184 -0
- package/dist/client/live-view.js.map +1 -0
- package/dist/client/react.d.ts +4 -0
- package/dist/client/react.d.ts.map +1 -0
- package/dist/client/react.js +6 -0
- package/dist/client/react.js.map +1 -0
- package/dist/client/realtime.d.ts +54 -0
- package/dist/client/realtime.d.ts.map +1 -0
- package/dist/client/realtime.js +105 -0
- package/dist/client/realtime.js.map +1 -0
- package/dist/client/rest.d.ts +2 -0
- package/dist/client/rest.d.ts.map +1 -0
- package/dist/client/rest.js +2 -0
- package/dist/client/rest.js.map +1 -0
- package/dist/client/rpc-client.d.ts +38 -0
- package/dist/client/rpc-client.d.ts.map +1 -0
- package/dist/client/rpc-client.js +59 -0
- package/dist/client/rpc-client.js.map +1 -0
- package/dist/client/solid.d.ts +7 -0
- package/dist/client/solid.d.ts.map +1 -0
- package/dist/client/solid.js +21 -0
- package/dist/client/solid.js.map +1 -0
- package/dist/client/svelte.d.ts +6 -0
- package/dist/client/svelte.d.ts.map +1 -0
- package/dist/client/svelte.js +9 -0
- package/dist/client/svelte.js.map +1 -0
- package/dist/client/vue.d.ts +5 -0
- package/dist/client/vue.d.ts.map +1 -0
- package/dist/client/vue.js +11 -0
- package/dist/client/vue.js.map +1 -0
- package/dist/codegen.d.ts +36 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/codegen.js +235 -0
- package/dist/codegen.js.map +1 -0
- package/dist/config.d.ts +129 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +75 -0
- package/dist/config.js.map +1 -0
- package/dist/cron.d.ts +4 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +3 -0
- package/dist/cron.js.map +1 -0
- package/dist/crud-operations.d.ts +43 -0
- package/dist/crud-operations.d.ts.map +1 -0
- package/dist/crud-operations.js +249 -0
- package/dist/crud-operations.js.map +1 -0
- package/dist/database/adapter.d.ts +26 -0
- package/dist/database/adapter.d.ts.map +1 -0
- package/dist/database/adapter.js +2 -0
- package/dist/database/adapter.js.map +1 -0
- package/dist/database/libsql.d.ts +3 -0
- package/dist/database/libsql.d.ts.map +1 -0
- package/dist/database/libsql.js +39 -0
- package/dist/database/libsql.js.map +1 -0
- package/dist/db.d.ts +15 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +8 -0
- package/dist/db.js.map +1 -0
- package/dist/dev/binaries.d.ts +22 -0
- package/dist/dev/binaries.d.ts.map +1 -0
- package/dist/dev/binaries.js +119 -0
- package/dist/dev/binaries.js.map +1 -0
- package/dist/dev/env.d.ts +11 -0
- package/dist/dev/env.d.ts.map +1 -0
- package/dist/dev/env.js +71 -0
- package/dist/dev/env.js.map +1 -0
- package/dist/dev/index.d.ts +39 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +273 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/processes.d.ts +37 -0
- package/dist/dev/processes.d.ts.map +1 -0
- package/dist/dev/processes.js +104 -0
- package/dist/dev/processes.js.map +1 -0
- package/dist/dev/push.d.ts +2 -0
- package/dist/dev/push.d.ts.map +1 -0
- package/dist/dev/push.js +30 -0
- package/dist/dev/push.js.map +1 -0
- package/dist/dialect.d.ts +17 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +12 -0
- package/dist/dialect.js.map +1 -0
- package/dist/email/smtp.d.ts +20 -0
- package/dist/email/smtp.d.ts.map +1 -0
- package/dist/email/smtp.js +31 -0
- package/dist/email/smtp.js.map +1 -0
- package/dist/email.d.ts +26 -0
- package/dist/email.d.ts.map +1 -0
- package/dist/email.js +2 -0
- package/dist/email.js.map +1 -0
- package/dist/env-probe.d.ts +9 -0
- package/dist/env-probe.d.ts.map +1 -0
- package/dist/env-probe.js +72 -0
- package/dist/env-probe.js.map +1 -0
- package/dist/env.d.ts +62 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +86 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +153 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +106 -0
- package/dist/errors.js.map +1 -0
- package/dist/handler.d.ts +11 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +21 -0
- package/dist/handler.js.map +1 -0
- package/dist/hosted-contract.d.ts +11 -0
- package/dist/hosted-contract.d.ts.map +1 -0
- package/dist/hosted-contract.js +50 -0
- package/dist/hosted-contract.js.map +1 -0
- package/dist/idempotency.d.ts +17 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +65 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect.d.ts +15 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +44 -0
- package/dist/inspect.js.map +1 -0
- package/dist/internal-tables.d.ts +1767 -0
- package/dist/internal-tables.d.ts.map +1 -0
- package/dist/internal-tables.js +114 -0
- package/dist/internal-tables.js.map +1 -0
- package/dist/jobs/cron.d.ts +15 -0
- package/dist/jobs/cron.d.ts.map +1 -0
- package/dist/jobs/cron.js +74 -0
- package/dist/jobs/cron.js.map +1 -0
- package/dist/jobs/define.d.ts +186 -0
- package/dist/jobs/define.d.ts.map +1 -0
- package/dist/jobs/define.js +99 -0
- package/dist/jobs/define.js.map +1 -0
- package/dist/jobs/index.d.ts +9 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +7 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/jobs/queue.d.ts +13 -0
- package/dist/jobs/queue.d.ts.map +1 -0
- package/dist/jobs/queue.js +71 -0
- package/dist/jobs/queue.js.map +1 -0
- package/dist/jobs/slots.d.ts +30 -0
- package/dist/jobs/slots.d.ts.map +1 -0
- package/dist/jobs/slots.js +53 -0
- package/dist/jobs/slots.js.map +1 -0
- package/dist/jobs/worker.d.ts +45 -0
- package/dist/jobs/worker.d.ts.map +1 -0
- package/dist/jobs/worker.js +605 -0
- package/dist/jobs/worker.js.map +1 -0
- package/dist/lifecycle.d.ts +10 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +37 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/list-query.d.ts +62 -0
- package/dist/list-query.d.ts.map +1 -0
- package/dist/list-query.js +190 -0
- package/dist/list-query.js.map +1 -0
- package/dist/live/apply.d.ts +13 -0
- package/dist/live/apply.d.ts.map +1 -0
- package/dist/live/apply.js +32 -0
- package/dist/live/apply.js.map +1 -0
- package/dist/live/index.d.ts +48 -0
- package/dist/live/index.d.ts.map +1 -0
- package/dist/live/index.js +108 -0
- package/dist/live/index.js.map +1 -0
- package/dist/live/protocol.d.ts +47 -0
- package/dist/live/protocol.d.ts.map +1 -0
- package/dist/live/protocol.js +12 -0
- package/dist/live/protocol.js.map +1 -0
- package/dist/live/sse.d.ts +22 -0
- package/dist/live/sse.d.ts.map +1 -0
- package/dist/live/sse.js +41 -0
- package/dist/live/sse.js.map +1 -0
- package/dist/logging.d.ts +8 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +6 -0
- package/dist/logging.js.map +1 -0
- package/dist/manifest-diff.d.ts +6 -0
- package/dist/manifest-diff.d.ts.map +1 -0
- package/dist/manifest-diff.js +42 -0
- package/dist/manifest-diff.js.map +1 -0
- package/dist/manifest.d.ts +87 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +237 -0
- package/dist/manifest.js.map +1 -0
- package/dist/messaging/email.d.ts +16 -0
- package/dist/messaging/email.d.ts.map +1 -0
- package/dist/messaging/email.js +8 -0
- package/dist/messaging/email.js.map +1 -0
- package/dist/messaging/index.d.ts +8 -0
- package/dist/messaging/index.d.ts.map +1 -0
- package/dist/messaging/index.js +4 -0
- package/dist/messaging/index.js.map +1 -0
- package/dist/messaging/journal.d.ts +4 -0
- package/dist/messaging/journal.d.ts.map +1 -0
- package/dist/messaging/journal.js +19 -0
- package/dist/messaging/journal.js.map +1 -0
- package/dist/messaging/runtime.d.ts +16 -0
- package/dist/messaging/runtime.d.ts.map +1 -0
- package/dist/messaging/runtime.js +213 -0
- package/dist/messaging/runtime.js.map +1 -0
- package/dist/messaging/standalone.d.ts +27 -0
- package/dist/messaging/standalone.d.ts.map +1 -0
- package/dist/messaging/standalone.js +18 -0
- package/dist/messaging/standalone.js.map +1 -0
- package/dist/messaging/telegram.d.ts +16 -0
- package/dist/messaging/telegram.d.ts.map +1 -0
- package/dist/messaging/telegram.js +5 -0
- package/dist/messaging/telegram.js.map +1 -0
- package/dist/messaging/types.d.ts +22 -0
- package/dist/messaging/types.d.ts.map +1 -0
- package/dist/messaging/types.js +17 -0
- package/dist/messaging/types.js.map +1 -0
- package/dist/platform.d.ts +25 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +24 -0
- package/dist/platform.js.map +1 -0
- package/dist/provision-internals.d.ts +23 -0
- package/dist/provision-internals.d.ts.map +1 -0
- package/dist/provision-internals.js +7 -0
- package/dist/provision-internals.js.map +1 -0
- package/dist/provision-runtime.d.ts +6 -0
- package/dist/provision-runtime.d.ts.map +1 -0
- package/dist/provision-runtime.js +41 -0
- package/dist/provision-runtime.js.map +1 -0
- package/dist/provision-schema.d.ts +16 -0
- package/dist/provision-schema.d.ts.map +1 -0
- package/dist/provision-schema.js +59 -0
- package/dist/provision-schema.js.map +1 -0
- package/dist/provision.d.ts +8 -0
- package/dist/provision.d.ts.map +1 -0
- package/dist/provision.js +16 -0
- package/dist/provision.js.map +1 -0
- package/dist/query/api.d.ts +10 -0
- package/dist/query/api.d.ts.map +1 -0
- package/dist/query/api.js +7 -0
- package/dist/query/api.js.map +1 -0
- package/dist/query/client.d.ts +28 -0
- package/dist/query/client.d.ts.map +1 -0
- package/dist/query/client.js +87 -0
- package/dist/query/client.js.map +1 -0
- package/dist/query/fetch.d.ts +3 -0
- package/dist/query/fetch.d.ts.map +1 -0
- package/dist/query/fetch.js +4 -0
- package/dist/query/fetch.js.map +1 -0
- package/dist/query/index.d.ts +8 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/index.js +4 -0
- package/dist/query/index.js.map +1 -0
- package/dist/query/infer.d.ts +25 -0
- package/dist/query/infer.d.ts.map +1 -0
- package/dist/query/infer.js +2 -0
- package/dist/query/infer.js.map +1 -0
- package/dist/query/react.d.ts +3 -0
- package/dist/query/react.d.ts.map +1 -0
- package/dist/query/react.js +2 -0
- package/dist/query/react.js.map +1 -0
- package/dist/query/realtime-flush.d.ts +26 -0
- package/dist/query/realtime-flush.d.ts.map +1 -0
- package/dist/query/realtime-flush.js +45 -0
- package/dist/query/realtime-flush.js.map +1 -0
- package/dist/query/realtime-stream.d.ts +3 -0
- package/dist/query/realtime-stream.d.ts.map +1 -0
- package/dist/query/realtime-stream.js +3 -0
- package/dist/query/realtime-stream.js.map +1 -0
- package/dist/query/realtime.d.ts +88 -0
- package/dist/query/realtime.d.ts.map +1 -0
- package/dist/query/realtime.js +255 -0
- package/dist/query/realtime.js.map +1 -0
- package/dist/rate-limit.d.ts +8 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +46 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/readiness-probes.d.ts +4 -0
- package/dist/readiness-probes.d.ts.map +1 -0
- package/dist/readiness-probes.js +23 -0
- package/dist/readiness-probes.js.map +1 -0
- package/dist/readiness.d.ts +39 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +72 -0
- package/dist/readiness.js.map +1 -0
- package/dist/realtime/facade.d.ts +20 -0
- package/dist/realtime/facade.d.ts.map +1 -0
- package/dist/realtime/facade.js +37 -0
- package/dist/realtime/facade.js.map +1 -0
- package/dist/realtime/filter.d.ts +42 -0
- package/dist/realtime/filter.d.ts.map +1 -0
- package/dist/realtime/filter.js +86 -0
- package/dist/realtime/filter.js.map +1 -0
- package/dist/realtime/heartbeat.d.ts +20 -0
- package/dist/realtime/heartbeat.d.ts.map +1 -0
- package/dist/realtime/heartbeat.js +64 -0
- package/dist/realtime/heartbeat.js.map +1 -0
- package/dist/realtime/publisher.d.ts +19 -0
- package/dist/realtime/publisher.d.ts.map +1 -0
- package/dist/realtime/publisher.js +8 -0
- package/dist/realtime/publisher.js.map +1 -0
- package/dist/runtime.d.ts +141 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +463 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema-export.d.ts +2 -0
- package/dist/schema-export.d.ts.map +1 -0
- package/dist/schema-export.js +2 -0
- package/dist/schema-export.js.map +1 -0
- package/dist/scope.d.ts +4 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +13 -0
- package/dist/scope.js.map +1 -0
- package/dist/standard-schema.d.ts +13 -0
- package/dist/standard-schema.d.ts.map +1 -0
- package/dist/standard-schema.js +34 -0
- package/dist/standard-schema.js.map +1 -0
- package/dist/start/auth-client.d.ts +11 -0
- package/dist/start/auth-client.d.ts.map +1 -0
- package/dist/start/auth-client.js +15 -0
- package/dist/start/auth-client.js.map +1 -0
- package/dist/start/create-start-worker.d.ts +16 -0
- package/dist/start/create-start-worker.d.ts.map +1 -0
- package/dist/start/create-start-worker.js +24 -0
- package/dist/start/create-start-worker.js.map +1 -0
- package/dist/start/index.d.ts +66 -0
- package/dist/start/index.d.ts.map +1 -0
- package/dist/start/index.js +49 -0
- package/dist/start/index.js.map +1 -0
- package/dist/start/isomorphic-fetch.d.ts +22 -0
- package/dist/start/isomorphic-fetch.d.ts.map +1 -0
- package/dist/start/isomorphic-fetch.js +49 -0
- package/dist/start/isomorphic-fetch.js.map +1 -0
- package/dist/start/server-entry.d.ts +7 -0
- package/dist/start/server-entry.d.ts.map +1 -0
- package/dist/start/server-entry.js +8 -0
- package/dist/start/server-entry.js.map +1 -0
- package/dist/storage/background.d.ts +3 -0
- package/dist/storage/background.d.ts.map +1 -0
- package/dist/storage/background.js +3 -0
- package/dist/storage/background.js.map +1 -0
- package/dist/storage/buckets.d.ts +78 -0
- package/dist/storage/buckets.d.ts.map +1 -0
- package/dist/storage/buckets.js +177 -0
- package/dist/storage/buckets.js.map +1 -0
- package/dist/storage/delete.d.ts +8 -0
- package/dist/storage/delete.d.ts.map +1 -0
- package/dist/storage/delete.js +10 -0
- package/dist/storage/delete.js.map +1 -0
- package/dist/storage/file-meta.d.ts +37 -0
- package/dist/storage/file-meta.d.ts.map +1 -0
- package/dist/storage/file-meta.js +166 -0
- package/dist/storage/file-meta.js.map +1 -0
- package/dist/storage/index.d.ts +28 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +5 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/local.d.ts +15 -0
- package/dist/storage/local.d.ts.map +1 -0
- package/dist/storage/local.js +56 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/storage/operations.d.ts +54 -0
- package/dist/storage/operations.d.ts.map +1 -0
- package/dist/storage/operations.js +212 -0
- package/dist/storage/operations.js.map +1 -0
- package/dist/storage/registry.d.ts +11 -0
- package/dist/storage/registry.d.ts.map +1 -0
- package/dist/storage/registry.js +23 -0
- package/dist/storage/registry.js.map +1 -0
- package/dist/storage/s3.d.ts +35 -0
- package/dist/storage/s3.d.ts.map +1 -0
- package/dist/storage/s3.js +125 -0
- package/dist/storage/s3.js.map +1 -0
- package/dist/storage/sigv4.d.ts +24 -0
- package/dist/storage/sigv4.d.ts.map +1 -0
- package/dist/storage/sigv4.js +107 -0
- package/dist/storage/sigv4.js.map +1 -0
- package/dist/storage/sweep.d.ts +9 -0
- package/dist/storage/sweep.d.ts.map +1 -0
- package/dist/storage/sweep.js +17 -0
- package/dist/storage/sweep.js.map +1 -0
- package/dist/sync/collection.d.ts +106 -0
- package/dist/sync/collection.d.ts.map +1 -0
- package/dist/sync/collection.js +258 -0
- package/dist/sync/collection.js.map +1 -0
- package/dist/sync/index.d.ts +8 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +4 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/realtime-sync.d.ts +31 -0
- package/dist/sync/realtime-sync.d.ts.map +1 -0
- package/dist/sync/realtime-sync.js +46 -0
- package/dist/sync/realtime-sync.js.map +1 -0
- package/dist/sync/sync-client.d.ts +28 -0
- package/dist/sync/sync-client.d.ts.map +1 -0
- package/dist/sync/sync-client.js +52 -0
- package/dist/sync/sync-client.js.map +1 -0
- package/dist/sync/update-queue.d.ts +27 -0
- package/dist/sync/update-queue.d.ts.map +1 -0
- package/dist/sync/update-queue.js +66 -0
- package/dist/sync/update-queue.js.map +1 -0
- package/dist/testing/auth.d.ts +85 -0
- package/dist/testing/auth.d.ts.map +1 -0
- package/dist/testing/auth.js +140 -0
- package/dist/testing/auth.js.map +1 -0
- package/dist/testing/client.d.ts +6 -0
- package/dist/testing/client.d.ts.map +1 -0
- package/dist/testing/client.js +9 -0
- package/dist/testing/client.js.map +1 -0
- package/dist/testing/database.d.ts +6 -0
- package/dist/testing/database.d.ts.map +1 -0
- package/dist/testing/database.js +8 -0
- package/dist/testing/database.js.map +1 -0
- package/dist/testing/email.d.ts +15 -0
- package/dist/testing/email.d.ts.map +1 -0
- package/dist/testing/email.js +32 -0
- package/dist/testing/email.js.map +1 -0
- package/dist/testing/fixture.d.ts +51 -0
- package/dist/testing/fixture.d.ts.map +1 -0
- package/dist/testing/fixture.js +177 -0
- package/dist/testing/fixture.js.map +1 -0
- package/dist/testing/jobs.d.ts +40 -0
- package/dist/testing/jobs.d.ts.map +1 -0
- package/dist/testing/jobs.js +76 -0
- package/dist/testing/jobs.js.map +1 -0
- package/dist/testing/logs.d.ts +18 -0
- package/dist/testing/logs.d.ts.map +1 -0
- package/dist/testing/logs.js +53 -0
- package/dist/testing/logs.js.map +1 -0
- package/dist/testing/messaging.d.ts +21 -0
- package/dist/testing/messaging.d.ts.map +1 -0
- package/dist/testing/messaging.js +34 -0
- package/dist/testing/messaging.js.map +1 -0
- package/dist/testing/storage.d.ts +7 -0
- package/dist/testing/storage.d.ts.map +1 -0
- package/dist/testing/storage.js +29 -0
- package/dist/testing/storage.js.map +1 -0
- package/dist/testing/workers-fakes.d.ts +17 -0
- package/dist/testing/workers-fakes.d.ts.map +1 -0
- package/dist/testing/workers-fakes.js +98 -0
- package/dist/testing/workers-fakes.js.map +1 -0
- package/dist/testing.d.ts +12 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +5 -0
- package/dist/testing.js.map +1 -0
- package/dist/typeid.d.ts +46 -0
- package/dist/typeid.d.ts.map +1 -0
- package/dist/typeid.js +130 -0
- package/dist/typeid.js.map +1 -0
- package/dist/vite.d.ts +26 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +100 -0
- package/dist/vite.js.map +1 -0
- package/dist/worker-plan.d.ts +37 -0
- package/dist/worker-plan.d.ts.map +1 -0
- package/dist/worker-plan.js +63 -0
- package/dist/worker-plan.js.map +1 -0
- package/dist/workers/app.d.ts +19 -0
- package/dist/workers/app.d.ts.map +1 -0
- package/dist/workers/app.js +56 -0
- package/dist/workers/app.js.map +1 -0
- package/dist/workers/entry.d.ts +7 -0
- package/dist/workers/entry.d.ts.map +1 -0
- package/dist/workers/entry.js +8 -0
- package/dist/workers/entry.js.map +1 -0
- package/dist/workers/index.d.ts +23 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +39 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/r2.d.ts +24 -0
- package/dist/workers/r2.d.ts.map +1 -0
- package/dist/workers/r2.js +76 -0
- package/dist/workers/r2.js.map +1 -0
- package/dist/workers/rate-limiter.d.ts +9 -0
- package/dist/workers/rate-limiter.d.ts.map +1 -0
- package/dist/workers/rate-limiter.js +22 -0
- package/dist/workers/rate-limiter.js.map +1 -0
- package/dist/workers/realtime-hub.d.ts +20 -0
- package/dist/workers/realtime-hub.d.ts.map +1 -0
- package/dist/workers/realtime-hub.js +117 -0
- package/dist/workers/realtime-hub.js.map +1 -0
- package/dist/workers/registry.d.ts +13 -0
- package/dist/workers/registry.d.ts.map +1 -0
- package/dist/workers/registry.js +19 -0
- package/dist/workers/registry.js.map +1 -0
- package/dist/workers/scheduler.d.ts +13 -0
- package/dist/workers/scheduler.d.ts.map +1 -0
- package/dist/workers/scheduler.js +71 -0
- package/dist/workers/scheduler.js.map +1 -0
- package/dist/workers/types.d.ts +55 -0
- package/dist/workers/types.d.ts.map +1 -0
- package/dist/workers/types.js +4 -0
- package/dist/workers/types.js.map +1 -0
- package/dist/workers/wrangler.d.ts +62 -0
- package/dist/workers/wrangler.d.ts.map +1 -0
- package/dist/workers/wrangler.js +90 -0
- package/dist/workers/wrangler.js.map +1 -0
- package/package.json +286 -0
- package/skills/creating-workerstack-apps/SKILL.md +45 -0
- package/skills/creating-workerstack-apps/agents/openai.yaml +4 -0
- package/skills/creating-workerstack-apps/references/application-structure.md +219 -0
- package/skills/creating-workerstack-apps/references/runtime-integrations.md +96 -0
- package/skills/creating-workerstack-apps/references/verification.md +29 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crud-router.d.ts","sourceRoot":"","sources":["../../src/api/crud-router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { eventIterator } from '@orpc/server';
|
|
2
|
+
import { getTableColumns, getTableName, isTable } from 'drizzle-orm';
|
|
3
|
+
import '@orpc/openapi/extensions/route';
|
|
4
|
+
import { createInsertSchema, createSelectSchema, createUpdateSchema, } from 'drizzle-valibot';
|
|
5
|
+
import * as v from 'valibot';
|
|
6
|
+
import { tableEntryForName, } from '../access.js';
|
|
7
|
+
import { createCrudOperations } from '../crud-operations.js';
|
|
8
|
+
import { WorkerstackError } from '../errors.js';
|
|
9
|
+
import { filterTableChanges } from '../realtime/filter.js';
|
|
10
|
+
import { REALTIME_HEARTBEAT_INTERVAL_MS, withRealtimeHeartbeat, } from '../realtime/heartbeat.js';
|
|
11
|
+
import { createApiBuilder } from './builder.js';
|
|
12
|
+
import { buildListInputSchema, buildLiveInputSchema } from './list-input-schema.js';
|
|
13
|
+
import { createLiveWindow } from './live-window.js';
|
|
14
|
+
function strictObject(schema) {
|
|
15
|
+
return v.strictObject(schema.entries);
|
|
16
|
+
}
|
|
17
|
+
export function buildTableCrudProcedures(args) {
|
|
18
|
+
const { table, operations, builder, access, schemaKey, livePublisher } = args;
|
|
19
|
+
const name = getTableName(table);
|
|
20
|
+
// drizzle-valibot infers its schemas through internal generics. Left as-is,
|
|
21
|
+
// declaration emit inlines those generics by name into the published `.d.ts`,
|
|
22
|
+
// where they are unbound — every column silently becomes optional for the
|
|
23
|
+
// consumer. Restating the type keeps the runtime schema and makes the emitted
|
|
24
|
+
// one both correct and readable.
|
|
25
|
+
const selectSchema = strictObject(createSelectSchema(table));
|
|
26
|
+
const generatedInsertSchema = strictObject(createInsertSchema(table));
|
|
27
|
+
const generatedColumns = Object.keys(generatedInsertSchema.entries);
|
|
28
|
+
const columns = getTableColumns(table);
|
|
29
|
+
const serverManagedColumns = [
|
|
30
|
+
...access.readonlyColumns,
|
|
31
|
+
...(access.writeScope && generatedColumns.includes('organizationId')
|
|
32
|
+
? ['organizationId']
|
|
33
|
+
: []),
|
|
34
|
+
...Object.entries(columns)
|
|
35
|
+
.filter(([, column]) => !column.notNull || column.hasDefault)
|
|
36
|
+
.map(([column]) => column),
|
|
37
|
+
].filter((column) => generatedColumns.includes(column));
|
|
38
|
+
const insertEntries = {
|
|
39
|
+
...generatedInsertSchema.entries,
|
|
40
|
+
};
|
|
41
|
+
for (const column of serverManagedColumns) {
|
|
42
|
+
const schema = insertEntries[column];
|
|
43
|
+
if (schema)
|
|
44
|
+
insertEntries[column] = v.optional(schema);
|
|
45
|
+
}
|
|
46
|
+
const insertSchema = v.strictObject(insertEntries);
|
|
47
|
+
const generatedUpdateSchema = createUpdateSchema(table);
|
|
48
|
+
const updateBodySchema = v.omit(generatedUpdateSchema, [
|
|
49
|
+
'id',
|
|
50
|
+
]);
|
|
51
|
+
// Compact input is transport-neutral: direct oRPC callers pass
|
|
52
|
+
// `{ id, ...changes }`, while OpenAPI still maps `id` to the path and the
|
|
53
|
+
// remaining fields to the PATCH body.
|
|
54
|
+
const updateInputSchema = v.strictObject({
|
|
55
|
+
id: v.string(),
|
|
56
|
+
...updateBodySchema.entries,
|
|
57
|
+
});
|
|
58
|
+
// One filter field per allowed column, typed by the column itself: a scalar
|
|
59
|
+
// for `=`, a list for `IN`, and `null` for `IS NULL`. Query strings are
|
|
60
|
+
// coerced to these types by SmartCoercionHandlerPlugin, so REST and RPC share
|
|
61
|
+
// one contract and nothing has to re-read the raw URL.
|
|
62
|
+
// The cast restates the schema with the literals the caller's `access`
|
|
63
|
+
// config carries, which the runtime column lists cannot name on their own.
|
|
64
|
+
const listQuerySchema = buildListInputSchema(table, {
|
|
65
|
+
filterableColumns: access.filterableColumns,
|
|
66
|
+
sortableColumns: access.sortableColumns,
|
|
67
|
+
});
|
|
68
|
+
const listOutputSchema = v.strictObject({
|
|
69
|
+
items: v.array(selectSchema),
|
|
70
|
+
nextCursor: v.optional(v.string()),
|
|
71
|
+
hasMore: v.boolean(),
|
|
72
|
+
total: v.optional(v.number()),
|
|
73
|
+
limit: v.optional(v.number()),
|
|
74
|
+
offset: v.optional(v.number()),
|
|
75
|
+
cursor: v.optional(v.string()),
|
|
76
|
+
q: v.optional(v.string()),
|
|
77
|
+
sort: v.optional(v.string()),
|
|
78
|
+
order: v.optional(v.string()),
|
|
79
|
+
});
|
|
80
|
+
// 1. LIST procedure
|
|
81
|
+
const list = builder.public
|
|
82
|
+
.route({
|
|
83
|
+
method: 'GET',
|
|
84
|
+
path: `/api/${name}`,
|
|
85
|
+
summary: `List ${name}`,
|
|
86
|
+
tags: [name],
|
|
87
|
+
})
|
|
88
|
+
.input(listQuerySchema)
|
|
89
|
+
.output(listOutputSchema)
|
|
90
|
+
.handler(async ({ input, context }) => {
|
|
91
|
+
const session = await context.getSession();
|
|
92
|
+
const execCtx = {
|
|
93
|
+
request: context.request,
|
|
94
|
+
user: session.user,
|
|
95
|
+
session: { activeOrganizationId: session.activeOrganizationId },
|
|
96
|
+
};
|
|
97
|
+
const result = await operations.list(name, input ?? {}, execCtx);
|
|
98
|
+
return {
|
|
99
|
+
...result,
|
|
100
|
+
items: result.items,
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
// 2. GET procedure
|
|
104
|
+
const get = builder.public
|
|
105
|
+
.route({
|
|
106
|
+
method: 'GET',
|
|
107
|
+
path: `/api/${name}/{id}`,
|
|
108
|
+
summary: `Get ${name} by ID`,
|
|
109
|
+
tags: [name],
|
|
110
|
+
})
|
|
111
|
+
.input(v.strictObject({ id: v.string() }))
|
|
112
|
+
.output(selectSchema)
|
|
113
|
+
.handler(async ({ input, context }) => {
|
|
114
|
+
const session = await context.getSession();
|
|
115
|
+
const execCtx = {
|
|
116
|
+
request: context.request,
|
|
117
|
+
user: session.user,
|
|
118
|
+
session: { activeOrganizationId: session.activeOrganizationId },
|
|
119
|
+
};
|
|
120
|
+
return (await operations.get(name, input.id, execCtx));
|
|
121
|
+
});
|
|
122
|
+
// 3. CREATE procedure
|
|
123
|
+
const create = builder.public
|
|
124
|
+
.route({
|
|
125
|
+
method: 'POST',
|
|
126
|
+
path: `/api/${name}`,
|
|
127
|
+
summary: `Create ${name}`,
|
|
128
|
+
tags: [name],
|
|
129
|
+
successStatus: 201,
|
|
130
|
+
})
|
|
131
|
+
.input(insertSchema)
|
|
132
|
+
.output(selectSchema)
|
|
133
|
+
.handler(async ({ input, context }) => {
|
|
134
|
+
const session = await context.getSession();
|
|
135
|
+
const execCtx = {
|
|
136
|
+
request: context.request,
|
|
137
|
+
user: session.user,
|
|
138
|
+
session: { activeOrganizationId: session.activeOrganizationId },
|
|
139
|
+
};
|
|
140
|
+
const idempotencyKey = context.request.headers
|
|
141
|
+
.get('Idempotency-Key')
|
|
142
|
+
?.trim();
|
|
143
|
+
const rawBody = await context.getRawBody();
|
|
144
|
+
const res = await operations.create(name, input, rawBody, idempotencyKey, execCtx);
|
|
145
|
+
if (res.type === 'replay') {
|
|
146
|
+
context.resHeaders.set('Idempotency-Replayed', 'true');
|
|
147
|
+
return res.record;
|
|
148
|
+
}
|
|
149
|
+
return res.record;
|
|
150
|
+
});
|
|
151
|
+
// 4. UPDATE procedure
|
|
152
|
+
const update = builder.public
|
|
153
|
+
.route({
|
|
154
|
+
method: 'PATCH',
|
|
155
|
+
path: `/api/${name}/{id}`,
|
|
156
|
+
summary: `Update ${name}`,
|
|
157
|
+
tags: [name],
|
|
158
|
+
})
|
|
159
|
+
.input(updateInputSchema)
|
|
160
|
+
.output(selectSchema)
|
|
161
|
+
.handler(async ({ input, context }) => {
|
|
162
|
+
const requestPath = new URL(context.request.url).pathname;
|
|
163
|
+
if (!requestPath.startsWith('/api/rpc')) {
|
|
164
|
+
const rawBody = await context.getRawBody();
|
|
165
|
+
if (rawBody) {
|
|
166
|
+
const body = JSON.parse(rawBody);
|
|
167
|
+
if (Object.hasOwn(body, 'id')) {
|
|
168
|
+
throw new WorkerstackError('BAD_REQUEST', 'The id field is immutable');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const session = await context.getSession();
|
|
173
|
+
const execCtx = {
|
|
174
|
+
request: context.request,
|
|
175
|
+
user: session.user,
|
|
176
|
+
session: { activeOrganizationId: session.activeOrganizationId },
|
|
177
|
+
};
|
|
178
|
+
const { id, ...changes } = input;
|
|
179
|
+
return (await operations.update(name, id, changes, execCtx));
|
|
180
|
+
});
|
|
181
|
+
// 5. DELETE procedure
|
|
182
|
+
const deleteProc = builder.public
|
|
183
|
+
.route({
|
|
184
|
+
method: 'DELETE',
|
|
185
|
+
path: `/api/${name}/{id}`,
|
|
186
|
+
summary: `Delete ${name}`,
|
|
187
|
+
tags: [name],
|
|
188
|
+
successStatus: 204,
|
|
189
|
+
})
|
|
190
|
+
.input(v.strictObject({ id: v.string() }))
|
|
191
|
+
.output(v.undefined())
|
|
192
|
+
.handler(async ({ input, context }) => {
|
|
193
|
+
const session = await context.getSession();
|
|
194
|
+
const execCtx = {
|
|
195
|
+
request: context.request,
|
|
196
|
+
user: session.user,
|
|
197
|
+
session: { activeOrganizationId: session.activeOrganizationId },
|
|
198
|
+
};
|
|
199
|
+
await operations.delete(name, input.id, execCtx);
|
|
200
|
+
return undefined;
|
|
201
|
+
});
|
|
202
|
+
// 6. LIVE procedure — one list query as a stream. A snapshot first, then the
|
|
203
|
+
// changes this view cares about, placed by the server. Every connection
|
|
204
|
+
// opens with a snapshot, so a reconnect is its own recovery: no client-side
|
|
205
|
+
// event buffer, no Last-Event-ID bookkeeping, and no refetch path.
|
|
206
|
+
const liveQuerySchema = buildLiveInputSchema(table, {
|
|
207
|
+
filterableColumns: access.filterableColumns,
|
|
208
|
+
sortableColumns: access.sortableColumns,
|
|
209
|
+
});
|
|
210
|
+
const liveFrameSchema = v.union([
|
|
211
|
+
v.strictObject({
|
|
212
|
+
type: v.literal('snapshot'),
|
|
213
|
+
items: v.array(selectSchema),
|
|
214
|
+
sort: v.string(),
|
|
215
|
+
order: v.picklist(['asc', 'desc']),
|
|
216
|
+
limit: v.number(),
|
|
217
|
+
hasMore: v.boolean(),
|
|
218
|
+
operationId: v.optional(v.string()),
|
|
219
|
+
}),
|
|
220
|
+
v.strictObject({
|
|
221
|
+
type: v.literal('upsert'),
|
|
222
|
+
record: selectSchema,
|
|
223
|
+
afterId: v.union([v.string(), v.null()]),
|
|
224
|
+
operationId: v.optional(v.string()),
|
|
225
|
+
}),
|
|
226
|
+
v.strictObject({
|
|
227
|
+
type: v.literal('remove'),
|
|
228
|
+
id: v.string(),
|
|
229
|
+
operationId: v.optional(v.string()),
|
|
230
|
+
}),
|
|
231
|
+
v.strictObject({ type: v.literal('heartbeat'), intervalMs: v.number() }),
|
|
232
|
+
]);
|
|
233
|
+
const live = builder.public
|
|
234
|
+
.route({
|
|
235
|
+
method: 'GET',
|
|
236
|
+
// A path of its own, not a child of the table: `/{table}/live`
|
|
237
|
+
// would make the id "live" unreachable on the get route, and a
|
|
238
|
+
// colon suffix is a wildcard parameter in the oRPC matcher.
|
|
239
|
+
path: `/api/live/${name}`,
|
|
240
|
+
summary: `Live view of ${name}`,
|
|
241
|
+
tags: [name],
|
|
242
|
+
// Filters arrive as one URL-encoded JSON value, so no per-key query
|
|
243
|
+
// parsing rule is needed.
|
|
244
|
+
queryStyles: { filters: 'json' },
|
|
245
|
+
})
|
|
246
|
+
.input(liveQuerySchema)
|
|
247
|
+
.output(eventIterator(liveFrameSchema))
|
|
248
|
+
.handler(({ input, context, signal }) => {
|
|
249
|
+
if (!livePublisher) {
|
|
250
|
+
throw new Error(`Realtime is not enabled for live view of ${name}`);
|
|
251
|
+
}
|
|
252
|
+
// Subscribe before anything awaits, so no change slips between the
|
|
253
|
+
// snapshot read and the start of the stream; events that arrive
|
|
254
|
+
// during the query buffer in the publisher and replay on first pull.
|
|
255
|
+
const changes = filterTableChanges(livePublisher.subscribe('change', { signal }), {
|
|
256
|
+
tableName: schemaKey,
|
|
257
|
+
entry: access,
|
|
258
|
+
rule: access.list,
|
|
259
|
+
request: context.request,
|
|
260
|
+
getSession: context.getSession,
|
|
261
|
+
});
|
|
262
|
+
return withRealtimeHeartbeat((async function* () {
|
|
263
|
+
const session = await context.getSession();
|
|
264
|
+
const execCtx = {
|
|
265
|
+
request: context.request,
|
|
266
|
+
user: session.user,
|
|
267
|
+
session: { activeOrganizationId: session.activeOrganizationId },
|
|
268
|
+
};
|
|
269
|
+
let view;
|
|
270
|
+
const readSnapshot = async (operationId) => {
|
|
271
|
+
const result = await operations.list(name, input ?? {}, execCtx);
|
|
272
|
+
view = createLiveWindow({
|
|
273
|
+
sort: result.sort,
|
|
274
|
+
order: result.order,
|
|
275
|
+
limit: result.limit,
|
|
276
|
+
filters: input?.filters,
|
|
277
|
+
});
|
|
278
|
+
view.reset(result.items, result.hasMore);
|
|
279
|
+
return {
|
|
280
|
+
type: 'snapshot',
|
|
281
|
+
items: result.items,
|
|
282
|
+
sort: result.sort,
|
|
283
|
+
order: result.order,
|
|
284
|
+
limit: result.limit,
|
|
285
|
+
hasMore: result.hasMore,
|
|
286
|
+
...(operationId ? { operationId } : {}),
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
yield await readSnapshot();
|
|
290
|
+
for await (const change of changes) {
|
|
291
|
+
const outcome = view.apply(change);
|
|
292
|
+
if (outcome.type === 'none') {
|
|
293
|
+
// The write succeeded but did not affect this view. A snapshot
|
|
294
|
+
// still acknowledges the operation without inventing a row.
|
|
295
|
+
if (change.operationId)
|
|
296
|
+
yield await readSnapshot(change.operationId);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (outcome.type === 'resnapshot') {
|
|
300
|
+
yield await readSnapshot(change.operationId);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
for (const frame of outcome.frames)
|
|
304
|
+
yield frame;
|
|
305
|
+
}
|
|
306
|
+
})(), { intervalMs: REALTIME_HEARTBEAT_INTERVAL_MS, signal });
|
|
307
|
+
});
|
|
308
|
+
const procedures = {
|
|
309
|
+
list,
|
|
310
|
+
get,
|
|
311
|
+
create,
|
|
312
|
+
update,
|
|
313
|
+
delete: deleteProc,
|
|
314
|
+
live,
|
|
315
|
+
};
|
|
316
|
+
if (!livePublisher) {
|
|
317
|
+
delete procedures.live;
|
|
318
|
+
}
|
|
319
|
+
return procedures;
|
|
320
|
+
}
|
|
321
|
+
export function buildCrudApiRouter(schema, db, options) {
|
|
322
|
+
const { access, realtime, idempotency, livePublisher } = options;
|
|
323
|
+
const builder = createApiBuilder();
|
|
324
|
+
const operations = createCrudOperations({
|
|
325
|
+
schema,
|
|
326
|
+
db,
|
|
327
|
+
access,
|
|
328
|
+
idempotency,
|
|
329
|
+
realtime,
|
|
330
|
+
});
|
|
331
|
+
const routerObj = {};
|
|
332
|
+
for (const [tableKey, table] of Object.entries(schema)) {
|
|
333
|
+
if (!isTable(table))
|
|
334
|
+
continue;
|
|
335
|
+
const name = getTableName(table);
|
|
336
|
+
const tableAccess = tableEntryForName(access, name);
|
|
337
|
+
if (!tableAccess?.enabled)
|
|
338
|
+
continue;
|
|
339
|
+
const idCol = getTableColumns(table)['id'];
|
|
340
|
+
if (!idCol)
|
|
341
|
+
continue;
|
|
342
|
+
const procedures = buildTableCrudProcedures({
|
|
343
|
+
table: table,
|
|
344
|
+
operations,
|
|
345
|
+
builder,
|
|
346
|
+
access: tableAccess,
|
|
347
|
+
schemaKey: tableKey,
|
|
348
|
+
livePublisher,
|
|
349
|
+
});
|
|
350
|
+
routerObj[tableKey] = procedures;
|
|
351
|
+
}
|
|
352
|
+
return routerObj;
|
|
353
|
+
}
|
|
354
|
+
//# sourceMappingURL=crud-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crud-router.js","sourceRoot":"","sources":["../../src/api/crud-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { eventIterator } from '@orpc/server'\nimport { getTableColumns, getTableName, isTable, type Table } from 'drizzle-orm'\nimport '@orpc/openapi/extensions/route'\nimport {\n createInsertSchema,\n createSelectSchema,\n createUpdateSchema,\n} from 'drizzle-valibot'\nimport * as v from 'valibot'\n\nimport type { AnyDb } from '../dialect'\nimport type { IdempotencyConfig } from '../idempotency'\nimport type { LiveSnapshotFrame } from '../live/protocol'\nimport type { RealtimeFacade } from '../realtime/facade'\nimport type { RealtimePublisher } from '../realtime/publisher'\nimport type { CrudApiRouterFor } from './types'\n\nimport {\n tableEntryForName,\n type ResolvedAccess,\n type ResolvedTableAccess,\n type TableAccessInput,\n} from '../access'\nimport { createCrudOperations, type CrudOperations } from '../crud-operations'\nimport { WorkerstackError } from '../errors'\nimport { filterTableChanges } from '../realtime/filter'\nimport {\n REALTIME_HEARTBEAT_INTERVAL_MS,\n withRealtimeHeartbeat,\n} from '../realtime/heartbeat'\nimport { createApiBuilder } from './builder'\nimport { buildListInputSchema, buildLiveInputSchema } from './list-input-schema'\nimport { createLiveWindow } from './live-window'\n\nexport type CrudApiRouterOptions<\n TSchema extends Record<string, unknown> = Record<string, unknown>,\n> = {\n access: ResolvedAccess\n idempotency?: boolean | IdempotencyConfig\n realtime?: RealtimeFacade<TSchema>\n /**\n * The raw realtime publisher, present when realtime is enabled. The CRUD\n * router subscribes on behalf of live views (`GET /{table}:live`);\n * publishing stays behind the facade.\n */\n livePublisher?: RealtimePublisher\n}\n\nfunction strictObject<TEntries extends v.ObjectEntries>(schema: {\n entries: TEntries\n}) {\n return v.strictObject(schema.entries)\n}\n\ntype CrudInsert<TTable extends Table> = Partial<TTable['$inferInsert']>\ntype CrudUpdate<TTable extends Table> = Partial<\n Omit<TTable['$inferInsert'], 'id'>\n>\n\n/** A filter accepts one value (`=`), a list (`IN`), or null (`IS NULL`). */\nexport type ListFilterValue<T> = T | readonly T[] | null | 'null'\n\nexport type ListFilters<TTable extends Table, TFilterable extends string> = {\n [K in Extract<keyof TTable['$inferSelect'], TFilterable>]?: ListFilterValue<\n TTable['$inferSelect'][K]\n >\n}\n\n/**\n * The `list` input as callers see it. Filter columns and sortable columns come\n * from the table's `access` entry, so `filters` autocompletes to real columns\n * and rejects wrong value types at compile time.\n */\nexport type ListInputFor<\n TTable extends Table,\n TFilterable extends string,\n TSortable extends string,\n> =\n | {\n limit?: number\n offset?: number\n cursor?: string\n sort?: TSortable\n order?: 'asc' | 'desc'\n q?: string\n count?: boolean\n filters?: ListFilters<TTable, TFilterable>\n }\n | undefined\n\n/**\n * The live-view input as callers see it: the list contract narrowed to what a\n * stream can honor — no text search, no pagination.\n */\nexport type LiveInputFor<\n TTable extends Table,\n TFilterable extends string,\n TSortable extends string,\n> =\n | {\n limit?: number\n sort?: TSortable\n order?: 'asc' | 'desc'\n filters?: ListFilters<TTable, TFilterable>\n }\n | undefined\n\nexport type BuildTableCrudProceduresArgs<\n TSchema extends Record<string, unknown>,\n TTable extends Table,\n> = {\n table: TTable\n operations: CrudOperations\n builder: ReturnType<typeof createApiBuilder<TSchema>>\n access: ResolvedTableAccess\n /** The schema key events are published under. */\n schemaKey: string\n /** Present when realtime is enabled; without it no live procedure is built. */\n livePublisher?: RealtimePublisher\n}\n\nexport function buildTableCrudProcedures<\n TSchema extends Record<string, unknown>,\n TTable extends Table,\n TFilterable extends string = string,\n TSortable extends string = string,\n>(args: BuildTableCrudProceduresArgs<TSchema, TTable>) {\n const { table, operations, builder, access, schemaKey, livePublisher } = args\n const name = getTableName(table)\n\n // drizzle-valibot infers its schemas through internal generics. Left as-is,\n // declaration emit inlines those generics by name into the published `.d.ts`,\n // where they are unbound — every column silently becomes optional for the\n // consumer. Restating the type keeps the runtime schema and makes the emitted\n // one both correct and readable.\n const selectSchema = strictObject(\n createSelectSchema(table),\n ) as unknown as v.GenericSchema<\n TTable['$inferSelect'],\n TTable['$inferSelect']\n >\n const generatedInsertSchema = strictObject(createInsertSchema(table))\n const generatedColumns = Object.keys(generatedInsertSchema.entries)\n const columns = getTableColumns(table)\n const serverManagedColumns = [\n ...access.readonlyColumns,\n ...(access.writeScope && generatedColumns.includes('organizationId')\n ? ['organizationId']\n : []),\n ...Object.entries(columns)\n .filter(([, column]) => !column.notNull || column.hasDefault)\n .map(([column]) => column),\n ].filter((column) => generatedColumns.includes(column))\n const insertEntries: Record<\n string,\n v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>\n > = {\n ...generatedInsertSchema.entries,\n }\n for (const column of serverManagedColumns) {\n const schema = insertEntries[column]\n if (schema) insertEntries[column] = v.optional(schema)\n }\n const insertSchema = v.strictObject(\n insertEntries,\n ) as unknown as v.GenericSchema<CrudInsert<TTable>, CrudInsert<TTable>>\n const generatedUpdateSchema = createUpdateSchema(table)\n const updateBodySchema = v.omit(generatedUpdateSchema, [\n 'id' as keyof typeof generatedUpdateSchema.entries,\n ])\n // Compact input is transport-neutral: direct oRPC callers pass\n // `{ id, ...changes }`, while OpenAPI still maps `id` to the path and the\n // remaining fields to the PATCH body.\n const updateInputSchema = v.strictObject({\n id: v.string(),\n ...updateBodySchema.entries,\n }) as unknown as v.GenericSchema<\n { id: string } & CrudUpdate<TTable>,\n { id: string } & CrudUpdate<TTable>\n >\n\n // One filter field per allowed column, typed by the column itself: a scalar\n // for `=`, a list for `IN`, and `null` for `IS NULL`. Query strings are\n // coerced to these types by SmartCoercionHandlerPlugin, so REST and RPC share\n // one contract and nothing has to re-read the raw URL.\n // The cast restates the schema with the literals the caller's `access`\n // config carries, which the runtime column lists cannot name on their own.\n const listQuerySchema = buildListInputSchema(table, {\n filterableColumns: access.filterableColumns,\n sortableColumns: access.sortableColumns,\n }) as unknown as v.GenericSchema<\n ListInputFor<TTable, TFilterable, TSortable>,\n ListInputFor<TTable, TFilterable, TSortable>\n >\n\n const listOutputSchema = v.strictObject({\n items: v.array(selectSchema),\n nextCursor: v.optional(v.string()),\n hasMore: v.boolean(),\n total: v.optional(v.number()),\n limit: v.optional(v.number()),\n offset: v.optional(v.number()),\n cursor: v.optional(v.string()),\n q: v.optional(v.string()),\n sort: v.optional(v.string()),\n order: v.optional(v.string()),\n })\n\n // 1. LIST procedure\n const list = builder.public\n .route({\n method: 'GET',\n path: `/api/${name}`,\n summary: `List ${name}`,\n tags: [name],\n })\n .input(listQuerySchema)\n .output(listOutputSchema)\n .handler(async ({ input, context }) => {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n const result = await operations.list(name, input ?? {}, execCtx)\n return {\n ...result,\n items: result.items as TTable['$inferSelect'][],\n }\n })\n\n // 2. GET procedure\n const get = builder.public\n .route({\n method: 'GET',\n path: `/api/${name}/{id}`,\n summary: `Get ${name} by ID`,\n tags: [name],\n })\n .input(v.strictObject({ id: v.string() }))\n .output(selectSchema)\n .handler(async ({ input, context }) => {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n return (await operations.get(\n name,\n input.id,\n execCtx,\n )) as TTable['$inferSelect']\n })\n\n // 3. CREATE procedure\n const create = builder.public\n .route({\n method: 'POST',\n path: `/api/${name}`,\n summary: `Create ${name}`,\n tags: [name],\n successStatus: 201,\n })\n .input(insertSchema)\n .output(selectSchema)\n .handler(async ({ input, context }) => {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n const idempotencyKey = context.request.headers\n .get('Idempotency-Key')\n ?.trim()\n const rawBody = await context.getRawBody()\n\n const res = await operations.create(\n name,\n input,\n rawBody,\n idempotencyKey,\n execCtx,\n )\n if (res.type === 'replay') {\n context.resHeaders.set('Idempotency-Replayed', 'true')\n return res.record as TTable['$inferSelect']\n }\n return res.record as TTable['$inferSelect']\n })\n\n // 4. UPDATE procedure\n const update = builder.public\n .route({\n method: 'PATCH',\n path: `/api/${name}/{id}`,\n summary: `Update ${name}`,\n tags: [name],\n })\n .input(updateInputSchema)\n .output(selectSchema)\n .handler(async ({ input, context }) => {\n const requestPath = new URL(context.request.url).pathname\n if (!requestPath.startsWith('/api/rpc')) {\n const rawBody = await context.getRawBody()\n if (rawBody) {\n const body = JSON.parse(rawBody) as Record<string, unknown>\n if (Object.hasOwn(body, 'id')) {\n throw new WorkerstackError(\n 'BAD_REQUEST',\n 'The id field is immutable',\n )\n }\n }\n }\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n\n const { id, ...changes } = input\n return (await operations.update(\n name,\n id,\n changes,\n execCtx,\n )) as TTable['$inferSelect']\n })\n\n // 5. DELETE procedure\n const deleteProc = builder.public\n .route({\n method: 'DELETE',\n path: `/api/${name}/{id}`,\n summary: `Delete ${name}`,\n tags: [name],\n successStatus: 204,\n })\n .input(v.strictObject({ id: v.string() }))\n .output(v.undefined())\n .handler(async ({ input, context }) => {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n await operations.delete(name, input.id, execCtx)\n return undefined\n })\n\n // 6. LIVE procedure — one list query as a stream. A snapshot first, then the\n // changes this view cares about, placed by the server. Every connection\n // opens with a snapshot, so a reconnect is its own recovery: no client-side\n // event buffer, no Last-Event-ID bookkeeping, and no refetch path.\n const liveQuerySchema = buildLiveInputSchema(table, {\n filterableColumns: access.filterableColumns,\n sortableColumns: access.sortableColumns,\n }) as unknown as v.GenericSchema<\n LiveInputFor<TTable, TFilterable, TSortable>,\n LiveInputFor<TTable, TFilterable, TSortable>\n >\n\n const liveFrameSchema = v.union([\n v.strictObject({\n type: v.literal('snapshot'),\n items: v.array(selectSchema),\n sort: v.string(),\n order: v.picklist(['asc', 'desc']),\n limit: v.number(),\n hasMore: v.boolean(),\n operationId: v.optional(v.string()),\n }),\n v.strictObject({\n type: v.literal('upsert'),\n record: selectSchema,\n afterId: v.union([v.string(), v.null()]),\n operationId: v.optional(v.string()),\n }),\n v.strictObject({\n type: v.literal('remove'),\n id: v.string(),\n operationId: v.optional(v.string()),\n }),\n v.strictObject({ type: v.literal('heartbeat'), intervalMs: v.number() }),\n ])\n\n const live = builder.public\n .route({\n method: 'GET',\n // A path of its own, not a child of the table: `/{table}/live`\n // would make the id \"live\" unreachable on the get route, and a\n // colon suffix is a wildcard parameter in the oRPC matcher.\n path: `/api/live/${name}`,\n summary: `Live view of ${name}`,\n tags: [name],\n // Filters arrive as one URL-encoded JSON value, so no per-key query\n // parsing rule is needed.\n queryStyles: { filters: 'json' },\n })\n .input(liveQuerySchema)\n .output(eventIterator(liveFrameSchema))\n .handler(({ input, context, signal }) => {\n if (!livePublisher) {\n throw new Error(`Realtime is not enabled for live view of ${name}`)\n }\n // Subscribe before anything awaits, so no change slips between the\n // snapshot read and the start of the stream; events that arrive\n // during the query buffer in the publisher and replay on first pull.\n const changes = filterTableChanges(\n livePublisher.subscribe('change', { signal }),\n {\n tableName: schemaKey,\n entry: access,\n rule: access.list,\n request: context.request,\n getSession: context.getSession,\n },\n )\n\n return withRealtimeHeartbeat(\n (async function* () {\n const session = await context.getSession()\n const execCtx = {\n request: context.request,\n user: session.user,\n session: { activeOrganizationId: session.activeOrganizationId },\n }\n let view: ReturnType<typeof createLiveWindow> | undefined\n\n const readSnapshot = async (\n operationId?: string,\n ): Promise<LiveSnapshotFrame<TTable['$inferSelect']>> => {\n const result = await operations.list(name, input ?? {}, execCtx)\n view = createLiveWindow({\n sort: result.sort,\n order: result.order,\n limit: result.limit,\n filters: input?.filters as Record<string, unknown> | undefined,\n })\n view.reset(result.items, result.hasMore)\n return {\n type: 'snapshot',\n items: result.items,\n sort: result.sort,\n order: result.order,\n limit: result.limit,\n hasMore: result.hasMore,\n ...(operationId ? { operationId } : {}),\n }\n }\n\n yield await readSnapshot()\n\n for await (const change of changes) {\n const outcome = view!.apply(change)\n if (outcome.type === 'none') {\n // The write succeeded but did not affect this view. A snapshot\n // still acknowledges the operation without inventing a row.\n if (change.operationId)\n yield await readSnapshot(change.operationId)\n continue\n }\n if (outcome.type === 'resnapshot') {\n yield await readSnapshot(change.operationId)\n continue\n }\n for (const frame of outcome.frames) yield frame\n }\n })(),\n { intervalMs: REALTIME_HEARTBEAT_INTERVAL_MS, signal },\n )\n })\n\n const procedures = {\n list,\n get,\n create,\n update,\n delete: deleteProc,\n live,\n }\n\n if (!livePublisher) {\n delete (procedures as { live?: unknown }).live\n }\n\n return procedures\n}\n\nexport type TableCrudProcedures<\n TTable extends Table,\n TFilterable extends string = string,\n TSortable extends string = string,\n TLive extends boolean = false,\n> = Omit<\n ReturnType<\n typeof buildTableCrudProcedures<\n Record<string, unknown>,\n TTable,\n TFilterable,\n TSortable\n >\n >,\n 'live'\n> &\n (TLive extends true\n ? {\n live: ReturnType<\n typeof buildTableCrudProcedures<\n Record<string, unknown>,\n TTable,\n TFilterable,\n TSortable\n >\n >['live']\n }\n : {})\n\nexport function buildCrudApiRouter<\n TSchema extends Record<string, unknown>,\n TAccess extends Record<string, TableAccessInput> | undefined = undefined,\n TLive extends boolean = false,\n>(\n schema: TSchema,\n db: AnyDb,\n options: CrudApiRouterOptions<TSchema>,\n): CrudApiRouterFor<TSchema, TAccess, TLive> {\n const { access, realtime, idempotency, livePublisher } = options\n const builder = createApiBuilder<TSchema>()\n const operations = createCrudOperations({\n schema,\n db,\n access,\n idempotency,\n realtime,\n })\n\n const routerObj: Record<string, unknown> = {}\n\n for (const [tableKey, table] of Object.entries(schema)) {\n if (!isTable(table)) continue\n\n const name = getTableName(table)\n const tableAccess = tableEntryForName(access, name)\n if (!tableAccess?.enabled) continue\n\n const idCol = getTableColumns(table)['id']\n if (!idCol) continue\n\n const procedures = buildTableCrudProcedures({\n table: table as Table,\n operations,\n builder,\n access: tableAccess,\n schemaKey: tableKey,\n livePublisher,\n })\n\n routerObj[tableKey] = procedures\n }\n\n return routerObj as CrudApiRouterFor<TSchema, TAccess, TLive>\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Table } from 'drizzle-orm';
|
|
2
|
+
import * as v from 'valibot';
|
|
3
|
+
/**
|
|
4
|
+
* The input contract every list endpoint shares. Built from runtime column
|
|
5
|
+
* lists, so the schema's own inferred type cannot name the columns; callers
|
|
6
|
+
* restate it with the literals their configuration carries. Runtime shape and
|
|
7
|
+
* the restated type are the same object.
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildListInputSchema(table: Table, options: {
|
|
10
|
+
filterableColumns: readonly string[];
|
|
11
|
+
sortableColumns: readonly string[];
|
|
12
|
+
}): v.OptionalSchema<v.StrictObjectSchema<{
|
|
13
|
+
readonly limit: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
|
|
14
|
+
readonly offset: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
|
|
15
|
+
readonly cursor: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
16
|
+
readonly sort: v.OptionalSchema<v.PicklistSchema<string[], undefined>, undefined>;
|
|
17
|
+
readonly order: v.OptionalSchema<v.PicklistSchema<["asc", "desc"], undefined>, undefined>;
|
|
18
|
+
readonly q: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MaxLengthAction<string, 100, undefined>]>, undefined>;
|
|
19
|
+
readonly count: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
20
|
+
readonly filters: v.OptionalSchema<v.StrictObjectSchema<v.ObjectEntries, undefined>, undefined>;
|
|
21
|
+
}, undefined>, undefined>;
|
|
22
|
+
/**
|
|
23
|
+
* The input contract of a live view (`GET /api/{table}:live`): the list
|
|
24
|
+
* contract narrowed to what a stream can honor. No text search, no pagination
|
|
25
|
+
* — membership is decided per streamed record on the server, which only
|
|
26
|
+
* equality-style filters allow.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildLiveInputSchema(table: Table, options: {
|
|
29
|
+
filterableColumns: readonly string[];
|
|
30
|
+
sortableColumns: readonly string[];
|
|
31
|
+
}): v.OptionalSchema<v.StrictObjectSchema<{
|
|
32
|
+
readonly limit: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
|
|
33
|
+
readonly sort: v.OptionalSchema<v.PicklistSchema<string[], undefined>, undefined>;
|
|
34
|
+
readonly order: v.OptionalSchema<v.PicklistSchema<["asc", "desc"], undefined>, undefined>;
|
|
35
|
+
readonly filters: v.OptionalSchema<v.StrictObjectSchema<v.ObjectEntries, undefined>, undefined>;
|
|
36
|
+
}, undefined>, undefined>;
|
|
37
|
+
//# sourceMappingURL=list-input-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-input-schema.d.ts","sourceRoot":"","sources":["../../src/api/list-input-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAGxC,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAsC5B;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE;IACP,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAA;CACnC;;;;;;;;;0BAiBF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE;IACP,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAA;CACnC;;;;;0BAaF"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createSelectSchema } from 'drizzle-valibot';
|
|
2
|
+
import * as v from 'valibot';
|
|
3
|
+
import { MAX_LIST_LIMIT } from '../list-query.js';
|
|
4
|
+
/**
|
|
5
|
+
* One filter field per allowed column, typed by the column itself: a scalar
|
|
6
|
+
* for `=`, a list for `IN`, and `null` for `IS NULL`. Query strings are
|
|
7
|
+
* coerced to these types by SmartCoercionHandlerPlugin, so REST and RPC share
|
|
8
|
+
* one contract and nothing has to re-read the raw URL.
|
|
9
|
+
*/
|
|
10
|
+
function buildFilterEntries(table, filterableColumns) {
|
|
11
|
+
const selectEntries = createSelectSchema(table).entries;
|
|
12
|
+
const filterEntries = {};
|
|
13
|
+
for (const column of filterableColumns) {
|
|
14
|
+
const base = selectEntries[column];
|
|
15
|
+
if (!base)
|
|
16
|
+
continue;
|
|
17
|
+
filterEntries[column] = v.optional(v.union([
|
|
18
|
+
// `?filters[col]=null` — a query string cannot carry a real null.
|
|
19
|
+
v.pipe(v.literal('null'), v.transform(() => null)),
|
|
20
|
+
base,
|
|
21
|
+
v.pipe(v.array(base), v.maxLength(MAX_LIST_LIMIT)),
|
|
22
|
+
v.null(),
|
|
23
|
+
]));
|
|
24
|
+
}
|
|
25
|
+
return filterEntries;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The input contract every list endpoint shares. Built from runtime column
|
|
29
|
+
* lists, so the schema's own inferred type cannot name the columns; callers
|
|
30
|
+
* restate it with the literals their configuration carries. Runtime shape and
|
|
31
|
+
* the restated type are the same object.
|
|
32
|
+
*/
|
|
33
|
+
export function buildListInputSchema(table, options) {
|
|
34
|
+
const filterEntries = buildFilterEntries(table, options.filterableColumns);
|
|
35
|
+
return v.optional(v.strictObject({
|
|
36
|
+
limit: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1))),
|
|
37
|
+
offset: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0))),
|
|
38
|
+
cursor: v.optional(v.string()),
|
|
39
|
+
sort: v.optional(v.picklist(options.sortableColumns)),
|
|
40
|
+
order: v.optional(v.picklist(['asc', 'desc'])),
|
|
41
|
+
q: v.optional(v.pipe(v.string(), v.maxLength(100))),
|
|
42
|
+
count: v.optional(v.boolean()),
|
|
43
|
+
// Always present, even with no filterable columns: clients send `{}`.
|
|
44
|
+
filters: v.optional(v.strictObject(filterEntries)),
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The input contract of a live view (`GET /api/{table}:live`): the list
|
|
49
|
+
* contract narrowed to what a stream can honor. No text search, no pagination
|
|
50
|
+
* — membership is decided per streamed record on the server, which only
|
|
51
|
+
* equality-style filters allow.
|
|
52
|
+
*/
|
|
53
|
+
export function buildLiveInputSchema(table, options) {
|
|
54
|
+
const filterEntries = buildFilterEntries(table, options.filterableColumns);
|
|
55
|
+
return v.optional(v.strictObject({
|
|
56
|
+
limit: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1))),
|
|
57
|
+
sort: v.optional(v.picklist(options.sortableColumns)),
|
|
58
|
+
order: v.optional(v.picklist(['asc', 'desc'])),
|
|
59
|
+
// Always present, even with no filterable columns: clients send `{}`.
|
|
60
|
+
filters: v.optional(v.strictObject(filterEntries)),
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=list-input-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-input-schema.js","sourceRoot":"","sources":["../../src/api/list-input-schema.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Table } from 'drizzle-orm'\n\nimport { createSelectSchema } from 'drizzle-valibot'\nimport * as v from 'valibot'\n\nimport { MAX_LIST_LIMIT } from '../list-query'\n\n/**\n * One filter field per allowed column, typed by the column itself: a scalar\n * for `=`, a list for `IN`, and `null` for `IS NULL`. Query strings are\n * coerced to these types by SmartCoercionHandlerPlugin, so REST and RPC share\n * one contract and nothing has to re-read the raw URL.\n */\nfunction buildFilterEntries(\n table: Table,\n filterableColumns: readonly string[],\n): v.ObjectEntries {\n const selectEntries = createSelectSchema(table).entries as Record<\n string,\n v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>\n >\n const filterEntries: v.ObjectEntries = {}\n for (const column of filterableColumns) {\n const base = selectEntries[column]\n if (!base) continue\n filterEntries[column] = v.optional(\n v.union([\n // `?filters[col]=null` — a query string cannot carry a real null.\n v.pipe(\n v.literal('null'),\n v.transform(() => null),\n ),\n base,\n v.pipe(v.array(base), v.maxLength(MAX_LIST_LIMIT)),\n v.null(),\n ]),\n )\n }\n return filterEntries\n}\n\n/**\n * The input contract every list endpoint shares. Built from runtime column\n * lists, so the schema's own inferred type cannot name the columns; callers\n * restate it with the literals their configuration carries. Runtime shape and\n * the restated type are the same object.\n */\nexport function buildListInputSchema(\n table: Table,\n options: {\n filterableColumns: readonly string[]\n sortableColumns: readonly string[]\n },\n) {\n const filterEntries = buildFilterEntries(table, options.filterableColumns)\n\n return v.optional(\n v.strictObject({\n limit: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1))),\n offset: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0))),\n cursor: v.optional(v.string()),\n sort: v.optional(v.picklist(options.sortableColumns as string[])),\n order: v.optional(v.picklist(['asc', 'desc'])),\n q: v.optional(v.pipe(v.string(), v.maxLength(100))),\n count: v.optional(v.boolean()),\n // Always present, even with no filterable columns: clients send `{}`.\n filters: v.optional(v.strictObject(filterEntries)),\n }),\n )\n}\n\n/**\n * The input contract of a live view (`GET /api/{table}:live`): the list\n * contract narrowed to what a stream can honor. No text search, no pagination\n * — membership is decided per streamed record on the server, which only\n * equality-style filters allow.\n */\nexport function buildLiveInputSchema(\n table: Table,\n options: {\n filterableColumns: readonly string[]\n sortableColumns: readonly string[]\n },\n) {\n const filterEntries = buildFilterEntries(table, options.filterableColumns)\n\n return v.optional(\n v.strictObject({\n limit: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1))),\n sort: v.optional(v.picklist(options.sortableColumns as string[])),\n order: v.optional(v.picklist(['asc', 'desc'])),\n // Always present, even with no filterable columns: clients send `{}`.\n filters: v.optional(v.strictObject(filterEntries)),\n }),\n )\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Table } from 'drizzle-orm';
|
|
2
|
+
import type * as v from 'valibot';
|
|
3
|
+
import type { ListParamsInput, ListResult } from '../list-query.js';
|
|
4
|
+
export type ListSpecOptions = {
|
|
5
|
+
/** Columns a caller may filter on. Others are rejected by the schema. */
|
|
6
|
+
filterable?: readonly string[];
|
|
7
|
+
/** Columns a caller may sort on. Defaults to the default sort column. */
|
|
8
|
+
sortable?: readonly string[];
|
|
9
|
+
defaultSort?: {
|
|
10
|
+
column: string;
|
|
11
|
+
order: 'asc' | 'desc';
|
|
12
|
+
};
|
|
13
|
+
/** Columns the `q` parameter searches. */
|
|
14
|
+
searchable?: readonly string[];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The input schema and handler of a list endpoint, with the same filter, sort,
|
|
18
|
+
* cursor, and count contract that the generated CRUD list uses.
|
|
19
|
+
*
|
|
20
|
+
* The caller applies both to its own base procedure:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* const logsList = listSpec(appLogs, { filterable: ['level'] })
|
|
24
|
+
* getLogs: adminProcedure.input(logsList.input).handler(logsList.handler)
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* The two parts are returned separately rather than as a finished procedure,
|
|
28
|
+
* because a procedure built here would have to receive the builder as a
|
|
29
|
+
* generic parameter. TypeScript resolves a method call on a generic parameter
|
|
30
|
+
* through its constraint, which erases the input schema and the row type. With
|
|
31
|
+
* this shape the builder stays concrete at the call site and keeps both.
|
|
32
|
+
*
|
|
33
|
+
* It reads no `access` configuration: the base procedure carries the policy.
|
|
34
|
+
*/
|
|
35
|
+
export declare function listSpec<TTable extends Table>(table: TTable, options?: ListSpecOptions): {
|
|
36
|
+
input: v.GenericSchema<ListParamsInput | undefined, ListParamsInput | undefined>;
|
|
37
|
+
handler: (handlerOptions: {
|
|
38
|
+
context: {
|
|
39
|
+
db: unknown;
|
|
40
|
+
};
|
|
41
|
+
input: ListParamsInput | undefined;
|
|
42
|
+
}) => Promise<ListResult<TTable["$inferSelect"]>>;
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=list-spec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-spec.d.ts","sourceRoot":"","sources":["../../src/api/list-spec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,KAAK,KAAK,CAAC,MAAM,SAAS,CAAA;AAKjC,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAKhE,MAAM,MAAM,eAAe,GAAG;IAC5B,yEAAyE;IACzE,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B,WAAW,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,CAAA;IACvD,0CAA0C;IAC1C,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC/B,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,QAAQ,CAAC,MAAM,SAAS,KAAK,EAC3C,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,eAAoB;;8BA8BU;QACrC,OAAO,EAAE;YAAE,EAAE,EAAE,OAAO,CAAA;SAAE,CAAA;QACxB,KAAK,EAAE,eAAe,GAAG,SAAS,CAAA;KACnC,KAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;EAYhD"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getTableColumns, getTableName } from 'drizzle-orm';
|
|
2
|
+
import { executeList, resolveListParams } from '../list-query.js';
|
|
3
|
+
import { buildListInputSchema } from './list-input-schema.js';
|
|
4
|
+
/**
|
|
5
|
+
* The input schema and handler of a list endpoint, with the same filter, sort,
|
|
6
|
+
* cursor, and count contract that the generated CRUD list uses.
|
|
7
|
+
*
|
|
8
|
+
* The caller applies both to its own base procedure:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* const logsList = listSpec(appLogs, { filterable: ['level'] })
|
|
12
|
+
* getLogs: adminProcedure.input(logsList.input).handler(logsList.handler)
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* The two parts are returned separately rather than as a finished procedure,
|
|
16
|
+
* because a procedure built here would have to receive the builder as a
|
|
17
|
+
* generic parameter. TypeScript resolves a method call on a generic parameter
|
|
18
|
+
* through its constraint, which erases the input schema and the row type. With
|
|
19
|
+
* this shape the builder stays concrete at the call site and keeps both.
|
|
20
|
+
*
|
|
21
|
+
* It reads no `access` configuration: the base procedure carries the policy.
|
|
22
|
+
*/
|
|
23
|
+
export function listSpec(table, options = {}) {
|
|
24
|
+
const defaultSort = options.defaultSort ?? { column: 'id', order: 'asc' };
|
|
25
|
+
const sortableColumns = options.sortable ?? [defaultSort.column];
|
|
26
|
+
const inputSchema = buildListInputSchema(table, {
|
|
27
|
+
filterableColumns: options.filterable ?? [],
|
|
28
|
+
sortableColumns,
|
|
29
|
+
});
|
|
30
|
+
// `executeList` reads its policy from a resolved access record. This spec
|
|
31
|
+
// declares that policy at the call site instead, because the base procedure
|
|
32
|
+
// — not `access.ts` — carries the authorization for it.
|
|
33
|
+
const access = {
|
|
34
|
+
filterableColumns: options.filterable ?? [],
|
|
35
|
+
sortableColumns,
|
|
36
|
+
searchableColumns: options.searchable ?? [],
|
|
37
|
+
defaultSort,
|
|
38
|
+
};
|
|
39
|
+
const idColumn = getTableColumns(table).id;
|
|
40
|
+
if (!idColumn) {
|
|
41
|
+
throw new Error(`[workerstack] listSpec requires an "id" column on table "${getTableName(table)}"`);
|
|
42
|
+
}
|
|
43
|
+
const handler = async (handlerOptions) => {
|
|
44
|
+
const params = resolveListParams(handlerOptions.input ?? {}, access);
|
|
45
|
+
return executeList(handlerOptions.context.db, table, access, params, idColumn);
|
|
46
|
+
};
|
|
47
|
+
return { input: inputSchema, handler };
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=list-spec.js.map
|