@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,147 @@
|
|
|
1
|
+
import { Context, Effect } from "effect";
|
|
2
|
+
import { SIGN_IN_EMAIL_PARAMETER } from "../emails.js";
|
|
3
|
+
import { SIGN_IN_EMAILS_TABLE } from "../storage/tables.js";
|
|
4
|
+
/** How long Neon Auth may take to issue a link, which includes the platform's webhook sending it. */
|
|
5
|
+
const REQUEST_TIMEOUT_MS = 20_000;
|
|
6
|
+
/** How many links are requested at once after one action's commit. */
|
|
7
|
+
const REQUEST_CONCURRENCY = 5;
|
|
8
|
+
/** How long sign-in email rows are kept, for an operator, before a later send deletes them. */
|
|
9
|
+
export const SIGN_IN_EMAIL_RETENTION_DAYS = 30;
|
|
10
|
+
/**
|
|
11
|
+
* Asks the app's Neon Auth for a sign-in link to `returnTo` for a queued email. Neon Auth posts the
|
|
12
|
+
* link to the platform's email webhook, which sends the email, before it answers. The ontology
|
|
13
|
+
* service provides it in production; a runtime without one marks its rows `failed`.
|
|
14
|
+
*/
|
|
15
|
+
export class SignInLinks extends Context.Service()("@segmentstream/ontology/SignInLinks") {
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The callback a queued email's link is requested with: the path on the app's origin, with
|
|
19
|
+
* {@link SIGN_IN_EMAIL_PARAMETER} naming the row, which the platform's webhook reads and removes.
|
|
20
|
+
*/
|
|
21
|
+
export function signInEmailCallback(origin, email) {
|
|
22
|
+
const url = new URL(email.returnTo, origin);
|
|
23
|
+
url.searchParams.set(SIGN_IN_EMAIL_PARAMETER, email.id);
|
|
24
|
+
return url.href;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* {@link SignInLinks} through Better Auth's `POST /sign-in/magic-link` on the app's Neon Auth, with
|
|
28
|
+
* the app's production origin, which Neon Auth trusts for callbacks. A 404 means the workspace has
|
|
29
|
+
* no magic links yet; it is reconciled for them.
|
|
30
|
+
*/
|
|
31
|
+
export function neonSignInLinks(config) {
|
|
32
|
+
const fetcher = config.fetch ?? fetch;
|
|
33
|
+
const endpoint = `${config.baseUrl.replace(/\/+$/, "")}/sign-in/magic-link`;
|
|
34
|
+
return {
|
|
35
|
+
request: (email) => Effect.promise(async () => {
|
|
36
|
+
let response;
|
|
37
|
+
try {
|
|
38
|
+
response = await fetcher(endpoint, {
|
|
39
|
+
method: "POST",
|
|
40
|
+
redirect: "error",
|
|
41
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
42
|
+
headers: { "content-type": "application/json", origin: config.origin },
|
|
43
|
+
body: JSON.stringify({
|
|
44
|
+
email: email.email,
|
|
45
|
+
callbackURL: signInEmailCallback(config.origin, email),
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return "unreachable";
|
|
51
|
+
}
|
|
52
|
+
void response.body?.cancel().catch(() => undefined);
|
|
53
|
+
if (response.ok) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
return response.status === 404 ? "disabled" : "refused";
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Requests the links of the emails an action queued, after its commit, in the same request, and
|
|
62
|
+
* records each outcome on its row; rows older than {@link SIGN_IN_EMAIL_RETENTION_DAYS} days go.
|
|
63
|
+
* Without {@link SignInLinks}, as in a preview, the rows are `failed` with `unavailable`. Nothing
|
|
64
|
+
* here fails the action, and logs never carry an address or a link.
|
|
65
|
+
*/
|
|
66
|
+
export function sendQueuedSignInEmails(sql, links, emails, requestId) {
|
|
67
|
+
if (emails.length === 0) {
|
|
68
|
+
return Effect.void;
|
|
69
|
+
}
|
|
70
|
+
return Effect.forEach(emails, (email) => (links === undefined
|
|
71
|
+
? Effect.succeed("unavailable")
|
|
72
|
+
: links.request(email)).pipe(Effect.flatMap((failure) => sql
|
|
73
|
+
.unsafe(failure === undefined
|
|
74
|
+
? `UPDATE "${SIGN_IN_EMAILS_TABLE}" SET "status" = 'requested',
|
|
75
|
+
"requested_at" = now() WHERE "id" = $1::uuid`
|
|
76
|
+
: `UPDATE "${SIGN_IN_EMAILS_TABLE}" SET "status" = 'failed', "failure" = $2
|
|
77
|
+
WHERE "id" = $1::uuid`, failure === undefined ? [email.id] : [email.id, failure])
|
|
78
|
+
.pipe(Effect.andThen(failure === undefined
|
|
79
|
+
? Effect.logInfo("ontology.sign_in_email.requested")
|
|
80
|
+
: Effect.logWarning("ontology.sign_in_email.failed")), Effect.annotateLogs({
|
|
81
|
+
requestId,
|
|
82
|
+
signInEmail: email.id,
|
|
83
|
+
...(failure === undefined ? {} : { failure }),
|
|
84
|
+
})))), { concurrency: REQUEST_CONCURRENCY, discard: true }).pipe(Effect.andThen(sql.unsafe(`DELETE FROM "${SIGN_IN_EMAILS_TABLE}"
|
|
85
|
+
WHERE "created_at" < now() - make_interval(days => $1)`, [SIGN_IN_EMAIL_RETENTION_DAYS])), Effect.asVoid, Effect.catchCause((cause) => Effect.logError("ontology.sign_in_email.record_failed", cause).pipe(Effect.annotateLogs({ requestId, queued: emails.length }))));
|
|
86
|
+
}
|
|
87
|
+
/** How long after it was queued an email's link may still arrive from Neon Auth. */
|
|
88
|
+
const DELIVERY_WINDOW_HOURS = 24;
|
|
89
|
+
/**
|
|
90
|
+
* The queued email with this id, while its link may still arrive; undefined for an unknown or
|
|
91
|
+
* older one, and in a database migrated before the table existed.
|
|
92
|
+
*/
|
|
93
|
+
export function findSignInEmail(sql, id) {
|
|
94
|
+
return Effect.gen(function* () {
|
|
95
|
+
const [table] = yield* sql.unsafe(`SELECT to_regclass($1) IS NOT NULL AS "present"`, [SIGN_IN_EMAILS_TABLE]);
|
|
96
|
+
if (!table?.present) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
const [row] = yield* sql.unsafe(`SELECT e."id", e."email", e."template", e."values", e."return_to",
|
|
100
|
+
u."name" AS "member_name", s."email" AS "sender_email"
|
|
101
|
+
FROM "${SIGN_IN_EMAILS_TABLE}" e
|
|
102
|
+
JOIN "user" u ON u."id" = e."user_id"
|
|
103
|
+
LEFT JOIN "user" s ON s."id" = e."sender_id" AND s."kind" = 'member'
|
|
104
|
+
WHERE e."id" = $1::uuid
|
|
105
|
+
AND e."created_at" > now() - make_interval(hours => $2)`, [id, DELIVERY_WINDOW_HOURS]);
|
|
106
|
+
return row === undefined
|
|
107
|
+
? undefined
|
|
108
|
+
: {
|
|
109
|
+
id: row.id,
|
|
110
|
+
email: row.email,
|
|
111
|
+
template: row.template,
|
|
112
|
+
values: row.values,
|
|
113
|
+
returnTo: row.return_to,
|
|
114
|
+
memberName: row.member_name ?? row.email,
|
|
115
|
+
senderEmail: row.sender_email ?? undefined,
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/** Sends committed queued context through the platform's deployment-authenticated mail boundary. */
|
|
120
|
+
export function segmentstreamSignInLinks(config) {
|
|
121
|
+
return {
|
|
122
|
+
request: (email) => Effect.promise(async () => {
|
|
123
|
+
const token = config.identityToken();
|
|
124
|
+
if (!token) {
|
|
125
|
+
return "disabled";
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const response = await (config.fetch ?? fetch)(new URL("/api/internal/v1/app-auth/sign-in-email", config.segmentstreamUrl), {
|
|
129
|
+
method: "POST",
|
|
130
|
+
redirect: "error",
|
|
131
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
132
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
133
|
+
body: JSON.stringify({
|
|
134
|
+
id: email.id,
|
|
135
|
+
workspace_id: config.workspaceId,
|
|
136
|
+
organization_id: config.organizationId,
|
|
137
|
+
}),
|
|
138
|
+
});
|
|
139
|
+
void response.body?.cancel().catch(() => undefined);
|
|
140
|
+
return response.ok ? undefined : "refused";
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return "unreachable";
|
|
144
|
+
}
|
|
145
|
+
}),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { SqlClient } from "effect/unstable/sql";
|
|
3
|
+
import { type SqlError } from "effect/unstable/sql/SqlError";
|
|
4
|
+
import { Conflict, type NotFound } from "../errors.js";
|
|
5
|
+
import type { Ontology } from "../ontology.js";
|
|
6
|
+
import type { Query, QueryResult } from "../query.js";
|
|
7
|
+
import type { OntologyRuntimeService, StoredRecord, StoredValues } from "../runtime.js";
|
|
8
|
+
import { type BatchOperation } from "./batch.js";
|
|
9
|
+
import { InvalidQuery, UnknownRole } from "./errors.js";
|
|
10
|
+
import type { Edit, HistoryEntry, HistoryScope } from "./history.js";
|
|
11
|
+
import { type QueuedSignInEmail } from "./member-writes.js";
|
|
12
|
+
import { type EntityModel } from "./model.js";
|
|
13
|
+
import { type RecordSnapshot, type WriteGuard } from "./writes.js";
|
|
14
|
+
import { type ScopeReader } from "./where.js";
|
|
15
|
+
/**
|
|
16
|
+
* The action a runtime writes for. Every write records history rows naming it, and sets
|
|
17
|
+
* `created_by`/`updated_by` to its principal.
|
|
18
|
+
*/
|
|
19
|
+
export interface WriteScope extends HistoryScope {
|
|
20
|
+
/** Set only when the enclosing action initialized replication in its outer transaction. */
|
|
21
|
+
readonly replicationInitialized?: boolean;
|
|
22
|
+
/** Channel onboarding cannot mutate platform principals through entity handles. */
|
|
23
|
+
readonly protectPrincipals?: true;
|
|
24
|
+
/** Receives the edits of each write that succeeded, in order. */
|
|
25
|
+
readonly onEdit?: (edit: Edit) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Receives the stored record before and after each create, update and delete that succeeded, in
|
|
28
|
+
* order. Unset, the writes collect none.
|
|
29
|
+
*/
|
|
30
|
+
readonly onSnapshot?: (snapshot: RecordSnapshot) => void;
|
|
31
|
+
/**
|
|
32
|
+
* The scopes the acting principal's grants put on the action's writes of its own entity, checked
|
|
33
|
+
* before and after each write. Absent when it holds the action's permissions unscoped.
|
|
34
|
+
*/
|
|
35
|
+
readonly guard?: WriteGuard;
|
|
36
|
+
/** Receives each sign-in email the action queued, which is sent after its commit. */
|
|
37
|
+
readonly onSignInEmail?: (email: QueuedSignInEmail) => void;
|
|
38
|
+
/**
|
|
39
|
+
* The entities the action declares it writes, by name, as `action({ writes })` lists them. A write
|
|
40
|
+
* of any other entity is a defect. Absent when the action declares none.
|
|
41
|
+
*/
|
|
42
|
+
readonly declaredWrites?: ReadonlySet<string>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The SQL runtime: the {@link OntologyRuntime} contract for action handlers, plus the paged reads
|
|
46
|
+
* the HTTP API serves.
|
|
47
|
+
*/
|
|
48
|
+
export interface SqlOntologyRuntime extends OntologyRuntimeService {
|
|
49
|
+
/** A page of records matching a query. Fails with InvalidQuery when it does not fit the entity. */
|
|
50
|
+
readonly query: (input: {
|
|
51
|
+
readonly entity: string;
|
|
52
|
+
readonly query: Query;
|
|
53
|
+
/** The caller, whose read scopes leave out the rows outside them. */
|
|
54
|
+
readonly reader?: ScopeReader;
|
|
55
|
+
}) => Effect.Effect<QueryResult<StoredRecord>, InvalidQuery>;
|
|
56
|
+
/** One record; outside the `reader`'s read scope it is not found. */
|
|
57
|
+
readonly get: (input: {
|
|
58
|
+
readonly entity: string;
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly forUpdate?: boolean;
|
|
61
|
+
readonly reader?: ScopeReader;
|
|
62
|
+
}) => Effect.Effect<StoredRecord, NotFound>;
|
|
63
|
+
/** A page of the records related to one record, as `GET /lead/{id}/notes` serves it. */
|
|
64
|
+
readonly related: (input: {
|
|
65
|
+
readonly entity: string;
|
|
66
|
+
readonly id: string;
|
|
67
|
+
readonly relationship: string;
|
|
68
|
+
readonly query?: Query;
|
|
69
|
+
readonly reader?: ScopeReader;
|
|
70
|
+
}) => Effect.Effect<QueryResult<StoredRecord>, InvalidQuery | NotFound>;
|
|
71
|
+
/**
|
|
72
|
+
* A page of one record's history, newest first. A `reader` with a read scope of the entity reads
|
|
73
|
+
* the history of a record inside it now, and finds no other.
|
|
74
|
+
*/
|
|
75
|
+
readonly history: (input: {
|
|
76
|
+
readonly entity: string;
|
|
77
|
+
readonly id: string;
|
|
78
|
+
readonly after?: string;
|
|
79
|
+
readonly limit?: number;
|
|
80
|
+
readonly reader?: ScopeReader;
|
|
81
|
+
}) => Effect.Effect<QueryResult<HistoryEntry>, InvalidQuery | NotFound>;
|
|
82
|
+
/**
|
|
83
|
+
* The member writes behind `grantUserRoles`, `revokeUserRoles`, `inviteUser` and `setUserActive`,
|
|
84
|
+
* failing with UnknownRole where those reject a role, for the console's and the CLI's principal
|
|
85
|
+
* functions. Each writes history under the scope's action when it changes something.
|
|
86
|
+
*/
|
|
87
|
+
readonly members: {
|
|
88
|
+
readonly grantRoles: (input: {
|
|
89
|
+
readonly id: string;
|
|
90
|
+
readonly roles: ReadonlyArray<string>;
|
|
91
|
+
}) => Effect.Effect<StoredRecord, UnknownRole | NotFound>;
|
|
92
|
+
readonly revokeRoles: (input: {
|
|
93
|
+
readonly id: string;
|
|
94
|
+
readonly roles: ReadonlyArray<string> | "all";
|
|
95
|
+
}) => Effect.Effect<StoredRecord, UnknownRole | NotFound>;
|
|
96
|
+
readonly invite: (input: {
|
|
97
|
+
readonly email: string;
|
|
98
|
+
readonly name?: string | undefined;
|
|
99
|
+
readonly roles?: ReadonlyArray<string> | undefined;
|
|
100
|
+
}) => Effect.Effect<StoredRecord, UnknownRole | Conflict>;
|
|
101
|
+
readonly setActive: (input: {
|
|
102
|
+
readonly id: string;
|
|
103
|
+
readonly active: boolean;
|
|
104
|
+
}) => Effect.Effect<StoredRecord, NotFound>;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** A unique violation becomes Conflict, naming the property when the index covers one column. */
|
|
108
|
+
export declare function uniqueConflict(entity: EntityModel, error: SqlError, batch?: {
|
|
109
|
+
readonly operation: BatchOperation;
|
|
110
|
+
readonly scope: HistoryScope;
|
|
111
|
+
}): Effect.Effect<never, Conflict>;
|
|
112
|
+
/**
|
|
113
|
+
* Platform identity bookkeeping touches only User properties and their history. User is excluded
|
|
114
|
+
* from business replication; keep this internal capability separate from the general runtime so
|
|
115
|
+
* deleting users, cascading relationships or editing business records still takes its fence.
|
|
116
|
+
*/
|
|
117
|
+
export declare function makePrincipalUserWriter(ontology: Ontology, sql: SqlClient.SqlClient, scope: Pick<HistoryScope, "action" | "actionId">): {
|
|
118
|
+
recordsSignIn: boolean;
|
|
119
|
+
create: (values: StoredValues) => Effect.Effect<Readonly<Record<string, unknown>>, Conflict | NotFound, never>;
|
|
120
|
+
update: (id: string, values: StoredValues) => Effect.Effect<Readonly<Record<string, unknown>>, Conflict | NotFound, never>;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* The runtime over one SQL client, normally `@effect/sql-pg`'s. Statements are parameterised; the
|
|
124
|
+
* client runs them on the transaction of the calling fiber, so a runtime made for an action shares
|
|
125
|
+
* the action's transaction.
|
|
126
|
+
*
|
|
127
|
+
* Reads work without a scope. Writes need one: each write runs in its own savepoint (or its own
|
|
128
|
+
* transaction outside an action), so that the write and its history rows land together and a
|
|
129
|
+
* failed write, such as a Conflict a handler catches, leaves the action's transaction usable.
|
|
130
|
+
*/
|
|
131
|
+
export declare function makeOntologyRuntime(ontology: Ontology, sql: SqlClient.SqlClient, scope?: WriteScope): SqlOntologyRuntime;
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { Effect, Exit, Option } from "effect";
|
|
2
|
+
import { beginReplicationWrite } from "../replication/transaction.js";
|
|
3
|
+
import { isSqlError } from "effect/unstable/sql/SqlError";
|
|
4
|
+
import { ActionRejected, Conflict } from "../errors.js";
|
|
5
|
+
import { toSnakeCase } from "../names.js";
|
|
6
|
+
import { User } from "../user.js";
|
|
7
|
+
import { UserIdentity } from "../user-identity.js";
|
|
8
|
+
import { ACTION_MAX_BATCH_ROWS, BATCH_MAX_BYTES, BATCH_MAX_ROWS, batchInputBytes, executeBatch, planBatch, } from "./batch.js";
|
|
9
|
+
import { logBatchConflict } from "./batch-diagnostics.js";
|
|
10
|
+
import { runMetricQuery } from "./metric-query.js";
|
|
11
|
+
import { InvalidQuery, UndeclaredWrite, UnknownRole, WriteOutsideAction } from "./errors.js";
|
|
12
|
+
import { grantMemberRoles, inviteMember, queueSignInEmail, revokeMemberRoles, setMemberActive, } from "./member-writes.js";
|
|
13
|
+
import { entityModel, ontologyModel } from "./model.js";
|
|
14
|
+
import { loadRecord, notFound, readHistory, relatedCondition, runQuery, validateQuery, } from "./reads.js";
|
|
15
|
+
import { createRecord, deleteRecord, linkRecords, unlinkRecords, updateRecord, upsertRecord, } from "./writes.js";
|
|
16
|
+
import { readScopeOf } from "./where.js";
|
|
17
|
+
/** A handler wrote an entity its action's `writes` does not list. */
|
|
18
|
+
function undeclaredWrite(scope, entity) {
|
|
19
|
+
return new UndeclaredWrite({
|
|
20
|
+
entity,
|
|
21
|
+
action: scope.action,
|
|
22
|
+
message: `${scope.action} wrote ${entity}, which its writes do not list ` +
|
|
23
|
+
`(${[...(scope.declaredWrites ?? [])].join(", ") || "none"}). Add ${entity} to the ` +
|
|
24
|
+
"action's writes, or remove the write.",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** A role a handler may not assign is the caller's to fix: its message is returned to them. */
|
|
28
|
+
const rejectRole = (effect) => effect.pipe(Effect.mapError((error) => error instanceof UnknownRole ? new ActionRejected({ message: error.message }) : error));
|
|
29
|
+
/** An unexpected database failure is a defect: callers see a sanitized server error. */
|
|
30
|
+
const orDieOnSql = (effect) => effect.pipe(Effect.catchIf(isSqlError, (error) => Effect.die(error)));
|
|
31
|
+
/** Drops `undefined` object values, which handlers may pass and JSON cannot express. */
|
|
32
|
+
function withoutUndefined(value) {
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
return value.map(withoutUndefined);
|
|
35
|
+
}
|
|
36
|
+
if (typeof value !== "object" || value === null) {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
return Object.fromEntries(Object.entries(value)
|
|
40
|
+
.filter(([, entry]) => entry !== undefined)
|
|
41
|
+
.map(([key, entry]) => [key, withoutUndefined(entry)]));
|
|
42
|
+
}
|
|
43
|
+
/** A unique violation becomes Conflict, naming the property when the index covers one column. */
|
|
44
|
+
export function uniqueConflict(entity, error, batch) {
|
|
45
|
+
const cause = (error.reason.cause ?? {});
|
|
46
|
+
const unique = error.reason._tag === "UniqueViolation" || cause.code === "23505";
|
|
47
|
+
if (!unique) {
|
|
48
|
+
return Effect.die(error);
|
|
49
|
+
}
|
|
50
|
+
const columns = typeof cause.detail === "string" ? /^Key \(([^)]*)\)=/.exec(cause.detail)?.[1] : undefined;
|
|
51
|
+
const field = entity.fields.find((candidate) => candidate.column === columns);
|
|
52
|
+
const fields = typeof columns === "string"
|
|
53
|
+
? columns.split(", ").flatMap((column) => {
|
|
54
|
+
const candidate = entity.fields.find((entry) => entry.column === column);
|
|
55
|
+
return candidate === undefined ? [] : [candidate.relationship ?? candidate.name];
|
|
56
|
+
})
|
|
57
|
+
: [];
|
|
58
|
+
const details = batch ? { batch: { indices: [], ...(fields.length > 0 ? { fields } : {}) } } : {};
|
|
59
|
+
const label = entity.label.toLowerCase();
|
|
60
|
+
const conflict = field === undefined
|
|
61
|
+
? new Conflict({
|
|
62
|
+
entity: entity.name,
|
|
63
|
+
message: `The ${label} conflicts with an existing one.`,
|
|
64
|
+
...details,
|
|
65
|
+
})
|
|
66
|
+
: new Conflict({
|
|
67
|
+
entity: entity.name,
|
|
68
|
+
property: field.name,
|
|
69
|
+
message: `A ${label} with this ${field.name} already exists.`,
|
|
70
|
+
...details,
|
|
71
|
+
});
|
|
72
|
+
// Keep the driver's cause for diagnostics and unique-violation retries, never on the wire.
|
|
73
|
+
Object.defineProperty(conflict, "cause", { value: error, enumerable: false });
|
|
74
|
+
if (batch) {
|
|
75
|
+
return logBatchConflict(entity, error, {
|
|
76
|
+
operation: batch.operation,
|
|
77
|
+
action: batch.scope.action,
|
|
78
|
+
actionId: batch.scope.actionId,
|
|
79
|
+
}).pipe(Effect.andThen(Effect.fail(conflict)));
|
|
80
|
+
}
|
|
81
|
+
return Effect.fail(conflict);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Platform identity bookkeeping touches only User properties and their history. User is excluded
|
|
85
|
+
* from business replication; keep this internal capability separate from the general runtime so
|
|
86
|
+
* deleting users, cascading relationships or editing business records still takes its fence.
|
|
87
|
+
*/
|
|
88
|
+
export function makePrincipalUserWriter(ontology, sql, scope) {
|
|
89
|
+
const model = ontologyModel(ontology);
|
|
90
|
+
const user = entityModel(model, User.name);
|
|
91
|
+
const allowed = new Set(Object.keys(User.definition.properties).map(toSnakeCase));
|
|
92
|
+
const client = sql.withoutTransforms();
|
|
93
|
+
const context = () => ({
|
|
94
|
+
model,
|
|
95
|
+
scope: { action: scope.action, actionId: scope.actionId, principalId: null },
|
|
96
|
+
edits: [],
|
|
97
|
+
run: (text, params) => client.unsafe(text, params.values),
|
|
98
|
+
});
|
|
99
|
+
const write = (values, operation) => Effect.suspend(() => {
|
|
100
|
+
if (!user.manifest.builtIn ||
|
|
101
|
+
Object.keys(values).some((name) => !allowed.has(name) || user.fieldsByName.get(name)?.source !== "property")) {
|
|
102
|
+
return Effect.die(new Error("Principal writes accept only built-in User properties."));
|
|
103
|
+
}
|
|
104
|
+
return sql
|
|
105
|
+
.withTransaction(operation())
|
|
106
|
+
.pipe(Effect.catchIf(isSqlError, (error) => uniqueConflict(user, error)));
|
|
107
|
+
});
|
|
108
|
+
return {
|
|
109
|
+
recordsSignIn: user.fieldsByName.has("last_sign_in_at"),
|
|
110
|
+
// Unguarded writes: no scope applies, so ScopeViolated cannot occur.
|
|
111
|
+
create: (values) => write(values, () => createRecord(context(), user, values).pipe(Effect.catchTag("ScopeViolated", (error) => Effect.die(error)))),
|
|
112
|
+
update: (id, values) => write(values, () => updateRecord(context(), user, id, values).pipe(Effect.catchTag("ScopeViolated", (error) => Effect.die(error)))),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The runtime over one SQL client, normally `@effect/sql-pg`'s. Statements are parameterised; the
|
|
117
|
+
* client runs them on the transaction of the calling fiber, so a runtime made for an action shares
|
|
118
|
+
* the action's transaction.
|
|
119
|
+
*
|
|
120
|
+
* Reads work without a scope. Writes need one: each write runs in its own savepoint (or its own
|
|
121
|
+
* transaction outside an action), so that the write and its history rows land together and a
|
|
122
|
+
* failed write, such as a Conflict a handler catches, leaves the action's transaction usable.
|
|
123
|
+
*/
|
|
124
|
+
export function makeOntologyRuntime(ontology, sql, scope) {
|
|
125
|
+
const model = ontologyModel(ontology);
|
|
126
|
+
const client = sql.withoutTransforms();
|
|
127
|
+
const context = {
|
|
128
|
+
model,
|
|
129
|
+
run: (text, params) => client.unsafe(text, params.values),
|
|
130
|
+
};
|
|
131
|
+
const entityOf = (name) => Effect.sync(() => entityModel(model, name));
|
|
132
|
+
const reading = (reader) => reader === undefined ? context : { ...context, reader };
|
|
133
|
+
// The checks of the action's own entity; other entities are written with the action's authority.
|
|
134
|
+
const guardOf = (entity) => scope?.guard?.entity === entity.name ? scope.guard : undefined;
|
|
135
|
+
let attemptedBatchRows = 0;
|
|
136
|
+
let attributionInitialized = false;
|
|
137
|
+
const write = (entityName, operation) => Effect.gen(function* () {
|
|
138
|
+
const entity = yield* entityOf(entityName);
|
|
139
|
+
if (scope?.protectPrincipals && entity.name === User.name) {
|
|
140
|
+
return yield* Effect.die(new Error("onNewUser cannot change platform principals."));
|
|
141
|
+
}
|
|
142
|
+
// Sign-in identities authenticate members: only the platform's identity writer changes them.
|
|
143
|
+
if (entity.name === UserIdentity.name) {
|
|
144
|
+
return yield* Effect.die(new Error("Sign-in identities change only through the platform, never in an action."));
|
|
145
|
+
}
|
|
146
|
+
if (scope === undefined) {
|
|
147
|
+
return yield* Effect.die(new WriteOutsideAction({
|
|
148
|
+
entity: entityName,
|
|
149
|
+
message: `Writes to ${entityName} run inside an action, which history records.`,
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
if (scope.declaredWrites !== undefined && !scope.declaredWrites.has(entity.name)) {
|
|
153
|
+
return yield* Effect.die(undeclaredWrite(scope, entity.name));
|
|
154
|
+
}
|
|
155
|
+
const edits = [];
|
|
156
|
+
const snapshots = scope.onSnapshot === undefined ? undefined : [];
|
|
157
|
+
const enclosingTransaction = yield* Effect.serviceOption(sql.transactionService);
|
|
158
|
+
const initialized = scope.replicationInitialized && Option.isSome(enclosingTransaction);
|
|
159
|
+
const result = yield* sql
|
|
160
|
+
.withTransaction((initialized ? Effect.void : beginReplicationWrite(sql)).pipe(Effect.andThen(operation({ ...context, scope, edits, ...(snapshots === undefined ? {} : { snapshots }) }, entity, guardOf(entity)))))
|
|
161
|
+
.pipe(Effect.catchIf(isSqlError, (error) => uniqueConflict(entity, error)));
|
|
162
|
+
for (const edit of edits) {
|
|
163
|
+
scope.onEdit?.(edit);
|
|
164
|
+
}
|
|
165
|
+
for (const snapshot of snapshots ?? []) {
|
|
166
|
+
scope.onSnapshot?.(snapshot);
|
|
167
|
+
}
|
|
168
|
+
if (edits.length > 0 && scope.execution !== undefined) {
|
|
169
|
+
attributionInitialized = true;
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
});
|
|
173
|
+
const batch = (entityName, operation, input) => Effect.suspend(() => {
|
|
174
|
+
const startedAt = Date.now();
|
|
175
|
+
let statements = 0;
|
|
176
|
+
let distinctReferences = 0;
|
|
177
|
+
let counts = { created: 0, updated: 0, unchanged: 0 };
|
|
178
|
+
const run = Effect.gen(function* () {
|
|
179
|
+
if (scope === undefined) {
|
|
180
|
+
return yield* Effect.die(new WriteOutsideAction({
|
|
181
|
+
entity: entityName,
|
|
182
|
+
message: `Writes to ${entityName} run inside an action, which history records.`,
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
if (scope.declaredWrites !== undefined && !scope.declaredWrites.has(entityName)) {
|
|
186
|
+
return yield* Effect.die(undeclaredWrite(scope, entityName));
|
|
187
|
+
}
|
|
188
|
+
const declared = ontology.entities.find((entity) => entity.name === entityName);
|
|
189
|
+
if (declared?.builtIn === true || declared?.entity.definition.source !== undefined) {
|
|
190
|
+
return yield* new ActionRejected({
|
|
191
|
+
message: "Batch writes require an ordinary stored entity.",
|
|
192
|
+
batch: { indices: [] },
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
const entity = yield* entityOf(entityName);
|
|
196
|
+
const length = input.records.length;
|
|
197
|
+
if (length > 0) {
|
|
198
|
+
attemptedBatchRows += length;
|
|
199
|
+
}
|
|
200
|
+
if (length > BATCH_MAX_ROWS || (length > 0 && attemptedBatchRows > ACTION_MAX_BATCH_ROWS)) {
|
|
201
|
+
return yield* new ActionRejected({
|
|
202
|
+
message: length > BATCH_MAX_ROWS
|
|
203
|
+
? `A batch accepts at most ${BATCH_MAX_ROWS} records.`
|
|
204
|
+
: `An action accepts at most ${ACTION_MAX_BATCH_ROWS} attempted batch records.`,
|
|
205
|
+
batch: { indices: [] },
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (batchInputBytes(input) > BATCH_MAX_BYTES) {
|
|
209
|
+
return yield* new ActionRejected({
|
|
210
|
+
message: `A batch accepts at most ${BATCH_MAX_BYTES} UTF-8 JSON bytes.`,
|
|
211
|
+
batch: { indices: [] },
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
const plan = yield* planBatch(entity, operation, input);
|
|
215
|
+
distinctReferences = plan.distinctReferences;
|
|
216
|
+
if (length === 0) {
|
|
217
|
+
return plan.returning === "summary" ? counts : { ...counts, items: [] };
|
|
218
|
+
}
|
|
219
|
+
const enclosingTransaction = yield* Effect.serviceOption(sql.transactionService);
|
|
220
|
+
if (Option.isNone(enclosingTransaction)) {
|
|
221
|
+
return yield* Effect.die(new WriteOutsideAction({
|
|
222
|
+
entity: entityName,
|
|
223
|
+
message: "Batch writes require the enclosing action transaction.",
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
const edits = [];
|
|
227
|
+
const snapshots = scope.onSnapshot === undefined ? undefined : [];
|
|
228
|
+
statements += 1; // The SQL client's nested transaction issues one savepoint.
|
|
229
|
+
const result = yield* sql
|
|
230
|
+
.withTransaction((scope.replicationInitialized ? Effect.void : beginReplicationWrite(sql)).pipe(Effect.andThen(executeBatch({
|
|
231
|
+
...context,
|
|
232
|
+
scope,
|
|
233
|
+
edits,
|
|
234
|
+
...(snapshots === undefined ? {} : { snapshots }),
|
|
235
|
+
attributionInitialized,
|
|
236
|
+
run: (text, params) => {
|
|
237
|
+
statements += 1;
|
|
238
|
+
return context.run(text, params);
|
|
239
|
+
},
|
|
240
|
+
}, entity, plan, guardOf(entity))), Effect.onExit((exit) => Effect.sync(() => {
|
|
241
|
+
if (Exit.isFailure(exit)) {
|
|
242
|
+
statements += 1; // A failed nested transaction rolls back to its savepoint.
|
|
243
|
+
}
|
|
244
|
+
}))))
|
|
245
|
+
.pipe(Effect.catchIf(isSqlError, (error) => uniqueConflict(entity, error, { operation, scope })));
|
|
246
|
+
counts = { created: result.created, updated: result.updated, unchanged: result.unchanged };
|
|
247
|
+
for (const edit of edits) {
|
|
248
|
+
scope.onEdit?.(edit);
|
|
249
|
+
}
|
|
250
|
+
for (const snapshot of snapshots ?? []) {
|
|
251
|
+
scope.onSnapshot?.(snapshot);
|
|
252
|
+
}
|
|
253
|
+
if (edits.length > 0 && scope.execution !== undefined) {
|
|
254
|
+
attributionInitialized = true;
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
});
|
|
258
|
+
return run.pipe(Effect.onExit((exit) => Effect.logDebug("ontology.action.batch_completed").pipe(Effect.annotateLogs({
|
|
259
|
+
operation,
|
|
260
|
+
entity: entityName,
|
|
261
|
+
action: scope?.action,
|
|
262
|
+
actionId: scope?.actionId,
|
|
263
|
+
inputCount: input.records.length,
|
|
264
|
+
distinctReferenceCount: distinctReferences,
|
|
265
|
+
changedCount: counts.created + counts.updated,
|
|
266
|
+
unchangedCount: counts.unchanged,
|
|
267
|
+
sqlStatements: statements,
|
|
268
|
+
batchMs: Date.now() - startedAt,
|
|
269
|
+
outcome: Exit.isSuccess(exit) ? "success" : "failure",
|
|
270
|
+
}))));
|
|
271
|
+
});
|
|
272
|
+
const declaredRoles = ontology.roles.map((candidate) => candidate.name);
|
|
273
|
+
const memberWrite = (operation) => write(User.name, (writeContext, entity) => operation({ ...writeContext, sql, roles: declaredRoles }, entity));
|
|
274
|
+
const members = {
|
|
275
|
+
grantRoles: (input) => memberWrite((context, entity) => grantMemberRoles(context, entity, input)).pipe(Effect.catchTag("Conflict", (error) => Effect.die(error))),
|
|
276
|
+
revokeRoles: (input) => memberWrite((context, entity) => revokeMemberRoles(context, entity, input)).pipe(Effect.catchTag("Conflict", (error) => Effect.die(error))),
|
|
277
|
+
invite: (input) => memberWrite((context, entity) => inviteMember(context, entity, input)),
|
|
278
|
+
setActive: (input) => memberWrite((context, entity) => setMemberActive(context, entity, input)).pipe(Effect.catchTag("Conflict", (error) => Effect.die(error))),
|
|
279
|
+
};
|
|
280
|
+
const query = (input) => Effect.gen(function* () {
|
|
281
|
+
const entity = yield* entityOf(input.entity);
|
|
282
|
+
const validated = yield* validateQuery(entity, input.query);
|
|
283
|
+
return yield* orDieOnSql(runQuery(reading(input.reader), entity, validated));
|
|
284
|
+
});
|
|
285
|
+
/** One record, or undefined when it does not exist or is outside the reader's read scope. */
|
|
286
|
+
const load = (entity, id, reader, forUpdate) => loadRecord(context, entity, id, forUpdate === true ? "update" : undefined, readScopeOf(reader, entity));
|
|
287
|
+
return {
|
|
288
|
+
createMany: ((input) => batch(input.entity, "create", input)),
|
|
289
|
+
updateMany: ((input) => batch(input.entity, "update", input)),
|
|
290
|
+
upsertMany: ((input) => batch(input.entity, "upsert", input)),
|
|
291
|
+
get: (input) => orDieOnSql(Effect.gen(function* () {
|
|
292
|
+
const entity = yield* entityOf(input.entity);
|
|
293
|
+
const record = yield* load(entity, input.id, input.reader, input.forUpdate);
|
|
294
|
+
return record ?? (yield* notFound(entity, input.id));
|
|
295
|
+
})),
|
|
296
|
+
find: (input) => Effect.gen(function* () {
|
|
297
|
+
const entity = yield* entityOf(input.entity);
|
|
298
|
+
const { forUpdate, ...options } = input.options;
|
|
299
|
+
if ("select" in options && options.select !== undefined) {
|
|
300
|
+
return yield* new InvalidQuery({
|
|
301
|
+
entity: entity.name,
|
|
302
|
+
message: "Action reads return complete records.",
|
|
303
|
+
issues: [{ path: ["select"], message: "Unknown key" }],
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
// `{ where: { name: input.name } }` with an absent name means no condition on it.
|
|
307
|
+
const validated = yield* validateQuery(entity, withoutUndefined(options));
|
|
308
|
+
const page = yield* runQuery(context, entity, validated, { forUpdate: forUpdate === true });
|
|
309
|
+
return page.items;
|
|
310
|
+
// Handlers pass typed options, so a query that does not fit is a defect in the handler.
|
|
311
|
+
}).pipe(Effect.orDie),
|
|
312
|
+
queryMetrics: (input) => Effect.gen(function* () {
|
|
313
|
+
const entity = yield* entityOf(input.entity);
|
|
314
|
+
return yield* runMetricQuery(context, entity, withoutUndefined(input.options));
|
|
315
|
+
// As with find, options that do not fit the entity are a defect in the handler.
|
|
316
|
+
}).pipe(Effect.orDie),
|
|
317
|
+
create: (input) => write(input.entity, (writeContext, entity, guard) => createRecord(writeContext, entity, input.values, guard)),
|
|
318
|
+
update: (input) => write(input.entity, (writeContext, entity, guard) => updateRecord(writeContext, entity, input.id, input.values, guard?.change)),
|
|
319
|
+
upsertBy: (input) => write(input.entity, (writeContext, entity, guard) => upsertRecord(writeContext, entity, input.property, input.values, guard)),
|
|
320
|
+
delete: (input) => write(input.entity, (writeContext, entity, guard) => deleteRecord(writeContext, entity, input.id, new Set(), guard?.change)),
|
|
321
|
+
link: (input) => write(input.entity, (writeContext, entity, guard) => linkRecords(writeContext, entity, input, guard?.change)).pipe(Effect.catchTag("Conflict", (error) => Effect.die(error))),
|
|
322
|
+
unlink: (input) => write(input.entity, (writeContext, entity, guard) => unlinkRecords(writeContext, entity, input, guard?.change)).pipe(Effect.catchTag("Conflict", (error) => Effect.die(error))),
|
|
323
|
+
query,
|
|
324
|
+
related: (input) => Effect.gen(function* () {
|
|
325
|
+
const entity = yield* entityOf(input.entity);
|
|
326
|
+
const link = entity.links.get(input.relationship);
|
|
327
|
+
if (link === undefined) {
|
|
328
|
+
return yield* new InvalidQuery({
|
|
329
|
+
entity: entity.name,
|
|
330
|
+
message: `${input.relationship} is not a relationship of ${entity.name}.`,
|
|
331
|
+
issues: [{ path: ["relationship"], message: "Expected a relationship of the entity" }],
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
const source = yield* orDieOnSql(load(entity, input.id, input.reader));
|
|
335
|
+
if (source === undefined) {
|
|
336
|
+
return yield* notFound(entity, input.id);
|
|
337
|
+
}
|
|
338
|
+
const target = entityModel(model, link.target);
|
|
339
|
+
const validated = yield* validateQuery(target, input.query ?? {});
|
|
340
|
+
const keyValue = link.via === "key" ? (source[`${link.name}_id`] ?? null) : null;
|
|
341
|
+
return yield* orDieOnSql(runQuery(reading(input.reader), target, validated, {
|
|
342
|
+
condition: relatedCondition(link, source.id, keyValue),
|
|
343
|
+
}));
|
|
344
|
+
}),
|
|
345
|
+
history: (input) => Effect.gen(function* () {
|
|
346
|
+
const entity = yield* entityOf(input.entity);
|
|
347
|
+
// A scoped reader reads the history of a record it can read now: the record must exist
|
|
348
|
+
// inside its read scope. Everyone else reads any record's history, a deleted one's too.
|
|
349
|
+
if (readScopeOf(input.reader, entity) !== undefined &&
|
|
350
|
+
(yield* orDieOnSql(load(entity, input.id, input.reader))) === undefined) {
|
|
351
|
+
return yield* notFound(entity, input.id);
|
|
352
|
+
}
|
|
353
|
+
return yield* orDieOnSql(readHistory(context, entity, input));
|
|
354
|
+
}),
|
|
355
|
+
grantUserRoles: (input) => rejectRole(members.grantRoles(input)),
|
|
356
|
+
revokeUserRoles: (input) => rejectRole(members.revokeRoles(input)),
|
|
357
|
+
inviteUser: (input) => rejectRole(members.invite(input)),
|
|
358
|
+
setUserActive: members.setActive,
|
|
359
|
+
sendUserSignInLink: (input) => memberWrite((context, entity) => queueSignInEmail(context, entity, ontology.manifest.emails ?? {}, input)).pipe(Effect.catchTag("Conflict", (error) => Effect.die(error)), Effect.flatMap((queued) => Effect.sync(() => scope?.onSignInEmail?.(queued)))),
|
|
360
|
+
members,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collects the parameters of one statement. Values are always bound as parameters, cast to the
|
|
3
|
+
* column's type; only identifiers from the definition are written into the SQL text.
|
|
4
|
+
*/
|
|
5
|
+
export declare class Params {
|
|
6
|
+
readonly values: Array<unknown>;
|
|
7
|
+
/** Binds a value and returns its placeholder, cast when a type is given: `$3::bigint`. */
|
|
8
|
+
add(value: unknown, cast?: string): string;
|
|
9
|
+
}
|
|
10
|
+
/** `"t0"."owner_id"`. */
|
|
11
|
+
export declare function columnOf(alias: string, column: string): string;
|
|
12
|
+
/** Hands out table aliases, `t0`, `t1`, …, unique within one statement. */
|
|
13
|
+
export declare class Aliases {
|
|
14
|
+
private next;
|
|
15
|
+
take(prefix?: string): string;
|
|
16
|
+
}
|
|
17
|
+
/** `%` and `_` match literally in LIKE patterns built from caller text; `\` is the escape. */
|
|
18
|
+
export declare function escapeLike(text: string): string;
|