@segmentstream/ontology 0.0.0-stage → 0.92.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/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,597 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { Cause, Data, Duration, Effect, Option, Schema } from "effect";
|
|
3
|
+
import { SqlClient } from "effect/unstable/sql";
|
|
4
|
+
import { isSqlError } from "effect/unstable/sql/SqlError";
|
|
5
|
+
import { ActionIdentity } from "../action.js";
|
|
6
|
+
import { ActionConfirmation } from "../confirmation.js";
|
|
7
|
+
import { ConnectionError, ConnectionTransport, connectionContext } from "../connection.js";
|
|
8
|
+
import { Files } from "../files.js";
|
|
9
|
+
import { FileAccess, unavailableFiles } from "../storage/file-access.js";
|
|
10
|
+
import { Conflict, PermissionDenied, } from "../errors.js";
|
|
11
|
+
import { canonicalJson } from "../manifest.js";
|
|
12
|
+
import { CHANNEL_USERS_TABLE, USER_TABLE } from "../migrate/storage.js";
|
|
13
|
+
import { CHANNEL_IDENTITIES_FROM, LIVE_CHANNEL_IDENTITY, SIGN_IN_ORIGIN_SQL, USER_IDENTITY_TABLE, } from "./user-identity-sql.js";
|
|
14
|
+
import { ATTACHMENTS_TABLE } from "../storage/tables.js";
|
|
15
|
+
import { beginReplicationWrite } from "../replication/transaction.js";
|
|
16
|
+
import { RuntimeDeployment } from "./deployment.js";
|
|
17
|
+
import { requireDeploymentCapability } from "../deployment.js";
|
|
18
|
+
import { authorizeBlobOperation } from "../storage/authorization.js";
|
|
19
|
+
import { effectiveScope, generatedActionPermissions, missingPermission, parsePermission, } from "../permissions.js";
|
|
20
|
+
import { OntologyReads, OntologyRuntime } from "../runtime.js";
|
|
21
|
+
import { UnknownAction, ValidationIssue, WorkflowRecoveryUnavailable, } from "./errors.js";
|
|
22
|
+
import { generatedActions, writesOnlyThroughActions } from "./generated-actions.js";
|
|
23
|
+
import { Edit } from "./history.js";
|
|
24
|
+
import { canonicalInstant, isRecordId } from "./records.js";
|
|
25
|
+
import { makeOntologyRuntime } from "./sql-runtime.js";
|
|
26
|
+
import { AgentFileRecords, resolveFileRecord } from "./files.js";
|
|
27
|
+
import { validate } from "./validation.js";
|
|
28
|
+
import { evaluateTriggers, insertStarts, matchingTriggers, startCommitted, WorkflowStarter, } from "./workflow-starts.js";
|
|
29
|
+
import { sendQueuedSignInEmails, SignInLinks } from "./sign-in-links.js";
|
|
30
|
+
import { ACTION_TRANSACTION_LIMITS, ActionTransactions } from "./action-transactions.js";
|
|
31
|
+
import { actionTransaction } from "./action-deadlocks.js";
|
|
32
|
+
/** Keep every primary, cleanup and interruption reason when hiding SQL failures from callers. */
|
|
33
|
+
const sqlFailuresAsDefects = (effect) => effect.pipe(Effect.catchCause((cause) => Effect.failCause(Cause.fromReasons(cause.reasons.map((reason) => reason._tag === "Fail" && isSqlError(reason.error)
|
|
34
|
+
? Cause.makeDieReason(reason.error)
|
|
35
|
+
: reason)))));
|
|
36
|
+
/** How long an action's `confirm` may take: a person waits for the card it describes. */
|
|
37
|
+
export const CONFIRM_TIMEOUT = Duration.seconds(15);
|
|
38
|
+
/** How long `prepare` may take, connection calls included; each call has its own 30 s deadline. */
|
|
39
|
+
export const PREPARE_TIMEOUT = Duration.seconds(60);
|
|
40
|
+
export const ActionValidation = Schema.Struct({
|
|
41
|
+
ok: Schema.Boolean,
|
|
42
|
+
issues: Schema.Array(ValidationIssue),
|
|
43
|
+
});
|
|
44
|
+
/** The response of an action: its result, the input's validation, and the records it changed. */
|
|
45
|
+
export const ActionOutcome = Schema.Struct({
|
|
46
|
+
result: Schema.Json,
|
|
47
|
+
validation: ActionValidation,
|
|
48
|
+
edits: Schema.Array(Edit),
|
|
49
|
+
});
|
|
50
|
+
const VALID = { ok: true, issues: [] };
|
|
51
|
+
/** Rolls back the transaction of a write that ran only to be validated. */
|
|
52
|
+
class ValidationRollback extends Data.TaggedError("ValidationRollback") {
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A validated write's NotFound or ScopeViolated as a validation issue. A record that was not found
|
|
56
|
+
* is at the input key that holds its id; a scope the change would leave is at the input's root.
|
|
57
|
+
*/
|
|
58
|
+
function writeIssue(input, error) {
|
|
59
|
+
const key = error._tag === "NotFound" && typeof input === "object" && input !== null
|
|
60
|
+
? Object.entries(input).find(([, value]) => value === error.id)?.[0]
|
|
61
|
+
: undefined;
|
|
62
|
+
return { path: key === undefined ? [] : [key], message: error.message };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The entity's authored action of that name with the permission it requires, or else its generated
|
|
66
|
+
* one with the permissions named after it.
|
|
67
|
+
*/
|
|
68
|
+
export function resolveAction(ontology, entityName, actionName) {
|
|
69
|
+
const entity = ontology.entities.find((candidate) => candidate.name === entityName);
|
|
70
|
+
if (entity === undefined) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const authored = entity.actions.find((candidate) => candidate.name === actionName);
|
|
74
|
+
if (authored !== undefined) {
|
|
75
|
+
return { action: authored.action, permissions: [authored.permission], generated: false };
|
|
76
|
+
}
|
|
77
|
+
const generated = generatedActions(ontology, entityName)[actionName];
|
|
78
|
+
return generated === undefined
|
|
79
|
+
? undefined
|
|
80
|
+
: {
|
|
81
|
+
action: generated,
|
|
82
|
+
permissions: generatedActionPermissions(entityName, actionName),
|
|
83
|
+
generated: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The scopes the principal's grants put on an action's writes of its own entity. An authored action
|
|
88
|
+
* checks every write of its entity against the scope of its permission. A generated action checks
|
|
89
|
+
* records it creates against `<entity>:create` and records it changes against the permission of its
|
|
90
|
+
* verb; `upsert_by_<property>` checks against `create` or `update`, whichever it does. `undefined`
|
|
91
|
+
* when every permission is held unscoped, so that unscoped principals write as before.
|
|
92
|
+
*/
|
|
93
|
+
export function writeGuard(entity, found, principal) {
|
|
94
|
+
const check = (permission) => {
|
|
95
|
+
const where = effectiveScope(permission, principal);
|
|
96
|
+
return where === undefined ? undefined : { permission, scope: { reader: principal, where } };
|
|
97
|
+
};
|
|
98
|
+
let create;
|
|
99
|
+
let change;
|
|
100
|
+
if (principal.scopes === undefined || principal.scopes.length === 0) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
for (const permission of found.permissions) {
|
|
104
|
+
const scoped = check(permission);
|
|
105
|
+
if (!found.generated) {
|
|
106
|
+
create = scoped;
|
|
107
|
+
change = scoped;
|
|
108
|
+
}
|
|
109
|
+
else if (parsePermission(permission)?.verb === "create") {
|
|
110
|
+
create = scoped;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
change = scoped;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return create === undefined && change === undefined
|
|
117
|
+
? undefined
|
|
118
|
+
: {
|
|
119
|
+
entity,
|
|
120
|
+
...(create === undefined ? {} : { create }),
|
|
121
|
+
...(change === undefined ? {} : { change }),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** The entity's authored action of that name, or else its generated one. */
|
|
125
|
+
export function findAction(ontology, entityName, actionName) {
|
|
126
|
+
return resolveAction(ontology, entityName, actionName)?.action;
|
|
127
|
+
}
|
|
128
|
+
/** A transport for a service that has none: every call is refused as a platform fault. */
|
|
129
|
+
const NO_CONNECTIONS = {
|
|
130
|
+
request: (call) => Effect.fail(new ConnectionError({
|
|
131
|
+
connection: call.connection,
|
|
132
|
+
reason: "not_granted",
|
|
133
|
+
message: "This ontology service has no connection transport.",
|
|
134
|
+
retryable: false,
|
|
135
|
+
})),
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Runs an action the way every action runs, authored or generated:
|
|
139
|
+
*
|
|
140
|
+
* 1. decode the input with the action's schema; issues are returned, not raised;
|
|
141
|
+
* 2. check that the principal's permissions cover the action's (PermissionDenied);
|
|
142
|
+
* 3. with `validateOnly`, stop, unless the action is generated, the principal's permission for it is
|
|
143
|
+
* scoped and the input holds no upload: then run step 6 in a transaction that is
|
|
144
|
+
* always rolled back, and answer a NotFound or ScopeViolated of the writes as a validation issue;
|
|
145
|
+
* 4. reauthorize and replay a committed blob action, if its idempotency key matches;
|
|
146
|
+
* otherwise run `prepare`, when the action has one, without a transaction: reads and the declared
|
|
147
|
+
* connections only, under {@link PREPARE_TIMEOUT};
|
|
148
|
+
* 5. without a handler, answer with what `prepare` returned; when a trigger matches the action, make
|
|
149
|
+
* sure through the {@link WorkflowStarter} in context that a recovery run will sweep after the
|
|
150
|
+
* transaction's deadline, and refuse the action with WorkflowRecoveryUnavailable when it cannot;
|
|
151
|
+
* 6. open a transaction and run the handler with what `prepare` returned and the runtime, which
|
|
152
|
+
* records history rows for every write, under one action id, with `created_by`/`updated_by` set to
|
|
153
|
+
* the principal;
|
|
154
|
+
* 7. evaluate the action triggers that match the action, and insert the starts they decide, in the
|
|
155
|
+
* same transaction: a trigger function that throws rolls the action back, an input its workflow
|
|
156
|
+
* does not accept fails it with TriggerInputInvalid;
|
|
157
|
+
* 8. commit, or roll back on any failure, defect or interruption; an action explicitly declared
|
|
158
|
+
* retryOnDeadlock may repeat steps 6–8 after a confirmed deadlock rollback, with fresh state;
|
|
159
|
+
* 9. start the decided runs through the {@link WorkflowStarter} in context, then run the
|
|
160
|
+
* after-commit effects, outside the transaction.
|
|
161
|
+
*
|
|
162
|
+
* Database failures are defects.
|
|
163
|
+
*/
|
|
164
|
+
export function runAction(ontology, request) {
|
|
165
|
+
return Effect.gen(function* () {
|
|
166
|
+
const sql = yield* SqlClient.SqlClient;
|
|
167
|
+
const deployment = Option.getOrUndefined(yield* Effect.serviceOption(RuntimeDeployment));
|
|
168
|
+
if (deployment) {
|
|
169
|
+
yield* Effect.sync(() => requireDeploymentCapability(deployment));
|
|
170
|
+
}
|
|
171
|
+
const qualified = `${request.entity}.${request.action}`;
|
|
172
|
+
const found = resolveAction(ontology, request.entity, request.action);
|
|
173
|
+
if (found === undefined) {
|
|
174
|
+
return yield* new UnknownAction({
|
|
175
|
+
entity: request.entity,
|
|
176
|
+
action: request.action,
|
|
177
|
+
message: writesOnlyThroughActions(ontology, request.entity, request.action) ??
|
|
178
|
+
`${qualified} is not an action of this ontology.`,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const { action } = found;
|
|
182
|
+
// An authored action that declares the entities it writes may write only those.
|
|
183
|
+
const declaredTargets = found.generated
|
|
184
|
+
? undefined
|
|
185
|
+
: ontology.manifest.entities
|
|
186
|
+
.find((candidate) => candidate.name === request.entity)
|
|
187
|
+
?.actions.find((candidate) => candidate.name === request.action)?.writes;
|
|
188
|
+
const declaredWrites = declaredTargets === undefined ? undefined : new Set(declaredTargets);
|
|
189
|
+
if (deployment?.target === "preview" && action.connections.length > 0) {
|
|
190
|
+
yield* Effect.sync(() => requireDeploymentCapability(deployment, "connections"));
|
|
191
|
+
}
|
|
192
|
+
const newUserHook = request.options?.newUserHook === true;
|
|
193
|
+
if (newUserHook &&
|
|
194
|
+
(ontology.signIn.onNewUser !== `${request.entity}:${request.action}` ||
|
|
195
|
+
action.prepare !== undefined ||
|
|
196
|
+
action.connections.length > 0 ||
|
|
197
|
+
action.hints?.idempotent !== true ||
|
|
198
|
+
(request.afterCommit?.length ?? 0) > 0)) {
|
|
199
|
+
return yield* Effect.die(new Error("Invalid onNewUser action."));
|
|
200
|
+
}
|
|
201
|
+
const decoded = validate(action.input, request.input);
|
|
202
|
+
if (!decoded.ok) {
|
|
203
|
+
return { result: null, validation: { ok: false, issues: decoded.issues }, edits: [] };
|
|
204
|
+
}
|
|
205
|
+
const missing = missingPermission(found.permissions, request.principal);
|
|
206
|
+
if (missing !== undefined) {
|
|
207
|
+
return yield* new PermissionDenied({
|
|
208
|
+
permission: missing,
|
|
209
|
+
message: `${qualified} requires ${missing}, which the principal's roles do not grant.`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
const confirming = request.options?.confirm;
|
|
213
|
+
// A principal whose permission for the action is scoped may not read what `confirm` reads
|
|
214
|
+
// about a record outside the scope: it gets the action's fields, read as it may read them.
|
|
215
|
+
if (confirming !== undefined) {
|
|
216
|
+
return {
|
|
217
|
+
result: action.confirm === undefined ||
|
|
218
|
+
writeGuard(request.entity, found, request.principal) !== undefined
|
|
219
|
+
? null
|
|
220
|
+
: yield* confirmation(ontology, sql, action, qualified, {
|
|
221
|
+
input: decoded.value,
|
|
222
|
+
principal: request.principal,
|
|
223
|
+
locale: confirming.locale,
|
|
224
|
+
requestId: request.requestId ?? randomUUID(),
|
|
225
|
+
}),
|
|
226
|
+
validation: VALID,
|
|
227
|
+
edits: [],
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const hasUploads = (value) => typeof value === "object" &&
|
|
231
|
+
value !== null &&
|
|
232
|
+
("uploadId" in value || Object.values(value).some(hasUploads));
|
|
233
|
+
// The tickets the caller handed this action, which it settles when it commits.
|
|
234
|
+
const suppliedUploads = (value, found = new Set()) => {
|
|
235
|
+
if (typeof value === "object" && value !== null) {
|
|
236
|
+
const uploadId = "uploadId" in value ? value.uploadId : undefined;
|
|
237
|
+
if (typeof uploadId === "string" && isRecordId(uploadId)) {
|
|
238
|
+
found.add(uploadId.toLowerCase());
|
|
239
|
+
}
|
|
240
|
+
for (const item of Object.values(value)) {
|
|
241
|
+
suppliedUploads(item, found);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return found;
|
|
245
|
+
};
|
|
246
|
+
const key = request.options?.idempotencyKey;
|
|
247
|
+
// A UUID in either case names one record; the uuid column answers in lower case.
|
|
248
|
+
const createIntent = request.options?.createIntent?.toLowerCase();
|
|
249
|
+
const hasBlobProperties = ontology.manifest.entities.some((entity) => entity.properties.some((property) => property.type === "blob"));
|
|
250
|
+
const consumesUpload = action.transactional &&
|
|
251
|
+
hasBlobProperties &&
|
|
252
|
+
(hasUploads(decoded.value) || key !== undefined || (action.files?.length ?? 0) > 0);
|
|
253
|
+
const guard = writeGuard(request.entity, found, request.principal);
|
|
254
|
+
// A scoped generated write is validated by running it and rolling it back: its scope checks
|
|
255
|
+
// need the record as the write would leave it. Authored handlers run only when executed.
|
|
256
|
+
const validateOnly = request.options?.validateOnly === true;
|
|
257
|
+
if (validateOnly && (guard === undefined || !found.generated || consumesUpload)) {
|
|
258
|
+
return { result: null, validation: VALID, edits: [] };
|
|
259
|
+
}
|
|
260
|
+
// Inside a caller's transaction, the commit would come after the after-commit effects, and
|
|
261
|
+
// `prepare` would hold the caller's locks while it waits for a provider.
|
|
262
|
+
if (Option.isSome(yield* Effect.serviceOption(sql.transactionService))) {
|
|
263
|
+
return yield* Effect.die(new Error(`${qualified} must run outside any transaction.`));
|
|
264
|
+
}
|
|
265
|
+
const actionId = randomUUID();
|
|
266
|
+
const requestId = request.requestId ?? actionId;
|
|
267
|
+
const context = {
|
|
268
|
+
input: decoded.value,
|
|
269
|
+
principal: request.principal,
|
|
270
|
+
requestId,
|
|
271
|
+
...(request.runtime === undefined ? {} : { runtime: request.runtime }),
|
|
272
|
+
};
|
|
273
|
+
if (consumesUpload && (!key || key.length > 128 || !request.branchId)) {
|
|
274
|
+
return yield* new Conflict({
|
|
275
|
+
entity: request.entity,
|
|
276
|
+
message: "Upload actions require an idempotency key.",
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const fingerprint = consumesUpload
|
|
280
|
+
? createHash("sha256")
|
|
281
|
+
.update(canonicalJson({
|
|
282
|
+
qualified,
|
|
283
|
+
manifestHash: ontology.manifest.hash,
|
|
284
|
+
branchId: request.branchId ?? null,
|
|
285
|
+
input: request.input,
|
|
286
|
+
createIntent: createIntent ?? null,
|
|
287
|
+
}))
|
|
288
|
+
.digest("hex")
|
|
289
|
+
: undefined;
|
|
290
|
+
// A committed retry must not depend on preparation's external services being available.
|
|
291
|
+
// The handler transaction below still locks and rechecks the key for concurrent requests.
|
|
292
|
+
if (consumesUpload) {
|
|
293
|
+
const saved = yield* sql
|
|
294
|
+
.withTransaction(Effect.gen(function* () {
|
|
295
|
+
yield* authorizeBlobOperation(ontology, sql, request.principal.id, found.permissions, request.principal.execution, request.principal.workflow);
|
|
296
|
+
const [saved] = yield* sql.unsafe(`SELECT fingerprint, outcome FROM "_blob_action_results"
|
|
297
|
+
WHERE principal_id = $1::uuid AND key = $2`, [request.principal.id, key]);
|
|
298
|
+
if (saved && saved.fingerprint !== fingerprint) {
|
|
299
|
+
return yield* new Conflict({
|
|
300
|
+
entity: request.entity,
|
|
301
|
+
message: "Idempotency key was used for another action.",
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return saved?.outcome;
|
|
305
|
+
}))
|
|
306
|
+
.pipe(sqlFailuresAsDefects);
|
|
307
|
+
if (saved) {
|
|
308
|
+
return saved;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const prepared = action.prepare === undefined
|
|
312
|
+
? undefined
|
|
313
|
+
: yield* prepare(ontology, sql, action, context, request.connections, qualified, {
|
|
314
|
+
principal: request.principal,
|
|
315
|
+
action: qualified,
|
|
316
|
+
idempotencyKey: key,
|
|
317
|
+
createIntent,
|
|
318
|
+
});
|
|
319
|
+
if (!action.transactional) {
|
|
320
|
+
return { result: prepared ?? null, validation: VALID, edits: [] };
|
|
321
|
+
}
|
|
322
|
+
const admission = Option.getOrUndefined(yield* Effect.serviceOption(ActionTransactions));
|
|
323
|
+
const configuredLimits = admission?.limits ?? ACTION_TRANSACTION_LIMITS;
|
|
324
|
+
const limits = newUserHook
|
|
325
|
+
? {
|
|
326
|
+
handlerTimeoutMs: Math.min(5_000, configuredLimits.handlerTimeoutMs),
|
|
327
|
+
statementTimeoutMs: Math.min(5_000, configuredLimits.statementTimeoutMs),
|
|
328
|
+
lockTimeoutMs: Math.min(5_000, configuredLimits.lockTimeoutMs),
|
|
329
|
+
}
|
|
330
|
+
: configuredLimits;
|
|
331
|
+
// Triggers are definitions; all mutable runtime and trigger state belongs to one attempt.
|
|
332
|
+
const triggers = matchingTriggers(ontology, request.entity, request.action, request.principal);
|
|
333
|
+
const starter = Option.getOrUndefined(yield* Effect.serviceOption(WorkflowStarter));
|
|
334
|
+
// Before the transaction: whatever it commits must be swept if this request does not start it.
|
|
335
|
+
if (triggers.length > 0 && !validateOnly && starter !== undefined) {
|
|
336
|
+
yield* starter.ensureRecovery({ commitBy: Date.now() + limits.handlerTimeoutMs }).pipe(Effect.mapError((error) => new WorkflowRecoveryUnavailable({
|
|
337
|
+
message: `${qualified} was not run: ${error.message}`,
|
|
338
|
+
cause: error,
|
|
339
|
+
})));
|
|
340
|
+
}
|
|
341
|
+
const transaction = actionTransaction(sql, () => {
|
|
342
|
+
const edits = [];
|
|
343
|
+
const held = new Map();
|
|
344
|
+
let starts = [];
|
|
345
|
+
const signInEmails = [];
|
|
346
|
+
const runtime = makeOntologyRuntime(ontology, sql, {
|
|
347
|
+
action: qualified,
|
|
348
|
+
actionId,
|
|
349
|
+
principalId: request.principal.id,
|
|
350
|
+
...(request.principal.execution ? { execution: request.principal.execution } : {}),
|
|
351
|
+
...(request.principal.workflow ? { workflow: request.principal.workflow } : {}),
|
|
352
|
+
...(key ? { blobActionKey: key } : {}),
|
|
353
|
+
...(request.branchId ? { branchId: request.branchId } : {}),
|
|
354
|
+
...(createIntent ? { createIntent } : {}),
|
|
355
|
+
...(declaredWrites ? { declaredWrites } : {}),
|
|
356
|
+
onEdit: (edit) => edits.push(edit),
|
|
357
|
+
onSignInEmail: (email) => signInEmails.push(email),
|
|
358
|
+
...(triggers.length > 0
|
|
359
|
+
? {
|
|
360
|
+
onSnapshot: (snapshot) => {
|
|
361
|
+
if (snapshot.entity !== request.entity) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const previous = held.get(snapshot.id);
|
|
365
|
+
held.set(snapshot.id, {
|
|
366
|
+
before: previous === undefined ? snapshot.before : previous.before,
|
|
367
|
+
after: snapshot.after,
|
|
368
|
+
});
|
|
369
|
+
},
|
|
370
|
+
}
|
|
371
|
+
: {}),
|
|
372
|
+
replicationInitialized: true,
|
|
373
|
+
...(newUserHook ? { protectPrincipals: true } : {}),
|
|
374
|
+
...(guard === undefined ? {} : { guard }),
|
|
375
|
+
});
|
|
376
|
+
const handle = action.handler;
|
|
377
|
+
const handler = Effect.gen(function* () {
|
|
378
|
+
const identities = isRecordId(context.principal.id)
|
|
379
|
+
? yield* sql
|
|
380
|
+
.unsafe(`SELECT "i"."provider", "i"."subject", true AS "verified", "i"."username" ` +
|
|
381
|
+
`FROM ${CHANNEL_IDENTITIES_FROM} ` +
|
|
382
|
+
`WHERE "i"."user_id" = $1::uuid AND ${LIVE_CHANNEL_IDENTITY}`, [context.principal.id])
|
|
383
|
+
.pipe(Effect.map((found) => Schema.decodeUnknownSync(Schema.Array(ActionIdentity))(found.map(({ username, ...identity }) => ({
|
|
384
|
+
...identity,
|
|
385
|
+
...(username === null ? {} : { username }),
|
|
386
|
+
})))))
|
|
387
|
+
: [];
|
|
388
|
+
const [provenance] = isRecordId(context.principal.id)
|
|
389
|
+
? yield* sql.unsafe(`SELECT COALESCE(c."created_via", CASE
|
|
390
|
+
WHEN u."kind"::text = 'service' THEN 'service'
|
|
391
|
+
WHEN NOT EXISTS (SELECT 1 FROM "${USER_IDENTITY_TABLE}" i WHERE i."user_id" = u."id"
|
|
392
|
+
AND i."origin"::text IN ${SIGN_IN_ORIGIN_SQL} AND i."revoked_at" IS NULL) OR EXISTS (
|
|
393
|
+
SELECT 1 FROM "user_history" h WHERE h."record_id" = u."id"
|
|
394
|
+
AND h."action" = 'user.invite' AND h."kind"::text = 'create'
|
|
395
|
+
) THEN 'invite' ELSE 'email' END) AS "created_via"
|
|
396
|
+
FROM "${USER_TABLE}" u
|
|
397
|
+
LEFT JOIN "${CHANNEL_USERS_TABLE}" c ON c."user_id" = u."id"
|
|
398
|
+
WHERE u."id" = $1::uuid`, [context.principal.id])
|
|
399
|
+
: [];
|
|
400
|
+
return yield* handle({
|
|
401
|
+
...context,
|
|
402
|
+
principal: {
|
|
403
|
+
...context.principal,
|
|
404
|
+
identities,
|
|
405
|
+
createdVia: provenance?.created_via ?? "unknown",
|
|
406
|
+
authenticatedVia: context.principal.authenticatedVia ?? "unknown",
|
|
407
|
+
},
|
|
408
|
+
prepared,
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
let replay;
|
|
412
|
+
const transactionalHandler = Effect.gen(function* () {
|
|
413
|
+
if (newUserHook) {
|
|
414
|
+
// Administrative mutations lock User first too. Keep this order before the lifecycle row
|
|
415
|
+
// so handler writes that reference the user cannot deadlock with blocking/deactivation.
|
|
416
|
+
yield* sql.unsafe(`SELECT "id" FROM "${USER_TABLE}" WHERE "id" = $1::uuid FOR UPDATE`, [
|
|
417
|
+
request.principal.id,
|
|
418
|
+
]);
|
|
419
|
+
const [pending] = yield* sql.unsafe(`SELECT 1 FROM "${CHANNEL_USERS_TABLE}" c JOIN "${USER_TABLE}" u ON u."id" = c."user_id"
|
|
420
|
+
WHERE c."user_id" = $1::uuid AND c."created_via" NOT IN ('invite', 'email')
|
|
421
|
+
AND c."blocked_at" IS NULL AND c."on_new_user_completed_at" IS NULL AND u."active" FOR UPDATE OF c`, [request.principal.id]);
|
|
422
|
+
if (pending === undefined) {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
// Workloads and workflow users are re-checked against the database in the transaction.
|
|
427
|
+
if (request.principal.execution !== undefined ||
|
|
428
|
+
request.principal.workflow !== undefined) {
|
|
429
|
+
yield* authorizeBlobOperation(ontology, sql, request.principal.id, found.permissions, request.principal.execution, request.principal.workflow);
|
|
430
|
+
}
|
|
431
|
+
if (consumesUpload) {
|
|
432
|
+
yield* authorizeBlobOperation(ontology, sql, request.principal.id, found.permissions, request.principal.execution, request.principal.workflow);
|
|
433
|
+
yield* sql.unsafe(`INSERT INTO "_blob_action_results" (principal_id, key, fingerprint)
|
|
434
|
+
VALUES ($1::uuid, $2, $3) ON CONFLICT DO NOTHING`, [request.principal.id, key, fingerprint]);
|
|
435
|
+
const [saved] = yield* sql.unsafe(`SELECT fingerprint, outcome FROM "_blob_action_results" WHERE principal_id = $1::uuid AND key = $2
|
|
436
|
+
FOR UPDATE`, [request.principal.id, key]);
|
|
437
|
+
if (saved.fingerprint !== fingerprint) {
|
|
438
|
+
return yield* new Conflict({
|
|
439
|
+
entity: request.entity,
|
|
440
|
+
message: "Idempotency key was used for another action.",
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
if (saved.outcome) {
|
|
444
|
+
replay = saved.outcome;
|
|
445
|
+
return replay.result;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const result = yield* handler.pipe(OntologyRuntime.provide(runtime));
|
|
449
|
+
if (validateOnly) {
|
|
450
|
+
return yield* new ValidationRollback();
|
|
451
|
+
}
|
|
452
|
+
if (triggers.length > 0 && edits.some((edit) => edit.entity === request.entity)) {
|
|
453
|
+
const [now] = yield* sql.unsafe(`SELECT to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS "at"`);
|
|
454
|
+
starts = yield* evaluateTriggers(triggers, {
|
|
455
|
+
ontology,
|
|
456
|
+
entity: request.entity,
|
|
457
|
+
action: request.action,
|
|
458
|
+
definition: action,
|
|
459
|
+
generated: found.generated,
|
|
460
|
+
actionId,
|
|
461
|
+
at: canonicalInstant(now.at),
|
|
462
|
+
principal: request.principal,
|
|
463
|
+
input: decoded.value,
|
|
464
|
+
result,
|
|
465
|
+
edits,
|
|
466
|
+
held,
|
|
467
|
+
load: (id) => runtime.get({ entity: request.entity, id }).pipe(Effect.map((record) => record), Effect.catchTag("NotFound", () => Effect.succeed(undefined))),
|
|
468
|
+
});
|
|
469
|
+
yield* insertStarts(sql, starts);
|
|
470
|
+
}
|
|
471
|
+
if (newUserHook) {
|
|
472
|
+
yield* sql.unsafe(`UPDATE "${CHANNEL_USERS_TABLE}" SET "on_new_user_completed_at" = now() WHERE "user_id" = $1::uuid`, [request.principal.id]);
|
|
473
|
+
}
|
|
474
|
+
if (consumesUpload) {
|
|
475
|
+
// A ticket this action was given and did not store, such as a file it found already
|
|
476
|
+
// kept, is released with the commit: it stops counting towards the caller's open
|
|
477
|
+
// uploads, and storage maintenance removes its bytes. A rejected action rolls this
|
|
478
|
+
// back and leaves the ticket for a retry; a retry of this committed action replays
|
|
479
|
+
// its saved outcome. Promoted chat attachments keep their file and their upload.
|
|
480
|
+
for (const uploadId of suppliedUploads(decoded.value)) {
|
|
481
|
+
yield* sql.unsafe(`UPDATE "_blob_uploads" u SET state = 'canceled', expires_at = now(),
|
|
482
|
+
claim = NULL, claimed_at = NULL
|
|
483
|
+
WHERE u.id = $1::uuid AND u.principal_id = $2::uuid AND u.action = $3
|
|
484
|
+
AND u.state = 'ready'
|
|
485
|
+
AND NOT EXISTS (SELECT 1 FROM "${ATTACHMENTS_TABLE}" a WHERE a.blob_id = u.blob_id)`, [uploadId, request.principal.id, qualified]);
|
|
486
|
+
}
|
|
487
|
+
yield* sql.unsafe(`UPDATE "_blob_action_results" SET outcome = $1::jsonb
|
|
488
|
+
WHERE principal_id = $2::uuid AND key = $3`, [
|
|
489
|
+
JSON.stringify({ result: result ?? null, validation: VALID, edits }),
|
|
490
|
+
request.principal.id,
|
|
491
|
+
key,
|
|
492
|
+
]);
|
|
493
|
+
}
|
|
494
|
+
return result;
|
|
495
|
+
});
|
|
496
|
+
const transactionBody = Effect.gen(function* () {
|
|
497
|
+
// Server deadlines also protect the reserved connection if client cancellation cannot arrive.
|
|
498
|
+
yield* sql.unsafe(`SELECT set_config('statement_timeout', $1, true), set_config('lock_timeout', $2, true)`, [`${limits.statementTimeoutMs}ms`, `${limits.lockTimeoutMs}ms`]);
|
|
499
|
+
yield* beginReplicationWrite(sql);
|
|
500
|
+
return yield* transactionalHandler;
|
|
501
|
+
});
|
|
502
|
+
return transactionBody.pipe(Effect.map((result) => ({ result, edits, starts, signInEmails, replay })));
|
|
503
|
+
}, {
|
|
504
|
+
qualified,
|
|
505
|
+
actionId,
|
|
506
|
+
requestId,
|
|
507
|
+
retryOnDeadlock: action.retryOnDeadlock === true,
|
|
508
|
+
// A resent request runs prepare again; only without connections does that repeat nothing.
|
|
509
|
+
conflictRetryable: action.connections.length === 0,
|
|
510
|
+
handlerTimeoutMs: limits.handlerTimeoutMs,
|
|
511
|
+
});
|
|
512
|
+
const settled = (admission ? admission.admit(transaction) : transaction).pipe(Effect.annotateLogs({ action: qualified, actionId, requestId }), sqlFailuresAsDefects);
|
|
513
|
+
if (validateOnly) {
|
|
514
|
+
return yield* settled.pipe(Effect.as(VALID), Effect.catchTag("ValidationRollback", () => Effect.succeed(VALID)), Effect.catchTag(["NotFound", "ScopeViolated"], (error) => Effect.succeed({ ok: false, issues: [writeIssue(request.input, error)] })), Effect.map((validation) => ({ result: null, validation, edits: [] })));
|
|
515
|
+
}
|
|
516
|
+
const { result, edits, starts, signInEmails, replay } = yield* settled.pipe(Effect.catchTag("ValidationRollback", (rollback) => Effect.die(rollback)));
|
|
517
|
+
if (replay) {
|
|
518
|
+
return replay;
|
|
519
|
+
}
|
|
520
|
+
// The decided runs start before the after-commit effects; a failure leaves them pending for the
|
|
521
|
+
// recovery run armed above.
|
|
522
|
+
yield* startCommitted(sql, starter, starts, requestId);
|
|
523
|
+
yield* sendQueuedSignInEmails(sql, Option.getOrUndefined(yield* Effect.serviceOption(SignInLinks)), signInEmails, requestId);
|
|
524
|
+
const committed = {
|
|
525
|
+
entity: request.entity,
|
|
526
|
+
action: request.action,
|
|
527
|
+
actionId,
|
|
528
|
+
principal: request.principal,
|
|
529
|
+
input: decoded.value,
|
|
530
|
+
result,
|
|
531
|
+
edits,
|
|
532
|
+
};
|
|
533
|
+
for (const afterCommit of request.afterCommit ?? []) {
|
|
534
|
+
yield* Effect.suspend(() => afterCommit(committed)).pipe(Effect.catchCause((cause) => Effect.logWarning("An after-commit effect failed; the action had committed.", Cause.pretty(cause)).pipe(Effect.annotateLogs({
|
|
535
|
+
operation: "ontology.after_commit",
|
|
536
|
+
action: qualified,
|
|
537
|
+
actionId,
|
|
538
|
+
}))));
|
|
539
|
+
}
|
|
540
|
+
return { result: result ?? null, validation: VALID, edits };
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Runs an action's `confirm` outside any transaction, with reads only, and checks what it describes.
|
|
545
|
+
* A confirmation the schema refuses is the action's defect, as a handler's would be.
|
|
546
|
+
*/
|
|
547
|
+
function confirmation(ontology, sql, action, qualified, context) {
|
|
548
|
+
const runtime = makeOntologyRuntime(ontology, sql);
|
|
549
|
+
const reads = {
|
|
550
|
+
get: ({ entity, id }) => runtime.get({ entity, id }),
|
|
551
|
+
find: ({ entity, options }) => runtime.find({ entity, options: { ...options, forUpdate: false } }),
|
|
552
|
+
queryMetrics: (input) => runtime.queryMetrics(input),
|
|
553
|
+
};
|
|
554
|
+
return Effect.suspend(() => action.confirm(context)).pipe(Effect.provideService(OntologyReads, reads), Effect.timeoutOrElse({
|
|
555
|
+
duration: CONFIRM_TIMEOUT,
|
|
556
|
+
orElse: () => Effect.die(new Error(`${qualified} confirm exceeded its ${Duration.format(CONFIRM_TIMEOUT)} deadline.`)),
|
|
557
|
+
}), Effect.flatMap((described) => Schema.decodeUnknownEffect(ActionConfirmation)(described, {
|
|
558
|
+
onExcessProperty: "error",
|
|
559
|
+
}).pipe(Effect.mapError((issue) => new Error(`${qualified} confirm described an invalid confirmation: ${issue}`)), Effect.orDie)), sqlFailuresAsDefects);
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Runs `prepare` outside any transaction, with reads, declared connections and file access.
|
|
563
|
+
* Reads take no locks here: `forUpdate` is dropped, since a lock
|
|
564
|
+
* outside a transaction would be released at once.
|
|
565
|
+
*/
|
|
566
|
+
function prepare(ontology, sql, action, context, connections, qualified, fileInvocation) {
|
|
567
|
+
return Effect.gen(function* () {
|
|
568
|
+
const transport = connections ?? Option.getOrUndefined(yield* Effect.serviceOption(ConnectionTransport));
|
|
569
|
+
const fileAccess = Option.getOrUndefined(yield* Effect.serviceOption(FileAccess));
|
|
570
|
+
const files = fileAccess?.forInvocation(fileInvocation) ?? unavailableFiles;
|
|
571
|
+
const runtime = makeOntologyRuntime(ontology, sql);
|
|
572
|
+
const reads = {
|
|
573
|
+
get: ({ entity, id }) => runtime.get({ entity, id }),
|
|
574
|
+
find: ({ entity, options }) => runtime.find({ entity, options: { ...options, forUpdate: false } }),
|
|
575
|
+
queryMetrics: (input) => runtime.queryMetrics(input),
|
|
576
|
+
};
|
|
577
|
+
const run = Effect.suspend(() => action.prepare(context));
|
|
578
|
+
return yield* run.pipe(Effect.provideService(OntologyReads, reads), Effect.provideService(Files, action.hints?.readOnly === true
|
|
579
|
+
? {
|
|
580
|
+
...files,
|
|
581
|
+
stage: () => Effect.fail(new Conflict({ entity: "upload", message: "file_write_not_permitted" })),
|
|
582
|
+
}
|
|
583
|
+
: files), Effect.provideService(AgentFileRecords, {
|
|
584
|
+
resolve: (reference, principal) => resolveFileRecord({ manifest: ontology.manifest, reads, reference, principal }),
|
|
585
|
+
}), Effect.provideContext(connectionContext(action.connections, transport ?? NO_CONNECTIONS, {
|
|
586
|
+
readOnly: action.hints?.readOnly === true || action.hints?.connectionsReadOnly === true,
|
|
587
|
+
})), Effect.timeoutOrElse({
|
|
588
|
+
duration: PREPARE_TIMEOUT,
|
|
589
|
+
orElse: () => Effect.fail(new ConnectionError({
|
|
590
|
+
connection: action.connections.map((handle) => handle.key).join(","),
|
|
591
|
+
reason: "timeout",
|
|
592
|
+
message: `${qualified} prepare exceeded its ${Duration.format(PREPARE_TIMEOUT)} deadline.`,
|
|
593
|
+
retryable: true,
|
|
594
|
+
})),
|
|
595
|
+
}), sqlFailuresAsDefects);
|
|
596
|
+
});
|
|
597
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Redacted } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* The TLS options for a connection string. A Neon endpoint routes by TLS server name and the
|
|
4
|
+
* pinned driver does not send one on its own, so the service and the migrate command would fail
|
|
5
|
+
* to connect without it. Other hosts keep the driver's defaults.
|
|
6
|
+
*/
|
|
7
|
+
export declare function postgresTls(connection: Redacted.Redacted<string>): {
|
|
8
|
+
readonly servername: string;
|
|
9
|
+
readonly rejectUnauthorized: true;
|
|
10
|
+
} | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Redacted } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* The TLS options for a connection string. A Neon endpoint routes by TLS server name and the
|
|
4
|
+
* pinned driver does not send one on its own, so the service and the migrate command would fail
|
|
5
|
+
* to connect without it. Other hosts keep the driver's defaults.
|
|
6
|
+
*/
|
|
7
|
+
export function postgresTls(connection) {
|
|
8
|
+
const value = Redacted.value(connection);
|
|
9
|
+
const hostname = URL.canParse(value) ? new URL(value).hostname : "";
|
|
10
|
+
if (!hostname.endsWith(".neon.tech")) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
return { servername: hostname, rejectUnauthorized: true };
|
|
14
|
+
}
|