@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,31 @@
|
|
|
1
|
+
import { quoteIdentifier } from "../migrate/storage.js";
|
|
2
|
+
/**
|
|
3
|
+
* Collects the parameters of one statement. Values are always bound as parameters, cast to the
|
|
4
|
+
* column's type; only identifiers from the definition are written into the SQL text.
|
|
5
|
+
*/
|
|
6
|
+
export class Params {
|
|
7
|
+
values = [];
|
|
8
|
+
/** Binds a value and returns its placeholder, cast when a type is given: `$3::bigint`. */
|
|
9
|
+
add(value, cast) {
|
|
10
|
+
this.values.push(value);
|
|
11
|
+
const placeholder = `$${this.values.length}`;
|
|
12
|
+
return cast === undefined ? placeholder : `${placeholder}::${cast}`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** `"t0"."owner_id"`. */
|
|
16
|
+
export function columnOf(alias, column) {
|
|
17
|
+
return `${quoteIdentifier(alias)}.${quoteIdentifier(column)}`;
|
|
18
|
+
}
|
|
19
|
+
/** Hands out table aliases, `t0`, `t1`, …, unique within one statement. */
|
|
20
|
+
export class Aliases {
|
|
21
|
+
next = 0;
|
|
22
|
+
take(prefix = "t") {
|
|
23
|
+
const alias = `${prefix}${this.next}`;
|
|
24
|
+
this.next += 1;
|
|
25
|
+
return alias;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** `%` and `_` match literally in LIKE patterns built from caller text; `\` is the escape. */
|
|
29
|
+
export function escapeLike(text) {
|
|
30
|
+
return text.replace(/[\\%_]/g, (character) => `\\${character}`);
|
|
31
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { SqlClient } from "effect/unstable/sql";
|
|
3
|
+
import { type SqlError } from "effect/unstable/sql/SqlError";
|
|
4
|
+
import type { Conflict, NotFound } from "../errors.js";
|
|
5
|
+
import type { Ontology } from "../ontology.js";
|
|
6
|
+
import type { StoredRecord } from "../runtime.js";
|
|
7
|
+
import type { UserIdentityOrigin } from "../user-identity.js";
|
|
8
|
+
import { type IdentityAccount } from "./user-identity-sql.js";
|
|
9
|
+
export * from "./user-identity-sql.js";
|
|
10
|
+
/**
|
|
11
|
+
* Whether the deployed ontology keeps `user_identity`: its table exists and the catalog declares
|
|
12
|
+
* it. The platform reads databases an earlier SDK deployed too, which have no such table, or keep
|
|
13
|
+
* one no runtime writes once an earlier SDK was deployed again; those read the legacy stores.
|
|
14
|
+
*/
|
|
15
|
+
export declare function userIdentitiesAvailable(sql: SqlClient.SqlClient): Effect.Effect<boolean, SqlError>;
|
|
16
|
+
export interface IdentityCreate extends IdentityAccount {
|
|
17
|
+
/** Keeps an id issued before the record, such as a channel account's `_agent_identities` id. */
|
|
18
|
+
readonly id?: string;
|
|
19
|
+
readonly userId: string;
|
|
20
|
+
readonly origin: UserIdentityOrigin;
|
|
21
|
+
readonly username?: string | null;
|
|
22
|
+
readonly display?: string | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The platform's writes of `user_identity`: each runs in its own savepoint of the caller's
|
|
26
|
+
* transaction and records history under `scope`, without a principal. Only these writes change the
|
|
27
|
+
* entity; the general runtime refuses to. They change `user_identity` alone: callers write the
|
|
28
|
+
* legacy stores beside them.
|
|
29
|
+
*/
|
|
30
|
+
export declare function makeIdentityWriter(ontology: Ontology, sql: SqlClient.SqlClient, scope: {
|
|
31
|
+
readonly action: string;
|
|
32
|
+
readonly actionId: string;
|
|
33
|
+
}): {
|
|
34
|
+
/** Creates a live identity; Conflict when its account or member key is held. */
|
|
35
|
+
create: (input: IdentityCreate) => Effect.Effect<StoredRecord, Conflict | NotFound>;
|
|
36
|
+
/** Revokes a live identity at `at`, freeing its keys; a revoked one changes nothing. */
|
|
37
|
+
revoke: (id: string, at: string) => Effect.Effect<StoredRecord, Conflict | NotFound>;
|
|
38
|
+
/** Records the account's current username and display; unchanged values write nothing. */
|
|
39
|
+
describe: (id: string, values: {
|
|
40
|
+
readonly username?: string | null;
|
|
41
|
+
readonly display?: string | null;
|
|
42
|
+
}) => Effect.Effect<StoredRecord, Conflict | NotFound>;
|
|
43
|
+
};
|
|
44
|
+
export type IdentityWriter = ReturnType<typeof makeIdentityWriter>;
|
|
45
|
+
/** A sign-in identity as the runtime reads it. */
|
|
46
|
+
export interface SignInIdentityRow {
|
|
47
|
+
readonly id: string;
|
|
48
|
+
readonly provider: string;
|
|
49
|
+
readonly issuer: string | null;
|
|
50
|
+
readonly subject: string;
|
|
51
|
+
}
|
|
52
|
+
/** The member's live sign-in identities: email, SegmentStream and app-token accounts. */
|
|
53
|
+
export declare function liveSignInIdentities(sql: SqlClient.SqlClient, userId: string): Effect.Effect<ReadonlyArray<SignInIdentityRow>, SqlError>;
|
|
54
|
+
/** The user the live account belongs to, or undefined; an account no key can name has none. */
|
|
55
|
+
export declare function userOfAccount(sql: SqlClient.SqlClient, account: IdentityAccount): Effect.Effect<string | undefined, SqlError>;
|
|
56
|
+
/**
|
|
57
|
+
* Records a live sign-in identity of the member unless the account is held already, by them or by
|
|
58
|
+
* another member: the caller decides what the latter means. An account no key can name is not
|
|
59
|
+
* recorded, and so never signs in.
|
|
60
|
+
*/
|
|
61
|
+
export declare function ensureSignInIdentity(writer: IdentityWriter, sql: SqlClient.SqlClient, input: IdentityAccount & {
|
|
62
|
+
readonly userId: string;
|
|
63
|
+
readonly origin: UserIdentityOrigin;
|
|
64
|
+
}): Effect.Effect<void, Conflict | NotFound | SqlError>;
|
|
65
|
+
/** Runs {@link USER_IDENTITY_SYNC_SQL} in the caller's transaction. */
|
|
66
|
+
export declare function syncUserIdentities(sql: SqlClient.SqlClient): Effect.Effect<void, SqlError>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { isSqlError } from "effect/unstable/sql/SqlError";
|
|
3
|
+
import { entityModel, ontologyModel } from "./model.js";
|
|
4
|
+
import { uniqueConflict } from "./sql-runtime.js";
|
|
5
|
+
import { accountKey, isRecordableAccount, memberKey, SIGN_IN_ORIGIN_SQL, USER_IDENTITY_ENTITY_ID, USER_IDENTITY_SYNC_SQL, USER_IDENTITY_TABLE, } from "./user-identity-sql.js";
|
|
6
|
+
import { createRecord, updateRecord } from "./writes.js";
|
|
7
|
+
export * from "./user-identity-sql.js";
|
|
8
|
+
/**
|
|
9
|
+
* Whether the deployed ontology keeps `user_identity`: its table exists and the catalog declares
|
|
10
|
+
* it. The platform reads databases an earlier SDK deployed too, which have no such table, or keep
|
|
11
|
+
* one no runtime writes once an earlier SDK was deployed again; those read the legacy stores.
|
|
12
|
+
*/
|
|
13
|
+
export function userIdentitiesAvailable(sql) {
|
|
14
|
+
return Effect.gen(function* () {
|
|
15
|
+
const [tables] = yield* sql.unsafe(`SELECT to_regclass($1) IS NOT NULL AS "identities", ` +
|
|
16
|
+
`to_regclass('"_ontology"') IS NOT NULL AS "catalog"`, [`"${USER_IDENTITY_TABLE}"`]);
|
|
17
|
+
if (tables?.identities !== true || tables.catalog !== true) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const [catalog] = yield* sql.unsafe(`SELECT EXISTS (SELECT 1 FROM "_ontology" WHERE "id" ` +
|
|
21
|
+
`AND "manifest"->'entities' @> $1::jsonb) AS "declared"`, [JSON.stringify([{ id: USER_IDENTITY_ENTITY_ID }])]);
|
|
22
|
+
return catalog?.declared === true;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The platform's writes of `user_identity`: each runs in its own savepoint of the caller's
|
|
27
|
+
* transaction and records history under `scope`, without a principal. Only these writes change the
|
|
28
|
+
* entity; the general runtime refuses to. They change `user_identity` alone: callers write the
|
|
29
|
+
* legacy stores beside them.
|
|
30
|
+
*/
|
|
31
|
+
export function makeIdentityWriter(ontology, sql, scope) {
|
|
32
|
+
const model = ontologyModel(ontology);
|
|
33
|
+
const entity = entityModel(model, USER_IDENTITY_TABLE);
|
|
34
|
+
const client = sql.withoutTransforms();
|
|
35
|
+
const context = () => ({
|
|
36
|
+
model,
|
|
37
|
+
scope: { action: scope.action, actionId: scope.actionId, principalId: null },
|
|
38
|
+
edits: [],
|
|
39
|
+
run: (text, params) => client.unsafe(text, params.values),
|
|
40
|
+
});
|
|
41
|
+
// A unique violation is a held key; any other database failure is a defect, as in the runtime.
|
|
42
|
+
const write = (operation) => sql.withTransaction(operation()).pipe(Effect.catchTag("ScopeViolated", (error) => Effect.die(error)), Effect.catchIf(isSqlError, (error) => uniqueConflict(entity, error)));
|
|
43
|
+
return {
|
|
44
|
+
/** Creates a live identity; Conflict when its account or member key is held. */
|
|
45
|
+
create: (input) => Effect.suspend(() => {
|
|
46
|
+
const values = {
|
|
47
|
+
provider: input.provider,
|
|
48
|
+
issuer: input.issuer,
|
|
49
|
+
subject: input.subject,
|
|
50
|
+
username: input.username ?? null,
|
|
51
|
+
display: input.display ?? null,
|
|
52
|
+
origin: input.origin,
|
|
53
|
+
revoked_at: null,
|
|
54
|
+
account_key: accountKey(input),
|
|
55
|
+
member_key: memberKey(input.userId, input.provider),
|
|
56
|
+
user: input.userId,
|
|
57
|
+
};
|
|
58
|
+
return write(() => createRecord(context(), entity, values, undefined, input.id));
|
|
59
|
+
}),
|
|
60
|
+
/** Revokes a live identity at `at`, freeing its keys; a revoked one changes nothing. */
|
|
61
|
+
revoke: (id, at) => write(() => updateRecord(context(), entity, id, {
|
|
62
|
+
revoked_at: at,
|
|
63
|
+
account_key: null,
|
|
64
|
+
member_key: null,
|
|
65
|
+
})),
|
|
66
|
+
/** Records the account's current username and display; unchanged values write nothing. */
|
|
67
|
+
describe: (id, values) => write(() => updateRecord(context(), entity, id, {
|
|
68
|
+
...(values.username === undefined ? {} : { username: values.username }),
|
|
69
|
+
...(values.display === undefined ? {} : { display: values.display }),
|
|
70
|
+
})),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** The member's live sign-in identities: email, SegmentStream and app-token accounts. */
|
|
74
|
+
export function liveSignInIdentities(sql, userId) {
|
|
75
|
+
return sql.unsafe(`SELECT "i"."id"::text AS "id", "i"."provider", "i"."issuer", "i"."subject" ` +
|
|
76
|
+
`FROM "${USER_IDENTITY_TABLE}" AS "i" WHERE "i"."user_id" = $1::uuid ` +
|
|
77
|
+
`AND "i"."origin"::text IN ${SIGN_IN_ORIGIN_SQL} AND "i"."revoked_at" IS NULL ` +
|
|
78
|
+
`ORDER BY "i"."created_at", "i"."id"`, [userId]);
|
|
79
|
+
}
|
|
80
|
+
/** The user the live account belongs to, or undefined; an account no key can name has none. */
|
|
81
|
+
export function userOfAccount(sql, account) {
|
|
82
|
+
if (!isRecordableAccount(account)) {
|
|
83
|
+
return Effect.succeed(undefined);
|
|
84
|
+
}
|
|
85
|
+
return sql
|
|
86
|
+
.unsafe(`SELECT "user_id"::text AS "user_id" FROM "${USER_IDENTITY_TABLE}" WHERE "account_key" = $1`, [accountKey(account)])
|
|
87
|
+
.pipe(Effect.map(([row]) => row?.user_id));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Records a live sign-in identity of the member unless the account is held already, by them or by
|
|
91
|
+
* another member: the caller decides what the latter means. An account no key can name is not
|
|
92
|
+
* recorded, and so never signs in.
|
|
93
|
+
*/
|
|
94
|
+
export function ensureSignInIdentity(writer, sql, input) {
|
|
95
|
+
return Effect.gen(function* () {
|
|
96
|
+
if (!isRecordableAccount(input) || (yield* userOfAccount(sql, input)) !== undefined) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
yield* writer.create(input);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// @legacy identities.legacy-stores-dual-write
|
|
103
|
+
/** Runs {@link USER_IDENTITY_SYNC_SQL} in the caller's transaction. */
|
|
104
|
+
export function syncUserIdentities(sql) {
|
|
105
|
+
return sql.unsafe(USER_IDENTITY_SYNC_SQL).unprepared.pipe(Effect.asVoid);
|
|
106
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { UserIdentityOrigin } from "../user-identity.js";
|
|
2
|
+
/**
|
|
3
|
+
* Members' external accounts live in the built-in `user_identity` entity. Until the contract step
|
|
4
|
+
* removes them, the stores earlier SDKs read stay written beside it in the same transaction: the
|
|
5
|
+
* member's `user.identity_provider` and `identity_subject`, `_user_identities` for SegmentStream
|
|
6
|
+
* (OIDC) bindings and `_agent_identities` for channel accounts. See
|
|
7
|
+
* docs/workspace-user-identities.md. This module holds SQL only, so that the planner can use it.
|
|
8
|
+
*/
|
|
9
|
+
export declare const USER_IDENTITY_TABLE = "user_identity";
|
|
10
|
+
/** The built-in entity's stable id, by which a manifest includes it. */
|
|
11
|
+
export declare const USER_IDENTITY_ENTITY_ID = "ent_user_identity";
|
|
12
|
+
/** The provider of email sign-in: the workspace's Neon Auth. */
|
|
13
|
+
export declare const NEON_AUTH_PROVIDER = "neon-auth";
|
|
14
|
+
/** The provider of SegmentStream (OIDC) sign-in; the issuer tells the issuing deployment apart. */
|
|
15
|
+
export declare const OIDC_PROVIDER = "oidc";
|
|
16
|
+
/** The legacy `user.identity_provider` of an OIDC binding: `oidc:<issuer>`. */
|
|
17
|
+
export declare const LEGACY_OIDC_PREFIX = "oidc:";
|
|
18
|
+
/** Origins of the identities a member signs in with, which the member's user columns mirror. */
|
|
19
|
+
export declare const SIGN_IN_ORIGINS: ReadonlyArray<UserIdentityOrigin>;
|
|
20
|
+
/** Origins of channel accounts, which `_agent_identities` mirrors with the same ids. */
|
|
21
|
+
export declare const CHANNEL_ORIGINS: ReadonlyArray<UserIdentityOrigin>;
|
|
22
|
+
/** `origin IN (…)` of sign-in identities, for SQL on the alias `i`. */
|
|
23
|
+
export declare const SIGN_IN_ORIGIN_SQL: string;
|
|
24
|
+
/** `origin IN (…)` of channel accounts, for SQL on the alias `i`. */
|
|
25
|
+
export declare const CHANNEL_ORIGIN_SQL: string;
|
|
26
|
+
/**
|
|
27
|
+
* Channel accounts as the runtime reads them: the identity `i`, beside its `_agent_identities` row
|
|
28
|
+
* `a`, which the dual write keeps with the same id. While earlier SDKs or platforms may still
|
|
29
|
+
* serve requests, a revocation one of them recorded only in `_agent_identities`, or a row it
|
|
30
|
+
* deleted there (a preview copy is cleared), ends the identity too; the next migration copies it.
|
|
31
|
+
*/
|
|
32
|
+
export declare const CHANNEL_IDENTITIES_FROM: string;
|
|
33
|
+
/** The `user_identity` record `i` is a live channel account, whatever the legacy store holds. */
|
|
34
|
+
export declare const LIVE_CHANNEL_RECORD: string;
|
|
35
|
+
/** The channel identity `i` (with `a`, see {@link CHANNEL_IDENTITIES_FROM}) is live. */
|
|
36
|
+
export declare const LIVE_CHANNEL_IDENTITY: string;
|
|
37
|
+
/**
|
|
38
|
+
* When the channel identity `i` (with `a`) stopped working: its own revocation, else the legacy
|
|
39
|
+
* store's, else, when the legacy row is gone, its last change.
|
|
40
|
+
*/
|
|
41
|
+
export declare const CHANNEL_REVOKED_AT: string;
|
|
42
|
+
/** An external account: the provider, the OIDC issuer for `oidc`, and the id at the provider. */
|
|
43
|
+
export interface IdentityAccount {
|
|
44
|
+
readonly provider: string;
|
|
45
|
+
readonly issuer: string | null;
|
|
46
|
+
readonly subject: string;
|
|
47
|
+
}
|
|
48
|
+
/** The account a legacy `identity_provider` and `identity_subject` name. */
|
|
49
|
+
export declare function accountOfLegacy(identityProvider: string, subject: string): IdentityAccount;
|
|
50
|
+
/** The legacy `identity_provider` of an account: `oidc:<issuer>`, or the provider itself. */
|
|
51
|
+
export declare function legacyProviderOf(account: IdentityAccount): string;
|
|
52
|
+
/** Whether the account can be recorded: a key part never holds whitespace, a subject is set. */
|
|
53
|
+
export declare function isRecordableAccount(account: IdentityAccount): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* The live account key: `<provider> <subject>`, or `<provider> <issuer> <subject>` for an issuer.
|
|
56
|
+
* The provider decides whether an issuer is present, and neither holds whitespace, so two accounts
|
|
57
|
+
* never share a key. {@link accountKeySql} computes the same key in SQL.
|
|
58
|
+
*/
|
|
59
|
+
export declare function accountKey(account: IdentityAccount): string;
|
|
60
|
+
/** The live member key, `<user id> <provider>`, or none for `oidc`, of which a member has several. */
|
|
61
|
+
export declare function memberKey(userId: string, provider: string): string | null;
|
|
62
|
+
/** {@link accountKey} over the columns `provider`, `issuer` and `subject` of the alias given. */
|
|
63
|
+
export declare function accountKeySql(alias: string): string;
|
|
64
|
+
/** {@link memberKey} over the columns `user_id` and `provider` of the alias given. */
|
|
65
|
+
export declare function memberKeySql(alias: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Refuses a deploy that drops email sign-in while an active member who signs in by email has no
|
|
68
|
+
* SegmentStream (OIDC) identity: they would lose access.
|
|
69
|
+
*/
|
|
70
|
+
export declare const SEGMENTSTREAM_LINK_FENCE_SQL: string;
|
|
71
|
+
/**
|
|
72
|
+
* Refuses a deploy that drops SegmentStream sign-in while an active member with a SegmentStream
|
|
73
|
+
* identity has no email sign-in: they would lose access.
|
|
74
|
+
*/
|
|
75
|
+
export declare const EMAIL_LINK_FENCE_SQL: string;
|
|
76
|
+
/**
|
|
77
|
+
* Copies what the legacy stores hold and `user_identity` lacks, idempotently, so that a database
|
|
78
|
+
* served or migrated by an earlier SDK in between is in step again. Each migration runs it, a no-op
|
|
79
|
+
* one included, after the entity's table exists. It deletes channel identities whose
|
|
80
|
+
* `_agent_identities` row is gone (a preview copy clears them), revokes those revoked there, keeps
|
|
81
|
+
* their username and display current, adds missing channel accounts with their ids, members'
|
|
82
|
+
* `identity_provider`/`identity_subject` sign-ins and `_user_identities` bindings, and copies
|
|
83
|
+
* first-contact notices. A row whose key another live identity holds is skipped rather than failing
|
|
84
|
+
* the deploy. Every change is recorded in history under `user_identity.sync`.
|
|
85
|
+
*/
|
|
86
|
+
export declare const USER_IDENTITY_SYNC_SQL: string;
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { AGENT_IDENTITIES_TABLE, IDENTITY_NOTICES_TABLE, quoteIdentifier, } from "../migrate/storage.js";
|
|
2
|
+
/**
|
|
3
|
+
* Members' external accounts live in the built-in `user_identity` entity. Until the contract step
|
|
4
|
+
* removes them, the stores earlier SDKs read stay written beside it in the same transaction: the
|
|
5
|
+
* member's `user.identity_provider` and `identity_subject`, `_user_identities` for SegmentStream
|
|
6
|
+
* (OIDC) bindings and `_agent_identities` for channel accounts. See
|
|
7
|
+
* docs/workspace-user-identities.md. This module holds SQL only, so that the planner can use it.
|
|
8
|
+
*/
|
|
9
|
+
export const USER_IDENTITY_TABLE = "user_identity";
|
|
10
|
+
/** The built-in entity's stable id, by which a manifest includes it. */
|
|
11
|
+
export const USER_IDENTITY_ENTITY_ID = "ent_user_identity";
|
|
12
|
+
/** The provider of email sign-in: the workspace's Neon Auth. */
|
|
13
|
+
export const NEON_AUTH_PROVIDER = "neon-auth";
|
|
14
|
+
/** The provider of SegmentStream (OIDC) sign-in; the issuer tells the issuing deployment apart. */
|
|
15
|
+
export const OIDC_PROVIDER = "oidc";
|
|
16
|
+
/** The legacy `user.identity_provider` of an OIDC binding: `oidc:<issuer>`. */
|
|
17
|
+
export const LEGACY_OIDC_PREFIX = "oidc:";
|
|
18
|
+
/** Origins of the identities a member signs in with, which the member's user columns mirror. */
|
|
19
|
+
export const SIGN_IN_ORIGINS = ["invite", "sign_in", "link"];
|
|
20
|
+
/** Origins of channel accounts, which `_agent_identities` mirrors with the same ids. */
|
|
21
|
+
export const CHANNEL_ORIGINS = ["pairing", "channel"];
|
|
22
|
+
const originList = (origins) => `(${origins.map((origin) => `'${origin}'`).join(", ")})`;
|
|
23
|
+
/** `origin IN (…)` of sign-in identities, for SQL on the alias `i`. */
|
|
24
|
+
export const SIGN_IN_ORIGIN_SQL = originList(SIGN_IN_ORIGINS);
|
|
25
|
+
/** `origin IN (…)` of channel accounts, for SQL on the alias `i`. */
|
|
26
|
+
export const CHANNEL_ORIGIN_SQL = originList(CHANNEL_ORIGINS);
|
|
27
|
+
/**
|
|
28
|
+
* Channel accounts as the runtime reads them: the identity `i`, beside its `_agent_identities` row
|
|
29
|
+
* `a`, which the dual write keeps with the same id. While earlier SDKs or platforms may still
|
|
30
|
+
* serve requests, a revocation one of them recorded only in `_agent_identities`, or a row it
|
|
31
|
+
* deleted there (a preview copy is cleared), ends the identity too; the next migration copies it.
|
|
32
|
+
*/
|
|
33
|
+
export const CHANNEL_IDENTITIES_FROM = `"${USER_IDENTITY_TABLE}" AS "i" LEFT JOIN "${AGENT_IDENTITIES_TABLE}" AS "a" ` +
|
|
34
|
+
`ON "a"."id" = "i"."id"`;
|
|
35
|
+
/** The `user_identity` record `i` is a live channel account, whatever the legacy store holds. */
|
|
36
|
+
export const LIVE_CHANNEL_RECORD = `"i"."origin"::text IN ${CHANNEL_ORIGIN_SQL} AND "i"."revoked_at" IS NULL`;
|
|
37
|
+
/** The channel identity `i` (with `a`, see {@link CHANNEL_IDENTITIES_FROM}) is live. */
|
|
38
|
+
export const LIVE_CHANNEL_IDENTITY = `${LIVE_CHANNEL_RECORD} AND "a"."id" IS NOT NULL AND "a"."revoked_at" IS NULL`;
|
|
39
|
+
/**
|
|
40
|
+
* When the channel identity `i` (with `a`) stopped working: its own revocation, else the legacy
|
|
41
|
+
* store's, else, when the legacy row is gone, its last change.
|
|
42
|
+
*/
|
|
43
|
+
export const CHANNEL_REVOKED_AT = `COALESCE("i"."revoked_at", "a"."revoked_at", ` +
|
|
44
|
+
`CASE WHEN "a"."id" IS NULL THEN "i"."updated_at" END)`;
|
|
45
|
+
/** The account a legacy `identity_provider` and `identity_subject` name. */
|
|
46
|
+
export function accountOfLegacy(identityProvider, subject) {
|
|
47
|
+
return identityProvider.startsWith(LEGACY_OIDC_PREFIX)
|
|
48
|
+
? {
|
|
49
|
+
provider: OIDC_PROVIDER,
|
|
50
|
+
issuer: identityProvider.slice(LEGACY_OIDC_PREFIX.length),
|
|
51
|
+
subject,
|
|
52
|
+
}
|
|
53
|
+
: { provider: identityProvider, issuer: null, subject };
|
|
54
|
+
}
|
|
55
|
+
/** The legacy `identity_provider` of an account: `oidc:<issuer>`, or the provider itself. */
|
|
56
|
+
export function legacyProviderOf(account) {
|
|
57
|
+
return account.provider === OIDC_PROVIDER
|
|
58
|
+
? `${LEGACY_OIDC_PREFIX}${account.issuer ?? ""}`
|
|
59
|
+
: account.provider;
|
|
60
|
+
}
|
|
61
|
+
/** Providers and issuers never hold whitespace, so the space-separated keys are unambiguous. */
|
|
62
|
+
function isKeyPart(value) {
|
|
63
|
+
return value !== "" && !/\s/.test(value);
|
|
64
|
+
}
|
|
65
|
+
function requireKeyPart(value, what) {
|
|
66
|
+
if (!isKeyPart(value)) {
|
|
67
|
+
throw new Error(`An identity ${what} must be non-empty and hold no whitespace.`);
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
/** Whether the account can be recorded: a key part never holds whitespace, a subject is set. */
|
|
72
|
+
export function isRecordableAccount(account) {
|
|
73
|
+
return (isKeyPart(account.provider) &&
|
|
74
|
+
(account.issuer === null || isKeyPart(account.issuer)) &&
|
|
75
|
+
account.subject !== "");
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The live account key: `<provider> <subject>`, or `<provider> <issuer> <subject>` for an issuer.
|
|
79
|
+
* The provider decides whether an issuer is present, and neither holds whitespace, so two accounts
|
|
80
|
+
* never share a key. {@link accountKeySql} computes the same key in SQL.
|
|
81
|
+
*/
|
|
82
|
+
export function accountKey(account) {
|
|
83
|
+
const provider = requireKeyPart(account.provider, "provider");
|
|
84
|
+
return account.issuer === null
|
|
85
|
+
? `${provider} ${account.subject}`
|
|
86
|
+
: `${provider} ${requireKeyPart(account.issuer, "issuer")} ${account.subject}`;
|
|
87
|
+
}
|
|
88
|
+
/** The live member key, `<user id> <provider>`, or none for `oidc`, of which a member has several. */
|
|
89
|
+
export function memberKey(userId, provider) {
|
|
90
|
+
return provider === OIDC_PROVIDER ? null : `${userId} ${provider}`;
|
|
91
|
+
}
|
|
92
|
+
/** {@link accountKey} over the columns `provider`, `issuer` and `subject` of the alias given. */
|
|
93
|
+
export function accountKeySql(alias) {
|
|
94
|
+
return (`(${alias}."provider" || ' ' || COALESCE(${alias}."issuer" || ' ', '') || ` +
|
|
95
|
+
`${alias}."subject")`);
|
|
96
|
+
}
|
|
97
|
+
/** {@link memberKey} over the columns `user_id` and `provider` of the alias given. */
|
|
98
|
+
export function memberKeySql(alias) {
|
|
99
|
+
return (`(CASE WHEN ${alias}."provider" = '${OIDC_PROVIDER}' THEN NULL ` +
|
|
100
|
+
`ELSE ${alias}."user_id"::text || ' ' || ${alias}."provider" END)`);
|
|
101
|
+
}
|
|
102
|
+
/** The active member `u` has a live identity of `provider` that `extra` (SQL on `i`) narrows. */
|
|
103
|
+
const HOLDS = (provider, extra = "") => `EXISTS (SELECT 1 FROM "${USER_IDENTITY_TABLE}" i WHERE i.user_id = u.id ` +
|
|
104
|
+
`AND i.provider = '${provider}' AND i.revoked_at IS NULL${extra})`;
|
|
105
|
+
const ACTIVE_MEMBER = `u.kind::text = 'member' AND u.active = true`;
|
|
106
|
+
/**
|
|
107
|
+
* Refuses a deploy that drops email sign-in while an active member who signs in by email has no
|
|
108
|
+
* SegmentStream (OIDC) identity: they would lose access.
|
|
109
|
+
*/
|
|
110
|
+
export const SEGMENTSTREAM_LINK_FENCE_SQL = `DO $$ BEGIN
|
|
111
|
+
IF EXISTS (SELECT 1 FROM "user" u WHERE ${ACTIVE_MEMBER}
|
|
112
|
+
AND ${HOLDS(NEON_AUTH_PROVIDER, ` AND i.origin::text IN ${SIGN_IN_ORIGIN_SQL}`)}
|
|
113
|
+
AND NOT ${HOLDS(OIDC_PROVIDER)})
|
|
114
|
+
THEN RAISE EXCEPTION 'browser_auth_segmentstream_link_required'; END IF;
|
|
115
|
+
END $$`;
|
|
116
|
+
/**
|
|
117
|
+
* Refuses a deploy that drops SegmentStream sign-in while an active member with a SegmentStream
|
|
118
|
+
* identity has no email sign-in: they would lose access.
|
|
119
|
+
*/
|
|
120
|
+
export const EMAIL_LINK_FENCE_SQL = `DO $$ BEGIN
|
|
121
|
+
IF EXISTS (SELECT 1 FROM "user" u WHERE ${ACTIVE_MEMBER} AND ${HOLDS(OIDC_PROVIDER)}
|
|
122
|
+
AND NOT ${HOLDS(NEON_AUTH_PROVIDER, ` AND i.origin::text IN ${SIGN_IN_ORIGIN_SQL}`)})
|
|
123
|
+
THEN RAISE EXCEPTION 'browser_auth_email_link_required'; END IF;
|
|
124
|
+
END $$`;
|
|
125
|
+
const HISTORY = quoteIdentifier(`${USER_IDENTITY_TABLE}_history`);
|
|
126
|
+
const IDENTITIES = quoteIdentifier(USER_IDENTITY_TABLE);
|
|
127
|
+
const NOTICES = quoteIdentifier(IDENTITY_NOTICES_TABLE);
|
|
128
|
+
const AGENT = quoteIdentifier(AGENT_IDENTITIES_TABLE);
|
|
129
|
+
const SYNC_ACTION = `'${USER_IDENTITY_TABLE}.sync'`;
|
|
130
|
+
const INSTANT = (column) => `to_char(${column} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`;
|
|
131
|
+
const CHANGE = (field, before, after) => `CASE WHEN (${before}) IS DISTINCT FROM (${after}) THEN jsonb_build_object('${field}', ` +
|
|
132
|
+
`jsonb_build_object('before', to_jsonb(${before}), 'after', to_jsonb(${after}))) ` +
|
|
133
|
+
`ELSE '{}'::jsonb END`;
|
|
134
|
+
/** The history `changes` of a record created from nothing, over the alias `r`. */
|
|
135
|
+
const CREATED = [
|
|
136
|
+
CHANGE("provider", "NULL::text", "r.provider"),
|
|
137
|
+
CHANGE("issuer", "NULL::text", "r.issuer"),
|
|
138
|
+
CHANGE("subject", "NULL::text", "r.subject"),
|
|
139
|
+
CHANGE("username", "NULL::text", "r.username"),
|
|
140
|
+
CHANGE("display", "NULL::text", "r.display"),
|
|
141
|
+
CHANGE("origin", "NULL::text", "r.origin::text"),
|
|
142
|
+
CHANGE("revoked_at", "NULL::text", INSTANT("r.revoked_at")),
|
|
143
|
+
CHANGE("account_key", "NULL::text", "r.account_key"),
|
|
144
|
+
CHANGE("member_key", "NULL::text", "r.member_key"),
|
|
145
|
+
CHANGE("user_id", "NULL::text", "r.user_id::text"),
|
|
146
|
+
].join(" || ");
|
|
147
|
+
const RECORD_HISTORY = (kind, changes, from) => `INSERT INTO ${HISTORY} ("id", "record_id", "action", "action_id", "kind", "principal_id", ` +
|
|
148
|
+
`"at", "changes") SELECT gen_random_uuid(), r.id, ${SYNC_ACTION}, sync_action, '${kind}', ` +
|
|
149
|
+
`NULL, now(), ${changes} FROM ${from} r`;
|
|
150
|
+
const INSERT_COLUMNS = `"id", "created_at", "updated_at", "created_by", "updated_by", "provider", "issuer", ` +
|
|
151
|
+
`"subject", "username", "display", "origin", "revoked_at", "account_key", "member_key", "user_id"`;
|
|
152
|
+
/** Inserts the rows of `source` (columns as {@link INSERT_COLUMNS}) whose keys are free. */
|
|
153
|
+
const INSERT_FREE = (source) => `WITH source AS (${source}), keyed AS (` +
|
|
154
|
+
`SELECT s.*, CASE WHEN s.revoked_at IS NULL THEN ${accountKeySql("s")} END AS account_key, ` +
|
|
155
|
+
`CASE WHEN s.revoked_at IS NULL THEN ${memberKeySql("s")} END AS member_key FROM source s), ` +
|
|
156
|
+
`free AS (SELECT k.* FROM keyed k ` +
|
|
157
|
+
`WHERE NOT EXISTS (SELECT 1 FROM ${IDENTITIES} i WHERE i.id = k.id ` +
|
|
158
|
+
`OR i.account_key = k.account_key OR i.member_key = k.member_key)), ` +
|
|
159
|
+
`inserted AS (INSERT INTO ${IDENTITIES} (${INSERT_COLUMNS}) ` +
|
|
160
|
+
`SELECT f.id, f.created_at, now(), NULL, NULL, f.provider, f.issuer, f.subject, f.username, ` +
|
|
161
|
+
`f.display, f.origin::"${USER_IDENTITY_TABLE}_origin", f.revoked_at, f.account_key, ` +
|
|
162
|
+
`f.member_key, f.user_id FROM free f RETURNING *) ` +
|
|
163
|
+
RECORD_HISTORY("create", CREATED, "inserted");
|
|
164
|
+
/**
|
|
165
|
+
* Whether the member claimed an invitation: as `_channel_users` records it since it exists, or as the
|
|
166
|
+
* history of `user.invite` does. A statement never names a table the database lacks, even one it
|
|
167
|
+
* would not read, so each variant is generated on its own.
|
|
168
|
+
*/
|
|
169
|
+
const INVITED = (userId, channelUsers) => `(${channelUsers
|
|
170
|
+
? `EXISTS (SELECT 1 FROM "_channel_users" c WHERE c.user_id = ${userId} ` +
|
|
171
|
+
`AND c.created_via = 'invite') OR `
|
|
172
|
+
: ""}EXISTS (SELECT 1 FROM "user_history" h WHERE h.record_id = ${userId} ` +
|
|
173
|
+
`AND h.action = 'user.invite' AND h.kind = 'create'))`;
|
|
174
|
+
/** Members' `identity_provider` and `identity_subject`: `oidc:<issuer>` is an `oidc` account. */
|
|
175
|
+
const MEMBER_SIGN_INS = (channelUsers) => INSERT_FREE(`SELECT gen_random_uuid() AS id, now() AS created_at, l.provider, l.issuer, l.subject,
|
|
176
|
+
NULL::text AS username, NULL::text AS display,
|
|
177
|
+
CASE WHEN ${INVITED("l.user_id", channelUsers)} THEN 'invite' ELSE 'sign_in' END AS origin,
|
|
178
|
+
NULL::timestamptz AS revoked_at, l.user_id
|
|
179
|
+
FROM (SELECT u.id AS user_id,
|
|
180
|
+
CASE WHEN u.identity_provider LIKE '${LEGACY_OIDC_PREFIX}%' THEN '${OIDC_PROVIDER}'
|
|
181
|
+
ELSE u.identity_provider END AS provider,
|
|
182
|
+
CASE WHEN u.identity_provider LIKE '${LEGACY_OIDC_PREFIX}%'
|
|
183
|
+
THEN substr(u.identity_provider, ${LEGACY_OIDC_PREFIX.length + 1}) END AS issuer,
|
|
184
|
+
u.identity_subject AS subject
|
|
185
|
+
FROM "user" u WHERE u.kind::text = 'member' AND u.identity_provider IS NOT NULL
|
|
186
|
+
AND u.identity_provider NOT IN ('', '${LEGACY_OIDC_PREFIX}')
|
|
187
|
+
AND u.identity_provider !~ '\\s' AND COALESCE(u.identity_subject, '') <> '') l
|
|
188
|
+
WHERE NOT EXISTS (SELECT 1 FROM ${IDENTITIES} i WHERE i.provider = l.provider
|
|
189
|
+
AND i.issuer IS NOT DISTINCT FROM l.issuer AND i.subject = l.subject)`);
|
|
190
|
+
/**
|
|
191
|
+
* `_user_identities` bindings. One the member's user columns or their history held is how they
|
|
192
|
+
* signed in; any other was linked to a signed-in member.
|
|
193
|
+
*/
|
|
194
|
+
const OIDC_BINDINGS = (channelUsers) => INSERT_FREE(`SELECT gen_random_uuid() AS id, now() AS created_at, '${OIDC_PROVIDER}'::text AS provider,
|
|
195
|
+
b.issuer, b.subject, NULL::text AS username, NULL::text AS display,
|
|
196
|
+
CASE WHEN NOT EXISTS (SELECT 1 FROM "user_history" h WHERE h.record_id = b.user_id
|
|
197
|
+
AND h.changes->'identity_provider'->>'after' = '${LEGACY_OIDC_PREFIX}' || b.issuer
|
|
198
|
+
AND h.changes->'identity_subject'->>'after' = b.subject) THEN 'link'
|
|
199
|
+
WHEN ${INVITED("b.user_id", channelUsers)} THEN 'invite'
|
|
200
|
+
ELSE 'sign_in' END AS origin,
|
|
201
|
+
NULL::timestamptz AS revoked_at, b.user_id
|
|
202
|
+
FROM "_user_identities" b JOIN "user" u ON u.id = b.user_id
|
|
203
|
+
WHERE u.kind::text = 'member' AND b.issuer !~ '\\s' AND b.issuer <> ''
|
|
204
|
+
AND NOT EXISTS (SELECT 1 FROM ${IDENTITIES} i WHERE i.provider = '${OIDC_PROVIDER}'
|
|
205
|
+
AND i.issuer = b.issuer AND i.subject = b.subject)`);
|
|
206
|
+
/**
|
|
207
|
+
* Copies what the legacy stores hold and `user_identity` lacks, idempotently, so that a database
|
|
208
|
+
* served or migrated by an earlier SDK in between is in step again. Each migration runs it, a no-op
|
|
209
|
+
* one included, after the entity's table exists. It deletes channel identities whose
|
|
210
|
+
* `_agent_identities` row is gone (a preview copy clears them), revokes those revoked there, keeps
|
|
211
|
+
* their username and display current, adds missing channel accounts with their ids, members'
|
|
212
|
+
* `identity_provider`/`identity_subject` sign-ins and `_user_identities` bindings, and copies
|
|
213
|
+
* first-contact notices. A row whose key another live identity holds is skipped rather than failing
|
|
214
|
+
* the deploy. Every change is recorded in history under `user_identity.sync`.
|
|
215
|
+
*/
|
|
216
|
+
export const USER_IDENTITY_SYNC_SQL = `DO $sync$
|
|
217
|
+
DECLARE
|
|
218
|
+
sync_action uuid := gen_random_uuid();
|
|
219
|
+
channel_users boolean := to_regclass('_channel_users') IS NOT NULL;
|
|
220
|
+
BEGIN
|
|
221
|
+
IF to_regclass('${USER_IDENTITY_TABLE}') IS NULL OR to_regclass('${USER_IDENTITY_TABLE}_history') IS NULL THEN
|
|
222
|
+
RETURN;
|
|
223
|
+
END IF;
|
|
224
|
+
IF to_regclass('${AGENT_IDENTITIES_TABLE}') IS NOT NULL THEN
|
|
225
|
+
WITH removed AS (
|
|
226
|
+
DELETE FROM ${IDENTITIES} i WHERE i.origin::text IN ${CHANNEL_ORIGIN_SQL}
|
|
227
|
+
AND NOT EXISTS (SELECT 1 FROM ${AGENT} a WHERE a.id = i.id)
|
|
228
|
+
RETURNING i.*
|
|
229
|
+
) ${RECORD_HISTORY("delete", [
|
|
230
|
+
CHANGE("provider", "r.provider", "NULL::text"),
|
|
231
|
+
CHANGE("issuer", "r.issuer", "NULL::text"),
|
|
232
|
+
CHANGE("subject", "r.subject", "NULL::text"),
|
|
233
|
+
CHANGE("username", "r.username", "NULL::text"),
|
|
234
|
+
CHANGE("display", "r.display", "NULL::text"),
|
|
235
|
+
CHANGE("origin", "r.origin::text", "NULL::text"),
|
|
236
|
+
CHANGE("revoked_at", INSTANT("r.revoked_at"), "NULL::text"),
|
|
237
|
+
CHANGE("account_key", "r.account_key", "NULL::text"),
|
|
238
|
+
CHANGE("member_key", "r.member_key", "NULL::text"),
|
|
239
|
+
CHANGE("user_id", "r.user_id::text", "NULL::text"),
|
|
240
|
+
].join(" || "), "removed")};
|
|
241
|
+
WITH stale AS (
|
|
242
|
+
SELECT i.id, i.account_key, i.member_key, a.revoked_at FROM ${IDENTITIES} i
|
|
243
|
+
JOIN ${AGENT} a ON a.id = i.id
|
|
244
|
+
WHERE a.revoked_at IS NOT NULL AND i.revoked_at IS NULL
|
|
245
|
+
FOR UPDATE OF i
|
|
246
|
+
), revoked AS (
|
|
247
|
+
UPDATE ${IDENTITIES} i SET revoked_at = s.revoked_at, account_key = NULL, member_key = NULL,
|
|
248
|
+
updated_at = now(), updated_by = NULL
|
|
249
|
+
FROM stale s WHERE i.id = s.id RETURNING i.id
|
|
250
|
+
) ${RECORD_HISTORY("update", [
|
|
251
|
+
CHANGE("revoked_at", "NULL::text", INSTANT("r.revoked_at")),
|
|
252
|
+
CHANGE("account_key", "r.account_key", "NULL::text"),
|
|
253
|
+
CHANGE("member_key", "r.member_key", "NULL::text"),
|
|
254
|
+
].join(" || "), "stale")};
|
|
255
|
+
WITH described AS (
|
|
256
|
+
SELECT i.id, i.username AS old_username, i.display AS old_display,
|
|
257
|
+
a.username, a.display FROM ${IDENTITIES} i JOIN ${AGENT} a ON a.id = i.id
|
|
258
|
+
WHERE i.revoked_at IS NULL AND a.revoked_at IS NULL
|
|
259
|
+
AND (i.username IS DISTINCT FROM a.username OR i.display IS DISTINCT FROM a.display)
|
|
260
|
+
FOR UPDATE OF i
|
|
261
|
+
), updated AS (
|
|
262
|
+
UPDATE ${IDENTITIES} i SET username = d.username, display = d.display, updated_at = now(),
|
|
263
|
+
updated_by = NULL FROM described d WHERE i.id = d.id RETURNING i.id
|
|
264
|
+
) ${RECORD_HISTORY("update", [
|
|
265
|
+
CHANGE("username", "r.old_username", "r.username"),
|
|
266
|
+
CHANGE("display", "r.old_display", "r.display"),
|
|
267
|
+
].join(" || "), "described")};
|
|
268
|
+
${INSERT_FREE(`SELECT a.id, a.created_at, a.provider, NULL::text AS issuer, a.external_id AS subject,
|
|
269
|
+
a.username, a.display,
|
|
270
|
+
CASE WHEN a.origin = 'provisioned' THEN 'channel' ELSE 'pairing' END AS origin,
|
|
271
|
+
a.revoked_at, a.user_id FROM ${AGENT} a`)};
|
|
272
|
+
END IF;
|
|
273
|
+
IF channel_users THEN
|
|
274
|
+
${MEMBER_SIGN_INS(true)};
|
|
275
|
+
ELSE
|
|
276
|
+
${MEMBER_SIGN_INS(false)};
|
|
277
|
+
END IF;
|
|
278
|
+
IF to_regclass('_user_identities') IS NOT NULL THEN
|
|
279
|
+
IF channel_users THEN
|
|
280
|
+
${OIDC_BINDINGS(true)};
|
|
281
|
+
ELSE
|
|
282
|
+
${OIDC_BINDINGS(false)};
|
|
283
|
+
END IF;
|
|
284
|
+
END IF;
|
|
285
|
+
IF to_regclass('${IDENTITY_NOTICES_TABLE}') IS NOT NULL THEN
|
|
286
|
+
IF to_regclass('${AGENT_IDENTITIES_TABLE}') IS NOT NULL THEN
|
|
287
|
+
INSERT INTO ${NOTICES} (identity_id, noticed_at, notice_sha256, notice_version)
|
|
288
|
+
SELECT a.id, a.noticed_at, a.notice_sha256, a.notice_version FROM ${AGENT} a
|
|
289
|
+
WHERE a.noticed_at IS NOT NULL AND EXISTS (SELECT 1 FROM ${IDENTITIES} i WHERE i.id = a.id)
|
|
290
|
+
ON CONFLICT (identity_id) DO UPDATE SET noticed_at = EXCLUDED.noticed_at,
|
|
291
|
+
notice_sha256 = EXCLUDED.notice_sha256, notice_version = EXCLUDED.notice_version
|
|
292
|
+
WHERE (${NOTICES}.noticed_at, ${NOTICES}.notice_sha256, ${NOTICES}.notice_version)
|
|
293
|
+
IS DISTINCT FROM (EXCLUDED.noticed_at, EXCLUDED.notice_sha256, EXCLUDED.notice_version);
|
|
294
|
+
END IF;
|
|
295
|
+
DELETE FROM ${NOTICES} n WHERE NOT EXISTS (SELECT 1 FROM ${IDENTITIES} i WHERE i.id = n.identity_id);
|
|
296
|
+
END IF;
|
|
297
|
+
END $sync$`;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The built-in user's `roles`: the roles assigned to the user in `_user_roles`, sorted, without the
|
|
3
|
+
* `member` every active member holds implicitly. It is the only `list` property, and has no column.
|
|
4
|
+
*/
|
|
5
|
+
export declare const USER_ROLES_PROPERTY = "roles";
|
|
6
|
+
/**
|
|
7
|
+
* The roles of the user whose id is the SQL expression `userId`, as a JSON array: sorted, without
|
|
8
|
+
* `member`, `[]` when none. As a subquery of the statement that reads the user, it is consistent
|
|
9
|
+
* with the row it reads.
|
|
10
|
+
*/
|
|
11
|
+
export declare function userRolesExpression(userId: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Whether the user whose id is the SQL expression `userId` is assigned one of the roles, given as
|
|
14
|
+
* SQL expressions such as parameters. `member` is never listed, so it matches no user.
|
|
15
|
+
*/
|
|
16
|
+
export declare function userHoldsRoleCondition(userId: string, roles: ReadonlyArray<string>): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { USER_ROLES_TABLE, quoteIdentifier, quoteLiteral } from "../migrate/storage.js";
|
|
2
|
+
import { MEMBER_ROLE } from "../names.js";
|
|
3
|
+
/**
|
|
4
|
+
* The built-in user's `roles`: the roles assigned to the user in `_user_roles`, sorted, without the
|
|
5
|
+
* `member` every active member holds implicitly. It is the only `list` property, and has no column.
|
|
6
|
+
*/
|
|
7
|
+
export const USER_ROLES_PROPERTY = "roles";
|
|
8
|
+
const ROLES = quoteIdentifier(USER_ROLES_TABLE);
|
|
9
|
+
/** Unlikely to shadow an alias of the enclosing statement, whose aliases are `t0`, `t1`, `j0`… */
|
|
10
|
+
const ASSIGNED = '"_assigned_role"';
|
|
11
|
+
const NOT_MEMBER = `${ASSIGNED}."role" <> ${quoteLiteral(MEMBER_ROLE)}`;
|
|
12
|
+
/**
|
|
13
|
+
* The roles of the user whose id is the SQL expression `userId`, as a JSON array: sorted, without
|
|
14
|
+
* `member`, `[]` when none. As a subquery of the statement that reads the user, it is consistent
|
|
15
|
+
* with the row it reads.
|
|
16
|
+
*/
|
|
17
|
+
export function userRolesExpression(userId) {
|
|
18
|
+
return (`COALESCE((SELECT json_agg(${ASSIGNED}."role" ORDER BY ${ASSIGNED}."role") ` +
|
|
19
|
+
`FROM ${ROLES} AS ${ASSIGNED} WHERE ${ASSIGNED}."user_id" = ${userId} AND ${NOT_MEMBER}), ` +
|
|
20
|
+
`'[]'::json)`);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Whether the user whose id is the SQL expression `userId` is assigned one of the roles, given as
|
|
24
|
+
* SQL expressions such as parameters. `member` is never listed, so it matches no user.
|
|
25
|
+
*/
|
|
26
|
+
export function userHoldsRoleCondition(userId, roles) {
|
|
27
|
+
if (roles.length === 0) {
|
|
28
|
+
return "FALSE";
|
|
29
|
+
}
|
|
30
|
+
return (`EXISTS (SELECT 1 FROM ${ROLES} AS ${ASSIGNED} WHERE ${ASSIGNED}."user_id" = ${userId} ` +
|
|
31
|
+
`AND ${NOT_MEMBER} AND ${ASSIGNED}."role" IN (${roles.join(", ")}))`);
|
|
32
|
+
}
|