@homeflare/alchemy 0.43.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/dist/caddy/index.js +35 -1
- package/dist/caddy/index.js.map +3 -3
- package/dist/launchd/index.js +35 -1
- package/dist/launchd/index.js.map +3 -3
- package/dist/linux/index.js +35 -1
- package/dist/linux/index.js.map +3 -3
- package/dist/litellm/access-group-form.d.ts +26 -0
- package/dist/litellm/access-group-form.d.ts.map +1 -0
- package/dist/litellm/access-group-operations.d.ts +37 -0
- package/dist/litellm/access-group-operations.d.ts.map +1 -0
- package/dist/litellm/access-group-types.d.ts +49 -0
- package/dist/litellm/access-group-types.d.ts.map +1 -0
- package/dist/litellm/access-group.d.ts +55 -0
- package/dist/litellm/access-group.d.ts.map +1 -0
- package/dist/litellm/credential-errors.d.ts +131 -0
- package/dist/litellm/credential-errors.d.ts.map +1 -0
- package/dist/litellm/credential-form.d.ts +79 -0
- package/dist/litellm/credential-form.d.ts.map +1 -0
- package/dist/litellm/credential-operations.d.ts +84 -0
- package/dist/litellm/credential-operations.d.ts.map +1 -0
- package/dist/litellm/credential-rewrite.d.ts +6 -0
- package/dist/litellm/credential-rewrite.d.ts.map +1 -0
- package/dist/litellm/credential-types.d.ts +87 -0
- package/dist/litellm/credential-types.d.ts.map +1 -0
- package/dist/litellm/credential-values.d.ts +53 -0
- package/dist/litellm/credential-values.d.ts.map +1 -0
- package/dist/litellm/credential.d.ts +83 -0
- package/dist/litellm/credential.d.ts.map +1 -0
- package/dist/litellm/fake-access-group-litellm.d.ts +34 -0
- package/dist/litellm/fake-access-group-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-credential-litellm.d.ts +54 -0
- package/dist/litellm/fake-credential-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-keys.d.ts +53 -0
- package/dist/litellm/fake-keys.d.ts.map +1 -0
- package/dist/litellm/fake-litellm.d.ts +4 -1
- package/dist/litellm/fake-litellm.d.ts.map +1 -1
- package/dist/litellm/fake-mcp-litellm.d.ts +67 -0
- package/dist/litellm/fake-mcp-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-model-litellm-post-update.d.ts +49 -0
- package/dist/litellm/fake-model-litellm-post-update.d.ts.map +1 -0
- package/dist/litellm/fake-model-litellm.d.ts +48 -0
- package/dist/litellm/fake-model-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-policy-attachment-litellm.d.ts +33 -0
- package/dist/litellm/fake-policy-attachment-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-policy-litellm.d.ts +32 -0
- package/dist/litellm/fake-policy-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-registry-base.d.ts +42 -0
- package/dist/litellm/fake-registry-base.d.ts.map +1 -0
- package/dist/litellm/fake-stack.d.ts +8 -1
- package/dist/litellm/fake-stack.d.ts.map +1 -1
- package/dist/litellm/fake-team-litellm.d.ts +47 -0
- package/dist/litellm/fake-team-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-tool-litellm.d.ts +28 -0
- package/dist/litellm/fake-tool-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-toolset-litellm.d.ts +29 -0
- package/dist/litellm/fake-toolset-litellm.d.ts.map +1 -0
- package/dist/litellm/index.d.ts +18 -0
- package/dist/litellm/index.d.ts.map +1 -1
- package/dist/litellm/index.js +3078 -2
- package/dist/litellm/index.js.map +65 -4
- package/dist/litellm/key-errors.d.ts +169 -0
- package/dist/litellm/key-errors.d.ts.map +1 -0
- package/dist/litellm/key-form.d.ts +119 -0
- package/dist/litellm/key-form.d.ts.map +1 -0
- package/dist/litellm/key-harness.d.ts +41 -0
- package/dist/litellm/key-harness.d.ts.map +1 -0
- package/dist/litellm/key-metadata-errors.d.ts +30 -0
- package/dist/litellm/key-metadata-errors.d.ts.map +1 -0
- package/dist/litellm/key-metadata.d.ts +22 -0
- package/dist/litellm/key-metadata.d.ts.map +1 -0
- package/dist/litellm/key-operations.d.ts +63 -0
- package/dist/litellm/key-operations.d.ts.map +1 -0
- package/dist/litellm/key-secret.d.ts +33 -0
- package/dist/litellm/key-secret.d.ts.map +1 -0
- package/dist/litellm/key.d.ts +78 -0
- package/dist/litellm/key.d.ts.map +1 -0
- package/dist/litellm/mcp-server-credential.d.ts +50 -0
- package/dist/litellm/mcp-server-credential.d.ts.map +1 -0
- package/dist/litellm/mcp-server-errors.d.ts +101 -0
- package/dist/litellm/mcp-server-errors.d.ts.map +1 -0
- package/dist/litellm/mcp-server-form.d.ts +56 -0
- package/dist/litellm/mcp-server-form.d.ts.map +1 -0
- package/dist/litellm/mcp-server-locate.d.ts +26 -0
- package/dist/litellm/mcp-server-locate.d.ts.map +1 -0
- package/dist/litellm/mcp-server-operations.d.ts +63 -0
- package/dist/litellm/mcp-server-operations.d.ts.map +1 -0
- package/dist/litellm/mcp-server-shadow.d.ts +24 -0
- package/dist/litellm/mcp-server-shadow.d.ts.map +1 -0
- package/dist/litellm/mcp-server-types.d.ts +118 -0
- package/dist/litellm/mcp-server-types.d.ts.map +1 -0
- package/dist/litellm/mcp-server-url.d.ts +11 -0
- package/dist/litellm/mcp-server-url.d.ts.map +1 -0
- package/dist/litellm/mcp-server.d.ts +95 -0
- package/dist/litellm/mcp-server.d.ts.map +1 -0
- package/dist/litellm/model-claim.d.ts +24 -0
- package/dist/litellm/model-claim.d.ts.map +1 -0
- package/dist/litellm/model-credential.d.ts +12 -0
- package/dist/litellm/model-credential.d.ts.map +1 -0
- package/dist/litellm/model-errors.d.ts +119 -0
- package/dist/litellm/model-errors.d.ts.map +1 -0
- package/dist/litellm/model-form.d.ts +78 -0
- package/dist/litellm/model-form.d.ts.map +1 -0
- package/dist/litellm/model-locate.d.ts +13 -0
- package/dist/litellm/model-locate.d.ts.map +1 -0
- package/dist/litellm/model-operations.d.ts +59 -0
- package/dist/litellm/model-operations.d.ts.map +1 -0
- package/dist/litellm/model-types.d.ts +91 -0
- package/dist/litellm/model-types.d.ts.map +1 -0
- package/dist/litellm/model.d.ts +88 -0
- package/dist/litellm/model.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-form.d.ts +19 -0
- package/dist/litellm/policy-attachment-form.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-operations.d.ts +31 -0
- package/dist/litellm/policy-attachment-operations.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-types.d.ts +48 -0
- package/dist/litellm/policy-attachment-types.d.ts.map +1 -0
- package/dist/litellm/policy-attachment.d.ts +61 -0
- package/dist/litellm/policy-attachment.d.ts.map +1 -0
- package/dist/litellm/policy-form.d.ts +22 -0
- package/dist/litellm/policy-form.d.ts.map +1 -0
- package/dist/litellm/policy-operations.d.ts +42 -0
- package/dist/litellm/policy-operations.d.ts.map +1 -0
- package/dist/litellm/policy-types.d.ts +57 -0
- package/dist/litellm/policy-types.d.ts.map +1 -0
- package/dist/litellm/policy.d.ts +65 -0
- package/dist/litellm/policy.d.ts.map +1 -0
- package/dist/litellm/providers.d.ts +1 -1
- package/dist/litellm/providers.d.ts.map +1 -1
- package/dist/litellm/registry-errors.d.ts +62 -0
- package/dist/litellm/registry-errors.d.ts.map +1 -0
- package/dist/litellm/registry-support.d.ts +42 -0
- package/dist/litellm/registry-support.d.ts.map +1 -0
- package/dist/litellm/team-form.d.ts +26 -0
- package/dist/litellm/team-form.d.ts.map +1 -0
- package/dist/litellm/team-members.d.ts +10 -0
- package/dist/litellm/team-members.d.ts.map +1 -0
- package/dist/litellm/team-operations.d.ts +38 -0
- package/dist/litellm/team-operations.d.ts.map +1 -0
- package/dist/litellm/team-permission.d.ts +35 -0
- package/dist/litellm/team-permission.d.ts.map +1 -0
- package/dist/litellm/team-types.d.ts +101 -0
- package/dist/litellm/team-types.d.ts.map +1 -0
- package/dist/litellm/team.d.ts +67 -0
- package/dist/litellm/team.d.ts.map +1 -0
- package/dist/litellm/tool-policy-form.d.ts +21 -0
- package/dist/litellm/tool-policy-form.d.ts.map +1 -0
- package/dist/litellm/tool-policy-operations.d.ts +30 -0
- package/dist/litellm/tool-policy-operations.d.ts.map +1 -0
- package/dist/litellm/tool-policy-types.d.ts +46 -0
- package/dist/litellm/tool-policy-types.d.ts.map +1 -0
- package/dist/litellm/tool-policy.d.ts +62 -0
- package/dist/litellm/tool-policy.d.ts.map +1 -0
- package/dist/litellm/toolset-form.d.ts +20 -0
- package/dist/litellm/toolset-form.d.ts.map +1 -0
- package/dist/litellm/toolset-operations.d.ts +39 -0
- package/dist/litellm/toolset-operations.d.ts.map +1 -0
- package/dist/litellm/toolset-types.d.ts +46 -0
- package/dist/litellm/toolset-types.d.ts.map +1 -0
- package/dist/litellm/toolset.d.ts +55 -0
- package/dist/litellm/toolset.d.ts.map +1 -0
- package/dist/openbao/index.js +35 -1
- package/dist/openbao/index.js.map +3 -3
- package/dist/ownership/rows.d.ts +11 -0
- package/dist/ownership/rows.d.ts.map +1 -1
- package/dist/postgres/connection.d.ts +14 -2
- package/dist/postgres/connection.d.ts.map +1 -1
- package/dist/postgres/database-sql.d.ts +27 -8
- package/dist/postgres/database-sql.d.ts.map +1 -1
- package/dist/postgres/fake-grants-parse.d.ts +37 -0
- package/dist/postgres/fake-grants-parse.d.ts.map +1 -0
- package/dist/postgres/fake-grants-read.d.ts +61 -0
- package/dist/postgres/fake-grants-read.d.ts.map +1 -0
- package/dist/postgres/fake-grants-sql.d.ts +46 -0
- package/dist/postgres/fake-grants-sql.d.ts.map +1 -0
- package/dist/postgres/fake-grants-write.d.ts +29 -0
- package/dist/postgres/fake-grants-write.d.ts.map +1 -0
- package/dist/postgres/fake-role-sql.d.ts +41 -0
- package/dist/postgres/fake-role-sql.d.ts.map +1 -0
- package/dist/postgres/fake-schema-sql.d.ts +42 -0
- package/dist/postgres/fake-schema-sql.d.ts.map +1 -0
- package/dist/postgres/fake-sql-parse.d.ts +23 -0
- package/dist/postgres/fake-sql-parse.d.ts.map +1 -0
- package/dist/postgres/fake-sql-quote.d.ts +12 -0
- package/dist/postgres/fake-sql-quote.d.ts.map +1 -0
- package/dist/postgres/fake-sql.d.ts +42 -0
- package/dist/postgres/fake-sql.d.ts.map +1 -1
- package/dist/postgres/grants-attrs.d.ts +115 -0
- package/dist/postgres/grants-attrs.d.ts.map +1 -0
- package/dist/postgres/grants-declare.d.ts +88 -0
- package/dist/postgres/grants-declare.d.ts.map +1 -0
- package/dist/postgres/grants-diff.d.ts +25 -0
- package/dist/postgres/grants-diff.d.ts.map +1 -0
- package/dist/postgres/grants-errors.d.ts +137 -0
- package/dist/postgres/grants-errors.d.ts.map +1 -0
- package/dist/postgres/grants-existence.d.ts +14 -0
- package/dist/postgres/grants-existence.d.ts.map +1 -0
- package/dist/postgres/grants-ops.d.ts +65 -0
- package/dist/postgres/grants-ops.d.ts.map +1 -0
- package/dist/postgres/grants-ownership.d.ts +18 -0
- package/dist/postgres/grants-ownership.d.ts.map +1 -0
- package/dist/postgres/grants-plan-restore.d.ts +22 -0
- package/dist/postgres/grants-plan-restore.d.ts.map +1 -0
- package/dist/postgres/grants-plan.d.ts +65 -0
- package/dist/postgres/grants-plan.d.ts.map +1 -0
- package/dist/postgres/grants-read-defaults.d.ts +17 -0
- package/dist/postgres/grants-read-defaults.d.ts.map +1 -0
- package/dist/postgres/grants-read.d.ts +77 -0
- package/dist/postgres/grants-read.d.ts.map +1 -0
- package/dist/postgres/grants-refuse.d.ts +25 -0
- package/dist/postgres/grants-refuse.d.ts.map +1 -0
- package/dist/postgres/grants-sql.d.ts +42 -0
- package/dist/postgres/grants-sql.d.ts.map +1 -0
- package/dist/postgres/grants-words.d.ts +29 -0
- package/dist/postgres/grants-words.d.ts.map +1 -0
- package/dist/postgres/grants.d.ts +37 -0
- package/dist/postgres/grants.d.ts.map +1 -0
- package/dist/postgres/index.d.ts +31 -6
- package/dist/postgres/index.d.ts.map +1 -1
- package/dist/postgres/index.js +1998 -37
- package/dist/postgres/index.js.map +46 -7
- package/dist/postgres/installed-classifier.d.ts +13 -0
- package/dist/postgres/installed-classifier.d.ts.map +1 -0
- package/dist/postgres/providers.d.ts +16 -8
- package/dist/postgres/providers.d.ts.map +1 -1
- package/dist/postgres/psql-executor.d.ts +4 -0
- package/dist/postgres/psql-executor.d.ts.map +1 -1
- package/dist/postgres/role-attrs.d.ts +112 -0
- package/dist/postgres/role-attrs.d.ts.map +1 -0
- package/dist/postgres/role-diff.d.ts +22 -0
- package/dist/postgres/role-diff.d.ts.map +1 -0
- package/dist/postgres/role-errors.d.ts +132 -0
- package/dist/postgres/role-errors.d.ts.map +1 -0
- package/dist/postgres/role-membership-sql.d.ts +78 -0
- package/dist/postgres/role-membership-sql.d.ts.map +1 -0
- package/dist/postgres/role-provider.d.ts +11 -0
- package/dist/postgres/role-provider.d.ts.map +1 -0
- package/dist/postgres/role-scram.d.ts +8 -0
- package/dist/postgres/role-scram.d.ts.map +1 -0
- package/dist/postgres/role-secrets.d.ts +38 -0
- package/dist/postgres/role-secrets.d.ts.map +1 -0
- package/dist/postgres/role-sql.d.ts +68 -0
- package/dist/postgres/role-sql.d.ts.map +1 -0
- package/dist/postgres/role.d.ts +57 -0
- package/dist/postgres/role.d.ts.map +1 -0
- package/dist/postgres/schema-assert.d.ts +50 -0
- package/dist/postgres/schema-assert.d.ts.map +1 -0
- package/dist/postgres/schema-attrs.d.ts +51 -0
- package/dist/postgres/schema-attrs.d.ts.map +1 -0
- package/dist/postgres/schema-delete.d.ts +28 -0
- package/dist/postgres/schema-delete.d.ts.map +1 -0
- package/dist/postgres/schema-diff.d.ts +14 -0
- package/dist/postgres/schema-diff.d.ts.map +1 -0
- package/dist/postgres/schema-errors.d.ts +139 -0
- package/dist/postgres/schema-errors.d.ts.map +1 -0
- package/dist/postgres/schema-identity.d.ts +8 -0
- package/dist/postgres/schema-identity.d.ts.map +1 -0
- package/dist/postgres/schema-reconcile.d.ts +12 -0
- package/dist/postgres/schema-reconcile.d.ts.map +1 -0
- package/dist/postgres/schema-sql.d.ts +45 -0
- package/dist/postgres/schema-sql.d.ts.map +1 -0
- package/dist/postgres/schema-test-kit.d.ts +67 -0
- package/dist/postgres/schema-test-kit.d.ts.map +1 -0
- package/dist/postgres/schema.d.ts +54 -0
- package/dist/postgres/schema.d.ts.map +1 -0
- package/dist/proxmox/index.js +35 -1
- package/dist/proxmox/index.js.map +3 -3
- package/dist/release/fake-engine.d.ts +10 -0
- package/dist/release/fake-engine.d.ts.map +1 -1
- package/dist/release/index.js +35 -1
- package/dist/release/index.js.map +3 -3
- package/dist/valkey/acl-attrs.d.ts +94 -0
- package/dist/valkey/acl-attrs.d.ts.map +1 -0
- package/dist/valkey/acl-compare.d.ts +28 -0
- package/dist/valkey/acl-compare.d.ts.map +1 -0
- package/dist/valkey/acl-connection.d.ts +6 -0
- package/dist/valkey/acl-connection.d.ts.map +1 -0
- package/dist/valkey/acl-form.d.ts +62 -0
- package/dist/valkey/acl-form.d.ts.map +1 -0
- package/dist/valkey/acl-managed.d.ts +4 -0
- package/dist/valkey/acl-managed.d.ts.map +1 -0
- package/dist/valkey/acl-ops.d.ts +37 -0
- package/dist/valkey/acl-ops.d.ts.map +1 -0
- package/dist/valkey/acl-profiles.d.ts +53 -0
- package/dist/valkey/acl-profiles.d.ts.map +1 -0
- package/dist/valkey/acl-reconcile.d.ts +26 -0
- package/dist/valkey/acl-reconcile.d.ts.map +1 -0
- package/dist/valkey/acl.d.ts +24 -0
- package/dist/valkey/acl.d.ts.map +1 -0
- package/dist/valkey/connection-service.d.ts +22 -0
- package/dist/valkey/connection-service.d.ts.map +1 -0
- package/dist/valkey/connection.d.ts +59 -0
- package/dist/valkey/connection.d.ts.map +1 -0
- package/dist/valkey/errors.d.ts +165 -0
- package/dist/valkey/errors.d.ts.map +1 -0
- package/dist/valkey/fake-acl.d.ts +29 -0
- package/dist/valkey/fake-acl.d.ts.map +1 -0
- package/dist/valkey/fake-valkey.d.ts +35 -0
- package/dist/valkey/fake-valkey.d.ts.map +1 -0
- package/dist/valkey/handler-test-fixture.d.ts +20 -0
- package/dist/valkey/handler-test-fixture.d.ts.map +1 -0
- package/dist/valkey/index.d.ts +18 -0
- package/dist/valkey/index.d.ts.map +1 -0
- package/dist/valkey/index.js +964 -0
- package/dist/valkey/index.js.map +27 -0
- package/dist/valkey/instance-attrs.d.ts +46 -0
- package/dist/valkey/instance-attrs.d.ts.map +1 -0
- package/dist/valkey/instance-form.d.ts +22 -0
- package/dist/valkey/instance-form.d.ts.map +1 -0
- package/dist/valkey/instance.d.ts +48 -0
- package/dist/valkey/instance.d.ts.map +1 -0
- package/dist/valkey/provider-socket-fixture.d.ts +8 -0
- package/dist/valkey/provider-socket-fixture.d.ts.map +1 -0
- package/dist/valkey/providers.d.ts +16 -0
- package/dist/valkey/providers.d.ts.map +1 -0
- package/dist/valkey/resp.d.ts +38 -0
- package/dist/valkey/resp.d.ts.map +1 -0
- package/dist/valkey/transport-error.d.ts +36 -0
- package/dist/valkey/transport-error.d.ts.map +1 -0
- package/dist/valkey/transport.d.ts +50 -0
- package/dist/valkey/transport.d.ts.map +1 -0
- package/docs/litellm-access-group.md +105 -0
- package/docs/litellm-credential.md +60 -0
- package/docs/litellm-key-metadata.md +56 -0
- package/docs/litellm-key.md +194 -0
- package/docs/litellm-mcp.md +187 -0
- package/docs/litellm-model.md +111 -0
- package/docs/litellm-policy-attachment.md +86 -0
- package/docs/litellm-policy.md +103 -0
- package/docs/litellm-team.md +117 -0
- package/docs/litellm-tool-policy.md +90 -0
- package/docs/litellm-toolset.md +92 -0
- package/docs/litellm.md +16 -9
- package/docs/peers.md +15 -6
- package/docs/postgres-grants-example.md +55 -0
- package/docs/postgres-grants.md +157 -0
- package/docs/postgres-role.md +126 -0
- package/docs/postgres-schema.md +139 -0
- package/docs/postgres.md +20 -3
- package/docs/valkey-transport.md +29 -0
- package/docs/valkey.md +187 -0
- package/package.json +6 -2
- package/src/ceph/ceph-argv.test.ts +1 -1
- package/src/launchd/sudo-allowlist.test.ts +2 -2
- package/src/linux/sudo-allowlist.test.ts +3 -3
- package/src/linux/sudo-runner.test.ts +2 -2
- package/src/litellm/access-group-form.ts +105 -0
- package/src/litellm/access-group-operations.ts +92 -0
- package/src/litellm/access-group-types.ts +49 -0
- package/src/litellm/access-group.test.ts +197 -0
- package/src/litellm/access-group.ts +151 -0
- package/src/litellm/credential-errors.ts +183 -0
- package/src/litellm/credential-form.test.ts +151 -0
- package/src/litellm/credential-form.ts +211 -0
- package/src/litellm/credential-guards.test.ts +241 -0
- package/src/litellm/credential-operations.ts +208 -0
- package/src/litellm/credential-recovery.test.ts +93 -0
- package/src/litellm/credential-rewrite.test.ts +164 -0
- package/src/litellm/credential-rewrite.ts +50 -0
- package/src/litellm/credential-types.ts +117 -0
- package/src/litellm/credential-values.ts +94 -0
- package/src/litellm/credential.test.ts +218 -0
- package/src/litellm/credential.ts +227 -0
- package/src/litellm/fake-access-group-litellm.ts +127 -0
- package/src/litellm/fake-credential-litellm.ts +216 -0
- package/src/litellm/fake-keys.ts +190 -0
- package/src/litellm/fake-litellm.ts +19 -9
- package/src/litellm/fake-mcp-litellm.ts +214 -0
- package/src/litellm/fake-model-litellm-post-update.ts +103 -0
- package/src/litellm/fake-model-litellm.ts +233 -0
- package/src/litellm/fake-policy-attachment-litellm.ts +97 -0
- package/src/litellm/fake-policy-litellm.ts +186 -0
- package/src/litellm/fake-registry-base.ts +69 -0
- package/src/litellm/fake-stack.ts +17 -1
- package/src/litellm/fake-team-litellm.ts +229 -0
- package/src/litellm/fake-tool-litellm.ts +88 -0
- package/src/litellm/fake-toolset-litellm.ts +110 -0
- package/src/litellm/index.ts +140 -0
- package/src/litellm/key-delete.test.ts +212 -0
- package/src/litellm/key-errors.ts +241 -0
- package/src/litellm/key-form.ts +200 -0
- package/src/litellm/key-harness.ts +121 -0
- package/src/litellm/key-metadata-errors.ts +49 -0
- package/src/litellm/key-metadata.test.ts +156 -0
- package/src/litellm/key-metadata.ts +74 -0
- package/src/litellm/key-operations.ts +184 -0
- package/src/litellm/key-rotation.test.ts +110 -0
- package/src/litellm/key-scope.test.ts +135 -0
- package/src/litellm/key-secret.test.ts +248 -0
- package/src/litellm/key-secret.ts +137 -0
- package/src/litellm/key-transport.test.ts +166 -0
- package/src/litellm/key.test.ts +231 -0
- package/src/litellm/key.ts +213 -0
- package/src/litellm/mcp-server-adopt.test.ts +86 -0
- package/src/litellm/mcp-server-alias.test.ts +184 -0
- package/src/litellm/mcp-server-credential.ts +85 -0
- package/src/litellm/mcp-server-errors.ts +141 -0
- package/src/litellm/mcp-server-exports.test.ts +15 -0
- package/src/litellm/mcp-server-form.test.ts +241 -0
- package/src/litellm/mcp-server-form.ts +233 -0
- package/src/litellm/mcp-server-handlers.test.ts +150 -0
- package/src/litellm/mcp-server-locate.ts +66 -0
- package/src/litellm/mcp-server-operations.ts +111 -0
- package/src/litellm/mcp-server-registry.test.ts +146 -0
- package/src/litellm/mcp-server-retype.test.ts +114 -0
- package/src/litellm/mcp-server-shadow.test.ts +91 -0
- package/src/litellm/mcp-server-shadow.ts +45 -0
- package/src/litellm/mcp-server-tools.test.ts +85 -0
- package/src/litellm/mcp-server-types.ts +143 -0
- package/src/litellm/mcp-server-url.ts +66 -0
- package/src/litellm/mcp-server.test.ts +213 -0
- package/src/litellm/mcp-server.ts +225 -0
- package/src/litellm/model-claim.ts +69 -0
- package/src/litellm/model-credential.ts +45 -0
- package/src/litellm/model-errors.ts +168 -0
- package/src/litellm/model-foreign-row.test.ts +173 -0
- package/src/litellm/model-form.test.ts +228 -0
- package/src/litellm/model-form.ts +223 -0
- package/src/litellm/model-locate.test.ts +128 -0
- package/src/litellm/model-locate.ts +67 -0
- package/src/litellm/model-operations.test.ts +93 -0
- package/src/litellm/model-operations.ts +146 -0
- package/src/litellm/model-params-preserve.test.ts +119 -0
- package/src/litellm/model-recovery.test.ts +57 -0
- package/src/litellm/model-types.ts +112 -0
- package/src/litellm/model.test.ts +222 -0
- package/src/litellm/model.ts +237 -0
- package/src/litellm/policy-attachment-form.ts +103 -0
- package/src/litellm/policy-attachment-operations.ts +74 -0
- package/src/litellm/policy-attachment-types.ts +48 -0
- package/src/litellm/policy-attachment.test.ts +198 -0
- package/src/litellm/policy-attachment.ts +178 -0
- package/src/litellm/policy-form.ts +122 -0
- package/src/litellm/policy-operations.ts +112 -0
- package/src/litellm/policy-types.ts +58 -0
- package/src/litellm/policy.test.ts +214 -0
- package/src/litellm/policy.ts +146 -0
- package/src/litellm/providers.ts +24 -3
- package/src/litellm/registry-errors.ts +88 -0
- package/src/litellm/registry-exports.test.ts +66 -0
- package/src/litellm/registry-support.ts +109 -0
- package/src/litellm/team-form.ts +140 -0
- package/src/litellm/team-members.test.ts +150 -0
- package/src/litellm/team-members.ts +58 -0
- package/src/litellm/team-operations.ts +90 -0
- package/src/litellm/team-permission.ts +159 -0
- package/src/litellm/team-read.test.ts +89 -0
- package/src/litellm/team-types.ts +106 -0
- package/src/litellm/team.test.ts +206 -0
- package/src/litellm/team.ts +159 -0
- package/src/litellm/tool-policy-form.ts +94 -0
- package/src/litellm/tool-policy-operations.ts +58 -0
- package/src/litellm/tool-policy-types.ts +48 -0
- package/src/litellm/tool-policy.test.ts +172 -0
- package/src/litellm/tool-policy.ts +125 -0
- package/src/litellm/toolset-form.ts +104 -0
- package/src/litellm/toolset-operations.ts +90 -0
- package/src/litellm/toolset-types.ts +47 -0
- package/src/litellm/toolset.test.ts +198 -0
- package/src/litellm/toolset.ts +151 -0
- package/src/ownership/ownership.test.ts +33 -1
- package/src/ownership/rows.ts +53 -0
- package/src/postgres/connection.ts +42 -7
- package/src/postgres/database-bigint-serialization.test.ts +1 -0
- package/src/postgres/database-refusals.test.ts +2 -2
- package/src/postgres/database-sql.ts +36 -9
- package/src/postgres/fake-grants-parse.ts +189 -0
- package/src/postgres/fake-grants-read.ts +250 -0
- package/src/postgres/fake-grants-sql.ts +207 -0
- package/src/postgres/fake-grants-write.ts +90 -0
- package/src/postgres/fake-role-sql.ts +240 -0
- package/src/postgres/fake-schema-semantics.test.ts +36 -0
- package/src/postgres/fake-schema-sql.ts +146 -0
- package/src/postgres/fake-sql-parse.ts +86 -0
- package/src/postgres/fake-sql-quote.ts +36 -0
- package/src/postgres/fake-sql.ts +166 -46
- package/src/postgres/fixtures/pg-REL_18_6-acl.h.txt +147 -0
- package/src/postgres/fixtures/pg-REL_18_6-aclexplode-func.txt +31 -0
- package/src/postgres/fixtures/pg-REL_18_6-alter-default-privileges-synopsis.txt +82 -0
- package/src/postgres/fixtures/pg-REL_18_6-alter_role.sgml.txt +44 -0
- package/src/postgres/fixtures/pg-REL_18_6-create_role.sgml.txt +26 -0
- package/src/postgres/fixtures/pg-REL_18_6-grant-synopsis.txt +95 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_attribute.h.txt +24 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_auth_members.h.txt +62 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_authid.h.txt +70 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_class.h.txt +33 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_default_acl.h.txt +81 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_namespace.h.txt +28 -0
- package/src/postgres/fixtures/pg-REL_18_6-revoke-synopsis.txt +124 -0
- package/src/postgres/grants-acl.test.ts +61 -0
- package/src/postgres/grants-attrs.ts +185 -0
- package/src/postgres/grants-database.test.ts +158 -0
- package/src/postgres/grants-declare.ts +172 -0
- package/src/postgres/grants-delete-proof.test.ts +154 -0
- package/src/postgres/grants-diff.ts +101 -0
- package/src/postgres/grants-errors.ts +215 -0
- package/src/postgres/grants-existence-guards.test.ts +113 -0
- package/src/postgres/grants-existence.ts +101 -0
- package/src/postgres/grants-lifecycle-guards.test.ts +208 -0
- package/src/postgres/grants-lifecycle-repair.test.ts +208 -0
- package/src/postgres/grants-lifecycle.test.ts +181 -0
- package/src/postgres/grants-ops.ts +229 -0
- package/src/postgres/grants-owner-revoke.test.ts +121 -0
- package/src/postgres/grants-ownership.ts +49 -0
- package/src/postgres/grants-plan-collateral.test.ts +202 -0
- package/src/postgres/grants-plan-diff.test.ts +204 -0
- package/src/postgres/grants-plan-restore.ts +43 -0
- package/src/postgres/grants-plan-revocations.test.ts +69 -0
- package/src/postgres/grants-plan.test.ts +148 -0
- package/src/postgres/grants-plan.ts +245 -0
- package/src/postgres/grants-provenance.test.ts +134 -0
- package/src/postgres/grants-psql-lifecycle.test.ts +105 -0
- package/src/postgres/grants-read-absent.test.ts +91 -0
- package/src/postgres/grants-read-defaults.ts +51 -0
- package/src/postgres/grants-read.ts +250 -0
- package/src/postgres/grants-refusals.test.ts +136 -0
- package/src/postgres/grants-refuse.ts +119 -0
- package/src/postgres/grants-relkind-wire.test.ts +111 -0
- package/src/postgres/grants-repair-plan.test.ts +201 -0
- package/src/postgres/grants-sql.test.ts +128 -0
- package/src/postgres/grants-sql.ts +144 -0
- package/src/postgres/grants-words.ts +34 -0
- package/src/postgres/grants.ts +110 -0
- package/src/postgres/index.ts +129 -5
- package/src/postgres/installed-classifier.ts +36 -0
- package/src/postgres/providers.ts +33 -9
- package/src/postgres/psql-executor-escaping.test.ts +67 -0
- package/src/postgres/psql-executor.test.ts +63 -2
- package/src/postgres/psql-executor.ts +74 -34
- package/src/postgres/role-adopt.test.ts +98 -0
- package/src/postgres/role-attrs.ts +137 -0
- package/src/postgres/role-diff.ts +93 -0
- package/src/postgres/role-errors.ts +206 -0
- package/src/postgres/role-guard.test.ts +60 -0
- package/src/postgres/role-identity.test.ts +197 -0
- package/src/postgres/role-lifecycle.test.ts +228 -0
- package/src/postgres/role-membership-options.test.ts +73 -0
- package/src/postgres/role-membership-repair.test.ts +153 -0
- package/src/postgres/role-membership-sql.ts +223 -0
- package/src/postgres/role-password-ascii.test.ts +53 -0
- package/src/postgres/role-password-transports.test.ts +123 -0
- package/src/postgres/role-password.test.ts +158 -0
- package/src/postgres/role-privilege.test.ts +107 -0
- package/src/postgres/role-provenance.test.ts +200 -0
- package/src/postgres/role-provider.ts +105 -0
- package/src/postgres/role-refusals.test.ts +225 -0
- package/src/postgres/role-scram.test.ts +38 -0
- package/src/postgres/role-scram.ts +40 -0
- package/src/postgres/role-secrets.ts +61 -0
- package/src/postgres/role-sql.ts +213 -0
- package/src/postgres/role-valid-until.test.ts +68 -0
- package/src/postgres/role.ts +226 -0
- package/src/postgres/schema-assert.ts +139 -0
- package/src/postgres/schema-attrs.ts +71 -0
- package/src/postgres/schema-delete-proofs.test.ts +192 -0
- package/src/postgres/schema-delete.ts +96 -0
- package/src/postgres/schema-diff.ts +44 -0
- package/src/postgres/schema-drop-2bp01.test.ts +146 -0
- package/src/postgres/schema-errors.ts +209 -0
- package/src/postgres/schema-handlers.test.ts +193 -0
- package/src/postgres/schema-identity.ts +39 -0
- package/src/postgres/schema-lifecycle.test.ts +246 -0
- package/src/postgres/schema-race.test.ts +70 -0
- package/src/postgres/schema-reconcile.ts +100 -0
- package/src/postgres/schema-refusals.test.ts +144 -0
- package/src/postgres/schema-socket.test.ts +162 -0
- package/src/postgres/schema-sql.ts +130 -0
- package/src/postgres/schema-takeover.test.ts +149 -0
- package/src/postgres/schema-test-kit.ts +85 -0
- package/src/postgres/schema.ts +130 -0
- package/src/release/binary-alias.test.ts +3 -3
- package/src/release/binary-claims.test.ts +3 -3
- package/src/release/binary-directory-umask.test.ts +65 -0
- package/src/release/fake-engine.ts +15 -0
- package/src/valkey/acl-attrs.ts +98 -0
- package/src/valkey/acl-compare.ts +57 -0
- package/src/valkey/acl-connection.ts +13 -0
- package/src/valkey/acl-delete.test.ts +127 -0
- package/src/valkey/acl-form.ts +239 -0
- package/src/valkey/acl-managed.ts +13 -0
- package/src/valkey/acl-ops.ts +122 -0
- package/src/valkey/acl-ownership.test.ts +118 -0
- package/src/valkey/acl-prefix.test.ts +38 -0
- package/src/valkey/acl-profiles.ts +81 -0
- package/src/valkey/acl-reconcile.ts +180 -0
- package/src/valkey/acl-reserved.test.ts +69 -0
- package/src/valkey/acl-revocation.test.ts +94 -0
- package/src/valkey/acl-seat-prefix.test.ts +111 -0
- package/src/valkey/acl-validation.test.ts +54 -0
- package/src/valkey/acl.test.ts +215 -0
- package/src/valkey/acl.ts +169 -0
- package/src/valkey/connection-service.ts +46 -0
- package/src/valkey/connection.ts +180 -0
- package/src/valkey/errors.ts +239 -0
- package/src/valkey/fake-acl.ts +128 -0
- package/src/valkey/fake-valkey.ts +224 -0
- package/src/valkey/handler-test-fixture.ts +43 -0
- package/src/valkey/handlers.test.ts +205 -0
- package/src/valkey/index.ts +54 -0
- package/src/valkey/instance-attrs.ts +48 -0
- package/src/valkey/instance-form.ts +104 -0
- package/src/valkey/instance.test.ts +108 -0
- package/src/valkey/instance.ts +160 -0
- package/src/valkey/provider-socket-fixture.ts +71 -0
- package/src/valkey/providers.test.ts +129 -0
- package/src/valkey/providers.ts +21 -0
- package/src/valkey/resp.ts +212 -0
- package/src/valkey/transport-bounds.test.ts +150 -0
- package/src/valkey/transport-error.ts +37 -0
- package/src/valkey/transport.test.ts +158 -0
- package/src/valkey/transport.ts +109 -0
- package/src/valkey/upstream-bounds.test.ts +14 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `Postgres.Schema` socket transport, mocked at the pool: what the review measured was a
|
|
3
|
+
* cold plan's `read` opening the declared database directly and failing untyped
|
|
4
|
+
* (`UnknownError`, SQLSTATE `3D000` — the pinned driver classification below). The handler
|
|
5
|
+
* now probes `pg_database` over the FAMILY pool first and treats a missing declared database
|
|
6
|
+
* as schema-absence, and the operation's pool opens the declared database through the
|
|
7
|
+
* `withPg` override — the config each `PgClient.layer` call receives is recorded here.
|
|
8
|
+
*/
|
|
9
|
+
import { beforeEach, describe, expect, test } from 'bun:test';
|
|
10
|
+
import * as PgClientModule from '@effect/sql-pg/PgClient';
|
|
11
|
+
import type { PgClient as PgClientService } from '@effect/sql-pg/PgClient';
|
|
12
|
+
import * as Effect from 'effect/Effect';
|
|
13
|
+
import * as Layer from 'effect/Layer';
|
|
14
|
+
import { SqlClient } from 'effect/unstable/sql/SqlClient';
|
|
15
|
+
import { isDependentObjectsError } from './schema-sql.ts';
|
|
16
|
+
import { classifyInstalled } from './installed-classifier.ts';
|
|
17
|
+
import { PostgresPool, postgresConnection } from './connection.ts';
|
|
18
|
+
import { postgresSchemaHandlers } from './schema.ts';
|
|
19
|
+
import { PostgresSchemaDeleteForeignRefused } from './schema-errors.ts';
|
|
20
|
+
import type { PostgresSchemaAttributes, PostgresSchemaProps } from './schema-attrs.ts';
|
|
21
|
+
import { type RouteAnswers, deleteArgs, readArgs, route } from './schema-test-kit.ts';
|
|
22
|
+
import { Unowned } from 'alchemy/AdoptPolicy';
|
|
23
|
+
|
|
24
|
+
if (postgresSchemaHandlers.read === undefined) {
|
|
25
|
+
throw new Error('Postgres.Schema is missing its read handler');
|
|
26
|
+
}
|
|
27
|
+
/** Narrowed once at module scope — the narrowing does not survive into `test` callbacks. */
|
|
28
|
+
const schemaRead = postgresSchemaHandlers.read;
|
|
29
|
+
|
|
30
|
+
/** Every `PgClient.layer` config the mocked layer was built with — one per `withPg` call. */
|
|
31
|
+
const pools: Array<{ database?: string | undefined }> = [];
|
|
32
|
+
/** Every SQL text the fake client ran, in order. */
|
|
33
|
+
const queries: string[] = [];
|
|
34
|
+
/** The router answers for the current test (`schema-test-kit.ts` markers, JSON strings). */
|
|
35
|
+
let answers: RouteAnswers = {};
|
|
36
|
+
|
|
37
|
+
// The fake service: `unsafe` is the only member the schema handlers call (`withPg`'s build
|
|
38
|
+
// callbacks), and `Statement<A>` is an `Effect<ReadonlyArray<A>, SqlError>` — a plain succeed
|
|
39
|
+
// satisfies the same call shape.
|
|
40
|
+
const fakeClient = {
|
|
41
|
+
unsafe: (text: string) => {
|
|
42
|
+
queries.push(text);
|
|
43
|
+
const answer = route(text, answers);
|
|
44
|
+
return Effect.succeed(
|
|
45
|
+
(answer === undefined ? [] : (JSON.parse(answer) as ReadonlyArray<object>)) as never,
|
|
46
|
+
);
|
|
47
|
+
},
|
|
48
|
+
} as unknown as PgClientService;
|
|
49
|
+
|
|
50
|
+
// The pool factory is provided to this Effect only; other suites keep the real driver.
|
|
51
|
+
const socket = Layer.merge(
|
|
52
|
+
// tmp-allow: unix socket directory the driver joins a socket name onto, not a directory this test creates
|
|
53
|
+
postgresConnection({ host: '/tmp', database: 'postgres', username: 'postgres' }),
|
|
54
|
+
Layer.succeed(PostgresPool, (config) => {
|
|
55
|
+
pools.push(config);
|
|
56
|
+
return Layer.merge(
|
|
57
|
+
Layer.succeed(PgClientModule.PgClient, fakeClient),
|
|
58
|
+
Layer.succeed(SqlClient, fakeClient),
|
|
59
|
+
);
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
const declared: PostgresSchemaProps = { name: 's4', database: 'agents', owner: 'seat_role' };
|
|
63
|
+
const persisted: PostgresSchemaAttributes = {
|
|
64
|
+
name: 's4',
|
|
65
|
+
database: 'agents',
|
|
66
|
+
oid: 16442,
|
|
67
|
+
owner: 'seat_role',
|
|
68
|
+
comment: null,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
describe('socket transport: a missing declared database is schema absent', () => {
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
pools.length = 0;
|
|
74
|
+
queries.length = 0;
|
|
75
|
+
answers = {};
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('delete is idempotent success: one probe over the FAMILY pool, no connection to the declared database', async () => {
|
|
79
|
+
answers = { probe: '[]' };
|
|
80
|
+
await Effect.runPromise(
|
|
81
|
+
postgresSchemaHandlers
|
|
82
|
+
.delete(deleteArgs({ ...declared, cascade: true }, persisted))
|
|
83
|
+
.pipe(Effect.provide(socket)),
|
|
84
|
+
);
|
|
85
|
+
expect(pools.length).toBe(1);
|
|
86
|
+
expect(pools[0]?.database).toBe('postgres');
|
|
87
|
+
expect(queries.length).toBe(1);
|
|
88
|
+
expect(queries[0]).toContain('FROM pg_database');
|
|
89
|
+
expect(queries.some((q) => q.startsWith('DROP SCHEMA'))).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('read answers absent: the untyped 3D000 connect never happens', async () => {
|
|
93
|
+
answers = { probe: '[]' };
|
|
94
|
+
const result = await Effect.runPromise(
|
|
95
|
+
schemaRead(readArgs(declared)).pipe(Effect.provide(socket)),
|
|
96
|
+
);
|
|
97
|
+
expect(result).toBeUndefined();
|
|
98
|
+
expect(pools.length).toBe(1);
|
|
99
|
+
expect(pools[0]?.database).toBe('postgres');
|
|
100
|
+
expect(queries.length).toBe(1);
|
|
101
|
+
expect(queries[0]).toContain('FROM pg_database');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('the `database` override reaches the pool: probe over the family pool, statements over the declared one', async () => {
|
|
105
|
+
answers = {
|
|
106
|
+
probe: '[{"present":1}]',
|
|
107
|
+
schema: '[{"name":"s4","oid":16442,"owner":"seat_role","comment":null,"database":"agents"}]',
|
|
108
|
+
empty: '[{"empty":true}]',
|
|
109
|
+
proof: '[{"database":"agents"}]',
|
|
110
|
+
};
|
|
111
|
+
await Effect.runPromise(
|
|
112
|
+
postgresSchemaHandlers.delete(deleteArgs(declared, persisted)).pipe(Effect.provide(socket)),
|
|
113
|
+
);
|
|
114
|
+
// The probe opened the family database; every statement after it opened `agents`.
|
|
115
|
+
expect(pools.map((pool) => pool.database)).toEqual(['postgres', 'agents']);
|
|
116
|
+
expect(queries.some((q) => q.startsWith('DROP SCHEMA'))).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('read over an existing declared database answers the row from the overridden pool', async () => {
|
|
120
|
+
answers = {
|
|
121
|
+
probe: '[{"present":1}]',
|
|
122
|
+
schema: '[{"name":"s4","oid":16442,"owner":"seat_role","comment":null,"database":"agents"}]',
|
|
123
|
+
proof: '[{"database":"agents"}]',
|
|
124
|
+
};
|
|
125
|
+
const result = await Effect.runPromise(
|
|
126
|
+
schemaRead(readArgs(declared)).pipe(Effect.provide(socket)),
|
|
127
|
+
);
|
|
128
|
+
expect(pools.map((pool) => pool.database)).toEqual(['postgres', 'agents']);
|
|
129
|
+
expect(Unowned.is(result)).toBe(true);
|
|
130
|
+
expect({ ...(result as object) }).toEqual({ ...persisted });
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('delete still proves ownership over the socket: an out-of-band recreation is refused', async () => {
|
|
134
|
+
// The re-read row is another role's (the review's s4 scenario): the persisted proof cannot
|
|
135
|
+
// vouch for it, and the socket pool never issues the DROP.
|
|
136
|
+
answers = {
|
|
137
|
+
probe: '[{"present":1}]',
|
|
138
|
+
schema: '[{"name":"s4","oid":16443,"owner":"other_role","comment":null,"database":"agents"}]',
|
|
139
|
+
proof: '[{"database":"agents"}]',
|
|
140
|
+
};
|
|
141
|
+
const error = await Effect.runPromise(
|
|
142
|
+
Effect.flip(
|
|
143
|
+
postgresSchemaHandlers
|
|
144
|
+
.delete(deleteArgs({ ...declared, cascade: true }, persisted))
|
|
145
|
+
.pipe(Effect.provide(socket)),
|
|
146
|
+
),
|
|
147
|
+
);
|
|
148
|
+
expect(error).toBeInstanceOf(PostgresSchemaDeleteForeignRefused);
|
|
149
|
+
expect(error).toMatchObject({ liveOid: 16443, liveOwner: 'other_role', lastOid: 16442 });
|
|
150
|
+
expect(queries.some((q) => q.startsWith('DROP SCHEMA'))).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('the installed classifier keeps 3D000 as UnknownError with the raw code — the untyped failure the probe prevents', async () => {
|
|
154
|
+
// `classifySqlState` only types class `42` as `SqlSyntaxError`; `3D000` (`invalid_catalog_name`)
|
|
155
|
+
// is class `3D`, so a socket connect to a missing database surfaces as this shape today —
|
|
156
|
+
// the reason `read`/`delete` probe `pg_database` first instead of opening it.
|
|
157
|
+
const error = await classifyInstalled('3D000', 'database "agents" does not exist');
|
|
158
|
+
expect(error.reason._tag).toBe('UnknownError');
|
|
159
|
+
expect((error.reason as { cause?: { code?: string } }).cause?.code).toBe('3D000');
|
|
160
|
+
expect(isDependentObjectsError(error)).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The statements `Postgres.Schema` issues and the quoting each needs.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ `CREATE SCHEMA` TAKES NO BIND PARAMETERS. Measured at `gram.y@REL_18_6`:
|
|
5
|
+
* `CreateSchemaStmt: CREATE SCHEMA [IF NOT EXISTS] opt_schema_name AUTHORIZATION
|
|
6
|
+
* RoleSpec | create_schema_name opt_schema_element_list`. Neither the name nor the
|
|
7
|
+
* authorization role reaches `PARAM` (`$n`), so every free-form value is escaped by hand
|
|
8
|
+
* (`quoteIdent`) and the assembled text runs through `.unsafe()`.
|
|
9
|
+
* ★ The schema name is a single `ColId` token, so `quoteIdent` (single-token quoting) is
|
|
10
|
+
* correct — same rule as `Postgres.Database`'s name.
|
|
11
|
+
*/
|
|
12
|
+
import type { SqlError } from 'effect/unstable/sql/SqlError';
|
|
13
|
+
import * as Effect from 'effect/Effect';
|
|
14
|
+
import type { PostgresSchemaAttributes, PostgresSchemaProps } from './schema-attrs.ts';
|
|
15
|
+
import { quoteIdent, quoteStringLiteral } from './database-sql.ts';
|
|
16
|
+
import type { PgExecutor } from './database-sql.ts';
|
|
17
|
+
|
|
18
|
+
/** `CREATE SCHEMA "name" [AUTHORIZATION "owner"]` — the only create this family issues. */
|
|
19
|
+
export const buildCreateSchemaSql = (props: PostgresSchemaProps): string =>
|
|
20
|
+
`CREATE SCHEMA ${quoteIdent(props.name)}${
|
|
21
|
+
props.owner === undefined ? '' : ` AUTHORIZATION ${quoteIdent(props.owner)}`
|
|
22
|
+
}`;
|
|
23
|
+
|
|
24
|
+
/** `COMMENT ON SCHEMA "name" IS E'…'` — issued only when a comment is declared. */
|
|
25
|
+
export const buildCommentSchemaSql = (name: string, comment: string): string =>
|
|
26
|
+
`COMMENT ON SCHEMA ${quoteIdent(name)} IS ${quoteStringLiteral(comment)}`;
|
|
27
|
+
|
|
28
|
+
/** `DROP SCHEMA IF EXISTS "name" [CASCADE]` — idempotent; `CASCADE` only when declared. */
|
|
29
|
+
export const buildDropSchemaSql = (name: string, cascade: boolean): string =>
|
|
30
|
+
`DROP SCHEMA IF EXISTS ${quoteIdent(name)}${cascade ? ' CASCADE' : ''}`;
|
|
31
|
+
|
|
32
|
+
const SELECT_SCHEMA_SQL = `SELECT
|
|
33
|
+
n.oid AS oid,
|
|
34
|
+
n.nspname AS name,
|
|
35
|
+
pg_get_userbyid(n.nspowner) AS owner,
|
|
36
|
+
obj_description(n.oid, 'pg_namespace') AS comment,
|
|
37
|
+
current_database() AS database
|
|
38
|
+
FROM pg_namespace n
|
|
39
|
+
WHERE n.nspname = $1`;
|
|
40
|
+
|
|
41
|
+
/** The read: one bound `SELECT` on `pg_namespace`. `undefined` when absent. The row carries
|
|
42
|
+
* `current_database()` as `database`, so every caller can prove where the statement ran. */
|
|
43
|
+
export const selectSchema = (
|
|
44
|
+
pg: PgExecutor,
|
|
45
|
+
name: string,
|
|
46
|
+
): Effect.Effect<PostgresSchemaAttributes | undefined, SqlError> =>
|
|
47
|
+
Effect.map(pg.unsafe<PostgresSchemaAttributes>(SELECT_SCHEMA_SQL, [name]), (rows) => rows[0]);
|
|
48
|
+
|
|
49
|
+
const CURRENT_DATABASE_SQL = 'SELECT current_database() AS database';
|
|
50
|
+
|
|
51
|
+
/** Which database this executor actually reaches — checked before any `CREATE` or `DROP SCHEMA`,
|
|
52
|
+
* so a schema declared for `agents` can never quietly land in the maintenance database the
|
|
53
|
+
* family connection points at. */
|
|
54
|
+
export const currentDatabase = (pg: PgExecutor): Effect.Effect<string, SqlError> =>
|
|
55
|
+
Effect.map(
|
|
56
|
+
pg.unsafe<{ readonly database: string }>(CURRENT_DATABASE_SQL),
|
|
57
|
+
// current_database() always answers one row; an empty fallback can only compare unequal,
|
|
58
|
+
// failing closed into PostgresSchemaWrongDatabase.
|
|
59
|
+
(rows) => rows[0]?.database ?? '',
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The role a `CREATE SCHEMA` without `AUTHORIZATION` is owned by. Measured at
|
|
64
|
+
* `gram.y@REL_18_6`: absent `AUTHORIZATION`, the schema owner is the current user.
|
|
65
|
+
* `reconcile` compares an omitted `owner` to this, so a race winner owned by someone else
|
|
66
|
+
* is drift rather than an adopted row.
|
|
67
|
+
*/
|
|
68
|
+
const CURRENT_USER_SQL = 'SELECT current_user AS role';
|
|
69
|
+
|
|
70
|
+
export const currentUser = (pg: PgExecutor): Effect.Effect<string, SqlError> =>
|
|
71
|
+
Effect.map(
|
|
72
|
+
pg.unsafe<{ readonly role: string }>(CURRENT_USER_SQL),
|
|
73
|
+
// An empty answer can only compare unequal, failing closed into PostgresSchemaDrift.
|
|
74
|
+
(rows) => rows[0]?.role ?? '',
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* "Empty" means no object of ANY kind the schema owns. Measured object kinds live in four
|
|
79
|
+
* catalogs: relations (tables, views, indexes, sequences — `pg_class`), functions and
|
|
80
|
+
* aggregates (`pg_proc`), types, domains and enums (`pg_type`), operators (`pg_operator`). A
|
|
81
|
+
* kind without a dedicated catalog (an extension's `extnamespace`, a collation) can still pass
|
|
82
|
+
* this check — and then the plain `DROP SCHEMA` answers `2BP01`, which `dropWithClient`
|
|
83
|
+
* classifies as the same typed refusal.
|
|
84
|
+
*
|
|
85
|
+
* ⚠️ THE FAKE (`fake-schema-sql.ts`) ROUTES THIS STATEMENT BY `AS empty` + `pg_class` — keep both
|
|
86
|
+
* markers in any rewrite, and keep this check matched BEFORE the plain `pg_namespace` branch
|
|
87
|
+
* (this SQL contains `FROM pg_namespace` too, inside its `WITH`).
|
|
88
|
+
*/
|
|
89
|
+
const SCHEMA_EMPTY_SQL = `WITH ns AS (SELECT oid FROM pg_namespace WHERE nspname = $1)
|
|
90
|
+
SELECT NOT EXISTS (
|
|
91
|
+
SELECT 1 FROM pg_class c WHERE c.relnamespace = (SELECT oid FROM ns)
|
|
92
|
+
) AND NOT EXISTS (
|
|
93
|
+
SELECT 1 FROM pg_proc p WHERE p.pronamespace = (SELECT oid FROM ns)
|
|
94
|
+
) AND NOT EXISTS (
|
|
95
|
+
SELECT 1 FROM pg_type t WHERE t.typnamespace = (SELECT oid FROM ns)
|
|
96
|
+
) AND NOT EXISTS (
|
|
97
|
+
SELECT 1 FROM pg_operator o WHERE o.oprnamespace = (SELECT oid FROM ns)
|
|
98
|
+
) AS empty`;
|
|
99
|
+
|
|
100
|
+
/** `true` when the schema holds none of the four object catalogs above (used before a
|
|
101
|
+
* non-cascade `DROP SCHEMA`). */
|
|
102
|
+
export const schemaIsEmpty = (pg: PgExecutor, name: string): Effect.Effect<boolean, SqlError> =>
|
|
103
|
+
Effect.map(
|
|
104
|
+
pg.unsafe<{ readonly empty: boolean }>(SCHEMA_EMPTY_SQL, [name]),
|
|
105
|
+
(rows) => rows[0]?.empty ?? true,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
/** SQLSTATE `2BP01` (`dependent_objects_still_exist`): the non-cascade `DROP SCHEMA` refused
|
|
109
|
+
* because an object kind outside the emptiness check's four catalogs still exists.
|
|
110
|
+
*
|
|
111
|
+
* Measured at `@effect/sql-pg@4.0.0-rc.115` `src/internal/sqlError.ts#classifySqlState`: only
|
|
112
|
+
* class `42` becomes `SqlSyntaxError`. `2BP01` is class `2B`, so it falls through to
|
|
113
|
+
* `UnknownError` (`sqlError.ts:74`). `PgConnection.ts#classifyFields` still puts the raw code
|
|
114
|
+
* on `reason.cause.code`. The runner transport copies that rule (`psql-executor.ts#failure`):
|
|
115
|
+
* `SqlSyntaxError` only when `state.startsWith('42')`, otherwise `UnknownError` with the same
|
|
116
|
+
* `cause.code`. Reading the code, not the tag, is what makes a real server refusal match. */
|
|
117
|
+
export const isDependentObjectsError = (error: SqlError): boolean => {
|
|
118
|
+
const cause = error.reason.cause;
|
|
119
|
+
return (
|
|
120
|
+
typeof cause === 'object' && cause !== null && (cause as { code?: unknown }).code === '2BP01'
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** REL_18_6 pg_namespace.c raises duplicate_schema; both transports retain cause.code. */
|
|
125
|
+
export const isDuplicateSchemaError = (error: SqlError): boolean => {
|
|
126
|
+
const cause = error.reason.cause;
|
|
127
|
+
return (
|
|
128
|
+
typeof cause === 'object' && cause !== null && (cause as { code?: unknown }).code === '42P06'
|
|
129
|
+
);
|
|
130
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/** Regressions for unresolved-Output apply paths, driven through the actual runner handlers. */
|
|
2
|
+
import { expect, test } from 'bun:test';
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
4
|
+
import { postgresSchemaHandlers, reconcileWithClient } from './schema.ts';
|
|
5
|
+
import { postgresRunnerConnection } from './connection.ts';
|
|
6
|
+
import { makeFakeSql } from './fake-sql.ts';
|
|
7
|
+
import { readArgs, router, runnerWith } from './schema-test-kit.ts';
|
|
8
|
+
import type { PostgresSchemaAttributes } from './schema-attrs.ts';
|
|
9
|
+
|
|
10
|
+
const props = { name: 'ledger', database: 'postgres', owner: 'postgres', cascade: true };
|
|
11
|
+
const stored: PostgresSchemaAttributes = { ...props, oid: 25533, comment: null };
|
|
12
|
+
const replacement = { ...stored, oid: 25534 };
|
|
13
|
+
const args = (output?: PostgresSchemaAttributes) => ({
|
|
14
|
+
...readArgs(props),
|
|
15
|
+
news: props,
|
|
16
|
+
output,
|
|
17
|
+
session: undefined as never,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
for (const name of ['ledger', 'public']) {
|
|
21
|
+
test(`apply refuses existing ${name} without output even when owner matches`, async () => {
|
|
22
|
+
const { run, stdins } = runnerWith(
|
|
23
|
+
router({ proof: '[{"database":"postgres"}]', schema: JSON.stringify([{ ...stored, name }]) }),
|
|
24
|
+
);
|
|
25
|
+
const error = await Effect.runPromise(
|
|
26
|
+
postgresSchemaHandlers
|
|
27
|
+
.reconcile({ ...args(), news: { ...props, name } })
|
|
28
|
+
.pipe(
|
|
29
|
+
Effect.provide(
|
|
30
|
+
postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
|
|
31
|
+
),
|
|
32
|
+
Effect.flip,
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
expect(error).toMatchObject({ _tag: 'PostgresSchemaExistsRefused' });
|
|
36
|
+
expect(stdins.every((s) => s.startsWith('SELECT'))).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test('update refuses a recycled oid before it can replace the delete proof', async () => {
|
|
41
|
+
const fake = makeFakeSql({ schemas: [replacement], schemasWithRelations: ['ledger'] });
|
|
42
|
+
const error = await Effect.runPromise(Effect.flip(reconcileWithClient(fake, props, stored)));
|
|
43
|
+
expect(error).toMatchObject({
|
|
44
|
+
_tag: 'PostgresSchemaIdentityRefused',
|
|
45
|
+
storedOid: 25533,
|
|
46
|
+
liveOid: 25534,
|
|
47
|
+
});
|
|
48
|
+
expect(fake.schemas.get('ledger')).toEqual(replacement);
|
|
49
|
+
expect(fake.relationsIn.has('ledger')).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('read refuses a recycled oid instead of refreshing persisted proof', async () => {
|
|
53
|
+
const { run } = runnerWith(
|
|
54
|
+
router({ probe: '[{"present":1}]', schema: JSON.stringify([replacement]) }),
|
|
55
|
+
);
|
|
56
|
+
const read = postgresSchemaHandlers.read;
|
|
57
|
+
if (read === undefined) throw new Error('missing read');
|
|
58
|
+
const error = await Effect.runPromise(
|
|
59
|
+
read({ ...readArgs(props), output: stored }).pipe(
|
|
60
|
+
Effect.provide(postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' })),
|
|
61
|
+
Effect.flip,
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
expect(error).toMatchObject({ _tag: 'PostgresSchemaIdentityRefused' });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
for (const [change, tag] of [
|
|
68
|
+
[{ name: 'new_ledger' }, 'PostgresSchemaRenameRefused'],
|
|
69
|
+
[{ database: 'new_database' }, 'PostgresSchemaDatabaseRefused'],
|
|
70
|
+
] as const) {
|
|
71
|
+
test(`apply repeats ${tag} after unresolved diff`, async () => {
|
|
72
|
+
const { run, stdins } = runnerWith(
|
|
73
|
+
router({
|
|
74
|
+
proof: JSON.stringify([{ database: change.database ?? 'postgres' }]),
|
|
75
|
+
schema: JSON.stringify([{ ...stored, ...change }]),
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
const error = await Effect.runPromise(
|
|
79
|
+
postgresSchemaHandlers
|
|
80
|
+
.reconcile({ ...args(stored), news: { ...props, ...change } })
|
|
81
|
+
.pipe(
|
|
82
|
+
Effect.provide(
|
|
83
|
+
postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
|
|
84
|
+
),
|
|
85
|
+
Effect.flip,
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
expect(error).toMatchObject({ _tag: tag });
|
|
89
|
+
expect(stdins).toEqual([]);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
test('same-owner create race is a typed exists refusal, never a comment or adoption', async () => {
|
|
94
|
+
const fake = makeFakeSql({ roles: ['postgres'], raceNextCreateSchema: 'postgres' });
|
|
95
|
+
const error = await Effect.runPromise(
|
|
96
|
+
Effect.flip(reconcileWithClient(fake, { ...props, comment: 'ours' })),
|
|
97
|
+
);
|
|
98
|
+
expect(error).toMatchObject({ _tag: 'PostgresSchemaExistsRefused' });
|
|
99
|
+
expect(fake.schemas.get('ledger')?.comment).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('runner 42P06 becomes an adoption refusal; other SQLSTATEs remain SqlError', async () => {
|
|
103
|
+
for (const code of ['42P06', '42501']) {
|
|
104
|
+
const run = ({ stdin }: { stdin: string }) =>
|
|
105
|
+
Promise.resolve(
|
|
106
|
+
stdin.startsWith('CREATE')
|
|
107
|
+
? { code: 3, stdout: '', stderr: `ERROR: ${code}: refused` }
|
|
108
|
+
: {
|
|
109
|
+
code: 0,
|
|
110
|
+
stdout:
|
|
111
|
+
stdin.includes('current_database()') && !stdin.includes('pg_namespace')
|
|
112
|
+
? '[{"database":"postgres"}]'
|
|
113
|
+
: stdin.includes('pg_roles')
|
|
114
|
+
? '[{"present":1}]'
|
|
115
|
+
: '[]',
|
|
116
|
+
stderr: '',
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
const error = await Effect.runPromise(
|
|
120
|
+
postgresSchemaHandlers
|
|
121
|
+
.reconcile(args())
|
|
122
|
+
.pipe(
|
|
123
|
+
Effect.provide(
|
|
124
|
+
postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
|
|
125
|
+
),
|
|
126
|
+
Effect.flip,
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
expect(error._tag).toBe(code === '42P06' ? 'PostgresSchemaExistsRefused' : 'SqlError');
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Apply.ts passes the adopted live attributes as output. The stricter gate must permit that.
|
|
134
|
+
test('explicit adoption output permits a matching schema with zero writes', async () => {
|
|
135
|
+
const { run, stdins } = runnerWith(
|
|
136
|
+
router({ proof: '[{"database":"postgres"}]', schema: JSON.stringify([stored]) }),
|
|
137
|
+
);
|
|
138
|
+
const result = await Effect.runPromise(
|
|
139
|
+
postgresSchemaHandlers
|
|
140
|
+
.reconcile(args(stored))
|
|
141
|
+
.pipe(
|
|
142
|
+
Effect.provide(
|
|
143
|
+
postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
|
|
144
|
+
),
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
expect(result).toEqual(stored);
|
|
148
|
+
expect(stdins.every((s) => s.startsWith('SELECT'))).toBe(true);
|
|
149
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wiring for the runner-transport `Postgres.Schema` handler tests: the recording
|
|
3
|
+
* `PsqlRunner` (every stdin and its `psql` argv), the handler-argument builders, and the
|
|
4
|
+
* one SQL router they all answer through.
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ THE ROUTER ORDER IS LOAD-BEARING (`schema-sql.ts`): the emptiness check's `WITH`
|
|
7
|
+
* contains `FROM pg_namespace`, and the schema row SELECT contains
|
|
8
|
+
* `current_database() AS database` — so `AS empty` must be matched before
|
|
9
|
+
* `FROM pg_namespace`, and `FROM pg_namespace` before `current_database()`. The database
|
|
10
|
+
* probe (`SELECT 1 AS present FROM pg_database`, `database-sql.ts`) carries no other
|
|
11
|
+
* branch's marker and matches its own.
|
|
12
|
+
*/
|
|
13
|
+
import type { PsqlRunner } from './psql-executor.ts';
|
|
14
|
+
import type { PostgresSchemaAttributes, PostgresSchemaProps } from './schema-attrs.ts';
|
|
15
|
+
|
|
16
|
+
export const ok = (stdout: string) => Promise.resolve({ code: 0, stdout, stderr: '' });
|
|
17
|
+
|
|
18
|
+
/** One answer per statement shape a handler issues; `undefined` falls through the router. */
|
|
19
|
+
export interface RouteAnswers {
|
|
20
|
+
/** `schemaIsEmpty` — the pre-DROP emptiness check (`cascade` unset). */
|
|
21
|
+
readonly empty?: string;
|
|
22
|
+
/** `selectSchema` — the schema row re-read, or `[]` for an absent schema. */
|
|
23
|
+
readonly schema?: string;
|
|
24
|
+
/** `databaseExists` — `[{\"present\":1}]` when the declared database exists, `[]` when not. */
|
|
25
|
+
readonly probe?: string;
|
|
26
|
+
/** `current_database()` — the proof that the override reached the declared database. */
|
|
27
|
+
readonly proof?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Route one stdin to its answer by the markers above, `undefined` when no branch matches. */
|
|
31
|
+
export const route = (stdin: string, answers: RouteAnswers): string | undefined => {
|
|
32
|
+
if (stdin.includes('AS empty')) return answers.empty;
|
|
33
|
+
if (stdin.includes('FROM pg_namespace')) return answers.schema;
|
|
34
|
+
if (stdin.includes('FROM pg_database')) return answers.probe;
|
|
35
|
+
if (stdin.includes('current_database()')) return answers.proof;
|
|
36
|
+
return undefined;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** A `router` that answers every stdin (unrouted statements answer the empty string). */
|
|
40
|
+
export const router = (answers: RouteAnswers) => (stdin: string) => route(stdin, answers) ?? '';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A recording `PsqlRunner`: every stdin and the `psql` argv are kept, each stdin answered by
|
|
44
|
+
* `answers`. The argv recording pins the `-d` database each statement ran against — the last
|
|
45
|
+
* element of the argv is the `psql -d` value (`psql-executor.ts`).
|
|
46
|
+
*/
|
|
47
|
+
export const runnerWith = (
|
|
48
|
+
answers: (stdin: string) => string,
|
|
49
|
+
): { run: PsqlRunner; stdins: string[]; argvs: Array<readonly string[]> } => {
|
|
50
|
+
const stdins: string[] = [];
|
|
51
|
+
const argvs: Array<readonly string[]> = [];
|
|
52
|
+
return {
|
|
53
|
+
run: ({ stdin, argv }) => {
|
|
54
|
+
stdins.push(stdin);
|
|
55
|
+
argvs.push(argv);
|
|
56
|
+
return Promise.resolve({ code: 0, stdout: answers(stdin), stderr: '' });
|
|
57
|
+
},
|
|
58
|
+
stdins,
|
|
59
|
+
argvs,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** The handler inputs the engine passes on `delete`. `output` is the persisted state — the
|
|
64
|
+
* `oid` + `owner` the last reconcile asserted, which delete must prove the live row still
|
|
65
|
+
* matches before it drops (`schema-delete.ts`). */
|
|
66
|
+
export const deleteArgs = (olds: PostgresSchemaProps, output: PostgresSchemaAttributes) => ({
|
|
67
|
+
id: 'x',
|
|
68
|
+
fqn: 'x',
|
|
69
|
+
instanceId: 'x',
|
|
70
|
+
olds,
|
|
71
|
+
output,
|
|
72
|
+
session: undefined as never,
|
|
73
|
+
bindings: [] as never,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/** The handler inputs the engine passes on `read` (undefined output is the adopt path). */
|
|
77
|
+
export const readArgs = (olds: PostgresSchemaProps) => ({
|
|
78
|
+
id: 'x',
|
|
79
|
+
fqn: 'x',
|
|
80
|
+
instanceId: 'x',
|
|
81
|
+
olds,
|
|
82
|
+
output: undefined,
|
|
83
|
+
session: undefined as never,
|
|
84
|
+
bindings: [] as never,
|
|
85
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Postgres.Schema` — create-and-assert over one schema in a self-hosted PostgreSQL 18 cluster.
|
|
3
|
+
*
|
|
4
|
+
* ★ A SCHEMA CARRIES NO OWNERSHIP MARK, exactly like `Postgres.Database` (H1). `read` answers
|
|
5
|
+
* `Unowned` for a match, so every already-live schema needs `adopt(true)` in the stack that
|
|
6
|
+
* declares it.
|
|
7
|
+
* ⛔ NOT ALTER-CAPABLE. `ALTER SCHEMA` only renames or changes owner in Postgres; neither is
|
|
8
|
+
* implemented. An owner or comment mismatch against the live row is a typed
|
|
9
|
+
* `PostgresSchemaDrift` refusal, never an `ALTER SCHEMA` — same rule `Postgres.Database`
|
|
10
|
+
* applies to its own asserted props. An omitted `owner` is compared to `current_user`
|
|
11
|
+
* (`schema-assert.ts`): a fresh create without `AUTHORIZATION` would be owned by that role.
|
|
12
|
+
* ⛔ THE DECLARED `database` IS PROVEN, NEVER ASSUMED. The family connection points at a
|
|
13
|
+
* maintenance database (a brand-new database cannot be connected to on a cold plan —
|
|
14
|
+
* `docs/postgres.md#measured-path`), so the handlers below open `props.database` themselves
|
|
15
|
+
* (`withPg`'s database override) and `reconcile`/`drop` first compare `current_database()`
|
|
16
|
+
* against it (`PostgresSchemaWrongDatabase`). Without that proof a seat's schema would be
|
|
17
|
+
* created in whatever database the single connection happened to target.
|
|
18
|
+
* ⛔ `delete` DROPS ONLY WHAT IT CAN PROVE IT CREATED. Before any `DROP`, the handler re-reads
|
|
19
|
+
* the live row (`schema-delete.ts`) and matches it against the persisted `oid` + `owner`;
|
|
20
|
+
* a mismatch is `PostgresSchemaDeleteForeignRefused`, never a `DROP` (`CASCADE` included) —
|
|
21
|
+
* the schema under that name may be another role's, recreated out of band.
|
|
22
|
+
* ⛔ A MISSING DECLARED DATABASE IS "SCHEMA ABSENT" IN `read` AND `delete`. Both probe
|
|
23
|
+
* `pg_database` over the family connection first (`database-sql.ts#databaseExists`), so a
|
|
24
|
+
* cold plan against a not-yet-created database plans a create (`read` answers absent)
|
|
25
|
+
* instead of failing untyped on connect (`ConnectionError` over psql, `UnknownError` 3D000
|
|
26
|
+
* over the socket), and a delete of a dropped database stays idempotent. `reconcile` still
|
|
27
|
+
* requires the database to exist — that ordering is the declaration's job (`docs/
|
|
28
|
+
* postgres-schema.md`: pass `database: db.name`).
|
|
29
|
+
* ⛔ `delete` DROPS ONLY WHEN SAFE. `DROP SCHEMA` refuses a non-empty schema unless
|
|
30
|
+
* `cascade: true` is declared (`PostgresSchemaDropNotEmptyError`); the `cascade` prop
|
|
31
|
+
* defaults to `false` (retain policy by default — see `defaultRemovalPolicy: 'retain'`).
|
|
32
|
+
*/
|
|
33
|
+
import { Resource } from 'alchemy';
|
|
34
|
+
import { Unowned } from 'alchemy/AdoptPolicy';
|
|
35
|
+
import * as Provider from 'alchemy/Provider';
|
|
36
|
+
import * as Effect from 'effect/Effect';
|
|
37
|
+
import { type PostgresSchemaAttributes, type PostgresSchemaProps } from './schema-attrs.ts';
|
|
38
|
+
import { schemaNameByteRefusal } from './schema-attrs.ts';
|
|
39
|
+
import { diffPostgresSchema } from './schema-diff.ts';
|
|
40
|
+
import { selectSchema } from './schema-sql.ts';
|
|
41
|
+
import { databaseExists } from './database-sql.ts';
|
|
42
|
+
import { reconcileWithClient } from './schema-reconcile.ts';
|
|
43
|
+
import { assertSchemaIdentity, assertSchemaTarget } from './schema-identity.ts';
|
|
44
|
+
import { deleteWithClient, dropWithClient } from './schema-delete.ts';
|
|
45
|
+
import type { PgExecutor } from './database-sql.ts';
|
|
46
|
+
import { type PostgresConnection, withPg } from './connection.ts';
|
|
47
|
+
import { PostgresSchemaNameRefused, PostgresSchemaWrongDatabase } from './schema-errors.ts';
|
|
48
|
+
|
|
49
|
+
export interface PostgresSchema extends Resource<
|
|
50
|
+
'Postgres.Schema',
|
|
51
|
+
PostgresSchemaProps,
|
|
52
|
+
PostgresSchemaAttributes,
|
|
53
|
+
never,
|
|
54
|
+
PostgresConnection
|
|
55
|
+
> {}
|
|
56
|
+
|
|
57
|
+
export const PostgresSchema = Resource<PostgresSchema>('Postgres.Schema', {
|
|
58
|
+
defaultRemovalPolicy: 'retain',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/** Same dual `typeof` guard as `isPostgresDatabase` (see `database.ts`): a resource constructor
|
|
62
|
+
* is a callable `Object.assign`ed function, not a plain object. */
|
|
63
|
+
export const isPostgresSchema = (value: unknown): value is PostgresSchema =>
|
|
64
|
+
(typeof value === 'object' || typeof value === 'function') &&
|
|
65
|
+
value !== null &&
|
|
66
|
+
(value as { Type?: unknown }).Type === 'Postgres.Schema';
|
|
67
|
+
|
|
68
|
+
/** The core of `read`: one bound `SELECT`, no ownership branding. */
|
|
69
|
+
export const readWithClient = (pg: PgExecutor, name: string) => selectSchema(pg, name);
|
|
70
|
+
|
|
71
|
+
export { diffPostgresSchema } from './schema-diff.ts';
|
|
72
|
+
export { reconcileWithClient } from './schema-reconcile.ts';
|
|
73
|
+
export { deleteWithClient, dropWithClient };
|
|
74
|
+
|
|
75
|
+
/** The five lifecycle handlers. Exported on its own so a test drives the real implementation. */
|
|
76
|
+
export const postgresSchemaHandlers = PostgresSchema.Provider.of({
|
|
77
|
+
// ⚠️ EMPTY, NOT A LIVE SWEEP — same reasoning as Postgres.Database's list.
|
|
78
|
+
list: () => Effect.succeed([]),
|
|
79
|
+
nuke: { skip: true },
|
|
80
|
+
|
|
81
|
+
read: ({ olds, output }) =>
|
|
82
|
+
Effect.gen(function* () {
|
|
83
|
+
const name = output?.name ?? olds.name;
|
|
84
|
+
const database = output?.database ?? olds.database;
|
|
85
|
+
// A declared database that does not exist IS a schema absent. The probe runs over the
|
|
86
|
+
// FAMILY connection (no override), which points at the maintenance database a cold plan
|
|
87
|
+
// can always reach — opening the declared database instead would fail untyped
|
|
88
|
+
// (`ConnectionError` over psql, `UnknownError` 3D000 over the socket).
|
|
89
|
+
const present = yield* withPg((pg) => databaseExists(pg, database));
|
|
90
|
+
if (!present) return undefined;
|
|
91
|
+
const live = yield* withPg((pg) => readWithClient(pg, name), database);
|
|
92
|
+
if (live !== undefined && live.database !== database) {
|
|
93
|
+
return yield* Effect.fail(
|
|
94
|
+
new PostgresSchemaWrongDatabase({
|
|
95
|
+
schema: name,
|
|
96
|
+
declared: database,
|
|
97
|
+
connected: live.database,
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (live !== undefined && output !== undefined) yield* assertSchemaIdentity(live, output);
|
|
102
|
+
return live === undefined ? undefined : Unowned(live);
|
|
103
|
+
}),
|
|
104
|
+
|
|
105
|
+
diff: ({ news, output, olds }) => diffPostgresSchema(news, output, olds),
|
|
106
|
+
|
|
107
|
+
reconcile: ({ news, output }) =>
|
|
108
|
+
Effect.gen(function* () {
|
|
109
|
+
const nameRefusal = schemaNameByteRefusal(news.name);
|
|
110
|
+
if (nameRefusal !== undefined) {
|
|
111
|
+
return yield* Effect.fail(
|
|
112
|
+
new PostgresSchemaNameRefused({ name: news.name, ...nameRefusal }),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
yield* assertSchemaTarget(news, output);
|
|
116
|
+
return yield* withPg((pg) => reconcileWithClient(pg, news, output), news.database);
|
|
117
|
+
}),
|
|
118
|
+
|
|
119
|
+
delete: ({ olds, output }) =>
|
|
120
|
+
Effect.gen(function* () {
|
|
121
|
+
// Same missing-database rule as `read`: an absent database is an absent schema, and the
|
|
122
|
+
// delete stays idempotent instead of failing untyped on connect.
|
|
123
|
+
const present = yield* withPg((pg) => databaseExists(pg, olds.database));
|
|
124
|
+
if (!present) return undefined;
|
|
125
|
+
return yield* withPg((pg) => deleteWithClient(pg, olds, output), olds.database);
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
export const PostgresSchemaProvider = () =>
|
|
130
|
+
Provider.succeed(PostgresSchema, postgresSchemaHandlers);
|