@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,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The write-semantics half of the `Postgres.Grants` test fake, extracted from
|
|
3
|
+
* `fake-grants-sql.ts` (the factory and the statement parser) to keep both files under the
|
|
4
|
+
* cap: how one `GRANT`/`REVOKE` edits the in-memory ACL with the server's rule that a
|
|
5
|
+
* superuser, the owner itself, or a member of the owning role performs the command AS the
|
|
6
|
+
* owner (`select_best_grantor`, acl.c@REL_18_6; `revoke.sgml@REL_18_6`).
|
|
7
|
+
*
|
|
8
|
+
* ★ THE EFFECTIVE GRANTOR. `effectiveGrantor` names the role a write is recorded as: the
|
|
9
|
+
* owner when the executor acts as owner, otherwise the executor. A relation (table, column)
|
|
10
|
+
* is owned by its `relowner`; a schema by its `nspowner`; a default ACL has no owner, so the
|
|
11
|
+
* executor's own grants are the only ones its writes touch.
|
|
12
|
+
* ★ `GRANT` records the words on the effective grantor's entry, upgrading a base word to its
|
|
13
|
+
* `*` (WITH GRANT OPTION) form rather than keeping both — one `aclitem` per pair.
|
|
14
|
+
* ★ `REVOKE ALL` removes the effective grantor's entry only: a third grantor's grant to the
|
|
15
|
+
* same grantee SURVIVES silently (the survival `PostgresGrantsRepairRefused` surfaces), and
|
|
16
|
+
* a grantee that re-granted onward fails like the server without CASCADE (`2BP01`).
|
|
17
|
+
*/
|
|
18
|
+
import { SqlError, SqlSyntaxError } from 'effect/unstable/sql/SqlError';
|
|
19
|
+
import type { AclObject } from './fake-grants-parse.ts';
|
|
20
|
+
import { type FakeGrantsModel, revokesAsOwner } from './fake-grants-read.ts';
|
|
21
|
+
|
|
22
|
+
const sameObject = (a: AclObject, b: AclObject): boolean =>
|
|
23
|
+
a.schema === b.schema &&
|
|
24
|
+
a.table === b.table &&
|
|
25
|
+
a.column === b.column &&
|
|
26
|
+
a.defaultFor === b.defaultFor;
|
|
27
|
+
|
|
28
|
+
/** The role a `GRANT`/`REVOKE` from the executor on `object` is performed AS (see the header). */
|
|
29
|
+
export const effectiveGrantor = (model: FakeGrantsModel, object: AclObject): string => {
|
|
30
|
+
if (object.table !== undefined) {
|
|
31
|
+
const owner = model.tableOwners.get(`${object.schema}\u0000${object.table}`) ?? model.executor;
|
|
32
|
+
return revokesAsOwner(model, owner) ? owner : model.executor;
|
|
33
|
+
}
|
|
34
|
+
if (object.defaultFor === undefined) {
|
|
35
|
+
const owner = model.schemaOwners.get(object.schema) ?? model.executor;
|
|
36
|
+
return revokesAsOwner(model, owner) ? owner : model.executor;
|
|
37
|
+
}
|
|
38
|
+
return model.executor;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Add words to the (object, grantee, effective-grantor) entry, upgrading a base word to its
|
|
42
|
+
* `*` form rather than keeping both. */
|
|
43
|
+
export const grantWords = (
|
|
44
|
+
model: FakeGrantsModel,
|
|
45
|
+
object: AclObject,
|
|
46
|
+
grantee: string,
|
|
47
|
+
words: ReadonlyArray<string>,
|
|
48
|
+
option: boolean,
|
|
49
|
+
): void => {
|
|
50
|
+
const grantor = effectiveGrantor(model, object);
|
|
51
|
+
const acl = model.acl;
|
|
52
|
+
let entry = acl.find(
|
|
53
|
+
(e) => sameObject(e.object, object) && e.grantee === grantee && e.grantor === grantor,
|
|
54
|
+
);
|
|
55
|
+
if (entry === undefined) {
|
|
56
|
+
entry = { object, grantee, grantor, words: [] };
|
|
57
|
+
acl.push(entry);
|
|
58
|
+
}
|
|
59
|
+
for (const word of words) {
|
|
60
|
+
const base = word.replace(/\*$/, '');
|
|
61
|
+
entry.words = entry.words.filter((w) => w.replace(/\*$/, '') !== base);
|
|
62
|
+
entry.words.push(option ? `${word}*` : word);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Remove the effective grantor's grants for (object, grantee); a third grantor's entry
|
|
67
|
+
* survives by the keying, and a grantee that re-granted onward fails without CASCADE. */
|
|
68
|
+
export const revoke = (
|
|
69
|
+
model: FakeGrantsModel,
|
|
70
|
+
object: AclObject,
|
|
71
|
+
grantee: string,
|
|
72
|
+
operation: string,
|
|
73
|
+
): SqlError | undefined => {
|
|
74
|
+
const grantor = effectiveGrantor(model, object);
|
|
75
|
+
const acl = model.acl;
|
|
76
|
+
const own = acl.filter(
|
|
77
|
+
(e) => sameObject(e.object, object) && e.grantee === grantee && e.grantor === grantor,
|
|
78
|
+
);
|
|
79
|
+
if (own.length > 0 && acl.some((e) => sameObject(e.object, object) && e.grantor === grantee)) {
|
|
80
|
+
return new SqlError({
|
|
81
|
+
reason: new SqlSyntaxError({
|
|
82
|
+
cause: Object.assign(new Error('dependent privileges exist'), { code: '2BP01' }),
|
|
83
|
+
message: 'dependent privileges exist',
|
|
84
|
+
operation,
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
for (const entry of own) acl.splice(acl.indexOf(entry), 1);
|
|
89
|
+
return undefined;
|
|
90
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `Postgres.Role` half of `fake-sql.ts`: the in-memory `pg_roles` / `pg_auth_members` catalog
|
|
3
|
+
* and every statement branch that touches it, split out so each fake file serves one family.
|
|
4
|
+
*
|
|
5
|
+
* ⛔ IT PARSES ITS OWN OUTPUT, NOT SQL IN GENERAL (the rule `fake-sql.ts` documents). Every
|
|
6
|
+
* parser here understands exactly the text `role-sql.ts` produces — quoted with `quoteIdent` /
|
|
7
|
+
* `quoteStringLiteral` — because that is the only `CREATE ROLE`, per-field `ALTER ROLE`,
|
|
8
|
+
* `GRANT`, `REVOKE` and `DROP ROLE` this family ever issues. A general SQL parser would hide a
|
|
9
|
+
* quoting bug instead of tripping over it.
|
|
10
|
+
* ★ THE DEDICATED PASSWORD `ALTER ROLE … WITH PASSWORD` IS RECORDED, NEVER PARSED: `pg_roles`
|
|
11
|
+
* answers no password value (S25), so the fake stores none either — the seal lives only in the
|
|
12
|
+
* attributes a test seeds or a reconcile returns.
|
|
13
|
+
*/
|
|
14
|
+
import * as Effect from 'effect/Effect';
|
|
15
|
+
import type { SqlError } from 'effect/unstable/sql/SqlError';
|
|
16
|
+
import type { PostgresRoleAttributes } from './role-attrs.ts';
|
|
17
|
+
import { unquoteIdent, unquoteLiteral } from './fake-sql-quote.ts';
|
|
18
|
+
|
|
19
|
+
/** The role catalog `fake-sql.ts` constructs and shares with its callers: seeded by a test, then
|
|
20
|
+
* mutated by the statements this module applies. */
|
|
21
|
+
export interface FakeRoleState {
|
|
22
|
+
/** Bare role names, backing the `roleExists` check the database family's owner probe uses. */
|
|
23
|
+
readonly roleNames: Set<string>;
|
|
24
|
+
readonly roleRows: Map<string, PostgresRoleAttributes>;
|
|
25
|
+
/** `member\0parent\0grantor` triples backing `pg_auth_members` (`''` grantor = unresolvable
|
|
26
|
+
* grantor); `GRANT`/`REVOKE` mutate it. */
|
|
27
|
+
readonly memberships: Set<string>;
|
|
28
|
+
/** Options a name-only compare would hide, keyed `member\0parent\0grantor`. */
|
|
29
|
+
readonly membershipOptions: Map<
|
|
30
|
+
string,
|
|
31
|
+
{ readonly admin: boolean; readonly set: boolean; readonly inherit?: boolean }
|
|
32
|
+
>;
|
|
33
|
+
/** Hands out the next `oid` a real cluster would assign. */
|
|
34
|
+
nextOid(): number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type RoleRow = Omit<PostgresRoleAttributes, 'memberOf' | 'passwordSeal'>;
|
|
38
|
+
|
|
39
|
+
/** Pull every field back out of exactly the text `buildCreateRoleSql` writes. The password never
|
|
40
|
+
* rides a `CREATE ROLE`, so this parses no secret. */
|
|
41
|
+
const parseCreateRole = (text: string): RoleRow => {
|
|
42
|
+
const name = /^CREATE ROLE "((?:[^"]|"")*)" WITH /.exec(text);
|
|
43
|
+
const login = /(?:^| )(NOLOGIN|LOGIN)(?: |$)/.exec(text);
|
|
44
|
+
const inherit = /(?:^| )(NOINHERIT|INHERIT)(?: |$)/.exec(text);
|
|
45
|
+
const limit = /CONNECTION LIMIT (-?\d+)/.exec(text);
|
|
46
|
+
const until = /VALID UNTIL E'((?:[^']|'')*)'/.exec(text);
|
|
47
|
+
if (name === null) {
|
|
48
|
+
throw new Error(`fake-sql: could not parse a generated CREATE ROLE statement: ${text}`);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
name: unquoteIdent(name[1] as string),
|
|
52
|
+
oid: 0,
|
|
53
|
+
login: login?.[1] !== 'NOLOGIN',
|
|
54
|
+
inherit: inherit?.[1] !== 'NOINHERIT',
|
|
55
|
+
connectionLimit: limit === null ? -1 : Number(limit[1]),
|
|
56
|
+
validUntil: until === null ? null : unquoteLiteral(until[1] as string),
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** A per-field `ALTER ROLE … WITH …`, parsed back into the one scalar it changes. */
|
|
61
|
+
const parseAlterRole = (text: string): Partial<RoleRow> => {
|
|
62
|
+
const name = /^ALTER ROLE "((?:[^"]|"")*)" WITH /.exec(text);
|
|
63
|
+
if (name === null) {
|
|
64
|
+
throw new Error(`fake-sql: could not parse a generated ALTER ROLE statement: ${text}`);
|
|
65
|
+
}
|
|
66
|
+
const who = unquoteIdent(name[1] as string);
|
|
67
|
+
const until = /VALID UNTIL E'((?:[^']|'')*)'/.exec(text);
|
|
68
|
+
const limit = /CONNECTION LIMIT (-?\d+)/.exec(text);
|
|
69
|
+
const login = /(?:^| )(NOLOGIN|LOGIN)(?: |$)/.exec(text);
|
|
70
|
+
const inherit = /(?:^| )(NOINHERIT|INHERIT)(?: |$)/.exec(text);
|
|
71
|
+
// The dedicated password statement carries no catalog-visible field; it is recorded by text
|
|
72
|
+
// only and never parsed back, because pg_roles answers no password value (S25).
|
|
73
|
+
if (/ PASSWORD /.test(text)) return { name: who };
|
|
74
|
+
if (until !== null) return { name: who, validUntil: unquoteLiteral(until[1] as string) };
|
|
75
|
+
if (limit !== null) return { name: who, connectionLimit: Number(limit[1]) };
|
|
76
|
+
if (login !== null) return { name: who, login: login[1] !== 'NOLOGIN' };
|
|
77
|
+
if (inherit !== null) return { name: who, inherit: inherit[1] !== 'NOINHERIT' };
|
|
78
|
+
throw new Error(`fake-sql: unrecognised ALTER ROLE field: ${text}`);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const EMPTY_ROLE: RoleRow = {
|
|
82
|
+
name: '',
|
|
83
|
+
oid: 0,
|
|
84
|
+
login: false,
|
|
85
|
+
connectionLimit: -1,
|
|
86
|
+
inherit: true,
|
|
87
|
+
validUntil: null,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Every `Postgres.Role` statement and both role reads, applied against the fake catalog. Answers
|
|
92
|
+
* the effect the statement produces — `undefined` when the text belongs to another family, so
|
|
93
|
+
* `fake-sql.ts` can fall through to its own branches.
|
|
94
|
+
*/
|
|
95
|
+
export const applyRoleStatement = <A extends object>(
|
|
96
|
+
state: FakeRoleState,
|
|
97
|
+
text: string,
|
|
98
|
+
params: ReadonlyArray<unknown>,
|
|
99
|
+
): Effect.Effect<ReadonlyArray<A>, SqlError> | undefined => {
|
|
100
|
+
if (text.includes('FROM pg_auth_members')) {
|
|
101
|
+
const member = params[0] as string;
|
|
102
|
+
const rows = [...state.memberships]
|
|
103
|
+
.filter((key) => key.split('\0')[0] === member)
|
|
104
|
+
.map((key) => {
|
|
105
|
+
const parts = key.split('\0');
|
|
106
|
+
const options = state.membershipOptions.get(key);
|
|
107
|
+
return {
|
|
108
|
+
parent: parts[1] as string,
|
|
109
|
+
grantor: parts[2] === undefined || parts[2] === '' ? null : (parts[2] as string),
|
|
110
|
+
admin: options?.admin === true,
|
|
111
|
+
set: options?.set === true,
|
|
112
|
+
inherit: options?.inherit === true,
|
|
113
|
+
};
|
|
114
|
+
})
|
|
115
|
+
.sort(
|
|
116
|
+
(a, b) =>
|
|
117
|
+
a.parent.localeCompare(b.parent) || (a.grantor ?? '').localeCompare(b.grantor ?? ''),
|
|
118
|
+
);
|
|
119
|
+
return Effect.succeed(rows as unknown as ReadonlyArray<A>);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (text.includes('FROM pg_roles r')) {
|
|
123
|
+
const name = params[0] as string;
|
|
124
|
+
const row = state.roleRows.get(name);
|
|
125
|
+
// pg_roles answers no password value (S25): even a seeded seal is not read back — state
|
|
126
|
+
// holds it, and a reconcile receives it explicitly, the same way the real server answers.
|
|
127
|
+
if (row === undefined) return Effect.succeed([] as unknown as ReadonlyArray<A>);
|
|
128
|
+
const { passwordSeal: _stateOnly, ...catalog } = row;
|
|
129
|
+
return Effect.succeed([catalog] as unknown as ReadonlyArray<A>);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (text.startsWith('CREATE ROLE')) {
|
|
133
|
+
const parsed = { ...EMPTY_ROLE, ...parseCreateRole(text), oid: state.nextOid() };
|
|
134
|
+
state.roleRows.set(parsed.name, { ...parsed, memberOf: [], passwordSeal: '' });
|
|
135
|
+
return Effect.succeed([] as unknown as ReadonlyArray<A>);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (text.startsWith('ALTER ROLE')) {
|
|
139
|
+
const parsed = parseAlterRole(text);
|
|
140
|
+
const row = state.roleRows.get(parsed.name ?? '');
|
|
141
|
+
if (row === undefined) {
|
|
142
|
+
throw new Error(`fake-sql: ALTER ROLE on absent role "${String(parsed.name)}"`);
|
|
143
|
+
}
|
|
144
|
+
state.roleRows.set(row.name, { ...row, ...parsed });
|
|
145
|
+
return Effect.succeed([] as unknown as ReadonlyArray<A>);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (text.startsWith('GRANT')) {
|
|
149
|
+
const m =
|
|
150
|
+
/^GRANT "((?:[^"]|"")*)" TO "((?:[^"]|"")*)"(?: WITH ADMIN FALSE, SET FALSE| WITH SET FALSE)?$/.exec(
|
|
151
|
+
text,
|
|
152
|
+
);
|
|
153
|
+
if (m === null) {
|
|
154
|
+
throw new Error(`fake-sql: could not parse a generated GRANT statement: ${text}`);
|
|
155
|
+
}
|
|
156
|
+
// The session issues the statement as the bootstrap superuser, so the grant it (re)makes
|
|
157
|
+
// is keyed on grantor `postgres` — `AddRoleMems` updates only that (role, member, grantor)
|
|
158
|
+
// tuple (`user.c@REL_18_6`). A later `REVOKE … GRANTED BY "postgres"` finds the same row.
|
|
159
|
+
const parent = unquoteIdent(m[1] as string);
|
|
160
|
+
const member = unquoteIdent(m[2] as string);
|
|
161
|
+
if (!state.roleRows.has(parent) && !state.roleNames.has(parent)) {
|
|
162
|
+
throw new Error(`fake-sql: GRANT parent "${parent}" does not exist`);
|
|
163
|
+
}
|
|
164
|
+
if (!state.roleRows.has(member) && !state.roleNames.has(member)) {
|
|
165
|
+
throw new Error(`fake-sql: GRANT member "${member}" does not exist`);
|
|
166
|
+
}
|
|
167
|
+
const key = `${member}\0${parent}\0postgres`;
|
|
168
|
+
state.memberships.add(key);
|
|
169
|
+
// `grant.sgml` (REL_18_6): altering an existing membership retains every option the new
|
|
170
|
+
// GRANT omits, so only the full-options form clears ADMIN; `WITH SET FALSE` alone keeps a
|
|
171
|
+
// current ADMIN. A bare `GRANT` (never issued by this family) leaves options untouched.
|
|
172
|
+
const options = state.membershipOptions.get(key) ?? {
|
|
173
|
+
admin: false,
|
|
174
|
+
set: false,
|
|
175
|
+
inherit: state.roleRows.get(member)?.inherit ?? true,
|
|
176
|
+
};
|
|
177
|
+
if (text.endsWith('WITH ADMIN FALSE, SET FALSE')) {
|
|
178
|
+
state.membershipOptions.set(key, { ...options, admin: false, set: false });
|
|
179
|
+
} else if (text.endsWith('WITH SET FALSE')) {
|
|
180
|
+
state.membershipOptions.set(key, { ...options, set: false });
|
|
181
|
+
}
|
|
182
|
+
return Effect.succeed([] as unknown as ReadonlyArray<A>);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (text.startsWith('REVOKE')) {
|
|
186
|
+
const option =
|
|
187
|
+
/^REVOKE (ADMIN|SET|INHERIT) OPTION FOR "((?:[^"]|"")*)" FROM "((?:[^"]|"")*)" GRANTED BY "((?:[^"]|"")*)" RESTRICT$/.exec(
|
|
188
|
+
text,
|
|
189
|
+
);
|
|
190
|
+
if (option !== null) {
|
|
191
|
+
const key = `${unquoteIdent(option[3] as string)}\0${unquoteIdent(option[2] as string)}\0${unquoteIdent(option[4] as string)}`;
|
|
192
|
+
const options = state.membershipOptions.get(key) ?? { admin: false, set: false };
|
|
193
|
+
const next = { ...options, [(option[1] as string).toLowerCase()]: false };
|
|
194
|
+
if (next.admin || next.set || next.inherit !== undefined)
|
|
195
|
+
state.membershipOptions.set(key, next);
|
|
196
|
+
else state.membershipOptions.delete(key);
|
|
197
|
+
return Effect.succeed([] as unknown as ReadonlyArray<A>);
|
|
198
|
+
}
|
|
199
|
+
const granted =
|
|
200
|
+
/^REVOKE "((?:[^"]|"")*)" FROM "((?:[^"]|"")*)" GRANTED BY "((?:[^"]|"")*)"$/.exec(text);
|
|
201
|
+
if (granted !== null) {
|
|
202
|
+
// Only the grantor's own row goes; other grantors' rows for the same parent stand.
|
|
203
|
+
const key = `${unquoteIdent(granted[2] as string)}\0${unquoteIdent(granted[1] as string)}\0${unquoteIdent(granted[3] as string)}`;
|
|
204
|
+
state.memberships.delete(key);
|
|
205
|
+
state.membershipOptions.delete(key);
|
|
206
|
+
return Effect.succeed([] as unknown as ReadonlyArray<A>);
|
|
207
|
+
}
|
|
208
|
+
const plain = /^REVOKE "((?:[^"]|"")*)" FROM "((?:[^"]|"")*)"$/.exec(text);
|
|
209
|
+
if (plain === null) {
|
|
210
|
+
throw new Error(`fake-sql: could not parse a generated REVOKE statement: ${text}`);
|
|
211
|
+
}
|
|
212
|
+
// `revoke.sgml`: a plain REVOKE removes only grants the session's grantor made and warns
|
|
213
|
+
// on the rest — a no-op against other grantors' rows. The session's grantor is
|
|
214
|
+
// `postgres`, so the key it can touch is `member\0parent\0postgres`.
|
|
215
|
+
const key = `${unquoteIdent(plain[2] as string)}\0${unquoteIdent(plain[1] as string)}\0postgres`;
|
|
216
|
+
state.memberships.delete(key);
|
|
217
|
+
state.membershipOptions.delete(key);
|
|
218
|
+
return Effect.succeed([] as unknown as ReadonlyArray<A>);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (text.startsWith('DROP ROLE')) {
|
|
222
|
+
const m = /^DROP ROLE IF EXISTS "((?:[^"]|"")*)"$/.exec(text);
|
|
223
|
+
if (m === null) {
|
|
224
|
+
throw new Error(`fake-sql: could not parse a generated DROP ROLE statement: ${text}`);
|
|
225
|
+
}
|
|
226
|
+
const name = unquoteIdent(m[1] as string);
|
|
227
|
+
state.roleRows.delete(name);
|
|
228
|
+
state.roleNames.delete(name);
|
|
229
|
+
for (const key of state.memberships) {
|
|
230
|
+
const parts = key.split('\0');
|
|
231
|
+
if (parts[0] === name || parts[1] === name) {
|
|
232
|
+
state.memberships.delete(key);
|
|
233
|
+
state.membershipOptions.delete(key);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return Effect.succeed([] as unknown as ReadonlyArray<A>);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return undefined;
|
|
240
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test';
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
3
|
+
import { makeFakeSql } from './fake-sql.ts';
|
|
4
|
+
|
|
5
|
+
const row = { name: 'ledger', database: 'postgres', owner: 'old_owner', oid: 42, comment: 'keep' };
|
|
6
|
+
|
|
7
|
+
test('PG18 IF NOT EXISTS preserves oid, owner, comment and contents', async () => {
|
|
8
|
+
const fake = makeFakeSql({ schemas: [row], schemasWithRelations: ['ledger'] });
|
|
9
|
+
await Effect.runPromise(
|
|
10
|
+
fake.unsafe('CREATE SCHEMA IF NOT EXISTS "ledger" AUTHORIZATION "new_owner"'),
|
|
11
|
+
);
|
|
12
|
+
expect(fake.schemas.get('ledger')).toEqual(row);
|
|
13
|
+
expect(fake.relationsIn.has('ledger')).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('PG18 plain CREATE on an existing schema raises 42P06', async () => {
|
|
17
|
+
const fake = makeFakeSql({ schemas: [row] });
|
|
18
|
+
const error = await Effect.runPromise(Effect.flip(fake.unsafe('CREATE SCHEMA "ledger"')));
|
|
19
|
+
expect(error.reason.cause).toMatchObject({ code: '42P06' });
|
|
20
|
+
expect(fake.schemas.get('ledger')).toEqual(row);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('PG18 nonempty DROP without CASCADE raises 2BP01 without deleting anything', async () => {
|
|
24
|
+
const fake = makeFakeSql({ schemas: [row], schemasWithRelations: ['ledger'] });
|
|
25
|
+
const error = await Effect.runPromise(Effect.flip(fake.unsafe('DROP SCHEMA IF EXISTS "ledger"')));
|
|
26
|
+
expect(error.reason.cause).toMatchObject({ code: '2BP01' });
|
|
27
|
+
expect(fake.schemas.get('ledger')).toEqual(row);
|
|
28
|
+
expect(fake.relationsIn.has('ledger')).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('PG18 bootstrap seeds public owned by pg_database_owner', () => {
|
|
32
|
+
expect(makeFakeSql().schemas.get('public')).toMatchObject({
|
|
33
|
+
oid: 2200,
|
|
34
|
+
owner: 'pg_database_owner',
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `Postgres.Schema` half of `fake-sql.ts`: the in-memory `pg_namespace` catalog and every
|
|
3
|
+
* statement branch that touches it, split out so each fake file serves one family.
|
|
4
|
+
*
|
|
5
|
+
* ⛔ IT PARSES ITS OWN OUTPUT, NOT SQL IN GENERAL (the rule `fake-sql.ts` documents). Every
|
|
6
|
+
* parser understands exactly the text `schema-sql.ts` produces. A general SQL parser would
|
|
7
|
+
* hide a quoting bug instead of tripping over it.
|
|
8
|
+
* ★ Reads answer `current_database()` from this fake's `database` — the same a real server
|
|
9
|
+
* answers for the connection it opened — and `schemaIsEmpty` from a seeded relation set.
|
|
10
|
+
*/
|
|
11
|
+
import * as Effect from 'effect/Effect';
|
|
12
|
+
import { SqlError, SqlSyntaxError, UnknownError } from 'effect/unstable/sql/SqlError';
|
|
13
|
+
import { parseCommentSchema, parseCreateSchema, parseDropSchema } from './fake-sql-parse.ts';
|
|
14
|
+
import type { PostgresSchemaAttributes } from './schema-attrs.ts';
|
|
15
|
+
|
|
16
|
+
/** The schema catalog `fake-sql.ts` constructs and shares with its callers. */
|
|
17
|
+
export interface FakeSchemaState {
|
|
18
|
+
readonly schemas: Map<string, PostgresSchemaAttributes>;
|
|
19
|
+
/** Names of schemas this fake pretends hold at least one relation. */
|
|
20
|
+
readonly relationsIn: Set<string>;
|
|
21
|
+
/** What `current_database()` answers, and the stamp on every read row. */
|
|
22
|
+
readonly database: string;
|
|
23
|
+
/** What `current_user` answers, and the owner of a `CREATE SCHEMA` without `AUTHORIZATION`. */
|
|
24
|
+
readonly sessionRole: string;
|
|
25
|
+
readonly standardConformingStrings: boolean;
|
|
26
|
+
/** Remaining swallowed `CREATE SCHEMA`s (the S10 lie). */
|
|
27
|
+
swallowRemaining: number;
|
|
28
|
+
/** Owner a raced `CREATE SCHEMA` stores instead, consumed once. */
|
|
29
|
+
raceOwner: string | undefined;
|
|
30
|
+
nextOid(): number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Every `Postgres.Schema` statement and the reads that back it. `undefined` when the text
|
|
35
|
+
* belongs to another family, so `fake-sql.ts` can fall through.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ THE EMPTY CHECK BEFORE THE NAMESPACE READ: `SCHEMA_EMPTY_SQL` contains `FROM pg_namespace`,
|
|
38
|
+
* so a plain `includes('FROM pg_namespace')` test would swallow it.
|
|
39
|
+
* ⚠️ `current_database()` is `startsWith`, not `includes`: `SELECT_SCHEMA_SQL` also projects
|
|
40
|
+
* `current_database() AS database`.
|
|
41
|
+
*/
|
|
42
|
+
export const applySchemaStatement = <A extends object>(
|
|
43
|
+
state: FakeSchemaState,
|
|
44
|
+
text: string,
|
|
45
|
+
params: ReadonlyArray<unknown>,
|
|
46
|
+
): Effect.Effect<ReadonlyArray<A>, SqlError> | undefined => {
|
|
47
|
+
if (text.startsWith('SELECT current_database()')) {
|
|
48
|
+
return Effect.succeed([{ database: state.database }] as unknown as ReadonlyArray<A>);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (text.startsWith('SELECT current_user')) {
|
|
52
|
+
return Effect.succeed([{ role: state.sessionRole }] as unknown as ReadonlyArray<A>);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (text.includes('AS empty') && text.includes('pg_class')) {
|
|
56
|
+
const name = params[0] as string;
|
|
57
|
+
return Effect.succeed([{ empty: !state.relationsIn.has(name) }] as unknown as ReadonlyArray<A>);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (text.includes('FROM pg_namespace')) {
|
|
61
|
+
const name = params[0] as string;
|
|
62
|
+
const row = state.schemas.get(name);
|
|
63
|
+
return Effect.succeed(
|
|
64
|
+
(row === undefined
|
|
65
|
+
? []
|
|
66
|
+
: [{ ...row, database: state.database }]) as unknown as ReadonlyArray<A>,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (text.startsWith('CREATE SCHEMA')) {
|
|
71
|
+
if (state.swallowRemaining > 0) {
|
|
72
|
+
state.swallowRemaining -= 1;
|
|
73
|
+
return Effect.succeed([] as unknown as ReadonlyArray<A>);
|
|
74
|
+
}
|
|
75
|
+
const parsed = parseCreateSchema(text, state.sessionRole);
|
|
76
|
+
if (state.raceOwner !== undefined) {
|
|
77
|
+
state.schemas.set(parsed.name, {
|
|
78
|
+
...parsed,
|
|
79
|
+
database: state.database,
|
|
80
|
+
oid: state.nextOid(),
|
|
81
|
+
owner: state.raceOwner,
|
|
82
|
+
});
|
|
83
|
+
state.raceOwner = undefined;
|
|
84
|
+
}
|
|
85
|
+
// PG18 CREATE SCHEMA docs: IF NOT EXISTS does nothing, even if the owner differs.
|
|
86
|
+
if (state.schemas.has(parsed.name)) {
|
|
87
|
+
if (!text.startsWith('CREATE SCHEMA IF NOT EXISTS'))
|
|
88
|
+
return Effect.fail(schemaSqlError('42P06'));
|
|
89
|
+
} else {
|
|
90
|
+
state.schemas.set(parsed.name, { ...parsed, database: state.database, oid: state.nextOid() });
|
|
91
|
+
}
|
|
92
|
+
return Effect.succeed([] as unknown as ReadonlyArray<A>);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (text.startsWith('COMMENT ON SCHEMA')) {
|
|
96
|
+
const parsed = parseCommentSchema(text, state.standardConformingStrings);
|
|
97
|
+
const existing = state.schemas.get(parsed.name);
|
|
98
|
+
if (existing === undefined) {
|
|
99
|
+
throw new Error(`fake-sql: COMMENT ON SCHEMA on absent schema "${parsed.name}"`);
|
|
100
|
+
}
|
|
101
|
+
state.schemas.set(parsed.name, { ...existing, comment: parsed.comment });
|
|
102
|
+
return Effect.succeed([] as unknown as ReadonlyArray<A>);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (text.startsWith('DROP SCHEMA')) {
|
|
106
|
+
const parsed = parseDropSchema(text);
|
|
107
|
+
if (!parsed.cascade && state.relationsIn.has(parsed.name))
|
|
108
|
+
return Effect.fail(schemaSqlError('2BP01'));
|
|
109
|
+
if (parsed.cascade) state.relationsIn.delete(parsed.name);
|
|
110
|
+
state.schemas.delete(parsed.name);
|
|
111
|
+
return Effect.succeed([] as unknown as ReadonlyArray<A>);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return undefined;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/** Match the socket/runner SQLSTATE classification, including class 2B as UnknownError. */
|
|
118
|
+
const schemaSqlError = (code: string): SqlError => {
|
|
119
|
+
const fields = {
|
|
120
|
+
cause: Object.assign(new Error(code), { code }),
|
|
121
|
+
message: code,
|
|
122
|
+
operation: 'schema DDL',
|
|
123
|
+
};
|
|
124
|
+
return new SqlError({
|
|
125
|
+
reason: code.startsWith('42') ? new SqlSyntaxError(fields) : new UnknownError(fields),
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/** REL_18_6 pg_namespace.dat: public exists on bootstrap, owned by pg_database_owner. */
|
|
130
|
+
export const seedSchemas = (
|
|
131
|
+
rows: readonly PostgresSchemaAttributes[] | undefined,
|
|
132
|
+
database: string,
|
|
133
|
+
): Map<string, PostgresSchemaAttributes> =>
|
|
134
|
+
new Map([
|
|
135
|
+
[
|
|
136
|
+
'public',
|
|
137
|
+
{
|
|
138
|
+
name: 'public',
|
|
139
|
+
oid: 2200,
|
|
140
|
+
owner: 'pg_database_owner',
|
|
141
|
+
comment: 'standard public schema',
|
|
142
|
+
database,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
...(rows?.map((row) => [row.name, row] as const) ?? []),
|
|
146
|
+
]);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsers for exactly the SQL `fake-sql.ts` records — the text this family's builders emit,
|
|
3
|
+
* not SQL in general. A general parser would hide a quoting bug instead of tripping over it.
|
|
4
|
+
*/
|
|
5
|
+
import type { PostgresDatabaseAttributes } from './database-attrs.ts';
|
|
6
|
+
import type { PostgresSchemaAttributes } from './schema-attrs.ts';
|
|
7
|
+
|
|
8
|
+
import { parseLiteral, unquoteIdent, unquoteLiteral } from './fake-sql-quote.ts';
|
|
9
|
+
|
|
10
|
+
/** Pull every field back out of exactly the text `buildCreateDatabaseSql` writes. */
|
|
11
|
+
export const parseCreate = (text: string): PostgresDatabaseAttributes => {
|
|
12
|
+
const name = /^CREATE DATABASE "((?:[^"]|"")*)" WITH /.exec(text);
|
|
13
|
+
const owner = /OWNER "((?:[^"]|"")*)"/.exec(text);
|
|
14
|
+
const encoding = /ENCODING E'((?:[^']|'')*)'/.exec(text);
|
|
15
|
+
const localeProvider = /LOCALE_PROVIDER E'((?:[^']|'')*)'/.exec(text);
|
|
16
|
+
const lcCollate = /LC_COLLATE E'((?:[^']|'')*)'/.exec(text);
|
|
17
|
+
const lcCtype = /LC_CTYPE E'((?:[^']|'')*)'/.exec(text);
|
|
18
|
+
const tablespace = /TABLESPACE "((?:[^"]|"")*)"/.exec(text);
|
|
19
|
+
const allowConnections = /ALLOW_CONNECTIONS (true|false)/.exec(text);
|
|
20
|
+
const connectionLimit = /CONNECTION LIMIT (-?\d+)/.exec(text);
|
|
21
|
+
const isTemplate = /IS_TEMPLATE (true|false)/.exec(text);
|
|
22
|
+
if (name === null || owner === null) {
|
|
23
|
+
throw new Error(`fake-sql: could not parse a generated CREATE DATABASE statement: ${text}`);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
name: unquoteIdent(name[1] as string),
|
|
27
|
+
oid: 0,
|
|
28
|
+
owner: unquoteIdent(owner[1] as string),
|
|
29
|
+
encoding: encoding === null ? 'UTF8' : unquoteLiteral(encoding[1] as string),
|
|
30
|
+
localeProvider:
|
|
31
|
+
localeProvider === null ? 'libc' : (unquoteLiteral(localeProvider[1] as string) as 'libc'),
|
|
32
|
+
collate: lcCollate === null ? 'C' : unquoteLiteral(lcCollate[1] as string),
|
|
33
|
+
ctype: lcCtype === null ? 'C' : unquoteLiteral(lcCtype[1] as string),
|
|
34
|
+
tablespace: tablespace === null ? 'pg_default' : unquoteIdent(tablespace[1] as string),
|
|
35
|
+
allowConnections: allowConnections === null ? true : allowConnections[1] === 'true',
|
|
36
|
+
connectionLimit: connectionLimit === null ? -1 : Number(connectionLimit[1]),
|
|
37
|
+
isTemplate: isTemplate === null ? false : isTemplate[1] === 'true',
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Parse exactly the text `buildCreateSchemaSql` writes. The caller stamps `database` and
|
|
42
|
+
* `oid`. Absent `AUTHORIZATION`, the owner is `executingRole` — Postgres's own default, the
|
|
43
|
+
* role `current_user` answers. */
|
|
44
|
+
export const parseCreateSchema = (
|
|
45
|
+
text: string,
|
|
46
|
+
executingRole: string,
|
|
47
|
+
): Omit<PostgresSchemaAttributes, 'database' | 'oid'> => {
|
|
48
|
+
const match =
|
|
49
|
+
/^CREATE SCHEMA(?: IF NOT EXISTS)? "((?:[^"]|"")*)"(?: AUTHORIZATION "((?:[^"]|"")*)")?$/.exec(
|
|
50
|
+
text,
|
|
51
|
+
);
|
|
52
|
+
if (match === null) {
|
|
53
|
+
throw new Error(`fake-sql: could not parse a generated CREATE SCHEMA statement: ${text}`);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
name: unquoteIdent(match[1] as string),
|
|
57
|
+
owner: match[2] === undefined ? executingRole : unquoteIdent(match[2] as string),
|
|
58
|
+
comment: null,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Parse exactly the text `buildCommentSchemaSql` writes. */
|
|
63
|
+
export const parseCommentSchema = (
|
|
64
|
+
text: string,
|
|
65
|
+
standardConformingStrings = true,
|
|
66
|
+
): { readonly name: string; readonly comment: string } => {
|
|
67
|
+
const match = /^COMMENT ON SCHEMA "((?:[^"]|"")*)" IS ([\s\S]+)$/.exec(text);
|
|
68
|
+
if (match === null) {
|
|
69
|
+
throw new Error(`fake-sql: could not parse a generated COMMENT ON SCHEMA statement: ${text}`);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
name: unquoteIdent(match[1] as string),
|
|
73
|
+
comment: parseLiteral(match[2] as string, standardConformingStrings),
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** Parse exactly the text `buildDropSchemaSql` writes. */
|
|
78
|
+
export const parseDropSchema = (
|
|
79
|
+
text: string,
|
|
80
|
+
): { readonly name: string; readonly cascade: boolean } => {
|
|
81
|
+
const match = /^DROP SCHEMA IF EXISTS "((?:[^"]|"")*)"( CASCADE)?$/.exec(text);
|
|
82
|
+
if (match === null) {
|
|
83
|
+
throw new Error(`fake-sql: could not parse a generated DROP SCHEMA statement: ${text}`);
|
|
84
|
+
}
|
|
85
|
+
return { name: unquoteIdent(match[1] as string), cascade: match[2] !== undefined };
|
|
86
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inverse of `quoteIdent` / `quoteStringLiteral` (`database-sql.ts`), for the fakes that
|
|
3
|
+
* parse their own generated statement text. One place, so the database and role fakes can never
|
|
4
|
+
* disagree about what a doubled quote means.
|
|
5
|
+
*/
|
|
6
|
+
export const unquoteIdent = (raw: string): string => raw.replace(/""/g, '"');
|
|
7
|
+
|
|
8
|
+
export const unquoteLiteral = (raw: string): string =>
|
|
9
|
+
raw.replace(/\\\\/g, '\\').replace(/''/g, "'");
|
|
10
|
+
|
|
11
|
+
/** Decode one entire literal, rejecting trailing SQL. PG18 §4.1.2.2: only E strings
|
|
12
|
+
* interpret backslashes when standard_conforming_strings is on. Deliberately tiny:
|
|
13
|
+
* accepts generated escapes, not the server's octal/hex/unicode grammar. */
|
|
14
|
+
export const parseLiteral = (text: string, standardConformingStrings = true): string => {
|
|
15
|
+
const escape = text.startsWith("E'") || !standardConformingStrings;
|
|
16
|
+
const start = text.startsWith("E'") ? 2 : 1;
|
|
17
|
+
if (text[start - 1] !== "'") throw new Error('fake-sql: expected literal');
|
|
18
|
+
let value = '';
|
|
19
|
+
for (let i = start; i < text.length; i += 1) {
|
|
20
|
+
const char = text[i];
|
|
21
|
+
if (char === "'") {
|
|
22
|
+
if (text[i + 1] === "'") {
|
|
23
|
+
value += "'";
|
|
24
|
+
i += 1;
|
|
25
|
+
} else if (i === text.length - 1) return value;
|
|
26
|
+
else throw new Error('fake-sql: trailing SQL after literal');
|
|
27
|
+
} else if (char === '\\' && escape) {
|
|
28
|
+
i += 1;
|
|
29
|
+
const next = text[i];
|
|
30
|
+
if (next === undefined) throw new Error('fake-sql: unterminated escape');
|
|
31
|
+
value +=
|
|
32
|
+
({ n: '\n', r: '\r', t: '\t', b: '\b', f: '\f' } as Record<string, string>)[next] ?? next;
|
|
33
|
+
} else value += char;
|
|
34
|
+
}
|
|
35
|
+
throw new Error('fake-sql: unterminated literal');
|
|
36
|
+
};
|