@segmentstream/ontology 0.0.0-stage → 0.92.1
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/LICENSE +201 -0
- package/NOTICE +2 -0
- package/README.md +1655 -2
- package/dist/action.d.ts +193 -0
- package/dist/action.js +41 -0
- package/dist/agent-chat.d.ts +44 -0
- package/dist/agent-chat.js +91 -0
- package/dist/agent-task-contract.d.ts +64 -0
- package/dist/agent-task-contract.js +15 -0
- package/dist/agent-task.d.ts +39 -0
- package/dist/agent-task.js +112 -0
- package/dist/analyze.d.ts +215 -0
- package/dist/analyze.js +2438 -0
- package/dist/app-feedback.d.ts +167 -0
- package/dist/app-feedback.js +176 -0
- package/dist/authoring-input.d.ts +3 -0
- package/dist/authoring-input.js +107 -0
- package/dist/authoring-names.d.ts +4 -0
- package/dist/authoring-names.js +13 -0
- package/dist/authoring-records.d.ts +35 -0
- package/dist/authoring-records.js +264 -0
- package/dist/authoring.d.ts +15 -0
- package/dist/authoring.js +331 -0
- package/dist/bin/check.d.ts +30 -0
- package/dist/bin/check.js +99 -0
- package/dist/bin/load.d.ts +48 -0
- package/dist/bin/load.js +139 -0
- package/dist/bin/main.d.ts +31 -0
- package/dist/bin/main.js +121 -0
- package/dist/bin/migrate.d.ts +57 -0
- package/dist/bin/migrate.js +170 -0
- package/dist/bin/output.d.ts +31 -0
- package/dist/bin/output.js +84 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +31 -0
- package/dist/blob.d.ts +52 -0
- package/dist/blob.js +87 -0
- package/dist/channel-session.d.ts +16 -0
- package/dist/channel-session.js +16 -0
- package/dist/chat.d.ts +43 -0
- package/dist/chat.js +34 -0
- package/dist/confirmation.d.ts +46 -0
- package/dist/confirmation.js +41 -0
- package/dist/connection.d.ts +213 -0
- package/dist/connection.js +289 -0
- package/dist/deployment.d.ts +35 -0
- package/dist/deployment.js +76 -0
- package/dist/diagnostics.d.ts +27 -0
- package/dist/diagnostics.js +106 -0
- package/dist/emails.d.ts +70 -0
- package/dist/emails.js +221 -0
- package/dist/entity.d.ts +331 -0
- package/dist/entity.js +153 -0
- package/dist/errors.d.ts +154 -0
- package/dist/errors.js +109 -0
- package/dist/feedback-config.d.ts +24 -0
- package/dist/feedback-config.js +126 -0
- package/dist/files.d.ts +39 -0
- package/dist/files.js +4 -0
- package/dist/groups.d.ts +95 -0
- package/dist/groups.js +280 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +70 -0
- package/dist/internal/diagnostics.js +594 -0
- package/dist/legacy.d.ts +160 -0
- package/dist/legacy.js +270 -0
- package/dist/manifest-compatibility.d.ts +8 -0
- package/dist/manifest-compatibility.js +87 -0
- package/dist/manifest.d.ts +3329 -0
- package/dist/manifest.js +757 -0
- package/dist/me.d.ts +46 -0
- package/dist/me.js +109 -0
- package/dist/metric-filters.d.ts +48 -0
- package/dist/metric-filters.js +252 -0
- package/dist/metric-routes.d.ts +132 -0
- package/dist/metric-routes.js +498 -0
- package/dist/metric.d.ts +172 -0
- package/dist/metric.js +132 -0
- package/dist/migrate/apply.d.ts +94 -0
- package/dist/migrate/apply.js +526 -0
- package/dist/migrate/errors.d.ts +46 -0
- package/dist/migrate/errors.js +38 -0
- package/dist/migrate/index.d.ts +10 -0
- package/dist/migrate/index.js +7 -0
- package/dist/migrate/model.d.ts +2311 -0
- package/dist/migrate/model.js +148 -0
- package/dist/migrate/plan.d.ts +51 -0
- package/dist/migrate/plan.js +1856 -0
- package/dist/migrate/preview.d.ts +13 -0
- package/dist/migrate/preview.js +65 -0
- package/dist/migrate/storage.d.ts +226 -0
- package/dist/migrate/storage.js +774 -0
- package/dist/model-metrics.d.ts +22 -0
- package/dist/model-metrics.js +545 -0
- package/dist/names.d.ts +49 -0
- package/dist/names.js +110 -0
- package/dist/navigation-artifact.d.ts +13 -0
- package/dist/navigation-artifact.js +11 -0
- package/dist/navigation-catalog.d.ts +55 -0
- package/dist/navigation-catalog.js +297 -0
- package/dist/nitro-navigation.d.ts +3 -0
- package/dist/nitro-navigation.js +64 -0
- package/dist/nitro-workflows.d.ts +108 -0
- package/dist/nitro-workflows.js +286 -0
- package/dist/nitro.d.ts +40 -0
- package/dist/nitro.js +50 -0
- package/dist/ontology.d.ts +41 -0
- package/dist/ontology.js +46 -0
- package/dist/permissions.d.ts +198 -0
- package/dist/permissions.js +272 -0
- package/dist/property.d.ts +188 -0
- package/dist/property.js +301 -0
- package/dist/query.d.ts +245 -0
- package/dist/query.js +615 -0
- package/dist/relationship.d.ts +99 -0
- package/dist/relationship.js +33 -0
- package/dist/render.d.ts +81 -0
- package/dist/render.js +512 -0
- package/dist/replication/baseline.d.ts +51 -0
- package/dist/replication/baseline.js +215 -0
- package/dist/replication/bigquery.d.ts +37 -0
- package/dist/replication/bigquery.js +211 -0
- package/dist/replication/coverage.d.ts +14 -0
- package/dist/replication/coverage.js +53 -0
- package/dist/replication/evolution.d.ts +25 -0
- package/dist/replication/evolution.js +184 -0
- package/dist/replication/identities.d.ts +6 -0
- package/dist/replication/identities.js +47 -0
- package/dist/replication/legacy-baseline.d.ts +27 -0
- package/dist/replication/legacy-baseline.js +109 -0
- package/dist/replication/legacy-store.d.ts +19 -0
- package/dist/replication/legacy-store.js +63 -0
- package/dist/replication/lineage.d.ts +36 -0
- package/dist/replication/lineage.js +72 -0
- package/dist/replication/migrate.d.ts +21 -0
- package/dist/replication/migrate.js +84 -0
- package/dist/replication/model.d.ts +52 -0
- package/dist/replication/model.js +99 -0
- package/dist/replication/scans.d.ts +34 -0
- package/dist/replication/scans.js +158 -0
- package/dist/replication/schema.d.ts +11 -0
- package/dist/replication/schema.js +226 -0
- package/dist/replication/shadow-schema.d.ts +4 -0
- package/dist/replication/shadow-schema.js +61 -0
- package/dist/replication/shadow.d.ts +127 -0
- package/dist/replication/shadow.js +614 -0
- package/dist/replication/source-replacement.d.ts +33 -0
- package/dist/replication/source-replacement.js +76 -0
- package/dist/replication/store.d.ts +36 -0
- package/dist/replication/store.js +382 -0
- package/dist/replication/transaction.d.ts +23 -0
- package/dist/replication/transaction.js +88 -0
- package/dist/role.d.ts +61 -0
- package/dist/role.js +76 -0
- package/dist/runtime/action-deadlocks.d.ts +21 -0
- package/dist/runtime/action-deadlocks.js +130 -0
- package/dist/runtime/action-transactions.d.ts +35 -0
- package/dist/runtime/action-transactions.js +54 -0
- package/dist/runtime/agent-counters.d.ts +26 -0
- package/dist/runtime/agent-counters.js +98 -0
- package/dist/runtime/agent-tasks.d.ts +63 -0
- package/dist/runtime/agent-tasks.js +120 -0
- package/dist/runtime/agents.d.ts +15 -0
- package/dist/runtime/agents.js +457 -0
- package/dist/runtime/batch-diagnostics.d.ts +22 -0
- package/dist/runtime/batch-diagnostics.js +89 -0
- package/dist/runtime/batch.d.ts +52 -0
- package/dist/runtime/batch.js +534 -0
- package/dist/runtime/channel-session-revocation.d.ts +3 -0
- package/dist/runtime/channel-session-revocation.js +5 -0
- package/dist/runtime/channel-sessions.d.ts +4 -0
- package/dist/runtime/channel-sessions.js +61 -0
- package/dist/runtime/confirmations.d.ts +29 -0
- package/dist/runtime/confirmations.js +57 -0
- package/dist/runtime/cursor.d.ts +23 -0
- package/dist/runtime/cursor.js +100 -0
- package/dist/runtime/deployment.d.ts +7 -0
- package/dist/runtime/deployment.js +4 -0
- package/dist/runtime/errors.d.ts +118 -0
- package/dist/runtime/errors.js +109 -0
- package/dist/runtime/federation-tables.d.ts +2 -0
- package/dist/runtime/federation-tables.js +20 -0
- package/dist/runtime/federation.d.ts +42 -0
- package/dist/runtime/federation.js +222 -0
- package/dist/runtime/files.d.ts +39 -0
- package/dist/runtime/files.js +44 -0
- package/dist/runtime/generated-actions.d.ts +37 -0
- package/dist/runtime/generated-actions.js +307 -0
- package/dist/runtime/history.d.ts +91 -0
- package/dist/runtime/history.js +77 -0
- package/dist/runtime/ids.d.ts +6 -0
- package/dist/runtime/ids.js +39 -0
- package/dist/runtime/member-tables.d.ts +8 -0
- package/dist/runtime/member-tables.js +33 -0
- package/dist/runtime/member-writes.d.ts +78 -0
- package/dist/runtime/member-writes.js +202 -0
- package/dist/runtime/metric-query.d.ts +19 -0
- package/dist/runtime/metric-query.js +411 -0
- package/dist/runtime/model.d.ts +67 -0
- package/dist/runtime/model.js +150 -0
- package/dist/runtime/new-user-hook.d.ts +6 -0
- package/dist/runtime/new-user-hook.js +37 -0
- package/dist/runtime/oauth.d.ts +9 -0
- package/dist/runtime/oauth.js +299 -0
- package/dist/runtime/pipeline.d.ts +155 -0
- package/dist/runtime/pipeline.js +597 -0
- package/dist/runtime/postgres-tls.d.ts +10 -0
- package/dist/runtime/postgres-tls.js +14 -0
- package/dist/runtime/principals.d.ts +302 -0
- package/dist/runtime/principals.js +1307 -0
- package/dist/runtime/reads.d.ts +55 -0
- package/dist/runtime/reads.js +313 -0
- package/dist/runtime/records.d.ts +42 -0
- package/dist/runtime/records.js +245 -0
- package/dist/runtime/resolve-agent-identity.d.ts +6 -0
- package/dist/runtime/resolve-agent-identity.js +181 -0
- package/dist/runtime/service-binding.d.ts +18 -0
- package/dist/runtime/service-binding.js +266 -0
- package/dist/runtime/sign-in-links.d.ts +68 -0
- package/dist/runtime/sign-in-links.js +147 -0
- package/dist/runtime/sql-runtime.d.ts +131 -0
- package/dist/runtime/sql-runtime.js +362 -0
- package/dist/runtime/statement.d.ts +18 -0
- package/dist/runtime/statement.js +31 -0
- package/dist/runtime/user-identities.d.ts +66 -0
- package/dist/runtime/user-identities.js +106 -0
- package/dist/runtime/user-identity-sql.d.ts +86 -0
- package/dist/runtime/user-identity-sql.js +297 -0
- package/dist/runtime/user-roles.d.ts +16 -0
- package/dist/runtime/user-roles.js +32 -0
- package/dist/runtime/validation.d.ts +16 -0
- package/dist/runtime/validation.js +26 -0
- package/dist/runtime/where.d.ts +57 -0
- package/dist/runtime/where.js +404 -0
- package/dist/runtime/workflow-admission.d.ts +49 -0
- package/dist/runtime/workflow-admission.js +92 -0
- package/dist/runtime/workflow-argument-codec.d.ts +10 -0
- package/dist/runtime/workflow-argument-codec.js +120 -0
- package/dist/runtime/workflow-arguments.d.ts +17 -0
- package/dist/runtime/workflow-arguments.js +99 -0
- package/dist/runtime/workflow-starts.d.ts +203 -0
- package/dist/runtime/workflow-starts.js +493 -0
- package/dist/runtime/workflow-users.d.ts +9 -0
- package/dist/runtime/workflow-users.js +17 -0
- package/dist/runtime/workload.d.ts +25 -0
- package/dist/runtime/workload.js +24 -0
- package/dist/runtime/writes.d.ts +108 -0
- package/dist/runtime/writes.js +491 -0
- package/dist/runtime-source.d.ts +22 -0
- package/dist/runtime-source.js +28 -0
- package/dist/runtime.d.ts +310 -0
- package/dist/runtime.js +31 -0
- package/dist/server.d.ts +23 -0
- package/dist/server.js +107 -0
- package/dist/service/agent/attachments.d.ts +33 -0
- package/dist/service/agent/attachments.js +228 -0
- package/dist/service/agent/authentication.d.ts +22 -0
- package/dist/service/agent/authentication.js +58 -0
- package/dist/service/agent/confirmations.d.ts +5 -0
- package/dist/service/agent/confirmations.js +73 -0
- package/dist/service/agent/conversation-routes.d.ts +14 -0
- package/dist/service/agent/conversation-routes.js +45 -0
- package/dist/service/agent/conversations.d.ts +40 -0
- package/dist/service/agent/conversations.js +69 -0
- package/dist/service/agent/files.d.ts +12 -0
- package/dist/service/agent/files.js +69 -0
- package/dist/service/agent/protocol.d.ts +90 -0
- package/dist/service/agent/protocol.js +114 -0
- package/dist/service/agent/server.d.ts +28 -0
- package/dist/service/agent/server.js +374 -0
- package/dist/service/agent/task-caller.d.ts +21 -0
- package/dist/service/agent/task-caller.js +35 -0
- package/dist/service/agent/task-start.d.ts +53 -0
- package/dist/service/agent/task-start.js +161 -0
- package/dist/service/agent/tasks.d.ts +40 -0
- package/dist/service/agent/tasks.js +155 -0
- package/dist/service/api.d.ts +50 -0
- package/dist/service/api.js +288 -0
- package/dist/service/authentication.d.ts +227 -0
- package/dist/service/authentication.js +767 -0
- package/dist/service/caller-context.d.ts +24 -0
- package/dist/service/caller-context.js +6 -0
- package/dist/service/catalog.d.ts +42 -0
- package/dist/service/catalog.js +66 -0
- package/dist/service/channel/provider.d.ts +10 -0
- package/dist/service/channel/provider.js +1 -0
- package/dist/service/channel/providers/telegram.d.ts +18 -0
- package/dist/service/channel/providers/telegram.js +108 -0
- package/dist/service/channel/sessions.d.ts +5 -0
- package/dist/service/channel/sessions.js +53 -0
- package/dist/service/channel/sign-in.d.ts +7 -0
- package/dist/service/channel/sign-in.js +78 -0
- package/dist/service/chat/archive.d.ts +64 -0
- package/dist/service/chat/archive.js +62 -0
- package/dist/service/chat/attribution.d.ts +26 -0
- package/dist/service/chat/attribution.js +28 -0
- package/dist/service/chat/delivery.d.ts +18 -0
- package/dist/service/chat/delivery.js +251 -0
- package/dist/service/chat/diagnostics.d.ts +2 -0
- package/dist/service/chat/diagnostics.js +35 -0
- package/dist/service/chat/envelope.d.ts +96 -0
- package/dist/service/chat/envelope.js +39 -0
- package/dist/service/chat/idempotency.d.ts +19 -0
- package/dist/service/chat/idempotency.js +37 -0
- package/dist/service/chat/providers/telegram.d.ts +36 -0
- package/dist/service/chat/providers/telegram.js +133 -0
- package/dist/service/chat/reads.d.ts +25 -0
- package/dist/service/chat/reads.js +75 -0
- package/dist/service/chat/resolve.d.ts +34 -0
- package/dist/service/chat/resolve.js +150 -0
- package/dist/service/chat/routes.d.ts +23 -0
- package/dist/service/chat/routes.js +75 -0
- package/dist/service/chat/schemas.d.ts +43 -0
- package/dist/service/chat/schemas.js +31 -0
- package/dist/service/config.d.ts +218 -0
- package/dist/service/config.js +265 -0
- package/dist/service/connection-stream.d.ts +10 -0
- package/dist/service/connection-stream.js +191 -0
- package/dist/service/connections.d.ts +60 -0
- package/dist/service/connections.js +271 -0
- package/dist/service/decisions.d.ts +35 -0
- package/dist/service/decisions.js +63 -0
- package/dist/service/descriptions.d.ts +8 -0
- package/dist/service/descriptions.js +20 -0
- package/dist/service/discovery-schemas.d.ts +92 -0
- package/dist/service/discovery-schemas.js +59 -0
- package/dist/service/discovery-search.d.ts +30 -0
- package/dist/service/discovery-search.js +95 -0
- package/dist/service/discovery.d.ts +37 -0
- package/dist/service/discovery.js +436 -0
- package/dist/service/docs.d.ts +9 -0
- package/dist/service/docs.js +197 -0
- package/dist/service/errors.d.ts +145 -0
- package/dist/service/errors.js +704 -0
- package/dist/service/federation-provider.d.ts +36 -0
- package/dist/service/federation-provider.js +110 -0
- package/dist/service/federation-server.d.ts +20 -0
- package/dist/service/federation-server.js +249 -0
- package/dist/service/federation-status.d.ts +40 -0
- package/dist/service/federation-status.js +151 -0
- package/dist/service/handlers.d.ts +18 -0
- package/dist/service/handlers.js +287 -0
- package/dist/service/mcp/confirmation.d.ts +68 -0
- package/dist/service/mcp/confirmation.js +178 -0
- package/dist/service/mcp/find-input.d.ts +12 -0
- package/dist/service/mcp/find-input.js +122 -0
- package/dist/service/mcp/input-schema.d.ts +6 -0
- package/dist/service/mcp/input-schema.js +126 -0
- package/dist/service/mcp/listing.d.ts +26 -0
- package/dist/service/mcp/listing.js +92 -0
- package/dist/service/mcp/metric-tools.d.ts +85 -0
- package/dist/service/mcp/metric-tools.js +258 -0
- package/dist/service/mcp/request-log.d.ts +5 -0
- package/dist/service/mcp/request-log.js +53 -0
- package/dist/service/mcp/runtime-policy.d.ts +27 -0
- package/dist/service/mcp/runtime-policy.js +62 -0
- package/dist/service/mcp/server.d.ts +52 -0
- package/dist/service/mcp/server.js +861 -0
- package/dist/service/mcp/tools.d.ts +99 -0
- package/dist/service/mcp/tools.js +938 -0
- package/dist/service/member-file-routes.d.ts +22 -0
- package/dist/service/member-file-routes.js +52 -0
- package/dist/service/member-files.d.ts +245 -0
- package/dist/service/member-files.js +258 -0
- package/dist/service/metric-query-platform.d.ts +77 -0
- package/dist/service/metric-query-platform.js +198 -0
- package/dist/service/metric-query-scopes.d.ts +43 -0
- package/dist/service/metric-query-scopes.js +147 -0
- package/dist/service/metric-query.d.ts +48 -0
- package/dist/service/metric-query.js +200 -0
- package/dist/service/name-suggestions.d.ts +3 -0
- package/dist/service/name-suggestions.js +38 -0
- package/dist/service/navigation-schemas.d.ts +80 -0
- package/dist/service/navigation-schemas.js +49 -0
- package/dist/service/navigation.d.ts +20 -0
- package/dist/service/navigation.js +165 -0
- package/dist/service/oauth/protocol.d.ts +70 -0
- package/dist/service/oauth/protocol.js +157 -0
- package/dist/service/oauth/refresh-replay.d.ts +2 -0
- package/dist/service/oauth/refresh-replay.js +20 -0
- package/dist/service/oauth/server.d.ts +28 -0
- package/dist/service/oauth/server.js +769 -0
- package/dist/service/operations.d.ts +170 -0
- package/dist/service/operations.js +392 -0
- package/dist/service/plan.d.ts +57 -0
- package/dist/service/plan.js +96 -0
- package/dist/service/rate-limit.d.ts +22 -0
- package/dist/service/rate-limit.js +52 -0
- package/dist/service/record-reads.d.ts +21 -0
- package/dist/service/record-reads.js +155 -0
- package/dist/service/replication.d.ts +5 -0
- package/dist/service/replication.js +39 -0
- package/dist/service/runtime-contract.d.ts +508 -0
- package/dist/service/runtime-contract.js +18 -0
- package/dist/service/runtime-identity.d.ts +71 -0
- package/dist/service/runtime-identity.js +122 -0
- package/dist/service/schemas.d.ts +961 -0
- package/dist/service/schemas.js +639 -0
- package/dist/service/service.d.ts +21 -0
- package/dist/service/service.js +526 -0
- package/dist/service/warehouse-platform.d.ts +17 -0
- package/dist/service/warehouse-platform.js +122 -0
- package/dist/service/warehouse-query.d.ts +24 -0
- package/dist/service/warehouse-query.js +280 -0
- package/dist/service/webhooks.d.ts +106 -0
- package/dist/service/webhooks.js +233 -0
- package/dist/service/workflow-admission-provider.d.ts +31 -0
- package/dist/service/workflow-admission-provider.js +205 -0
- package/dist/service/workflow-argument-serialization.d.ts +2 -0
- package/dist/service/workflow-argument-serialization.js +14 -0
- package/dist/service/workflow-callback.d.ts +22 -0
- package/dist/service/workflow-callback.js +75 -0
- package/dist/service/workflow-deployment.d.ts +8 -0
- package/dist/service/workflow-deployment.js +131 -0
- package/dist/service/workflow-diagnostics.d.ts +23 -0
- package/dist/service/workflow-diagnostics.js +263 -0
- package/dist/service/workflow-errors.d.ts +26 -0
- package/dist/service/workflow-errors.js +181 -0
- package/dist/service/workflow-logging.d.ts +2 -0
- package/dist/service/workflow-logging.js +50 -0
- package/dist/service/workflow-queue-fence.d.ts +6 -0
- package/dist/service/workflow-queue-fence.js +66 -0
- package/dist/service/workflows.d.ts +200 -0
- package/dist/service/workflows.js +1549 -0
- package/dist/sign-in.d.ts +58 -0
- package/dist/sign-in.js +57 -0
- package/dist/source.d.ts +30 -0
- package/dist/source.js +107 -0
- package/dist/start-recovery-contract.d.ts +49 -0
- package/dist/start-recovery-contract.js +26 -0
- package/dist/start-recovery.d.ts +27 -0
- package/dist/start-recovery.js +81 -0
- package/dist/storage/api.d.ts +4 -0
- package/dist/storage/api.js +40 -0
- package/dist/storage/attachments.d.ts +146 -0
- package/dist/storage/attachments.js +733 -0
- package/dist/storage/authorization.d.ts +38 -0
- package/dist/storage/authorization.js +83 -0
- package/dist/storage/file-access.d.ts +17 -0
- package/dist/storage/file-access.js +8 -0
- package/dist/storage/limits.d.ts +12 -0
- package/dist/storage/limits.js +17 -0
- package/dist/storage/maintenance-auth.d.ts +20 -0
- package/dist/storage/maintenance-auth.js +24 -0
- package/dist/storage/maintenance.d.ts +11 -0
- package/dist/storage/maintenance.js +67 -0
- package/dist/storage/neon.d.ts +4 -0
- package/dist/storage/neon.js +318 -0
- package/dist/storage/operator.d.ts +11 -0
- package/dist/storage/operator.js +30 -0
- package/dist/storage/provider.d.ts +93 -0
- package/dist/storage/provider.js +36 -0
- package/dist/storage/sessions.d.ts +184 -0
- package/dist/storage/sessions.js +688 -0
- package/dist/storage/tables.d.ts +54 -0
- package/dist/storage/tables.js +205 -0
- package/dist/storage/transfers.d.ts +16 -0
- package/dist/storage/transfers.js +153 -0
- package/dist/storage/writes.d.ts +7 -0
- package/dist/storage/writes.js +88 -0
- package/dist/telegram.d.ts +35 -0
- package/dist/telegram.js +173 -0
- package/dist/user-identity.d.ts +28 -0
- package/dist/user-identity.js +106 -0
- package/dist/user.d.ts +75 -0
- package/dist/user.js +111 -0
- package/dist/warehouse-backed.d.ts +6 -0
- package/dist/warehouse-backed.js +495 -0
- package/dist/webhook-token.d.ts +11 -0
- package/dist/webhook-token.js +18 -0
- package/dist/webhooks.d.ts +40 -0
- package/dist/webhooks.js +55 -0
- package/dist/wire.d.ts +10 -0
- package/dist/wire.js +35 -0
- package/dist/workflow-runtime.d.ts +116 -0
- package/dist/workflow-runtime.js +109 -0
- package/dist/workflow-topics.d.ts +28 -0
- package/dist/workflow-topics.js +28 -0
- package/dist/workflow.d.ts +284 -0
- package/dist/workflow.js +259 -0
- package/dist/workflows.d.ts +24 -0
- package/dist/workflows.js +18 -0
- package/package.json +127 -4
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { isSqlError, isSqlErrorReason } from "effect/unstable/sql/SqlError";
|
|
3
|
+
import { fieldColumnName, indexName, primaryKeyName, uniqueIndexName } from "../migrate/storage.js";
|
|
4
|
+
const DRIVER_MESSAGES = new Set([
|
|
5
|
+
"PgConnection: Query failed",
|
|
6
|
+
"PgConnection: The server reported an error",
|
|
7
|
+
]);
|
|
8
|
+
const FRAME_PREFIX = "^\\s+at (?:(?:async |new )?(?:[\\w.$<>]+ )?(?:\\[as [\\w$]+\\] )?\\()?";
|
|
9
|
+
const FRAME_LOCATION = "(?:(?:file:\\/\\/)?\\/[\\w./@%+~:-]+|node:[\\w./-]+)";
|
|
10
|
+
const SOURCE_FRAME = new RegExp(`${FRAME_PREFIX}${FRAME_LOCATION}:\\d+:\\d+\\)?$`);
|
|
11
|
+
function knownConstraint(entity, error) {
|
|
12
|
+
const cause = error.reason.cause;
|
|
13
|
+
const constraint = error.reason._tag === "UniqueViolation"
|
|
14
|
+
? error.reason.constraint
|
|
15
|
+
: typeof cause === "object" && cause !== null && "constraint" in cause
|
|
16
|
+
? cause.constraint
|
|
17
|
+
: undefined;
|
|
18
|
+
const names = [
|
|
19
|
+
primaryKeyName(entity.table),
|
|
20
|
+
primaryKeyName(entity.historyTable),
|
|
21
|
+
...entity.manifest.properties
|
|
22
|
+
.filter((field) => field.unique)
|
|
23
|
+
.map((field) => uniqueIndexName(entity.name, field.name)),
|
|
24
|
+
...entity.manifest.indexes
|
|
25
|
+
.filter((index) => index.unique)
|
|
26
|
+
.map((index) => indexName(entity.table, index.properties.map((name) => fieldColumnName(entity.manifest, name) ?? name))),
|
|
27
|
+
];
|
|
28
|
+
// PostgreSQL truncates these generated ASCII identifiers to its 63-byte identifier limit.
|
|
29
|
+
return typeof constraint === "string" && names.some((name) => name.slice(0, 63) === constraint)
|
|
30
|
+
? constraint
|
|
31
|
+
: undefined;
|
|
32
|
+
}
|
|
33
|
+
function causeTag(cause) {
|
|
34
|
+
if (isSqlError(cause)) {
|
|
35
|
+
return "SqlError";
|
|
36
|
+
}
|
|
37
|
+
if (isSqlErrorReason(cause)) {
|
|
38
|
+
return cause._tag;
|
|
39
|
+
}
|
|
40
|
+
return cause instanceof Error ? "Error" : "UnknownCause";
|
|
41
|
+
}
|
|
42
|
+
function safeCause(cause, constraint) {
|
|
43
|
+
if (!(cause instanceof Error)) {
|
|
44
|
+
return { tag: causeTag(cause), message: "[redacted database cause]", messageRedacted: true };
|
|
45
|
+
}
|
|
46
|
+
const original = cause.message;
|
|
47
|
+
const knownUniqueMessage = constraint === undefined
|
|
48
|
+
? undefined
|
|
49
|
+
: `duplicate key value violates unique constraint "${constraint}"`;
|
|
50
|
+
const allowed = DRIVER_MESSAGES.has(original) || original === knownUniqueMessage;
|
|
51
|
+
const message = allowed ? original : "[redacted database message]";
|
|
52
|
+
const headline = `${cause.name}: ${original}`;
|
|
53
|
+
const stack = typeof cause.stack === "string" && cause.stack.startsWith(`${headline}\n`)
|
|
54
|
+
? cause.stack.slice(headline.length + 1)
|
|
55
|
+
: "";
|
|
56
|
+
// Remove the complete (possibly multiline) original headline before keeping JS source frames.
|
|
57
|
+
// SQL, hints, parameters and arbitrary database explanations never enter this projection.
|
|
58
|
+
const frames = stack.split("\n").filter((line) => SOURCE_FRAME.test(line));
|
|
59
|
+
return {
|
|
60
|
+
tag: causeTag(cause),
|
|
61
|
+
message,
|
|
62
|
+
messageRedacted: !allowed,
|
|
63
|
+
...(frames.length > 0 ? { stack: frames.join("\n") } : {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** A safe projection of every wrapper; the original driver cause stays on the Conflict itself. */
|
|
67
|
+
export function batchConflictDiagnostic(entity, error) {
|
|
68
|
+
const constraint = knownConstraint(entity, error);
|
|
69
|
+
const causeChain = [];
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
let current = error;
|
|
72
|
+
while (current !== undefined && !seen.has(current)) {
|
|
73
|
+
seen.add(current);
|
|
74
|
+
causeChain.push(safeCause(current, constraint));
|
|
75
|
+
current =
|
|
76
|
+
typeof current === "object" && current !== null && "cause" in current
|
|
77
|
+
? current.cause
|
|
78
|
+
: undefined;
|
|
79
|
+
}
|
|
80
|
+
return { sqlState: "23505", ...(constraint === undefined ? {} : { constraint }), causeChain };
|
|
81
|
+
}
|
|
82
|
+
/** Emit before mapping the error, so even a handler-caught conflict has a correlated diagnostic. */
|
|
83
|
+
export function logBatchConflict(entity, error, scope) {
|
|
84
|
+
return Effect.logWarning("ontology.action.batch_conflict").pipe(Effect.annotateLogs({
|
|
85
|
+
...scope,
|
|
86
|
+
entity: entity.name,
|
|
87
|
+
...batchConflictDiagnostic(entity, error),
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { SqlError } from "effect/unstable/sql/SqlError";
|
|
3
|
+
import { ActionRejected, Conflict, NotFound, type ScopeViolated } from "../errors.js";
|
|
4
|
+
import type { BatchResult, BatchReturning, StoredValues } from "../runtime.js";
|
|
5
|
+
import { type EntityModel, type FieldModel } from "./model.js";
|
|
6
|
+
import { type PreparedWrite } from "./records.js";
|
|
7
|
+
import { type WriteContext, type WriteGuard } from "./writes.js";
|
|
8
|
+
export declare const BATCH_MAX_ROWS = 200;
|
|
9
|
+
export declare const BATCH_MAX_BYTES: number;
|
|
10
|
+
export declare const ACTION_MAX_BATCH_ROWS = 500;
|
|
11
|
+
/** The pinned PostgreSQL driver's signed Int16 count caps bindings at 32,767. */
|
|
12
|
+
export declare const BATCH_MAX_PARAMETERS = 30000;
|
|
13
|
+
/** Includes bound values and an allowance for their SQL placeholders. */
|
|
14
|
+
export declare const BATCH_MAX_STATEMENT_BYTES: number;
|
|
15
|
+
export type BatchOperation = "create" | "update" | "upsert";
|
|
16
|
+
export type BatchError = ActionRejected | Conflict | NotFound | ScopeViolated | SqlError;
|
|
17
|
+
export interface BatchInput {
|
|
18
|
+
readonly records: ReadonlyArray<StoredValues | {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly values: StoredValues;
|
|
21
|
+
}>;
|
|
22
|
+
readonly returning?: BatchReturning;
|
|
23
|
+
readonly on?: ReadonlyArray<string>;
|
|
24
|
+
}
|
|
25
|
+
interface PlannedRow {
|
|
26
|
+
readonly index: number;
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly create: PreparedWrite;
|
|
29
|
+
readonly patch: PreparedWrite;
|
|
30
|
+
}
|
|
31
|
+
export interface BatchPlan {
|
|
32
|
+
readonly operation: BatchOperation;
|
|
33
|
+
readonly returning: BatchReturning;
|
|
34
|
+
readonly rows: ReadonlyArray<PlannedRow>;
|
|
35
|
+
readonly keys: ReadonlyArray<FieldModel>;
|
|
36
|
+
readonly distinctReferences: number;
|
|
37
|
+
}
|
|
38
|
+
export interface BatchContext extends WriteContext {
|
|
39
|
+
/** Set only after a prior write's savepoint succeeded. */
|
|
40
|
+
readonly attributionInitialized: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** One UTF-8 JSON encoding defines the public byte bound, including IDs and conflict fields. */
|
|
43
|
+
export declare function batchInputBytes(input: BatchInput): number;
|
|
44
|
+
/** All authored-value validation finishes before a record mutation can execute. */
|
|
45
|
+
export declare function planBatch(entity: EntityModel, operation: BatchOperation, input: BatchInput): Effect.Effect<BatchPlan, ActionRejected | NotFound>;
|
|
46
|
+
/**
|
|
47
|
+
* Executes one set of mutations; the caller owns its single savepoint and edit publication. With a
|
|
48
|
+
* guard, updated records must be inside its `change` scope before and after, created ones inside
|
|
49
|
+
* its `create` scope.
|
|
50
|
+
*/
|
|
51
|
+
export declare function executeBatch(context: BatchContext, entity: EntityModel, plan: BatchPlan, guard?: WriteGuard): Effect.Effect<BatchResult, BatchError>;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { ActionRejected, Conflict, NotFound } from "../errors.js";
|
|
3
|
+
import { ACTION_ATTRIBUTION_TABLE, quoteIdentifier } from "../migrate/storage.js";
|
|
4
|
+
import { InvalidValues, StoredRecordMismatch } from "./errors.js";
|
|
5
|
+
import { diffRecords } from "./history.js";
|
|
6
|
+
import { uuidv7 } from "./ids.js";
|
|
7
|
+
import { entityModel } from "./model.js";
|
|
8
|
+
import { normalizeId } from "./reads.js";
|
|
9
|
+
import { bindValue, decodeRecord, isRecordId, prepareWrite, selectList, } from "./records.js";
|
|
10
|
+
import { Aliases, columnOf, Params } from "./statement.js";
|
|
11
|
+
import { scopeCondition, scopeViolated, } from "./writes.js";
|
|
12
|
+
import { validate } from "./validation.js";
|
|
13
|
+
export const BATCH_MAX_ROWS = 200;
|
|
14
|
+
export const BATCH_MAX_BYTES = 2 * 1024 * 1024;
|
|
15
|
+
export const ACTION_MAX_BATCH_ROWS = 500;
|
|
16
|
+
/** The pinned PostgreSQL driver's signed Int16 count caps bindings at 32,767. */
|
|
17
|
+
export const BATCH_MAX_PARAMETERS = 30_000;
|
|
18
|
+
/** Includes bound values and an allowance for their SQL placeholders. */
|
|
19
|
+
export const BATCH_MAX_STATEMENT_BYTES = 4 * 1024 * 1024;
|
|
20
|
+
const summaryFields = new WeakMap();
|
|
21
|
+
const KeyOrderRow = Schema.Struct({ index: Schema.Int, duplicates: Schema.Int });
|
|
22
|
+
function rejected(message, indices, fields) {
|
|
23
|
+
return new ActionRejected({
|
|
24
|
+
message,
|
|
25
|
+
batch: { indices: [...indices], ...(fields === undefined ? {} : { fields: [...fields] }) },
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** One UTF-8 JSON encoding defines the public byte bound, including IDs and conflict fields. */
|
|
29
|
+
export function batchInputBytes(input) {
|
|
30
|
+
return Buffer.byteLength(JSON.stringify({ records: input.records, ...(input.on ? { on: input.on } : {}) }));
|
|
31
|
+
}
|
|
32
|
+
function unsupportedFields(entity, values) {
|
|
33
|
+
return Object.keys(values).filter((name) => {
|
|
34
|
+
const field = entity.fieldsByName.get(name);
|
|
35
|
+
const link = entity.links.get(name);
|
|
36
|
+
return (values[name] !== undefined &&
|
|
37
|
+
(field?.storage === "blob" || link?.via === "join" || link?.via === "inverse-key"));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function conflictFields(entity, on) {
|
|
41
|
+
return Effect.suspend(() => {
|
|
42
|
+
const fields = on.map((name) => entity.fieldsByName.get(name));
|
|
43
|
+
const uniqueProperty = on.length === 1 && fields[0]?.property?.definition.unique === true;
|
|
44
|
+
const declaredIndex = entity.manifest.indexes.find((index) => index.unique &&
|
|
45
|
+
index.properties.length === on.length &&
|
|
46
|
+
index.properties.every((name) => on.includes(name)));
|
|
47
|
+
if (on.length === 0 ||
|
|
48
|
+
new Set(on).size !== on.length ||
|
|
49
|
+
fields.some((field) => field?.source !== "property") ||
|
|
50
|
+
(!uniqueProperty && declaredIndex === undefined)) {
|
|
51
|
+
return Effect.die(new InvalidValues({
|
|
52
|
+
entity: entity.name,
|
|
53
|
+
message: `upsertMany needs declared ordinary properties forming a unique key of ${entity.name}.`,
|
|
54
|
+
issues: [
|
|
55
|
+
{
|
|
56
|
+
path: ["on"],
|
|
57
|
+
message: "Expected a declared unique property or compound unique index",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
// Equivalent conflict-key permutations must acquire unique-index locks in the same order.
|
|
63
|
+
const canonical = declaredIndex?.properties ?? on;
|
|
64
|
+
return Effect.succeed(canonical.map((name) => entity.fieldsByName.get(name)));
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function prepareBatchWrite(entity, values, mode, index) {
|
|
68
|
+
return prepareWrite(entity, values, mode).pipe(Effect.catchDefect((defect) => {
|
|
69
|
+
if (!(defect instanceof InvalidValues)) {
|
|
70
|
+
return Effect.die(defect);
|
|
71
|
+
}
|
|
72
|
+
const indexed = new InvalidValues({
|
|
73
|
+
entity: defect.entity,
|
|
74
|
+
message: `Invalid ${entity.name} values at batch index ${index}.`,
|
|
75
|
+
issues: defect.issues.map((issue) => ({
|
|
76
|
+
...issue,
|
|
77
|
+
path: ["records", index, ...issue.path],
|
|
78
|
+
})),
|
|
79
|
+
});
|
|
80
|
+
Object.defineProperty(indexed, "cause", { value: defect, enumerable: false });
|
|
81
|
+
return Effect.die(indexed);
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
/** All authored-value validation finishes before a record mutation can execute. */
|
|
85
|
+
export function planBatch(entity, operation, input) {
|
|
86
|
+
return Effect.gen(function* () {
|
|
87
|
+
const returning = input.returning ?? "records";
|
|
88
|
+
if (returning !== "records" && returning !== "summary") {
|
|
89
|
+
return yield* rejected("Unknown batch returning mode.", []);
|
|
90
|
+
}
|
|
91
|
+
const keys = operation === "upsert" ? yield* conflictFields(entity, input.on ?? []) : [];
|
|
92
|
+
const rows = [];
|
|
93
|
+
const updateIds = new Map();
|
|
94
|
+
const references = new Set();
|
|
95
|
+
for (const [index, entry] of input.records.entries()) {
|
|
96
|
+
const update = entry;
|
|
97
|
+
const values = operation === "update" ? update.values : entry;
|
|
98
|
+
const unsupported = unsupportedFields(entity, values);
|
|
99
|
+
if (unsupported.length > 0) {
|
|
100
|
+
return yield* rejected("Batch writes do not support blob or collection assignments.", [index], unsupported);
|
|
101
|
+
}
|
|
102
|
+
if (operation === "upsert") {
|
|
103
|
+
const missing = keys.filter((field) => values[field.name] === undefined || values[field.name] === null);
|
|
104
|
+
if (missing.length > 0) {
|
|
105
|
+
return yield* Effect.die(new InvalidValues({
|
|
106
|
+
entity: entity.name,
|
|
107
|
+
message: "Every upsertMany record needs non-null conflict key values.",
|
|
108
|
+
issues: missing.map((field) => ({
|
|
109
|
+
path: [index, field.name],
|
|
110
|
+
message: `${field.name} is required`,
|
|
111
|
+
})),
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const id = operation === "update" ? normalizeId(update.id) : uuidv7();
|
|
116
|
+
if (operation === "update") {
|
|
117
|
+
if (!isRecordId(id)) {
|
|
118
|
+
return yield* missingRecord(entity, id, [index], ["id"]);
|
|
119
|
+
}
|
|
120
|
+
const indices = updateIds.get(id) ?? [];
|
|
121
|
+
indices.push(index);
|
|
122
|
+
updateIds.set(id, indices);
|
|
123
|
+
}
|
|
124
|
+
const create = yield* prepareBatchWrite(entity, values, operation === "update" ? "update" : "create", index);
|
|
125
|
+
const patch = operation === "upsert"
|
|
126
|
+
? yield* prepareBatchWrite(entity, Object.fromEntries(Object.entries(values).filter(([name]) => !keys.some((key) => key.name === name))), "update", index)
|
|
127
|
+
: create;
|
|
128
|
+
for (const reference of create.references) {
|
|
129
|
+
references.add(`${reference.link.target}:${normalizeId(reference.id)}`);
|
|
130
|
+
}
|
|
131
|
+
rows.push({ index, id, create, patch });
|
|
132
|
+
}
|
|
133
|
+
const duplicates = [...updateIds.values()].filter((indices) => indices.length > 1).flat();
|
|
134
|
+
if (duplicates.length > 0) {
|
|
135
|
+
return yield* rejected("A batch cannot update a record more than once.", duplicates.sort((a, b) => a - b), ["id"]);
|
|
136
|
+
}
|
|
137
|
+
return { operation, returning, rows, keys, distinctReferences: references.size };
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function missingRecord(entity, id, indices, fields) {
|
|
141
|
+
return new NotFound({
|
|
142
|
+
entity: entity.name,
|
|
143
|
+
id,
|
|
144
|
+
message: `A required ${entity.label.toLowerCase()} record does not exist.`,
|
|
145
|
+
batch: { indices: [...indices], ...(fields ? { fields: [...fields] } : {}) },
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/** Chunk by protocol parameters and encoded statement weight; chunks share one savepoint. */
|
|
149
|
+
function chunks(rows, width, valueBytes) {
|
|
150
|
+
return Effect.gen(function* () {
|
|
151
|
+
const result = [];
|
|
152
|
+
let current = [];
|
|
153
|
+
let bytes = 0;
|
|
154
|
+
for (const row of rows) {
|
|
155
|
+
const rowBytes = valueBytes(row) + width * 48;
|
|
156
|
+
if (width > BATCH_MAX_PARAMETERS || rowBytes > BATCH_MAX_STATEMENT_BYTES) {
|
|
157
|
+
return yield* rejected("A batch row exceeds the SQL statement resource limit.", []);
|
|
158
|
+
}
|
|
159
|
+
if (current.length > 0 &&
|
|
160
|
+
((current.length + 1) * width > BATCH_MAX_PARAMETERS ||
|
|
161
|
+
bytes + rowBytes > BATCH_MAX_STATEMENT_BYTES)) {
|
|
162
|
+
result.push(current);
|
|
163
|
+
current = [];
|
|
164
|
+
bytes = 0;
|
|
165
|
+
}
|
|
166
|
+
current.push(row);
|
|
167
|
+
bytes += rowBytes;
|
|
168
|
+
}
|
|
169
|
+
if (current.length > 0) {
|
|
170
|
+
result.push(current);
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function writeBytes(write) {
|
|
176
|
+
return Buffer.byteLength(JSON.stringify(write.columns.map(({ field, value }) => bindValue(field, value))));
|
|
177
|
+
}
|
|
178
|
+
function selectedFields(entity, returning) {
|
|
179
|
+
if (returning === "records") {
|
|
180
|
+
return entity.fields;
|
|
181
|
+
}
|
|
182
|
+
const fields = summaryFields.get(entity) ??
|
|
183
|
+
entity.fields.filter((field) => field.source !== "system" || field.name === "id");
|
|
184
|
+
summaryFields.set(entity, fields);
|
|
185
|
+
return fields;
|
|
186
|
+
}
|
|
187
|
+
/** Summary mode needs history values, but never decodes a projected row as a complete record. */
|
|
188
|
+
function decodeBatchRow(entity, row, returning) {
|
|
189
|
+
return decodeRecord(entity, row, selectedFields(entity, returning));
|
|
190
|
+
}
|
|
191
|
+
function requireBatchReferences(context, plan) {
|
|
192
|
+
return Effect.gen(function* () {
|
|
193
|
+
const targets = new Map();
|
|
194
|
+
for (const row of plan.rows) {
|
|
195
|
+
for (const reference of row.create.references) {
|
|
196
|
+
const ids = targets.get(reference.link.target) ?? new Map();
|
|
197
|
+
const id = normalizeId(reference.id);
|
|
198
|
+
const positions = ids.get(id) ?? { indices: new Set(), fields: new Set() };
|
|
199
|
+
positions.indices.add(row.index);
|
|
200
|
+
positions.fields.add(reference.link.name);
|
|
201
|
+
ids.set(id, positions);
|
|
202
|
+
targets.set(reference.link.target, ids);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
for (const name of [...targets.keys()].sort()) {
|
|
206
|
+
const entity = entityModel(context.model, name);
|
|
207
|
+
const ids = targets.get(name);
|
|
208
|
+
const wanted = [...ids.keys()].sort();
|
|
209
|
+
const malformed = wanted.find((id) => !isRecordId(id));
|
|
210
|
+
if (malformed !== undefined) {
|
|
211
|
+
const positions = ids.get(malformed);
|
|
212
|
+
return yield* missingRecord(entity, malformed, [...positions.indices], [...positions.fields]);
|
|
213
|
+
}
|
|
214
|
+
for (const part of yield* chunks(wanted, 1, (id) => id.length)) {
|
|
215
|
+
const params = new Params();
|
|
216
|
+
const placeholders = part.map((id) => params.add(id, "uuid"));
|
|
217
|
+
const rows = yield* context.run(`SELECT "t0"."id"::text AS "id" FROM ${quoteIdentifier(entity.table)} AS "t0" ` +
|
|
218
|
+
`WHERE "t0"."id" IN (${placeholders.join(", ")}) ORDER BY "t0"."id" FOR KEY SHARE OF "t0"`, params);
|
|
219
|
+
const found = new Set(rows.map((row) => row.id));
|
|
220
|
+
const missing = part.filter((id) => !found.has(id));
|
|
221
|
+
if (missing.length > 0) {
|
|
222
|
+
const positions = missing.map((id) => ids.get(id));
|
|
223
|
+
return yield* missingRecord(entity, missing[0], [...new Set(positions.flatMap((position) => [...position.indices]))].sort((a, b) => a - b), [...new Set(positions.flatMap((position) => [...position.fields]))].sort());
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
function keyValue(row, field) {
|
|
230
|
+
return row.create.columns.find((column) => column.field.name === field.name).value;
|
|
231
|
+
}
|
|
232
|
+
/** PostgreSQL casts, equality and collation define duplicate keys; JS string encodings do not. */
|
|
233
|
+
function requireDistinctKeys(context, entity, plan) {
|
|
234
|
+
return Effect.gen(function* () {
|
|
235
|
+
const params = new Params();
|
|
236
|
+
const input = plan.rows.map((row) => Object.fromEntries([
|
|
237
|
+
["index", row.index],
|
|
238
|
+
...plan.keys.map((field, index) => [`k${index}`, keyValue(row, field)]),
|
|
239
|
+
]));
|
|
240
|
+
const keyNames = plan.keys.map((_, index) => quoteIdentifier(`k${index}`));
|
|
241
|
+
const definitions = plan.keys.map((field, index) => `${keyNames[index]} ${field.sqlType}`);
|
|
242
|
+
const rows = yield* context.run(`SELECT "index", count(*) OVER (PARTITION BY ${keyNames.join(", ")})::integer AS "duplicates" ` +
|
|
243
|
+
`FROM jsonb_to_recordset(${params.add(JSON.stringify(input), "jsonb")}) AS "input" ` +
|
|
244
|
+
`("index" integer, ${definitions.join(", ")}) ORDER BY ${keyNames.join(", ")}`, params);
|
|
245
|
+
const decoded = validate(Schema.Array(KeyOrderRow), rows);
|
|
246
|
+
if (!decoded.ok) {
|
|
247
|
+
return yield* Effect.die(new StoredRecordMismatch({
|
|
248
|
+
entity: entity.name,
|
|
249
|
+
message: "The database returned an invalid batch key ordering projection.",
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
const indices = decoded.value
|
|
253
|
+
.filter((row) => Number(row.duplicates) > 1)
|
|
254
|
+
.map((row) => Number(row.index))
|
|
255
|
+
.sort((a, b) => a - b);
|
|
256
|
+
if (indices.length > 0) {
|
|
257
|
+
return yield* rejected("The batch contains duplicate conflict keys.", indices, plan.keys.map((field) => field.name));
|
|
258
|
+
}
|
|
259
|
+
const byIndex = new Map(plan.rows.map((row) => [row.index, row]));
|
|
260
|
+
return decoded.value.map((row) => byIndex.get(row.index));
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
function createRows(context, entity, plan) {
|
|
264
|
+
return Effect.gen(function* () {
|
|
265
|
+
const records = new Map();
|
|
266
|
+
const fields = entity.fields.filter((field) => field.source !== "system");
|
|
267
|
+
const sorted = plan.operation === "upsert"
|
|
268
|
+
? plan.rows
|
|
269
|
+
: [...plan.rows].sort((left, right) => left.id.localeCompare(right.id));
|
|
270
|
+
for (const part of yield* chunks(sorted, fields.length + 2, (row) => writeBytes(row.create))) {
|
|
271
|
+
const params = new Params();
|
|
272
|
+
const principal = params.add(context.scope.principalId, "uuid");
|
|
273
|
+
const values = part.map((row) => {
|
|
274
|
+
const columns = new Map(row.create.columns.map((column) => [column.field.name, column.value]));
|
|
275
|
+
const placeholders = [
|
|
276
|
+
params.add(row.id, "uuid"),
|
|
277
|
+
...fields.map((field) => params.add(bindValue(field, columns.get(field.name)), field.sqlType)),
|
|
278
|
+
];
|
|
279
|
+
return `(${placeholders.join(", ")})`;
|
|
280
|
+
});
|
|
281
|
+
const columns = fields.map((field) => quoteIdentifier(field.column));
|
|
282
|
+
const inputColumns = ['"id"', ...columns];
|
|
283
|
+
const targetColumns = [
|
|
284
|
+
'"id"',
|
|
285
|
+
'"created_at"',
|
|
286
|
+
'"updated_at"',
|
|
287
|
+
'"created_by"',
|
|
288
|
+
'"updated_by"',
|
|
289
|
+
...columns,
|
|
290
|
+
];
|
|
291
|
+
const selected = [
|
|
292
|
+
'"input"."id"',
|
|
293
|
+
"now()",
|
|
294
|
+
"now()",
|
|
295
|
+
principal,
|
|
296
|
+
principal,
|
|
297
|
+
...columns.map((column) => `"input".${column}`),
|
|
298
|
+
];
|
|
299
|
+
const conflict = plan.operation === "upsert"
|
|
300
|
+
? ` ON CONFLICT (${plan.keys.map((field) => quoteIdentifier(field.column)).join(", ")}) DO NOTHING`
|
|
301
|
+
: "";
|
|
302
|
+
const order = plan.operation === "upsert"
|
|
303
|
+
? plan.keys.map((field) => columnOf("input", field.column)).join(", ")
|
|
304
|
+
: '"input"."id"';
|
|
305
|
+
const rows = yield* context.run(`INSERT INTO ${quoteIdentifier(entity.table)} AS "t0" ` +
|
|
306
|
+
`(${targetColumns.join(", ")}) SELECT ${selected.join(", ")} ` +
|
|
307
|
+
`FROM (VALUES ${values.join(", ")}) AS "input" (${inputColumns.join(", ")}) ` +
|
|
308
|
+
`ORDER BY ${order}${conflict} RETURNING ${selectList(entity, "t0", selectedFields(entity, plan.returning))}`, params);
|
|
309
|
+
const byId = new Map(part.map((row) => [row.id, row.index]));
|
|
310
|
+
for (const row of rows) {
|
|
311
|
+
const record = yield* decodeBatchRow(entity, row, plan.returning);
|
|
312
|
+
records.set(byId.get(record.id), record);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return records;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Locks the existing records the batch updates. With a check, a record outside its scope is not
|
|
320
|
+
* found (the before-check): it is locked and reported rather than skipped, so that an upsert does
|
|
321
|
+
* not take it for a record that disappeared.
|
|
322
|
+
*/
|
|
323
|
+
function lockRows(context, entity, plan, pending, check) {
|
|
324
|
+
return Effect.gen(function* () {
|
|
325
|
+
const records = new Map();
|
|
326
|
+
const outside = [];
|
|
327
|
+
const sorted = plan.operation === "upsert"
|
|
328
|
+
? pending
|
|
329
|
+
: [...pending].sort((left, right) => left.id.localeCompare(right.id));
|
|
330
|
+
const width = plan.operation === "upsert" ? plan.keys.length + 1 : 2;
|
|
331
|
+
for (const part of yield* chunks(sorted, width, (row) => writeBytes(row.create))) {
|
|
332
|
+
const params = new Params();
|
|
333
|
+
const keys = plan.operation === "upsert" ? plan.keys : [entity.fieldsByName.get("id")];
|
|
334
|
+
const values = part.map((row) => `(${params.add(row.index, "integer")}, ${keys
|
|
335
|
+
.map((field) => params.add(field.name === "id" ? row.id : bindValue(field, keyValue(row, field)), field.sqlType))
|
|
336
|
+
.join(", ")})`);
|
|
337
|
+
const keyNames = keys.map((_, index) => quoteIdentifier(`k${index}`));
|
|
338
|
+
const condition = keys
|
|
339
|
+
.map((field, index) => `${columnOf("t0", field.column)} = "input".${keyNames[index]}`)
|
|
340
|
+
.join(" AND ");
|
|
341
|
+
const order = keys.map((field) => columnOf("t0", field.column)).join(", ");
|
|
342
|
+
const inside = check === undefined
|
|
343
|
+
? ""
|
|
344
|
+
: `(${scopeOnT0(context, entity, params, check)}) IS TRUE AS "__inside", `;
|
|
345
|
+
const rows = yield* context.run(`SELECT "input"."index" AS "__batch_index", ${inside}` +
|
|
346
|
+
`${selectList(entity, "t0", selectedFields(entity, plan.returning))} ` +
|
|
347
|
+
`FROM ${quoteIdentifier(entity.table)} AS "t0" ` +
|
|
348
|
+
`JOIN (VALUES ${values.join(", ")}) AS "input" ("index", ${keyNames.join(", ")}) ON ${condition} ` +
|
|
349
|
+
`ORDER BY ${order} FOR UPDATE OF "t0"`, params);
|
|
350
|
+
for (const row of rows) {
|
|
351
|
+
const { __inside: inside, ...selected } = row;
|
|
352
|
+
records.set(Number(row.__batch_index), yield* decodeBatchRow(entity, check === undefined ? row : selected, plan.returning));
|
|
353
|
+
if (check !== undefined && inside !== true) {
|
|
354
|
+
outside.push(pending.find((candidate) => candidate.index === Number(row.__batch_index)));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (outside.length > 0) {
|
|
359
|
+
const indices = outside.map((row) => row.index).sort((a, b) => a - b);
|
|
360
|
+
return yield* missingRecord(entity, String(records.get(indices[0]).id), indices, ["id"]);
|
|
361
|
+
}
|
|
362
|
+
const missing = pending.filter((row) => !records.has(row.index));
|
|
363
|
+
if (missing.length > 0) {
|
|
364
|
+
if (plan.operation === "upsert") {
|
|
365
|
+
return yield* new Conflict({
|
|
366
|
+
entity: entity.name,
|
|
367
|
+
message: "A conflicting upsert record disappeared before it could be locked.",
|
|
368
|
+
batch: {
|
|
369
|
+
indices: missing.map((row) => row.index),
|
|
370
|
+
fields: plan.keys.map((field) => field.name),
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
return yield* missingRecord(entity, missing[0].id, missing.map((row) => row.index), ["id"]);
|
|
375
|
+
}
|
|
376
|
+
return records;
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/** Presence flags support heterogeneous patches in one set mutation without filling omissions. */
|
|
380
|
+
function updateRows(context, entity, plan, pending, before) {
|
|
381
|
+
return Effect.gen(function* () {
|
|
382
|
+
const records = new Map();
|
|
383
|
+
const names = new Set(pending.flatMap((row) => row.patch.columns.map(({ field }) => field.name)));
|
|
384
|
+
const fields = entity.fields.filter((field) => names.has(field.name));
|
|
385
|
+
if (fields.length === 0) {
|
|
386
|
+
return records;
|
|
387
|
+
}
|
|
388
|
+
const sorted = [...pending].sort((left, right) => String(before.get(left.index).id).localeCompare(String(before.get(right.index).id)));
|
|
389
|
+
for (const part of yield* chunks(sorted, fields.length * 2 + 2, (row) => writeBytes(row.patch))) {
|
|
390
|
+
const params = new Params();
|
|
391
|
+
const principal = params.add(context.scope.principalId, "uuid");
|
|
392
|
+
const values = part.map((row) => {
|
|
393
|
+
const columns = new Map(row.patch.columns.map((column) => [column.field.name, column.value]));
|
|
394
|
+
const fieldsSql = fields.flatMap((field) => [
|
|
395
|
+
params.add(columns.has(field.name), "boolean"),
|
|
396
|
+
params.add(columns.has(field.name) ? bindValue(field, columns.get(field.name)) : null, field.sqlType),
|
|
397
|
+
]);
|
|
398
|
+
return `(${params.add(before.get(row.index).id, "uuid")}, ${fieldsSql.join(", ")})`;
|
|
399
|
+
});
|
|
400
|
+
const inputColumns = fields.flatMap((_, index) => [`"has${index}"`, `"v${index}"`]);
|
|
401
|
+
const assignments = fields.map((field, index) => `${quoteIdentifier(field.column)} = CASE WHEN "input"."has${index}" ` +
|
|
402
|
+
`THEN "input"."v${index}" ELSE ${columnOf("t0", field.column)} END`);
|
|
403
|
+
const changed = fields.map((field, index) => `("input"."has${index}" AND ${columnOf("t0", field.column)} IS DISTINCT FROM "input"."v${index}")`);
|
|
404
|
+
const rows = yield* context.run(`UPDATE ${quoteIdentifier(entity.table)} AS "t0" SET ${assignments.join(", ")}, ` +
|
|
405
|
+
`"updated_at" = now(), "updated_by" = ${principal} ` +
|
|
406
|
+
`FROM (VALUES ${values.join(", ")}) AS "input" ("id", ${inputColumns.join(", ")}) ` +
|
|
407
|
+
`WHERE "t0"."id" = "input"."id" AND (${changed.join(" OR ")}) ` +
|
|
408
|
+
`RETURNING ${selectList(entity, "t0", selectedFields(entity, plan.returning))}`, params);
|
|
409
|
+
const byId = new Map(part.map((row) => [before.get(row.index).id, row.index]));
|
|
410
|
+
for (const row of rows) {
|
|
411
|
+
const record = yield* decodeBatchRow(entity, row, plan.returning);
|
|
412
|
+
records.set(byId.get(record.id), record);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return records;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
function recordBatchEdits(context, entity, edits) {
|
|
419
|
+
return Effect.gen(function* () {
|
|
420
|
+
if (edits.length === 0) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (context.scope.execution !== undefined && !context.attributionInitialized) {
|
|
424
|
+
const params = new Params();
|
|
425
|
+
yield* context.run(`INSERT INTO "${ACTION_ATTRIBUTION_TABLE}" (action_id, execution) VALUES (` +
|
|
426
|
+
`${params.add(context.scope.actionId, "uuid")}, ` +
|
|
427
|
+
`${params.add(JSON.stringify(context.scope.execution), "jsonb")}) ON CONFLICT DO NOTHING`, params);
|
|
428
|
+
}
|
|
429
|
+
for (const part of yield* chunks(edits, 7, (edit) => Buffer.byteLength(JSON.stringify(edit.changes)))) {
|
|
430
|
+
const params = new Params();
|
|
431
|
+
const values = part.map((edit) => `(${[
|
|
432
|
+
params.add(uuidv7(), "uuid"),
|
|
433
|
+
params.add(edit.id, "uuid"),
|
|
434
|
+
params.add(context.scope.action, "text"),
|
|
435
|
+
params.add(context.scope.actionId, "uuid"),
|
|
436
|
+
params.add(edit.kind, "text"),
|
|
437
|
+
params.add(context.scope.principalId, "uuid"),
|
|
438
|
+
"now()",
|
|
439
|
+
params.add(JSON.stringify(edit.changes), "jsonb"),
|
|
440
|
+
].join(", ")})`);
|
|
441
|
+
yield* context.run(`INSERT INTO ${quoteIdentifier(entity.historyTable)} ` +
|
|
442
|
+
'("id", "record_id", "action", "action_id", "kind", "principal_id", "at", "changes") ' +
|
|
443
|
+
`VALUES ${values.join(", ")}`, params);
|
|
444
|
+
}
|
|
445
|
+
context.edits.push(...edits);
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
/** The scope condition on the table aliased `t0` of a statement, with aliases of its own. */
|
|
449
|
+
function scopeOnT0(context, entity, params, check) {
|
|
450
|
+
const aliases = new Aliases();
|
|
451
|
+
aliases.take(); // t0
|
|
452
|
+
return scopeCondition(context, entity, params, aliases, "t0", check);
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* The after-check of the records a batch wrote: fails with ScopeViolated, naming the batch indices,
|
|
456
|
+
* unless every record is inside the scope as the batch left it.
|
|
457
|
+
*/
|
|
458
|
+
function requireBatchInScope(context, entity, written, check) {
|
|
459
|
+
return Effect.gen(function* () {
|
|
460
|
+
if (check === undefined || written.size === 0) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const params = new Params();
|
|
464
|
+
const scope = scopeOnT0(context, entity, params, check);
|
|
465
|
+
const values = [...written].map(([index, record]) => `(${params.add(index, "integer")}, ${params.add(record.id, "uuid")})`);
|
|
466
|
+
const rows = yield* context.run(`SELECT "input"."index" AS "index" FROM ${quoteIdentifier(entity.table)} AS "t0" ` +
|
|
467
|
+
`JOIN (VALUES ${values.join(", ")}) AS "input" ("index", "id") ON "t0"."id" = "input"."id" ` +
|
|
468
|
+
`WHERE (${scope}) IS NOT TRUE ORDER BY "input"."index"`, params);
|
|
469
|
+
if (rows.length > 0) {
|
|
470
|
+
return yield* scopeViolated(entity, check, undefined, {
|
|
471
|
+
indices: rows.map((row) => Number(row.index)),
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Executes one set of mutations; the caller owns its single savepoint and edit publication. With a
|
|
478
|
+
* guard, updated records must be inside its `change` scope before and after, created ones inside
|
|
479
|
+
* its `create` scope.
|
|
480
|
+
*/
|
|
481
|
+
export function executeBatch(context, entity, plan, guard) {
|
|
482
|
+
return Effect.gen(function* () {
|
|
483
|
+
const sortedRows = plan.operation === "upsert" ? yield* requireDistinctKeys(context, entity, plan) : plan.rows;
|
|
484
|
+
const orderedPlan = { ...plan, rows: sortedRows };
|
|
485
|
+
yield* requireBatchReferences(context, plan);
|
|
486
|
+
const created = plan.operation === "update"
|
|
487
|
+
? new Map()
|
|
488
|
+
: yield* createRows(context, entity, orderedPlan);
|
|
489
|
+
const pending = sortedRows.filter((row) => !created.has(row.index));
|
|
490
|
+
// A new READ COMMITTED statement observes insertions that DO NOTHING waited for.
|
|
491
|
+
const before = pending.length === 0
|
|
492
|
+
? new Map()
|
|
493
|
+
: yield* lockRows(context, entity, plan, pending, guard?.change);
|
|
494
|
+
const updated = pending.length === 0
|
|
495
|
+
? new Map()
|
|
496
|
+
: yield* updateRows(context, entity, plan, pending, before);
|
|
497
|
+
yield* requireBatchInScope(context, entity, created, guard?.create);
|
|
498
|
+
yield* requireBatchInScope(context, entity, updated, guard?.change);
|
|
499
|
+
const edits = [];
|
|
500
|
+
// Published with the edits: recordBatchEdits runs in the batch's savepoint.
|
|
501
|
+
const snapshots = context.snapshots === undefined ? undefined : [];
|
|
502
|
+
const items = plan.rows.map((row) => {
|
|
503
|
+
const record = created.get(row.index) ?? updated.get(row.index) ?? before.get(row.index);
|
|
504
|
+
const status = created.has(row.index)
|
|
505
|
+
? "created"
|
|
506
|
+
: updated.has(row.index)
|
|
507
|
+
? "updated"
|
|
508
|
+
: "unchanged";
|
|
509
|
+
if (status !== "unchanged") {
|
|
510
|
+
snapshots?.push({
|
|
511
|
+
entity: entity.name,
|
|
512
|
+
id: record.id,
|
|
513
|
+
before: status === "created" ? undefined : before.get(row.index),
|
|
514
|
+
after: record,
|
|
515
|
+
});
|
|
516
|
+
edits.push({
|
|
517
|
+
entity: entity.name,
|
|
518
|
+
id: record.id,
|
|
519
|
+
kind: status === "created" ? "create" : "update",
|
|
520
|
+
changes: diffRecords(entity, status === "created" ? undefined : before.get(row.index), record),
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
return { index: row.index, status, record };
|
|
524
|
+
});
|
|
525
|
+
yield* recordBatchEdits(context, entity, edits);
|
|
526
|
+
context.snapshots?.push(...(snapshots ?? []));
|
|
527
|
+
const counts = {
|
|
528
|
+
created: created.size,
|
|
529
|
+
updated: updated.size,
|
|
530
|
+
unchanged: before.size - updated.size,
|
|
531
|
+
};
|
|
532
|
+
return plan.returning === "summary" ? counts : { ...counts, items };
|
|
533
|
+
});
|
|
534
|
+
}
|