@homeflare/alchemy 0.42.0 → 0.44.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/budget-errors.d.ts +36 -0
- package/dist/litellm/budget-errors.d.ts.map +1 -0
- package/dist/litellm/budget-form.d.ts +50 -0
- package/dist/litellm/budget-form.d.ts.map +1 -0
- package/dist/litellm/budget-operations.d.ts +20 -0
- package/dist/litellm/budget-operations.d.ts.map +1 -0
- package/dist/litellm/budget.d.ts +61 -0
- package/dist/litellm/budget.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 +9 -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 +20 -0
- package/dist/litellm/index.d.ts.map +1 -1
- package/dist/litellm/index.js +3201 -2
- package/dist/litellm/index.js.map +70 -5
- 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/operations.d.ts +22 -0
- package/dist/litellm/operations.d.ts.map +1 -1
- 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/auth-role-form.d.ts +9 -0
- package/dist/openbao/auth-role-form.d.ts.map +1 -1
- package/dist/openbao/auth-role-wire.d.ts.map +1 -1
- package/dist/openbao/fake-engines-roles.d.ts.map +1 -1
- package/dist/openbao/index.js +43 -6
- package/dist/openbao/index.js.map +5 -5
- package/dist/ownership/rows.d.ts +11 -0
- package/dist/ownership/rows.d.ts.map +1 -1
- package/dist/postgres/connection.d.ts +32 -2
- package/dist/postgres/connection.d.ts.map +1 -1
- package/dist/postgres/database-sql.d.ts +28 -9
- package/dist/postgres/database-sql.d.ts.map +1 -1
- package/dist/postgres/database.d.ts +1 -1
- package/dist/postgres/database.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 +40 -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 +35 -8
- package/dist/postgres/index.d.ts.map +1 -1
- package/dist/postgres/index.js +2063 -39
- package/dist/postgres/index.js.map +47 -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 +17 -7
- package/dist/postgres/providers.d.ts.map +1 -1
- package/dist/postgres/psql-executor.d.ts +25 -0
- package/dist/postgres/psql-executor.d.ts.map +1 -0
- package/dist/postgres/role-attrs.d.ts +109 -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 +126 -0
- package/dist/postgres/role-errors.d.ts.map +1 -0
- package/dist/postgres/role-membership-sql.d.ts +75 -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 +6 -0
- package/dist/postgres/role-scram.d.ts.map +1 -0
- package/dist/postgres/role-secrets.d.ts +30 -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/ceph-flag-types.d.ts +57 -0
- package/dist/proxmox/ceph-flag-types.d.ts.map +1 -0
- package/dist/proxmox/ceph-flag-wire.d.ts +77 -0
- package/dist/proxmox/ceph-flag-wire.d.ts.map +1 -0
- package/dist/proxmox/ceph-flag.d.ts +15 -50
- package/dist/proxmox/ceph-flag.d.ts.map +1 -1
- package/dist/proxmox/ceph-osd-write.d.ts +10 -13
- package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
- package/dist/proxmox/index.js +806 -740
- package/dist/proxmox/index.js.map +9 -8
- 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-budget.md +55 -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 -8
- 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 +114 -0
- package/docs/postgres-schema.md +139 -0
- package/docs/postgres.md +39 -0
- package/docs/proxmox-sdk-migration.md +45 -0
- package/docs/valkey-transport.md +29 -0
- package/docs/valkey.md +187 -0
- package/package.json +7 -3
- 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/budget-errors.ts +38 -0
- package/src/litellm/budget-form.ts +94 -0
- package/src/litellm/budget-operations.ts +51 -0
- package/src/litellm/budget.test.ts +105 -0
- package/src/litellm/budget.ts +127 -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 +66 -7
- 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 +153 -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/operations.ts +1 -1
- 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 +25 -1
- 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/openbao/auth-role-form.test.ts +37 -0
- package/src/openbao/auth-role-form.ts +12 -1
- package/src/openbao/auth-role-wire.test.ts +1 -0
- package/src/openbao/auth-role-wire.ts +13 -1
- package/src/openbao/distilled-retry.test.ts +1 -0
- package/src/openbao/fake-engines-roles.ts +7 -1
- package/src/ownership/ownership.test.ts +33 -1
- package/src/ownership/rows.ts +53 -0
- package/src/postgres/connection.ts +72 -3
- 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 +40 -12
- package/src/postgres/database.ts +3 -2
- 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 +220 -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 +161 -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 +137 -7
- package/src/postgres/installed-classifier.ts +36 -0
- package/src/postgres/providers.ts +44 -7
- package/src/postgres/psql-executor-escaping.test.ts +67 -0
- package/src/postgres/psql-executor.test.ts +167 -0
- package/src/postgres/psql-executor.ts +170 -0
- package/src/postgres/role-adopt.test.ts +98 -0
- package/src/postgres/role-attrs.ts +134 -0
- package/src/postgres/role-diff.ts +89 -0
- package/src/postgres/role-errors.ts +198 -0
- package/src/postgres/role-guard.test.ts +60 -0
- package/src/postgres/role-identity.test.ts +185 -0
- package/src/postgres/role-lifecycle.test.ts +228 -0
- package/src/postgres/role-membership-sql.ts +198 -0
- package/src/postgres/role-password.test.ts +158 -0
- package/src/postgres/role-privilege.test.ts +80 -0
- package/src/postgres/role-provenance.test.ts +200 -0
- package/src/postgres/role-provider.ts +104 -0
- package/src/postgres/role-refusals.test.ts +250 -0
- package/src/postgres/role-scram.test.ts +38 -0
- package/src/postgres/role-scram.ts +38 -0
- package/src/postgres/role-secrets.ts +46 -0
- package/src/postgres/role-sql.ts +211 -0
- package/src/postgres/role-valid-until.test.ts +68 -0
- package/src/postgres/role.ts +218 -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 +161 -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/proxmox/ceph-flag-types.ts +70 -0
- package/src/proxmox/ceph-flag-wire.test.ts +72 -0
- package/src/proxmox/ceph-flag-wire.ts +123 -0
- package/src/proxmox/ceph-flag.ts +77 -130
- package/src/proxmox/ceph-osd-write.ts +10 -13
- 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
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
// src/postgres/connection.ts
|
|
2
2
|
import * as PgClient from "@effect/sql-pg/PgClient";
|
|
3
3
|
import * as Context from "effect/Context";
|
|
4
|
-
import * as
|
|
4
|
+
import * as Effect3 from "effect/Effect";
|
|
5
5
|
import * as Layer from "effect/Layer";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
var postgresConnection = (config) => Layer.succeed(PostgresConnection, Effect.succeed(config));
|
|
10
|
-
var withPg = (build) => Effect.gen(function* () {
|
|
11
|
-
const resolveConfig = yield* PostgresConnection;
|
|
12
|
-
const config = yield* resolveConfig;
|
|
13
|
-
return yield* Effect.provide(Effect.flatMap(PgClient.PgClient, build), PgClient.layer(config));
|
|
14
|
-
});
|
|
15
|
-
// src/postgres/database-attrs.ts
|
|
16
|
-
var POSTGRES_NAME_MAX_BYTES = 63;
|
|
17
|
-
var utf8ByteLength = (value) => new TextEncoder().encode(value).byteLength;
|
|
18
|
-
var nameByteRefusal = (name) => {
|
|
19
|
-
const byteLength = utf8ByteLength(name);
|
|
20
|
-
return byteLength > POSTGRES_NAME_MAX_BYTES ? { byteLength, limit: POSTGRES_NAME_MAX_BYTES } : undefined;
|
|
21
|
-
};
|
|
22
|
-
// src/postgres/database-sql.ts
|
|
7
|
+
// src/postgres/psql-executor.ts
|
|
23
8
|
import * as Effect2 from "effect/Effect";
|
|
9
|
+
import {
|
|
10
|
+
ConnectionError,
|
|
11
|
+
SqlError,
|
|
12
|
+
SqlSyntaxError,
|
|
13
|
+
UnknownError
|
|
14
|
+
} from "effect/unstable/sql/SqlError";
|
|
15
|
+
|
|
16
|
+
// src/postgres/database-sql.ts
|
|
17
|
+
import * as Effect from "effect/Effect";
|
|
24
18
|
var quoteIdent = (value) => `"${value.replace(/"/g, '""')}"`;
|
|
25
|
-
var quoteStringLiteral = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
26
|
-
var withOptions = (props) => {
|
|
19
|
+
var quoteStringLiteral = (value) => `E'${value.replace(/\\/g, "\\\\").replace(/'/g, "''")}'`;
|
|
20
|
+
var withOptions = (props, template) => {
|
|
27
21
|
const parts = [`OWNER ${quoteIdent(props.owner)}`];
|
|
22
|
+
if (template !== undefined)
|
|
23
|
+
parts.push(`TEMPLATE ${quoteIdent(template)}`);
|
|
28
24
|
if (props.encoding !== undefined)
|
|
29
25
|
parts.push(`ENCODING ${quoteStringLiteral(props.encoding)}`);
|
|
30
26
|
if (props.localeProvider !== undefined) {
|
|
@@ -46,7 +42,7 @@ var withOptions = (props) => {
|
|
|
46
42
|
parts.push(`IS_TEMPLATE ${props.isTemplate ? "true" : "false"}`);
|
|
47
43
|
return parts.join(" ");
|
|
48
44
|
};
|
|
49
|
-
var buildCreateDatabaseSql = (props) => `CREATE DATABASE ${quoteIdent(props.name)} WITH ${withOptions(props)}`;
|
|
45
|
+
var buildCreateDatabaseSql = (props, template) => `CREATE DATABASE ${quoteIdent(props.name)} WITH ${withOptions(props, template)}`;
|
|
50
46
|
var isDuplicateDatabaseRace = (error) => {
|
|
51
47
|
if (error.reason._tag !== "SqlSyntaxError")
|
|
52
48
|
return false;
|
|
@@ -54,7 +50,9 @@ var isDuplicateDatabaseRace = (error) => {
|
|
|
54
50
|
return typeof cause === "object" && cause !== null && cause.code === "42P04";
|
|
55
51
|
};
|
|
56
52
|
var ROLE_EXISTS_SQL = "SELECT 1 AS present FROM pg_roles WHERE rolname = $1";
|
|
57
|
-
var roleExists = (pg, role) =>
|
|
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);
|
|
58
56
|
var SELECT_DATABASE_SQL = `SELECT
|
|
59
57
|
d.oid AS oid,
|
|
60
58
|
d.datname AS name,
|
|
@@ -73,7 +71,7 @@ var SELECT_DATABASE_SQL = `SELECT
|
|
|
73
71
|
FROM pg_database d
|
|
74
72
|
JOIN pg_tablespace t ON t.oid = d.dattablespace
|
|
75
73
|
WHERE d.datname = $1`;
|
|
76
|
-
var selectDatabase = (pg, name) =>
|
|
74
|
+
var selectDatabase = (pg, name) => Effect.map(pg.unsafe(SELECT_DATABASE_SQL, [name]), (rows) => rows[0]);
|
|
77
75
|
var firstDrift = (props, live) => {
|
|
78
76
|
const checks = [
|
|
79
77
|
["encoding", props.encoding, live.encoding],
|
|
@@ -91,12 +89,121 @@ var firstDrift = (props, live) => {
|
|
|
91
89
|
}
|
|
92
90
|
return;
|
|
93
91
|
};
|
|
92
|
+
|
|
93
|
+
// src/postgres/psql-executor.ts
|
|
94
|
+
var inlineParams = (sql, params) => params.length === 0 ? sql : sql.replace(/\$(\d+)/g, (_match, index) => {
|
|
95
|
+
const value = params[Number(index) - 1];
|
|
96
|
+
if (typeof value !== "string") {
|
|
97
|
+
throw new Error(`psql executor: param $${index} must be a string, got ${typeof value}`);
|
|
98
|
+
}
|
|
99
|
+
return quoteStringLiteral(value);
|
|
100
|
+
});
|
|
101
|
+
var normalizeRow = (row) => typeof row === "object" && row !== null && typeof row.oid === "string" ? { ...row, oid: Number(row.oid) } : row;
|
|
102
|
+
var returnsRows = (sql) => /^\s*(SELECT|WITH)\b/i.test(sql);
|
|
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]'");
|
|
105
|
+
var failure = (operation, result) => {
|
|
106
|
+
const state = /ERROR:\s+([0-9A-Z]{5}):/.exec(result.stderr)?.[1];
|
|
107
|
+
const stderr = redactPasswordLiterals(result.stderr.trim());
|
|
108
|
+
const fields = {
|
|
109
|
+
cause: Object.assign(new Error(stderr), state === undefined ? {} : { code: state }),
|
|
110
|
+
message: stderr,
|
|
111
|
+
operation: redactPasswordLiterals(operation)
|
|
112
|
+
};
|
|
113
|
+
const reason = state === undefined ? new ConnectionError(fields) : state.startsWith("42") ? new SqlSyntaxError(fields) : new UnknownError(fields);
|
|
114
|
+
return new SqlError({ reason });
|
|
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
|
+
});
|
|
143
|
+
var makePsqlExecutor = (run, target) => ({
|
|
144
|
+
unsafe: (sql, params = []) => Effect2.gen(function* () {
|
|
145
|
+
const inlined = yield* Effect2.try({
|
|
146
|
+
try: () => inlineParams(sql, params),
|
|
147
|
+
catch: (cause) => new SqlError({ reason: new UnknownError({ cause, operation: "psql inline params" }) })
|
|
148
|
+
});
|
|
149
|
+
const rows = returnsRows(inlined);
|
|
150
|
+
const result = yield* runScript(run, target, rows ? wrapRows(inlined) : `${inlined};`, redactPasswordLiterals(inlined).slice(0, 40));
|
|
151
|
+
if (!rows)
|
|
152
|
+
return [];
|
|
153
|
+
const parsed = yield* Effect2.try({
|
|
154
|
+
try: () => JSON.parse(result.stdout.trim() || "[]").map(normalizeRow),
|
|
155
|
+
catch: (cause) => new SqlError({ reason: new UnknownError({ cause, operation: "psql parse output" }) })
|
|
156
|
+
});
|
|
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");
|
|
163
|
+
})
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// src/postgres/connection.ts
|
|
167
|
+
class PostgresConnection extends Context.Service()("Postgres.Connection") {
|
|
168
|
+
}
|
|
169
|
+
var postgresConnection = (config) => Layer.succeed(PostgresConnection, Effect3.succeed(config));
|
|
170
|
+
var postgresRunnerConnection = (config) => Layer.succeed(PostgresConnection, Effect3.succeed(config));
|
|
171
|
+
var PostgresPool = Context.Reference("homeflare/postgres/Pool", {
|
|
172
|
+
defaultValue: () => PgClient.layer
|
|
173
|
+
});
|
|
174
|
+
var isRunner = (c) => ("run" in c);
|
|
175
|
+
var withPg = (build, database) => Effect3.gen(function* () {
|
|
176
|
+
const resolveConfig = yield* PostgresConnection;
|
|
177
|
+
const config = yield* resolveConfig;
|
|
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
|
|
183
|
+
});
|
|
184
|
+
}
|
|
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));
|
|
193
|
+
});
|
|
194
|
+
// src/postgres/database-attrs.ts
|
|
195
|
+
var POSTGRES_NAME_MAX_BYTES = 63;
|
|
196
|
+
var utf8ByteLength = (value) => new TextEncoder().encode(value).byteLength;
|
|
197
|
+
var nameByteRefusal = (name) => {
|
|
198
|
+
const byteLength = utf8ByteLength(name);
|
|
199
|
+
return byteLength > POSTGRES_NAME_MAX_BYTES ? { byteLength, limit: POSTGRES_NAME_MAX_BYTES } : undefined;
|
|
200
|
+
};
|
|
94
201
|
// src/postgres/database.ts
|
|
95
202
|
import { Resource } from "alchemy";
|
|
96
203
|
import { Unowned } from "alchemy/AdoptPolicy";
|
|
97
204
|
import { isResolved } from "alchemy/Diff";
|
|
98
205
|
import * as Provider from "alchemy/Provider";
|
|
99
|
-
import * as
|
|
206
|
+
import * as Effect4 from "effect/Effect";
|
|
100
207
|
|
|
101
208
|
// src/postgres/errors.ts
|
|
102
209
|
import * as Data from "effect/Data";
|
|
@@ -148,23 +255,23 @@ var liveDrift = (props, live) => {
|
|
|
148
255
|
}
|
|
149
256
|
return firstDrift(props, live);
|
|
150
257
|
};
|
|
151
|
-
var reconcileWithClient = (pg, props) =>
|
|
258
|
+
var reconcileWithClient = (pg, props, template) => Effect4.gen(function* () {
|
|
152
259
|
const observed = yield* selectDatabase(pg, props.name);
|
|
153
260
|
if (observed === undefined) {
|
|
154
261
|
const ownerPresent = yield* roleExists(pg, props.owner);
|
|
155
262
|
if (!ownerPresent) {
|
|
156
|
-
return yield*
|
|
263
|
+
return yield* Effect4.fail(new PostgresDatabaseOwnerMissing({ database: props.name, owner: props.owner }));
|
|
157
264
|
}
|
|
158
|
-
yield* pg.unsafe(buildCreateDatabaseSql(props)).pipe(
|
|
265
|
+
yield* pg.unsafe(buildCreateDatabaseSql(props, template)).pipe(Effect4.asVoid, Effect4.catchTag("SqlError", (error) => isDuplicateDatabaseRace(error) ? Effect4.succeed(undefined) : Effect4.fail(error)));
|
|
159
266
|
const created = yield* selectDatabase(pg, props.name);
|
|
160
267
|
if (created === undefined) {
|
|
161
|
-
return yield*
|
|
268
|
+
return yield* Effect4.fail(new PostgresDatabaseCreateVanished({ database: props.name }));
|
|
162
269
|
}
|
|
163
270
|
return created;
|
|
164
271
|
}
|
|
165
272
|
const drift = liveDrift(props, observed);
|
|
166
273
|
if (drift !== undefined) {
|
|
167
|
-
return yield*
|
|
274
|
+
return yield* Effect4.fail(new PostgresDatabaseDrift({
|
|
168
275
|
database: props.name,
|
|
169
276
|
prop: drift.prop,
|
|
170
277
|
declared: drift.declared,
|
|
@@ -174,42 +281,1871 @@ var reconcileWithClient = (pg, props) => Effect3.gen(function* () {
|
|
|
174
281
|
return observed;
|
|
175
282
|
});
|
|
176
283
|
var readWithClient = (pg, name) => selectDatabase(pg, name);
|
|
177
|
-
var diffPostgresDatabase = (news, output) =>
|
|
284
|
+
var diffPostgresDatabase = (news, output) => Effect4.gen(function* () {
|
|
178
285
|
if (output === undefined || !isResolved(news))
|
|
179
286
|
return;
|
|
180
287
|
if (news.name !== output.name) {
|
|
181
|
-
return yield*
|
|
288
|
+
return yield* Effect4.fail(new PostgresDatabaseRenameRefused({ from: output.name, to: news.name }));
|
|
182
289
|
}
|
|
183
290
|
const nameRefusal = nameByteRefusal(news.name);
|
|
184
291
|
if (nameRefusal !== undefined) {
|
|
185
|
-
return yield*
|
|
292
|
+
return yield* Effect4.fail(new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }));
|
|
186
293
|
}
|
|
187
294
|
const changed = news.owner !== output.owner || firstDrift(news, output) !== undefined;
|
|
188
295
|
return changed ? { action: "update" } : { action: "noop" };
|
|
189
296
|
});
|
|
190
297
|
var postgresDatabaseHandlers = PostgresDatabase.Provider.of({
|
|
191
|
-
list: () =>
|
|
298
|
+
list: () => Effect4.succeed([]),
|
|
192
299
|
nuke: { skip: true },
|
|
193
|
-
read: ({ olds, output }) =>
|
|
300
|
+
read: ({ olds, output }) => Effect4.gen(function* () {
|
|
194
301
|
const name = output?.name ?? olds.name;
|
|
195
302
|
const live = yield* withPg((pg) => readWithClient(pg, name));
|
|
196
303
|
return live === undefined ? undefined : Unowned(live);
|
|
197
304
|
}),
|
|
198
305
|
diff: ({ news, output }) => diffPostgresDatabase(news, output),
|
|
199
|
-
reconcile: ({ news }) =>
|
|
306
|
+
reconcile: ({ news }) => Effect4.gen(function* () {
|
|
200
307
|
const nameRefusal = nameByteRefusal(news.name);
|
|
201
308
|
if (nameRefusal !== undefined) {
|
|
202
|
-
return yield*
|
|
309
|
+
return yield* Effect4.fail(new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }));
|
|
203
310
|
}
|
|
204
|
-
return yield* withPg((pg) => reconcileWithClient(pg, news));
|
|
311
|
+
return yield* withPg((pg, ctx) => reconcileWithClient(pg, news, ctx.template));
|
|
205
312
|
}),
|
|
206
|
-
delete: ({ olds }) =>
|
|
313
|
+
delete: ({ olds }) => Effect4.fail(new PostgresDatabaseDropRefused({ database: olds.name }))
|
|
207
314
|
});
|
|
208
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 ? "a grantor role that no longer exists" : `grantor "${this.grantor}"`;
|
|
1485
|
+
return `Postgres.Role "${this.role}": membership of "${this.parent}" ` + `(${this.declared ? "declared, options unsafe" : "not declared"}) could not be repaired ` + `after its statements — the row still stands under ${who}. pg_auth_members keys a ` + "membership on (parent, member, grantor), and a grantor that no longer exists cannot be " + "named in REVOKE … GRANTED BY. Revoke the row by hand as a bootstrap superuser " + "(`REVOKE role FROM member GRANTED BY <grantor>`; a grantor oid with no role name needs " + "catalog surgery) and re-plan.";
|
|
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
|
+
// src/postgres/role-membership-sql.ts
|
|
1502
|
+
var MEMBERSHIP_SQL = `SELECT parent.rolname AS parent,
|
|
1503
|
+
grantor.rolname AS grantor,
|
|
1504
|
+
m.admin_option AS admin,
|
|
1505
|
+
m.set_option AS set
|
|
1506
|
+
FROM pg_auth_members m
|
|
1507
|
+
JOIN pg_roles member ON member.oid = m.member
|
|
1508
|
+
JOIN pg_roles parent ON parent.oid = m.roleid
|
|
1509
|
+
LEFT JOIN pg_roles grantor ON grantor.oid = m.grantor
|
|
1510
|
+
WHERE member.rolname = $1
|
|
1511
|
+
ORDER BY parent.rolname, grantor.rolname NULLS FIRST`;
|
|
1512
|
+
var selectRoleMemberships = (pg, name) => Effect18.map(pg.unsafe(MEMBERSHIP_SQL, [name]), (rows) => rows);
|
|
1513
|
+
var buildGrantMembershipSql = (member, parent) => `GRANT ${quoteIdent(parent)} TO ${quoteIdent(member)} WITH SET FALSE`;
|
|
1514
|
+
var buildRepairMembershipSql = (member, parent) => `GRANT ${quoteIdent(parent)} TO ${quoteIdent(member)} WITH ADMIN FALSE, SET FALSE`;
|
|
1515
|
+
var buildRevokeGrantorMembershipSql = (member, parent, grantor) => `REVOKE ${quoteIdent(parent)} FROM ${quoteIdent(member)} GRANTED BY ${quoteIdent(grantor)}`;
|
|
1516
|
+
var unsafeMemberships = (live) => new Set((live.memberships ?? []).filter((row) => row.admin || row.set).map((row) => row.parent));
|
|
1517
|
+
var membershipDrift = (declared, live, unsafe = new Set) => {
|
|
1518
|
+
if (declared === undefined)
|
|
1519
|
+
return { grants: [], revokes: [] };
|
|
1520
|
+
const wanted = [...new Set(declared)].sort();
|
|
1521
|
+
const current = [...new Set(live)].sort();
|
|
1522
|
+
return {
|
|
1523
|
+
grants: wanted.filter((parent) => !current.includes(parent) || unsafe.has(parent)),
|
|
1524
|
+
revokes: [
|
|
1525
|
+
...current.filter((parent) => !wanted.includes(parent)),
|
|
1526
|
+
...wanted.filter((parent) => current.includes(parent) && unsafe.has(parent))
|
|
1527
|
+
]
|
|
1528
|
+
};
|
|
1529
|
+
};
|
|
1530
|
+
var assertParentsExist = (pg, role, parents) => Effect18.gen(function* () {
|
|
1531
|
+
if (parents === undefined)
|
|
1532
|
+
return;
|
|
1533
|
+
for (const parent of [...new Set(parents)].sort()) {
|
|
1534
|
+
if (!(yield* roleExists(pg, parent))) {
|
|
1535
|
+
return yield* Effect18.fail(new PostgresRoleParentMissing({ role, parent }));
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
var syncMemberships = (pg, props, rows) => Effect18.gen(function* () {
|
|
1540
|
+
if (props.memberOf === undefined) {
|
|
1541
|
+
return [...new Set(rows.map((row) => row.parent))].sort();
|
|
1542
|
+
}
|
|
1543
|
+
yield* assertParentsExist(pg, props.name, props.memberOf);
|
|
1544
|
+
const wanted = [...new Set(props.memberOf)].sort();
|
|
1545
|
+
for (const row of rows) {
|
|
1546
|
+
if (wanted.includes(row.parent) || row.grantor === null)
|
|
1547
|
+
continue;
|
|
1548
|
+
yield* pg.unsafe(buildRevokeGrantorMembershipSql(props.name, row.parent, row.grantor)).pipe(Effect18.asVoid);
|
|
1549
|
+
}
|
|
1550
|
+
const repaired = [
|
|
1551
|
+
...new Set(rows.filter((row) => wanted.includes(row.parent) && (row.admin || row.set) && row.grantor !== null).map((row) => row.parent))
|
|
1552
|
+
];
|
|
1553
|
+
for (const parent of repaired) {
|
|
1554
|
+
yield* pg.unsafe(buildRepairMembershipSql(props.name, parent)).pipe(Effect18.asVoid);
|
|
1555
|
+
}
|
|
1556
|
+
if (repaired.length > 0) {
|
|
1557
|
+
const midRead = yield* selectRoleMemberships(pg, props.name);
|
|
1558
|
+
for (const row of midRead) {
|
|
1559
|
+
if (wanted.includes(row.parent) && (row.admin || row.set) && row.grantor !== null) {
|
|
1560
|
+
yield* pg.unsafe(buildRevokeGrantorMembershipSql(props.name, row.parent, row.grantor)).pipe(Effect18.asVoid);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
for (const parent of wanted.filter((p) => !rows.some((row) => row.parent === p))) {
|
|
1565
|
+
yield* pg.unsafe(buildGrantMembershipSql(props.name, parent)).pipe(Effect18.asVoid);
|
|
1566
|
+
}
|
|
1567
|
+
for (const row of yield* selectRoleMemberships(pg, props.name)) {
|
|
1568
|
+
if (!wanted.includes(row.parent) || row.admin || row.set) {
|
|
1569
|
+
return yield* Effect18.fail(new PostgresRoleMembershipUnrepaired({
|
|
1570
|
+
role: props.name,
|
|
1571
|
+
parent: row.parent,
|
|
1572
|
+
grantor: row.grantor,
|
|
1573
|
+
declared: wanted.includes(row.parent)
|
|
1574
|
+
}));
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
return wanted;
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
// src/postgres/role-sql.ts
|
|
1581
|
+
var SELECT_ROLE_SQL = `SELECT
|
|
1582
|
+
r.oid AS oid,
|
|
1583
|
+
r.rolname AS name,
|
|
1584
|
+
r.rolcanlogin AS login,
|
|
1585
|
+
r.rolconnlimit AS "connectionLimit",
|
|
1586
|
+
r.rolinherit AS inherit,
|
|
1587
|
+
r.rolsuper AS superuser,
|
|
1588
|
+
r.rolcreaterole AS createrole,
|
|
1589
|
+
r.rolcreatedb AS createdb,
|
|
1590
|
+
r.rolreplication AS replication,
|
|
1591
|
+
r.rolbypassrls AS bypassrls,
|
|
1592
|
+
to_char(r.rolvaliduntil AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS "validUntil"
|
|
1593
|
+
FROM pg_roles r
|
|
1594
|
+
WHERE r.rolname = $1`;
|
|
1595
|
+
var selectRole = (pg, name) => Effect19.map(pg.unsafe(SELECT_ROLE_SQL, [name]), (rows) => rows[0]);
|
|
1596
|
+
var readRoleWithClient = (pg, name) => Effect19.gen(function* () {
|
|
1597
|
+
const row = yield* selectRole(pg, name);
|
|
1598
|
+
if (row === undefined)
|
|
1599
|
+
return;
|
|
1600
|
+
const memberships = yield* selectRoleMemberships(pg, name);
|
|
1601
|
+
return {
|
|
1602
|
+
...row,
|
|
1603
|
+
memberOf: [...new Set(memberships.map((membership) => membership.parent))].sort(),
|
|
1604
|
+
memberships
|
|
1605
|
+
};
|
|
1606
|
+
});
|
|
1607
|
+
var withOptions2 = (props) => {
|
|
1608
|
+
const parts = [];
|
|
1609
|
+
parts.push(props.login ? "LOGIN" : "NOLOGIN");
|
|
1610
|
+
parts.push(props.inherit ? "INHERIT" : "NOINHERIT");
|
|
1611
|
+
parts.push(`CONNECTION LIMIT ${String(Math.trunc(props.connectionLimit))}`);
|
|
1612
|
+
if (props.validUntil !== undefined) {
|
|
1613
|
+
parts.push(`VALID UNTIL ${quoteStringLiteral(props.validUntil)}`);
|
|
1614
|
+
}
|
|
1615
|
+
return parts.join(" ");
|
|
1616
|
+
};
|
|
1617
|
+
var buildCreateRoleSql = (props) => `CREATE ROLE ${quoteIdent(props.name)} WITH ${withOptions2(props)}`;
|
|
1618
|
+
var buildAlterRoleSql = (name, change) => {
|
|
1619
|
+
const who = quoteIdent(name);
|
|
1620
|
+
switch (change.prop) {
|
|
1621
|
+
case "login":
|
|
1622
|
+
return `ALTER ROLE ${who} WITH ${change.value ? "LOGIN" : "NOLOGIN"}`;
|
|
1623
|
+
case "inherit":
|
|
1624
|
+
return `ALTER ROLE ${who} WITH ${change.value ? "INHERIT" : "NOINHERIT"}`;
|
|
1625
|
+
case "connectionLimit":
|
|
1626
|
+
return `ALTER ROLE ${who} WITH CONNECTION LIMIT ${String(Math.trunc(change.value))}`;
|
|
1627
|
+
case "validUntil":
|
|
1628
|
+
return `ALTER ROLE ${who} WITH VALID UNTIL ${quoteStringLiteral(change.value)}`;
|
|
1629
|
+
}
|
|
1630
|
+
};
|
|
1631
|
+
var buildSetPasswordSql = (name, verifier) => `ALTER ROLE ${quoteIdent(name)} WITH PASSWORD ${quoteStringLiteral(verifier)}`;
|
|
1632
|
+
var buildDropRoleSql = (name) => `DROP ROLE IF EXISTS ${quoteIdent(name)}`;
|
|
1633
|
+
var buildCreateStatements = (props, verifier) => [
|
|
1634
|
+
buildCreateRoleSql(props),
|
|
1635
|
+
...verifier === undefined ? [] : [buildSetPasswordSql(props.name, verifier)],
|
|
1636
|
+
...[...new Set(props.memberOf ?? [])].sort().map((parent) => buildGrantMembershipSql(props.name, parent))
|
|
1637
|
+
];
|
|
1638
|
+
var scalarDrift = (props, live) => {
|
|
1639
|
+
const changes = [];
|
|
1640
|
+
if (props.login !== live.login)
|
|
1641
|
+
changes.push({ prop: "login", value: props.login });
|
|
1642
|
+
if (props.inherit !== live.inherit)
|
|
1643
|
+
changes.push({ prop: "inherit", value: props.inherit });
|
|
1644
|
+
if (props.connectionLimit !== live.connectionLimit) {
|
|
1645
|
+
changes.push({ prop: "connectionLimit", value: props.connectionLimit });
|
|
1646
|
+
}
|
|
1647
|
+
if (props.validUntil !== undefined && !sameValidUntil(props.validUntil, live.validUntil)) {
|
|
1648
|
+
changes.push({ prop: "validUntil", value: props.validUntil });
|
|
1649
|
+
}
|
|
1650
|
+
return changes;
|
|
1651
|
+
};
|
|
1652
|
+
var declarationMatches = (props, live) => {
|
|
1653
|
+
const wanted = props.memberOf === undefined ? undefined : [...new Set(props.memberOf)].sort().join("\x00");
|
|
1654
|
+
const found = [...live.memberOf ?? []].sort().join("\x00");
|
|
1655
|
+
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);
|
|
1656
|
+
};
|
|
1657
|
+
var storedAttributes = (live) => {
|
|
1658
|
+
const { memberships: _observeOnly, ...stored } = live;
|
|
1659
|
+
return stored;
|
|
1660
|
+
};
|
|
1661
|
+
var privilegedFlags = (live) => {
|
|
1662
|
+
const flags = [];
|
|
1663
|
+
if (live.superuser === true)
|
|
1664
|
+
flags.push("SUPERUSER");
|
|
1665
|
+
if (live.createrole === true)
|
|
1666
|
+
flags.push("CREATEROLE");
|
|
1667
|
+
if (live.createdb === true)
|
|
1668
|
+
flags.push("CREATEDB");
|
|
1669
|
+
if (live.replication === true)
|
|
1670
|
+
flags.push("REPLICATION");
|
|
1671
|
+
if (live.bypassrls === true)
|
|
1672
|
+
flags.push("BYPASSRLS");
|
|
1673
|
+
return flags;
|
|
1674
|
+
};
|
|
1675
|
+
// src/postgres/role.ts
|
|
1676
|
+
import { Resource as Resource4 } from "alchemy";
|
|
1677
|
+
import * as Effect20 from "effect/Effect";
|
|
1678
|
+
import * as Redacted2 from "effect/Redacted";
|
|
1679
|
+
|
|
1680
|
+
// src/postgres/role-secrets.ts
|
|
1681
|
+
import * as Redacted from "effect/Redacted";
|
|
1682
|
+
|
|
1683
|
+
// src/secrets/write-only.ts
|
|
1684
|
+
import { Buffer } from "node:buffer";
|
|
1685
|
+
import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
|
|
1686
|
+
var resolveAll = (refs, env = process.env) => {
|
|
1687
|
+
const values = {};
|
|
1688
|
+
const missing = [];
|
|
1689
|
+
for (const [key, ref] of Object.entries(refs)) {
|
|
1690
|
+
const value = env[ref.fromEnv];
|
|
1691
|
+
if (value === undefined || value === "")
|
|
1692
|
+
missing.push(ref.fromEnv);
|
|
1693
|
+
else
|
|
1694
|
+
values[key] = value;
|
|
1695
|
+
}
|
|
1696
|
+
return { missing: [...new Set(missing)].sort(), values };
|
|
1697
|
+
};
|
|
1698
|
+
var COST = { N: 16384, maxmem: 64 * 1024 * 1024, p: 1, r: 8 };
|
|
1699
|
+
var PREFIX = "scrypt";
|
|
1700
|
+
var canonical = (values) => JSON.stringify(Object.keys(values).sort().map((key) => [key, values[key]]));
|
|
1701
|
+
var digest = (salt, values) => scryptSync(canonical(values), salt, 32, COST);
|
|
1702
|
+
var seal = (values, salt) => {
|
|
1703
|
+
if (Object.keys(values).length === 0)
|
|
1704
|
+
return "";
|
|
1705
|
+
const bytes = salt === undefined ? randomBytes(16) : Buffer.from(salt, "utf8");
|
|
1706
|
+
return `${PREFIX}:${bytes.toString("base64url")}:${digest(bytes, values).toString("base64url")}`;
|
|
1707
|
+
};
|
|
1708
|
+
var sealMatches = (sealed, values) => {
|
|
1709
|
+
if (sealed === "")
|
|
1710
|
+
return Object.keys(values).length === 0;
|
|
1711
|
+
const [prefix, salt, stored, extra] = sealed.split(":");
|
|
1712
|
+
if (prefix !== PREFIX || salt === undefined || stored === undefined || extra !== undefined) {
|
|
1713
|
+
return false;
|
|
1714
|
+
}
|
|
1715
|
+
const expected = Buffer.from(stored, "base64url");
|
|
1716
|
+
const actual = digest(Buffer.from(salt, "base64url"), values);
|
|
1717
|
+
return expected.length === actual.length && timingSafeEqual(expected, actual);
|
|
1718
|
+
};
|
|
1719
|
+
|
|
1720
|
+
// src/postgres/role-secrets.ts
|
|
1721
|
+
var resolvePassword = (props, env = process.env) => {
|
|
1722
|
+
if (props.password === undefined)
|
|
1723
|
+
return { variable: undefined, value: undefined };
|
|
1724
|
+
const { values } = resolveAll({ password: props.password }, env);
|
|
1725
|
+
const raw = values["password"];
|
|
1726
|
+
return {
|
|
1727
|
+
variable: props.password.fromEnv,
|
|
1728
|
+
value: raw === undefined ? undefined : Redacted.make(raw)
|
|
1729
|
+
};
|
|
1730
|
+
};
|
|
1731
|
+
var sealPassword = (value2) => seal({ password: Redacted.value(value2) });
|
|
1732
|
+
var passwordMatchesSeal = (value2, sealed) => sealMatches(sealed, { password: Redacted.value(value2) });
|
|
1733
|
+
|
|
1734
|
+
// src/postgres/role-scram.ts
|
|
1735
|
+
import { createHash, createHmac, pbkdf2Sync, randomBytes as randomBytes2 } from "node:crypto";
|
|
1736
|
+
var ITERATIONS = 4096;
|
|
1737
|
+
var SALT_BYTES = 16;
|
|
1738
|
+
var KEY_BYTES = 32;
|
|
1739
|
+
var hmac = (key, label) => createHmac("sha256", key).update(label, "utf8").digest();
|
|
1740
|
+
var b64 = (value2) => value2.toString("base64");
|
|
1741
|
+
var scramSha256Verifier = (password, salt) => {
|
|
1742
|
+
const bytes = salt ?? randomBytes2(SALT_BYTES);
|
|
1743
|
+
const saltedPassword = pbkdf2Sync(password, bytes, ITERATIONS, KEY_BYTES, "sha256");
|
|
1744
|
+
const storedKey = createHash("sha256").update(hmac(saltedPassword, "Client Key")).digest();
|
|
1745
|
+
const serverKey = hmac(saltedPassword, "Server Key");
|
|
1746
|
+
return `SCRAM-SHA-256$${ITERATIONS}:${b64(bytes)}$${b64(storedKey)}:${b64(serverKey)}`;
|
|
1747
|
+
};
|
|
1748
|
+
|
|
1749
|
+
// src/postgres/role.ts
|
|
1750
|
+
var PostgresRole = Resource4("Postgres.Role", {
|
|
1751
|
+
defaultRemovalPolicy: "retain"
|
|
1752
|
+
});
|
|
1753
|
+
var isPostgresRole = (value3) => (typeof value3 === "object" || typeof value3 === "function") && value3 !== null && value3.Type === "Postgres.Role";
|
|
1754
|
+
var reconcileWithClient4 = (pg, props, env = process.env, previousSeal = "", storedOid) => Effect20.gen(function* () {
|
|
1755
|
+
const resolved = resolvePassword(props, env);
|
|
1756
|
+
const observed = yield* readRoleWithClient(pg, props.name);
|
|
1757
|
+
if (observed === undefined) {
|
|
1758
|
+
if (resolved.variable !== undefined && resolved.value === undefined) {
|
|
1759
|
+
return yield* Effect20.fail(new PostgresRolePasswordEnvUnsetError({ role: props.name, variable: resolved.variable }));
|
|
1760
|
+
}
|
|
1761
|
+
yield* assertParentsExist(pg, props.name, props.memberOf);
|
|
1762
|
+
const verifier = resolved.value === undefined ? undefined : scramSha256Verifier(Redacted2.value(resolved.value));
|
|
1763
|
+
yield* pg.transaction(buildCreateStatements(props, verifier));
|
|
1764
|
+
const created = yield* readRoleWithClient(pg, props.name);
|
|
1765
|
+
if (created === undefined) {
|
|
1766
|
+
return yield* Effect20.fail(new PostgresRoleCreateVanished({ role: props.name }));
|
|
1767
|
+
}
|
|
1768
|
+
return {
|
|
1769
|
+
...storedAttributes(created),
|
|
1770
|
+
passwordSeal: resolved.value === undefined ? "" : sealPassword(resolved.value)
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
if (storedOid !== undefined && observed.oid !== storedOid) {
|
|
1774
|
+
return yield* Effect20.fail(new PostgresRoleIdentityRefused({
|
|
1775
|
+
role: props.name,
|
|
1776
|
+
storedOid,
|
|
1777
|
+
liveOid: observed.oid
|
|
1778
|
+
}));
|
|
1779
|
+
}
|
|
1780
|
+
const flags = privilegedFlags(observed);
|
|
1781
|
+
if (flags.length > 0) {
|
|
1782
|
+
return yield* Effect20.fail(new PostgresRolePrivilegedRefused({ role: props.name, flags }));
|
|
1783
|
+
}
|
|
1784
|
+
if (resolved.variable !== undefined && resolved.value === undefined && previousSeal === "") {
|
|
1785
|
+
return yield* Effect20.fail(new PostgresRolePasswordEnvUnsetError({ role: props.name, variable: resolved.variable }));
|
|
1786
|
+
}
|
|
1787
|
+
yield* assertParentsExist(pg, props.name, props.memberOf);
|
|
1788
|
+
for (const change of scalarDrift(props, observed)) {
|
|
1789
|
+
yield* pg.unsafe(buildAlterRoleSql(props.name, change)).pipe(Effect20.asVoid);
|
|
1790
|
+
}
|
|
1791
|
+
const passwordMatched = resolved.value !== undefined && passwordMatchesSeal(resolved.value, previousSeal);
|
|
1792
|
+
if (resolved.value !== undefined && !passwordMatched) {
|
|
1793
|
+
yield* pg.unsafe(buildSetPasswordSql(props.name, scramSha256Verifier(Redacted2.value(resolved.value)))).pipe(Effect20.asVoid);
|
|
1794
|
+
}
|
|
1795
|
+
yield* syncMemberships(pg, props, observed.memberships ?? []);
|
|
1796
|
+
const after = yield* readRoleWithClient(pg, props.name);
|
|
1797
|
+
if (after === undefined) {
|
|
1798
|
+
return yield* Effect20.fail(new PostgresRoleCreateVanished({ role: props.name }));
|
|
1799
|
+
}
|
|
1800
|
+
return {
|
|
1801
|
+
...storedAttributes(after),
|
|
1802
|
+
passwordSeal: resolved.value === undefined || passwordMatched ? previousSeal : sealPassword(resolved.value)
|
|
1803
|
+
};
|
|
1804
|
+
});
|
|
1805
|
+
var readRole = (pg, name) => readRoleWithClient(pg, name);
|
|
1806
|
+
var refuseAtPlan = (props) => Effect20.gen(function* () {
|
|
1807
|
+
const nameRefusal = nameByteRefusal(props.name);
|
|
1808
|
+
if (nameRefusal !== undefined) {
|
|
1809
|
+
return yield* Effect20.fail(new PostgresRoleNameRefused({ name: props.name, ...nameRefusal }));
|
|
1810
|
+
}
|
|
1811
|
+
if (props.validUntil !== undefined) {
|
|
1812
|
+
const untilRefusal = validUntilRefusal(props.validUntil);
|
|
1813
|
+
if (untilRefusal !== undefined) {
|
|
1814
|
+
return yield* Effect20.fail(new PostgresRoleValidUntilRefused({
|
|
1815
|
+
role: props.name,
|
|
1816
|
+
value: props.validUntil,
|
|
1817
|
+
reason: untilRefusal.reason
|
|
1818
|
+
}));
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
});
|
|
1822
|
+
// src/postgres/role-provider.ts
|
|
1823
|
+
import * as Provider4 from "alchemy/Provider";
|
|
1824
|
+
import { isResolved as isResolved5 } from "alchemy/Diff";
|
|
1825
|
+
import * as Effect27 from "effect/Effect";
|
|
1826
|
+
|
|
1827
|
+
// src/ownership/probe.ts
|
|
1828
|
+
import { Unowned as Unowned4 } from "alchemy/AdoptPolicy";
|
|
1829
|
+
import * as Effect23 from "effect/Effect";
|
|
1830
|
+
|
|
1831
|
+
// src/ownership/rows.ts
|
|
1832
|
+
import { Stack as Stack2 } from "alchemy/Stack";
|
|
1833
|
+
import { State, isActionState } from "alchemy/State/State";
|
|
1834
|
+
import * as Effect22 from "effect/Effect";
|
|
1835
|
+
import * as Option2 from "effect/Option";
|
|
1836
|
+
|
|
1837
|
+
// src/ownership/whole.ts
|
|
1838
|
+
import { Stack } from "alchemy/Stack";
|
|
1839
|
+
import * as Effect21 from "effect/Effect";
|
|
1840
|
+
import * as Option from "effect/Option";
|
|
1841
|
+
var absent = (value3) => value3 === undefined || value3 === null;
|
|
1842
|
+
var isPlainObject = (value3) => {
|
|
1843
|
+
if (typeof value3 !== "object" || value3 === null || Array.isArray(value3))
|
|
1844
|
+
return false;
|
|
1845
|
+
const proto = Object.getPrototypeOf(value3);
|
|
1846
|
+
return proto === Object.prototype || proto === null;
|
|
1847
|
+
};
|
|
1848
|
+
var carries = (declared, recorded) => {
|
|
1849
|
+
if (absent(declared))
|
|
1850
|
+
return true;
|
|
1851
|
+
if (absent(recorded))
|
|
1852
|
+
return false;
|
|
1853
|
+
if (Array.isArray(declared)) {
|
|
1854
|
+
if (!Array.isArray(recorded))
|
|
1855
|
+
return false;
|
|
1856
|
+
return declared.every((each, index) => carries(each, recorded[index]));
|
|
1857
|
+
}
|
|
1858
|
+
if (isPlainObject(declared)) {
|
|
1859
|
+
if (!isPlainObject(recorded))
|
|
1860
|
+
return false;
|
|
1861
|
+
return Object.entries(declared).every(([key, each]) => carries(each, recorded[key]));
|
|
1862
|
+
}
|
|
1863
|
+
return true;
|
|
1864
|
+
};
|
|
1865
|
+
var wholeDeclaration = (fqn, props) => Effect21.map(Effect21.serviceOption(Stack), (stack) => {
|
|
1866
|
+
const declared = Option.isSome(stack) ? stack.value.resources[fqn] : undefined;
|
|
1867
|
+
return declared !== undefined && carries(declared.Props, props);
|
|
1868
|
+
});
|
|
1869
|
+
|
|
1870
|
+
// src/ownership/rows.ts
|
|
1871
|
+
var storeOf = Effect22.gen(function* () {
|
|
1872
|
+
const stack = yield* Effect22.serviceOption(Stack2);
|
|
1873
|
+
const state = yield* Effect22.serviceOption(State);
|
|
1874
|
+
if (Option2.isNone(stack) || Option2.isNone(state))
|
|
1875
|
+
return;
|
|
1876
|
+
const store = yield* state.value;
|
|
1877
|
+
const where = { stack: stack.value.name, stage: stack.value.stage };
|
|
1878
|
+
return { resources: stack.value.resources, store, where };
|
|
1879
|
+
});
|
|
1880
|
+
var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect22.orElseSucceed(() => {
|
|
1881
|
+
return;
|
|
1882
|
+
}));
|
|
1883
|
+
var generationOf = (row, instanceId) => {
|
|
1884
|
+
let at = row;
|
|
1885
|
+
while (typeof at === "object" && at !== null && !isActionState(at)) {
|
|
1886
|
+
const generation = at;
|
|
1887
|
+
if (generation.instanceId === instanceId)
|
|
1888
|
+
return generation;
|
|
1889
|
+
at = generation.old;
|
|
1890
|
+
}
|
|
1891
|
+
return;
|
|
1892
|
+
};
|
|
1893
|
+
var recordedGeneration = (fqn, instanceId) => Effect22.gen(function* () {
|
|
1894
|
+
const found = yield* storeOf;
|
|
1895
|
+
if (found === undefined)
|
|
1896
|
+
return;
|
|
1897
|
+
for (const at of [fqn, ...found.resources[fqn]?.FormerFqns ?? []]) {
|
|
1898
|
+
const generation = generationOf(yield* rowAt(found.store, found.where, at), instanceId);
|
|
1899
|
+
if (generation !== undefined)
|
|
1900
|
+
return generation;
|
|
1901
|
+
}
|
|
1902
|
+
return;
|
|
1903
|
+
});
|
|
1904
|
+
var recordedInstance = (fqn, instanceId) => Effect22.gen(function* () {
|
|
1905
|
+
const generation = yield* recordedGeneration(fqn, instanceId);
|
|
1906
|
+
if (generation === undefined)
|
|
1907
|
+
return "absent";
|
|
1908
|
+
return (yield* wholeDeclaration(fqn, generation.props)) ? "whole" : "partial";
|
|
1909
|
+
});
|
|
1910
|
+
var isCreate = (fqn, instanceId) => Effect22.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
|
|
1911
|
+
var idOf = (attr) => {
|
|
1912
|
+
if (typeof attr !== "object" || attr === null)
|
|
1913
|
+
return;
|
|
1914
|
+
const id = attr.id;
|
|
1915
|
+
return typeof id === "string" && id !== "" ? id : undefined;
|
|
1916
|
+
};
|
|
1917
|
+
var olderGenerationIds = (fqn, instanceId) => Effect22.gen(function* () {
|
|
1918
|
+
const found = yield* storeOf;
|
|
1919
|
+
if (found === undefined)
|
|
1920
|
+
return [];
|
|
1921
|
+
const ids = [];
|
|
1922
|
+
for (const at of [fqn, ...found.resources[fqn]?.FormerFqns ?? []]) {
|
|
1923
|
+
collectOlder(yield* rowAt(found.store, found.where, at), instanceId, ids);
|
|
1924
|
+
}
|
|
1925
|
+
return ids;
|
|
1926
|
+
});
|
|
1927
|
+
var collectOlder = (row, instanceId, ids) => {
|
|
1928
|
+
let at = row;
|
|
1929
|
+
let seen = false;
|
|
1930
|
+
while (typeof at === "object" && at !== null && !isActionState(at)) {
|
|
1931
|
+
const generation = at;
|
|
1932
|
+
if (!seen) {
|
|
1933
|
+
seen = generation.instanceId === instanceId;
|
|
1934
|
+
at = generation.old;
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
if (generation.instanceId !== instanceId) {
|
|
1938
|
+
const id = idOf(generation.attr);
|
|
1939
|
+
if (id !== undefined)
|
|
1940
|
+
ids.push(id);
|
|
1941
|
+
}
|
|
1942
|
+
at = generation.old;
|
|
1943
|
+
}
|
|
1944
|
+
};
|
|
1945
|
+
var forgetRefusedCreate = (fqn, instanceId) => Effect22.gen(function* () {
|
|
1946
|
+
const found = yield* storeOf;
|
|
1947
|
+
if (found === undefined)
|
|
1948
|
+
return;
|
|
1949
|
+
const row = yield* rowAt(found.store, found.where, fqn);
|
|
1950
|
+
const mine = row;
|
|
1951
|
+
if (mine?.status !== "creating" || mine.instanceId !== instanceId || mine.attr !== undefined) {
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
yield* found.store.delete({ ...found.where, fqn }).pipe(Effect22.ignore);
|
|
1955
|
+
});
|
|
1956
|
+
|
|
1957
|
+
// src/ownership/probe.ts
|
|
1958
|
+
var provenOurs = (ask, settled) => Effect23.gen(function* () {
|
|
1959
|
+
const recorded = yield* recordedInstance(ask.fqn, ask.instanceId);
|
|
1960
|
+
if (recorded === "absent")
|
|
1961
|
+
return false;
|
|
1962
|
+
if (recorded === "partial") {
|
|
1963
|
+
yield* Effect23.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");
|
|
1964
|
+
return false;
|
|
1965
|
+
}
|
|
1966
|
+
const unproven = (reason) => Effect23.as(Effect23.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);
|
|
1967
|
+
return yield* settled.pipe(Effect23.catch(unproven), Effect23.catchDefect(unproven));
|
|
1968
|
+
});
|
|
1969
|
+
var ownedRead = (ask, found, settled) => Effect23.gen(function* () {
|
|
1970
|
+
if (found === undefined || ask.output !== undefined)
|
|
1971
|
+
return found;
|
|
1972
|
+
return (yield* provenOurs(ask, settled)) ? found : Unowned4(found);
|
|
1973
|
+
});
|
|
1974
|
+
|
|
1975
|
+
// src/ownership/resume.ts
|
|
1976
|
+
import { Unowned as Unowned5 } from "alchemy/AdoptPolicy";
|
|
1977
|
+
import { Artifacts } from "alchemy/Artifacts";
|
|
1978
|
+
import * as Effect24 from "effect/Effect";
|
|
1979
|
+
import * as Option3 from "effect/Option";
|
|
1980
|
+
var PROVEN = "homeflare/ownership/resumes";
|
|
1981
|
+
var UNFINISHED = "homeflare/ownership/unfinished";
|
|
1982
|
+
var note = (key, instanceId) => Effect24.gen(function* () {
|
|
1983
|
+
const bag = yield* Effect24.serviceOption(Artifacts);
|
|
1984
|
+
if (Option3.isSome(bag))
|
|
1985
|
+
yield* bag.value.set(key, instanceId);
|
|
1986
|
+
});
|
|
1987
|
+
var noted = (key, instanceId) => Effect24.gen(function* () {
|
|
1988
|
+
const bag = yield* Effect24.serviceOption(Artifacts);
|
|
1989
|
+
if (Option3.isNone(bag))
|
|
1990
|
+
return false;
|
|
1991
|
+
return (yield* bag.value.get(key)) === instanceId;
|
|
1992
|
+
});
|
|
1993
|
+
var noteUnfinished = (instanceId) => Effect24.as(note(UNFINISHED, instanceId), undefined);
|
|
1994
|
+
var noteResume = (instanceId, recovered) => Effect24.gen(function* () {
|
|
1995
|
+
yield* noteUnfinished(instanceId);
|
|
1996
|
+
const unproven = () => Effect24.succeed(undefined);
|
|
1997
|
+
const found = yield* recovered.pipe(Effect24.catch(unproven), Effect24.catchDefect(unproven));
|
|
1998
|
+
if (found === undefined || found === null || Unowned5.is(found))
|
|
1999
|
+
return;
|
|
2000
|
+
yield* note(PROVEN, instanceId);
|
|
2001
|
+
});
|
|
2002
|
+
var resumes = (instanceId) => noted(PROVEN, instanceId);
|
|
2003
|
+
var unfinished = (instanceId) => noted(UNFINISHED, instanceId);
|
|
2004
|
+
var provingResumes = (service) => {
|
|
2005
|
+
const { diff, read } = service;
|
|
2006
|
+
if (diff === undefined || read === undefined)
|
|
2007
|
+
return service;
|
|
2008
|
+
const proving = (input) => Effect24.gen(function* () {
|
|
2009
|
+
if (input.output === undefined) {
|
|
2010
|
+
yield* noteResume(input.instanceId, read({ ...input, output: undefined }));
|
|
2011
|
+
}
|
|
2012
|
+
return yield* diff(input);
|
|
2013
|
+
});
|
|
2014
|
+
return { ...service, diff: proving };
|
|
2015
|
+
};
|
|
2016
|
+
|
|
2017
|
+
// src/ownership/adopt.ts
|
|
2018
|
+
import { AdoptPolicy } from "alchemy/AdoptPolicy";
|
|
2019
|
+
import { AlchemyContext } from "alchemy/AlchemyContext";
|
|
2020
|
+
import { Stack as Stack3 } from "alchemy/Stack";
|
|
2021
|
+
import * as Effect25 from "effect/Effect";
|
|
2022
|
+
import * as Option4 from "effect/Option";
|
|
2023
|
+
var adoptEnabled = (fqn) => Effect25.gen(function* () {
|
|
2024
|
+
const stack = yield* Effect25.serviceOption(Stack3);
|
|
2025
|
+
const scoped = Option4.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
|
|
2026
|
+
if (scoped !== undefined)
|
|
2027
|
+
return scoped;
|
|
2028
|
+
const policy = yield* Effect25.serviceOption(AdoptPolicy);
|
|
2029
|
+
if (Option4.isSome(policy))
|
|
2030
|
+
return policy.value;
|
|
2031
|
+
const context = yield* Effect25.serviceOption(AlchemyContext);
|
|
2032
|
+
return Option4.isSome(context) ? context.value.adopt : false;
|
|
2033
|
+
});
|
|
2034
|
+
var adoptable = (owner) => Effect25.gen(function* () {
|
|
2035
|
+
if (yield* isCreate(owner.fqn, owner.instanceId))
|
|
2036
|
+
return true;
|
|
2037
|
+
return yield* unfinished(owner.instanceId);
|
|
2038
|
+
});
|
|
2039
|
+
var adoptsAtApply = (owner) => Effect25.gen(function* () {
|
|
2040
|
+
if (!(yield* adoptEnabled(owner.fqn)))
|
|
2041
|
+
return false;
|
|
2042
|
+
return yield* adoptable(owner);
|
|
2043
|
+
});
|
|
2044
|
+
var refuseTakeover = (owner, what) => Effect25.gen(function* () {
|
|
2045
|
+
if (owner.output !== undefined)
|
|
2046
|
+
return;
|
|
2047
|
+
if (yield* resumes(owner.instanceId))
|
|
2048
|
+
return;
|
|
2049
|
+
const may = yield* adoptable(owner);
|
|
2050
|
+
if (may && (yield* adoptEnabled(owner.fqn)))
|
|
2051
|
+
return;
|
|
2052
|
+
yield* forgetRefusedCreate(owner.fqn, owner.instanceId);
|
|
2053
|
+
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.";
|
|
2054
|
+
return yield* Effect25.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}`));
|
|
2055
|
+
});
|
|
2056
|
+
var claimFor = (owner) => (what) => refuseTakeover(owner, what);
|
|
2057
|
+
|
|
2058
|
+
// src/postgres/role-diff.ts
|
|
2059
|
+
import { isResolved as isResolved4 } from "alchemy/Diff";
|
|
2060
|
+
import * as Effect26 from "effect/Effect";
|
|
2061
|
+
var diffPostgresRole = (news, output, env = process.env, observation) => Effect26.gen(function* () {
|
|
2062
|
+
if (output === undefined || !isResolved4(news))
|
|
2063
|
+
return;
|
|
2064
|
+
if (news.name !== output.name) {
|
|
2065
|
+
return yield* Effect26.fail(new PostgresRoleRenameRefused({ from: output.name, to: news.name }));
|
|
2066
|
+
}
|
|
2067
|
+
yield* refuseAtPlan(news);
|
|
2068
|
+
const live = observation?.found;
|
|
2069
|
+
if (live !== undefined && live.oid !== output.oid) {
|
|
2070
|
+
return yield* Effect26.fail(new PostgresRoleIdentityRefused({
|
|
2071
|
+
role: output.name,
|
|
2072
|
+
storedOid: output.oid,
|
|
2073
|
+
liveOid: live.oid
|
|
2074
|
+
}));
|
|
2075
|
+
}
|
|
2076
|
+
const compared = live ?? output;
|
|
2077
|
+
const scalars = scalarDrift(news, compared);
|
|
2078
|
+
const membership = membershipDrift(news.memberOf, compared.memberOf ?? [], live === undefined ? new Set : unsafeMemberships(live));
|
|
2079
|
+
const resolved = resolvePassword(news, env);
|
|
2080
|
+
const passwordStale = resolved.value !== undefined && !passwordMatchesSeal(resolved.value, output.passwordSeal);
|
|
2081
|
+
const absent2 = observation !== undefined && live === undefined;
|
|
2082
|
+
const changed = absent2 || scalars.length > 0 || membership.grants.length > 0 || membership.revokes.length > 0 || passwordStale;
|
|
2083
|
+
return changed ? { action: "update" } : { action: "noop" };
|
|
2084
|
+
});
|
|
2085
|
+
|
|
2086
|
+
// src/postgres/role-provider.ts
|
|
2087
|
+
var postgresRoleHandlers = PostgresRole.Provider.of({
|
|
2088
|
+
list: () => Effect27.succeed([]),
|
|
2089
|
+
nuke: { skip: true },
|
|
2090
|
+
read: ({ fqn, instanceId, olds, output }) => Effect27.gen(function* () {
|
|
2091
|
+
const name = output?.name ?? olds.name;
|
|
2092
|
+
const live = yield* withPg((pg) => readRole(pg, name));
|
|
2093
|
+
if (live === undefined)
|
|
2094
|
+
return;
|
|
2095
|
+
if (output !== undefined && live.oid !== output.oid) {
|
|
2096
|
+
return yield* Effect27.fail(new PostgresRoleIdentityRefused({
|
|
2097
|
+
role: name,
|
|
2098
|
+
storedOid: output.oid,
|
|
2099
|
+
liveOid: live.oid
|
|
2100
|
+
}));
|
|
2101
|
+
}
|
|
2102
|
+
const passwordSeal = output?.passwordSeal ?? "";
|
|
2103
|
+
const found = { ...live, passwordSeal };
|
|
2104
|
+
return yield* ownedRead({ fqn, instanceId, output }, found, Effect27.succeed(declarationMatches(olds, live)));
|
|
2105
|
+
}),
|
|
2106
|
+
diff: ({ news, output, instanceId }) => Effect27.gen(function* () {
|
|
2107
|
+
if (output === undefined)
|
|
2108
|
+
return yield* noteUnfinished(instanceId);
|
|
2109
|
+
if (!isResolved5(news))
|
|
2110
|
+
return;
|
|
2111
|
+
const found = yield* withPg((pg) => readRole(pg, output.name));
|
|
2112
|
+
return yield* diffPostgresRole(news, output, process.env, { found });
|
|
2113
|
+
}),
|
|
2114
|
+
reconcile: ({ fqn, instanceId, news, output }) => Effect27.gen(function* () {
|
|
2115
|
+
yield* refuseAtPlan(news);
|
|
2116
|
+
return yield* withPg((pg) => Effect27.gen(function* () {
|
|
2117
|
+
const observed = yield* readRole(pg, news.name);
|
|
2118
|
+
if (observed !== undefined && output === undefined) {
|
|
2119
|
+
yield* refuseTakeover({ fqn, instanceId, output }, "Postgres.Role");
|
|
2120
|
+
}
|
|
2121
|
+
return yield* reconcileWithClient4(pg, news, process.env, output?.passwordSeal, output?.oid);
|
|
2122
|
+
}));
|
|
2123
|
+
}),
|
|
2124
|
+
delete: ({ output }) => withPg((pg) => Effect27.gen(function* () {
|
|
2125
|
+
if (output === undefined)
|
|
2126
|
+
return;
|
|
2127
|
+
const live = yield* readRole(pg, output.name);
|
|
2128
|
+
if (live === undefined)
|
|
2129
|
+
return;
|
|
2130
|
+
if (live.oid !== output.oid) {
|
|
2131
|
+
return yield* Effect27.fail(new PostgresRoleIdentityRefused({
|
|
2132
|
+
role: output.name,
|
|
2133
|
+
storedOid: output.oid,
|
|
2134
|
+
liveOid: live.oid
|
|
2135
|
+
}));
|
|
2136
|
+
}
|
|
2137
|
+
yield* pg.unsafe(buildDropRoleSql(output.name)).pipe(Effect27.asVoid);
|
|
2138
|
+
}))
|
|
2139
|
+
});
|
|
2140
|
+
var PostgresRoleProvider = () => Provider4.succeed(PostgresRole, postgresRoleHandlers);
|
|
209
2141
|
// src/postgres/providers.ts
|
|
210
2142
|
import * as Layer2 from "effect/Layer";
|
|
211
|
-
var postgresProviders = (config) => PostgresDatabaseProvider().pipe(Layer2.
|
|
2143
|
+
var postgresProviders = (config) => Layer2.mergeAll(PostgresDatabaseProvider().pipe(Layer2.provideMerge(postgresConnection(config))), PostgresSchemaProvider().pipe(Layer2.provideMerge(postgresConnection(config))), PostgresGrantsProvider().pipe(Layer2.provideMerge(postgresConnection(config))));
|
|
2144
|
+
var postgresRunnerProviders = (config) => Layer2.mergeAll(PostgresDatabaseProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config))), PostgresSchemaProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config))), PostgresGrantsProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config))));
|
|
2145
|
+
var postgresRoleProviders = (config) => PostgresRoleProvider().pipe(Layer2.provideMerge(postgresConnection(config)));
|
|
2146
|
+
var postgresRoleRunnerProviders = (config) => PostgresRoleProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config)));
|
|
212
2147
|
export {
|
|
2148
|
+
COLUMN_PRIVILEGES,
|
|
213
2149
|
POSTGRES_NAME_MAX_BYTES,
|
|
214
2150
|
PostgresConnection,
|
|
215
2151
|
PostgresDatabase,
|
|
@@ -219,20 +2155,108 @@ export {
|
|
|
219
2155
|
PostgresDatabaseOwnerMissing,
|
|
220
2156
|
PostgresDatabaseProvider,
|
|
221
2157
|
PostgresDatabaseRenameRefused,
|
|
2158
|
+
PostgresGrants,
|
|
2159
|
+
PostgresGrantsColumnMissing,
|
|
2160
|
+
PostgresGrantsDatabaseMismatch,
|
|
2161
|
+
PostgresGrantsDatabaseMissing,
|
|
2162
|
+
PostgresGrantsDuplicateObject,
|
|
2163
|
+
PostgresGrantsNameRefused,
|
|
2164
|
+
PostgresGrantsPrivilegeRefused,
|
|
2165
|
+
PostgresGrantsProvider,
|
|
2166
|
+
PostgresGrantsRepairRefused,
|
|
2167
|
+
PostgresGrantsRetargetRefused,
|
|
2168
|
+
PostgresGrantsRoleMissing,
|
|
2169
|
+
PostgresGrantsSchemaMissing,
|
|
2170
|
+
PostgresGrantsTableMissing,
|
|
2171
|
+
PostgresRole,
|
|
2172
|
+
PostgresRoleCreateVanished,
|
|
2173
|
+
PostgresRoleIdentityRefused,
|
|
2174
|
+
PostgresRoleMembershipUnrepaired,
|
|
2175
|
+
PostgresRoleNameRefused,
|
|
2176
|
+
PostgresRoleParentMissing,
|
|
2177
|
+
PostgresRolePasswordEnvUnsetError,
|
|
2178
|
+
PostgresRolePrivilegedRefused,
|
|
2179
|
+
PostgresRoleProvider,
|
|
2180
|
+
PostgresRoleRenameRefused,
|
|
2181
|
+
PostgresRoleValidUntilRefused,
|
|
2182
|
+
PostgresSchema,
|
|
2183
|
+
PostgresSchemaCreateVanished,
|
|
2184
|
+
PostgresSchemaDatabaseRefused,
|
|
2185
|
+
PostgresSchemaDeleteForeignRefused,
|
|
2186
|
+
PostgresSchemaDrift,
|
|
2187
|
+
PostgresSchemaDropNotEmptyError,
|
|
2188
|
+
PostgresSchemaExistsRefused,
|
|
2189
|
+
PostgresSchemaIdentityRefused,
|
|
2190
|
+
PostgresSchemaNameRefused,
|
|
2191
|
+
PostgresSchemaOwnerMissing,
|
|
2192
|
+
PostgresSchemaProvider,
|
|
2193
|
+
PostgresSchemaRenameRefused,
|
|
2194
|
+
PostgresSchemaWrongDatabase,
|
|
2195
|
+
SCHEMA_PRIVILEGES,
|
|
2196
|
+
TABLE_PRIVILEGES,
|
|
2197
|
+
buildAlterRoleSql,
|
|
2198
|
+
buildCommentSchemaSql,
|
|
222
2199
|
buildCreateDatabaseSql,
|
|
2200
|
+
buildCreateRoleSql,
|
|
2201
|
+
buildCreateSchemaSql,
|
|
2202
|
+
buildDropRoleSql,
|
|
2203
|
+
buildDropSchemaSql,
|
|
2204
|
+
buildGrantMembershipSql,
|
|
2205
|
+
buildRepairMembershipSql,
|
|
2206
|
+
buildRevokeGrantorMembershipSql,
|
|
2207
|
+
buildSetPasswordSql,
|
|
2208
|
+
currentDatabase,
|
|
2209
|
+
currentUser,
|
|
2210
|
+
databaseExists,
|
|
2211
|
+
diffPostgresSchema,
|
|
223
2212
|
firstDrift,
|
|
2213
|
+
grantColumnSql,
|
|
2214
|
+
grantDefaultSql,
|
|
2215
|
+
grantSchemaSql,
|
|
2216
|
+
grantTableSql,
|
|
2217
|
+
grantsNameByteRefusal,
|
|
2218
|
+
inlineParams,
|
|
2219
|
+
isDependentObjectsError,
|
|
224
2220
|
isDuplicateDatabaseRace,
|
|
225
2221
|
isPostgresDatabase,
|
|
2222
|
+
isPostgresGrants,
|
|
2223
|
+
isPostgresRole,
|
|
2224
|
+
isPostgresSchema,
|
|
2225
|
+
letterForPrivilege,
|
|
2226
|
+
makePsqlExecutor,
|
|
2227
|
+
membershipDrift,
|
|
226
2228
|
nameByteRefusal,
|
|
2229
|
+
normalizedComment,
|
|
227
2230
|
postgresConnection,
|
|
228
2231
|
postgresProviders,
|
|
2232
|
+
postgresRoleProviders,
|
|
2233
|
+
postgresRoleRunnerProviders,
|
|
2234
|
+
postgresRunnerConnection,
|
|
2235
|
+
postgresRunnerProviders,
|
|
229
2236
|
quoteIdent,
|
|
230
2237
|
quoteStringLiteral,
|
|
2238
|
+
resolvePassword,
|
|
2239
|
+
revokeColumnSql,
|
|
2240
|
+
revokeDefaultSql,
|
|
2241
|
+
revokePublicSchemaSql,
|
|
2242
|
+
revokePublicTablesSql,
|
|
2243
|
+
revokeSchemaSql,
|
|
2244
|
+
revokeTableSql,
|
|
231
2245
|
roleExists,
|
|
2246
|
+
sameValidUntil,
|
|
2247
|
+
scalarDrift,
|
|
2248
|
+
schemaIsEmpty,
|
|
2249
|
+
schemaNameByteRefusal,
|
|
232
2250
|
selectDatabase,
|
|
2251
|
+
selectRole,
|
|
2252
|
+
selectRoleMemberships,
|
|
2253
|
+
selectSchema,
|
|
2254
|
+
syncMemberships,
|
|
2255
|
+
unsafeMemberships,
|
|
233
2256
|
utf8ByteLength,
|
|
2257
|
+
validUntilRefusal,
|
|
234
2258
|
withPg
|
|
235
2259
|
};
|
|
236
2260
|
|
|
237
|
-
//# debugId=
|
|
2261
|
+
//# debugId=C4337C797D2364A164756E2164756E21
|
|
238
2262
|
//# sourceMappingURL=index.js.map
|