@homeflare/alchemy 0.43.0 → 0.45.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/README.md +10 -1
- package/dist/caddy/index.js +35 -1
- package/dist/caddy/index.js.map +3 -3
- package/dist/launchd/index.js +35 -1
- package/dist/launchd/index.js.map +3 -3
- package/dist/linux/index.js +35 -1
- package/dist/linux/index.js.map +3 -3
- package/dist/litellm/access-group-form.d.ts +26 -0
- package/dist/litellm/access-group-form.d.ts.map +1 -0
- package/dist/litellm/access-group-operations.d.ts +37 -0
- package/dist/litellm/access-group-operations.d.ts.map +1 -0
- package/dist/litellm/access-group-types.d.ts +49 -0
- package/dist/litellm/access-group-types.d.ts.map +1 -0
- package/dist/litellm/access-group.d.ts +55 -0
- package/dist/litellm/access-group.d.ts.map +1 -0
- package/dist/litellm/credential-errors.d.ts +131 -0
- package/dist/litellm/credential-errors.d.ts.map +1 -0
- package/dist/litellm/credential-form.d.ts +79 -0
- package/dist/litellm/credential-form.d.ts.map +1 -0
- package/dist/litellm/credential-operations.d.ts +84 -0
- package/dist/litellm/credential-operations.d.ts.map +1 -0
- package/dist/litellm/credential-rewrite.d.ts +6 -0
- package/dist/litellm/credential-rewrite.d.ts.map +1 -0
- package/dist/litellm/credential-types.d.ts +87 -0
- package/dist/litellm/credential-types.d.ts.map +1 -0
- package/dist/litellm/credential-values.d.ts +53 -0
- package/dist/litellm/credential-values.d.ts.map +1 -0
- package/dist/litellm/credential.d.ts +83 -0
- package/dist/litellm/credential.d.ts.map +1 -0
- package/dist/litellm/fake-access-group-litellm.d.ts +34 -0
- package/dist/litellm/fake-access-group-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-credential-litellm.d.ts +54 -0
- package/dist/litellm/fake-credential-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-keys.d.ts +53 -0
- package/dist/litellm/fake-keys.d.ts.map +1 -0
- package/dist/litellm/fake-litellm.d.ts +4 -1
- package/dist/litellm/fake-litellm.d.ts.map +1 -1
- package/dist/litellm/fake-mcp-litellm.d.ts +67 -0
- package/dist/litellm/fake-mcp-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-model-litellm-post-update.d.ts +49 -0
- package/dist/litellm/fake-model-litellm-post-update.d.ts.map +1 -0
- package/dist/litellm/fake-model-litellm.d.ts +48 -0
- package/dist/litellm/fake-model-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-policy-attachment-litellm.d.ts +33 -0
- package/dist/litellm/fake-policy-attachment-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-policy-litellm.d.ts +32 -0
- package/dist/litellm/fake-policy-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-registry-base.d.ts +42 -0
- package/dist/litellm/fake-registry-base.d.ts.map +1 -0
- package/dist/litellm/fake-stack.d.ts +8 -1
- package/dist/litellm/fake-stack.d.ts.map +1 -1
- package/dist/litellm/fake-team-litellm.d.ts +47 -0
- package/dist/litellm/fake-team-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-tool-litellm.d.ts +28 -0
- package/dist/litellm/fake-tool-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-toolset-litellm.d.ts +29 -0
- package/dist/litellm/fake-toolset-litellm.d.ts.map +1 -0
- package/dist/litellm/index.d.ts +18 -0
- package/dist/litellm/index.d.ts.map +1 -1
- package/dist/litellm/index.js +3078 -2
- package/dist/litellm/index.js.map +65 -4
- package/dist/litellm/key-errors.d.ts +169 -0
- package/dist/litellm/key-errors.d.ts.map +1 -0
- package/dist/litellm/key-form.d.ts +119 -0
- package/dist/litellm/key-form.d.ts.map +1 -0
- package/dist/litellm/key-harness.d.ts +41 -0
- package/dist/litellm/key-harness.d.ts.map +1 -0
- package/dist/litellm/key-metadata-errors.d.ts +30 -0
- package/dist/litellm/key-metadata-errors.d.ts.map +1 -0
- package/dist/litellm/key-metadata.d.ts +22 -0
- package/dist/litellm/key-metadata.d.ts.map +1 -0
- package/dist/litellm/key-operations.d.ts +63 -0
- package/dist/litellm/key-operations.d.ts.map +1 -0
- package/dist/litellm/key-secret.d.ts +33 -0
- package/dist/litellm/key-secret.d.ts.map +1 -0
- package/dist/litellm/key.d.ts +78 -0
- package/dist/litellm/key.d.ts.map +1 -0
- package/dist/litellm/mcp-server-credential.d.ts +50 -0
- package/dist/litellm/mcp-server-credential.d.ts.map +1 -0
- package/dist/litellm/mcp-server-errors.d.ts +101 -0
- package/dist/litellm/mcp-server-errors.d.ts.map +1 -0
- package/dist/litellm/mcp-server-form.d.ts +56 -0
- package/dist/litellm/mcp-server-form.d.ts.map +1 -0
- package/dist/litellm/mcp-server-locate.d.ts +26 -0
- package/dist/litellm/mcp-server-locate.d.ts.map +1 -0
- package/dist/litellm/mcp-server-operations.d.ts +63 -0
- package/dist/litellm/mcp-server-operations.d.ts.map +1 -0
- package/dist/litellm/mcp-server-shadow.d.ts +24 -0
- package/dist/litellm/mcp-server-shadow.d.ts.map +1 -0
- package/dist/litellm/mcp-server-types.d.ts +118 -0
- package/dist/litellm/mcp-server-types.d.ts.map +1 -0
- package/dist/litellm/mcp-server-url.d.ts +11 -0
- package/dist/litellm/mcp-server-url.d.ts.map +1 -0
- package/dist/litellm/mcp-server.d.ts +95 -0
- package/dist/litellm/mcp-server.d.ts.map +1 -0
- package/dist/litellm/model-claim.d.ts +24 -0
- package/dist/litellm/model-claim.d.ts.map +1 -0
- package/dist/litellm/model-credential.d.ts +12 -0
- package/dist/litellm/model-credential.d.ts.map +1 -0
- package/dist/litellm/model-errors.d.ts +119 -0
- package/dist/litellm/model-errors.d.ts.map +1 -0
- package/dist/litellm/model-form.d.ts +78 -0
- package/dist/litellm/model-form.d.ts.map +1 -0
- package/dist/litellm/model-locate.d.ts +13 -0
- package/dist/litellm/model-locate.d.ts.map +1 -0
- package/dist/litellm/model-operations.d.ts +59 -0
- package/dist/litellm/model-operations.d.ts.map +1 -0
- package/dist/litellm/model-types.d.ts +91 -0
- package/dist/litellm/model-types.d.ts.map +1 -0
- package/dist/litellm/model.d.ts +88 -0
- package/dist/litellm/model.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-form.d.ts +19 -0
- package/dist/litellm/policy-attachment-form.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-operations.d.ts +31 -0
- package/dist/litellm/policy-attachment-operations.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-types.d.ts +48 -0
- package/dist/litellm/policy-attachment-types.d.ts.map +1 -0
- package/dist/litellm/policy-attachment.d.ts +61 -0
- package/dist/litellm/policy-attachment.d.ts.map +1 -0
- package/dist/litellm/policy-form.d.ts +22 -0
- package/dist/litellm/policy-form.d.ts.map +1 -0
- package/dist/litellm/policy-operations.d.ts +42 -0
- package/dist/litellm/policy-operations.d.ts.map +1 -0
- package/dist/litellm/policy-types.d.ts +57 -0
- package/dist/litellm/policy-types.d.ts.map +1 -0
- package/dist/litellm/policy.d.ts +65 -0
- package/dist/litellm/policy.d.ts.map +1 -0
- package/dist/litellm/providers.d.ts +1 -1
- package/dist/litellm/providers.d.ts.map +1 -1
- package/dist/litellm/registry-errors.d.ts +62 -0
- package/dist/litellm/registry-errors.d.ts.map +1 -0
- package/dist/litellm/registry-support.d.ts +42 -0
- package/dist/litellm/registry-support.d.ts.map +1 -0
- package/dist/litellm/team-form.d.ts +26 -0
- package/dist/litellm/team-form.d.ts.map +1 -0
- package/dist/litellm/team-members.d.ts +10 -0
- package/dist/litellm/team-members.d.ts.map +1 -0
- package/dist/litellm/team-operations.d.ts +38 -0
- package/dist/litellm/team-operations.d.ts.map +1 -0
- package/dist/litellm/team-permission.d.ts +35 -0
- package/dist/litellm/team-permission.d.ts.map +1 -0
- package/dist/litellm/team-types.d.ts +101 -0
- package/dist/litellm/team-types.d.ts.map +1 -0
- package/dist/litellm/team.d.ts +67 -0
- package/dist/litellm/team.d.ts.map +1 -0
- package/dist/litellm/tool-policy-form.d.ts +21 -0
- package/dist/litellm/tool-policy-form.d.ts.map +1 -0
- package/dist/litellm/tool-policy-operations.d.ts +30 -0
- package/dist/litellm/tool-policy-operations.d.ts.map +1 -0
- package/dist/litellm/tool-policy-types.d.ts +46 -0
- package/dist/litellm/tool-policy-types.d.ts.map +1 -0
- package/dist/litellm/tool-policy.d.ts +62 -0
- package/dist/litellm/tool-policy.d.ts.map +1 -0
- package/dist/litellm/toolset-form.d.ts +20 -0
- package/dist/litellm/toolset-form.d.ts.map +1 -0
- package/dist/litellm/toolset-operations.d.ts +39 -0
- package/dist/litellm/toolset-operations.d.ts.map +1 -0
- package/dist/litellm/toolset-types.d.ts +46 -0
- package/dist/litellm/toolset-types.d.ts.map +1 -0
- package/dist/litellm/toolset.d.ts +55 -0
- package/dist/litellm/toolset.d.ts.map +1 -0
- package/dist/openbao/index.js +35 -1
- package/dist/openbao/index.js.map +3 -3
- package/dist/ownership/rows.d.ts +11 -0
- package/dist/ownership/rows.d.ts.map +1 -1
- package/dist/postgres/connection.d.ts +14 -2
- package/dist/postgres/connection.d.ts.map +1 -1
- package/dist/postgres/database-sql.d.ts +27 -8
- package/dist/postgres/database-sql.d.ts.map +1 -1
- package/dist/postgres/fake-grants-parse.d.ts +37 -0
- package/dist/postgres/fake-grants-parse.d.ts.map +1 -0
- package/dist/postgres/fake-grants-read.d.ts +61 -0
- package/dist/postgres/fake-grants-read.d.ts.map +1 -0
- package/dist/postgres/fake-grants-sql.d.ts +46 -0
- package/dist/postgres/fake-grants-sql.d.ts.map +1 -0
- package/dist/postgres/fake-grants-write.d.ts +29 -0
- package/dist/postgres/fake-grants-write.d.ts.map +1 -0
- package/dist/postgres/fake-role-sql.d.ts +41 -0
- package/dist/postgres/fake-role-sql.d.ts.map +1 -0
- package/dist/postgres/fake-schema-sql.d.ts +42 -0
- package/dist/postgres/fake-schema-sql.d.ts.map +1 -0
- package/dist/postgres/fake-sql-parse.d.ts +23 -0
- package/dist/postgres/fake-sql-parse.d.ts.map +1 -0
- package/dist/postgres/fake-sql-quote.d.ts +12 -0
- package/dist/postgres/fake-sql-quote.d.ts.map +1 -0
- package/dist/postgres/fake-sql.d.ts +42 -0
- package/dist/postgres/fake-sql.d.ts.map +1 -1
- package/dist/postgres/grants-attrs.d.ts +115 -0
- package/dist/postgres/grants-attrs.d.ts.map +1 -0
- package/dist/postgres/grants-declare.d.ts +88 -0
- package/dist/postgres/grants-declare.d.ts.map +1 -0
- package/dist/postgres/grants-diff.d.ts +25 -0
- package/dist/postgres/grants-diff.d.ts.map +1 -0
- package/dist/postgres/grants-errors.d.ts +137 -0
- package/dist/postgres/grants-errors.d.ts.map +1 -0
- package/dist/postgres/grants-existence.d.ts +14 -0
- package/dist/postgres/grants-existence.d.ts.map +1 -0
- package/dist/postgres/grants-ops.d.ts +65 -0
- package/dist/postgres/grants-ops.d.ts.map +1 -0
- package/dist/postgres/grants-ownership.d.ts +18 -0
- package/dist/postgres/grants-ownership.d.ts.map +1 -0
- package/dist/postgres/grants-plan-restore.d.ts +22 -0
- package/dist/postgres/grants-plan-restore.d.ts.map +1 -0
- package/dist/postgres/grants-plan.d.ts +65 -0
- package/dist/postgres/grants-plan.d.ts.map +1 -0
- package/dist/postgres/grants-read-defaults.d.ts +17 -0
- package/dist/postgres/grants-read-defaults.d.ts.map +1 -0
- package/dist/postgres/grants-read.d.ts +77 -0
- package/dist/postgres/grants-read.d.ts.map +1 -0
- package/dist/postgres/grants-refuse.d.ts +25 -0
- package/dist/postgres/grants-refuse.d.ts.map +1 -0
- package/dist/postgres/grants-sql.d.ts +42 -0
- package/dist/postgres/grants-sql.d.ts.map +1 -0
- package/dist/postgres/grants-words.d.ts +29 -0
- package/dist/postgres/grants-words.d.ts.map +1 -0
- package/dist/postgres/grants.d.ts +37 -0
- package/dist/postgres/grants.d.ts.map +1 -0
- package/dist/postgres/index.d.ts +31 -6
- package/dist/postgres/index.d.ts.map +1 -1
- package/dist/postgres/index.js +1998 -37
- package/dist/postgres/index.js.map +46 -7
- package/dist/postgres/installed-classifier.d.ts +13 -0
- package/dist/postgres/installed-classifier.d.ts.map +1 -0
- package/dist/postgres/providers.d.ts +16 -8
- package/dist/postgres/providers.d.ts.map +1 -1
- package/dist/postgres/psql-executor.d.ts +4 -0
- package/dist/postgres/psql-executor.d.ts.map +1 -1
- package/dist/postgres/role-attrs.d.ts +112 -0
- package/dist/postgres/role-attrs.d.ts.map +1 -0
- package/dist/postgres/role-diff.d.ts +22 -0
- package/dist/postgres/role-diff.d.ts.map +1 -0
- package/dist/postgres/role-errors.d.ts +132 -0
- package/dist/postgres/role-errors.d.ts.map +1 -0
- package/dist/postgres/role-membership-sql.d.ts +78 -0
- package/dist/postgres/role-membership-sql.d.ts.map +1 -0
- package/dist/postgres/role-provider.d.ts +11 -0
- package/dist/postgres/role-provider.d.ts.map +1 -0
- package/dist/postgres/role-scram.d.ts +8 -0
- package/dist/postgres/role-scram.d.ts.map +1 -0
- package/dist/postgres/role-secrets.d.ts +38 -0
- package/dist/postgres/role-secrets.d.ts.map +1 -0
- package/dist/postgres/role-sql.d.ts +68 -0
- package/dist/postgres/role-sql.d.ts.map +1 -0
- package/dist/postgres/role.d.ts +57 -0
- package/dist/postgres/role.d.ts.map +1 -0
- package/dist/postgres/schema-assert.d.ts +50 -0
- package/dist/postgres/schema-assert.d.ts.map +1 -0
- package/dist/postgres/schema-attrs.d.ts +51 -0
- package/dist/postgres/schema-attrs.d.ts.map +1 -0
- package/dist/postgres/schema-delete.d.ts +28 -0
- package/dist/postgres/schema-delete.d.ts.map +1 -0
- package/dist/postgres/schema-diff.d.ts +14 -0
- package/dist/postgres/schema-diff.d.ts.map +1 -0
- package/dist/postgres/schema-errors.d.ts +139 -0
- package/dist/postgres/schema-errors.d.ts.map +1 -0
- package/dist/postgres/schema-identity.d.ts +8 -0
- package/dist/postgres/schema-identity.d.ts.map +1 -0
- package/dist/postgres/schema-reconcile.d.ts +12 -0
- package/dist/postgres/schema-reconcile.d.ts.map +1 -0
- package/dist/postgres/schema-sql.d.ts +45 -0
- package/dist/postgres/schema-sql.d.ts.map +1 -0
- package/dist/postgres/schema-test-kit.d.ts +67 -0
- package/dist/postgres/schema-test-kit.d.ts.map +1 -0
- package/dist/postgres/schema.d.ts +54 -0
- package/dist/postgres/schema.d.ts.map +1 -0
- package/dist/proxmox/index.js +35 -1
- package/dist/proxmox/index.js.map +3 -3
- package/dist/release/fake-engine.d.ts +10 -0
- package/dist/release/fake-engine.d.ts.map +1 -1
- package/dist/release/index.js +35 -1
- package/dist/release/index.js.map +3 -3
- package/dist/valkey/acl-attrs.d.ts +94 -0
- package/dist/valkey/acl-attrs.d.ts.map +1 -0
- package/dist/valkey/acl-compare.d.ts +28 -0
- package/dist/valkey/acl-compare.d.ts.map +1 -0
- package/dist/valkey/acl-connection.d.ts +6 -0
- package/dist/valkey/acl-connection.d.ts.map +1 -0
- package/dist/valkey/acl-form.d.ts +62 -0
- package/dist/valkey/acl-form.d.ts.map +1 -0
- package/dist/valkey/acl-managed.d.ts +4 -0
- package/dist/valkey/acl-managed.d.ts.map +1 -0
- package/dist/valkey/acl-ops.d.ts +37 -0
- package/dist/valkey/acl-ops.d.ts.map +1 -0
- package/dist/valkey/acl-profiles.d.ts +53 -0
- package/dist/valkey/acl-profiles.d.ts.map +1 -0
- package/dist/valkey/acl-reconcile.d.ts +26 -0
- package/dist/valkey/acl-reconcile.d.ts.map +1 -0
- package/dist/valkey/acl.d.ts +24 -0
- package/dist/valkey/acl.d.ts.map +1 -0
- package/dist/valkey/connection-service.d.ts +22 -0
- package/dist/valkey/connection-service.d.ts.map +1 -0
- package/dist/valkey/connection.d.ts +59 -0
- package/dist/valkey/connection.d.ts.map +1 -0
- package/dist/valkey/errors.d.ts +165 -0
- package/dist/valkey/errors.d.ts.map +1 -0
- package/dist/valkey/fake-acl.d.ts +29 -0
- package/dist/valkey/fake-acl.d.ts.map +1 -0
- package/dist/valkey/fake-valkey.d.ts +35 -0
- package/dist/valkey/fake-valkey.d.ts.map +1 -0
- package/dist/valkey/handler-test-fixture.d.ts +20 -0
- package/dist/valkey/handler-test-fixture.d.ts.map +1 -0
- package/dist/valkey/index.d.ts +18 -0
- package/dist/valkey/index.d.ts.map +1 -0
- package/dist/valkey/index.js +964 -0
- package/dist/valkey/index.js.map +27 -0
- package/dist/valkey/instance-attrs.d.ts +46 -0
- package/dist/valkey/instance-attrs.d.ts.map +1 -0
- package/dist/valkey/instance-form.d.ts +22 -0
- package/dist/valkey/instance-form.d.ts.map +1 -0
- package/dist/valkey/instance.d.ts +48 -0
- package/dist/valkey/instance.d.ts.map +1 -0
- package/dist/valkey/provider-socket-fixture.d.ts +8 -0
- package/dist/valkey/provider-socket-fixture.d.ts.map +1 -0
- package/dist/valkey/providers.d.ts +16 -0
- package/dist/valkey/providers.d.ts.map +1 -0
- package/dist/valkey/resp.d.ts +38 -0
- package/dist/valkey/resp.d.ts.map +1 -0
- package/dist/valkey/transport-error.d.ts +36 -0
- package/dist/valkey/transport-error.d.ts.map +1 -0
- package/dist/valkey/transport.d.ts +50 -0
- package/dist/valkey/transport.d.ts.map +1 -0
- package/docs/litellm-access-group.md +105 -0
- package/docs/litellm-credential.md +60 -0
- package/docs/litellm-key-metadata.md +56 -0
- package/docs/litellm-key.md +194 -0
- package/docs/litellm-mcp.md +187 -0
- package/docs/litellm-model.md +111 -0
- package/docs/litellm-policy-attachment.md +86 -0
- package/docs/litellm-policy.md +103 -0
- package/docs/litellm-team.md +117 -0
- package/docs/litellm-tool-policy.md +90 -0
- package/docs/litellm-toolset.md +92 -0
- package/docs/litellm.md +16 -9
- package/docs/peers.md +15 -6
- package/docs/postgres-grants-example.md +55 -0
- package/docs/postgres-grants.md +157 -0
- package/docs/postgres-role.md +126 -0
- package/docs/postgres-schema.md +139 -0
- package/docs/postgres.md +20 -3
- package/docs/valkey-transport.md +29 -0
- package/docs/valkey.md +187 -0
- package/package.json +6 -2
- package/src/ceph/ceph-argv.test.ts +1 -1
- package/src/launchd/sudo-allowlist.test.ts +2 -2
- package/src/linux/sudo-allowlist.test.ts +3 -3
- package/src/linux/sudo-runner.test.ts +2 -2
- package/src/litellm/access-group-form.ts +105 -0
- package/src/litellm/access-group-operations.ts +92 -0
- package/src/litellm/access-group-types.ts +49 -0
- package/src/litellm/access-group.test.ts +197 -0
- package/src/litellm/access-group.ts +151 -0
- package/src/litellm/credential-errors.ts +183 -0
- package/src/litellm/credential-form.test.ts +151 -0
- package/src/litellm/credential-form.ts +211 -0
- package/src/litellm/credential-guards.test.ts +241 -0
- package/src/litellm/credential-operations.ts +208 -0
- package/src/litellm/credential-recovery.test.ts +93 -0
- package/src/litellm/credential-rewrite.test.ts +164 -0
- package/src/litellm/credential-rewrite.ts +50 -0
- package/src/litellm/credential-types.ts +117 -0
- package/src/litellm/credential-values.ts +94 -0
- package/src/litellm/credential.test.ts +218 -0
- package/src/litellm/credential.ts +227 -0
- package/src/litellm/fake-access-group-litellm.ts +127 -0
- package/src/litellm/fake-credential-litellm.ts +216 -0
- package/src/litellm/fake-keys.ts +190 -0
- package/src/litellm/fake-litellm.ts +19 -9
- package/src/litellm/fake-mcp-litellm.ts +214 -0
- package/src/litellm/fake-model-litellm-post-update.ts +103 -0
- package/src/litellm/fake-model-litellm.ts +233 -0
- package/src/litellm/fake-policy-attachment-litellm.ts +97 -0
- package/src/litellm/fake-policy-litellm.ts +186 -0
- package/src/litellm/fake-registry-base.ts +69 -0
- package/src/litellm/fake-stack.ts +17 -1
- package/src/litellm/fake-team-litellm.ts +229 -0
- package/src/litellm/fake-tool-litellm.ts +88 -0
- package/src/litellm/fake-toolset-litellm.ts +110 -0
- package/src/litellm/index.ts +140 -0
- package/src/litellm/key-delete.test.ts +212 -0
- package/src/litellm/key-errors.ts +241 -0
- package/src/litellm/key-form.ts +200 -0
- package/src/litellm/key-harness.ts +121 -0
- package/src/litellm/key-metadata-errors.ts +49 -0
- package/src/litellm/key-metadata.test.ts +156 -0
- package/src/litellm/key-metadata.ts +74 -0
- package/src/litellm/key-operations.ts +184 -0
- package/src/litellm/key-rotation.test.ts +110 -0
- package/src/litellm/key-scope.test.ts +135 -0
- package/src/litellm/key-secret.test.ts +248 -0
- package/src/litellm/key-secret.ts +137 -0
- package/src/litellm/key-transport.test.ts +166 -0
- package/src/litellm/key.test.ts +231 -0
- package/src/litellm/key.ts +213 -0
- package/src/litellm/mcp-server-adopt.test.ts +86 -0
- package/src/litellm/mcp-server-alias.test.ts +184 -0
- package/src/litellm/mcp-server-credential.ts +85 -0
- package/src/litellm/mcp-server-errors.ts +141 -0
- package/src/litellm/mcp-server-exports.test.ts +15 -0
- package/src/litellm/mcp-server-form.test.ts +241 -0
- package/src/litellm/mcp-server-form.ts +233 -0
- package/src/litellm/mcp-server-handlers.test.ts +150 -0
- package/src/litellm/mcp-server-locate.ts +66 -0
- package/src/litellm/mcp-server-operations.ts +111 -0
- package/src/litellm/mcp-server-registry.test.ts +146 -0
- package/src/litellm/mcp-server-retype.test.ts +114 -0
- package/src/litellm/mcp-server-shadow.test.ts +91 -0
- package/src/litellm/mcp-server-shadow.ts +45 -0
- package/src/litellm/mcp-server-tools.test.ts +85 -0
- package/src/litellm/mcp-server-types.ts +143 -0
- package/src/litellm/mcp-server-url.ts +66 -0
- package/src/litellm/mcp-server.test.ts +213 -0
- package/src/litellm/mcp-server.ts +225 -0
- package/src/litellm/model-claim.ts +69 -0
- package/src/litellm/model-credential.ts +45 -0
- package/src/litellm/model-errors.ts +168 -0
- package/src/litellm/model-foreign-row.test.ts +173 -0
- package/src/litellm/model-form.test.ts +228 -0
- package/src/litellm/model-form.ts +223 -0
- package/src/litellm/model-locate.test.ts +128 -0
- package/src/litellm/model-locate.ts +67 -0
- package/src/litellm/model-operations.test.ts +93 -0
- package/src/litellm/model-operations.ts +146 -0
- package/src/litellm/model-params-preserve.test.ts +119 -0
- package/src/litellm/model-recovery.test.ts +57 -0
- package/src/litellm/model-types.ts +112 -0
- package/src/litellm/model.test.ts +222 -0
- package/src/litellm/model.ts +237 -0
- package/src/litellm/policy-attachment-form.ts +103 -0
- package/src/litellm/policy-attachment-operations.ts +74 -0
- package/src/litellm/policy-attachment-types.ts +48 -0
- package/src/litellm/policy-attachment.test.ts +198 -0
- package/src/litellm/policy-attachment.ts +178 -0
- package/src/litellm/policy-form.ts +122 -0
- package/src/litellm/policy-operations.ts +112 -0
- package/src/litellm/policy-types.ts +58 -0
- package/src/litellm/policy.test.ts +214 -0
- package/src/litellm/policy.ts +146 -0
- package/src/litellm/providers.ts +24 -3
- package/src/litellm/registry-errors.ts +88 -0
- package/src/litellm/registry-exports.test.ts +66 -0
- package/src/litellm/registry-support.ts +109 -0
- package/src/litellm/team-form.ts +140 -0
- package/src/litellm/team-members.test.ts +150 -0
- package/src/litellm/team-members.ts +58 -0
- package/src/litellm/team-operations.ts +90 -0
- package/src/litellm/team-permission.ts +159 -0
- package/src/litellm/team-read.test.ts +89 -0
- package/src/litellm/team-types.ts +106 -0
- package/src/litellm/team.test.ts +206 -0
- package/src/litellm/team.ts +159 -0
- package/src/litellm/tool-policy-form.ts +94 -0
- package/src/litellm/tool-policy-operations.ts +58 -0
- package/src/litellm/tool-policy-types.ts +48 -0
- package/src/litellm/tool-policy.test.ts +172 -0
- package/src/litellm/tool-policy.ts +125 -0
- package/src/litellm/toolset-form.ts +104 -0
- package/src/litellm/toolset-operations.ts +90 -0
- package/src/litellm/toolset-types.ts +47 -0
- package/src/litellm/toolset.test.ts +198 -0
- package/src/litellm/toolset.ts +151 -0
- package/src/ownership/ownership.test.ts +33 -1
- package/src/ownership/rows.ts +53 -0
- package/src/postgres/connection.ts +42 -7
- package/src/postgres/database-bigint-serialization.test.ts +1 -0
- package/src/postgres/database-refusals.test.ts +2 -2
- package/src/postgres/database-sql.ts +36 -9
- package/src/postgres/fake-grants-parse.ts +189 -0
- package/src/postgres/fake-grants-read.ts +250 -0
- package/src/postgres/fake-grants-sql.ts +207 -0
- package/src/postgres/fake-grants-write.ts +90 -0
- package/src/postgres/fake-role-sql.ts +240 -0
- package/src/postgres/fake-schema-semantics.test.ts +36 -0
- package/src/postgres/fake-schema-sql.ts +146 -0
- package/src/postgres/fake-sql-parse.ts +86 -0
- package/src/postgres/fake-sql-quote.ts +36 -0
- package/src/postgres/fake-sql.ts +166 -46
- package/src/postgres/fixtures/pg-REL_18_6-acl.h.txt +147 -0
- package/src/postgres/fixtures/pg-REL_18_6-aclexplode-func.txt +31 -0
- package/src/postgres/fixtures/pg-REL_18_6-alter-default-privileges-synopsis.txt +82 -0
- package/src/postgres/fixtures/pg-REL_18_6-alter_role.sgml.txt +44 -0
- package/src/postgres/fixtures/pg-REL_18_6-create_role.sgml.txt +26 -0
- package/src/postgres/fixtures/pg-REL_18_6-grant-synopsis.txt +95 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_attribute.h.txt +24 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_auth_members.h.txt +62 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_authid.h.txt +70 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_class.h.txt +33 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_default_acl.h.txt +81 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_namespace.h.txt +28 -0
- package/src/postgres/fixtures/pg-REL_18_6-revoke-synopsis.txt +124 -0
- package/src/postgres/grants-acl.test.ts +61 -0
- package/src/postgres/grants-attrs.ts +185 -0
- package/src/postgres/grants-database.test.ts +158 -0
- package/src/postgres/grants-declare.ts +172 -0
- package/src/postgres/grants-delete-proof.test.ts +154 -0
- package/src/postgres/grants-diff.ts +101 -0
- package/src/postgres/grants-errors.ts +215 -0
- package/src/postgres/grants-existence-guards.test.ts +113 -0
- package/src/postgres/grants-existence.ts +101 -0
- package/src/postgres/grants-lifecycle-guards.test.ts +208 -0
- package/src/postgres/grants-lifecycle-repair.test.ts +208 -0
- package/src/postgres/grants-lifecycle.test.ts +181 -0
- package/src/postgres/grants-ops.ts +229 -0
- package/src/postgres/grants-owner-revoke.test.ts +121 -0
- package/src/postgres/grants-ownership.ts +49 -0
- package/src/postgres/grants-plan-collateral.test.ts +202 -0
- package/src/postgres/grants-plan-diff.test.ts +204 -0
- package/src/postgres/grants-plan-restore.ts +43 -0
- package/src/postgres/grants-plan-revocations.test.ts +69 -0
- package/src/postgres/grants-plan.test.ts +148 -0
- package/src/postgres/grants-plan.ts +245 -0
- package/src/postgres/grants-provenance.test.ts +134 -0
- package/src/postgres/grants-psql-lifecycle.test.ts +105 -0
- package/src/postgres/grants-read-absent.test.ts +91 -0
- package/src/postgres/grants-read-defaults.ts +51 -0
- package/src/postgres/grants-read.ts +250 -0
- package/src/postgres/grants-refusals.test.ts +136 -0
- package/src/postgres/grants-refuse.ts +119 -0
- package/src/postgres/grants-relkind-wire.test.ts +111 -0
- package/src/postgres/grants-repair-plan.test.ts +201 -0
- package/src/postgres/grants-sql.test.ts +128 -0
- package/src/postgres/grants-sql.ts +144 -0
- package/src/postgres/grants-words.ts +34 -0
- package/src/postgres/grants.ts +110 -0
- package/src/postgres/index.ts +129 -5
- package/src/postgres/installed-classifier.ts +36 -0
- package/src/postgres/providers.ts +33 -9
- package/src/postgres/psql-executor-escaping.test.ts +67 -0
- package/src/postgres/psql-executor.test.ts +63 -2
- package/src/postgres/psql-executor.ts +74 -34
- package/src/postgres/role-adopt.test.ts +98 -0
- package/src/postgres/role-attrs.ts +137 -0
- package/src/postgres/role-diff.ts +93 -0
- package/src/postgres/role-errors.ts +206 -0
- package/src/postgres/role-guard.test.ts +60 -0
- package/src/postgres/role-identity.test.ts +197 -0
- package/src/postgres/role-lifecycle.test.ts +228 -0
- package/src/postgres/role-membership-options.test.ts +73 -0
- package/src/postgres/role-membership-repair.test.ts +153 -0
- package/src/postgres/role-membership-sql.ts +223 -0
- package/src/postgres/role-password-ascii.test.ts +53 -0
- package/src/postgres/role-password-transports.test.ts +123 -0
- package/src/postgres/role-password.test.ts +158 -0
- package/src/postgres/role-privilege.test.ts +107 -0
- package/src/postgres/role-provenance.test.ts +200 -0
- package/src/postgres/role-provider.ts +105 -0
- package/src/postgres/role-refusals.test.ts +225 -0
- package/src/postgres/role-scram.test.ts +38 -0
- package/src/postgres/role-scram.ts +40 -0
- package/src/postgres/role-secrets.ts +61 -0
- package/src/postgres/role-sql.ts +213 -0
- package/src/postgres/role-valid-until.test.ts +68 -0
- package/src/postgres/role.ts +226 -0
- package/src/postgres/schema-assert.ts +139 -0
- package/src/postgres/schema-attrs.ts +71 -0
- package/src/postgres/schema-delete-proofs.test.ts +192 -0
- package/src/postgres/schema-delete.ts +96 -0
- package/src/postgres/schema-diff.ts +44 -0
- package/src/postgres/schema-drop-2bp01.test.ts +146 -0
- package/src/postgres/schema-errors.ts +209 -0
- package/src/postgres/schema-handlers.test.ts +193 -0
- package/src/postgres/schema-identity.ts +39 -0
- package/src/postgres/schema-lifecycle.test.ts +246 -0
- package/src/postgres/schema-race.test.ts +70 -0
- package/src/postgres/schema-reconcile.ts +100 -0
- package/src/postgres/schema-refusals.test.ts +144 -0
- package/src/postgres/schema-socket.test.ts +162 -0
- package/src/postgres/schema-sql.ts +130 -0
- package/src/postgres/schema-takeover.test.ts +149 -0
- package/src/postgres/schema-test-kit.ts +85 -0
- package/src/postgres/schema.ts +130 -0
- package/src/release/binary-alias.test.ts +3 -3
- package/src/release/binary-claims.test.ts +3 -3
- package/src/release/binary-directory-umask.test.ts +65 -0
- package/src/release/fake-engine.ts +15 -0
- package/src/valkey/acl-attrs.ts +98 -0
- package/src/valkey/acl-compare.ts +57 -0
- package/src/valkey/acl-connection.ts +13 -0
- package/src/valkey/acl-delete.test.ts +127 -0
- package/src/valkey/acl-form.ts +239 -0
- package/src/valkey/acl-managed.ts +13 -0
- package/src/valkey/acl-ops.ts +122 -0
- package/src/valkey/acl-ownership.test.ts +118 -0
- package/src/valkey/acl-prefix.test.ts +38 -0
- package/src/valkey/acl-profiles.ts +81 -0
- package/src/valkey/acl-reconcile.ts +180 -0
- package/src/valkey/acl-reserved.test.ts +69 -0
- package/src/valkey/acl-revocation.test.ts +94 -0
- package/src/valkey/acl-seat-prefix.test.ts +111 -0
- package/src/valkey/acl-validation.test.ts +54 -0
- package/src/valkey/acl.test.ts +215 -0
- package/src/valkey/acl.ts +169 -0
- package/src/valkey/connection-service.ts +46 -0
- package/src/valkey/connection.ts +180 -0
- package/src/valkey/errors.ts +239 -0
- package/src/valkey/fake-acl.ts +128 -0
- package/src/valkey/fake-valkey.ts +224 -0
- package/src/valkey/handler-test-fixture.ts +43 -0
- package/src/valkey/handlers.test.ts +205 -0
- package/src/valkey/index.ts +54 -0
- package/src/valkey/instance-attrs.ts +48 -0
- package/src/valkey/instance-form.ts +104 -0
- package/src/valkey/instance.test.ts +108 -0
- package/src/valkey/instance.ts +160 -0
- package/src/valkey/provider-socket-fixture.ts +71 -0
- package/src/valkey/providers.test.ts +129 -0
- package/src/valkey/providers.ts +21 -0
- package/src/valkey/resp.ts +212 -0
- package/src/valkey/transport-bounds.test.ts +150 -0
- package/src/valkey/transport-error.ts +37 -0
- package/src/valkey/transport.test.ts +158 -0
- package/src/valkey/transport.ts +109 -0
- package/src/valkey/upstream-bounds.test.ts +14 -0
package/dist/postgres/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
// src/postgres/database-sql.ts
|
|
17
17
|
import * as Effect from "effect/Effect";
|
|
18
18
|
var quoteIdent = (value) => `"${value.replace(/"/g, '""')}"`;
|
|
19
|
-
var quoteStringLiteral = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
19
|
+
var quoteStringLiteral = (value) => `E'${value.replace(/\\/g, "\\\\").replace(/'/g, "''")}'`;
|
|
20
20
|
var withOptions = (props, template) => {
|
|
21
21
|
const parts = [`OWNER ${quoteIdent(props.owner)}`];
|
|
22
22
|
if (template !== undefined)
|
|
@@ -51,6 +51,8 @@ var isDuplicateDatabaseRace = (error) => {
|
|
|
51
51
|
};
|
|
52
52
|
var ROLE_EXISTS_SQL = "SELECT 1 AS present FROM pg_roles WHERE rolname = $1";
|
|
53
53
|
var roleExists = (pg, role) => Effect.map(pg.unsafe(ROLE_EXISTS_SQL, [role]), (rows) => rows.length > 0);
|
|
54
|
+
var DATABASE_EXISTS_SQL = "SELECT 1 AS present FROM pg_database WHERE datname = $1";
|
|
55
|
+
var databaseExists = (pg, name) => Effect.map(pg.unsafe(DATABASE_EXISTS_SQL, [name]), (rows) => rows.length > 0);
|
|
54
56
|
var SELECT_DATABASE_SQL = `SELECT
|
|
55
57
|
d.oid AS oid,
|
|
56
58
|
d.datname AS name,
|
|
@@ -97,48 +99,55 @@ var inlineParams = (sql, params) => params.length === 0 ? sql : sql.replace(/\$(
|
|
|
97
99
|
return quoteStringLiteral(value);
|
|
98
100
|
});
|
|
99
101
|
var normalizeRow = (row) => typeof row === "object" && row !== null && typeof row.oid === "string" ? { ...row, oid: Number(row.oid) } : row;
|
|
100
|
-
var
|
|
102
|
+
var returnsRows = (sql) => /^\s*(SELECT|WITH)\b/i.test(sql);
|
|
101
103
|
var wrapRows = (sql) => `SELECT coalesce(json_agg(t), '[]'::json)::text FROM (${sql}) t;`;
|
|
104
|
+
var redactPasswordLiterals = (text) => text.replace(/PASSWORD\s+(?:E'(?:[^'\\]|\\[\s\S]|'')*'|'(?:[^']|'')*')/gi, "PASSWORD '[redacted]'");
|
|
102
105
|
var failure = (operation, result) => {
|
|
103
106
|
const state = /ERROR:\s+([0-9A-Z]{5}):/.exec(result.stderr)?.[1];
|
|
107
|
+
const stderr = redactPasswordLiterals(result.stderr.trim());
|
|
104
108
|
const fields = {
|
|
105
|
-
cause: Object.assign(new Error(
|
|
106
|
-
message:
|
|
107
|
-
operation
|
|
109
|
+
cause: Object.assign(new Error(stderr), state === undefined ? {} : { code: state }),
|
|
110
|
+
message: stderr,
|
|
111
|
+
operation: redactPasswordLiterals(operation)
|
|
108
112
|
};
|
|
109
113
|
const reason = state === undefined ? new ConnectionError(fields) : state.startsWith("42") ? new SqlSyntaxError(fields) : new UnknownError(fields);
|
|
110
114
|
return new SqlError({ reason });
|
|
111
115
|
};
|
|
116
|
+
var runScript = (run, target, stdin, operation) => Effect2.gen(function* () {
|
|
117
|
+
const result = yield* Effect2.tryPromise({
|
|
118
|
+
try: () => run({
|
|
119
|
+
argv: [
|
|
120
|
+
"psql",
|
|
121
|
+
"-X",
|
|
122
|
+
"-q",
|
|
123
|
+
"-A",
|
|
124
|
+
"-t",
|
|
125
|
+
"-v",
|
|
126
|
+
"ON_ERROR_STOP=1",
|
|
127
|
+
"-v",
|
|
128
|
+
"VERBOSITY=verbose",
|
|
129
|
+
"-U",
|
|
130
|
+
target.username,
|
|
131
|
+
"-d",
|
|
132
|
+
target.database
|
|
133
|
+
],
|
|
134
|
+
stdin
|
|
135
|
+
}),
|
|
136
|
+
catch: (cause) => new SqlError({ reason: new ConnectionError({ cause, operation: "psql exec" }) })
|
|
137
|
+
});
|
|
138
|
+
if (result.code !== 0) {
|
|
139
|
+
return yield* Effect2.fail(failure(operation, result));
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
});
|
|
112
143
|
var makePsqlExecutor = (run, target) => ({
|
|
113
144
|
unsafe: (sql, params = []) => Effect2.gen(function* () {
|
|
114
145
|
const inlined = yield* Effect2.try({
|
|
115
146
|
try: () => inlineParams(sql, params),
|
|
116
147
|
catch: (cause) => new SqlError({ reason: new UnknownError({ cause, operation: "psql inline params" }) })
|
|
117
148
|
});
|
|
118
|
-
const rows =
|
|
119
|
-
const result = yield*
|
|
120
|
-
try: () => run({
|
|
121
|
-
argv: [
|
|
122
|
-
"psql",
|
|
123
|
-
"-X",
|
|
124
|
-
"-q",
|
|
125
|
-
"-A",
|
|
126
|
-
"-t",
|
|
127
|
-
"-v",
|
|
128
|
-
"ON_ERROR_STOP=1",
|
|
129
|
-
"-v",
|
|
130
|
-
"VERBOSITY=verbose",
|
|
131
|
-
"-U",
|
|
132
|
-
target.username,
|
|
133
|
-
"-d",
|
|
134
|
-
target.database
|
|
135
|
-
],
|
|
136
|
-
stdin: rows ? wrapRows(inlined) : `${inlined};`
|
|
137
|
-
}),
|
|
138
|
-
catch: (cause) => new SqlError({ reason: new ConnectionError({ cause, operation: "psql exec" }) })
|
|
139
|
-
});
|
|
140
|
-
if (result.code !== 0)
|
|
141
|
-
return yield* Effect2.fail(failure(inlined.slice(0, 40), result));
|
|
149
|
+
const rows = returnsRows(inlined);
|
|
150
|
+
const result = yield* runScript(run, target, rows ? wrapRows(inlined) : `${inlined};`, redactPasswordLiterals(inlined).slice(0, 40));
|
|
142
151
|
if (!rows)
|
|
143
152
|
return [];
|
|
144
153
|
const parsed = yield* Effect2.try({
|
|
@@ -146,6 +155,11 @@ var makePsqlExecutor = (run, target) => ({
|
|
|
146
155
|
catch: (cause) => new SqlError({ reason: new UnknownError({ cause, operation: "psql parse output" }) })
|
|
147
156
|
});
|
|
148
157
|
return parsed;
|
|
158
|
+
}),
|
|
159
|
+
transaction: (statements) => Effect2.gen(function* () {
|
|
160
|
+
const script = ["BEGIN", ...statements, "COMMIT"].join(`;
|
|
161
|
+
`);
|
|
162
|
+
yield* runScript(run, target, `${script};`, "BEGIN");
|
|
149
163
|
})
|
|
150
164
|
});
|
|
151
165
|
|
|
@@ -154,16 +168,28 @@ class PostgresConnection extends Context.Service()("Postgres.Connection") {
|
|
|
154
168
|
}
|
|
155
169
|
var postgresConnection = (config) => Layer.succeed(PostgresConnection, Effect3.succeed(config));
|
|
156
170
|
var postgresRunnerConnection = (config) => Layer.succeed(PostgresConnection, Effect3.succeed(config));
|
|
171
|
+
var PostgresPool = Context.Reference("homeflare/postgres/Pool", {
|
|
172
|
+
defaultValue: () => PgClient.layer
|
|
173
|
+
});
|
|
157
174
|
var isRunner = (c) => ("run" in c);
|
|
158
|
-
var withPg = (build) => Effect3.gen(function* () {
|
|
175
|
+
var withPg = (build, database) => Effect3.gen(function* () {
|
|
159
176
|
const resolveConfig = yield* PostgresConnection;
|
|
160
177
|
const config = yield* resolveConfig;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
178
|
+
const target = database === undefined ? config : { ...config, database };
|
|
179
|
+
if (isRunner(target)) {
|
|
180
|
+
return yield* build(makePsqlExecutor(target.run, target), {
|
|
181
|
+
template: target.template ?? "template0",
|
|
182
|
+
database: target.database
|
|
164
183
|
});
|
|
165
184
|
}
|
|
166
|
-
|
|
185
|
+
const pool = yield* PostgresPool;
|
|
186
|
+
return yield* Effect3.provide(Effect3.flatMap(PgClient.PgClient, (pg) => build({
|
|
187
|
+
unsafe: (sql, params) => pg.unsafe(sql, params),
|
|
188
|
+
transaction: (statements) => pg.withTransaction(Effect3.gen(function* () {
|
|
189
|
+
for (const sql of statements)
|
|
190
|
+
yield* pg.unsafe(sql).pipe(Effect3.asVoid);
|
|
191
|
+
}))
|
|
192
|
+
}, { database: target.database })), pool(target));
|
|
167
193
|
});
|
|
168
194
|
// src/postgres/database-attrs.ts
|
|
169
195
|
var POSTGRES_NAME_MAX_BYTES = 63;
|
|
@@ -287,11 +313,1861 @@ var postgresDatabaseHandlers = PostgresDatabase.Provider.of({
|
|
|
287
313
|
delete: ({ olds }) => Effect4.fail(new PostgresDatabaseDropRefused({ database: olds.name }))
|
|
288
314
|
});
|
|
289
315
|
var PostgresDatabaseProvider = () => Provider.succeed(PostgresDatabase, postgresDatabaseHandlers);
|
|
316
|
+
// src/postgres/schema-attrs.ts
|
|
317
|
+
var normalizedComment = (comment) => comment === "" ? undefined : comment;
|
|
318
|
+
var schemaNameByteRefusal = (name) => {
|
|
319
|
+
const byteLength = utf8ByteLength(name);
|
|
320
|
+
return byteLength > POSTGRES_NAME_MAX_BYTES ? { byteLength, limit: POSTGRES_NAME_MAX_BYTES } : undefined;
|
|
321
|
+
};
|
|
322
|
+
// src/postgres/schema-sql.ts
|
|
323
|
+
import * as Effect5 from "effect/Effect";
|
|
324
|
+
var buildCreateSchemaSql = (props) => `CREATE SCHEMA ${quoteIdent(props.name)}${props.owner === undefined ? "" : ` AUTHORIZATION ${quoteIdent(props.owner)}`}`;
|
|
325
|
+
var buildCommentSchemaSql = (name, comment) => `COMMENT ON SCHEMA ${quoteIdent(name)} IS ${quoteStringLiteral(comment)}`;
|
|
326
|
+
var buildDropSchemaSql = (name, cascade) => `DROP SCHEMA IF EXISTS ${quoteIdent(name)}${cascade ? " CASCADE" : ""}`;
|
|
327
|
+
var SELECT_SCHEMA_SQL = `SELECT
|
|
328
|
+
n.oid AS oid,
|
|
329
|
+
n.nspname AS name,
|
|
330
|
+
pg_get_userbyid(n.nspowner) AS owner,
|
|
331
|
+
obj_description(n.oid, 'pg_namespace') AS comment,
|
|
332
|
+
current_database() AS database
|
|
333
|
+
FROM pg_namespace n
|
|
334
|
+
WHERE n.nspname = $1`;
|
|
335
|
+
var selectSchema = (pg, name) => Effect5.map(pg.unsafe(SELECT_SCHEMA_SQL, [name]), (rows) => rows[0]);
|
|
336
|
+
var CURRENT_DATABASE_SQL = "SELECT current_database() AS database";
|
|
337
|
+
var currentDatabase = (pg) => Effect5.map(pg.unsafe(CURRENT_DATABASE_SQL), (rows) => rows[0]?.database ?? "");
|
|
338
|
+
var CURRENT_USER_SQL = "SELECT current_user AS role";
|
|
339
|
+
var currentUser = (pg) => Effect5.map(pg.unsafe(CURRENT_USER_SQL), (rows) => rows[0]?.role ?? "");
|
|
340
|
+
var SCHEMA_EMPTY_SQL = `WITH ns AS (SELECT oid FROM pg_namespace WHERE nspname = $1)
|
|
341
|
+
SELECT NOT EXISTS (
|
|
342
|
+
SELECT 1 FROM pg_class c WHERE c.relnamespace = (SELECT oid FROM ns)
|
|
343
|
+
) AND NOT EXISTS (
|
|
344
|
+
SELECT 1 FROM pg_proc p WHERE p.pronamespace = (SELECT oid FROM ns)
|
|
345
|
+
) AND NOT EXISTS (
|
|
346
|
+
SELECT 1 FROM pg_type t WHERE t.typnamespace = (SELECT oid FROM ns)
|
|
347
|
+
) AND NOT EXISTS (
|
|
348
|
+
SELECT 1 FROM pg_operator o WHERE o.oprnamespace = (SELECT oid FROM ns)
|
|
349
|
+
) AS empty`;
|
|
350
|
+
var schemaIsEmpty = (pg, name) => Effect5.map(pg.unsafe(SCHEMA_EMPTY_SQL, [name]), (rows) => rows[0]?.empty ?? true);
|
|
351
|
+
var isDependentObjectsError = (error) => {
|
|
352
|
+
const cause = error.reason.cause;
|
|
353
|
+
return typeof cause === "object" && cause !== null && cause.code === "2BP01";
|
|
354
|
+
};
|
|
355
|
+
var isDuplicateSchemaError = (error) => {
|
|
356
|
+
const cause = error.reason.cause;
|
|
357
|
+
return typeof cause === "object" && cause !== null && cause.code === "42P06";
|
|
358
|
+
};
|
|
359
|
+
// src/postgres/schema.ts
|
|
360
|
+
import { Resource as Resource2 } from "alchemy";
|
|
361
|
+
import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
|
|
362
|
+
import * as Provider2 from "alchemy/Provider";
|
|
363
|
+
import * as Effect11 from "effect/Effect";
|
|
364
|
+
|
|
365
|
+
// src/postgres/schema-identity.ts
|
|
366
|
+
import * as Effect6 from "effect/Effect";
|
|
367
|
+
|
|
368
|
+
// src/postgres/schema-errors.ts
|
|
369
|
+
import * as Data2 from "effect/Data";
|
|
370
|
+
|
|
371
|
+
class PostgresSchemaNameRefused extends Data2.TaggedError("PostgresSchemaNameRefused") {
|
|
372
|
+
get message() {
|
|
373
|
+
return `Postgres.Schema "${this.name}": ${String(this.byteLength)} UTF-8 bytes, over the ` + `${String(this.limit)}-byte limit (NAMEDATALEN 64, minus the terminator). PostgreSQL would ` + "accept the CREATE and silently truncate the name with only a NOTICE, so this plan refuses " + "it instead of creating a schema no later read will ever find again.";
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
class PostgresSchemaRenameRefused extends Data2.TaggedError("PostgresSchemaRenameRefused") {
|
|
378
|
+
get message() {
|
|
379
|
+
return `Postgres.Schema: the declared name changed from "${this.from}" to "${this.to}". A ` + "rename is refused during plan or apply — this provider never runs ALTER SCHEMA … RENAME. Declare a new " + "logical id for the new name and remove the old one once its contents have moved.";
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
class PostgresSchemaOwnerMissing extends Data2.TaggedError("PostgresSchemaOwnerMissing") {
|
|
384
|
+
get message() {
|
|
385
|
+
return `Postgres.Schema "${this.schema}": AUTHORIZATION "${this.owner}" is not a role in pg_roles.`;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
class PostgresSchemaDrift extends Data2.TaggedError("PostgresSchemaDrift") {
|
|
390
|
+
get message() {
|
|
391
|
+
return `Postgres.Schema "${this.schema}": ${this.prop} is declared as ` + `${JSON.stringify(this.declared)} but the live schema has ${JSON.stringify(this.live)}. ` + "This provider never issues ALTER SCHEMA — update the declaration to match the live value, " + "or change it by hand and re-plan.";
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
class PostgresSchemaCreateVanished extends Data2.TaggedError("PostgresSchemaCreateVanished") {
|
|
396
|
+
get message() {
|
|
397
|
+
return `Postgres.Schema "${this.schema}": CREATE SCHEMA raised no error, but the schema is still ` + "absent on the immediate re-read. Re-plan — this is not a state this provider expects to reach.";
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
class PostgresSchemaDropNotEmptyError extends Data2.TaggedError("PostgresSchemaDropNotEmptyError") {
|
|
402
|
+
get message() {
|
|
403
|
+
return `Postgres.Schema "${this.schema}": DROP SCHEMA refused because the schema is not empty and ` + "cascade is false. Declare cascade: true to drop its contents, or empty it by hand first.";
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
class PostgresSchemaWrongDatabase extends Data2.TaggedError("PostgresSchemaWrongDatabase") {
|
|
408
|
+
get message() {
|
|
409
|
+
return `Postgres.Schema "${this.schema}": declared in database "${this.declared}" but the ` + `connection answers current_database() "${this.connected}". The handlers open the declared ` + "database themselves, so this points at broken provider wiring — fix it before touching any " + "schema, or the next create would land in the wrong database.";
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
class PostgresSchemaDeleteForeignRefused extends Data2.TaggedError("PostgresSchemaDeleteForeignRefused") {
|
|
414
|
+
get message() {
|
|
415
|
+
const proof = this.lastOid === undefined || this.lastOwner === undefined ? "delete carried no persisted oid/owner to prove ownership" : `the persisted proof is oid ${String(this.lastOid)}, owner "${this.lastOwner}"`;
|
|
416
|
+
return `Postgres.Schema "${this.schema}": the live schema in database "${this.database}" (oid ` + `${String(this.liveOid)}, owner "${this.liveOwner}") is not the one this resource created — ` + `${proof}. No DROP was issued: a schema with this name was created out of band. Drop it by ` + "hand, or adopt it on a resource whose declared props match it.";
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
class PostgresSchemaDatabaseRefused extends Data2.TaggedError("PostgresSchemaDatabaseRefused") {
|
|
421
|
+
get message() {
|
|
422
|
+
return `Postgres.Schema: the declared database changed from "${this.from}" to "${this.to}". A ` + "schema is never moved across databases — declare a new logical id in the new database and " + "remove the old one once its contents have moved.";
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
class PostgresSchemaExistsRefused extends Data2.TaggedError("PostgresSchemaExistsRefused") {
|
|
427
|
+
get message() {
|
|
428
|
+
return `Postgres.Schema "${this.schema}" in "${this.database}" already exists. Re-plan with ` + "--adopt (adopt(true)) to adopt it explicitly; crash recovery also requires adoption. " + "No existing schema was changed.";
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
class PostgresSchemaIdentityRefused extends Data2.TaggedError("PostgresSchemaIdentityRefused") {
|
|
433
|
+
get message() {
|
|
434
|
+
return `Postgres.Schema "${this.schema}": live oid ${String(this.liveOid)} differs from stored oid ` + `${String(this.storedOid)}. The schema was dropped and recreated outside this stack. ` + "Remove stale state and explicitly adopt the replacement before managing it.";
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/postgres/schema-identity.ts
|
|
439
|
+
var assertSchemaTarget = (props, output) => Effect6.gen(function* () {
|
|
440
|
+
if (output === undefined)
|
|
441
|
+
return;
|
|
442
|
+
if (props.name !== output.name)
|
|
443
|
+
return yield* Effect6.fail(new PostgresSchemaRenameRefused({ from: output.name, to: props.name }));
|
|
444
|
+
if (props.database !== output.database)
|
|
445
|
+
return yield* Effect6.fail(new PostgresSchemaDatabaseRefused({ from: output.database, to: props.database }));
|
|
446
|
+
});
|
|
447
|
+
var assertSchemaIdentity = (live, output) => live.oid === output.oid ? Effect6.void : Effect6.fail(new PostgresSchemaIdentityRefused({
|
|
448
|
+
schema: live.name,
|
|
449
|
+
storedOid: output.oid,
|
|
450
|
+
liveOid: live.oid
|
|
451
|
+
}));
|
|
452
|
+
|
|
453
|
+
// src/postgres/schema-diff.ts
|
|
454
|
+
import { isResolved as isResolved2 } from "alchemy/Diff";
|
|
455
|
+
import * as Effect7 from "effect/Effect";
|
|
456
|
+
var diffPostgresSchema = (news, output, olds) => Effect7.gen(function* () {
|
|
457
|
+
if (output === undefined || !isResolved2(news))
|
|
458
|
+
return;
|
|
459
|
+
yield* assertSchemaTarget(news, output);
|
|
460
|
+
const nameRefusal = schemaNameByteRefusal(news.name);
|
|
461
|
+
if (nameRefusal !== undefined) {
|
|
462
|
+
return yield* Effect7.fail(new PostgresSchemaNameRefused({ name: news.name, ...nameRefusal }));
|
|
463
|
+
}
|
|
464
|
+
const comment = normalizedComment(news.comment);
|
|
465
|
+
const changed = news.owner !== undefined && news.owner !== output.owner || comment !== undefined && comment !== (output.comment ?? undefined) || (news.cascade ?? false) !== (olds?.cascade ?? false);
|
|
466
|
+
return changed ? { action: "update" } : { action: "noop" };
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// src/postgres/schema-reconcile.ts
|
|
470
|
+
import * as Effect9 from "effect/Effect";
|
|
471
|
+
|
|
472
|
+
// src/postgres/schema-assert.ts
|
|
473
|
+
import * as Effect8 from "effect/Effect";
|
|
474
|
+
var assertDatabase = (props, pg) => Effect8.flatMap(currentDatabase(pg), (connected) => connected === props.database ? Effect8.void : Effect8.fail(new PostgresSchemaWrongDatabase({
|
|
475
|
+
schema: props.name,
|
|
476
|
+
declared: props.database,
|
|
477
|
+
connected
|
|
478
|
+
})));
|
|
479
|
+
var declaredOwner = (props, executingRole) => props.owner ?? executingRole;
|
|
480
|
+
var ownerDrift = (props, live, executingRole) => {
|
|
481
|
+
const declared = declaredOwner(props, executingRole);
|
|
482
|
+
return declared === live.owner ? undefined : { prop: "owner", declared, live: live.owner };
|
|
483
|
+
};
|
|
484
|
+
var liveDrift2 = (props, live, executingRole) => {
|
|
485
|
+
const owner = ownerDrift(props, live, executingRole);
|
|
486
|
+
if (owner !== undefined)
|
|
487
|
+
return owner;
|
|
488
|
+
const comment = normalizedComment(props.comment);
|
|
489
|
+
if (comment !== undefined && comment !== (live.comment ?? undefined)) {
|
|
490
|
+
return { prop: "comment", declared: comment, live: live.comment };
|
|
491
|
+
}
|
|
492
|
+
return;
|
|
493
|
+
};
|
|
494
|
+
var refuse = (props, drift) => Effect8.fail(new PostgresSchemaDrift({
|
|
495
|
+
schema: props.name,
|
|
496
|
+
prop: drift.prop,
|
|
497
|
+
declared: drift.declared,
|
|
498
|
+
live: drift.live
|
|
499
|
+
}));
|
|
500
|
+
var assertOwner = (props, row, executingRole) => {
|
|
501
|
+
const drift = ownerDrift(props, row, executingRole);
|
|
502
|
+
return drift === undefined ? Effect8.void : refuse(props, drift);
|
|
503
|
+
};
|
|
504
|
+
var assertLive = (props, row, executingRole) => {
|
|
505
|
+
const drift = liveDrift2(props, row, executingRole);
|
|
506
|
+
return drift === undefined ? Effect8.succeed(row) : refuse(props, drift);
|
|
507
|
+
};
|
|
508
|
+
var deleteForeignRefusal = (props, live, output) => {
|
|
509
|
+
if (output !== undefined && output.oid === live.oid && output.owner === live.owner) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
return new PostgresSchemaDeleteForeignRefused({
|
|
513
|
+
schema: props.name,
|
|
514
|
+
database: props.database,
|
|
515
|
+
liveOid: live.oid,
|
|
516
|
+
liveOwner: live.owner,
|
|
517
|
+
lastOid: output?.oid,
|
|
518
|
+
lastOwner: output?.owner
|
|
519
|
+
});
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
// src/postgres/schema-reconcile.ts
|
|
523
|
+
var reconcileWithClient2 = (pg, props, output) => Effect9.gen(function* () {
|
|
524
|
+
yield* assertSchemaTarget(props, output);
|
|
525
|
+
yield* assertDatabase(props, pg);
|
|
526
|
+
const executingRole = props.owner !== undefined ? props.owner : yield* currentUser(pg);
|
|
527
|
+
const observed = yield* selectSchema(pg, props.name);
|
|
528
|
+
if (observed === undefined) {
|
|
529
|
+
if (props.owner !== undefined) {
|
|
530
|
+
const ownerPresent = yield* roleExists(pg, props.owner);
|
|
531
|
+
if (!ownerPresent) {
|
|
532
|
+
return yield* Effect9.fail(new PostgresSchemaOwnerMissing({ schema: props.name, owner: props.owner }));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const createRefusal = (error) => isDuplicateSchemaError(error) ? new PostgresSchemaExistsRefused({ schema: props.name, database: props.database }) : error;
|
|
536
|
+
yield* pg.unsafe(buildCreateSchemaSql(props)).pipe(Effect9.asVoid, Effect9.catchTag("SqlError", (error) => Effect9.fail(createRefusal(error))));
|
|
537
|
+
const created = yield* selectSchema(pg, props.name);
|
|
538
|
+
if (created === undefined) {
|
|
539
|
+
return yield* Effect9.fail(new PostgresSchemaCreateVanished({ schema: props.name }));
|
|
540
|
+
}
|
|
541
|
+
yield* assertOwner(props, created, executingRole);
|
|
542
|
+
const comment = normalizedComment(props.comment);
|
|
543
|
+
if (comment !== undefined && comment !== (created.comment ?? undefined)) {
|
|
544
|
+
yield* pg.unsafe(buildCommentSchemaSql(props.name, comment)).pipe(Effect9.asVoid);
|
|
545
|
+
const commented = yield* selectSchema(pg, props.name);
|
|
546
|
+
if (commented === undefined) {
|
|
547
|
+
return yield* Effect9.fail(new PostgresSchemaCreateVanished({ schema: props.name }));
|
|
548
|
+
}
|
|
549
|
+
return yield* assertLive(props, commented, executingRole);
|
|
550
|
+
}
|
|
551
|
+
return yield* assertLive(props, created, executingRole);
|
|
552
|
+
}
|
|
553
|
+
if (output === undefined) {
|
|
554
|
+
return yield* Effect9.fail(new PostgresSchemaExistsRefused({ schema: props.name, database: props.database }));
|
|
555
|
+
}
|
|
556
|
+
yield* assertSchemaIdentity(observed, output);
|
|
557
|
+
return yield* assertLive(props, observed, executingRole);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// src/postgres/schema-delete.ts
|
|
561
|
+
import * as Effect10 from "effect/Effect";
|
|
562
|
+
var dropGuarded = (pg, props) => Effect10.gen(function* () {
|
|
563
|
+
if (props.cascade !== true) {
|
|
564
|
+
const empty = yield* schemaIsEmpty(pg, props.name);
|
|
565
|
+
if (!empty) {
|
|
566
|
+
return yield* Effect10.fail(new PostgresSchemaDropNotEmptyError({ schema: props.name }));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const dropRefusal = (error) => props.cascade !== true && isDependentObjectsError(error) ? new PostgresSchemaDropNotEmptyError({ schema: props.name }) : error;
|
|
570
|
+
yield* pg.unsafe(buildDropSchemaSql(props.name, props.cascade === true)).pipe(Effect10.asVoid, Effect10.catchTag("SqlError", (error) => Effect10.fail(dropRefusal(error))));
|
|
571
|
+
});
|
|
572
|
+
var deleteWithClient = (pg, props, output) => Effect10.gen(function* () {
|
|
573
|
+
yield* assertDatabase(props, pg);
|
|
574
|
+
const live = yield* selectSchema(pg, props.name);
|
|
575
|
+
if (live === undefined)
|
|
576
|
+
return;
|
|
577
|
+
const refusal = deleteForeignRefusal(props, live, output);
|
|
578
|
+
if (refusal !== undefined)
|
|
579
|
+
return yield* Effect10.fail(refusal);
|
|
580
|
+
yield* dropGuarded(pg, props);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// src/postgres/schema.ts
|
|
584
|
+
var PostgresSchema = Resource2("Postgres.Schema", {
|
|
585
|
+
defaultRemovalPolicy: "retain"
|
|
586
|
+
});
|
|
587
|
+
var isPostgresSchema = (value) => (typeof value === "object" || typeof value === "function") && value !== null && value.Type === "Postgres.Schema";
|
|
588
|
+
var readWithClient2 = (pg, name) => selectSchema(pg, name);
|
|
589
|
+
var postgresSchemaHandlers = PostgresSchema.Provider.of({
|
|
590
|
+
list: () => Effect11.succeed([]),
|
|
591
|
+
nuke: { skip: true },
|
|
592
|
+
read: ({ olds, output }) => Effect11.gen(function* () {
|
|
593
|
+
const name = output?.name ?? olds.name;
|
|
594
|
+
const database = output?.database ?? olds.database;
|
|
595
|
+
const present = yield* withPg((pg) => databaseExists(pg, database));
|
|
596
|
+
if (!present)
|
|
597
|
+
return;
|
|
598
|
+
const live = yield* withPg((pg) => readWithClient2(pg, name), database);
|
|
599
|
+
if (live !== undefined && live.database !== database) {
|
|
600
|
+
return yield* Effect11.fail(new PostgresSchemaWrongDatabase({
|
|
601
|
+
schema: name,
|
|
602
|
+
declared: database,
|
|
603
|
+
connected: live.database
|
|
604
|
+
}));
|
|
605
|
+
}
|
|
606
|
+
if (live !== undefined && output !== undefined)
|
|
607
|
+
yield* assertSchemaIdentity(live, output);
|
|
608
|
+
return live === undefined ? undefined : Unowned2(live);
|
|
609
|
+
}),
|
|
610
|
+
diff: ({ news, output, olds }) => diffPostgresSchema(news, output, olds),
|
|
611
|
+
reconcile: ({ news, output }) => Effect11.gen(function* () {
|
|
612
|
+
const nameRefusal = schemaNameByteRefusal(news.name);
|
|
613
|
+
if (nameRefusal !== undefined) {
|
|
614
|
+
return yield* Effect11.fail(new PostgresSchemaNameRefused({ name: news.name, ...nameRefusal }));
|
|
615
|
+
}
|
|
616
|
+
yield* assertSchemaTarget(news, output);
|
|
617
|
+
return yield* withPg((pg) => reconcileWithClient2(pg, news, output), news.database);
|
|
618
|
+
}),
|
|
619
|
+
delete: ({ olds, output }) => Effect11.gen(function* () {
|
|
620
|
+
const present = yield* withPg((pg) => databaseExists(pg, olds.database));
|
|
621
|
+
if (!present)
|
|
622
|
+
return;
|
|
623
|
+
return yield* withPg((pg) => deleteWithClient(pg, olds, output), olds.database);
|
|
624
|
+
})
|
|
625
|
+
});
|
|
626
|
+
var PostgresSchemaProvider = () => Provider2.succeed(PostgresSchema, postgresSchemaHandlers);
|
|
627
|
+
// src/postgres/grants-attrs.ts
|
|
628
|
+
var SCHEMA_PRIVILEGES = ["usage", "create"];
|
|
629
|
+
var TABLE_PRIVILEGES = [
|
|
630
|
+
"select",
|
|
631
|
+
"insert",
|
|
632
|
+
"update",
|
|
633
|
+
"delete",
|
|
634
|
+
"truncate",
|
|
635
|
+
"references",
|
|
636
|
+
"trigger",
|
|
637
|
+
"maintain"
|
|
638
|
+
];
|
|
639
|
+
var COLUMN_PRIVILEGES = ["select", "insert", "update", "references"];
|
|
640
|
+
var ALL_GRANT_WORDS = [...SCHEMA_PRIVILEGES, ...TABLE_PRIVILEGES];
|
|
641
|
+
var LETTER_FOR_PRIVILEGE = {
|
|
642
|
+
insert: "a",
|
|
643
|
+
select: "r",
|
|
644
|
+
update: "w",
|
|
645
|
+
delete: "d",
|
|
646
|
+
truncate: "D",
|
|
647
|
+
references: "x",
|
|
648
|
+
trigger: "t",
|
|
649
|
+
maintain: "m",
|
|
650
|
+
usage: "U",
|
|
651
|
+
create: "C"
|
|
652
|
+
};
|
|
653
|
+
var letterForPrivilege = (word) => {
|
|
654
|
+
const letter = LETTER_FOR_PRIVILEGE[word];
|
|
655
|
+
if (letter === undefined)
|
|
656
|
+
throw new Error(`grants-attrs: no ACL letter for "${word}"`);
|
|
657
|
+
return letter;
|
|
658
|
+
};
|
|
659
|
+
var grantsNameByteRefusal = (name) => {
|
|
660
|
+
const byteLength = utf8ByteLength(name);
|
|
661
|
+
return byteLength > POSTGRES_NAME_MAX_BYTES ? { byteLength, limit: POSTGRES_NAME_MAX_BYTES } : undefined;
|
|
662
|
+
};
|
|
663
|
+
// src/postgres/grants-sql.ts
|
|
664
|
+
var splitParts = (words) => {
|
|
665
|
+
const parts = [];
|
|
666
|
+
let optionCount = 0;
|
|
667
|
+
for (const word of words) {
|
|
668
|
+
const base = word.endsWith("*") ? word.slice(0, -1) : word;
|
|
669
|
+
if (!ALL_GRANT_WORDS.includes(base)) {
|
|
670
|
+
throw new Error(`grants-sql: "${base}" is not a privilege this family can grant — see the three ` + "vocabularies in grants-attrs.ts (traced to acl.h at REL_18_6)");
|
|
671
|
+
}
|
|
672
|
+
if (word.endsWith("*"))
|
|
673
|
+
optionCount += 1;
|
|
674
|
+
parts.push(base);
|
|
675
|
+
}
|
|
676
|
+
if (optionCount > 0 && optionCount < words.length) {
|
|
677
|
+
throw new Error(`grants-sql: a mixed grant-option list cannot be one GRANT statement: ${words.join(", ")}` + " — split it with splitGrantWords first (one option clause grants the option to every word)");
|
|
678
|
+
}
|
|
679
|
+
return { parts, anyOption: optionCount > 0 };
|
|
680
|
+
};
|
|
681
|
+
var optionClause = (anyOption) => anyOption ? " WITH GRANT OPTION" : "";
|
|
682
|
+
var grantTableSql = (schema, table, role, words) => {
|
|
683
|
+
const { parts, anyOption } = splitParts(words);
|
|
684
|
+
return `GRANT ${parts.join(", ")} ON ${quoteIdent(schema)}.${quoteIdent(table)} TO ${quoteIdent(role)}${optionClause(anyOption)}`;
|
|
685
|
+
};
|
|
686
|
+
var grantColumnSql = (schema, table, column, role, words) => {
|
|
687
|
+
const { parts, anyOption } = splitParts(words);
|
|
688
|
+
const columnList = `(${quoteIdent(column)})`;
|
|
689
|
+
return `GRANT ${parts.map((word) => `${word} ${columnList}`).join(", ")} ON ${quoteIdent(schema)}.${quoteIdent(table)} TO ${quoteIdent(role)}${optionClause(anyOption)}`;
|
|
690
|
+
};
|
|
691
|
+
var grantSchemaSql = (schema, role, words) => {
|
|
692
|
+
const { parts, anyOption } = splitParts(words);
|
|
693
|
+
return `GRANT ${parts.join(", ")} ON SCHEMA ${quoteIdent(schema)} TO ${quoteIdent(role)}${optionClause(anyOption)}`;
|
|
694
|
+
};
|
|
695
|
+
var grantDefaultSql = (schema, forRole, role, words) => {
|
|
696
|
+
const { parts, anyOption } = splitParts(words);
|
|
697
|
+
return `ALTER DEFAULT PRIVILEGES FOR ROLE ${quoteIdent(forRole)} IN SCHEMA ${quoteIdent(schema)} GRANT ${parts.join(", ")} ON TABLES TO ${quoteIdent(role)}${optionClause(anyOption)}`;
|
|
698
|
+
};
|
|
699
|
+
var revokeTableSql = (schema, table, role) => `REVOKE ALL ON ${quoteIdent(schema)}.${quoteIdent(table)} FROM ${quoteIdent(role)}`;
|
|
700
|
+
var revokeColumnSql = (schema, table, column, role) => `REVOKE ALL (${quoteIdent(column)}) ON ${quoteIdent(schema)}.${quoteIdent(table)} FROM ${quoteIdent(role)}`;
|
|
701
|
+
var revokeSchemaSql = (schema, role) => `REVOKE ALL ON SCHEMA ${quoteIdent(schema)} FROM ${quoteIdent(role)}`;
|
|
702
|
+
var revokeDefaultSql = (schema, forRole, role) => `ALTER DEFAULT PRIVILEGES FOR ROLE ${quoteIdent(forRole)} IN SCHEMA ${quoteIdent(schema)} REVOKE ALL ON TABLES FROM ${quoteIdent(role)}`;
|
|
703
|
+
var revokePublicSchemaSql = (schema) => `REVOKE ALL ON SCHEMA ${quoteIdent(schema)} FROM PUBLIC`;
|
|
704
|
+
var revokePublicTablesSql = (schema) => `REVOKE ALL ON ALL TABLES IN SCHEMA ${quoteIdent(schema)} FROM PUBLIC`;
|
|
705
|
+
// src/postgres/grants.ts
|
|
706
|
+
import { Resource as Resource3 } from "alchemy";
|
|
707
|
+
import { Unowned as Unowned3 } from "alchemy/AdoptPolicy";
|
|
708
|
+
import * as Provider3 from "alchemy/Provider";
|
|
709
|
+
import * as Effect17 from "effect/Effect";
|
|
710
|
+
|
|
711
|
+
// src/postgres/grants-declare.ts
|
|
712
|
+
var cleanWords = (words) => [...new Set(words)].sort();
|
|
713
|
+
var baseWord = (word) => word.endsWith("*") ? word.slice(0, -1) : word;
|
|
714
|
+
var resolveProps = (props) => ({
|
|
715
|
+
role: props.role,
|
|
716
|
+
database: props.database,
|
|
717
|
+
schema: props.schema,
|
|
718
|
+
schemaPrivileges: cleanWords([
|
|
719
|
+
...props.schemaUsage === false ? [] : ["usage"],
|
|
720
|
+
...props.schemaCreate === true ? ["create"] : []
|
|
721
|
+
]),
|
|
722
|
+
tables: [...props.tables ?? []].map((table) => ({ table: table.table, privileges: cleanWords(table.privileges) })).sort((a, b) => a.table.localeCompare(b.table)),
|
|
723
|
+
columns: [...props.columnGrants ?? []].map((column) => ({
|
|
724
|
+
table: column.table,
|
|
725
|
+
column: column.column,
|
|
726
|
+
privileges: cleanWords(column.privileges)
|
|
727
|
+
})).sort((a, b) => a.table.localeCompare(b.table) || a.column.localeCompare(b.column)),
|
|
728
|
+
defaults: [...props.defaultPrivileges ?? []].map((entry) => ({ forRole: entry.forRole, privileges: cleanWords(entry.privileges) })).sort((a, b) => a.forRole.localeCompare(b.forRole)),
|
|
729
|
+
publicSchemaRevoked: props.revokeFromPublic === true,
|
|
730
|
+
publicTablesRevoked: props.revokeFromPublic === true
|
|
731
|
+
});
|
|
732
|
+
var splitGrantWords = (words) => ({
|
|
733
|
+
plain: words.filter((word) => !word.endsWith("*")),
|
|
734
|
+
grantable: words.filter((word) => word.endsWith("*"))
|
|
735
|
+
});
|
|
736
|
+
var declaredNames = (declared) => ({
|
|
737
|
+
role: declared.role,
|
|
738
|
+
database: declared.database,
|
|
739
|
+
schema: declared.schema,
|
|
740
|
+
tables: declared.tables.map((table) => table.table),
|
|
741
|
+
columns: declared.columns.map(({ table, column }) => ({ table, column })),
|
|
742
|
+
defaults: declared.defaults.map((entry) => entry.forRole)
|
|
743
|
+
});
|
|
744
|
+
var namesFromAttrs = (attrs) => ({
|
|
745
|
+
role: attrs.role,
|
|
746
|
+
database: attrs.database,
|
|
747
|
+
schema: attrs.schema,
|
|
748
|
+
tables: attrs.tables.map((table) => table.table),
|
|
749
|
+
columns: attrs.columns.map(({ table, column }) => ({ table, column })),
|
|
750
|
+
defaults: attrs.defaults.map((entry) => entry.forRole)
|
|
751
|
+
});
|
|
752
|
+
var clearedDeclaration = (declared) => ({
|
|
753
|
+
...declared,
|
|
754
|
+
schemaPrivileges: [],
|
|
755
|
+
tables: declared.tables.map((table) => ({ table: table.table, privileges: [] })),
|
|
756
|
+
columns: declared.columns.map((column) => ({
|
|
757
|
+
table: column.table,
|
|
758
|
+
column: column.column,
|
|
759
|
+
privileges: []
|
|
760
|
+
})),
|
|
761
|
+
defaults: declared.defaults.map((entry) => ({ forRole: entry.forRole, privileges: [] })),
|
|
762
|
+
publicSchemaRevoked: false,
|
|
763
|
+
publicTablesRevoked: false
|
|
764
|
+
});
|
|
765
|
+
var removedNames = (output, declared) => {
|
|
766
|
+
if (output === undefined)
|
|
767
|
+
return;
|
|
768
|
+
const keepTables = new Set(declared.tables.map((table) => table.table));
|
|
769
|
+
const keepColumns = new Set(declared.columns.map((column) => `${column.table}\x00${column.column}`));
|
|
770
|
+
const keepDefaults = new Set(declared.defaults.map((entry) => entry.forRole));
|
|
771
|
+
const tables = output.tables.filter((table) => !keepTables.has(table.table)).map((table) => ({ table: table.table, privileges: [] }));
|
|
772
|
+
const columns = output.columns.filter((column) => !keepColumns.has(`${column.table}\x00${column.column}`)).map((column) => ({
|
|
773
|
+
table: column.table,
|
|
774
|
+
column: column.column,
|
|
775
|
+
privileges: []
|
|
776
|
+
}));
|
|
777
|
+
const defaults = output.defaults.filter((entry) => !keepDefaults.has(entry.forRole)).map((entry) => ({ forRole: entry.forRole, privileges: [] }));
|
|
778
|
+
if (tables.length === 0 && columns.length === 0 && defaults.length === 0)
|
|
779
|
+
return;
|
|
780
|
+
return { schema: declared.schema, role: declared.role, tables, columns, defaults };
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
// src/postgres/grants-ops.ts
|
|
784
|
+
import * as Effect16 from "effect/Effect";
|
|
785
|
+
import { isResolved as isResolved3 } from "alchemy/Diff";
|
|
786
|
+
|
|
787
|
+
// src/postgres/grants-refuse.ts
|
|
788
|
+
var wordRefusal = (words, vocabulary) => {
|
|
789
|
+
for (const word of words) {
|
|
790
|
+
if (word === "*")
|
|
791
|
+
return word;
|
|
792
|
+
const bare = baseWord(word);
|
|
793
|
+
if (!vocabulary.includes(bare))
|
|
794
|
+
return bare;
|
|
795
|
+
}
|
|
796
|
+
return;
|
|
797
|
+
};
|
|
798
|
+
var listRefusal = (words, vocabulary, prop) => {
|
|
799
|
+
const word = wordRefusal(words, vocabulary);
|
|
800
|
+
if (word !== undefined)
|
|
801
|
+
return { kind: "privilege", prop, word };
|
|
802
|
+
const seen = new Set;
|
|
803
|
+
for (const entry of words) {
|
|
804
|
+
const base = baseWord(entry);
|
|
805
|
+
if (seen.has(base))
|
|
806
|
+
return { kind: "duplicate", prop, name: base };
|
|
807
|
+
seen.add(base);
|
|
808
|
+
}
|
|
809
|
+
return;
|
|
810
|
+
};
|
|
811
|
+
var declarationRefusal = (declared) => {
|
|
812
|
+
const schema = listRefusal(declared.schemaPrivileges, SCHEMA_PRIVILEGES, "schemaPrivileges");
|
|
813
|
+
if (schema !== undefined)
|
|
814
|
+
return schema;
|
|
815
|
+
for (const table2 of declared.tables) {
|
|
816
|
+
const refusal = listRefusal(table2.privileges, TABLE_PRIVILEGES, `tables.${table2.table}`);
|
|
817
|
+
if (refusal !== undefined)
|
|
818
|
+
return refusal;
|
|
819
|
+
}
|
|
820
|
+
for (const column2 of declared.columns) {
|
|
821
|
+
const refusal = listRefusal(column2.privileges, COLUMN_PRIVILEGES, `columnGrants.${column2.table}.${column2.column}`);
|
|
822
|
+
if (refusal !== undefined)
|
|
823
|
+
return refusal;
|
|
824
|
+
}
|
|
825
|
+
for (const entry of declared.defaults) {
|
|
826
|
+
const refusal = listRefusal(entry.privileges, TABLE_PRIVILEGES, `defaultPrivileges.${entry.forRole}`);
|
|
827
|
+
if (refusal !== undefined)
|
|
828
|
+
return refusal;
|
|
829
|
+
}
|
|
830
|
+
const duplicate = (names) => {
|
|
831
|
+
const seen = new Set;
|
|
832
|
+
for (const name of names) {
|
|
833
|
+
if (seen.has(name))
|
|
834
|
+
return name;
|
|
835
|
+
seen.add(name);
|
|
836
|
+
}
|
|
837
|
+
return;
|
|
838
|
+
};
|
|
839
|
+
const table = duplicate(declared.tables.map((entry) => entry.table));
|
|
840
|
+
if (table !== undefined)
|
|
841
|
+
return { kind: "duplicate", prop: "tables", name: table };
|
|
842
|
+
const column = duplicate(declared.columns.map((entry) => `${entry.table}\x00${entry.column}`));
|
|
843
|
+
if (column !== undefined)
|
|
844
|
+
return { kind: "duplicate", prop: "columnGrants", name: column.replace("\x00", ".") };
|
|
845
|
+
const forRole = duplicate(declared.defaults.map((entry) => entry.forRole));
|
|
846
|
+
if (forRole !== undefined)
|
|
847
|
+
return { kind: "duplicate", prop: "defaultPrivileges", name: forRole };
|
|
848
|
+
return;
|
|
849
|
+
};
|
|
850
|
+
var grantsNamesRefusal = (declared) => {
|
|
851
|
+
const names = [
|
|
852
|
+
declared.role,
|
|
853
|
+
declared.database,
|
|
854
|
+
declared.schema,
|
|
855
|
+
...declared.tables.map((table) => table.table),
|
|
856
|
+
...declared.columns.flatMap((column) => [column.table, column.column]),
|
|
857
|
+
...declared.defaults.map((entry) => entry.forRole)
|
|
858
|
+
];
|
|
859
|
+
for (const name of names) {
|
|
860
|
+
const refusal = grantsNameByteRefusal(name);
|
|
861
|
+
if (refusal !== undefined)
|
|
862
|
+
return { name, ...refusal };
|
|
863
|
+
}
|
|
864
|
+
return;
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
// src/postgres/grants-plan-restore.ts
|
|
868
|
+
var tableWords = (live, table) => live.tables.find((entry) => entry.table === table)?.role ?? [];
|
|
869
|
+
var columnWords = (live, table, column) => live.columns.find((entry) => entry.table === table && entry.column === column)?.role ?? [];
|
|
870
|
+
var removalRevokes = (removed, live) => {
|
|
871
|
+
const ownedTables = new Set(live.ownedTables);
|
|
872
|
+
return {
|
|
873
|
+
tables: removed.tables.filter((table) => !ownedTables.has(table.table) && tableWords(live, table.table).length > 0).map((table) => table.table),
|
|
874
|
+
columns: removed.columns.filter((column) => !ownedTables.has(column.table) && columnWords(live, column.table, column.column).length > 0).map(({ table, column }) => ({ table, column }))
|
|
875
|
+
};
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
// src/postgres/grants-words.ts
|
|
879
|
+
var encodeWord = (row) => `${row.privilege.toLowerCase()}${row.grantable ? "*" : ""}`;
|
|
880
|
+
var wordsOf = (rows, wantPublic) => [...new Set(rows.filter((row) => row.public === wantPublic).map(encodeWord))].sort();
|
|
881
|
+
var PUBLIC_REVOCABLE_RELKINDS = ["r", "v", "m", "f", "p"];
|
|
882
|
+
var relkindIsPublicRevocable = (relkind) => PUBLIC_REVOCABLE_RELKINDS.includes(relkind);
|
|
883
|
+
|
|
884
|
+
// src/postgres/grants-plan.ts
|
|
885
|
+
var wordsDiffer = (declared, live) => declared.length !== live.length || declared.some((word, index) => word !== live[index]);
|
|
886
|
+
var grantStatements = (words, grant) => {
|
|
887
|
+
const { plain, grantable } = splitGrantWords(words);
|
|
888
|
+
return [
|
|
889
|
+
...plain.length > 0 ? [grant(plain)] : [],
|
|
890
|
+
...grantable.length > 0 ? [grant(grantable)] : []
|
|
891
|
+
];
|
|
892
|
+
};
|
|
893
|
+
var liveTableWords = (live, table) => live.tables.find((entry) => entry.table === table)?.role ?? [];
|
|
894
|
+
var liveColumnWords = (live, table, column) => live.columns.find((entry) => entry.table === table && entry.column === column)?.role ?? [];
|
|
895
|
+
var liveDefaultWords = (live, forRole) => live.defaults.find((entry) => entry.forRole === forRole)?.role ?? [];
|
|
896
|
+
var columnKey = (table, column) => `${table}\x00${column}`;
|
|
897
|
+
var restoredColumnGrants = (declared, live, table, skip) => {
|
|
898
|
+
const statements = [];
|
|
899
|
+
for (const column of live.columns) {
|
|
900
|
+
if (column.table !== table || skip.has(columnKey(column.table, column.column)))
|
|
901
|
+
continue;
|
|
902
|
+
if (column.restorable.length === 0)
|
|
903
|
+
continue;
|
|
904
|
+
statements.push(...grantStatements(column.restorable, (words) => grantColumnSql(declared.schema, column.table, column.column, declared.role, words)));
|
|
905
|
+
}
|
|
906
|
+
return statements;
|
|
907
|
+
};
|
|
908
|
+
var planRepair = (declared, live, prior = {}) => {
|
|
909
|
+
const statements = [];
|
|
910
|
+
const ownedTables = new Set(live.ownedTables);
|
|
911
|
+
if (!live.schemaOwnedByRole && wordsDiffer(declared.schemaPrivileges, live.schema.role)) {
|
|
912
|
+
statements.push(revokeSchemaSql(declared.schema, declared.role));
|
|
913
|
+
statements.push(...grantStatements(declared.schemaPrivileges, (words) => grantSchemaSql(declared.schema, declared.role, words)));
|
|
914
|
+
}
|
|
915
|
+
const revokedTables = new Set(prior.revokedTables ?? []);
|
|
916
|
+
const skipColumns = new Set([
|
|
917
|
+
...declared.columns.map((column) => columnKey(column.table, column.column)),
|
|
918
|
+
...(prior.revokedColumns ?? []).map((column) => columnKey(column.table, column.column))
|
|
919
|
+
]);
|
|
920
|
+
const declaredTableNames = new Set(declared.tables.map((table) => table.table));
|
|
921
|
+
for (const table of declared.tables) {
|
|
922
|
+
if (ownedTables.has(table.table))
|
|
923
|
+
continue;
|
|
924
|
+
if (wordsDiffer(table.privileges, liveTableWords(live, table.table))) {
|
|
925
|
+
revokedTables.add(table.table);
|
|
926
|
+
statements.push(revokeTableSql(declared.schema, table.table, declared.role));
|
|
927
|
+
statements.push(...grantStatements(table.privileges, (words) => grantTableSql(declared.schema, table.table, declared.role, words)));
|
|
928
|
+
statements.push(...restoredColumnGrants(declared, live, table.table, skipColumns));
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
for (const table of prior.revokedTables ?? []) {
|
|
932
|
+
if (declaredTableNames.has(table) || ownedTables.has(table))
|
|
933
|
+
continue;
|
|
934
|
+
statements.push(...restoredColumnGrants(declared, live, table, skipColumns));
|
|
935
|
+
}
|
|
936
|
+
for (const column of declared.columns) {
|
|
937
|
+
if (ownedTables.has(column.table))
|
|
938
|
+
continue;
|
|
939
|
+
const liveWords = revokedTables.has(column.table) ? [] : liveColumnWords(live, column.table, column.column);
|
|
940
|
+
if (wordsDiffer(column.privileges, liveWords)) {
|
|
941
|
+
statements.push(revokeColumnSql(declared.schema, column.table, column.column, declared.role));
|
|
942
|
+
statements.push(...grantStatements(column.privileges, (words) => grantColumnSql(declared.schema, column.table, column.column, declared.role, words)));
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
for (const entry of declared.defaults) {
|
|
946
|
+
if (wordsDiffer(entry.privileges, liveDefaultWords(live, entry.forRole))) {
|
|
947
|
+
statements.push(revokeDefaultSql(declared.schema, entry.forRole, declared.role));
|
|
948
|
+
statements.push(...grantStatements(entry.privileges, (words) => grantDefaultSql(declared.schema, entry.forRole, declared.role, words)));
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (declared.publicSchemaRevoked && live.schema.public.length > 0) {
|
|
952
|
+
statements.push(revokePublicSchemaSql(declared.schema));
|
|
953
|
+
}
|
|
954
|
+
if (declared.publicTablesRevoked && (live.tables.some((table) => relkindIsPublicRevocable(table.relkind) && table.public.length > 0) || live.columns.some((column) => column.public.length > 0))) {
|
|
955
|
+
statements.push(revokePublicTablesSql(declared.schema));
|
|
956
|
+
}
|
|
957
|
+
return statements;
|
|
958
|
+
};
|
|
959
|
+
var planRevocations = (removed, live) => {
|
|
960
|
+
const statements = [];
|
|
961
|
+
const revoked = removalRevokes(removed, live);
|
|
962
|
+
for (const table of revoked.tables) {
|
|
963
|
+
statements.push(revokeTableSql(removed.schema, table, removed.role));
|
|
964
|
+
}
|
|
965
|
+
for (const column of revoked.columns) {
|
|
966
|
+
statements.push(revokeColumnSql(removed.schema, column.table, column.column, removed.role));
|
|
967
|
+
}
|
|
968
|
+
for (const entry of removed.defaults) {
|
|
969
|
+
if (liveDefaultWords(live, entry.forRole).length > 0) {
|
|
970
|
+
statements.push(revokeDefaultSql(removed.schema, entry.forRole, removed.role));
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return statements;
|
|
974
|
+
};
|
|
975
|
+
var planReconcile = (declared, live, removed) => {
|
|
976
|
+
const revoked = removed === undefined ? undefined : removalRevokes(removed, live);
|
|
977
|
+
return [
|
|
978
|
+
...removed !== undefined ? planRevocations(removed, live) : [],
|
|
979
|
+
...planRepair(declared, live, {
|
|
980
|
+
revokedTables: revoked?.tables ?? [],
|
|
981
|
+
revokedColumns: removed?.columns ?? []
|
|
982
|
+
})
|
|
983
|
+
];
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
// src/postgres/grants-diff.ts
|
|
987
|
+
var attributesOf = (live, names) => ({
|
|
988
|
+
role: names.role,
|
|
989
|
+
database: names.database,
|
|
990
|
+
schema: names.schema,
|
|
991
|
+
schemaPrivileges: live.schema.role,
|
|
992
|
+
tables: names.tables.map((table) => ({
|
|
993
|
+
table,
|
|
994
|
+
privileges: liveTableWords(live, table)
|
|
995
|
+
})),
|
|
996
|
+
columns: names.columns.map(({ table, column }) => ({
|
|
997
|
+
table,
|
|
998
|
+
column,
|
|
999
|
+
privileges: liveColumnWords(live, table, column)
|
|
1000
|
+
})),
|
|
1001
|
+
defaults: names.defaults.map((forRole) => ({
|
|
1002
|
+
forRole,
|
|
1003
|
+
privileges: liveDefaultWords(live, forRole)
|
|
1004
|
+
})),
|
|
1005
|
+
publicSchemaRevoked: live.schema.public.length === 0,
|
|
1006
|
+
publicTablesRevoked: live.tables.every((table) => !relkindIsPublicRevocable(table.relkind) || table.public.length === 0) && live.columns.every((column) => column.public.length === 0),
|
|
1007
|
+
schemaOwnedByRole: live.schemaOwnedByRole,
|
|
1008
|
+
ownedTables: names.tables.filter((table) => live.ownedTables.includes(table))
|
|
1009
|
+
});
|
|
1010
|
+
var grantsDiffer = (declared, live) => {
|
|
1011
|
+
if (declared.role !== live.role || declared.database !== live.database || declared.schema !== live.schema) {
|
|
1012
|
+
return true;
|
|
1013
|
+
}
|
|
1014
|
+
if (declared.publicSchemaRevoked && !live.publicSchemaRevoked)
|
|
1015
|
+
return true;
|
|
1016
|
+
if (declared.publicTablesRevoked && !live.publicTablesRevoked)
|
|
1017
|
+
return true;
|
|
1018
|
+
if (!live.schemaOwnedByRole && wordsDiffer(declared.schemaPrivileges, live.schemaPrivileges)) {
|
|
1019
|
+
return true;
|
|
1020
|
+
}
|
|
1021
|
+
if (declared.tables.length !== live.tables.length)
|
|
1022
|
+
return true;
|
|
1023
|
+
const ownedTables = new Set(live.ownedTables);
|
|
1024
|
+
if (declared.tables.some((table, index) => table.table !== (live.tables[index]?.table ?? null) || !ownedTables.has(table.table) && wordsDiffer(table.privileges, live.tables[index]?.privileges ?? []))) {
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
if (declared.columns.length !== live.columns.length)
|
|
1028
|
+
return true;
|
|
1029
|
+
if (declared.columns.some((column, index) => column.table !== (live.columns[index]?.table ?? null) || column.column !== (live.columns[index]?.column ?? null) || !ownedTables.has(column.table) && wordsDiffer(column.privileges, live.columns[index]?.privileges ?? []))) {
|
|
1030
|
+
return true;
|
|
1031
|
+
}
|
|
1032
|
+
if (declared.defaults.length !== live.defaults.length)
|
|
1033
|
+
return true;
|
|
1034
|
+
if (declared.defaults.some((entry, index) => entry.forRole !== (live.defaults[index]?.forRole ?? null) || wordsDiffer(entry.privileges, live.defaults[index]?.privileges ?? []))) {
|
|
1035
|
+
return true;
|
|
1036
|
+
}
|
|
1037
|
+
return false;
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
// src/postgres/grants-existence.ts
|
|
1041
|
+
import * as Effect12 from "effect/Effect";
|
|
1042
|
+
|
|
1043
|
+
// src/postgres/grants-errors.ts
|
|
1044
|
+
import * as Data3 from "effect/Data";
|
|
1045
|
+
|
|
1046
|
+
class PostgresGrantsPrivilegeRefused extends Data3.TaggedError("PostgresGrantsPrivilegeRefused") {
|
|
1047
|
+
get message() {
|
|
1048
|
+
return `Postgres.Grants: "${this.prop}" declares "${this.word}", which is not in the vocabulary ` + "PostgreSQL 18 grants on that object class (see grants-attrs.ts for the three sets, traced " + "to acl.h at REL_18_6). Column grants take select/insert/update/references only — TRUNCATE, " + "TRIGGER and MAINTAIN are table-wide.";
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
class PostgresGrantsNameRefused extends Data3.TaggedError("PostgresGrantsNameRefused") {
|
|
1053
|
+
get message() {
|
|
1054
|
+
return `Postgres.Grants: the name "${this.name}" is ${String(this.byteLength)} UTF-8 bytes, over the ` + `${String(this.limit)}-byte limit (NAMEDATALEN 64, minus the terminator). PostgreSQL would ` + "accept the statement and silently truncate the name with only a NOTICE, so this plan refuses " + "it instead of granting on an object no later read will ever find again.";
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
class PostgresGrantsDuplicateObject extends Data3.TaggedError("PostgresGrantsDuplicateObject") {
|
|
1059
|
+
get message() {
|
|
1060
|
+
return `Postgres.Grants: "${this.prop}" declares "${this.name}" twice. Merge the entries into one — ` + "the declaration must state each object at most once, and each privilege word in exactly one " + "form: plain, or WITH GRANT OPTION (`select` and `select*` cannot both hold, the server keeps " + "one aclitem per grantee per grantor).";
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
class PostgresGrantsRetargetRefused extends Data3.TaggedError("PostgresGrantsRetargetRefused") {
|
|
1065
|
+
get message() {
|
|
1066
|
+
return `Postgres.Grants: the declared ${this.prop} changed from "${this.from}" to "${this.to}". A ` + "retarget is refused at plan — declare a new logical id for the new target and delete the old " + "one (its retain policy keeps the grants until a destroy revokes them).";
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
class PostgresGrantsDatabaseMismatch extends Data3.TaggedError("PostgresGrantsDatabaseMismatch") {
|
|
1071
|
+
get message() {
|
|
1072
|
+
return `Postgres.Grants: the declaration names database "${this.declared}" but ` + `current_database() is "${this.connected}". The handlers open the declared database and ` + "prove it before any GRANT or REVOKE — a mismatch means that open did not land there.";
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
class PostgresGrantsSchemaMissing extends Data3.TaggedError("PostgresGrantsSchemaMissing") {
|
|
1077
|
+
get message() {
|
|
1078
|
+
return `Postgres.Grants: schema "${this.schema}" is not in pg_namespace. Grants are written into a ` + "schema that already exists — declare a Postgres.Schema (or create it by hand) before the " + "grant set that scopes to it.";
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
class PostgresGrantsDatabaseMissing extends Data3.TaggedError("PostgresGrantsDatabaseMissing") {
|
|
1083
|
+
get message() {
|
|
1084
|
+
return `Postgres.Grants: database "${this.database}" is not in pg_database. Grants are written into ` + "a database that already exists — declare a Postgres.Database (or create it by hand) before " + "the grant set that scopes to it.";
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
class PostgresGrantsRoleMissing extends Data3.TaggedError("PostgresGrantsRoleMissing") {
|
|
1089
|
+
get message() {
|
|
1090
|
+
return `Postgres.Grants: role "${this.role}" is not in pg_roles. GRANT and ALTER DEFAULT PRIVILEGES ` + "would fail against it — create the role (a group role per seat, membership owned by a human) " + "before declaring its grants.";
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
class PostgresGrantsTableMissing extends Data3.TaggedError("PostgresGrantsTableMissing") {
|
|
1095
|
+
get message() {
|
|
1096
|
+
return `Postgres.Grants: table "${this.schema}"."${this.table}" does not exist. The declaration ` + "names a relation before it is created; create the table (or fix the name) before granting.";
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
class PostgresGrantsColumnMissing extends Data3.TaggedError("PostgresGrantsColumnMissing") {
|
|
1101
|
+
get message() {
|
|
1102
|
+
return `Postgres.Grants: column "${this.column}" of "${this.schema}"."${this.table}" does not ` + "exist. Declare the column (or fix its name) before granting on it.";
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
class PostgresGrantsRepairRefused extends Data3.TaggedError("PostgresGrantsRepairRefused") {
|
|
1107
|
+
get message() {
|
|
1108
|
+
return `Postgres.Grants: the repair on schema "${this.schema}" for role "${this.role}" re-read as ` + `still drifted; these statements would run again: ${this.remaining.join(" ; ")}. A grant made ` + "by another role survives this resource’s REVOKE (only its grantor or the object’s " + "owner can revoke it) — revoke it as that grantor or the owner, or declare the privilege so " + "the set matches.";
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// src/postgres/grants-existence.ts
|
|
1113
|
+
var TABLES_PRESENT_SQL = `SELECT
|
|
1114
|
+
c.relname AS table
|
|
1115
|
+
FROM pg_class c
|
|
1116
|
+
WHERE c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
|
|
1117
|
+
AND c.relname::text IN (SELECT json_array_elements_text($2::json))`;
|
|
1118
|
+
var findMissingTable = (pg, schema, tables) => tables.length === 0 ? Effect12.succeed(undefined) : Effect12.map(pg.unsafe(TABLES_PRESENT_SQL, [schema, JSON.stringify(tables)]), (rows) => {
|
|
1119
|
+
const present = new Set(rows.map((row) => row.table));
|
|
1120
|
+
return tables.find((table) => !present.has(table));
|
|
1121
|
+
});
|
|
1122
|
+
var COLUMNS_PRESENT_SQL = `SELECT
|
|
1123
|
+
c.relname AS table,
|
|
1124
|
+
a.attname AS column
|
|
1125
|
+
FROM pg_attribute a
|
|
1126
|
+
JOIN pg_class c ON c.oid = a.attrelid
|
|
1127
|
+
WHERE c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
|
|
1128
|
+
AND a.attnum > 0
|
|
1129
|
+
AND NOT a.attisdropped
|
|
1130
|
+
AND (c.relname, a.attname) IN (
|
|
1131
|
+
SELECT x->>0, x->>1 FROM json_array_elements($2::json) x
|
|
1132
|
+
)`;
|
|
1133
|
+
var findMissingColumn = (pg, schema, columns) => columns.length === 0 ? Effect12.succeed(undefined) : Effect12.map(pg.unsafe(COLUMNS_PRESENT_SQL, [
|
|
1134
|
+
schema,
|
|
1135
|
+
JSON.stringify(columns.map(({ table, column }) => [table, column]))
|
|
1136
|
+
]), (rows) => {
|
|
1137
|
+
const present = new Set(rows.map((row) => `${row.table}\x00${row.column}`));
|
|
1138
|
+
return columns.find(({ table, column }) => !present.has(`${table}\x00${column}`));
|
|
1139
|
+
});
|
|
1140
|
+
var requireDeclaredObjectsExist = (pg, declared) => Effect12.gen(function* () {
|
|
1141
|
+
const missingTable = yield* findMissingTable(pg, declared.schema, declared.tables.map((table) => table.table));
|
|
1142
|
+
if (missingTable !== undefined) {
|
|
1143
|
+
return yield* Effect12.fail(new PostgresGrantsTableMissing({ schema: declared.schema, table: missingTable }));
|
|
1144
|
+
}
|
|
1145
|
+
const missingColumn = yield* findMissingColumn(pg, declared.schema, declared.columns.map((column) => ({ table: column.table, column: column.column })));
|
|
1146
|
+
if (missingColumn !== undefined) {
|
|
1147
|
+
return yield* Effect12.fail(new PostgresGrantsColumnMissing({
|
|
1148
|
+
schema: declared.schema,
|
|
1149
|
+
table: missingColumn.table,
|
|
1150
|
+
column: missingColumn.column
|
|
1151
|
+
}));
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
// src/postgres/grants-read.ts
|
|
1156
|
+
import * as Effect15 from "effect/Effect";
|
|
1157
|
+
|
|
1158
|
+
// src/postgres/grants-ownership.ts
|
|
1159
|
+
import * as Effect13 from "effect/Effect";
|
|
1160
|
+
var SCHEMA_OWNER_SQL = `SELECT
|
|
1161
|
+
n.nspowner = (SELECT oid FROM pg_roles WHERE rolname = $2) AS role_owns
|
|
1162
|
+
FROM pg_namespace n
|
|
1163
|
+
WHERE n.nspname = $1`;
|
|
1164
|
+
var readSchemaOwnership = (pg, schema, role) => Effect13.map(pg.unsafe(SCHEMA_OWNER_SQL, [schema, role]), (rows) => rows[0]?.role_owns === true);
|
|
1165
|
+
var OWNED_TABLES_SQL = `SELECT c.relname AS table
|
|
1166
|
+
FROM pg_class c
|
|
1167
|
+
WHERE c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
|
|
1168
|
+
AND c.relowner = (SELECT oid FROM pg_roles WHERE rolname = $2)
|
|
1169
|
+
ORDER BY c.relname`;
|
|
1170
|
+
var readOwnedTables = (pg, schema, role) => Effect13.map(pg.unsafe(OWNED_TABLES_SQL, [schema, role]), (rows) => rows.map((row) => row.table).sort((a, b) => a.localeCompare(b)));
|
|
1171
|
+
|
|
1172
|
+
// src/postgres/grants-read-defaults.ts
|
|
1173
|
+
import * as Effect14 from "effect/Effect";
|
|
1174
|
+
var DEFAULTS_SQL = `SELECT
|
|
1175
|
+
pg_get_userbyid(d.defaclrole) AS for_role,
|
|
1176
|
+
a.privilege_type AS privilege,
|
|
1177
|
+
a.is_grantable AS grantable
|
|
1178
|
+
FROM pg_default_acl d
|
|
1179
|
+
CROSS JOIN LATERAL aclexplode(d.defaclacl) AS a
|
|
1180
|
+
WHERE d.defaclnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
|
|
1181
|
+
AND d.defaclobjtype = 'r'
|
|
1182
|
+
AND a.grantee = (SELECT oid FROM pg_roles WHERE rolname = $2)
|
|
1183
|
+
ORDER BY for_role`;
|
|
1184
|
+
var readDefaultAcls = (pg, schema, role) => Effect14.map(pg.unsafe(DEFAULTS_SQL, [schema, role]), (rows) => {
|
|
1185
|
+
const byRole = new Map;
|
|
1186
|
+
for (const row of rows) {
|
|
1187
|
+
const words = byRole.get(row.for_role) ?? [];
|
|
1188
|
+
words.push(encodeWord(row));
|
|
1189
|
+
byRole.set(row.for_role, words);
|
|
1190
|
+
}
|
|
1191
|
+
return [...byRole.entries()].map(([forRole, words]) => ({ forRole, role: [...new Set(words)].sort() })).sort((a, b) => a.forRole.localeCompare(b.forRole));
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
// src/postgres/grants-read.ts
|
|
1195
|
+
var SCHEMA_PRESENT_SQL = "SELECT 1 AS present FROM pg_namespace WHERE nspname = $1";
|
|
1196
|
+
var schemaExists = (pg, schema) => Effect15.map(pg.unsafe(SCHEMA_PRESENT_SQL, [schema]), (rows) => rows.length > 0);
|
|
1197
|
+
var SCHEMA_ACL_SQL = `SELECT
|
|
1198
|
+
a.grantee = 0 AS public,
|
|
1199
|
+
a.privilege_type AS privilege,
|
|
1200
|
+
a.is_grantable AS grantable
|
|
1201
|
+
FROM pg_namespace n
|
|
1202
|
+
CROSS JOIN LATERAL aclexplode(n.nspacl) AS a
|
|
1203
|
+
WHERE n.nspname = $1
|
|
1204
|
+
AND (a.grantee = 0 OR a.grantee = (SELECT oid FROM pg_roles WHERE rolname = $2))`;
|
|
1205
|
+
var readSchemaAcl = (pg, schema, role) => Effect15.map(pg.unsafe(SCHEMA_ACL_SQL, [schema, role]), (rows) => ({
|
|
1206
|
+
role: wordsOf(rows, false),
|
|
1207
|
+
public: wordsOf(rows, true)
|
|
1208
|
+
}));
|
|
1209
|
+
var TABLES_SQL = `SELECT
|
|
1210
|
+
c.relname AS table,
|
|
1211
|
+
c.relkind::text AS relkind,
|
|
1212
|
+
a.grantee = 0 AS public,
|
|
1213
|
+
a.privilege_type AS privilege,
|
|
1214
|
+
a.is_grantable AS grantable
|
|
1215
|
+
FROM pg_class c
|
|
1216
|
+
CROSS JOIN LATERAL aclexplode(c.relacl) AS a
|
|
1217
|
+
WHERE c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
|
|
1218
|
+
AND (a.grantee = 0 OR a.grantee = (SELECT oid FROM pg_roles WHERE rolname = $2))
|
|
1219
|
+
ORDER BY c.relname`;
|
|
1220
|
+
var readTableAcls = (pg, schema, role) => Effect15.map(pg.unsafe(TABLES_SQL, [
|
|
1221
|
+
schema,
|
|
1222
|
+
role
|
|
1223
|
+
]), (rows) => {
|
|
1224
|
+
const byTable = new Map;
|
|
1225
|
+
for (const row of rows) {
|
|
1226
|
+
const entry = byTable.get(row.table) ?? { relkind: row.relkind, role: [], public: [] };
|
|
1227
|
+
entry[row.public ? "public" : "role"].push(encodeWord(row));
|
|
1228
|
+
byTable.set(row.table, entry);
|
|
1229
|
+
}
|
|
1230
|
+
return [...byTable.entries()].map(([table, entry]) => ({
|
|
1231
|
+
table,
|
|
1232
|
+
relkind: entry.relkind,
|
|
1233
|
+
role: [...new Set(entry.role)].sort(),
|
|
1234
|
+
public: [...new Set(entry.public)].sort()
|
|
1235
|
+
})).sort((a, b) => a.table.localeCompare(b.table));
|
|
1236
|
+
});
|
|
1237
|
+
var COLUMNS_SQL = `SELECT
|
|
1238
|
+
c.relname AS table,
|
|
1239
|
+
v.attname AS column,
|
|
1240
|
+
a.grantee = 0 AS public,
|
|
1241
|
+
a.privilege_type AS privilege,
|
|
1242
|
+
a.is_grantable AS grantable,
|
|
1243
|
+
pg_get_userbyid(a.grantor) AS grantor,
|
|
1244
|
+
pg_get_userbyid(c.relowner) AS owner,
|
|
1245
|
+
current_user AS revoker,
|
|
1246
|
+
pg_has_role(current_user, c.relowner, 'USAGE') AS revoker_as_owner
|
|
1247
|
+
FROM pg_attribute v
|
|
1248
|
+
JOIN pg_class c ON c.oid = v.attrelid
|
|
1249
|
+
CROSS JOIN LATERAL aclexplode(v.attacl) AS a
|
|
1250
|
+
WHERE c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
|
|
1251
|
+
AND v.attnum > 0
|
|
1252
|
+
AND NOT v.attisdropped
|
|
1253
|
+
AND (a.grantee = 0 OR a.grantee = (SELECT oid FROM pg_roles WHERE rolname = $2))
|
|
1254
|
+
ORDER BY c.relname, v.attname`;
|
|
1255
|
+
var readColumnAcls = (pg, schema, role) => Effect15.map(pg.unsafe(COLUMNS_SQL, [schema, role]), (rows) => {
|
|
1256
|
+
const byColumn = new Map;
|
|
1257
|
+
for (const row of rows) {
|
|
1258
|
+
const key = `${row.table}\x00${row.column}`;
|
|
1259
|
+
const entry = byColumn.get(key) ?? { role: [], public: [], restorable: [] };
|
|
1260
|
+
const word = encodeWord(row);
|
|
1261
|
+
entry[row.public ? "public" : "role"].push(word);
|
|
1262
|
+
if (!row.public && (row.grantor === row.revoker || row.grantor === row.owner && row.revoker_as_owner)) {
|
|
1263
|
+
entry.restorable.push(word);
|
|
1264
|
+
}
|
|
1265
|
+
byColumn.set(key, entry);
|
|
1266
|
+
}
|
|
1267
|
+
return [...byColumn.entries()].map(([key, entry]) => {
|
|
1268
|
+
const [table, column] = key.split("\x00");
|
|
1269
|
+
return {
|
|
1270
|
+
table,
|
|
1271
|
+
column,
|
|
1272
|
+
role: [...new Set(entry.role)].sort(),
|
|
1273
|
+
public: [...new Set(entry.public)].sort(),
|
|
1274
|
+
restorable: [...new Set(entry.restorable)].sort()
|
|
1275
|
+
};
|
|
1276
|
+
}).sort((a, b) => a.table.localeCompare(b.table) || a.column.localeCompare(b.column));
|
|
1277
|
+
});
|
|
1278
|
+
var readGrants = (pg, schema, role) => Effect15.all({
|
|
1279
|
+
schemaExists: schemaExists(pg, schema),
|
|
1280
|
+
schema: readSchemaAcl(pg, schema, role),
|
|
1281
|
+
tables: readTableAcls(pg, schema, role),
|
|
1282
|
+
columns: readColumnAcls(pg, schema, role),
|
|
1283
|
+
defaults: readDefaultAcls(pg, schema, role),
|
|
1284
|
+
schemaOwnedByRole: readSchemaOwnership(pg, schema, role),
|
|
1285
|
+
ownedTables: readOwnedTables(pg, schema, role)
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
// src/postgres/grants-ops.ts
|
|
1289
|
+
var failRefusal = (refusal) => refusal.kind === "privilege" ? Effect16.fail(new PostgresGrantsPrivilegeRefused({ prop: refusal.prop, word: refusal.word })) : Effect16.fail(new PostgresGrantsDuplicateObject({ prop: refusal.prop, name: refusal.name }));
|
|
1290
|
+
var proveDatabase = (pg, declared) => Effect16.flatMap(currentDatabase(pg), (connected) => connected === declared ? Effect16.void : Effect16.fail(new PostgresGrantsDatabaseMismatch({ declared, connected })));
|
|
1291
|
+
var refuseNames = (props) => {
|
|
1292
|
+
const declared = resolveProps(props);
|
|
1293
|
+
const names = grantsNamesRefusal(declared);
|
|
1294
|
+
return names === undefined ? undefined : new PostgresGrantsNameRefused({
|
|
1295
|
+
name: names.name,
|
|
1296
|
+
byteLength: names.byteLength,
|
|
1297
|
+
limit: names.limit
|
|
1298
|
+
});
|
|
1299
|
+
};
|
|
1300
|
+
var reconcileWithClient3 = (pg, props, output) => Effect16.gen(function* () {
|
|
1301
|
+
const declared = resolveProps(props);
|
|
1302
|
+
const declRefusal = declarationRefusal(declared);
|
|
1303
|
+
if (declRefusal !== undefined)
|
|
1304
|
+
return yield* failRefusal(declRefusal);
|
|
1305
|
+
const nameRefusal = refuseNames(props);
|
|
1306
|
+
if (nameRefusal !== undefined)
|
|
1307
|
+
return yield* Effect16.fail(nameRefusal);
|
|
1308
|
+
for (const [prop, from, to] of [
|
|
1309
|
+
["role", output?.role, declared.role],
|
|
1310
|
+
["database", output?.database, declared.database],
|
|
1311
|
+
["schema", output?.schema, declared.schema]
|
|
1312
|
+
]) {
|
|
1313
|
+
if (from !== undefined && from !== to) {
|
|
1314
|
+
return yield* Effect16.fail(new PostgresGrantsRetargetRefused({ prop, from, to }));
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
yield* proveDatabase(pg, declared.database);
|
|
1318
|
+
if (!(yield* schemaExists(pg, declared.schema))) {
|
|
1319
|
+
return yield* Effect16.fail(new PostgresGrantsSchemaMissing({ schema: declared.schema }));
|
|
1320
|
+
}
|
|
1321
|
+
for (const role of [declared.role, ...declared.defaults.map((entry) => entry.forRole)]) {
|
|
1322
|
+
if (!(yield* roleExists(pg, role))) {
|
|
1323
|
+
return yield* Effect16.fail(new PostgresGrantsRoleMissing({ role }));
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
yield* requireDeclaredObjectsExist(pg, declared);
|
|
1327
|
+
const removed = removedNames(output, declared);
|
|
1328
|
+
const current = yield* readGrants(pg, declared.schema, declared.role);
|
|
1329
|
+
const planned = planReconcile(declared, current, removed);
|
|
1330
|
+
if (planned.length > 0)
|
|
1331
|
+
yield* pg.transaction(planned);
|
|
1332
|
+
const after = yield* readGrants(pg, declared.schema, declared.role);
|
|
1333
|
+
const remaining = planReconcile(declared, after, removed);
|
|
1334
|
+
if (remaining.length > 0) {
|
|
1335
|
+
return yield* Effect16.fail(new PostgresGrantsRepairRefused({
|
|
1336
|
+
schema: declared.schema,
|
|
1337
|
+
role: declared.role,
|
|
1338
|
+
remaining
|
|
1339
|
+
}));
|
|
1340
|
+
}
|
|
1341
|
+
return attributesOf(after, declaredNames(declared));
|
|
1342
|
+
});
|
|
1343
|
+
var readWithClient3 = (pg, names, stored) => Effect16.gen(function* () {
|
|
1344
|
+
yield* proveDatabase(pg, names.database);
|
|
1345
|
+
if (!(yield* schemaExists(pg, names.schema)))
|
|
1346
|
+
return;
|
|
1347
|
+
const live = yield* readGrants(pg, names.schema, names.role);
|
|
1348
|
+
const projected = attributesOf(live, names);
|
|
1349
|
+
const holdsAnything = projected.schemaPrivileges.length > 0 || projected.tables.some((table) => table.privileges.length > 0) || projected.columns.some((column) => column.privileges.length > 0) || projected.defaults.some((entry) => entry.privileges.length > 0);
|
|
1350
|
+
return stored || holdsAnything ? projected : undefined;
|
|
1351
|
+
});
|
|
1352
|
+
var deleteWithClient2 = (pg, props) => Effect16.gen(function* () {
|
|
1353
|
+
yield* proveDatabase(pg, props.database);
|
|
1354
|
+
if (!(yield* roleExists(pg, props.role)))
|
|
1355
|
+
return;
|
|
1356
|
+
if (!(yield* schemaExists(pg, props.schema)))
|
|
1357
|
+
return;
|
|
1358
|
+
const cleared = clearedDeclaration(resolveProps(props));
|
|
1359
|
+
const live = yield* readGrants(pg, cleared.schema, cleared.role);
|
|
1360
|
+
const planned = planRepair(cleared, live);
|
|
1361
|
+
if (planned.length > 0)
|
|
1362
|
+
yield* pg.transaction(planned);
|
|
1363
|
+
const after = yield* readGrants(pg, cleared.schema, cleared.role);
|
|
1364
|
+
const remaining = planRepair(cleared, after);
|
|
1365
|
+
if (remaining.length > 0) {
|
|
1366
|
+
return yield* Effect16.fail(new PostgresGrantsRepairRefused({
|
|
1367
|
+
schema: cleared.schema,
|
|
1368
|
+
role: cleared.role,
|
|
1369
|
+
remaining
|
|
1370
|
+
}));
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
var diffPostgresGrants = (news, output) => Effect16.gen(function* () {
|
|
1374
|
+
if (output === undefined || !isResolved3(news))
|
|
1375
|
+
return;
|
|
1376
|
+
const declared = resolveProps(news);
|
|
1377
|
+
for (const [prop, from, to] of [
|
|
1378
|
+
["role", output.role, declared.role],
|
|
1379
|
+
["database", output.database, declared.database],
|
|
1380
|
+
["schema", output.schema, declared.schema]
|
|
1381
|
+
]) {
|
|
1382
|
+
if (from !== to) {
|
|
1383
|
+
return yield* Effect16.fail(new PostgresGrantsRetargetRefused({ prop, from, to }));
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
const nameRefusal = refuseNames(news);
|
|
1387
|
+
if (nameRefusal !== undefined)
|
|
1388
|
+
return yield* Effect16.fail(nameRefusal);
|
|
1389
|
+
const declRefusal = declarationRefusal(declared);
|
|
1390
|
+
if (declRefusal !== undefined)
|
|
1391
|
+
return yield* failRefusal(declRefusal);
|
|
1392
|
+
return grantsDiffer(declared, output) ? { action: "update" } : { action: "noop" };
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
// src/postgres/grants.ts
|
|
1396
|
+
var PostgresGrants = Resource3("Postgres.Grants", {
|
|
1397
|
+
defaultRemovalPolicy: "retain"
|
|
1398
|
+
});
|
|
1399
|
+
var isPostgresGrants = (value) => (typeof value === "object" || typeof value === "function") && value !== null && value.Type === "Postgres.Grants";
|
|
1400
|
+
var postgresGrantsHandlers = PostgresGrants.Provider.of({
|
|
1401
|
+
list: () => Effect17.succeed([]),
|
|
1402
|
+
nuke: { skip: true },
|
|
1403
|
+
read: ({ olds, output }) => Effect17.gen(function* () {
|
|
1404
|
+
const names = output !== undefined ? namesFromAttrs(output) : declaredNames(resolveProps(olds));
|
|
1405
|
+
const present = yield* withPg((pg) => databaseExists(pg, names.database));
|
|
1406
|
+
if (!present)
|
|
1407
|
+
return;
|
|
1408
|
+
const live = yield* withPg((pg) => readWithClient3(pg, names, output !== undefined), names.database);
|
|
1409
|
+
return live === undefined ? undefined : Unowned3(live);
|
|
1410
|
+
}),
|
|
1411
|
+
diff: ({ news, output }) => diffPostgresGrants(news, output),
|
|
1412
|
+
reconcile: ({ news, output }) => Effect17.gen(function* () {
|
|
1413
|
+
const present = yield* withPg((pg) => databaseExists(pg, news.database));
|
|
1414
|
+
if (!present) {
|
|
1415
|
+
return yield* Effect17.fail(new PostgresGrantsDatabaseMissing({ database: news.database }));
|
|
1416
|
+
}
|
|
1417
|
+
return yield* withPg((pg) => reconcileWithClient3(pg, news, output), news.database);
|
|
1418
|
+
}),
|
|
1419
|
+
delete: ({ olds }) => Effect17.gen(function* () {
|
|
1420
|
+
const present = yield* withPg((pg) => databaseExists(pg, olds.database));
|
|
1421
|
+
if (!present)
|
|
1422
|
+
return;
|
|
1423
|
+
return yield* withPg((pg) => deleteWithClient2(pg, olds), olds.database);
|
|
1424
|
+
})
|
|
1425
|
+
});
|
|
1426
|
+
var PostgresGrantsProvider = () => Provider3.succeed(PostgresGrants, postgresGrantsHandlers);
|
|
1427
|
+
// src/postgres/role-attrs.ts
|
|
1428
|
+
var VALID_UNTIL_OFFSET = /(?:Z|[+-]\d{2}:\d{2})$/;
|
|
1429
|
+
var validUntilRefusal = (value) => {
|
|
1430
|
+
if (Number.isNaN(Date.parse(value)))
|
|
1431
|
+
return { reason: "unparseable" };
|
|
1432
|
+
if (VALID_UNTIL_OFFSET.test(value))
|
|
1433
|
+
return;
|
|
1434
|
+
return { reason: "zone-free" };
|
|
1435
|
+
};
|
|
1436
|
+
var sameValidUntil = (declared, live) => live !== null && Date.parse(declared) === Date.parse(live);
|
|
1437
|
+
// src/postgres/role-sql.ts
|
|
1438
|
+
import * as Effect19 from "effect/Effect";
|
|
1439
|
+
|
|
1440
|
+
// src/postgres/role-membership-sql.ts
|
|
1441
|
+
import * as Effect18 from "effect/Effect";
|
|
1442
|
+
|
|
1443
|
+
// src/postgres/role-errors.ts
|
|
1444
|
+
import * as Data4 from "effect/Data";
|
|
1445
|
+
|
|
1446
|
+
class PostgresRoleNameRefused extends Data4.TaggedError("PostgresRoleNameRefused") {
|
|
1447
|
+
get message() {
|
|
1448
|
+
return `Postgres.Role "${this.name}": ${String(this.byteLength)} UTF-8 bytes, over the ` + `${String(this.limit)}-byte limit (NAMEDATALEN 64, minus the terminator). PostgreSQL would ` + "accept the CREATE and silently truncate the name with only a NOTICE, so this plan refuses " + "it instead of creating a role no later read will ever find again.";
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
class PostgresRoleRenameRefused extends Data4.TaggedError("PostgresRoleRenameRefused") {
|
|
1453
|
+
get message() {
|
|
1454
|
+
return `Postgres.Role: the declared name changed from "${this.from}" to "${this.to}". A ` + "rename is refused at plan — this provider never replaces a role (that is DROP then " + "CREATE, which loses memberships, grants and ownerships). Declare a new logical id for " + "the new name, and remove the old one once its objects have moved.";
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
class PostgresRolePasswordEnvUnsetError extends Data4.TaggedError("PostgresRolePasswordEnvUnsetError") {
|
|
1459
|
+
get message() {
|
|
1460
|
+
return `Postgres.Role "${this.role}": password.fromEnv "${this.variable}" is unset or empty ` + "in the deploying process, but this write requires a password this stack has not sealed.";
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
class PostgresRolePrivilegedRefused extends Data4.TaggedError("PostgresRolePrivilegedRefused") {
|
|
1465
|
+
get message() {
|
|
1466
|
+
return `Postgres.Role "${this.role}": the live role has ${this.flags.join(", ")} set. This ` + "family never declares those attributes (a create lands on the server default, all off), " + "and they are not inherited — a member who SET ROLEs to this role would exercise them. " + "Clear the flag on the cluster before this stack adopts the role.";
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
class PostgresRoleValidUntilRefused extends Data4.TaggedError("PostgresRoleValidUntilRefused") {
|
|
1471
|
+
get message() {
|
|
1472
|
+
return `Postgres.Role "${this.role}": validUntil "${this.value}" is ${this.reason === "unparseable" ? "not a timestamp this kit can parse" : "missing its UTC designator or numeric offset"}. ` + 'Declare an explicit-zone ISO 8601 value such as "2027-01-01T00:00:00Z" — PostgreSQL would ' + "read a zone-free value in the session time zone while the plan compares UTC, and the two " + "would never agree.";
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
class PostgresRoleCreateVanished extends Data4.TaggedError("PostgresRoleCreateVanished") {
|
|
1477
|
+
get message() {
|
|
1478
|
+
return `Postgres.Role "${this.role}": CREATE ROLE raised no error, but the role is still ` + "absent on the immediate re-read. Re-plan — this is not a state this provider expects to reach.";
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
class PostgresRoleMembershipUnrepaired extends Data4.TaggedError("PostgresRoleMembershipUnrepaired") {
|
|
1483
|
+
get message() {
|
|
1484
|
+
const who = this.grantor === null ? "an unresolvable grantor (inconsistent catalog)" : `grantor "${this.grantor}"`;
|
|
1485
|
+
return `Postgres.Role "${this.role}": membership of "${this.parent}" ` + `(${this.declared ? "declared, options unsafe" : "not declared"}) could not be repaired ` + `under ${who}. The server refused the repair or the catalog re-read still found the row ` + "outside the declaration. pg_auth_members keys a membership on (parent, member, grantor). " + "Check dependent grants, executor permissions and grantor identity before re-planning.";
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
class PostgresRoleParentMissing extends Data4.TaggedError("PostgresRoleParentMissing") {
|
|
1490
|
+
get message() {
|
|
1491
|
+
return `Postgres.Role "${this.role}": memberOf parent "${this.parent}" does not exist. ` + "Nothing was written — declare that role first (or drop it from memberOf). A GRANT of a " + "missing role fails only after CREATE ROLE has committed, and the next plan then needs " + "--adopt to finish the orphan.";
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
class PostgresRoleIdentityRefused extends Data4.TaggedError("PostgresRoleIdentityRefused") {
|
|
1496
|
+
get message() {
|
|
1497
|
+
return `Postgres.Role "${this.role}": the live oid is ${String(this.liveOid)}, but state recorded ` + `${String(this.storedOid)}. The name was dropped and recreated outside this stack. Nothing ` + "was altered and the live role was not dropped — remove the stale state, or adopt the new " + "role under a new logical id once you mean to own it.";
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
class PostgresRolePasswordNonAsciiRefused extends Data4.TaggedError("PostgresRolePasswordNonAsciiRefused") {
|
|
1502
|
+
get message() {
|
|
1503
|
+
return `Postgres.Role "${this.role}": password must be ASCII; this provider does not implement PostgreSQL SASLprep normalization. Nothing was written.`;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// src/postgres/role-membership-sql.ts
|
|
1508
|
+
var MEMBERSHIP_SQL = `SELECT parent.rolname AS parent,
|
|
1509
|
+
grantor.rolname AS grantor,
|
|
1510
|
+
m.admin_option AS admin,
|
|
1511
|
+
m.set_option AS set,
|
|
1512
|
+
m.inherit_option AS inherit
|
|
1513
|
+
FROM pg_auth_members m
|
|
1514
|
+
JOIN pg_roles member ON member.oid = m.member
|
|
1515
|
+
JOIN pg_roles parent ON parent.oid = m.roleid
|
|
1516
|
+
LEFT JOIN pg_roles grantor ON grantor.oid = m.grantor
|
|
1517
|
+
WHERE member.rolname = $1
|
|
1518
|
+
ORDER BY parent.rolname, grantor.rolname NULLS FIRST`;
|
|
1519
|
+
var selectRoleMemberships = (pg, name) => Effect18.map(pg.unsafe(MEMBERSHIP_SQL, [name]), (rows) => rows);
|
|
1520
|
+
var buildGrantMembershipSql = (member, parent) => `GRANT ${quoteIdent(parent)} TO ${quoteIdent(member)} WITH SET FALSE`;
|
|
1521
|
+
var buildRepairMembershipSql = (member, parent, grantor, option) => `REVOKE ${option} OPTION FOR ${quoteIdent(parent)} FROM ${quoteIdent(member)} GRANTED BY ${quoteIdent(grantor)} RESTRICT`;
|
|
1522
|
+
var buildRevokeGrantorMembershipSql = (member, parent, grantor) => `REVOKE ${quoteIdent(parent)} FROM ${quoteIdent(member)} GRANTED BY ${quoteIdent(grantor)}`;
|
|
1523
|
+
var unsafeMemberships = (live, inherit) => new Set((live.memberships ?? []).filter((row) => row.admin || row.set || row.inherit && !inherit).map((row) => row.parent));
|
|
1524
|
+
var membershipDrift = (declared, live, unsafe = new Set) => {
|
|
1525
|
+
if (declared === undefined)
|
|
1526
|
+
return { grants: [], revokes: [] };
|
|
1527
|
+
const wanted = [...new Set(declared)].sort();
|
|
1528
|
+
const current = [...new Set(live)].sort();
|
|
1529
|
+
return {
|
|
1530
|
+
grants: wanted.filter((parent) => !current.includes(parent) || unsafe.has(parent)),
|
|
1531
|
+
revokes: [
|
|
1532
|
+
...current.filter((parent) => !wanted.includes(parent)),
|
|
1533
|
+
...wanted.filter((parent) => current.includes(parent) && unsafe.has(parent))
|
|
1534
|
+
]
|
|
1535
|
+
};
|
|
1536
|
+
};
|
|
1537
|
+
var assertParentsExist = (pg, role, parents) => Effect18.gen(function* () {
|
|
1538
|
+
if (parents === undefined)
|
|
1539
|
+
return;
|
|
1540
|
+
for (const parent of [...new Set(parents)].sort()) {
|
|
1541
|
+
if (!(yield* roleExists(pg, parent))) {
|
|
1542
|
+
return yield* Effect18.fail(new PostgresRoleParentMissing({ role, parent }));
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
var syncMemberships = (pg, props, rows) => Effect18.gen(function* () {
|
|
1547
|
+
if (props.memberOf === undefined) {
|
|
1548
|
+
return [...new Set(rows.map((row) => row.parent))].sort();
|
|
1549
|
+
}
|
|
1550
|
+
yield* assertParentsExist(pg, props.name, props.memberOf);
|
|
1551
|
+
const wanted = [...new Set(props.memberOf)].sort();
|
|
1552
|
+
const revokes = [];
|
|
1553
|
+
for (const row of rows) {
|
|
1554
|
+
if (!wanted.includes(row.parent) && row.grantor !== null) {
|
|
1555
|
+
revokes.push(buildRevokeGrantorMembershipSql(props.name, row.parent, row.grantor));
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
if (revokes.length > 0)
|
|
1559
|
+
yield* pg.transaction(revokes);
|
|
1560
|
+
for (const row of rows) {
|
|
1561
|
+
if (row.grantor === null || !wanted.includes(row.parent))
|
|
1562
|
+
continue;
|
|
1563
|
+
const statements = [];
|
|
1564
|
+
for (const option of ["ADMIN", "SET", "INHERIT"]) {
|
|
1565
|
+
const unsafe = option === "ADMIN" ? row.admin : option === "SET" ? row.set : row.inherit && !props.inherit;
|
|
1566
|
+
if (unsafe) {
|
|
1567
|
+
statements.push(buildRepairMembershipSql(props.name, row.parent, row.grantor, option));
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
if (statements.length > 0)
|
|
1571
|
+
yield* pg.transaction(statements).pipe(Effect18.catchTag("SqlError", (error) => {
|
|
1572
|
+
const cause = error.reason.cause;
|
|
1573
|
+
const code = typeof cause === "object" && cause !== null ? cause.code : undefined;
|
|
1574
|
+
return Effect18.fail(code === "2BP01" || code === "42501" ? new PostgresRoleMembershipUnrepaired({
|
|
1575
|
+
role: props.name,
|
|
1576
|
+
parent: row.parent,
|
|
1577
|
+
grantor: row.grantor,
|
|
1578
|
+
declared: true
|
|
1579
|
+
}) : error);
|
|
1580
|
+
}));
|
|
1581
|
+
}
|
|
1582
|
+
const grants = wanted.filter((p) => !rows.some((row) => row.parent === p)).map((parent) => buildGrantMembershipSql(props.name, parent));
|
|
1583
|
+
if (grants.length > 0)
|
|
1584
|
+
yield* pg.transaction(grants);
|
|
1585
|
+
for (const row of yield* selectRoleMemberships(pg, props.name)) {
|
|
1586
|
+
if (!wanted.includes(row.parent) || row.admin || row.set || row.inherit && !props.inherit) {
|
|
1587
|
+
return yield* Effect18.fail(new PostgresRoleMembershipUnrepaired({
|
|
1588
|
+
role: props.name,
|
|
1589
|
+
parent: row.parent,
|
|
1590
|
+
grantor: row.grantor,
|
|
1591
|
+
declared: wanted.includes(row.parent)
|
|
1592
|
+
}));
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
return wanted;
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
// src/postgres/role-sql.ts
|
|
1599
|
+
var SELECT_ROLE_SQL = `SELECT
|
|
1600
|
+
r.oid AS oid,
|
|
1601
|
+
r.rolname AS name,
|
|
1602
|
+
r.rolcanlogin AS login,
|
|
1603
|
+
r.rolconnlimit AS "connectionLimit",
|
|
1604
|
+
r.rolinherit AS inherit,
|
|
1605
|
+
r.rolsuper AS superuser,
|
|
1606
|
+
r.rolcreaterole AS createrole,
|
|
1607
|
+
r.rolcreatedb AS createdb,
|
|
1608
|
+
r.rolreplication AS replication,
|
|
1609
|
+
r.rolbypassrls AS bypassrls,
|
|
1610
|
+
to_char(r.rolvaliduntil AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS "validUntil"
|
|
1611
|
+
FROM pg_roles r
|
|
1612
|
+
WHERE r.rolname = $1`;
|
|
1613
|
+
var selectRole = (pg, name) => Effect19.map(pg.unsafe(SELECT_ROLE_SQL, [name]), (rows) => rows[0]);
|
|
1614
|
+
var readRoleWithClient = (pg, name) => Effect19.gen(function* () {
|
|
1615
|
+
const row = yield* selectRole(pg, name);
|
|
1616
|
+
if (row === undefined)
|
|
1617
|
+
return;
|
|
1618
|
+
const memberships = yield* selectRoleMemberships(pg, name);
|
|
1619
|
+
return {
|
|
1620
|
+
...row,
|
|
1621
|
+
memberOf: [...new Set(memberships.map((membership) => membership.parent))].sort(),
|
|
1622
|
+
memberships
|
|
1623
|
+
};
|
|
1624
|
+
});
|
|
1625
|
+
var withOptions2 = (props) => {
|
|
1626
|
+
const parts = [];
|
|
1627
|
+
parts.push(props.login ? "LOGIN" : "NOLOGIN");
|
|
1628
|
+
parts.push(props.inherit ? "INHERIT" : "NOINHERIT");
|
|
1629
|
+
parts.push(`CONNECTION LIMIT ${String(Math.trunc(props.connectionLimit))}`);
|
|
1630
|
+
if (props.validUntil !== undefined) {
|
|
1631
|
+
parts.push(`VALID UNTIL ${quoteStringLiteral(props.validUntil)}`);
|
|
1632
|
+
}
|
|
1633
|
+
return parts.join(" ");
|
|
1634
|
+
};
|
|
1635
|
+
var buildCreateRoleSql = (props) => `CREATE ROLE ${quoteIdent(props.name)} WITH ${withOptions2(props)}`;
|
|
1636
|
+
var buildAlterRoleSql = (name, change) => {
|
|
1637
|
+
const who = quoteIdent(name);
|
|
1638
|
+
switch (change.prop) {
|
|
1639
|
+
case "login":
|
|
1640
|
+
return `ALTER ROLE ${who} WITH ${change.value ? "LOGIN" : "NOLOGIN"}`;
|
|
1641
|
+
case "inherit":
|
|
1642
|
+
return `ALTER ROLE ${who} WITH ${change.value ? "INHERIT" : "NOINHERIT"}`;
|
|
1643
|
+
case "connectionLimit":
|
|
1644
|
+
return `ALTER ROLE ${who} WITH CONNECTION LIMIT ${String(Math.trunc(change.value))}`;
|
|
1645
|
+
case "validUntil":
|
|
1646
|
+
return `ALTER ROLE ${who} WITH VALID UNTIL ${quoteStringLiteral(change.value)}`;
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
var buildSetPasswordSql = (name, verifier) => `ALTER ROLE ${quoteIdent(name)} WITH PASSWORD ${quoteStringLiteral(verifier)}`;
|
|
1650
|
+
var buildDropRoleSql = (name) => `DROP ROLE IF EXISTS ${quoteIdent(name)}`;
|
|
1651
|
+
var buildCreateStatements = (props, verifier) => [
|
|
1652
|
+
buildCreateRoleSql(props),
|
|
1653
|
+
...verifier === undefined ? [] : [buildSetPasswordSql(props.name, verifier)],
|
|
1654
|
+
...[...new Set(props.memberOf ?? [])].sort().map((parent) => buildGrantMembershipSql(props.name, parent))
|
|
1655
|
+
];
|
|
1656
|
+
var scalarDrift = (props, live) => {
|
|
1657
|
+
const changes = [];
|
|
1658
|
+
if (props.login !== live.login)
|
|
1659
|
+
changes.push({ prop: "login", value: props.login });
|
|
1660
|
+
if (props.inherit !== live.inherit)
|
|
1661
|
+
changes.push({ prop: "inherit", value: props.inherit });
|
|
1662
|
+
if (props.connectionLimit !== live.connectionLimit) {
|
|
1663
|
+
changes.push({ prop: "connectionLimit", value: props.connectionLimit });
|
|
1664
|
+
}
|
|
1665
|
+
if (props.validUntil !== undefined && !sameValidUntil(props.validUntil, live.validUntil)) {
|
|
1666
|
+
changes.push({ prop: "validUntil", value: props.validUntil });
|
|
1667
|
+
}
|
|
1668
|
+
return changes;
|
|
1669
|
+
};
|
|
1670
|
+
var declarationMatches = (props, live) => {
|
|
1671
|
+
const wanted = props.memberOf === undefined ? undefined : [...new Set(props.memberOf)].sort().join("\x00");
|
|
1672
|
+
const found = [...live.memberOf ?? []].sort().join("\x00");
|
|
1673
|
+
return props.name === live.name && props.login === live.login && props.inherit === live.inherit && props.connectionLimit === live.connectionLimit && (props.validUntil === undefined || sameValidUntil(props.validUntil, live.validUntil)) && (wanted === undefined || wanted === found) && privilegedFlags(live).length === 0 && (live.memberships ?? []).every((row) => !row.admin && !row.set && (!row.inherit || props.inherit));
|
|
1674
|
+
};
|
|
1675
|
+
var storedAttributes = (live) => {
|
|
1676
|
+
const { memberships: _observeOnly, ...stored } = live;
|
|
1677
|
+
return stored;
|
|
1678
|
+
};
|
|
1679
|
+
var privilegedFlags = (live) => {
|
|
1680
|
+
const flags = [];
|
|
1681
|
+
if (live.superuser === true)
|
|
1682
|
+
flags.push("SUPERUSER");
|
|
1683
|
+
if (live.createrole === true)
|
|
1684
|
+
flags.push("CREATEROLE");
|
|
1685
|
+
if (live.createdb === true)
|
|
1686
|
+
flags.push("CREATEDB");
|
|
1687
|
+
if (live.replication === true)
|
|
1688
|
+
flags.push("REPLICATION");
|
|
1689
|
+
if (live.bypassrls === true)
|
|
1690
|
+
flags.push("BYPASSRLS");
|
|
1691
|
+
return flags;
|
|
1692
|
+
};
|
|
1693
|
+
// src/postgres/role.ts
|
|
1694
|
+
import { Resource as Resource4 } from "alchemy";
|
|
1695
|
+
import * as Effect21 from "effect/Effect";
|
|
1696
|
+
import * as Redacted2 from "effect/Redacted";
|
|
1697
|
+
|
|
1698
|
+
// src/postgres/role-secrets.ts
|
|
1699
|
+
import * as Redacted from "effect/Redacted";
|
|
1700
|
+
import * as Effect20 from "effect/Effect";
|
|
1701
|
+
|
|
1702
|
+
// src/secrets/write-only.ts
|
|
1703
|
+
import { Buffer } from "node:buffer";
|
|
1704
|
+
import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
|
|
1705
|
+
var resolveAll = (refs, env = process.env) => {
|
|
1706
|
+
const values = {};
|
|
1707
|
+
const missing = [];
|
|
1708
|
+
for (const [key, ref] of Object.entries(refs)) {
|
|
1709
|
+
const value = env[ref.fromEnv];
|
|
1710
|
+
if (value === undefined || value === "")
|
|
1711
|
+
missing.push(ref.fromEnv);
|
|
1712
|
+
else
|
|
1713
|
+
values[key] = value;
|
|
1714
|
+
}
|
|
1715
|
+
return { missing: [...new Set(missing)].sort(), values };
|
|
1716
|
+
};
|
|
1717
|
+
var COST = { N: 16384, maxmem: 64 * 1024 * 1024, p: 1, r: 8 };
|
|
1718
|
+
var PREFIX = "scrypt";
|
|
1719
|
+
var canonical = (values) => JSON.stringify(Object.keys(values).sort().map((key) => [key, values[key]]));
|
|
1720
|
+
var digest = (salt, values) => scryptSync(canonical(values), salt, 32, COST);
|
|
1721
|
+
var seal = (values, salt) => {
|
|
1722
|
+
if (Object.keys(values).length === 0)
|
|
1723
|
+
return "";
|
|
1724
|
+
const bytes = salt === undefined ? randomBytes(16) : Buffer.from(salt, "utf8");
|
|
1725
|
+
return `${PREFIX}:${bytes.toString("base64url")}:${digest(bytes, values).toString("base64url")}`;
|
|
1726
|
+
};
|
|
1727
|
+
var sealMatches = (sealed, values) => {
|
|
1728
|
+
if (sealed === "")
|
|
1729
|
+
return Object.keys(values).length === 0;
|
|
1730
|
+
const [prefix, salt, stored, extra] = sealed.split(":");
|
|
1731
|
+
if (prefix !== PREFIX || salt === undefined || stored === undefined || extra !== undefined) {
|
|
1732
|
+
return false;
|
|
1733
|
+
}
|
|
1734
|
+
const expected = Buffer.from(stored, "base64url");
|
|
1735
|
+
const actual = digest(Buffer.from(salt, "base64url"), values);
|
|
1736
|
+
return expected.length === actual.length && timingSafeEqual(expected, actual);
|
|
1737
|
+
};
|
|
1738
|
+
|
|
1739
|
+
// src/postgres/role-secrets.ts
|
|
1740
|
+
var assertPasswordAscii = (role, resolved) => resolved.value !== undefined && /[\u0080-\uffff]/.test(Redacted.value(resolved.value)) ? Effect20.fail(new PostgresRolePasswordNonAsciiRefused({ role })) : Effect20.void;
|
|
1741
|
+
var resolvePassword = (props, env = process.env) => {
|
|
1742
|
+
if (props.password === undefined)
|
|
1743
|
+
return { variable: undefined, value: undefined };
|
|
1744
|
+
const { values } = resolveAll({ password: props.password }, env);
|
|
1745
|
+
const raw = values["password"];
|
|
1746
|
+
return {
|
|
1747
|
+
variable: props.password.fromEnv,
|
|
1748
|
+
value: raw === undefined ? undefined : Redacted.make(raw)
|
|
1749
|
+
};
|
|
1750
|
+
};
|
|
1751
|
+
var sealPassword = (value2) => seal({ password: Redacted.value(value2) });
|
|
1752
|
+
var passwordMatchesSeal = (value2, sealed) => sealMatches(sealed, { password: Redacted.value(value2) });
|
|
1753
|
+
|
|
1754
|
+
// src/postgres/role-scram.ts
|
|
1755
|
+
import { createHash, createHmac, pbkdf2Sync, randomBytes as randomBytes2 } from "node:crypto";
|
|
1756
|
+
var ITERATIONS = 4096;
|
|
1757
|
+
var SALT_BYTES = 16;
|
|
1758
|
+
var KEY_BYTES = 32;
|
|
1759
|
+
var hmac = (key, label) => createHmac("sha256", key).update(label, "utf8").digest();
|
|
1760
|
+
var b64 = (value2) => value2.toString("base64");
|
|
1761
|
+
var scramSha256Verifier = (password, salt) => {
|
|
1762
|
+
const bytes = salt ?? randomBytes2(SALT_BYTES);
|
|
1763
|
+
const saltedPassword = pbkdf2Sync(password, bytes, ITERATIONS, KEY_BYTES, "sha256");
|
|
1764
|
+
const storedKey = createHash("sha256").update(hmac(saltedPassword, "Client Key")).digest();
|
|
1765
|
+
const serverKey = hmac(saltedPassword, "Server Key");
|
|
1766
|
+
return `SCRAM-SHA-256$${ITERATIONS}:${b64(bytes)}$${b64(storedKey)}:${b64(serverKey)}`;
|
|
1767
|
+
};
|
|
1768
|
+
|
|
1769
|
+
// src/postgres/role.ts
|
|
1770
|
+
var PostgresRole = Resource4("Postgres.Role", {
|
|
1771
|
+
defaultRemovalPolicy: "retain"
|
|
1772
|
+
});
|
|
1773
|
+
var isPostgresRole = (value3) => (typeof value3 === "object" || typeof value3 === "function") && value3 !== null && value3.Type === "Postgres.Role";
|
|
1774
|
+
var reconcileWithClient4 = (pg, props, env = process.env, previousSeal = "", storedOid) => Effect21.gen(function* () {
|
|
1775
|
+
const resolved = resolvePassword(props, env);
|
|
1776
|
+
yield* assertPasswordAscii(props.name, resolved);
|
|
1777
|
+
const observed = yield* readRoleWithClient(pg, props.name);
|
|
1778
|
+
if (observed === undefined) {
|
|
1779
|
+
if (resolved.variable !== undefined && resolved.value === undefined) {
|
|
1780
|
+
return yield* Effect21.fail(new PostgresRolePasswordEnvUnsetError({ role: props.name, variable: resolved.variable }));
|
|
1781
|
+
}
|
|
1782
|
+
yield* assertParentsExist(pg, props.name, props.memberOf);
|
|
1783
|
+
const verifier = resolved.value === undefined ? undefined : scramSha256Verifier(Redacted2.value(resolved.value));
|
|
1784
|
+
yield* pg.transaction(buildCreateStatements(props, verifier));
|
|
1785
|
+
const created = yield* readRoleWithClient(pg, props.name);
|
|
1786
|
+
if (created === undefined) {
|
|
1787
|
+
return yield* Effect21.fail(new PostgresRoleCreateVanished({ role: props.name }));
|
|
1788
|
+
}
|
|
1789
|
+
return {
|
|
1790
|
+
...storedAttributes(created),
|
|
1791
|
+
passwordSeal: resolved.value === undefined ? "" : sealPassword(resolved.value)
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
if (storedOid !== undefined && observed.oid !== storedOid) {
|
|
1795
|
+
return yield* Effect21.fail(new PostgresRoleIdentityRefused({
|
|
1796
|
+
role: props.name,
|
|
1797
|
+
storedOid,
|
|
1798
|
+
liveOid: observed.oid
|
|
1799
|
+
}));
|
|
1800
|
+
}
|
|
1801
|
+
const flags = privilegedFlags(observed);
|
|
1802
|
+
if (flags.length > 0) {
|
|
1803
|
+
return yield* Effect21.fail(new PostgresRolePrivilegedRefused({ role: props.name, flags }));
|
|
1804
|
+
}
|
|
1805
|
+
if (resolved.variable !== undefined && resolved.value === undefined && previousSeal === "") {
|
|
1806
|
+
return yield* Effect21.fail(new PostgresRolePasswordEnvUnsetError({ role: props.name, variable: resolved.variable }));
|
|
1807
|
+
}
|
|
1808
|
+
yield* assertParentsExist(pg, props.name, props.memberOf);
|
|
1809
|
+
for (const change of scalarDrift(props, observed)) {
|
|
1810
|
+
yield* pg.unsafe(buildAlterRoleSql(props.name, change)).pipe(Effect21.asVoid);
|
|
1811
|
+
}
|
|
1812
|
+
const passwordMatched = resolved.value !== undefined && passwordMatchesSeal(resolved.value, previousSeal);
|
|
1813
|
+
if (resolved.value !== undefined && !passwordMatched) {
|
|
1814
|
+
yield* pg.unsafe(buildSetPasswordSql(props.name, scramSha256Verifier(Redacted2.value(resolved.value)))).pipe(Effect21.asVoid);
|
|
1815
|
+
}
|
|
1816
|
+
yield* syncMemberships(pg, props, observed.memberships ?? []);
|
|
1817
|
+
const after = yield* readRoleWithClient(pg, props.name);
|
|
1818
|
+
if (after === undefined) {
|
|
1819
|
+
return yield* Effect21.fail(new PostgresRoleCreateVanished({ role: props.name }));
|
|
1820
|
+
}
|
|
1821
|
+
return {
|
|
1822
|
+
...storedAttributes(after),
|
|
1823
|
+
passwordSeal: resolved.value === undefined || passwordMatched ? previousSeal : sealPassword(resolved.value)
|
|
1824
|
+
};
|
|
1825
|
+
});
|
|
1826
|
+
var readRole = (pg, name) => readRoleWithClient(pg, name);
|
|
1827
|
+
var refuseAtPlan = (props) => Effect21.gen(function* () {
|
|
1828
|
+
const nameRefusal = nameByteRefusal(props.name);
|
|
1829
|
+
if (nameRefusal !== undefined) {
|
|
1830
|
+
return yield* Effect21.fail(new PostgresRoleNameRefused({ name: props.name, ...nameRefusal }));
|
|
1831
|
+
}
|
|
1832
|
+
if (props.validUntil !== undefined) {
|
|
1833
|
+
const untilRefusal = validUntilRefusal(props.validUntil);
|
|
1834
|
+
if (untilRefusal !== undefined) {
|
|
1835
|
+
return yield* Effect21.fail(new PostgresRoleValidUntilRefused({
|
|
1836
|
+
role: props.name,
|
|
1837
|
+
value: props.validUntil,
|
|
1838
|
+
reason: untilRefusal.reason
|
|
1839
|
+
}));
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
});
|
|
1843
|
+
// src/postgres/role-provider.ts
|
|
1844
|
+
import * as Provider4 from "alchemy/Provider";
|
|
1845
|
+
import { isResolved as isResolved5 } from "alchemy/Diff";
|
|
1846
|
+
import * as Effect28 from "effect/Effect";
|
|
1847
|
+
|
|
1848
|
+
// src/ownership/probe.ts
|
|
1849
|
+
import { Unowned as Unowned4 } from "alchemy/AdoptPolicy";
|
|
1850
|
+
import * as Effect24 from "effect/Effect";
|
|
1851
|
+
|
|
1852
|
+
// src/ownership/rows.ts
|
|
1853
|
+
import { Stack as Stack2 } from "alchemy/Stack";
|
|
1854
|
+
import { State, isActionState } from "alchemy/State/State";
|
|
1855
|
+
import * as Effect23 from "effect/Effect";
|
|
1856
|
+
import * as Option2 from "effect/Option";
|
|
1857
|
+
|
|
1858
|
+
// src/ownership/whole.ts
|
|
1859
|
+
import { Stack } from "alchemy/Stack";
|
|
1860
|
+
import * as Effect22 from "effect/Effect";
|
|
1861
|
+
import * as Option from "effect/Option";
|
|
1862
|
+
var absent = (value3) => value3 === undefined || value3 === null;
|
|
1863
|
+
var isPlainObject = (value3) => {
|
|
1864
|
+
if (typeof value3 !== "object" || value3 === null || Array.isArray(value3))
|
|
1865
|
+
return false;
|
|
1866
|
+
const proto = Object.getPrototypeOf(value3);
|
|
1867
|
+
return proto === Object.prototype || proto === null;
|
|
1868
|
+
};
|
|
1869
|
+
var carries = (declared, recorded) => {
|
|
1870
|
+
if (absent(declared))
|
|
1871
|
+
return true;
|
|
1872
|
+
if (absent(recorded))
|
|
1873
|
+
return false;
|
|
1874
|
+
if (Array.isArray(declared)) {
|
|
1875
|
+
if (!Array.isArray(recorded))
|
|
1876
|
+
return false;
|
|
1877
|
+
return declared.every((each, index) => carries(each, recorded[index]));
|
|
1878
|
+
}
|
|
1879
|
+
if (isPlainObject(declared)) {
|
|
1880
|
+
if (!isPlainObject(recorded))
|
|
1881
|
+
return false;
|
|
1882
|
+
return Object.entries(declared).every(([key, each]) => carries(each, recorded[key]));
|
|
1883
|
+
}
|
|
1884
|
+
return true;
|
|
1885
|
+
};
|
|
1886
|
+
var wholeDeclaration = (fqn, props) => Effect22.map(Effect22.serviceOption(Stack), (stack) => {
|
|
1887
|
+
const declared = Option.isSome(stack) ? stack.value.resources[fqn] : undefined;
|
|
1888
|
+
return declared !== undefined && carries(declared.Props, props);
|
|
1889
|
+
});
|
|
1890
|
+
|
|
1891
|
+
// src/ownership/rows.ts
|
|
1892
|
+
var storeOf = Effect23.gen(function* () {
|
|
1893
|
+
const stack = yield* Effect23.serviceOption(Stack2);
|
|
1894
|
+
const state = yield* Effect23.serviceOption(State);
|
|
1895
|
+
if (Option2.isNone(stack) || Option2.isNone(state))
|
|
1896
|
+
return;
|
|
1897
|
+
const store = yield* state.value;
|
|
1898
|
+
const where = { stack: stack.value.name, stage: stack.value.stage };
|
|
1899
|
+
return { resources: stack.value.resources, store, where };
|
|
1900
|
+
});
|
|
1901
|
+
var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect23.orElseSucceed(() => {
|
|
1902
|
+
return;
|
|
1903
|
+
}));
|
|
1904
|
+
var generationOf = (row, instanceId) => {
|
|
1905
|
+
let at = row;
|
|
1906
|
+
while (typeof at === "object" && at !== null && !isActionState(at)) {
|
|
1907
|
+
const generation = at;
|
|
1908
|
+
if (generation.instanceId === instanceId)
|
|
1909
|
+
return generation;
|
|
1910
|
+
at = generation.old;
|
|
1911
|
+
}
|
|
1912
|
+
return;
|
|
1913
|
+
};
|
|
1914
|
+
var recordedGeneration = (fqn, instanceId) => Effect23.gen(function* () {
|
|
1915
|
+
const found = yield* storeOf;
|
|
1916
|
+
if (found === undefined)
|
|
1917
|
+
return;
|
|
1918
|
+
for (const at of [fqn, ...found.resources[fqn]?.FormerFqns ?? []]) {
|
|
1919
|
+
const generation = generationOf(yield* rowAt(found.store, found.where, at), instanceId);
|
|
1920
|
+
if (generation !== undefined)
|
|
1921
|
+
return generation;
|
|
1922
|
+
}
|
|
1923
|
+
return;
|
|
1924
|
+
});
|
|
1925
|
+
var recordedInstance = (fqn, instanceId) => Effect23.gen(function* () {
|
|
1926
|
+
const generation = yield* recordedGeneration(fqn, instanceId);
|
|
1927
|
+
if (generation === undefined)
|
|
1928
|
+
return "absent";
|
|
1929
|
+
return (yield* wholeDeclaration(fqn, generation.props)) ? "whole" : "partial";
|
|
1930
|
+
});
|
|
1931
|
+
var isCreate = (fqn, instanceId) => Effect23.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
|
|
1932
|
+
var idOf = (attr) => {
|
|
1933
|
+
if (typeof attr !== "object" || attr === null)
|
|
1934
|
+
return;
|
|
1935
|
+
const id = attr.id;
|
|
1936
|
+
return typeof id === "string" && id !== "" ? id : undefined;
|
|
1937
|
+
};
|
|
1938
|
+
var olderGenerationIds = (fqn, instanceId) => Effect23.gen(function* () {
|
|
1939
|
+
const found = yield* storeOf;
|
|
1940
|
+
if (found === undefined)
|
|
1941
|
+
return [];
|
|
1942
|
+
const ids = [];
|
|
1943
|
+
for (const at of [fqn, ...found.resources[fqn]?.FormerFqns ?? []]) {
|
|
1944
|
+
collectOlder(yield* rowAt(found.store, found.where, at), instanceId, ids);
|
|
1945
|
+
}
|
|
1946
|
+
return ids;
|
|
1947
|
+
});
|
|
1948
|
+
var collectOlder = (row, instanceId, ids) => {
|
|
1949
|
+
let at = row;
|
|
1950
|
+
let seen = false;
|
|
1951
|
+
while (typeof at === "object" && at !== null && !isActionState(at)) {
|
|
1952
|
+
const generation = at;
|
|
1953
|
+
if (!seen) {
|
|
1954
|
+
seen = generation.instanceId === instanceId;
|
|
1955
|
+
at = generation.old;
|
|
1956
|
+
continue;
|
|
1957
|
+
}
|
|
1958
|
+
if (generation.instanceId !== instanceId) {
|
|
1959
|
+
const id = idOf(generation.attr);
|
|
1960
|
+
if (id !== undefined)
|
|
1961
|
+
ids.push(id);
|
|
1962
|
+
}
|
|
1963
|
+
at = generation.old;
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
var forgetRefusedCreate = (fqn, instanceId) => Effect23.gen(function* () {
|
|
1967
|
+
const found = yield* storeOf;
|
|
1968
|
+
if (found === undefined)
|
|
1969
|
+
return;
|
|
1970
|
+
const row = yield* rowAt(found.store, found.where, fqn);
|
|
1971
|
+
const mine = row;
|
|
1972
|
+
if (mine?.status !== "creating" || mine.instanceId !== instanceId || mine.attr !== undefined) {
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
yield* found.store.delete({ ...found.where, fqn }).pipe(Effect23.ignore);
|
|
1976
|
+
});
|
|
1977
|
+
|
|
1978
|
+
// src/ownership/probe.ts
|
|
1979
|
+
var provenOurs = (ask, settled) => Effect24.gen(function* () {
|
|
1980
|
+
const recorded = yield* recordedInstance(ask.fqn, ask.instanceId);
|
|
1981
|
+
if (recorded === "absent")
|
|
1982
|
+
return false;
|
|
1983
|
+
if (recorded === "partial") {
|
|
1984
|
+
yield* Effect24.logWarning(`${ask.fqn}: the interrupted create's state row lacks part of the declaration (a prop ` + "was still an Output when it was written), so the live object is not proven ours");
|
|
1985
|
+
return false;
|
|
1986
|
+
}
|
|
1987
|
+
const unproven = (reason) => Effect24.as(Effect24.logWarning(`${ask.fqn}: the interrupted create's object could not be compared with its props ` + `(${reason instanceof Error ? reason.message : String(reason)}), so it is not treated as ours`), false);
|
|
1988
|
+
return yield* settled.pipe(Effect24.catch(unproven), Effect24.catchDefect(unproven));
|
|
1989
|
+
});
|
|
1990
|
+
var ownedRead = (ask, found, settled) => Effect24.gen(function* () {
|
|
1991
|
+
if (found === undefined || ask.output !== undefined)
|
|
1992
|
+
return found;
|
|
1993
|
+
return (yield* provenOurs(ask, settled)) ? found : Unowned4(found);
|
|
1994
|
+
});
|
|
1995
|
+
|
|
1996
|
+
// src/ownership/resume.ts
|
|
1997
|
+
import { Unowned as Unowned5 } from "alchemy/AdoptPolicy";
|
|
1998
|
+
import { Artifacts } from "alchemy/Artifacts";
|
|
1999
|
+
import * as Effect25 from "effect/Effect";
|
|
2000
|
+
import * as Option3 from "effect/Option";
|
|
2001
|
+
var PROVEN = "homeflare/ownership/resumes";
|
|
2002
|
+
var UNFINISHED = "homeflare/ownership/unfinished";
|
|
2003
|
+
var note = (key, instanceId) => Effect25.gen(function* () {
|
|
2004
|
+
const bag = yield* Effect25.serviceOption(Artifacts);
|
|
2005
|
+
if (Option3.isSome(bag))
|
|
2006
|
+
yield* bag.value.set(key, instanceId);
|
|
2007
|
+
});
|
|
2008
|
+
var noted = (key, instanceId) => Effect25.gen(function* () {
|
|
2009
|
+
const bag = yield* Effect25.serviceOption(Artifacts);
|
|
2010
|
+
if (Option3.isNone(bag))
|
|
2011
|
+
return false;
|
|
2012
|
+
return (yield* bag.value.get(key)) === instanceId;
|
|
2013
|
+
});
|
|
2014
|
+
var noteUnfinished = (instanceId) => Effect25.as(note(UNFINISHED, instanceId), undefined);
|
|
2015
|
+
var noteResume = (instanceId, recovered) => Effect25.gen(function* () {
|
|
2016
|
+
yield* noteUnfinished(instanceId);
|
|
2017
|
+
const unproven = () => Effect25.succeed(undefined);
|
|
2018
|
+
const found = yield* recovered.pipe(Effect25.catch(unproven), Effect25.catchDefect(unproven));
|
|
2019
|
+
if (found === undefined || found === null || Unowned5.is(found))
|
|
2020
|
+
return;
|
|
2021
|
+
yield* note(PROVEN, instanceId);
|
|
2022
|
+
});
|
|
2023
|
+
var resumes = (instanceId) => noted(PROVEN, instanceId);
|
|
2024
|
+
var unfinished = (instanceId) => noted(UNFINISHED, instanceId);
|
|
2025
|
+
var provingResumes = (service) => {
|
|
2026
|
+
const { diff, read } = service;
|
|
2027
|
+
if (diff === undefined || read === undefined)
|
|
2028
|
+
return service;
|
|
2029
|
+
const proving = (input) => Effect25.gen(function* () {
|
|
2030
|
+
if (input.output === undefined) {
|
|
2031
|
+
yield* noteResume(input.instanceId, read({ ...input, output: undefined }));
|
|
2032
|
+
}
|
|
2033
|
+
return yield* diff(input);
|
|
2034
|
+
});
|
|
2035
|
+
return { ...service, diff: proving };
|
|
2036
|
+
};
|
|
2037
|
+
|
|
2038
|
+
// src/ownership/adopt.ts
|
|
2039
|
+
import { AdoptPolicy } from "alchemy/AdoptPolicy";
|
|
2040
|
+
import { AlchemyContext } from "alchemy/AlchemyContext";
|
|
2041
|
+
import { Stack as Stack3 } from "alchemy/Stack";
|
|
2042
|
+
import * as Effect26 from "effect/Effect";
|
|
2043
|
+
import * as Option4 from "effect/Option";
|
|
2044
|
+
var adoptEnabled = (fqn) => Effect26.gen(function* () {
|
|
2045
|
+
const stack = yield* Effect26.serviceOption(Stack3);
|
|
2046
|
+
const scoped = Option4.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
|
|
2047
|
+
if (scoped !== undefined)
|
|
2048
|
+
return scoped;
|
|
2049
|
+
const policy = yield* Effect26.serviceOption(AdoptPolicy);
|
|
2050
|
+
if (Option4.isSome(policy))
|
|
2051
|
+
return policy.value;
|
|
2052
|
+
const context = yield* Effect26.serviceOption(AlchemyContext);
|
|
2053
|
+
return Option4.isSome(context) ? context.value.adopt : false;
|
|
2054
|
+
});
|
|
2055
|
+
var adoptable = (owner) => Effect26.gen(function* () {
|
|
2056
|
+
if (yield* isCreate(owner.fqn, owner.instanceId))
|
|
2057
|
+
return true;
|
|
2058
|
+
return yield* unfinished(owner.instanceId);
|
|
2059
|
+
});
|
|
2060
|
+
var adoptsAtApply = (owner) => Effect26.gen(function* () {
|
|
2061
|
+
if (!(yield* adoptEnabled(owner.fqn)))
|
|
2062
|
+
return false;
|
|
2063
|
+
return yield* adoptable(owner);
|
|
2064
|
+
});
|
|
2065
|
+
var refuseTakeover = (owner, what) => Effect26.gen(function* () {
|
|
2066
|
+
if (owner.output !== undefined)
|
|
2067
|
+
return;
|
|
2068
|
+
if (yield* resumes(owner.instanceId))
|
|
2069
|
+
return;
|
|
2070
|
+
const may = yield* adoptable(owner);
|
|
2071
|
+
if (may && (yield* adoptEnabled(owner.fqn)))
|
|
2072
|
+
return;
|
|
2073
|
+
yield* forgetRefusedCreate(owner.fqn, owner.instanceId);
|
|
2074
|
+
const remedy = may ? "Deploy with --adopt (or wrap the resource in adopt(true)) to take it over, or remove it first." : "It is the new identity of a replace, which --adopt does not cover: remove it first.";
|
|
2075
|
+
return yield* Effect26.die(new Error(`${what}: already exists, and this stack holds no state for it — the plan could not ask ` + "first (a prop was still an Output, a replace moved onto it, or it appeared after the " + `plan). Nothing was written. ${remedy}`));
|
|
2076
|
+
});
|
|
2077
|
+
var claimFor = (owner) => (what) => refuseTakeover(owner, what);
|
|
2078
|
+
|
|
2079
|
+
// src/postgres/role-diff.ts
|
|
2080
|
+
import { isResolved as isResolved4 } from "alchemy/Diff";
|
|
2081
|
+
import * as Effect27 from "effect/Effect";
|
|
2082
|
+
var diffPostgresRole = (news, output, env = process.env, observation) => Effect27.gen(function* () {
|
|
2083
|
+
if (output === undefined || !isResolved4(news))
|
|
2084
|
+
return;
|
|
2085
|
+
if (news.name !== output.name) {
|
|
2086
|
+
return yield* Effect27.fail(new PostgresRoleRenameRefused({ from: output.name, to: news.name }));
|
|
2087
|
+
}
|
|
2088
|
+
yield* refuseAtPlan(news);
|
|
2089
|
+
const live = observation?.found;
|
|
2090
|
+
if (live !== undefined && live.oid !== output.oid) {
|
|
2091
|
+
return yield* Effect27.fail(new PostgresRoleIdentityRefused({
|
|
2092
|
+
role: output.name,
|
|
2093
|
+
storedOid: output.oid,
|
|
2094
|
+
liveOid: live.oid
|
|
2095
|
+
}));
|
|
2096
|
+
}
|
|
2097
|
+
const compared = live ?? output;
|
|
2098
|
+
const scalars = scalarDrift(news, compared);
|
|
2099
|
+
const membership = membershipDrift(news.memberOf, compared.memberOf ?? [], live === undefined ? new Set : unsafeMemberships(live, news.inherit));
|
|
2100
|
+
const resolved = resolvePassword(news, env);
|
|
2101
|
+
yield* assertPasswordAscii(news.name, resolved);
|
|
2102
|
+
const passwordStale = resolved.value !== undefined && !passwordMatchesSeal(resolved.value, output.passwordSeal);
|
|
2103
|
+
const absent2 = observation !== undefined && live === undefined;
|
|
2104
|
+
const changed = absent2 || privilegedFlags(compared).length > 0 || scalars.length > 0 || membership.grants.length > 0 || membership.revokes.length > 0 || passwordStale;
|
|
2105
|
+
return changed ? { action: "update" } : { action: "noop" };
|
|
2106
|
+
});
|
|
2107
|
+
|
|
2108
|
+
// src/postgres/role-provider.ts
|
|
2109
|
+
var postgresRoleHandlers = PostgresRole.Provider.of({
|
|
2110
|
+
list: () => Effect28.succeed([]),
|
|
2111
|
+
nuke: { skip: true },
|
|
2112
|
+
read: ({ fqn, instanceId, olds, output }) => Effect28.gen(function* () {
|
|
2113
|
+
const name = output?.name ?? olds.name;
|
|
2114
|
+
const live = yield* withPg((pg) => readRole(pg, name));
|
|
2115
|
+
if (live === undefined)
|
|
2116
|
+
return;
|
|
2117
|
+
if (output !== undefined && live.oid !== output.oid) {
|
|
2118
|
+
return yield* Effect28.fail(new PostgresRoleIdentityRefused({
|
|
2119
|
+
role: name,
|
|
2120
|
+
storedOid: output.oid,
|
|
2121
|
+
liveOid: live.oid
|
|
2122
|
+
}));
|
|
2123
|
+
}
|
|
2124
|
+
const passwordSeal = output?.passwordSeal ?? "";
|
|
2125
|
+
const found = { ...storedAttributes(live), passwordSeal };
|
|
2126
|
+
return yield* ownedRead({ fqn, instanceId, output }, found, Effect28.succeed(declarationMatches(olds, live)));
|
|
2127
|
+
}),
|
|
2128
|
+
diff: ({ news, output, instanceId }) => Effect28.gen(function* () {
|
|
2129
|
+
if (output === undefined)
|
|
2130
|
+
return yield* noteUnfinished(instanceId);
|
|
2131
|
+
if (!isResolved5(news))
|
|
2132
|
+
return;
|
|
2133
|
+
const found = yield* withPg((pg) => readRole(pg, output.name));
|
|
2134
|
+
return yield* diffPostgresRole(news, output, process.env, { found });
|
|
2135
|
+
}),
|
|
2136
|
+
reconcile: ({ fqn, instanceId, news, output }) => Effect28.gen(function* () {
|
|
2137
|
+
yield* refuseAtPlan(news);
|
|
2138
|
+
return yield* withPg((pg) => Effect28.gen(function* () {
|
|
2139
|
+
const observed = yield* readRole(pg, news.name);
|
|
2140
|
+
if (observed !== undefined && output === undefined) {
|
|
2141
|
+
yield* refuseTakeover({ fqn, instanceId, output }, "Postgres.Role");
|
|
2142
|
+
}
|
|
2143
|
+
return yield* reconcileWithClient4(pg, news, process.env, output?.passwordSeal, output?.oid);
|
|
2144
|
+
}));
|
|
2145
|
+
}),
|
|
2146
|
+
delete: ({ output }) => withPg((pg) => Effect28.gen(function* () {
|
|
2147
|
+
if (output === undefined)
|
|
2148
|
+
return;
|
|
2149
|
+
const live = yield* readRole(pg, output.name);
|
|
2150
|
+
if (live === undefined)
|
|
2151
|
+
return;
|
|
2152
|
+
if (live.oid !== output.oid) {
|
|
2153
|
+
return yield* Effect28.fail(new PostgresRoleIdentityRefused({
|
|
2154
|
+
role: output.name,
|
|
2155
|
+
storedOid: output.oid,
|
|
2156
|
+
liveOid: live.oid
|
|
2157
|
+
}));
|
|
2158
|
+
}
|
|
2159
|
+
yield* pg.unsafe(buildDropRoleSql(output.name)).pipe(Effect28.asVoid);
|
|
2160
|
+
}))
|
|
2161
|
+
});
|
|
2162
|
+
var PostgresRoleProvider = () => Provider4.succeed(PostgresRole, postgresRoleHandlers);
|
|
290
2163
|
// src/postgres/providers.ts
|
|
291
2164
|
import * as Layer2 from "effect/Layer";
|
|
292
|
-
var postgresProviders = (config) => PostgresDatabaseProvider().pipe(Layer2.provideMerge(postgresConnection(config)));
|
|
293
|
-
var postgresRunnerProviders = (config) => PostgresDatabaseProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config)));
|
|
2165
|
+
var postgresProviders = (config) => Layer2.mergeAll(PostgresDatabaseProvider().pipe(Layer2.provideMerge(postgresConnection(config))), PostgresSchemaProvider().pipe(Layer2.provideMerge(postgresConnection(config))), PostgresGrantsProvider().pipe(Layer2.provideMerge(postgresConnection(config))));
|
|
2166
|
+
var postgresRunnerProviders = (config) => Layer2.mergeAll(PostgresDatabaseProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config))), PostgresSchemaProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config))), PostgresGrantsProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config))));
|
|
2167
|
+
var postgresRoleProviders = (config) => PostgresRoleProvider().pipe(Layer2.provideMerge(postgresConnection(config)));
|
|
2168
|
+
var postgresRoleRunnerProviders = (config) => PostgresRoleProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config)));
|
|
294
2169
|
export {
|
|
2170
|
+
COLUMN_PRIVILEGES,
|
|
295
2171
|
POSTGRES_NAME_MAX_BYTES,
|
|
296
2172
|
PostgresConnection,
|
|
297
2173
|
PostgresDatabase,
|
|
@@ -301,24 +2177,109 @@ export {
|
|
|
301
2177
|
PostgresDatabaseOwnerMissing,
|
|
302
2178
|
PostgresDatabaseProvider,
|
|
303
2179
|
PostgresDatabaseRenameRefused,
|
|
2180
|
+
PostgresGrants,
|
|
2181
|
+
PostgresGrantsColumnMissing,
|
|
2182
|
+
PostgresGrantsDatabaseMismatch,
|
|
2183
|
+
PostgresGrantsDatabaseMissing,
|
|
2184
|
+
PostgresGrantsDuplicateObject,
|
|
2185
|
+
PostgresGrantsNameRefused,
|
|
2186
|
+
PostgresGrantsPrivilegeRefused,
|
|
2187
|
+
PostgresGrantsProvider,
|
|
2188
|
+
PostgresGrantsRepairRefused,
|
|
2189
|
+
PostgresGrantsRetargetRefused,
|
|
2190
|
+
PostgresGrantsRoleMissing,
|
|
2191
|
+
PostgresGrantsSchemaMissing,
|
|
2192
|
+
PostgresGrantsTableMissing,
|
|
2193
|
+
PostgresRole,
|
|
2194
|
+
PostgresRoleCreateVanished,
|
|
2195
|
+
PostgresRoleIdentityRefused,
|
|
2196
|
+
PostgresRoleMembershipUnrepaired,
|
|
2197
|
+
PostgresRoleNameRefused,
|
|
2198
|
+
PostgresRoleParentMissing,
|
|
2199
|
+
PostgresRolePasswordEnvUnsetError,
|
|
2200
|
+
PostgresRolePasswordNonAsciiRefused,
|
|
2201
|
+
PostgresRolePrivilegedRefused,
|
|
2202
|
+
PostgresRoleProvider,
|
|
2203
|
+
PostgresRoleRenameRefused,
|
|
2204
|
+
PostgresRoleValidUntilRefused,
|
|
2205
|
+
PostgresSchema,
|
|
2206
|
+
PostgresSchemaCreateVanished,
|
|
2207
|
+
PostgresSchemaDatabaseRefused,
|
|
2208
|
+
PostgresSchemaDeleteForeignRefused,
|
|
2209
|
+
PostgresSchemaDrift,
|
|
2210
|
+
PostgresSchemaDropNotEmptyError,
|
|
2211
|
+
PostgresSchemaExistsRefused,
|
|
2212
|
+
PostgresSchemaIdentityRefused,
|
|
2213
|
+
PostgresSchemaNameRefused,
|
|
2214
|
+
PostgresSchemaOwnerMissing,
|
|
2215
|
+
PostgresSchemaProvider,
|
|
2216
|
+
PostgresSchemaRenameRefused,
|
|
2217
|
+
PostgresSchemaWrongDatabase,
|
|
2218
|
+
SCHEMA_PRIVILEGES,
|
|
2219
|
+
TABLE_PRIVILEGES,
|
|
2220
|
+
buildAlterRoleSql,
|
|
2221
|
+
buildCommentSchemaSql,
|
|
304
2222
|
buildCreateDatabaseSql,
|
|
2223
|
+
buildCreateRoleSql,
|
|
2224
|
+
buildCreateSchemaSql,
|
|
2225
|
+
buildDropRoleSql,
|
|
2226
|
+
buildDropSchemaSql,
|
|
2227
|
+
buildGrantMembershipSql,
|
|
2228
|
+
buildRepairMembershipSql,
|
|
2229
|
+
buildRevokeGrantorMembershipSql,
|
|
2230
|
+
buildSetPasswordSql,
|
|
2231
|
+
currentDatabase,
|
|
2232
|
+
currentUser,
|
|
2233
|
+
databaseExists,
|
|
2234
|
+
diffPostgresSchema,
|
|
305
2235
|
firstDrift,
|
|
2236
|
+
grantColumnSql,
|
|
2237
|
+
grantDefaultSql,
|
|
2238
|
+
grantSchemaSql,
|
|
2239
|
+
grantTableSql,
|
|
2240
|
+
grantsNameByteRefusal,
|
|
306
2241
|
inlineParams,
|
|
2242
|
+
isDependentObjectsError,
|
|
307
2243
|
isDuplicateDatabaseRace,
|
|
308
2244
|
isPostgresDatabase,
|
|
2245
|
+
isPostgresGrants,
|
|
2246
|
+
isPostgresRole,
|
|
2247
|
+
isPostgresSchema,
|
|
2248
|
+
letterForPrivilege,
|
|
309
2249
|
makePsqlExecutor,
|
|
2250
|
+
membershipDrift,
|
|
310
2251
|
nameByteRefusal,
|
|
2252
|
+
normalizedComment,
|
|
311
2253
|
postgresConnection,
|
|
312
2254
|
postgresProviders,
|
|
2255
|
+
postgresRoleProviders,
|
|
2256
|
+
postgresRoleRunnerProviders,
|
|
313
2257
|
postgresRunnerConnection,
|
|
314
2258
|
postgresRunnerProviders,
|
|
315
2259
|
quoteIdent,
|
|
316
2260
|
quoteStringLiteral,
|
|
2261
|
+
resolvePassword,
|
|
2262
|
+
revokeColumnSql,
|
|
2263
|
+
revokeDefaultSql,
|
|
2264
|
+
revokePublicSchemaSql,
|
|
2265
|
+
revokePublicTablesSql,
|
|
2266
|
+
revokeSchemaSql,
|
|
2267
|
+
revokeTableSql,
|
|
317
2268
|
roleExists,
|
|
2269
|
+
sameValidUntil,
|
|
2270
|
+
scalarDrift,
|
|
2271
|
+
schemaIsEmpty,
|
|
2272
|
+
schemaNameByteRefusal,
|
|
318
2273
|
selectDatabase,
|
|
2274
|
+
selectRole,
|
|
2275
|
+
selectRoleMemberships,
|
|
2276
|
+
selectSchema,
|
|
2277
|
+
syncMemberships,
|
|
2278
|
+
unsafeMemberships,
|
|
319
2279
|
utf8ByteLength,
|
|
2280
|
+
validUntilRefusal,
|
|
320
2281
|
withPg
|
|
321
2282
|
};
|
|
322
2283
|
|
|
323
|
-
//# debugId=
|
|
2284
|
+
//# debugId=584CB763916085BE64756E2164756E21
|
|
324
2285
|
//# sourceMappingURL=index.js.map
|