@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
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The real `Postgres.Schema` lifecycle handlers, driven directly over the loopback runner
|
|
3
|
+
* transport — no live database, the same wiring `psql-executor.test.ts` uses: the delete path
|
|
4
|
+
* (drop, non-empty refusal, wrong-database proof, the retain default) and `read`'s `Unowned`
|
|
5
|
+
* adopt branding. Plan-time refusals live in `schema-refusals.test.ts`; the fake-driven
|
|
6
|
+
* create/adopt/drop lifecycle in `schema-lifecycle.test.ts`.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, test } from 'bun:test';
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import * as Effect from 'effect/Effect';
|
|
11
|
+
import { postgresRunnerConnection } from './connection.ts';
|
|
12
|
+
import type { PsqlRunner } from './psql-executor.ts';
|
|
13
|
+
import type { PostgresSchemaAttributes, PostgresSchemaProps } from './schema-attrs.ts';
|
|
14
|
+
import { Unowned } from 'alchemy/AdoptPolicy';
|
|
15
|
+
import { postgresSchemaHandlers } from './schema.ts';
|
|
16
|
+
import { PostgresSchemaDropNotEmptyError, PostgresSchemaWrongDatabase } from './schema-errors.ts';
|
|
17
|
+
import { deleteArgs, ok, readArgs, route, router, runnerWith } from './schema-test-kit.ts';
|
|
18
|
+
|
|
19
|
+
const sampleOutput: PostgresSchemaAttributes = {
|
|
20
|
+
name: 'ledger',
|
|
21
|
+
database: 'postgres',
|
|
22
|
+
oid: 1,
|
|
23
|
+
owner: 'tim',
|
|
24
|
+
comment: null,
|
|
25
|
+
};
|
|
26
|
+
const base: PostgresSchemaProps = { name: 'ledger', database: 'postgres', owner: 'tim' };
|
|
27
|
+
|
|
28
|
+
// `read` is optional on the service type; these tests drive the real one.
|
|
29
|
+
if (postgresSchemaHandlers.read === undefined) {
|
|
30
|
+
throw new Error('Postgres.Schema is missing its read handler');
|
|
31
|
+
}
|
|
32
|
+
const schemaRead = postgresSchemaHandlers.read;
|
|
33
|
+
const proof = '[{"database":"postgres"}]';
|
|
34
|
+
|
|
35
|
+
describe('delete handler (runner transport)', () => {
|
|
36
|
+
test('the real handler drops an empty schema over the runner transport, with CASCADE', async () => {
|
|
37
|
+
const { run, stdins, argvs } = runnerWith(
|
|
38
|
+
router({
|
|
39
|
+
probe: '[{"present":1}]',
|
|
40
|
+
schema: '[{"name":"ledger","oid":1,"owner":"tim"}]',
|
|
41
|
+
empty: '[{"empty":true}]',
|
|
42
|
+
proof,
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
await Effect.runPromise(
|
|
46
|
+
postgresSchemaHandlers
|
|
47
|
+
.delete(deleteArgs({ ...base, cascade: true }, sampleOutput))
|
|
48
|
+
.pipe(
|
|
49
|
+
Effect.provide(
|
|
50
|
+
postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
|
|
51
|
+
),
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
expect(stdins.find((s) => s.startsWith('DROP SCHEMA'))).toBe(
|
|
55
|
+
'DROP SCHEMA IF EXISTS "ledger" CASCADE;',
|
|
56
|
+
);
|
|
57
|
+
// One `psql` argv per STATEMENT: probe, proof, ownership re-read, drop. The probe's argv
|
|
58
|
+
// targets the family database; the delete's argvs target the declared one — the `-d` value
|
|
59
|
+
// is the argv's last element.
|
|
60
|
+
expect(argvs.length).toBe(4);
|
|
61
|
+
expect(argvs[0]?.at(-1)).toBe('postgres');
|
|
62
|
+
expect(argvs.slice(1).every((argv) => argv.at(-1) === 'postgres')).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('the real handler drops an empty schema without CASCADE — a plain DROP', async () => {
|
|
66
|
+
const { run, stdins } = runnerWith(
|
|
67
|
+
router({
|
|
68
|
+
probe: '[{"present":1}]',
|
|
69
|
+
schema: '[{"name":"ledger","oid":1,"owner":"tim"}]',
|
|
70
|
+
empty: '[{"empty":true}]',
|
|
71
|
+
proof,
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
await Effect.runPromise(
|
|
75
|
+
postgresSchemaHandlers
|
|
76
|
+
.delete(deleteArgs(base, sampleOutput))
|
|
77
|
+
.pipe(
|
|
78
|
+
Effect.provide(
|
|
79
|
+
postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
expect(stdins.find((s) => s.startsWith('DROP SCHEMA'))).toBe('DROP SCHEMA IF EXISTS "ledger";');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('the real handler refuses a non-empty schema over the runner transport, no DROP issued', async () => {
|
|
87
|
+
const { run, stdins } = runnerWith(
|
|
88
|
+
router({
|
|
89
|
+
probe: '[{"present":1}]',
|
|
90
|
+
schema: '[{"name":"ledger","oid":1,"owner":"tim"}]',
|
|
91
|
+
empty: '[{"empty":false}]',
|
|
92
|
+
proof,
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
const error = await Effect.runPromise(
|
|
96
|
+
Effect.flip(
|
|
97
|
+
postgresSchemaHandlers
|
|
98
|
+
.delete(deleteArgs(base, sampleOutput))
|
|
99
|
+
.pipe(
|
|
100
|
+
Effect.provide(
|
|
101
|
+
postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
|
|
102
|
+
),
|
|
103
|
+
),
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
expect(error).toBeInstanceOf(PostgresSchemaDropNotEmptyError);
|
|
107
|
+
expect(stdins.some((s) => s.startsWith('DROP SCHEMA'))).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('the real handler refuses a wrong connected database before any DROP', async () => {
|
|
111
|
+
// The connection targets `postgres` while the declaration says `agents` — the proof must
|
|
112
|
+
// fail the delete before anything is dropped. The database probe answers PRESENT, so the
|
|
113
|
+
// absent-database short-circuit does not swallow the wrong-database refusal.
|
|
114
|
+
const { run, stdins } = runnerWith(
|
|
115
|
+
router({ probe: '[{"present":1}]', proof: '[{"database":"postgres"}]' }),
|
|
116
|
+
);
|
|
117
|
+
const error = await Effect.runPromise(
|
|
118
|
+
Effect.flip(
|
|
119
|
+
postgresSchemaHandlers
|
|
120
|
+
.delete(
|
|
121
|
+
deleteArgs({ ...base, database: 'agents' }, { ...sampleOutput, database: 'agents' }),
|
|
122
|
+
)
|
|
123
|
+
.pipe(
|
|
124
|
+
Effect.provide(
|
|
125
|
+
postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
|
|
126
|
+
),
|
|
127
|
+
),
|
|
128
|
+
),
|
|
129
|
+
);
|
|
130
|
+
expect(error).toBeInstanceOf(PostgresSchemaWrongDatabase);
|
|
131
|
+
expect(stdins.some((s) => s.startsWith('DROP SCHEMA'))).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('defaultRemovalPolicy is retain — declared on the Resource for Postgres.Schema itself', () => {
|
|
135
|
+
// The engine keeps the default in a closure, not on the constructor, so the source IS the
|
|
136
|
+
// assertable fact — anchored to this resource's own declaration, not any string in the file.
|
|
137
|
+
const source = readFileSync(new URL('./schema.ts', import.meta.url), 'utf8');
|
|
138
|
+
expect(source).toMatch(
|
|
139
|
+
/Resource<PostgresSchema>\('Postgres\.Schema', \{\s*\n\s*defaultRemovalPolicy: 'retain',/,
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('read handler (runner transport)', () => {
|
|
145
|
+
test('answers Unowned for a live row — the adopt-path branding a stack needs', async () => {
|
|
146
|
+
const run: PsqlRunner = ({ stdin }) => {
|
|
147
|
+
const r = route(stdin, {
|
|
148
|
+
probe: '[{"present":1}]',
|
|
149
|
+
schema:
|
|
150
|
+
'[{"name":"ledger","oid":"16384","owner":"tim","comment":null,"database":"postgres"}]',
|
|
151
|
+
proof,
|
|
152
|
+
});
|
|
153
|
+
if (r !== undefined) return ok(r);
|
|
154
|
+
return ok('');
|
|
155
|
+
};
|
|
156
|
+
const result = await Effect.runPromise(
|
|
157
|
+
schemaRead(readArgs(base)).pipe(
|
|
158
|
+
Effect.provide(
|
|
159
|
+
postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
|
|
160
|
+
),
|
|
161
|
+
),
|
|
162
|
+
);
|
|
163
|
+
expect(Unowned.is(result)).toBe(true);
|
|
164
|
+
// The brand is a non-enumerable symbol that bun's toEqual compares; spread it off.
|
|
165
|
+
expect({ ...result }).toEqual({ ...sampleOutput, oid: 16384 });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('refuses with the typed wrong-database tag when the row was read from another database', async () => {
|
|
169
|
+
const run: PsqlRunner = ({ stdin }) =>
|
|
170
|
+
Promise.resolve({
|
|
171
|
+
code: 0,
|
|
172
|
+
stdout:
|
|
173
|
+
route(stdin, {
|
|
174
|
+
probe: '[{"present":1}]',
|
|
175
|
+
schema:
|
|
176
|
+
'[{"name":"ledger","oid":"16384","owner":"tim","comment":null,"database":"agents"}]',
|
|
177
|
+
proof: '[{"database":"agents"}]',
|
|
178
|
+
}) ?? '',
|
|
179
|
+
stderr: '',
|
|
180
|
+
});
|
|
181
|
+
const error = await Effect.runPromise(
|
|
182
|
+
Effect.flip(
|
|
183
|
+
schemaRead(readArgs(base)).pipe(
|
|
184
|
+
Effect.provide(
|
|
185
|
+
postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
|
|
186
|
+
),
|
|
187
|
+
),
|
|
188
|
+
),
|
|
189
|
+
);
|
|
190
|
+
expect(error).toBeInstanceOf(PostgresSchemaWrongDatabase);
|
|
191
|
+
expect((error as PostgresSchemaWrongDatabase).connected).toBe('agents');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Apply repeats plan guards: unresolved Outputs can skip both diff and the adoption probe. */
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
3
|
+
import type { PostgresSchemaAttributes, PostgresSchemaProps } from './schema-attrs.ts';
|
|
4
|
+
import {
|
|
5
|
+
PostgresSchemaDatabaseRefused,
|
|
6
|
+
PostgresSchemaIdentityRefused,
|
|
7
|
+
PostgresSchemaRenameRefused,
|
|
8
|
+
} from './schema-errors.ts';
|
|
9
|
+
|
|
10
|
+
export const assertSchemaTarget = (
|
|
11
|
+
props: PostgresSchemaProps,
|
|
12
|
+
output: PostgresSchemaAttributes | undefined,
|
|
13
|
+
): Effect.Effect<void, PostgresSchemaRenameRefused | PostgresSchemaDatabaseRefused> =>
|
|
14
|
+
Effect.gen(function* () {
|
|
15
|
+
if (output === undefined) return;
|
|
16
|
+
if (props.name !== output.name)
|
|
17
|
+
return yield* Effect.fail(
|
|
18
|
+
new PostgresSchemaRenameRefused({ from: output.name, to: props.name }),
|
|
19
|
+
);
|
|
20
|
+
if (props.database !== output.database)
|
|
21
|
+
return yield* Effect.fail(
|
|
22
|
+
new PostgresSchemaDatabaseRefused({ from: output.database, to: props.database }),
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/** Like Role, refuse recycled names on read as well: drift must not refresh away the proof. */
|
|
27
|
+
export const assertSchemaIdentity = (
|
|
28
|
+
live: PostgresSchemaAttributes,
|
|
29
|
+
output: PostgresSchemaAttributes,
|
|
30
|
+
): Effect.Effect<void, PostgresSchemaIdentityRefused> =>
|
|
31
|
+
live.oid === output.oid
|
|
32
|
+
? Effect.void
|
|
33
|
+
: Effect.fail(
|
|
34
|
+
new PostgresSchemaIdentityRefused({
|
|
35
|
+
schema: live.name,
|
|
36
|
+
storedOid: output.oid,
|
|
37
|
+
liveOid: live.oid,
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reconcile`, `read`, and `drop` for `Postgres.Schema` against `fake-sql.ts`'s recording fake.
|
|
3
|
+
* Tests the greenfield create, comment creation, already-present/drift, owner-missing guard,
|
|
4
|
+
* quoting, the safe-drop rules, and the `current_database()` proof every write path runs first.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, test } from 'bun:test';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
import { SqlError, UnknownError } from 'effect/unstable/sql/SqlError';
|
|
9
|
+
import { makeFakeSql } from './fake-sql.ts';
|
|
10
|
+
import { type PostgresSchemaAttributes, type PostgresSchemaProps } from './schema-attrs.ts';
|
|
11
|
+
import type { PgExecutor } from './database-sql.ts';
|
|
12
|
+
import {
|
|
13
|
+
PostgresSchemaCreateVanished,
|
|
14
|
+
PostgresSchemaDrift,
|
|
15
|
+
PostgresSchemaDropNotEmptyError,
|
|
16
|
+
PostgresSchemaExistsRefused,
|
|
17
|
+
PostgresSchemaOwnerMissing,
|
|
18
|
+
PostgresSchemaWrongDatabase,
|
|
19
|
+
} from './schema-errors.ts';
|
|
20
|
+
import { dropWithClient, readWithClient, reconcileWithClient } from './schema.ts';
|
|
21
|
+
|
|
22
|
+
const run = <A, E>(eff: Effect.Effect<A, E>): Promise<A> => Effect.runPromise(eff);
|
|
23
|
+
const fails = <A, E>(eff: Effect.Effect<A, E>): Promise<E> => Effect.runPromise(Effect.flip(eff));
|
|
24
|
+
|
|
25
|
+
const baseProps: PostgresSchemaProps = { name: 'ledger', database: 'postgres', owner: 'tim' };
|
|
26
|
+
|
|
27
|
+
const liveRow = (over: Partial<PostgresSchemaAttributes> = {}): PostgresSchemaAttributes => ({
|
|
28
|
+
name: 'ledger',
|
|
29
|
+
database: 'postgres',
|
|
30
|
+
oid: 30000,
|
|
31
|
+
owner: 'tim',
|
|
32
|
+
comment: null,
|
|
33
|
+
...over,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('the current_database() proof', () => {
|
|
37
|
+
test('reconcile refuses a declared database the connection does not answer, before any write', async () => {
|
|
38
|
+
const fake = makeFakeSql({ roles: ['tim'] });
|
|
39
|
+
const error = await fails(reconcileWithClient(fake, { ...baseProps, database: 'agents' }));
|
|
40
|
+
expect(error).toBeInstanceOf(PostgresSchemaWrongDatabase);
|
|
41
|
+
const refusal = error as PostgresSchemaWrongDatabase;
|
|
42
|
+
expect(refusal.declared).toBe('agents');
|
|
43
|
+
expect(refusal.connected).toBe('postgres');
|
|
44
|
+
// The refusal fires before the first pg_namespace read, so nothing but the proof ran.
|
|
45
|
+
expect(fake.statements.some((s) => s.text.includes('FROM pg_namespace'))).toBe(false);
|
|
46
|
+
expect(fake.statements.some((s) => s.text.startsWith('CREATE SCHEMA'))).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('drop refuses the same mismatch before any DROP or emptiness check', async () => {
|
|
50
|
+
const fake = makeFakeSql({ schemas: [liveRow()] });
|
|
51
|
+
const error = await fails(dropWithClient(fake, { ...baseProps, database: 'agents' }));
|
|
52
|
+
expect(error).toBeInstanceOf(PostgresSchemaWrongDatabase);
|
|
53
|
+
expect(fake.statements.some((s) => s.text.startsWith('DROP SCHEMA'))).toBe(false);
|
|
54
|
+
expect(fake.schemas.get('ledger')).not.toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('every read row carries the connected database', async () => {
|
|
58
|
+
const fake = makeFakeSql({ schemas: [liveRow()] });
|
|
59
|
+
const row = await run(readWithClient(fake, 'ledger'));
|
|
60
|
+
expect(row?.database).toBe('postgres');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('reconcile: greenfield', () => {
|
|
65
|
+
test('issues exactly one CREATE SCHEMA with an AUTHORIZATION clause', async () => {
|
|
66
|
+
const fake = makeFakeSql({ roles: ['tim'] });
|
|
67
|
+
const attrs = await run(reconcileWithClient(fake, baseProps));
|
|
68
|
+
expect(attrs.name).toBe('ledger');
|
|
69
|
+
expect(attrs.owner).toBe('tim');
|
|
70
|
+
const creates = fake.statements.filter((s) => s.text.startsWith('CREATE SCHEMA'));
|
|
71
|
+
expect(creates.length).toBe(1);
|
|
72
|
+
expect(creates[0]?.text).toBe('CREATE SCHEMA "ledger" AUTHORIZATION "tim"');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('no owner clause falls back to the executing role', async () => {
|
|
76
|
+
const fake = makeFakeSql({ roles: ['postgres'] });
|
|
77
|
+
const attrs = await run(reconcileWithClient(fake, { name: 'plain', database: 'postgres' }));
|
|
78
|
+
expect(attrs.owner).toBe('postgres');
|
|
79
|
+
const create = fake.statements.find((s) => s.text.startsWith('CREATE SCHEMA'));
|
|
80
|
+
expect(create?.text).toBe('CREATE SCHEMA "plain"');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('declared comment is issued with a single COMMENT ON SCHEMA statement', async () => {
|
|
84
|
+
const fake = makeFakeSql({ roles: ['tim'] });
|
|
85
|
+
const attrs = await run(reconcileWithClient(fake, { ...baseProps, comment: 'seat ledger' }));
|
|
86
|
+
expect(attrs.comment).toBe('seat ledger');
|
|
87
|
+
const comments = fake.statements.filter((s) => s.text.startsWith('COMMENT ON SCHEMA'));
|
|
88
|
+
expect(comments.length).toBe(1);
|
|
89
|
+
expect(comments[0]?.text).toBe('COMMENT ON SCHEMA "ledger" IS E\'seat ledger\'');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('a declared empty comment IS "no comment": no statement issued, no drift later', async () => {
|
|
93
|
+
const fake = makeFakeSql({ roles: ['tim'] });
|
|
94
|
+
const attrs = await run(reconcileWithClient(fake, { ...baseProps, comment: '' }));
|
|
95
|
+
expect(attrs.comment).toBeNull();
|
|
96
|
+
expect(fake.statements.some((s) => s.text.startsWith('COMMENT ON SCHEMA'))).toBe(false);
|
|
97
|
+
// Postgres stores IS E'' as NULL, so a second plan must stay a noop — the permanent drift
|
|
98
|
+
// loop an unnormalized '' used to cause. Reads (the current_database() and schema-row
|
|
99
|
+
// SELECT proofs) always run, so compare write statements only.
|
|
100
|
+
const writeCount = () =>
|
|
101
|
+
fake.statements.filter((s) => !/^\s*(SELECT|WITH)\b/i.test(s.text)).length;
|
|
102
|
+
const writesBefore = writeCount();
|
|
103
|
+
await run(reconcileWithClient(fake, { ...baseProps, comment: '' }, attrs));
|
|
104
|
+
expect(writeCount()).toBe(writesBefore);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('refuses before any CREATE when the owner role does not exist', async () => {
|
|
108
|
+
const fake = makeFakeSql({ roles: [] });
|
|
109
|
+
const error = await fails(reconcileWithClient(fake, baseProps));
|
|
110
|
+
expect(error).toBeInstanceOf(PostgresSchemaOwnerMissing);
|
|
111
|
+
expect(fake.statements.some((s) => s.text.startsWith('CREATE SCHEMA'))).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("fails with the vanish tag when the create's re-read finds nothing (S10)", async () => {
|
|
115
|
+
const fake = makeFakeSql({ roles: ['tim'], swallowNextCreateSchema: true });
|
|
116
|
+
const error = await fails(reconcileWithClient(fake, baseProps));
|
|
117
|
+
expect(error).toBeInstanceOf(PostgresSchemaCreateVanished);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('a concurrent creator that won the IF NOT EXISTS race is refused, not adopted', async () => {
|
|
121
|
+
const fake = makeFakeSql({ roles: ['tim'], raceNextCreateSchema: 'someone-else' });
|
|
122
|
+
const error = await fails(reconcileWithClient(fake, baseProps));
|
|
123
|
+
expect(error._tag).toBe('PostgresSchemaExistsRefused');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('hostile names are quoted exactly: embedded quote and a hyphen', async () => {
|
|
127
|
+
const fake = makeFakeSql({ roles: ['tim'] });
|
|
128
|
+
await run(reconcileWithClient(fake, { name: 'a"b-c', database: 'postgres', owner: 'tim' }));
|
|
129
|
+
const create = fake.statements.find((s) => s.text.startsWith('CREATE SCHEMA'));
|
|
130
|
+
expect(create?.text).toBe('CREATE SCHEMA "a""b-c" AUTHORIZATION "tim"');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('reconcile: already present', () => {
|
|
135
|
+
test('adopted output with no drift records zero write statements', async () => {
|
|
136
|
+
const fake = makeFakeSql({ roles: ['tim'], schemas: [liveRow()] });
|
|
137
|
+
const attrs = await run(reconcileWithClient(fake, baseProps, liveRow()));
|
|
138
|
+
expect(attrs).toEqual(liveRow());
|
|
139
|
+
expect(
|
|
140
|
+
fake.statements.every(
|
|
141
|
+
(s) => !s.text.startsWith('CREATE SCHEMA') && !s.text.startsWith('COMMENT ON SCHEMA'),
|
|
142
|
+
),
|
|
143
|
+
).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('an owner mismatch fails with the typed drift tag and issues no ALTER', async () => {
|
|
147
|
+
const fake = makeFakeSql({
|
|
148
|
+
roles: ['tim', 'someone-else'],
|
|
149
|
+
schemas: [liveRow({ owner: 'someone-else' })],
|
|
150
|
+
});
|
|
151
|
+
const error = await fails(reconcileWithClient(fake, baseProps, liveRow()));
|
|
152
|
+
expect(error).toBeInstanceOf(PostgresSchemaDrift);
|
|
153
|
+
expect((error as PostgresSchemaDrift).prop).toBe('owner');
|
|
154
|
+
expect(fake.statements.some((s) => s.text.startsWith('ALTER'))).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('a comment mismatch fails with the typed drift tag naming "comment"', async () => {
|
|
158
|
+
const fake = makeFakeSql({ roles: ['tim'], schemas: [liveRow({ comment: 'old comment' })] });
|
|
159
|
+
const error = await fails(
|
|
160
|
+
reconcileWithClient(fake, { ...baseProps, comment: 'new comment' }, liveRow()),
|
|
161
|
+
);
|
|
162
|
+
expect(error).toBeInstanceOf(PostgresSchemaDrift);
|
|
163
|
+
expect((error as PostgresSchemaDrift).prop).toBe('comment');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('a missing comment declaration does not drift against an existing comment', async () => {
|
|
167
|
+
const fake = makeFakeSql({ roles: ['tim'], schemas: [liveRow({ comment: 'legacy' })] });
|
|
168
|
+
const attrs = await run(reconcileWithClient(fake, baseProps, liveRow()));
|
|
169
|
+
expect(attrs.comment).toBe('legacy');
|
|
170
|
+
expect(fake.statements.some((s) => s.text.startsWith('COMMENT'))).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('pre-existing same-owner schema without output is refused', async () => {
|
|
175
|
+
const fake = makeFakeSql({ schemas: [liveRow()], schemasWithRelations: ['ledger'] });
|
|
176
|
+
const error = await fails(reconcileWithClient(fake, baseProps));
|
|
177
|
+
expect(error).toBeInstanceOf(PostgresSchemaExistsRefused);
|
|
178
|
+
expect(fake.schemas.get('ledger')).toEqual(liveRow());
|
|
179
|
+
expect(fake.relationsIn.has('ledger')).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('read', () => {
|
|
183
|
+
test('answers undefined when the schema is absent', async () => {
|
|
184
|
+
const fake = makeFakeSql();
|
|
185
|
+
expect(await run(readWithClient(fake, 'ledger'))).toBeUndefined();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("answers the live row when present (ownership branding is the caller's job)", async () => {
|
|
189
|
+
const fake = makeFakeSql({ schemas: [liveRow()] });
|
|
190
|
+
expect(await run(readWithClient(fake, 'ledger'))).toEqual(liveRow());
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('drop', () => {
|
|
195
|
+
test('drops an empty schema with cascade false', async () => {
|
|
196
|
+
const fake = makeFakeSql({ schemas: [liveRow()] });
|
|
197
|
+
await run(dropWithClient(fake, baseProps));
|
|
198
|
+
const drops = fake.statements.filter((s) => s.text.startsWith('DROP SCHEMA'));
|
|
199
|
+
expect(drops.length).toBe(1);
|
|
200
|
+
expect(drops[0]?.text).toBe('DROP SCHEMA IF EXISTS "ledger"');
|
|
201
|
+
expect(fake.schemas.get('ledger')).toBeUndefined();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('refuses to drop a non-empty schema with cascade false', async () => {
|
|
205
|
+
const fake = makeFakeSql({ schemas: [liveRow()], schemasWithRelations: ['ledger'] });
|
|
206
|
+
const error = await fails(dropWithClient(fake, baseProps));
|
|
207
|
+
expect(error).toBeInstanceOf(PostgresSchemaDropNotEmptyError);
|
|
208
|
+
expect(fake.schemas.get('ledger')).not.toBeUndefined();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('drops a non-empty schema with cascade true', async () => {
|
|
212
|
+
const fake = makeFakeSql({ schemas: [liveRow()], schemasWithRelations: ['ledger'] });
|
|
213
|
+
await run(dropWithClient(fake, { ...baseProps, cascade: true }));
|
|
214
|
+
const drop = fake.statements.find((s) => s.text.startsWith('DROP SCHEMA'));
|
|
215
|
+
expect(drop?.text).toBe('DROP SCHEMA IF EXISTS "ledger" CASCADE');
|
|
216
|
+
expect(fake.schemas.get('ledger')).toBeUndefined();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('classifies the server 2BP01 refusal as the typed not-empty tag', async () => {
|
|
220
|
+
// The emptiness check said empty, but the server refuses with SQLSTATE 2BP01 — an object
|
|
221
|
+
// kind the check's four catalogs do not cover. Class `2B` is not `42`, so both drivers
|
|
222
|
+
// wrap it as UnknownError with the raw code on the cause; this executor replays that shape.
|
|
223
|
+
const refusing: PgExecutor = {
|
|
224
|
+
unsafe: <A extends object>(text: string) =>
|
|
225
|
+
text.startsWith('SELECT current_database()')
|
|
226
|
+
? Effect.succeed([{ database: 'postgres' }] as unknown as ReadonlyArray<A>)
|
|
227
|
+
: text.includes('AS empty')
|
|
228
|
+
? Effect.succeed([{ empty: true }] as unknown as ReadonlyArray<A>)
|
|
229
|
+
: Effect.fail(
|
|
230
|
+
new SqlError({
|
|
231
|
+
reason: new UnknownError({
|
|
232
|
+
cause: Object.assign(
|
|
233
|
+
new Error('ERROR: 2BP01: dependent objects still exist'),
|
|
234
|
+
{ code: '2BP01' },
|
|
235
|
+
),
|
|
236
|
+
message: 'dependent_objects_still_exist',
|
|
237
|
+
operation: 'DROP SCHEMA',
|
|
238
|
+
}),
|
|
239
|
+
}),
|
|
240
|
+
),
|
|
241
|
+
transaction: () => Effect.void,
|
|
242
|
+
};
|
|
243
|
+
const error = await fails(dropWithClient(refusing, baseProps));
|
|
244
|
+
expect(error).toBeInstanceOf(PostgresSchemaDropNotEmptyError);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `IF NOT EXISTS` race the lifecycle test does not pin: a concurrent creator wins
|
|
3
|
+
* between the not-found SELECT and CREATE SCHEMA. Plain CREATE now refuses it with 42P06.
|
|
4
|
+
*
|
|
5
|
+
* Two shapes the existing race test skips. A declared comment must not `COMMENT ON` that
|
|
6
|
+
* row before the refusal — the kit role is superuser, so the comment would land and
|
|
7
|
+
* a failed effect would not roll it back. An omitted `owner` must not adopt the foreign
|
|
8
|
+
* row: a fresh CREATE without AUTHORIZATION is owned by current_user. Even a matching owner
|
|
9
|
+
* now fails with PostgresSchemaExistsRefused, never a row a later destroy could drop.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, expect, test } from 'bun:test';
|
|
12
|
+
import * as Effect from 'effect/Effect';
|
|
13
|
+
import { makeFakeSql } from './fake-sql.ts';
|
|
14
|
+
import { PostgresSchemaExistsRefused } from './schema-errors.ts';
|
|
15
|
+
import { reconcileWithClient } from './schema.ts';
|
|
16
|
+
|
|
17
|
+
const fails = <A, E>(eff: Effect.Effect<A, E>): Promise<E> => Effect.runPromise(Effect.flip(eff));
|
|
18
|
+
|
|
19
|
+
describe('IF NOT EXISTS race, the shapes the owner-only test skips', () => {
|
|
20
|
+
test('a race plus a declared comment issues zero COMMENT ON', async () => {
|
|
21
|
+
const fake = makeFakeSql({ roles: ['seat_role'], raceNextCreateSchema: 'other_role' });
|
|
22
|
+
const error = await fails(
|
|
23
|
+
reconcileWithClient(fake, {
|
|
24
|
+
name: 'ledger',
|
|
25
|
+
database: 'postgres',
|
|
26
|
+
owner: 'seat_role',
|
|
27
|
+
comment: 'seat ledger',
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
expect(error).toBeInstanceOf(PostgresSchemaExistsRefused);
|
|
31
|
+
expect(fake.statements.some((s) => s.text.startsWith('COMMENT ON SCHEMA'))).toBe(false);
|
|
32
|
+
// The foreign row is untouched: comment still absent, owner still the racer's.
|
|
33
|
+
expect(fake.schemas.get('ledger')?.comment).toBeNull();
|
|
34
|
+
expect(fake.schemas.get('ledger')?.owner).toBe('other_role');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('a race plus an omitted owner refuses takeover and does not return the foreign row', async () => {
|
|
38
|
+
// `current_user` is `seat_role`, not the fake's historical `'postgres'` default, so a
|
|
39
|
+
// hardcoded owner cannot satisfy `declared`.
|
|
40
|
+
const fake = makeFakeSql({
|
|
41
|
+
roles: ['seat_role'],
|
|
42
|
+
raceNextCreateSchema: 'other_role',
|
|
43
|
+
currentUser: 'seat_role',
|
|
44
|
+
});
|
|
45
|
+
const error = await fails(reconcileWithClient(fake, { name: 'ledger', database: 'postgres' }));
|
|
46
|
+
expect(error).toBeInstanceOf(PostgresSchemaExistsRefused);
|
|
47
|
+
expect(fake.schemas.get('ledger')?.owner).toBe('other_role');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('a later plan sees the foreign row as already present and still refuses it', async () => {
|
|
51
|
+
// The failed race leaves the racer's schema in place. The next reconcile takes the
|
|
52
|
+
// already-present path, which must refuse too — otherwise that plan adopts the row.
|
|
53
|
+
const fake = makeFakeSql({
|
|
54
|
+
currentUser: 'seat_role',
|
|
55
|
+
schemas: [
|
|
56
|
+
{
|
|
57
|
+
name: 'ledger',
|
|
58
|
+
database: 'postgres',
|
|
59
|
+
oid: 30000,
|
|
60
|
+
owner: 'other_role',
|
|
61
|
+
comment: null,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
const error = await fails(reconcileWithClient(fake, { name: 'ledger', database: 'postgres' }));
|
|
66
|
+
expect(error).toBeInstanceOf(PostgresSchemaExistsRefused);
|
|
67
|
+
expect(fake.statements.some((s) => s.text.startsWith('CREATE SCHEMA'))).toBe(false);
|
|
68
|
+
expect(fake.statements.some((s) => s.text.startsWith('COMMENT ON SCHEMA'))).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/** Schema reconcile: prove the stored target and identity before asserting a live row. */
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
3
|
+
import type { SqlError } from 'effect/unstable/sql/SqlError';
|
|
4
|
+
import { type PgExecutor, roleExists } from './database-sql.ts';
|
|
5
|
+
import {
|
|
6
|
+
type PostgresSchemaAttributes,
|
|
7
|
+
type PostgresSchemaProps,
|
|
8
|
+
normalizedComment,
|
|
9
|
+
} from './schema-attrs.ts';
|
|
10
|
+
import { assertDatabase, assertLive, assertOwner } from './schema-assert.ts';
|
|
11
|
+
import { assertSchemaIdentity, assertSchemaTarget } from './schema-identity.ts';
|
|
12
|
+
import {
|
|
13
|
+
buildCommentSchemaSql,
|
|
14
|
+
buildCreateSchemaSql,
|
|
15
|
+
currentUser,
|
|
16
|
+
isDuplicateSchemaError,
|
|
17
|
+
selectSchema,
|
|
18
|
+
} from './schema-sql.ts';
|
|
19
|
+
import {
|
|
20
|
+
PostgresSchemaCreateVanished,
|
|
21
|
+
type PostgresSchemaDatabaseRefused,
|
|
22
|
+
type PostgresSchemaDrift,
|
|
23
|
+
PostgresSchemaExistsRefused,
|
|
24
|
+
type PostgresSchemaIdentityRefused,
|
|
25
|
+
PostgresSchemaOwnerMissing,
|
|
26
|
+
type PostgresSchemaRenameRefused,
|
|
27
|
+
type PostgresSchemaWrongDatabase,
|
|
28
|
+
} from './schema-errors.ts';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The core of `reconcile`, against any {@link PgExecutor} — the real pooled client through
|
|
32
|
+
* `withPg`, or `fake-sql.ts`'s recording fake in tests.
|
|
33
|
+
*/
|
|
34
|
+
export const reconcileWithClient = (
|
|
35
|
+
pg: PgExecutor,
|
|
36
|
+
props: PostgresSchemaProps,
|
|
37
|
+
output?: PostgresSchemaAttributes,
|
|
38
|
+
): Effect.Effect<
|
|
39
|
+
PostgresSchemaAttributes,
|
|
40
|
+
| PostgresSchemaWrongDatabase
|
|
41
|
+
| PostgresSchemaOwnerMissing
|
|
42
|
+
| PostgresSchemaDrift
|
|
43
|
+
| PostgresSchemaCreateVanished
|
|
44
|
+
| PostgresSchemaExistsRefused
|
|
45
|
+
| PostgresSchemaIdentityRefused
|
|
46
|
+
| PostgresSchemaRenameRefused
|
|
47
|
+
| PostgresSchemaDatabaseRefused
|
|
48
|
+
| SqlError
|
|
49
|
+
> =>
|
|
50
|
+
Effect.gen(function* () {
|
|
51
|
+
yield* assertSchemaTarget(props, output);
|
|
52
|
+
yield* assertDatabase(props, pg);
|
|
53
|
+
// Only an omitted owner needs the session role: a declared owner is the comparison itself.
|
|
54
|
+
const executingRole = props.owner !== undefined ? props.owner : yield* currentUser(pg);
|
|
55
|
+
const observed = yield* selectSchema(pg, props.name);
|
|
56
|
+
if (observed === undefined) {
|
|
57
|
+
if (props.owner !== undefined) {
|
|
58
|
+
const ownerPresent = yield* roleExists(pg, props.owner);
|
|
59
|
+
if (!ownerPresent) {
|
|
60
|
+
return yield* Effect.fail(
|
|
61
|
+
new PostgresSchemaOwnerMissing({ schema: props.name, owner: props.owner }),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// REL_18_6 pg_namespace.c: duplicate_schema is 42P06. Never turn a race into adoption.
|
|
66
|
+
const createRefusal = (error: SqlError): PostgresSchemaExistsRefused | SqlError =>
|
|
67
|
+
isDuplicateSchemaError(error)
|
|
68
|
+
? new PostgresSchemaExistsRefused({ schema: props.name, database: props.database })
|
|
69
|
+
: error;
|
|
70
|
+
yield* pg.unsafe(buildCreateSchemaSql(props)).pipe(
|
|
71
|
+
Effect.asVoid,
|
|
72
|
+
Effect.catchTag('SqlError', (error) => Effect.fail(createRefusal(error))),
|
|
73
|
+
);
|
|
74
|
+
// Never trust the write's own report (S10): re-read what the server actually stored.
|
|
75
|
+
const created = yield* selectSchema(pg, props.name);
|
|
76
|
+
if (created === undefined) {
|
|
77
|
+
return yield* Effect.fail(new PostgresSchemaCreateVanished({ schema: props.name }));
|
|
78
|
+
}
|
|
79
|
+
// Owner before COMMENT ON (schema-assert.ts): preserve the post-write assertion too;
|
|
80
|
+
// duplicate CREATE already refused the race before this re-read.
|
|
81
|
+
yield* assertOwner(props, created, executingRole);
|
|
82
|
+
const comment = normalizedComment(props.comment);
|
|
83
|
+
if (comment !== undefined && comment !== (created.comment ?? undefined)) {
|
|
84
|
+
yield* pg.unsafe(buildCommentSchemaSql(props.name, comment)).pipe(Effect.asVoid);
|
|
85
|
+
const commented = yield* selectSchema(pg, props.name);
|
|
86
|
+
if (commented === undefined) {
|
|
87
|
+
return yield* Effect.fail(new PostgresSchemaCreateVanished({ schema: props.name }));
|
|
88
|
+
}
|
|
89
|
+
return yield* assertLive(props, commented, executingRole);
|
|
90
|
+
}
|
|
91
|
+
return yield* assertLive(props, created, executingRole);
|
|
92
|
+
}
|
|
93
|
+
if (output === undefined) {
|
|
94
|
+
return yield* Effect.fail(
|
|
95
|
+
new PostgresSchemaExistsRefused({ schema: props.name, database: props.database }),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
yield* assertSchemaIdentity(observed, output);
|
|
99
|
+
return yield* assertLive(props, observed, executingRole);
|
|
100
|
+
});
|