@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,108 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { SqlError } from "effect/unstable/sql/SqlError";
|
|
3
|
+
import { Conflict, ScopeViolated, type NotFound } from "../errors.js";
|
|
4
|
+
import type { StoredRecord, StoredValues, UpsertResult } from "../runtime.js";
|
|
5
|
+
import { type Edit, type HistoryScope } from "./history.js";
|
|
6
|
+
import { type EntityModel } from "./model.js";
|
|
7
|
+
import { type SqlContext } from "./reads.js";
|
|
8
|
+
import { Aliases, Params } from "./statement.js";
|
|
9
|
+
import { type RowScope } from "./where.js";
|
|
10
|
+
/**
|
|
11
|
+
* Writes inside one action. Each write records its history rows in the same transaction and
|
|
12
|
+
* collects its edits; the runtime hands them to the action once the write has succeeded.
|
|
13
|
+
*/
|
|
14
|
+
export interface WriteContext extends SqlContext {
|
|
15
|
+
readonly scope: HistoryScope;
|
|
16
|
+
readonly edits: Array<Edit>;
|
|
17
|
+
/** When set, the stored record before and after each create, update and delete. */
|
|
18
|
+
readonly snapshots?: Array<RecordSnapshot>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A record a write changed, as stored before and after it: `before` is undefined on a create,
|
|
22
|
+
* `after` on a delete. Action triggers read them; links and unlinks change neither.
|
|
23
|
+
*/
|
|
24
|
+
export interface RecordSnapshot {
|
|
25
|
+
readonly entity: string;
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly before: StoredRecord | undefined;
|
|
28
|
+
readonly after: StoredRecord | undefined;
|
|
29
|
+
}
|
|
30
|
+
type WriteError = Conflict | NotFound | ScopeViolated | SqlError;
|
|
31
|
+
/** A scoped permission a write of one record must stay inside. */
|
|
32
|
+
export interface ScopeCheck {
|
|
33
|
+
/** The permission whose scope it is, for example `staff_member:update`. */
|
|
34
|
+
readonly permission: string;
|
|
35
|
+
readonly scope: RowScope;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The scopes an action's writes of its own entity are checked against: `create` for records it
|
|
39
|
+
* creates (after the insert), `change` for records it updates, deletes, links or unlinks (before the
|
|
40
|
+
* change and, except for a delete, after it). Absent when the acting principal holds the action's
|
|
41
|
+
* permissions unscoped.
|
|
42
|
+
*/
|
|
43
|
+
export interface WriteGuard {
|
|
44
|
+
/** The action's entity; writes of other entities run with the action's authority. */
|
|
45
|
+
readonly entity: string;
|
|
46
|
+
readonly create?: ScopeCheck;
|
|
47
|
+
readonly change?: ScopeCheck;
|
|
48
|
+
}
|
|
49
|
+
/** The scope condition on the table aliased `alias`, with its own aliases from `aliases`. */
|
|
50
|
+
export declare function scopeCondition(context: SqlContext, entity: EntityModel, params: Params, aliases: Aliases, alias: string, check: ScopeCheck): string;
|
|
51
|
+
export declare function scopeViolated(entity: EntityModel, check: ScopeCheck, id?: string, batch?: {
|
|
52
|
+
readonly indices: ReadonlyArray<number>;
|
|
53
|
+
}): ScopeViolated;
|
|
54
|
+
/**
|
|
55
|
+
* Creates a record; with a guard, the record must be inside its `create` scope. `id` is for the
|
|
56
|
+
* platform's own records that keep an id issued before them; every other record gets a new one.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createRecord(context: WriteContext, entity: EntityModel, values: StoredValues, guard?: WriteGuard, id?: string): Effect.Effect<StoredRecord, WriteError>;
|
|
59
|
+
/**
|
|
60
|
+
* Updates a record. With a check, the record must be inside its scope before the change (else
|
|
61
|
+
* NotFound) and after it (else ScopeViolated).
|
|
62
|
+
*/
|
|
63
|
+
export declare function updateRecord(context: WriteContext, entity: EntityModel, id: string, values: StoredValues, check?: ScopeCheck): Effect.Effect<StoredRecord, WriteError>;
|
|
64
|
+
/**
|
|
65
|
+
* Creates the record, or updates the one with the same value of a unique property. The insert
|
|
66
|
+
* yields to an existing record with `ON CONFLICT DO NOTHING`, which also waits for a concurrent
|
|
67
|
+
* insert of the same value; the existing record is then locked and updated with the given values
|
|
68
|
+
* only, so defaults never overwrite it.
|
|
69
|
+
*/
|
|
70
|
+
export declare function upsertRecord(context: WriteContext, entity: EntityModel, property: string, values: StoredValues, guard?: WriteGuard): Effect.Effect<UpsertResult, WriteError>;
|
|
71
|
+
/**
|
|
72
|
+
* Deletes a record after applying what each relationship pointing at it asks for: `restrict`
|
|
73
|
+
* refuses, `cascade` deletes the dependent records and `setNull` clears their key, each with its
|
|
74
|
+
* own history. Join rows go too, with an unlink in the history of both records. Doing this here
|
|
75
|
+
* rather than through the foreign keys keeps every change in history.
|
|
76
|
+
*
|
|
77
|
+
* With a check, the record must be inside its scope before the delete (else NotFound), and so must
|
|
78
|
+
* every record of the same entity the delete cascades to or clears, however many relationships
|
|
79
|
+
* away (else ScopeViolated): a cascade writes the action's entity with the caller's scope too.
|
|
80
|
+
*/
|
|
81
|
+
export declare function deleteRecord(context: WriteContext, entity: EntityModel, id: string, deleting?: Set<string>, check?: ScopeCheck): Effect.Effect<void, WriteError>;
|
|
82
|
+
/** Links two records through a `rel.many` the entity declares. Linking twice changes nothing. */
|
|
83
|
+
export declare function linkRecords(context: WriteContext, entity: EntityModel, input: {
|
|
84
|
+
readonly relationship: string;
|
|
85
|
+
readonly id: string;
|
|
86
|
+
readonly targetId: string;
|
|
87
|
+
}, check?: ScopeCheck): Effect.Effect<void, NotFound | ScopeViolated | SqlError>;
|
|
88
|
+
/** Removes a link between two existing records. Unlinking records that are not linked changes nothing. */
|
|
89
|
+
export declare function unlinkRecords(context: WriteContext, entity: EntityModel, input: {
|
|
90
|
+
readonly relationship: string;
|
|
91
|
+
readonly id: string;
|
|
92
|
+
readonly targetId: string;
|
|
93
|
+
}, check?: ScopeCheck): Effect.Effect<void, NotFound | ScopeViolated | SqlError>;
|
|
94
|
+
/** A change to a user's role assignments. A role in both `grant` and `revoke` stays assigned. */
|
|
95
|
+
export interface UserRolesChange {
|
|
96
|
+
readonly grant?: ReadonlyArray<string>;
|
|
97
|
+
/** Roles to remove, or every assigned role. */
|
|
98
|
+
readonly revoke?: ReadonlyArray<string> | "all";
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Grants and revokes a user's roles, which the caller checked against the ontology, and records the
|
|
102
|
+
* change as an update of the user: one history row with `roles` before and after, and a new
|
|
103
|
+
* `updated_at`. A change that leaves the roles as they were writes nothing. Granting a role the
|
|
104
|
+
* user holds, or revoking one they do not, changes nothing. The record returned carries the new
|
|
105
|
+
* roles.
|
|
106
|
+
*/
|
|
107
|
+
export declare function changeUserRoles(context: WriteContext, entity: EntityModel, id: string, change: UserRolesChange): Effect.Effect<StoredRecord, NotFound | SqlError>;
|
|
108
|
+
export {};
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { resolveBlobWrites } from "../storage/writes.js";
|
|
3
|
+
import { Conflict, ScopeViolated } from "../errors.js";
|
|
4
|
+
import { ACTION_ATTRIBUTION_TABLE, USER_ROLES_TABLE, quoteIdentifier } from "../migrate/storage.js";
|
|
5
|
+
import { canonicalJson } from "../manifest.js";
|
|
6
|
+
import { User } from "../user.js";
|
|
7
|
+
import { InvalidValues } from "./errors.js";
|
|
8
|
+
import { uuidv7 } from "./ids.js";
|
|
9
|
+
import { diffRecords, insertHistory, } from "./history.js";
|
|
10
|
+
import { entityModel } from "./model.js";
|
|
11
|
+
import { loadRecord, normalizeId, notFound, requireRecords } from "./reads.js";
|
|
12
|
+
import { bindValue, decodeRecord, prepareWrite, selectList, } from "./records.js";
|
|
13
|
+
import { Aliases, columnOf, Params } from "./statement.js";
|
|
14
|
+
import { USER_ROLES_PROPERTY } from "./user-roles.js";
|
|
15
|
+
import { compileScope } from "./where.js";
|
|
16
|
+
/** The scope condition on the table aliased `alias`, with its own aliases from `aliases`. */
|
|
17
|
+
export function scopeCondition(context, entity, params, aliases, alias, check) {
|
|
18
|
+
return compileScope({ model: context.model, params, aliases }, entity, alias, check.scope);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The after-check: fails with ScopeViolated unless the record, as the write left it, is inside the
|
|
22
|
+
* scope. It runs in the write's own savepoint, so the failure rolls the write and its history back.
|
|
23
|
+
*/
|
|
24
|
+
function requireInScope(context, entity, id, check) {
|
|
25
|
+
return Effect.gen(function* () {
|
|
26
|
+
if (check === undefined) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const params = new Params();
|
|
30
|
+
const aliases = new Aliases();
|
|
31
|
+
const alias = aliases.take();
|
|
32
|
+
const scope = scopeCondition(context, entity, params, aliases, alias, check);
|
|
33
|
+
const rows = yield* context.run(`SELECT 1 AS "inside" FROM ${quoteIdentifier(entity.table)} AS ${quoteIdentifier(alias)} ` +
|
|
34
|
+
`WHERE ${columnOf(alias, "id")} = ${params.add(id, "uuid")} AND (${scope})`, params);
|
|
35
|
+
if (rows.length === 0) {
|
|
36
|
+
return yield* scopeViolated(entity, check, id);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export function scopeViolated(entity, check, id, batch) {
|
|
41
|
+
return new ScopeViolated({
|
|
42
|
+
entity: entity.name,
|
|
43
|
+
...(id === undefined ? {} : { id }),
|
|
44
|
+
permission: check.permission,
|
|
45
|
+
message: `${check.permission} does not cover the ${entity.label.toLowerCase()} as the change ` +
|
|
46
|
+
"would leave it.",
|
|
47
|
+
...(batch === undefined ? {} : { batch: { indices: [...batch.indices] } }),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** Records an edit: one history row, and the edit for the action's response. */
|
|
51
|
+
function recordEdit(context, entity, edit, snapshot) {
|
|
52
|
+
return Effect.gen(function* () {
|
|
53
|
+
const params = new Params();
|
|
54
|
+
if (context.scope.execution !== undefined) {
|
|
55
|
+
const attribution = new Params();
|
|
56
|
+
yield* context.run(`INSERT INTO "${ACTION_ATTRIBUTION_TABLE}" (action_id, execution) VALUES (` +
|
|
57
|
+
`${attribution.add(context.scope.actionId, "uuid")}, ` +
|
|
58
|
+
`${attribution.add(JSON.stringify(context.scope.execution), "jsonb")}) ON CONFLICT DO NOTHING`, attribution);
|
|
59
|
+
}
|
|
60
|
+
yield* context.run(insertHistory(context.scope, { entity, edit }, params), params);
|
|
61
|
+
context.edits.push(edit);
|
|
62
|
+
if (snapshot !== undefined) {
|
|
63
|
+
context.snapshots?.push({ entity: entity.name, id: edit.id, ...snapshot });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function requireReferences(context, references) {
|
|
68
|
+
return Effect.forEach(references, (reference) => requireRecords(context, entityModel(context.model, reference.link.target), [reference.id]), { discard: true });
|
|
69
|
+
}
|
|
70
|
+
function insertRecord(context, entity, values, onConflict, check, id) {
|
|
71
|
+
return Effect.gen(function* () {
|
|
72
|
+
const recordId = id ?? uuidv7();
|
|
73
|
+
const resolved = yield* resolveBlobWrites(context, entity, recordId, values, true);
|
|
74
|
+
const write = yield* prepareWrite(entity, resolved, "create");
|
|
75
|
+
yield* requireReferences(context, write.references);
|
|
76
|
+
const params = new Params();
|
|
77
|
+
const principal = params.add(context.scope.principalId, "uuid");
|
|
78
|
+
const columns = [
|
|
79
|
+
'"id"',
|
|
80
|
+
'"created_at"',
|
|
81
|
+
'"updated_at"',
|
|
82
|
+
'"created_by"',
|
|
83
|
+
'"updated_by"',
|
|
84
|
+
...write.columns.map(({ field }) => quoteIdentifier(field.column)),
|
|
85
|
+
];
|
|
86
|
+
const placeholders = [
|
|
87
|
+
params.add(recordId, "uuid"),
|
|
88
|
+
"now()",
|
|
89
|
+
"now()",
|
|
90
|
+
principal,
|
|
91
|
+
principal,
|
|
92
|
+
...write.columns.map(({ field, value }) => params.add(bindValue(field, value), field.sqlType)),
|
|
93
|
+
];
|
|
94
|
+
const rows = yield* context.run(`INSERT INTO ${quoteIdentifier(entity.table)} AS "t0" (${columns.join(", ")}) ` +
|
|
95
|
+
`VALUES (${placeholders.join(", ")})${onConflict} ` +
|
|
96
|
+
`RETURNING ${selectList(entity, "t0")}`, params);
|
|
97
|
+
const [row] = rows;
|
|
98
|
+
if (row === undefined) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
const record = yield* decodeRecord(entity, row);
|
|
102
|
+
yield* recordEdit(context, entity, {
|
|
103
|
+
entity: entity.name,
|
|
104
|
+
id: record.id,
|
|
105
|
+
kind: "create",
|
|
106
|
+
changes: diffRecords(entity, undefined, record),
|
|
107
|
+
}, { before: undefined, after: record });
|
|
108
|
+
yield* setCollections(context, entity, record.id, write.collections);
|
|
109
|
+
yield* requireInScope(context, entity, record.id, check);
|
|
110
|
+
return record;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Creates a record; with a guard, the record must be inside its `create` scope. `id` is for the
|
|
115
|
+
* platform's own records that keep an id issued before them; every other record gets a new one.
|
|
116
|
+
*/
|
|
117
|
+
export function createRecord(context, entity, values, guard, id) {
|
|
118
|
+
return insertRecord(context, entity, values, "", guard?.create, id).pipe(Effect.map((record) => record));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Updates a record. With a check, the record must be inside its scope before the change (else
|
|
122
|
+
* NotFound) and after it (else ScopeViolated).
|
|
123
|
+
*/
|
|
124
|
+
export function updateRecord(context, entity, id, values, check) {
|
|
125
|
+
return Effect.gen(function* () {
|
|
126
|
+
const before = yield* loadRecord(context, entity, id, "update", check?.scope);
|
|
127
|
+
if (before === undefined) {
|
|
128
|
+
return yield* notFound(entity, id);
|
|
129
|
+
}
|
|
130
|
+
const resolved = yield* resolveBlobWrites(context, entity, id, values, false);
|
|
131
|
+
const write = yield* prepareWrite(entity, resolved, "update");
|
|
132
|
+
yield* requireReferences(context, write.references);
|
|
133
|
+
let after = before;
|
|
134
|
+
if (write.columns.length > 0) {
|
|
135
|
+
const params = new Params();
|
|
136
|
+
const assignments = write.columns.map(({ field, value }) => `${quoteIdentifier(field.column)} = ${params.add(bindValue(field, value), field.sqlType)}`);
|
|
137
|
+
// Only a real change touches the row, its updated_at and its history.
|
|
138
|
+
const changed = write.columns.map(({ field, value }) => `${columnOf("t0", field.column)} IS DISTINCT FROM ` +
|
|
139
|
+
params.add(bindValue(field, value), field.sqlType));
|
|
140
|
+
const rows = yield* context.run(`UPDATE ${quoteIdentifier(entity.table)} AS "t0" ` +
|
|
141
|
+
`SET ${assignments.join(", ")}, "updated_at" = now(), ` +
|
|
142
|
+
`"updated_by" = ${params.add(context.scope.principalId, "uuid")} ` +
|
|
143
|
+
`WHERE "t0"."id" = ${params.add(before.id, "uuid")} AND (${changed.join(" OR ")}) ` +
|
|
144
|
+
`RETURNING ${selectList(entity, "t0")}`, params);
|
|
145
|
+
const [row] = rows;
|
|
146
|
+
if (row !== undefined) {
|
|
147
|
+
after = yield* decodeRecord(entity, row);
|
|
148
|
+
yield* recordEdit(context, entity, {
|
|
149
|
+
entity: entity.name,
|
|
150
|
+
id: before.id,
|
|
151
|
+
kind: "update",
|
|
152
|
+
changes: diffRecords(entity, before, after),
|
|
153
|
+
}, { before, after });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
yield* setCollections(context, entity, before.id, write.collections);
|
|
157
|
+
yield* requireInScope(context, entity, before.id, check);
|
|
158
|
+
return after;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Creates the record, or updates the one with the same value of a unique property. The insert
|
|
163
|
+
* yields to an existing record with `ON CONFLICT DO NOTHING`, which also waits for a concurrent
|
|
164
|
+
* insert of the same value; the existing record is then locked and updated with the given values
|
|
165
|
+
* only, so defaults never overwrite it.
|
|
166
|
+
*/
|
|
167
|
+
export function upsertRecord(context, entity, property, values, guard) {
|
|
168
|
+
return Effect.gen(function* () {
|
|
169
|
+
const field = entity.fieldsByName.get(property);
|
|
170
|
+
const key = values[property];
|
|
171
|
+
if (field?.source !== "property" || field.property?.definition.unique !== true) {
|
|
172
|
+
return yield* Effect.die(new InvalidValues({
|
|
173
|
+
entity: entity.name,
|
|
174
|
+
message: `upsertBy needs a unique property of ${entity.name}; ${property} is not one.`,
|
|
175
|
+
issues: [{ path: [property], message: "Expected a unique property" }],
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
if (key === undefined || key === null) {
|
|
179
|
+
return yield* Effect.die(new InvalidValues({
|
|
180
|
+
entity: entity.name,
|
|
181
|
+
message: `upsertBy("${property}") needs a value of ${property}.`,
|
|
182
|
+
issues: [{ path: [property], message: `${property} is required` }],
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
// A file ticket for an existing record must not be consumed by a speculative INSERT.
|
|
186
|
+
if (entity.fields.some((candidate) => candidate.storage === "blob" && values[candidate.name] != null)) {
|
|
187
|
+
const lookup = new Params();
|
|
188
|
+
const [existing] = yield* context.run(`SELECT id::text AS id FROM ${quoteIdentifier(entity.table)}
|
|
189
|
+
WHERE ${quoteIdentifier(field.column)} = ${lookup.add(bindValue(field, key), field.sqlType)} FOR UPDATE`, lookup);
|
|
190
|
+
if (existing) {
|
|
191
|
+
const record = yield* updateRecord(context, entity, existing.id, values, guard?.change);
|
|
192
|
+
return { record, created: false };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const created = yield* insertRecord(context, entity, values, ` ON CONFLICT (${quoteIdentifier(field.column)}) DO NOTHING`, guard?.create);
|
|
196
|
+
if (created !== undefined) {
|
|
197
|
+
return { record: created, created: true };
|
|
198
|
+
}
|
|
199
|
+
const params = new Params();
|
|
200
|
+
const rows = yield* context.run(`SELECT "t0"."id"::text AS "id" FROM ${quoteIdentifier(entity.table)} AS "t0" ` +
|
|
201
|
+
`WHERE ${columnOf("t0", field.column)} = ${params.add(bindValue(field, key), field.sqlType)} ` +
|
|
202
|
+
`FOR UPDATE OF "t0"`, params);
|
|
203
|
+
const [existing] = rows;
|
|
204
|
+
if (existing === undefined) {
|
|
205
|
+
return yield* new Conflict({
|
|
206
|
+
entity: entity.name,
|
|
207
|
+
property,
|
|
208
|
+
message: `The ${entity.label.toLowerCase()} with this ${property} changed concurrently.`,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
const record = yield* updateRecord(context, entity, existing.id, values, guard?.change);
|
|
212
|
+
return { record, created: false };
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Deletes a record after applying what each relationship pointing at it asks for: `restrict`
|
|
217
|
+
* refuses, `cascade` deletes the dependent records and `setNull` clears their key, each with its
|
|
218
|
+
* own history. Join rows go too, with an unlink in the history of both records. Doing this here
|
|
219
|
+
* rather than through the foreign keys keeps every change in history.
|
|
220
|
+
*
|
|
221
|
+
* With a check, the record must be inside its scope before the delete (else NotFound), and so must
|
|
222
|
+
* every record of the same entity the delete cascades to or clears, however many relationships
|
|
223
|
+
* away (else ScopeViolated): a cascade writes the action's entity with the caller's scope too.
|
|
224
|
+
*/
|
|
225
|
+
export function deleteRecord(context, entity, id, deleting = new Set(), check) {
|
|
226
|
+
return deleteChecked(context, entity, id, deleting, check === undefined ? undefined : { entity: entity.name, check });
|
|
227
|
+
}
|
|
228
|
+
function deleteChecked(context, entity, id, deleting, cascade) {
|
|
229
|
+
return Effect.gen(function* () {
|
|
230
|
+
// A delete is checked before only: the record is gone after it.
|
|
231
|
+
const check = cascade?.entity === entity.name ? cascade.check : undefined;
|
|
232
|
+
const before = yield* loadRecord(context, entity, id, "update", check?.scope);
|
|
233
|
+
if (before === undefined) {
|
|
234
|
+
return yield* notFound(entity, id);
|
|
235
|
+
}
|
|
236
|
+
const recordId = before.id;
|
|
237
|
+
deleting.add(`${entity.name}:${recordId}`);
|
|
238
|
+
for (const link of entity.links.values()) {
|
|
239
|
+
if (link.via === "inverse-key") {
|
|
240
|
+
yield* releaseDependents(context, entity, recordId, link, deleting, cascade);
|
|
241
|
+
}
|
|
242
|
+
else if (link.via === "join") {
|
|
243
|
+
yield* removeLinks(context, entity, recordId, link);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const params = new Params();
|
|
247
|
+
yield* context.run(`DELETE FROM ${quoteIdentifier(entity.table)} WHERE "id" = ${params.add(recordId, "uuid")}`, params);
|
|
248
|
+
yield* recordEdit(context, entity, {
|
|
249
|
+
entity: entity.name,
|
|
250
|
+
id: recordId,
|
|
251
|
+
kind: "delete",
|
|
252
|
+
changes: diffRecords(entity, before, undefined),
|
|
253
|
+
}, { before, after: undefined });
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function releaseDependents(context, entity, id, link, deleting, cascade) {
|
|
257
|
+
return Effect.gen(function* () {
|
|
258
|
+
const dependent = entityModel(context.model, link.target);
|
|
259
|
+
const params = new Params();
|
|
260
|
+
const rows = yield* context.run(`SELECT "t0"."id"::text AS "id" FROM ${quoteIdentifier(dependent.table)} AS "t0" ` +
|
|
261
|
+
`WHERE ${columnOf("t0", link.column)} = ${params.add(id, "uuid")} ` +
|
|
262
|
+
`ORDER BY "t0"."id" FOR UPDATE OF "t0"`, params);
|
|
263
|
+
const ids = rows
|
|
264
|
+
.map((row) => row.id)
|
|
265
|
+
.filter((candidate) => !deleting.has(`${dependent.name}:${candidate}`));
|
|
266
|
+
if (ids.length === 0) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (link.onDelete === "restrict") {
|
|
270
|
+
return yield* new Conflict({
|
|
271
|
+
entity: entity.name,
|
|
272
|
+
message: `The ${entity.label.toLowerCase()} cannot be deleted while ` +
|
|
273
|
+
`${dependent.manifest.plural.toLowerCase()} refer to it through ${link.relationship}.`,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (cascade?.entity === dependent.name) {
|
|
277
|
+
yield* requireDependentsInScope(context, dependent, ids, link, cascade.check);
|
|
278
|
+
}
|
|
279
|
+
for (const dependentId of ids) {
|
|
280
|
+
if (link.onDelete === "cascade") {
|
|
281
|
+
yield* deleteChecked(context, dependent, dependentId, deleting, cascade);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
yield* updateRecord(context, dependent, dependentId, { [link.relationship]: null });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* The before-check of the records of the action's entity a guarded delete cascades to or clears:
|
|
291
|
+
* fails with ScopeViolated, without naming a record the caller may not see, unless all are inside
|
|
292
|
+
* the scope. The cascade's dependents of other entities are written with the action's authority.
|
|
293
|
+
*/
|
|
294
|
+
function requireDependentsInScope(context, dependent, ids, link, check) {
|
|
295
|
+
return Effect.gen(function* () {
|
|
296
|
+
const params = new Params();
|
|
297
|
+
const aliases = new Aliases();
|
|
298
|
+
const alias = aliases.take();
|
|
299
|
+
const scope = scopeCondition(context, dependent, params, aliases, alias, check);
|
|
300
|
+
const bound = ids.map((candidate) => params.add(candidate, "uuid"));
|
|
301
|
+
const rows = yield* context.run(`SELECT count(*)::int AS "inside" FROM ${quoteIdentifier(dependent.table)} AS ` +
|
|
302
|
+
`${quoteIdentifier(alias)} WHERE ${columnOf(alias, "id")} IN (${bound.join(", ")}) ` +
|
|
303
|
+
`AND (${scope})`, params);
|
|
304
|
+
if (Number(rows[0]?.inside ?? 0) < ids.length) {
|
|
305
|
+
return yield* new ScopeViolated({
|
|
306
|
+
entity: dependent.name,
|
|
307
|
+
permission: check.permission,
|
|
308
|
+
message: `${check.permission} does not cover every ${dependent.label.toLowerCase()} the delete ` +
|
|
309
|
+
`would ${link.onDelete === "cascade" ? "delete" : "change"} through ${link.relationship}.`,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
function removeLinks(context, entity, id, link) {
|
|
315
|
+
return Effect.gen(function* () {
|
|
316
|
+
const params = new Params();
|
|
317
|
+
const rows = yield* context.run(`DELETE FROM ${quoteIdentifier(link.table)} ` +
|
|
318
|
+
`WHERE ${quoteIdentifier(link.selfColumn)} = ${params.add(id, "uuid")} ` +
|
|
319
|
+
`RETURNING ${quoteIdentifier(link.otherColumn)}::text AS "other"`, params);
|
|
320
|
+
for (const row of rows) {
|
|
321
|
+
yield* recordLinkEdits(context, entity, link, id, row.other, "unlink");
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/** History of a link or an unlink on both records, each keyed by the relationship's name on its side. */
|
|
326
|
+
function recordLinkEdits(context, entity, link, id, targetId, kind) {
|
|
327
|
+
return Effect.gen(function* () {
|
|
328
|
+
const target = entityModel(context.model, link.target);
|
|
329
|
+
const change = (other) => kind === "link" ? { before: null, after: other } : { before: other, after: null };
|
|
330
|
+
yield* recordEdit(context, entity, {
|
|
331
|
+
entity: entity.name,
|
|
332
|
+
id,
|
|
333
|
+
kind,
|
|
334
|
+
changes: { [link.name]: change(targetId) },
|
|
335
|
+
});
|
|
336
|
+
yield* recordEdit(context, target, {
|
|
337
|
+
entity: target.name,
|
|
338
|
+
id: targetId,
|
|
339
|
+
kind,
|
|
340
|
+
changes: { [link.inverseName]: change(id) },
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
function addLink(context, entity, link, id, targetId) {
|
|
345
|
+
return Effect.gen(function* () {
|
|
346
|
+
const params = new Params();
|
|
347
|
+
const rows = yield* context.run(`INSERT INTO ${quoteIdentifier(link.table)} ` +
|
|
348
|
+
`(${quoteIdentifier(link.selfColumn)}, ${quoteIdentifier(link.otherColumn)}, "created_at", "created_by") ` +
|
|
349
|
+
`VALUES (${params.add(id, "uuid")}, ${params.add(targetId, "uuid")}, now(), ` +
|
|
350
|
+
`${params.add(context.scope.principalId, "uuid")}) ` +
|
|
351
|
+
`ON CONFLICT DO NOTHING RETURNING 1 AS "linked"`, params);
|
|
352
|
+
if (rows.length > 0) {
|
|
353
|
+
yield* recordLinkEdits(context, entity, link, id, targetId, "link");
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
function removeLink(context, entity, link, id, targetId) {
|
|
358
|
+
return Effect.gen(function* () {
|
|
359
|
+
const params = new Params();
|
|
360
|
+
const rows = yield* context.run(`DELETE FROM ${quoteIdentifier(link.table)} ` +
|
|
361
|
+
`WHERE ${quoteIdentifier(link.selfColumn)} = ${params.add(id, "uuid")} ` +
|
|
362
|
+
`AND ${quoteIdentifier(link.otherColumn)} = ${params.add(targetId, "uuid")} ` +
|
|
363
|
+
`RETURNING 1 AS "unlinked"`, params);
|
|
364
|
+
if (rows.length > 0) {
|
|
365
|
+
yield* recordLinkEdits(context, entity, link, id, targetId, "unlink");
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
/** Replaces `rel.many` collections: links what is new and unlinks what is gone. */
|
|
370
|
+
function setCollections(context, entity, id, collections) {
|
|
371
|
+
return Effect.forEach(collections, ({ link, ids }) => Effect.gen(function* () {
|
|
372
|
+
const params = new Params();
|
|
373
|
+
const rows = yield* context.run(`SELECT ${quoteIdentifier(link.otherColumn)}::text AS "other" FROM ${quoteIdentifier(link.table)} ` +
|
|
374
|
+
`WHERE ${quoteIdentifier(link.selfColumn)} = ${params.add(id, "uuid")}`, params);
|
|
375
|
+
const current = new Set(rows.map((row) => row.other));
|
|
376
|
+
const wanted = ids.map(normalizeId);
|
|
377
|
+
const added = wanted.filter((targetId) => !current.has(targetId));
|
|
378
|
+
const removed = [...current].filter((targetId) => !wanted.includes(targetId));
|
|
379
|
+
yield* requireRecords(context, entityModel(context.model, link.target), added);
|
|
380
|
+
for (const targetId of added) {
|
|
381
|
+
yield* addLink(context, entity, link, id, targetId);
|
|
382
|
+
}
|
|
383
|
+
for (const targetId of removed) {
|
|
384
|
+
yield* removeLink(context, entity, link, id, targetId);
|
|
385
|
+
}
|
|
386
|
+
}), { discard: true });
|
|
387
|
+
}
|
|
388
|
+
function joinLink(entity, relationship) {
|
|
389
|
+
const link = entity.links.get(relationship);
|
|
390
|
+
if (link?.via !== "join" || link.inverse) {
|
|
391
|
+
throw new Error(`${entity.name}.${relationship} is not a rel.many of ${entity.name}.`);
|
|
392
|
+
}
|
|
393
|
+
return link;
|
|
394
|
+
}
|
|
395
|
+
/** Links two records through a `rel.many` the entity declares. Linking twice changes nothing. */
|
|
396
|
+
export function linkRecords(context, entity, input, check) {
|
|
397
|
+
return Effect.gen(function* () {
|
|
398
|
+
const link = joinLink(entity, input.relationship);
|
|
399
|
+
const id = normalizeId(input.id);
|
|
400
|
+
const targetId = normalizeId(input.targetId);
|
|
401
|
+
if (check === undefined) {
|
|
402
|
+
yield* requireRecords(context, entity, [id]);
|
|
403
|
+
}
|
|
404
|
+
else if ((yield* loadRecord(context, entity, id, "update", check.scope)) === undefined) {
|
|
405
|
+
return yield* notFound(entity, id);
|
|
406
|
+
}
|
|
407
|
+
yield* requireRecords(context, entityModel(context.model, link.target), [targetId]);
|
|
408
|
+
yield* addLink(context, entity, link, id, targetId);
|
|
409
|
+
yield* requireInScope(context, entity, id, check);
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
/** Removes a link between two existing records. Unlinking records that are not linked changes nothing. */
|
|
413
|
+
export function unlinkRecords(context, entity, input, check) {
|
|
414
|
+
return Effect.gen(function* () {
|
|
415
|
+
const link = joinLink(entity, input.relationship);
|
|
416
|
+
const id = normalizeId(input.id);
|
|
417
|
+
const targetId = normalizeId(input.targetId);
|
|
418
|
+
if (check === undefined) {
|
|
419
|
+
yield* requireRecords(context, entity, [id]);
|
|
420
|
+
}
|
|
421
|
+
else if ((yield* loadRecord(context, entity, id, "update", check.scope)) === undefined) {
|
|
422
|
+
return yield* notFound(entity, id);
|
|
423
|
+
}
|
|
424
|
+
yield* requireRecords(context, entityModel(context.model, link.target), [targetId]);
|
|
425
|
+
yield* removeLink(context, entity, link, id, targetId);
|
|
426
|
+
yield* requireInScope(context, entity, id, check);
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Grants and revokes a user's roles, which the caller checked against the ontology, and records the
|
|
431
|
+
* change as an update of the user: one history row with `roles` before and after, and a new
|
|
432
|
+
* `updated_at`. A change that leaves the roles as they were writes nothing. Granting a role the
|
|
433
|
+
* user holds, or revoking one they do not, changes nothing. The record returned carries the new
|
|
434
|
+
* roles.
|
|
435
|
+
*/
|
|
436
|
+
export function changeUserRoles(context, entity, id, change) {
|
|
437
|
+
return Effect.gen(function* () {
|
|
438
|
+
if (entity.name !== User.name || !entity.fieldsByName.has(USER_ROLES_PROPERTY)) {
|
|
439
|
+
return yield* Effect.die(new Error("Only the built-in user has role assignments."));
|
|
440
|
+
}
|
|
441
|
+
const before = yield* loadRecord(context, entity, id, "update");
|
|
442
|
+
if (before === undefined) {
|
|
443
|
+
return yield* notFound(entity, id);
|
|
444
|
+
}
|
|
445
|
+
const userId = before.id;
|
|
446
|
+
const grant = [...new Set(change.grant ?? [])];
|
|
447
|
+
const revoke = change.revoke === "all"
|
|
448
|
+
? "all"
|
|
449
|
+
: [...new Set(change.revoke ?? [])].filter((role) => !grant.includes(role));
|
|
450
|
+
const table = quoteIdentifier(USER_ROLES_TABLE);
|
|
451
|
+
if (revoke === "all" || revoke.length > 0) {
|
|
452
|
+
const params = new Params();
|
|
453
|
+
const user = params.add(userId, "uuid");
|
|
454
|
+
const roles = (revoke === "all" ? grant : revoke).map((role) => params.add(role, "text"));
|
|
455
|
+
const condition = revoke === "all"
|
|
456
|
+
? roles.length === 0
|
|
457
|
+
? ""
|
|
458
|
+
: ` AND "role" NOT IN (${roles.join(", ")})`
|
|
459
|
+
: ` AND "role" IN (${roles.join(", ")})`;
|
|
460
|
+
yield* context.run(`DELETE FROM ${table} WHERE "user_id" = ${user}${condition}`, params);
|
|
461
|
+
}
|
|
462
|
+
if (grant.length > 0) {
|
|
463
|
+
const params = new Params();
|
|
464
|
+
const user = params.add(userId, "uuid");
|
|
465
|
+
const rows = grant.map((role) => `(${user}, ${params.add(role, "text")})`);
|
|
466
|
+
yield* context.run(`INSERT INTO ${table} ("user_id", "role") VALUES ${rows.join(", ")} ` +
|
|
467
|
+
`ON CONFLICT ("user_id", "role") DO NOTHING`, params);
|
|
468
|
+
}
|
|
469
|
+
const params = new Params();
|
|
470
|
+
const [row] = yield* context.run(`SELECT ${selectList(entity, "t0")} FROM ${quoteIdentifier(entity.table)} AS "t0" ` +
|
|
471
|
+
`WHERE "t0"."id" = ${params.add(userId, "uuid")}`, params);
|
|
472
|
+
const current = yield* decodeRecord(entity, row);
|
|
473
|
+
const previous = (before[USER_ROLES_PROPERTY] ?? []);
|
|
474
|
+
const next = (current[USER_ROLES_PROPERTY] ?? []);
|
|
475
|
+
if (canonicalJson(previous) === canonicalJson(next)) {
|
|
476
|
+
return current;
|
|
477
|
+
}
|
|
478
|
+
const touch = new Params();
|
|
479
|
+
const [touched] = yield* context.run(`UPDATE ${quoteIdentifier(entity.table)} AS "t0" SET "updated_at" = now(), ` +
|
|
480
|
+
`"updated_by" = ${touch.add(context.scope.principalId, "uuid")} ` +
|
|
481
|
+
`WHERE "t0"."id" = ${touch.add(userId, "uuid")} RETURNING ${selectList(entity, "t0")}`, touch);
|
|
482
|
+
const after = yield* decodeRecord(entity, touched);
|
|
483
|
+
yield* recordEdit(context, entity, {
|
|
484
|
+
entity: entity.name,
|
|
485
|
+
id: userId,
|
|
486
|
+
kind: "update",
|
|
487
|
+
changes: { [USER_ROLES_PROPERTY]: { before: previous, after: next } },
|
|
488
|
+
});
|
|
489
|
+
return after;
|
|
490
|
+
});
|
|
491
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
/** Decoded only at a verified runtime boundary; ordinary action arguments never establish provenance. */
|
|
3
|
+
export declare const RuntimeSource: Schema.Struct<{
|
|
4
|
+
readonly provider: Schema.Literal<"telegram">;
|
|
5
|
+
readonly chat_id: Schema.String;
|
|
6
|
+
readonly thread_id: Schema.String;
|
|
7
|
+
readonly message_id: Schema.optionalKey<Schema.String>;
|
|
8
|
+
readonly reply_to_message_id: Schema.optionalKey<Schema.String>;
|
|
9
|
+
readonly callback_query_id: Schema.optionalKey<Schema.String>;
|
|
10
|
+
readonly sender: Schema.optionalKey<Schema.String>;
|
|
11
|
+
readonly attachments: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
12
|
+
readonly file_id: Schema.String;
|
|
13
|
+
readonly file_unique_id: Schema.optionalKey<Schema.String>;
|
|
14
|
+
readonly kind: Schema.Literals<readonly ["photo", "document", "voice", "audio", "video", "video_note", "sticker", "animation"]>;
|
|
15
|
+
readonly media_type: Schema.optionalKey<Schema.String>;
|
|
16
|
+
readonly staged_id: Schema.String;
|
|
17
|
+
}>>>;
|
|
18
|
+
}>;
|
|
19
|
+
export type RuntimeSource = typeof RuntimeSource.Type;
|
|
20
|
+
export interface ActionRuntime {
|
|
21
|
+
readonly source: RuntimeSource;
|
|
22
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
const Id = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(512));
|
|
3
|
+
/** Decoded only at a verified runtime boundary; ordinary action arguments never establish provenance. */
|
|
4
|
+
export const RuntimeSource = Schema.Struct({
|
|
5
|
+
provider: Schema.Literal("telegram"),
|
|
6
|
+
chat_id: Id,
|
|
7
|
+
thread_id: Schema.String.check(Schema.isMaxLength(512)),
|
|
8
|
+
message_id: Schema.optionalKey(Id),
|
|
9
|
+
reply_to_message_id: Schema.optionalKey(Id),
|
|
10
|
+
callback_query_id: Schema.optionalKey(Id),
|
|
11
|
+
sender: Schema.optionalKey(Id),
|
|
12
|
+
attachments: Schema.optionalKey(Schema.Array(Schema.Struct({
|
|
13
|
+
file_id: Id,
|
|
14
|
+
file_unique_id: Schema.optionalKey(Id),
|
|
15
|
+
kind: Schema.Literals([
|
|
16
|
+
"photo",
|
|
17
|
+
"document",
|
|
18
|
+
"voice",
|
|
19
|
+
"audio",
|
|
20
|
+
"video",
|
|
21
|
+
"video_note",
|
|
22
|
+
"sticker",
|
|
23
|
+
"animation",
|
|
24
|
+
]),
|
|
25
|
+
media_type: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(128))),
|
|
26
|
+
staged_id: Id,
|
|
27
|
+
})).check(Schema.isMaxLength(10))),
|
|
28
|
+
});
|