@geekmidas/db 10.0.0-alpha.4 → 10.0.0-alpha.40

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.
Files changed (85) hide show
  1. package/dist/errors.cjs +72 -5
  2. package/dist/errors.cjs.map +1 -0
  3. package/dist/errors.d.cts +4 -6
  4. package/dist/errors.d.cts.map +1 -1
  5. package/dist/errors.d.mts +4 -6
  6. package/dist/errors.d.mts.map +1 -1
  7. package/dist/errors.mjs +68 -2
  8. package/dist/errors.mjs.map +1 -0
  9. package/dist/kysely/pagination.cjs +7 -9
  10. package/dist/kysely/pagination.cjs.map +1 -1
  11. package/dist/kysely/pagination.d.cts +4 -14
  12. package/dist/kysely/pagination.d.cts.map +1 -1
  13. package/dist/kysely/pagination.d.mts +4 -14
  14. package/dist/kysely/pagination.d.mts.map +1 -1
  15. package/dist/kysely/pagination.mjs +6 -9
  16. package/dist/kysely/pagination.mjs.map +1 -1
  17. package/dist/kysely.cjs +11 -2
  18. package/dist/kysely.cjs.map +1 -0
  19. package/dist/kysely.d.cts +9 -2
  20. package/dist/kysely.d.cts.map +1 -0
  21. package/dist/kysely.d.mts +9 -2
  22. package/dist/kysely.d.mts.map +1 -0
  23. package/dist/kysely.mjs +10 -2
  24. package/dist/kysely.mjs.map +1 -0
  25. package/dist/objection/pagination.cjs +6 -7
  26. package/dist/objection/pagination.cjs.map +1 -1
  27. package/dist/objection/pagination.d.cts +4 -14
  28. package/dist/objection/pagination.d.cts.map +1 -1
  29. package/dist/objection/pagination.d.mts +4 -14
  30. package/dist/objection/pagination.d.mts.map +1 -1
  31. package/dist/objection/pagination.mjs +5 -7
  32. package/dist/objection/pagination.mjs.map +1 -1
  33. package/dist/pagination.cjs +39 -4
  34. package/dist/pagination.cjs.map +1 -0
  35. package/dist/pagination.d.cts +30 -2
  36. package/dist/pagination.d.cts.map +1 -0
  37. package/dist/pagination.d.mts +30 -2
  38. package/dist/pagination.d.mts.map +1 -0
  39. package/dist/pagination.mjs +36 -2
  40. package/dist/pagination.mjs.map +1 -0
  41. package/dist/pg/roles.cjs +26 -3
  42. package/dist/pg/roles.cjs.map +1 -1
  43. package/dist/pg/roles.d.cts +16 -8
  44. package/dist/pg/roles.d.cts.map +1 -1
  45. package/dist/pg/roles.d.mts +16 -8
  46. package/dist/pg/roles.d.mts.map +1 -1
  47. package/dist/pg/roles.mjs +25 -2
  48. package/dist/pg/roles.mjs.map +1 -1
  49. package/dist/pg/url.cjs +18 -3
  50. package/dist/pg/url.cjs.map +1 -1
  51. package/dist/pg/url.d.cts +2 -4
  52. package/dist/pg/url.d.cts.map +1 -1
  53. package/dist/pg/url.d.mts +2 -4
  54. package/dist/pg/url.d.mts.map +1 -1
  55. package/dist/pg/url.mjs +17 -3
  56. package/dist/pg/url.mjs.map +1 -1
  57. package/dist/rls.cjs +5 -29
  58. package/dist/rls.cjs.map +1 -1
  59. package/dist/rls.d.cts +6 -10
  60. package/dist/rls.d.cts.map +1 -1
  61. package/dist/rls.d.mts +6 -10
  62. package/dist/rls.d.mts.map +1 -1
  63. package/dist/rls.mjs +3 -5
  64. package/dist/rls.mjs.map +1 -1
  65. package/package.json +2 -2
  66. package/dist/errors-CfFCan-X.mjs +0 -69
  67. package/dist/errors-CfFCan-X.mjs.map +0 -1
  68. package/dist/errors-Cs5jlAv-.cjs +0 -93
  69. package/dist/errors-Cs5jlAv-.cjs.map +0 -1
  70. package/dist/kysely-BAedYsvn.d.mts +0 -13
  71. package/dist/kysely-BAedYsvn.d.mts.map +0 -1
  72. package/dist/kysely-Bo_Zg-D3.mjs +0 -11
  73. package/dist/kysely-Bo_Zg-D3.mjs.map +0 -1
  74. package/dist/kysely-D3eeuId4.d.cts +0 -13
  75. package/dist/kysely-D3eeuId4.d.cts.map +0 -1
  76. package/dist/kysely-LWvS2ekY.cjs +0 -17
  77. package/dist/kysely-LWvS2ekY.cjs.map +0 -1
  78. package/dist/pagination-BAFX7C1I.d.cts +0 -32
  79. package/dist/pagination-BAFX7C1I.d.cts.map +0 -1
  80. package/dist/pagination-Bu1dqOy0.cjs +0 -55
  81. package/dist/pagination-Bu1dqOy0.cjs.map +0 -1
  82. package/dist/pagination-CQ8QHXaL.mjs +0 -37
  83. package/dist/pagination-CQ8QHXaL.mjs.map +0 -1
  84. package/dist/pagination-kv-KWS-7.d.mts +0 -32
  85. package/dist/pagination-kv-KWS-7.d.mts.map +0 -1
package/dist/pg/roles.mjs CHANGED
@@ -7,18 +7,20 @@
7
7
  * leaving a half-granted role that looks complete.
8
8
  */
9
9
  function roleStatements(spec) {
10
- const { runtime, owner, schema, passwords } = spec;
10
+ const { runtime, owner, schema, passwords, database } = spec;
11
11
  return [
12
12
  {
13
13
  describe: `role ${owner}`,
14
14
  exists: roleExists(owner),
15
15
  sql: `CREATE ROLE ${ident(owner)} LOGIN PASSWORD ${literal(passwords.owner)}`
16
16
  },
17
+ passwordStatement(owner, passwords.owner),
17
18
  {
18
19
  describe: `role ${runtime}`,
19
20
  exists: roleExists(runtime),
20
21
  sql: `CREATE ROLE ${ident(runtime)} LOGIN PASSWORD ${literal(passwords.runtime)}`
21
22
  },
23
+ passwordStatement(runtime, passwords.runtime),
22
24
  {
23
25
  describe: `${owner} granted to the connecting role`,
24
26
  sql: `GRANT ${ident(owner)} TO CURRENT_USER`
@@ -35,6 +37,10 @@ function roleStatements(spec) {
35
37
  describe: `${schema} is owned by ${owner}`,
36
38
  sql: `ALTER SCHEMA ${ident(schema)} OWNER TO ${ident(owner)}`
37
39
  },
40
+ ...database ? [{
41
+ describe: `${owner} may create in database ${database}`,
42
+ sql: `GRANT CREATE ON DATABASE ${ident(database)} TO ${ident(owner)}`
43
+ }] : [],
38
44
  {
39
45
  describe: `${runtime} may use ${schema}`,
40
46
  sql: `GRANT USAGE ON SCHEMA ${ident(schema)} TO ${ident(runtime)}`
@@ -67,6 +73,21 @@ function roleStatements(spec) {
67
73
  ];
68
74
  }
69
75
  /**
76
+ * The role's password, set to what the caller supplied — every time.
77
+ *
78
+ * `CREATE ROLE` only runs for a role that is not there, so on its own a role
79
+ * keeps the password it was born with, and an app handed a different one is
80
+ * locked out. Setting it on every apply is what lets the supplied password be
81
+ * the truth: rotated, or re-derived, the role follows. Idempotent, so it needs
82
+ * no check.
83
+ */
84
+ function passwordStatement(role, password) {
85
+ return {
86
+ describe: `${role} has its current password`,
87
+ sql: `ALTER ROLE ${ident(role)} WITH LOGIN PASSWORD ${literal(password)}`
88
+ };
89
+ }
90
+ /**
70
91
  * A role that may read and nothing else.
71
92
  *
72
93
  * `SELECT` and no more, on what exists and on what the owner creates later. No
@@ -84,6 +105,7 @@ function readerStatements(spec, reader) {
84
105
  exists: roleExists(reader),
85
106
  sql: `CREATE ROLE ${ident(reader)} LOGIN PASSWORD ${literal(password)}`
86
107
  },
108
+ passwordStatement(reader, password),
87
109
  {
88
110
  describe: `${reader} may use ${schema}`,
89
111
  sql: `GRANT USAGE ON SCHEMA ${ident(schema)} TO ${ident(reader)}`
@@ -104,6 +126,7 @@ function readerStatements(spec, reader) {
104
126
  }
105
127
  /** A reader role was named and no password was supplied for it. */
106
128
  var ReaderNeedsPassword = class extends Error {
129
+ role;
107
130
  constructor(role) {
108
131
  super(`'${role}' is a reader role and no password was supplied for it. This module composes DDL and invents no secrets: pass passwords.reader alongside the role name.`);
109
132
  this.role = role;
@@ -144,7 +167,7 @@ function ident(name) {
144
167
  function literal(value) {
145
168
  return `'${value.replace(/'/g, "''")}'`;
146
169
  }
147
-
148
170
  //#endregion
149
171
  export { ReaderNeedsPassword, ownerRole, readerRole, roleStatements };
172
+
150
173
  //# sourceMappingURL=roles.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"roles.mjs","names":["spec: RoleSpec","reader: string","role: string","runtime: string","name: string","value: string"],"sources":["../../src/pg/roles.ts"],"sourcesContent":["/**\n * The role DDL — an owner, a runtime role, and the grants between them.\n *\n * Generation only. The same statements have to run in-process from `gkm dev`\n * and from a provisioner inside a VPC at deploy, and one implementation is what\n * stops \"works locally, fails deployed\" — so this composes SQL and applies\n * none of it.\n *\n * **What the split is for.** A handler connects as the runtime role, which can\n * read and write rows and cannot create, alter, or drop anything. A migrator\n * connects as the owner, which can. That is a security property rather than a\n * convention — a compromised handler cannot `DROP TABLE` because its role holds\n * no such grant — and it is only a property at all once these statements have\n * actually run. Until then a schema is a namespace, not a boundary.\n *\n * **Passwords are an input.** This module invents no secrets: a caller derives\n * or fetches them and passes them in, because where a password comes from is\n * the one part that legitimately differs between a laptop and a deploy.\n */\n\n/** The roles and schema one tenant needs. */\nexport interface RoleSpec {\n\t/** The role a handler connects as. Reads and writes rows; owns nothing. */\n\truntime: string;\n\t/** The role a migrator connects as. Owns the schema and everything in it. */\n\towner: string;\n\t/**\n\t * A third role that may only read, for a reader endpoint.\n\t *\n\t * Optional because it is only worth creating where something points at it.\n\t * Read-only is enforced *here*, by the grants, rather than by which endpoint\n\t * a URL happens to name — which is what makes falling back to the writer's\n\t * endpoint safe when a cluster has no replica.\n\t */\n\treader?: string;\n\t/** The schema they operate in, pinned on every role's `search_path`. */\n\tschema: string;\n\t/** Supplied, never generated here. */\n\tpasswords: { runtime: string; owner: string; reader?: string };\n}\n\n/** One statement, and how to tell whether it still needs running. */\nexport interface RoleStatement {\n\t/** What it does, for the line a reconciler prints. */\n\tdescribe: string;\n\t/**\n\t * Answers \"does this already exist\", parameterised.\n\t *\n\t * Absent means the statement is idempotent in itself and is run every time —\n\t * a `GRANT` re-granting what is already granted is a no-op, and checking\n\t * would cost a round trip to learn nothing.\n\t */\n\texists?: { sql: string; values: unknown[] };\n\tsql: string;\n}\n\n/**\n * Everything one tenant's roles need, in dependency order.\n *\n * The owner before the schema it owns, the schema before the grants on it. A\n * caller applies them in sequence and may stop at the first failure without\n * leaving a half-granted role that looks complete.\n */\nexport function roleStatements(spec: RoleSpec): RoleStatement[] {\n\tconst { runtime, owner, schema, passwords } = spec;\n\n\treturn [\n\t\t{\n\t\t\tdescribe: `role ${owner}`,\n\t\t\texists: roleExists(owner),\n\t\t\tsql: `CREATE ROLE ${ident(owner)} LOGIN PASSWORD ${literal(passwords.owner)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `role ${runtime}`,\n\t\t\texists: roleExists(runtime),\n\t\t\tsql: `CREATE ROLE ${ident(runtime)} LOGIN PASSWORD ${literal(passwords.runtime)}`,\n\t\t},\n\t\t{\n\t\t\t// Postgres will not let you create an object owned by a role you are\n\t\t\t// not a member of, and an RDS master is not a superuser — so\n\t\t\t// `CREATE SCHEMA … AUTHORIZATION` fails with \"must be able to SET\n\t\t\t// ROLE\" unless the connected role is granted membership first.\n\t\t\t//\n\t\t\t// Granting is idempotent and needs no check. It also leaves the master\n\t\t\t// able to administer the schema, which is what you want from the one\n\t\t\t// credential that exists before any role does.\n\t\t\tdescribe: `${owner} granted to the connecting role`,\n\t\t\tsql: `GRANT ${ident(owner)} TO CURRENT_USER`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `schema ${schema}`,\n\t\t\texists: {\n\t\t\t\tsql: 'SELECT 1 FROM information_schema.schemata WHERE schema_name = $1',\n\t\t\t\tvalues: [schema],\n\t\t\t},\n\t\t\t// Owned by the owner from the moment it exists. Creating it as the\n\t\t\t// cluster master and granting afterwards leaves a window where the\n\t\t\t// master owns objects the owner is supposed to.\n\t\t\tsql: `CREATE SCHEMA ${ident(schema)} AUTHORIZATION ${ident(owner)}`,\n\t\t},\n\t\t{\n\t\t\t// And the schema that was already there, which the guard above skips.\n\t\t\t// A schema created before its roles existed is owned by whoever\n\t\t\t// created it — the cluster master — and nothing below fixes that, so\n\t\t\t// the owner cannot create in the schema it is supposed to own. The\n\t\t\t// failure is `no schema has been selected to create in`, which names\n\t\t\t// neither the schema nor the owner and reads like a missing\n\t\t\t// `search_path`.\n\t\t\t//\n\t\t\t// Idempotent when the owner is already right, so it costs a statement\n\t\t\t// rather than a check.\n\t\t\tdescribe: `${schema} is owned by ${owner}`,\n\t\t\tsql: `ALTER SCHEMA ${ident(schema)} OWNER TO ${ident(owner)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${runtime} may use ${schema}`,\n\t\t\tsql: `GRANT USAGE ON SCHEMA ${ident(schema)} TO ${ident(runtime)}`,\n\t\t},\n\t\t{\n\t\t\t// The important half: *future* tables. A grant on what exists today\n\t\t\t// covers nothing the next migration creates, which is how a runtime\n\t\t\t// role silently loses access one deploy later.\n\t\t\tdescribe: `${runtime} may read and write future tables in ${schema}`,\n\t\t\tsql:\n\t\t\t\t`ALTER DEFAULT PRIVILEGES FOR ROLE ${ident(owner)} IN SCHEMA ${ident(schema)} ` +\n\t\t\t\t`GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ${ident(runtime)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${runtime} may use future sequences in ${schema}`,\n\t\t\tsql:\n\t\t\t\t`ALTER DEFAULT PRIVILEGES FOR ROLE ${ident(owner)} IN SCHEMA ${ident(schema)} ` +\n\t\t\t\t`GRANT USAGE, SELECT ON SEQUENCES TO ${ident(runtime)}`,\n\t\t},\n\t\t{\n\t\t\t// And the tables that are already there, for a schema that existed\n\t\t\t// before its roles did.\n\t\t\tdescribe: `${runtime} may read and write existing tables in ${schema}`,\n\t\t\tsql:\n\t\t\t\t`GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA ${ident(schema)} ` +\n\t\t\t\t`TO ${ident(runtime)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${runtime} may use existing sequences in ${schema}`,\n\t\t\tsql: `GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA ${ident(schema)} TO ${ident(runtime)}`,\n\t\t},\n\t\t{\n\t\t\t// Pinned on the role rather than carried in every URL. A connection\n\t\t\t// string that has to remember `search_path` is one that eventually\n\t\t\t// forgets, and the forgetting looks like an empty database.\n\t\t\tdescribe: `${runtime} resolves names in ${schema}`,\n\t\t\tsql: `ALTER ROLE ${ident(runtime)} SET search_path TO ${ident(schema)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${owner} resolves names in ${schema}`,\n\t\t\tsql: `ALTER ROLE ${ident(owner)} SET search_path TO ${ident(schema)}`,\n\t\t},\n\t\t...(spec.reader ? readerStatements(spec, spec.reader) : []),\n\t];\n}\n\n/**\n * A role that may read and nothing else.\n *\n * `SELECT` and no more, on what exists and on what the owner creates later. No\n * `INSERT`, no `UPDATE`, no `DELETE`, and no sequence `USAGE` — a reader that\n * could take a sequence value could not write the row it was for, so granting\n * it would be surface with no use.\n */\nfunction readerStatements(spec: RoleSpec, reader: string): RoleStatement[] {\n\tconst { owner, schema, passwords } = spec;\n\tconst password = passwords.reader;\n\n\tif (!password) throw new ReaderNeedsPassword(reader);\n\n\treturn [\n\t\t{\n\t\t\tdescribe: `role ${reader}`,\n\t\t\texists: roleExists(reader),\n\t\t\tsql: `CREATE ROLE ${ident(reader)} LOGIN PASSWORD ${literal(password)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${reader} may use ${schema}`,\n\t\t\tsql: `GRANT USAGE ON SCHEMA ${ident(schema)} TO ${ident(reader)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${reader} may read future tables in ${schema}`,\n\t\t\tsql:\n\t\t\t\t`ALTER DEFAULT PRIVILEGES FOR ROLE ${ident(owner)} IN SCHEMA ${ident(schema)} ` +\n\t\t\t\t`GRANT SELECT ON TABLES TO ${ident(reader)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${reader} may read existing tables in ${schema}`,\n\t\t\tsql: `GRANT SELECT ON ALL TABLES IN SCHEMA ${ident(schema)} TO ${ident(reader)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${reader} resolves names in ${schema}`,\n\t\t\tsql: `ALTER ROLE ${ident(reader)} SET search_path TO ${ident(schema)}`,\n\t\t},\n\t];\n}\n\n/** A reader role was named and no password was supplied for it. */\nexport class ReaderNeedsPassword extends Error {\n\tconstructor(readonly role: string) {\n\t\tsuper(\n\t\t\t`'${role}' is a reader role and no password was supplied for it. ` +\n\t\t\t\t`This module composes DDL and invents no secrets: pass ` +\n\t\t\t\t`passwords.reader alongside the role name.`,\n\t\t);\n\t\tthis.name = 'ReaderNeedsPassword';\n\t}\n}\n\n/** The read-only role's name for a given runtime role. */\nexport function readerRole(runtime: string): string {\n\treturn `${runtime}_reader`;\n}\n\n/** The owner role's name for a given runtime role. */\nexport function ownerRole(runtime: string): string {\n\treturn `${runtime}_owner`;\n}\n\nfunction roleExists(role: string): { sql: string; values: unknown[] } {\n\treturn { sql: 'SELECT 1 FROM pg_roles WHERE rolname = $1', values: [role] };\n}\n\n/**\n * Quote an identifier for use in DDL.\n *\n * Names reaching here are already canonical, so this is defence rather than\n * sanitisation — but DDL cannot be parameterised, and an unquoted identifier is\n * the one place a name could ever be more than a name.\n */\nfunction ident(name: string): string {\n\treturn `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\n/**\n * Quote a string literal.\n *\n * A password cannot be a bind parameter in `CREATE ROLE`, which is the one\n * place in this module where a value rather than a name is interpolated — so it\n * is escaped rather than trusted, whatever the caller believes it derived.\n */\nfunction literal(value: string): string {\n\treturn `'${value.replace(/'/g, \"''\")}'`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"roles.mjs","names":[],"sources":["../../src/pg/roles.ts"],"sourcesContent":["/**\n * The role DDL — an owner, a runtime role, and the grants between them.\n *\n * Generation only. The same statements have to run in-process from `gkm dev`\n * and from a provisioner inside a VPC at deploy, and one implementation is what\n * stops \"works locally, fails deployed\" — so this composes SQL and applies\n * none of it.\n *\n * **What the split is for.** A handler connects as the runtime role, which can\n * read and write rows and cannot create, alter, or drop anything. A migrator\n * connects as the owner, which can. That is a security property rather than a\n * convention — a compromised handler cannot `DROP TABLE` because its role holds\n * no such grant — and it is only a property at all once these statements have\n * actually run. Until then a schema is a namespace, not a boundary.\n *\n * **Passwords are an input.** This module invents no secrets: a caller derives\n * or fetches them and passes them in, because where a password comes from is\n * the one part that legitimately differs between a laptop and a deploy.\n */\n\n/** The roles and schema one tenant needs. */\nexport interface RoleSpec {\n\t/** The role a handler connects as. Reads and writes rows; owns nothing. */\n\truntime: string;\n\t/** The role a migrator connects as. Owns the schema and everything in it. */\n\towner: string;\n\t/**\n\t * A third role that may only read, for a reader endpoint.\n\t *\n\t * Optional because it is only worth creating where something points at it.\n\t * Read-only is enforced *here*, by the grants, rather than by which endpoint\n\t * a URL happens to name — which is what makes falling back to the writer's\n\t * endpoint safe when a cluster has no replica.\n\t */\n\treader?: string;\n\t/** The schema they operate in, pinned on every role's `search_path`. */\n\tschema: string;\n\t/**\n\t * The database itself, when these are a database construct's roles rather\n\t * than a schema tenant's.\n\t *\n\t * Its owner may then create in the database — which is what a migration\n\t * needs to create a trusted extension (`citext`, `pg_trgm`): Postgres asks\n\t * for `CREATE` on the database for that, not on the schema. A tenant's owner\n\t * is not given it, so it stays confined to its own schema.\n\t */\n\tdatabase?: string;\n\t/** Supplied, never generated here. */\n\tpasswords: { runtime: string; owner: string; reader?: string };\n}\n\n/** One statement, and how to tell whether it still needs running. */\nexport interface RoleStatement {\n\t/** What it does, for the line a reconciler prints. */\n\tdescribe: string;\n\t/**\n\t * Answers \"does this already exist\", parameterised.\n\t *\n\t * Absent means the statement is idempotent in itself and is run every time —\n\t * a `GRANT` re-granting what is already granted is a no-op, and checking\n\t * would cost a round trip to learn nothing.\n\t */\n\texists?: { sql: string; values: unknown[] };\n\tsql: string;\n}\n\n/**\n * Everything one tenant's roles need, in dependency order.\n *\n * The owner before the schema it owns, the schema before the grants on it. A\n * caller applies them in sequence and may stop at the first failure without\n * leaving a half-granted role that looks complete.\n */\nexport function roleStatements(spec: RoleSpec): RoleStatement[] {\n\tconst { runtime, owner, schema, passwords, database } = spec;\n\n\treturn [\n\t\t{\n\t\t\tdescribe: `role ${owner}`,\n\t\t\texists: roleExists(owner),\n\t\t\tsql: `CREATE ROLE ${ident(owner)} LOGIN PASSWORD ${literal(passwords.owner)}`,\n\t\t},\n\t\tpasswordStatement(owner, passwords.owner),\n\t\t{\n\t\t\tdescribe: `role ${runtime}`,\n\t\t\texists: roleExists(runtime),\n\t\t\tsql: `CREATE ROLE ${ident(runtime)} LOGIN PASSWORD ${literal(passwords.runtime)}`,\n\t\t},\n\t\tpasswordStatement(runtime, passwords.runtime),\n\t\t{\n\t\t\t// Postgres will not let you create an object owned by a role you are\n\t\t\t// not a member of, and an RDS master is not a superuser — so\n\t\t\t// `CREATE SCHEMA … AUTHORIZATION` fails with \"must be able to SET\n\t\t\t// ROLE\" unless the connected role is granted membership first.\n\t\t\t//\n\t\t\t// Granting is idempotent and needs no check. It also leaves the master\n\t\t\t// able to administer the schema, which is what you want from the one\n\t\t\t// credential that exists before any role does.\n\t\t\tdescribe: `${owner} granted to the connecting role`,\n\t\t\tsql: `GRANT ${ident(owner)} TO CURRENT_USER`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `schema ${schema}`,\n\t\t\texists: {\n\t\t\t\tsql: 'SELECT 1 FROM information_schema.schemata WHERE schema_name = $1',\n\t\t\t\tvalues: [schema],\n\t\t\t},\n\t\t\t// Owned by the owner from the moment it exists. Creating it as the\n\t\t\t// cluster master and granting afterwards leaves a window where the\n\t\t\t// master owns objects the owner is supposed to.\n\t\t\tsql: `CREATE SCHEMA ${ident(schema)} AUTHORIZATION ${ident(owner)}`,\n\t\t},\n\t\t{\n\t\t\t// And the schema that was already there, which the guard above skips.\n\t\t\t// A schema created before its roles existed is owned by whoever\n\t\t\t// created it — the cluster master — and nothing below fixes that, so\n\t\t\t// the owner cannot create in the schema it is supposed to own. The\n\t\t\t// failure is `no schema has been selected to create in`, which names\n\t\t\t// neither the schema nor the owner and reads like a missing\n\t\t\t// `search_path`.\n\t\t\t//\n\t\t\t// Idempotent when the owner is already right, so it costs a statement\n\t\t\t// rather than a check.\n\t\t\tdescribe: `${schema} is owned by ${owner}`,\n\t\t\tsql: `ALTER SCHEMA ${ident(schema)} OWNER TO ${ident(owner)}`,\n\t\t},\n\t\t...(database\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\t// The migrator of a database, not of a tenant inside one: it may\n\t\t\t\t\t\t// create trusted extensions, which Postgres grants on the\n\t\t\t\t\t\t// database rather than the schema. Idempotent, so no check.\n\t\t\t\t\t\tdescribe: `${owner} may create in database ${database}`,\n\t\t\t\t\t\tsql: `GRANT CREATE ON DATABASE ${ident(database)} TO ${ident(owner)}`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t\t{\n\t\t\tdescribe: `${runtime} may use ${schema}`,\n\t\t\tsql: `GRANT USAGE ON SCHEMA ${ident(schema)} TO ${ident(runtime)}`,\n\t\t},\n\t\t{\n\t\t\t// The important half: *future* tables. A grant on what exists today\n\t\t\t// covers nothing the next migration creates, which is how a runtime\n\t\t\t// role silently loses access one deploy later.\n\t\t\tdescribe: `${runtime} may read and write future tables in ${schema}`,\n\t\t\tsql:\n\t\t\t\t`ALTER DEFAULT PRIVILEGES FOR ROLE ${ident(owner)} IN SCHEMA ${ident(schema)} ` +\n\t\t\t\t`GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ${ident(runtime)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${runtime} may use future sequences in ${schema}`,\n\t\t\tsql:\n\t\t\t\t`ALTER DEFAULT PRIVILEGES FOR ROLE ${ident(owner)} IN SCHEMA ${ident(schema)} ` +\n\t\t\t\t`GRANT USAGE, SELECT ON SEQUENCES TO ${ident(runtime)}`,\n\t\t},\n\t\t{\n\t\t\t// And the tables that are already there, for a schema that existed\n\t\t\t// before its roles did.\n\t\t\tdescribe: `${runtime} may read and write existing tables in ${schema}`,\n\t\t\tsql:\n\t\t\t\t`GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA ${ident(schema)} ` +\n\t\t\t\t`TO ${ident(runtime)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${runtime} may use existing sequences in ${schema}`,\n\t\t\tsql: `GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA ${ident(schema)} TO ${ident(runtime)}`,\n\t\t},\n\t\t{\n\t\t\t// Pinned on the role rather than carried in every URL. A connection\n\t\t\t// string that has to remember `search_path` is one that eventually\n\t\t\t// forgets, and the forgetting looks like an empty database.\n\t\t\tdescribe: `${runtime} resolves names in ${schema}`,\n\t\t\tsql: `ALTER ROLE ${ident(runtime)} SET search_path TO ${ident(schema)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${owner} resolves names in ${schema}`,\n\t\t\tsql: `ALTER ROLE ${ident(owner)} SET search_path TO ${ident(schema)}`,\n\t\t},\n\t\t...(spec.reader ? readerStatements(spec, spec.reader) : []),\n\t];\n}\n\n/**\n * The role's password, set to what the caller supplied — every time.\n *\n * `CREATE ROLE` only runs for a role that is not there, so on its own a role\n * keeps the password it was born with, and an app handed a different one is\n * locked out. Setting it on every apply is what lets the supplied password be\n * the truth: rotated, or re-derived, the role follows. Idempotent, so it needs\n * no check.\n */\nfunction passwordStatement(role: string, password: string): RoleStatement {\n\treturn {\n\t\tdescribe: `${role} has its current password`,\n\t\tsql: `ALTER ROLE ${ident(role)} WITH LOGIN PASSWORD ${literal(password)}`,\n\t};\n}\n\n/**\n * A role that may read and nothing else.\n *\n * `SELECT` and no more, on what exists and on what the owner creates later. No\n * `INSERT`, no `UPDATE`, no `DELETE`, and no sequence `USAGE` — a reader that\n * could take a sequence value could not write the row it was for, so granting\n * it would be surface with no use.\n */\nfunction readerStatements(spec: RoleSpec, reader: string): RoleStatement[] {\n\tconst { owner, schema, passwords } = spec;\n\tconst password = passwords.reader;\n\n\tif (!password) throw new ReaderNeedsPassword(reader);\n\n\treturn [\n\t\t{\n\t\t\tdescribe: `role ${reader}`,\n\t\t\texists: roleExists(reader),\n\t\t\tsql: `CREATE ROLE ${ident(reader)} LOGIN PASSWORD ${literal(password)}`,\n\t\t},\n\t\tpasswordStatement(reader, password),\n\t\t{\n\t\t\tdescribe: `${reader} may use ${schema}`,\n\t\t\tsql: `GRANT USAGE ON SCHEMA ${ident(schema)} TO ${ident(reader)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${reader} may read future tables in ${schema}`,\n\t\t\tsql:\n\t\t\t\t`ALTER DEFAULT PRIVILEGES FOR ROLE ${ident(owner)} IN SCHEMA ${ident(schema)} ` +\n\t\t\t\t`GRANT SELECT ON TABLES TO ${ident(reader)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${reader} may read existing tables in ${schema}`,\n\t\t\tsql: `GRANT SELECT ON ALL TABLES IN SCHEMA ${ident(schema)} TO ${ident(reader)}`,\n\t\t},\n\t\t{\n\t\t\tdescribe: `${reader} resolves names in ${schema}`,\n\t\t\tsql: `ALTER ROLE ${ident(reader)} SET search_path TO ${ident(schema)}`,\n\t\t},\n\t];\n}\n\n/** A reader role was named and no password was supplied for it. */\nexport class ReaderNeedsPassword extends Error {\n\tconstructor(readonly role: string) {\n\t\tsuper(\n\t\t\t`'${role}' is a reader role and no password was supplied for it. ` +\n\t\t\t\t`This module composes DDL and invents no secrets: pass ` +\n\t\t\t\t`passwords.reader alongside the role name.`,\n\t\t);\n\t\tthis.name = 'ReaderNeedsPassword';\n\t}\n}\n\n/** The read-only role's name for a given runtime role. */\nexport function readerRole(runtime: string): string {\n\treturn `${runtime}_reader`;\n}\n\n/** The owner role's name for a given runtime role. */\nexport function ownerRole(runtime: string): string {\n\treturn `${runtime}_owner`;\n}\n\nfunction roleExists(role: string): { sql: string; values: unknown[] } {\n\treturn { sql: 'SELECT 1 FROM pg_roles WHERE rolname = $1', values: [role] };\n}\n\n/**\n * Quote an identifier for use in DDL.\n *\n * Names reaching here are already canonical, so this is defence rather than\n * sanitisation — but DDL cannot be parameterised, and an unquoted identifier is\n * the one place a name could ever be more than a name.\n */\nfunction ident(name: string): string {\n\treturn `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\n/**\n * Quote a string literal.\n *\n * A password cannot be a bind parameter in `CREATE ROLE`, which is the one\n * place in this module where a value rather than a name is interpolated — so it\n * is escaped rather than trusted, whatever the caller believes it derived.\n */\nfunction literal(value: string): string {\n\treturn `'${value.replace(/'/g, \"''\")}'`;\n}\n"],"mappings":"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"}
package/dist/pg/url.cjs CHANGED
@@ -1,6 +1,21 @@
1
- const require_errors = require('../errors-Cs5jlAv-.cjs');
2
-
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_errors = require("../errors.cjs");
3
3
  //#region src/pg/url.ts
4
+ /**
5
+ * Composing a `postgres://` connection string.
6
+ *
7
+ * One direction only. Parsing exists in `pg` already — every consumer hands the
8
+ * string straight to `new Pool({ connectionString })` — so a parser here would
9
+ * be surface whose only caller is its own round-trip test.
10
+ *
11
+ * What is *not* already solved is composition: a provisioned instance exposes
12
+ * host, port, database, username, and password as five separate values, and the
13
+ * construct reads one URL. Doing that join by hand is where `search_path`
14
+ * quietly goes missing.
15
+ *
16
+ * A URL built here is a secret — it carries the role's password. See
17
+ * {@link redactDatabaseUrl} before writing one anywhere.
18
+ */
4
19
  const SCHEME = "postgres:";
5
20
  const DEFAULT_PORT = 5432;
6
21
  /**
@@ -27,7 +42,7 @@ function build(address) {
27
42
  if (ssl === false) url.searchParams.set("sslmode", "disable");
28
43
  return url.toString();
29
44
  }
30
-
31
45
  //#endregion
32
46
  exports.build = build;
47
+
33
48
  //# sourceMappingURL=url.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"url.cjs","names":["schema: string","address: PostgresAddress","MissingDatabaseHost","MissingDatabaseName"],"sources":["../../src/pg/url.ts"],"sourcesContent":["/**\n * Composing a `postgres://` connection string.\n *\n * One direction only. Parsing exists in `pg` already — every consumer hands the\n * string straight to `new Pool({ connectionString })` — so a parser here would\n * be surface whose only caller is its own round-trip test.\n *\n * What is *not* already solved is composition: a provisioned instance exposes\n * host, port, database, username, and password as five separate values, and the\n * construct reads one URL. Doing that join by hand is where `search_path`\n * quietly goes missing.\n *\n * A URL built here is a secret — it carries the role's password. See\n * {@link redactDatabaseUrl} before writing one anywhere.\n */\n\nimport { MissingDatabaseHost, MissingDatabaseName } from '../errors';\n\n/** What a Postgres connection string addresses. */\nexport interface PostgresAddress {\n\thost: string;\n\tport?: number;\n\t/** The logical database. */\n\tdatabase: string;\n\t/** The role connecting — which is what decides DDL versus DML. */\n\tusername?: string;\n\tpassword?: string;\n\t/**\n\t * The schema to resolve unqualified names against.\n\t *\n\t * Normally pinned on the *role* instead (`ALTER ROLE … SET search_path`), so\n\t * it does not appear in the URL at all. It is carried here for the one case\n\t * with no role of its own to pin — connecting as the cluster master under\n\t * `roles: false` — where without it every query looks in `public`.\n\t *\n\t * Postgres-specific by nature, not just by spelling: it becomes a libpq\n\t * connection option, and no other engine accepts one. MySQL has no schemas\n\t * distinct from databases at all.\n\t */\n\tsearchPath?: string;\n\t/** Off only for local development, where there is no certificate to verify. */\n\tssl?: boolean;\n}\n\nconst SCHEME = 'postgres:';\n\nconst DEFAULT_PORT = 5432;\n\n/**\n * `search_path` travels as a libpq connection option, not a query parameter.\n * The server reads `options`; a plain `?search_path=app` is accepted by the URL\n * parser, ignored by the server, and produces a database that looks empty.\n * The `-c` prefix is what makes it a runtime configuration setting.\n */\nfunction searchPathOption(schema: string): string {\n\treturn `-c search_path=${schema}`;\n}\n\n/** Compose an address into a connection string. */\nexport function build(address: PostgresAddress): string {\n\tconst { host, port, database, username, password, searchPath, ssl } = address;\n\n\tif (!host) throw new MissingDatabaseHost();\n\tif (!database) throw new MissingDatabaseName();\n\n\tconst url = new URL(`${SCHEME}//`);\n\turl.hostname = host;\n\tif (port && port !== DEFAULT_PORT) url.port = String(port);\n\t// Username before password: WHATWG URL drops a password set without one.\n\tif (username) url.username = encodeURIComponent(username);\n\tif (password) url.password = encodeURIComponent(password);\n\turl.pathname = `/${database}`;\n\n\tif (searchPath) url.searchParams.set('options', searchPathOption(searchPath));\n\tif (ssl === false) url.searchParams.set('sslmode', 'disable');\n\n\treturn url.toString();\n}\n"],"mappings":";;;AA4CA,MAAM,SAAS;AAEf,MAAM,eAAe;;;;;;;AAQrB,SAAS,iBAAiBA,QAAwB;AACjD,SAAQ,iBAAiB,OAAO;AAChC;;AAGD,SAAgB,MAAMC,SAAkC;CACvD,MAAM,EAAE,MAAM,MAAM,UAAU,UAAU,UAAU,YAAY,KAAK,GAAG;AAEtE,MAAK,KAAM,OAAM,IAAIC;AACrB,MAAK,SAAU,OAAM,IAAIC;CAEzB,MAAM,MAAM,IAAI,KAAK,EAAE,OAAO;AAC9B,KAAI,WAAW;AACf,KAAI,QAAQ,SAAS,aAAc,KAAI,OAAO,OAAO,KAAK;AAE1D,KAAI,SAAU,KAAI,WAAW,mBAAmB,SAAS;AACzD,KAAI,SAAU,KAAI,WAAW,mBAAmB,SAAS;AACzD,KAAI,YAAY,GAAG,SAAS;AAE5B,KAAI,WAAY,KAAI,aAAa,IAAI,WAAW,iBAAiB,WAAW,CAAC;AAC7E,KAAI,QAAQ,MAAO,KAAI,aAAa,IAAI,WAAW,UAAU;AAE7D,QAAO,IAAI,UAAU;AACrB"}
1
+ {"version":3,"file":"url.cjs","names":["MissingDatabaseHost","MissingDatabaseName"],"sources":["../../src/pg/url.ts"],"sourcesContent":["/**\n * Composing a `postgres://` connection string.\n *\n * One direction only. Parsing exists in `pg` already — every consumer hands the\n * string straight to `new Pool({ connectionString })` — so a parser here would\n * be surface whose only caller is its own round-trip test.\n *\n * What is *not* already solved is composition: a provisioned instance exposes\n * host, port, database, username, and password as five separate values, and the\n * construct reads one URL. Doing that join by hand is where `search_path`\n * quietly goes missing.\n *\n * A URL built here is a secret — it carries the role's password. See\n * {@link redactDatabaseUrl} before writing one anywhere.\n */\n\nimport { MissingDatabaseHost, MissingDatabaseName } from '../errors';\n\n/** What a Postgres connection string addresses. */\nexport interface PostgresAddress {\n\thost: string;\n\tport?: number;\n\t/** The logical database. */\n\tdatabase: string;\n\t/** The role connecting — which is what decides DDL versus DML. */\n\tusername?: string;\n\tpassword?: string;\n\t/**\n\t * The schema to resolve unqualified names against.\n\t *\n\t * Normally pinned on the *role* instead (`ALTER ROLE … SET search_path`), so\n\t * it does not appear in the URL at all. It is carried here for the one case\n\t * with no role of its own to pin — connecting as the cluster master under\n\t * `roles: false` — where without it every query looks in `public`.\n\t *\n\t * Postgres-specific by nature, not just by spelling: it becomes a libpq\n\t * connection option, and no other engine accepts one. MySQL has no schemas\n\t * distinct from databases at all.\n\t */\n\tsearchPath?: string;\n\t/** Off only for local development, where there is no certificate to verify. */\n\tssl?: boolean;\n}\n\nconst SCHEME = 'postgres:';\n\nconst DEFAULT_PORT = 5432;\n\n/**\n * `search_path` travels as a libpq connection option, not a query parameter.\n * The server reads `options`; a plain `?search_path=app` is accepted by the URL\n * parser, ignored by the server, and produces a database that looks empty.\n * The `-c` prefix is what makes it a runtime configuration setting.\n */\nfunction searchPathOption(schema: string): string {\n\treturn `-c search_path=${schema}`;\n}\n\n/** Compose an address into a connection string. */\nexport function build(address: PostgresAddress): string {\n\tconst { host, port, database, username, password, searchPath, ssl } = address;\n\n\tif (!host) throw new MissingDatabaseHost();\n\tif (!database) throw new MissingDatabaseName();\n\n\tconst url = new URL(`${SCHEME}//`);\n\turl.hostname = host;\n\tif (port && port !== DEFAULT_PORT) url.port = String(port);\n\t// Username before password: WHATWG URL drops a password set without one.\n\tif (username) url.username = encodeURIComponent(username);\n\tif (password) url.password = encodeURIComponent(password);\n\turl.pathname = `/${database}`;\n\n\tif (searchPath) url.searchParams.set('options', searchPathOption(searchPath));\n\tif (ssl === false) url.searchParams.set('sslmode', 'disable');\n\n\treturn url.toString();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA4CA,MAAM,SAAS;AAEf,MAAM,eAAe;;;;;;;AAQrB,SAAS,iBAAiB,QAAwB;CACjD,OAAO,kBAAkB;AAC1B;;AAGA,SAAgB,MAAM,SAAkC;CACvD,MAAM,EAAE,MAAM,MAAM,UAAU,UAAU,UAAU,YAAY,QAAQ;CAEtE,IAAI,CAAC,MAAM,MAAM,IAAIA,eAAAA,oBAAoB;CACzC,IAAI,CAAC,UAAU,MAAM,IAAIC,eAAAA,oBAAoB;CAE7C,MAAM,MAAM,IAAI,IAAI,GAAG,OAAO,GAAG;CACjC,IAAI,WAAW;CACf,IAAI,QAAQ,SAAS,cAAc,IAAI,OAAO,OAAO,IAAI;CAEzD,IAAI,UAAU,IAAI,WAAW,mBAAmB,QAAQ;CACxD,IAAI,UAAU,IAAI,WAAW,mBAAmB,QAAQ;CACxD,IAAI,WAAW,IAAI;CAEnB,IAAI,YAAY,IAAI,aAAa,IAAI,WAAW,iBAAiB,UAAU,CAAC;CAC5E,IAAI,QAAQ,OAAO,IAAI,aAAa,IAAI,WAAW,SAAS;CAE5D,OAAO,IAAI,SAAS;AACrB"}
package/dist/pg/url.d.cts CHANGED
@@ -15,7 +15,7 @@
15
15
  * {@link redactDatabaseUrl} before writing one anywhere.
16
16
  */
17
17
  /** What a Postgres connection string addresses. */
18
- interface PostgresAddress {
18
+ export interface PostgresAddress {
19
19
  host: string;
20
20
  port?: number;
21
21
  /** The logical database. */
@@ -40,8 +40,6 @@ interface PostgresAddress {
40
40
  ssl?: boolean;
41
41
  }
42
42
  /** Compose an address into a connection string. */
43
- declare function build(address: PostgresAddress): string;
44
- //# sourceMappingURL=url.d.ts.map
43
+ export declare function build(address: PostgresAddress): string;
45
44
  //#endregion
46
- export { PostgresAddress, build };
47
45
  //# sourceMappingURL=url.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"url.d.cts","names":[],"sources":["../../src/pg/url.ts"],"sourcesContent":[],"mappings":";;AAmBA;AAwCA;;;;;;;;;;;;;;UAxCiB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBAwCD,KAAA,UAAe"}
1
+ {"version":3,"file":"url.d.cts","names":[],"sources":["../../src/pg/url.ts"],"mappings":";;;;;;;;;;;;;;;;;iBAmBiB;EAChB;EACA;;EAEA;;EAEA;EACA;;;;;;;;;;;;;EAaA;;EAEA;;;wBAkBe,MAAM,SAAS"}
package/dist/pg/url.d.mts CHANGED
@@ -15,7 +15,7 @@
15
15
  * {@link redactDatabaseUrl} before writing one anywhere.
16
16
  */
17
17
  /** What a Postgres connection string addresses. */
18
- interface PostgresAddress {
18
+ export interface PostgresAddress {
19
19
  host: string;
20
20
  port?: number;
21
21
  /** The logical database. */
@@ -40,8 +40,6 @@ interface PostgresAddress {
40
40
  ssl?: boolean;
41
41
  }
42
42
  /** Compose an address into a connection string. */
43
- declare function build(address: PostgresAddress): string;
44
- //# sourceMappingURL=url.d.ts.map
43
+ export declare function build(address: PostgresAddress): string;
45
44
  //#endregion
46
- export { PostgresAddress, build };
47
45
  //# sourceMappingURL=url.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"url.d.mts","names":[],"sources":["../../src/pg/url.ts"],"sourcesContent":[],"mappings":";;AAmBA;AAwCA;;;;;;;;;;;;;;UAxCiB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBAwCD,KAAA,UAAe"}
1
+ {"version":3,"file":"url.d.mts","names":[],"sources":["../../src/pg/url.ts"],"mappings":";;;;;;;;;;;;;;;;;iBAmBiB;EAChB;EACA;;EAEA;;EAEA;EACA;;;;;;;;;;;;;EAaA;;EAEA;;;wBAkBe,MAAM,SAAS"}
package/dist/pg/url.mjs CHANGED
@@ -1,6 +1,20 @@
1
- import { MissingDatabaseHost, MissingDatabaseName } from "../errors-CfFCan-X.mjs";
2
-
1
+ import { MissingDatabaseHost, MissingDatabaseName } from "../errors.mjs";
3
2
  //#region src/pg/url.ts
3
+ /**
4
+ * Composing a `postgres://` connection string.
5
+ *
6
+ * One direction only. Parsing exists in `pg` already — every consumer hands the
7
+ * string straight to `new Pool({ connectionString })` — so a parser here would
8
+ * be surface whose only caller is its own round-trip test.
9
+ *
10
+ * What is *not* already solved is composition: a provisioned instance exposes
11
+ * host, port, database, username, and password as five separate values, and the
12
+ * construct reads one URL. Doing that join by hand is where `search_path`
13
+ * quietly goes missing.
14
+ *
15
+ * A URL built here is a secret — it carries the role's password. See
16
+ * {@link redactDatabaseUrl} before writing one anywhere.
17
+ */
4
18
  const SCHEME = "postgres:";
5
19
  const DEFAULT_PORT = 5432;
6
20
  /**
@@ -27,7 +41,7 @@ function build(address) {
27
41
  if (ssl === false) url.searchParams.set("sslmode", "disable");
28
42
  return url.toString();
29
43
  }
30
-
31
44
  //#endregion
32
45
  export { build };
46
+
33
47
  //# sourceMappingURL=url.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"url.mjs","names":["schema: string","address: PostgresAddress"],"sources":["../../src/pg/url.ts"],"sourcesContent":["/**\n * Composing a `postgres://` connection string.\n *\n * One direction only. Parsing exists in `pg` already — every consumer hands the\n * string straight to `new Pool({ connectionString })` — so a parser here would\n * be surface whose only caller is its own round-trip test.\n *\n * What is *not* already solved is composition: a provisioned instance exposes\n * host, port, database, username, and password as five separate values, and the\n * construct reads one URL. Doing that join by hand is where `search_path`\n * quietly goes missing.\n *\n * A URL built here is a secret — it carries the role's password. See\n * {@link redactDatabaseUrl} before writing one anywhere.\n */\n\nimport { MissingDatabaseHost, MissingDatabaseName } from '../errors';\n\n/** What a Postgres connection string addresses. */\nexport interface PostgresAddress {\n\thost: string;\n\tport?: number;\n\t/** The logical database. */\n\tdatabase: string;\n\t/** The role connecting — which is what decides DDL versus DML. */\n\tusername?: string;\n\tpassword?: string;\n\t/**\n\t * The schema to resolve unqualified names against.\n\t *\n\t * Normally pinned on the *role* instead (`ALTER ROLE … SET search_path`), so\n\t * it does not appear in the URL at all. It is carried here for the one case\n\t * with no role of its own to pin — connecting as the cluster master under\n\t * `roles: false` — where without it every query looks in `public`.\n\t *\n\t * Postgres-specific by nature, not just by spelling: it becomes a libpq\n\t * connection option, and no other engine accepts one. MySQL has no schemas\n\t * distinct from databases at all.\n\t */\n\tsearchPath?: string;\n\t/** Off only for local development, where there is no certificate to verify. */\n\tssl?: boolean;\n}\n\nconst SCHEME = 'postgres:';\n\nconst DEFAULT_PORT = 5432;\n\n/**\n * `search_path` travels as a libpq connection option, not a query parameter.\n * The server reads `options`; a plain `?search_path=app` is accepted by the URL\n * parser, ignored by the server, and produces a database that looks empty.\n * The `-c` prefix is what makes it a runtime configuration setting.\n */\nfunction searchPathOption(schema: string): string {\n\treturn `-c search_path=${schema}`;\n}\n\n/** Compose an address into a connection string. */\nexport function build(address: PostgresAddress): string {\n\tconst { host, port, database, username, password, searchPath, ssl } = address;\n\n\tif (!host) throw new MissingDatabaseHost();\n\tif (!database) throw new MissingDatabaseName();\n\n\tconst url = new URL(`${SCHEME}//`);\n\turl.hostname = host;\n\tif (port && port !== DEFAULT_PORT) url.port = String(port);\n\t// Username before password: WHATWG URL drops a password set without one.\n\tif (username) url.username = encodeURIComponent(username);\n\tif (password) url.password = encodeURIComponent(password);\n\turl.pathname = `/${database}`;\n\n\tif (searchPath) url.searchParams.set('options', searchPathOption(searchPath));\n\tif (ssl === false) url.searchParams.set('sslmode', 'disable');\n\n\treturn url.toString();\n}\n"],"mappings":";;;AA4CA,MAAM,SAAS;AAEf,MAAM,eAAe;;;;;;;AAQrB,SAAS,iBAAiBA,QAAwB;AACjD,SAAQ,iBAAiB,OAAO;AAChC;;AAGD,SAAgB,MAAMC,SAAkC;CACvD,MAAM,EAAE,MAAM,MAAM,UAAU,UAAU,UAAU,YAAY,KAAK,GAAG;AAEtE,MAAK,KAAM,OAAM,IAAI;AACrB,MAAK,SAAU,OAAM,IAAI;CAEzB,MAAM,MAAM,IAAI,KAAK,EAAE,OAAO;AAC9B,KAAI,WAAW;AACf,KAAI,QAAQ,SAAS,aAAc,KAAI,OAAO,OAAO,KAAK;AAE1D,KAAI,SAAU,KAAI,WAAW,mBAAmB,SAAS;AACzD,KAAI,SAAU,KAAI,WAAW,mBAAmB,SAAS;AACzD,KAAI,YAAY,GAAG,SAAS;AAE5B,KAAI,WAAY,KAAI,aAAa,IAAI,WAAW,iBAAiB,WAAW,CAAC;AAC7E,KAAI,QAAQ,MAAO,KAAI,aAAa,IAAI,WAAW,UAAU;AAE7D,QAAO,IAAI,UAAU;AACrB"}
1
+ {"version":3,"file":"url.mjs","names":[],"sources":["../../src/pg/url.ts"],"sourcesContent":["/**\n * Composing a `postgres://` connection string.\n *\n * One direction only. Parsing exists in `pg` already — every consumer hands the\n * string straight to `new Pool({ connectionString })` — so a parser here would\n * be surface whose only caller is its own round-trip test.\n *\n * What is *not* already solved is composition: a provisioned instance exposes\n * host, port, database, username, and password as five separate values, and the\n * construct reads one URL. Doing that join by hand is where `search_path`\n * quietly goes missing.\n *\n * A URL built here is a secret — it carries the role's password. See\n * {@link redactDatabaseUrl} before writing one anywhere.\n */\n\nimport { MissingDatabaseHost, MissingDatabaseName } from '../errors';\n\n/** What a Postgres connection string addresses. */\nexport interface PostgresAddress {\n\thost: string;\n\tport?: number;\n\t/** The logical database. */\n\tdatabase: string;\n\t/** The role connecting — which is what decides DDL versus DML. */\n\tusername?: string;\n\tpassword?: string;\n\t/**\n\t * The schema to resolve unqualified names against.\n\t *\n\t * Normally pinned on the *role* instead (`ALTER ROLE … SET search_path`), so\n\t * it does not appear in the URL at all. It is carried here for the one case\n\t * with no role of its own to pin — connecting as the cluster master under\n\t * `roles: false` — where without it every query looks in `public`.\n\t *\n\t * Postgres-specific by nature, not just by spelling: it becomes a libpq\n\t * connection option, and no other engine accepts one. MySQL has no schemas\n\t * distinct from databases at all.\n\t */\n\tsearchPath?: string;\n\t/** Off only for local development, where there is no certificate to verify. */\n\tssl?: boolean;\n}\n\nconst SCHEME = 'postgres:';\n\nconst DEFAULT_PORT = 5432;\n\n/**\n * `search_path` travels as a libpq connection option, not a query parameter.\n * The server reads `options`; a plain `?search_path=app` is accepted by the URL\n * parser, ignored by the server, and produces a database that looks empty.\n * The `-c` prefix is what makes it a runtime configuration setting.\n */\nfunction searchPathOption(schema: string): string {\n\treturn `-c search_path=${schema}`;\n}\n\n/** Compose an address into a connection string. */\nexport function build(address: PostgresAddress): string {\n\tconst { host, port, database, username, password, searchPath, ssl } = address;\n\n\tif (!host) throw new MissingDatabaseHost();\n\tif (!database) throw new MissingDatabaseName();\n\n\tconst url = new URL(`${SCHEME}//`);\n\turl.hostname = host;\n\tif (port && port !== DEFAULT_PORT) url.port = String(port);\n\t// Username before password: WHATWG URL drops a password set without one.\n\tif (username) url.username = encodeURIComponent(username);\n\tif (password) url.password = encodeURIComponent(password);\n\turl.pathname = `/${database}`;\n\n\tif (searchPath) url.searchParams.set('options', searchPathOption(searchPath));\n\tif (ssl === false) url.searchParams.set('sslmode', 'disable');\n\n\treturn url.toString();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA4CA,MAAM,SAAS;AAEf,MAAM,eAAe;;;;;;;AAQrB,SAAS,iBAAiB,QAAwB;CACjD,OAAO,kBAAkB;AAC1B;;AAGA,SAAgB,MAAM,SAAkC;CACvD,MAAM,EAAE,MAAM,MAAM,UAAU,UAAU,UAAU,YAAY,QAAQ;CAEtE,IAAI,CAAC,MAAM,MAAM,IAAI,oBAAoB;CACzC,IAAI,CAAC,UAAU,MAAM,IAAI,oBAAoB;CAE7C,MAAM,MAAM,IAAI,IAAI,GAAG,OAAO,GAAG;CACjC,IAAI,WAAW;CACf,IAAI,QAAQ,SAAS,cAAc,IAAI,OAAO,OAAO,IAAI;CAEzD,IAAI,UAAU,IAAI,WAAW,mBAAmB,QAAQ;CACxD,IAAI,UAAU,IAAI,WAAW,mBAAmB,QAAQ;CACxD,IAAI,WAAW,IAAI;CAEnB,IAAI,YAAY,IAAI,aAAa,IAAI,WAAW,iBAAiB,UAAU,CAAC;CAC5E,IAAI,QAAQ,OAAO,IAAI,aAAa,IAAI,WAAW,SAAS;CAE5D,OAAO,IAAI,SAAS;AACrB"}
package/dist/rls.cjs CHANGED
@@ -1,29 +1,6 @@
1
- //#region rolldown:runtime
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
-
23
- //#endregion
24
- const require_kysely = require('./kysely-LWvS2ekY.cjs');
25
- const kysely = __toESM(require("kysely"));
26
-
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_kysely = require("./kysely.cjs");
3
+ let kysely = require("kysely");
27
4
  //#region src/rls.ts
28
5
  /**
29
6
  * Execute a callback within a transaction with RLS context variables set.
@@ -55,8 +32,7 @@ async function withRlsContext(db, context, callback, options) {
55
32
  for (const [key, value] of Object.entries(context)) {
56
33
  if (value === null || value === void 0) continue;
57
34
  const settingName = `${prefix}.${key}`;
58
- const settingValue = String(value);
59
- await kysely.sql`SELECT set_config(${settingName}, ${settingValue}, true)`.execute(trx);
35
+ await kysely.sql`SELECT set_config(${settingName}, ${String(value)}, true)`.execute(trx);
60
36
  }
61
37
  return callback(trx);
62
38
  }, options?.settings);
@@ -65,8 +41,8 @@ async function withRlsContext(db, context, callback, options) {
65
41
  * Bypass marker symbol for explicitly skipping RLS context.
66
42
  */
67
43
  const RLS_BYPASS = Symbol.for("geekmidas.rls.bypass");
68
-
69
44
  //#endregion
70
45
  exports.RLS_BYPASS = RLS_BYPASS;
71
46
  exports.withRlsContext = withRlsContext;
47
+
72
48
  //# sourceMappingURL=rls.cjs.map
package/dist/rls.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"rls.cjs","names":["db: DatabaseConnection<DB>","context: RlsContext","callback: (trx: Transaction<DB>) => Promise<T>","options?: WithRlsContextOptions"],"sources":["../src/rls.ts"],"sourcesContent":["import type { Transaction } from 'kysely';\nimport { sql } from 'kysely';\nimport {\n\ttype DatabaseConnection,\n\ttype TransactionSettings,\n\twithTransaction,\n} from './kysely';\n\n/**\n * RLS context - key-value pairs to set as PostgreSQL session variables.\n * Keys become `prefix.key` (e.g., `app.user_id`).\n */\nexport interface RlsContext {\n\t[key: string]: string | number | boolean | null | undefined;\n}\n\n/**\n * Options for withRlsContext function.\n */\nexport interface WithRlsContextOptions {\n\t/** Prefix for PostgreSQL session variables (default: 'app') */\n\tprefix?: string;\n\t/** Transaction settings (isolation level) */\n\tsettings?: TransactionSettings;\n}\n\n/**\n * Execute a callback within a transaction with RLS context variables set.\n *\n * Sets PostgreSQL session variables using `SET LOCAL` which scopes them to the\n * current transaction. Variables are automatically cleared when the transaction\n * ends (commit or rollback).\n *\n * @example\n * ```ts\n * await withRlsContext(\n * db,\n * { user_id: session.userId, tenant_id: session.tenantId },\n * async (trx) => {\n * // RLS policies can now use current_setting('app.user_id')\n * return trx.selectFrom('orders').selectAll().execute();\n * }\n * );\n * ```\n *\n * @param db - Database connection (Kysely, Transaction, or ControlledTransaction)\n * @param context - Key-value pairs to set as session variables\n * @param callback - Function to execute within the RLS context\n * @param options - Optional prefix and transaction settings\n */\nexport async function withRlsContext<DB, T>(\n\tdb: DatabaseConnection<DB>,\n\tcontext: RlsContext,\n\tcallback: (trx: Transaction<DB>) => Promise<T>,\n\toptions?: WithRlsContextOptions,\n): Promise<T> {\n\tconst prefix = options?.prefix ?? 'app';\n\n\treturn withTransaction(\n\t\tdb,\n\t\tasync (trx) => {\n\t\t\t// Set each context variable using SET LOCAL (scoped to transaction)\n\t\t\tfor (const [key, value] of Object.entries(context)) {\n\t\t\t\tif (value === null || value === undefined) continue;\n\n\t\t\t\tconst settingName = `${prefix}.${key}`;\n\t\t\t\tconst settingValue = String(value);\n\n\t\t\t\t// Use raw SQL for SET LOCAL with proper escaping\n\t\t\t\t// The setting name is an identifier, value is a string literal\n\t\t\t\tawait sql`SELECT set_config(${settingName}, ${settingValue}, true)`.execute(\n\t\t\t\t\ttrx,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn callback(trx);\n\t\t},\n\t\toptions?.settings,\n\t);\n}\n\n/**\n * Bypass marker symbol for explicitly skipping RLS context.\n */\nexport const RLS_BYPASS = Symbol.for('geekmidas.rls.bypass');\n\n/**\n * Type for RLS bypass marker.\n */\nexport type RlsBypass = typeof RLS_BYPASS;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,eAAsB,eACrBA,IACAC,SACAC,UACAC,SACa;CACb,MAAM,SAAS,SAAS,UAAU;AAElC,QAAO,+BACN,IACA,OAAO,QAAQ;AAEd,OAAK,MAAM,CAAC,KAAK,MAAM,IAAI,OAAO,QAAQ,QAAQ,EAAE;AACnD,OAAI,UAAU,QAAQ,iBAAqB;GAE3C,MAAM,eAAe,EAAE,OAAO,GAAG,IAAI;GACrC,MAAM,eAAe,OAAO,MAAM;AAIlC,SAAM,WAAI,oBAAoB,YAAY,IAAI,aAAa,SAAS,QACnE,IACA;EACD;AAED,SAAO,SAAS,IAAI;CACpB,GACD,SAAS,SACT;AACD;;;;AAKD,MAAa,aAAa,OAAO,IAAI,uBAAuB"}
1
+ {"version":3,"file":"rls.cjs","names":["withTransaction"],"sources":["../src/rls.ts"],"sourcesContent":["import type { Transaction } from 'kysely';\nimport { sql } from 'kysely';\nimport {\n\ttype DatabaseConnection,\n\ttype TransactionSettings,\n\twithTransaction,\n} from './kysely';\n\n/**\n * RLS context - key-value pairs to set as PostgreSQL session variables.\n * Keys become `prefix.key` (e.g., `app.user_id`).\n */\nexport interface RlsContext {\n\t[key: string]: string | number | boolean | null | undefined;\n}\n\n/**\n * Options for withRlsContext function.\n */\nexport interface WithRlsContextOptions {\n\t/** Prefix for PostgreSQL session variables (default: 'app') */\n\tprefix?: string;\n\t/** Transaction settings (isolation level) */\n\tsettings?: TransactionSettings;\n}\n\n/**\n * Execute a callback within a transaction with RLS context variables set.\n *\n * Sets PostgreSQL session variables using `SET LOCAL` which scopes them to the\n * current transaction. Variables are automatically cleared when the transaction\n * ends (commit or rollback).\n *\n * @example\n * ```ts\n * await withRlsContext(\n * db,\n * { user_id: session.userId, tenant_id: session.tenantId },\n * async (trx) => {\n * // RLS policies can now use current_setting('app.user_id')\n * return trx.selectFrom('orders').selectAll().execute();\n * }\n * );\n * ```\n *\n * @param db - Database connection (Kysely, Transaction, or ControlledTransaction)\n * @param context - Key-value pairs to set as session variables\n * @param callback - Function to execute within the RLS context\n * @param options - Optional prefix and transaction settings\n */\nexport async function withRlsContext<DB, T>(\n\tdb: DatabaseConnection<DB>,\n\tcontext: RlsContext,\n\tcallback: (trx: Transaction<DB>) => Promise<T>,\n\toptions?: WithRlsContextOptions,\n): Promise<T> {\n\tconst prefix = options?.prefix ?? 'app';\n\n\treturn withTransaction(\n\t\tdb,\n\t\tasync (trx) => {\n\t\t\t// Set each context variable using SET LOCAL (scoped to transaction)\n\t\t\tfor (const [key, value] of Object.entries(context)) {\n\t\t\t\tif (value === null || value === undefined) continue;\n\n\t\t\t\tconst settingName = `${prefix}.${key}`;\n\t\t\t\tconst settingValue = String(value);\n\n\t\t\t\t// Use raw SQL for SET LOCAL with proper escaping\n\t\t\t\t// The setting name is an identifier, value is a string literal\n\t\t\t\tawait sql`SELECT set_config(${settingName}, ${settingValue}, true)`.execute(\n\t\t\t\t\ttrx,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn callback(trx);\n\t\t},\n\t\toptions?.settings,\n\t);\n}\n\n/**\n * Bypass marker symbol for explicitly skipping RLS context.\n */\nexport const RLS_BYPASS = Symbol.for('geekmidas.rls.bypass');\n\n/**\n * Type for RLS bypass marker.\n */\nexport type RlsBypass = typeof RLS_BYPASS;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,eAAsB,eACrB,IACA,SACA,UACA,SACa;CACb,MAAM,SAAS,SAAS,UAAU;CAElC,OAAOA,eAAAA,gBACN,IACA,OAAO,QAAQ;EAEd,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAAG;GACnD,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW;GAE3C,MAAM,cAAc,GAAG,OAAO,GAAG;GAKjC,MAAM,OAAA,GAAG,qBAAqB,YAAY,IAJrB,OAAO,KAI6B,EAAE,SAAS,QACnE,GACD;EACD;EAEA,OAAO,SAAS,GAAG;CACpB,GACA,SAAS,QACV;AACD;;;;AAKA,MAAa,aAAa,OAAO,IAAI,sBAAsB"}
package/dist/rls.d.cts CHANGED
@@ -1,19 +1,17 @@
1
- import { DatabaseConnection, TransactionSettings } from "./kysely-D3eeuId4.cjs";
1
+ import { DatabaseConnection, TransactionSettings } from "./kysely.cjs";
2
2
  import { Transaction } from "kysely";
3
-
4
3
  //#region src/rls.d.ts
5
-
6
4
  /**
7
5
  * RLS context - key-value pairs to set as PostgreSQL session variables.
8
6
  * Keys become `prefix.key` (e.g., `app.user_id`).
9
7
  */
10
- interface RlsContext {
8
+ export interface RlsContext {
11
9
  [key: string]: string | number | boolean | null | undefined;
12
10
  }
13
11
  /**
14
12
  * Options for withRlsContext function.
15
13
  */
16
- interface WithRlsContextOptions {
14
+ export interface WithRlsContextOptions {
17
15
  /** Prefix for PostgreSQL session variables (default: 'app') */
18
16
  prefix?: string;
19
17
  /** Transaction settings (isolation level) */
@@ -43,16 +41,14 @@ interface WithRlsContextOptions {
43
41
  * @param callback - Function to execute within the RLS context
44
42
  * @param options - Optional prefix and transaction settings
45
43
  */
46
- declare function withRlsContext<DB, T>(db: DatabaseConnection<DB>, context: RlsContext, callback: (trx: Transaction<DB>) => Promise<T>, options?: WithRlsContextOptions): Promise<T>;
44
+ export declare function withRlsContext<DB, T>(db: DatabaseConnection<DB>, context: RlsContext, callback: (trx: Transaction<DB>) => Promise<T>, options?: WithRlsContextOptions): Promise<T>;
47
45
  /**
48
46
  * Bypass marker symbol for explicitly skipping RLS context.
49
47
  */
50
- declare const RLS_BYPASS: unique symbol;
48
+ export declare const RLS_BYPASS: unique symbol;
51
49
  /**
52
50
  * Type for RLS bypass marker.
53
51
  */
54
- type RlsBypass = typeof RLS_BYPASS;
55
- //# sourceMappingURL=rls.d.ts.map
52
+ export type RlsBypass = typeof RLS_BYPASS;
56
53
  //#endregion
57
- export { RLS_BYPASS, RlsBypass, RlsContext, WithRlsContextOptions, withRlsContext };
58
54
  //# sourceMappingURL=rls.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rls.d.cts","names":[],"sources":["../src/rls.ts"],"sourcesContent":[],"mappings":";;;;;;;AAYA;AAOA;AA+BsB,UAtCL,UAAA,CAsCmB;EAAA,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,MAAA,GAAA,OAAA,GAAA,IAAA,GAAA,SAAA;;;;;AAGnB,UAlCA,qBAAA,CAkCA;EAAW;EAAkB,MAAT,CAAA,EAAA,MAAA;EAAO;EACZ,QACrB,CAAA,EAhCC,mBAgCD;;AAAD;AA6BV;AAKA;;;;;;;;;;;;;;;;;;;;;;iBAvCsB,0BACjB,mBAAmB,cACd,4BACO,YAAY,QAAQ,QAAQ,cAClC,wBACR,QAAQ;;;;cA6BE;;;;KAKD,SAAA,UAAmB"}
1
+ {"version":3,"file":"rls.d.cts","names":[],"sources":["../src/rls.ts"],"mappings":";;;;;;;iBAYiB;GACf;;;;;iBAMe;;EAEhB;;EAEA,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;wBA2BU,eAAe,IAAI,GACxC,IAAI,mBAAmB,KACvB,SAAS,YACT,WAAW,KAAK,YAAY,QAAQ,QAAQ,IAC5C,UAAU,wBACR,QAAQ;;;;qBA6BE;;;;YAKD,mBAAmB"}
package/dist/rls.d.mts CHANGED
@@ -1,19 +1,17 @@
1
- import { DatabaseConnection, TransactionSettings } from "./kysely-BAedYsvn.mjs";
1
+ import { DatabaseConnection, TransactionSettings } from "./kysely.mjs";
2
2
  import { Transaction } from "kysely";
3
-
4
3
  //#region src/rls.d.ts
5
-
6
4
  /**
7
5
  * RLS context - key-value pairs to set as PostgreSQL session variables.
8
6
  * Keys become `prefix.key` (e.g., `app.user_id`).
9
7
  */
10
- interface RlsContext {
8
+ export interface RlsContext {
11
9
  [key: string]: string | number | boolean | null | undefined;
12
10
  }
13
11
  /**
14
12
  * Options for withRlsContext function.
15
13
  */
16
- interface WithRlsContextOptions {
14
+ export interface WithRlsContextOptions {
17
15
  /** Prefix for PostgreSQL session variables (default: 'app') */
18
16
  prefix?: string;
19
17
  /** Transaction settings (isolation level) */
@@ -43,16 +41,14 @@ interface WithRlsContextOptions {
43
41
  * @param callback - Function to execute within the RLS context
44
42
  * @param options - Optional prefix and transaction settings
45
43
  */
46
- declare function withRlsContext<DB, T>(db: DatabaseConnection<DB>, context: RlsContext, callback: (trx: Transaction<DB>) => Promise<T>, options?: WithRlsContextOptions): Promise<T>;
44
+ export declare function withRlsContext<DB, T>(db: DatabaseConnection<DB>, context: RlsContext, callback: (trx: Transaction<DB>) => Promise<T>, options?: WithRlsContextOptions): Promise<T>;
47
45
  /**
48
46
  * Bypass marker symbol for explicitly skipping RLS context.
49
47
  */
50
- declare const RLS_BYPASS: unique symbol;
48
+ export declare const RLS_BYPASS: unique symbol;
51
49
  /**
52
50
  * Type for RLS bypass marker.
53
51
  */
54
- type RlsBypass = typeof RLS_BYPASS;
55
- //# sourceMappingURL=rls.d.ts.map
52
+ export type RlsBypass = typeof RLS_BYPASS;
56
53
  //#endregion
57
- export { RLS_BYPASS, RlsBypass, RlsContext, WithRlsContextOptions, withRlsContext };
58
54
  //# sourceMappingURL=rls.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rls.d.mts","names":[],"sources":["../src/rls.ts"],"sourcesContent":[],"mappings":";;;;;;;AAYA;AAOA;AA+BsB,UAtCL,UAAA,CAsCmB;EAAA,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,MAAA,GAAA,OAAA,GAAA,IAAA,GAAA,SAAA;;;;;AAGnB,UAlCA,qBAAA,CAkCA;EAAW;EAAkB,MAAT,CAAA,EAAA,MAAA;EAAO;EACZ,QACrB,CAAA,EAhCC,mBAgCD;;AAAD;AA6BV;AAKA;;;;;;;;;;;;;;;;;;;;;;iBAvCsB,0BACjB,mBAAmB,cACd,4BACO,YAAY,QAAQ,QAAQ,cAClC,wBACR,QAAQ;;;;cA6BE;;;;KAKD,SAAA,UAAmB"}
1
+ {"version":3,"file":"rls.d.mts","names":[],"sources":["../src/rls.ts"],"mappings":";;;;;;;iBAYiB;GACf;;;;;iBAMe;;EAEhB;;EAEA,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;wBA2BU,eAAe,IAAI,GACxC,IAAI,mBAAmB,KACvB,SAAS,YACT,WAAW,KAAK,YAAY,QAAQ,QAAQ,IAC5C,UAAU,wBACR,QAAQ;;;;qBA6BE;;;;YAKD,mBAAmB"}
package/dist/rls.mjs CHANGED
@@ -1,6 +1,5 @@
1
- import { withTransaction } from "./kysely-Bo_Zg-D3.mjs";
1
+ import { withTransaction } from "./kysely.mjs";
2
2
  import { sql } from "kysely";
3
-
4
3
  //#region src/rls.ts
5
4
  /**
6
5
  * Execute a callback within a transaction with RLS context variables set.
@@ -32,8 +31,7 @@ async function withRlsContext(db, context, callback, options) {
32
31
  for (const [key, value] of Object.entries(context)) {
33
32
  if (value === null || value === void 0) continue;
34
33
  const settingName = `${prefix}.${key}`;
35
- const settingValue = String(value);
36
- await sql`SELECT set_config(${settingName}, ${settingValue}, true)`.execute(trx);
34
+ await sql`SELECT set_config(${settingName}, ${String(value)}, true)`.execute(trx);
37
35
  }
38
36
  return callback(trx);
39
37
  }, options?.settings);
@@ -42,7 +40,7 @@ async function withRlsContext(db, context, callback, options) {
42
40
  * Bypass marker symbol for explicitly skipping RLS context.
43
41
  */
44
42
  const RLS_BYPASS = Symbol.for("geekmidas.rls.bypass");
45
-
46
43
  //#endregion
47
44
  export { RLS_BYPASS, withRlsContext };
45
+
48
46
  //# sourceMappingURL=rls.mjs.map
package/dist/rls.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"rls.mjs","names":["db: DatabaseConnection<DB>","context: RlsContext","callback: (trx: Transaction<DB>) => Promise<T>","options?: WithRlsContextOptions"],"sources":["../src/rls.ts"],"sourcesContent":["import type { Transaction } from 'kysely';\nimport { sql } from 'kysely';\nimport {\n\ttype DatabaseConnection,\n\ttype TransactionSettings,\n\twithTransaction,\n} from './kysely';\n\n/**\n * RLS context - key-value pairs to set as PostgreSQL session variables.\n * Keys become `prefix.key` (e.g., `app.user_id`).\n */\nexport interface RlsContext {\n\t[key: string]: string | number | boolean | null | undefined;\n}\n\n/**\n * Options for withRlsContext function.\n */\nexport interface WithRlsContextOptions {\n\t/** Prefix for PostgreSQL session variables (default: 'app') */\n\tprefix?: string;\n\t/** Transaction settings (isolation level) */\n\tsettings?: TransactionSettings;\n}\n\n/**\n * Execute a callback within a transaction with RLS context variables set.\n *\n * Sets PostgreSQL session variables using `SET LOCAL` which scopes them to the\n * current transaction. Variables are automatically cleared when the transaction\n * ends (commit or rollback).\n *\n * @example\n * ```ts\n * await withRlsContext(\n * db,\n * { user_id: session.userId, tenant_id: session.tenantId },\n * async (trx) => {\n * // RLS policies can now use current_setting('app.user_id')\n * return trx.selectFrom('orders').selectAll().execute();\n * }\n * );\n * ```\n *\n * @param db - Database connection (Kysely, Transaction, or ControlledTransaction)\n * @param context - Key-value pairs to set as session variables\n * @param callback - Function to execute within the RLS context\n * @param options - Optional prefix and transaction settings\n */\nexport async function withRlsContext<DB, T>(\n\tdb: DatabaseConnection<DB>,\n\tcontext: RlsContext,\n\tcallback: (trx: Transaction<DB>) => Promise<T>,\n\toptions?: WithRlsContextOptions,\n): Promise<T> {\n\tconst prefix = options?.prefix ?? 'app';\n\n\treturn withTransaction(\n\t\tdb,\n\t\tasync (trx) => {\n\t\t\t// Set each context variable using SET LOCAL (scoped to transaction)\n\t\t\tfor (const [key, value] of Object.entries(context)) {\n\t\t\t\tif (value === null || value === undefined) continue;\n\n\t\t\t\tconst settingName = `${prefix}.${key}`;\n\t\t\t\tconst settingValue = String(value);\n\n\t\t\t\t// Use raw SQL for SET LOCAL with proper escaping\n\t\t\t\t// The setting name is an identifier, value is a string literal\n\t\t\t\tawait sql`SELECT set_config(${settingName}, ${settingValue}, true)`.execute(\n\t\t\t\t\ttrx,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn callback(trx);\n\t\t},\n\t\toptions?.settings,\n\t);\n}\n\n/**\n * Bypass marker symbol for explicitly skipping RLS context.\n */\nexport const RLS_BYPASS = Symbol.for('geekmidas.rls.bypass');\n\n/**\n * Type for RLS bypass marker.\n */\nexport type RlsBypass = typeof RLS_BYPASS;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,eAAsB,eACrBA,IACAC,SACAC,UACAC,SACa;CACb,MAAM,SAAS,SAAS,UAAU;AAElC,QAAO,gBACN,IACA,OAAO,QAAQ;AAEd,OAAK,MAAM,CAAC,KAAK,MAAM,IAAI,OAAO,QAAQ,QAAQ,EAAE;AACnD,OAAI,UAAU,QAAQ,iBAAqB;GAE3C,MAAM,eAAe,EAAE,OAAO,GAAG,IAAI;GACrC,MAAM,eAAe,OAAO,MAAM;AAIlC,SAAM,IAAI,oBAAoB,YAAY,IAAI,aAAa,SAAS,QACnE,IACA;EACD;AAED,SAAO,SAAS,IAAI;CACpB,GACD,SAAS,SACT;AACD;;;;AAKD,MAAa,aAAa,OAAO,IAAI,uBAAuB"}
1
+ {"version":3,"file":"rls.mjs","names":[],"sources":["../src/rls.ts"],"sourcesContent":["import type { Transaction } from 'kysely';\nimport { sql } from 'kysely';\nimport {\n\ttype DatabaseConnection,\n\ttype TransactionSettings,\n\twithTransaction,\n} from './kysely';\n\n/**\n * RLS context - key-value pairs to set as PostgreSQL session variables.\n * Keys become `prefix.key` (e.g., `app.user_id`).\n */\nexport interface RlsContext {\n\t[key: string]: string | number | boolean | null | undefined;\n}\n\n/**\n * Options for withRlsContext function.\n */\nexport interface WithRlsContextOptions {\n\t/** Prefix for PostgreSQL session variables (default: 'app') */\n\tprefix?: string;\n\t/** Transaction settings (isolation level) */\n\tsettings?: TransactionSettings;\n}\n\n/**\n * Execute a callback within a transaction with RLS context variables set.\n *\n * Sets PostgreSQL session variables using `SET LOCAL` which scopes them to the\n * current transaction. Variables are automatically cleared when the transaction\n * ends (commit or rollback).\n *\n * @example\n * ```ts\n * await withRlsContext(\n * db,\n * { user_id: session.userId, tenant_id: session.tenantId },\n * async (trx) => {\n * // RLS policies can now use current_setting('app.user_id')\n * return trx.selectFrom('orders').selectAll().execute();\n * }\n * );\n * ```\n *\n * @param db - Database connection (Kysely, Transaction, or ControlledTransaction)\n * @param context - Key-value pairs to set as session variables\n * @param callback - Function to execute within the RLS context\n * @param options - Optional prefix and transaction settings\n */\nexport async function withRlsContext<DB, T>(\n\tdb: DatabaseConnection<DB>,\n\tcontext: RlsContext,\n\tcallback: (trx: Transaction<DB>) => Promise<T>,\n\toptions?: WithRlsContextOptions,\n): Promise<T> {\n\tconst prefix = options?.prefix ?? 'app';\n\n\treturn withTransaction(\n\t\tdb,\n\t\tasync (trx) => {\n\t\t\t// Set each context variable using SET LOCAL (scoped to transaction)\n\t\t\tfor (const [key, value] of Object.entries(context)) {\n\t\t\t\tif (value === null || value === undefined) continue;\n\n\t\t\t\tconst settingName = `${prefix}.${key}`;\n\t\t\t\tconst settingValue = String(value);\n\n\t\t\t\t// Use raw SQL for SET LOCAL with proper escaping\n\t\t\t\t// The setting name is an identifier, value is a string literal\n\t\t\t\tawait sql`SELECT set_config(${settingName}, ${settingValue}, true)`.execute(\n\t\t\t\t\ttrx,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn callback(trx);\n\t\t},\n\t\toptions?.settings,\n\t);\n}\n\n/**\n * Bypass marker symbol for explicitly skipping RLS context.\n */\nexport const RLS_BYPASS = Symbol.for('geekmidas.rls.bypass');\n\n/**\n * Type for RLS bypass marker.\n */\nexport type RlsBypass = typeof RLS_BYPASS;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,eAAsB,eACrB,IACA,SACA,UACA,SACa;CACb,MAAM,SAAS,SAAS,UAAU;CAElC,OAAO,gBACN,IACA,OAAO,QAAQ;EAEd,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAAG;GACnD,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW;GAE3C,MAAM,cAAc,GAAG,OAAO,GAAG;GAKjC,MAAM,GAAG,qBAAqB,YAAY,IAJrB,OAAO,KAI6B,EAAE,SAAS,QACnE,GACD;EACD;EAEA,OAAO,SAAS,GAAG;CACpB,GACA,SAAS,QACV;AACD;;;;AAKA,MAAa,aAAa,OAAO,IAAI,sBAAsB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/db",
3
- "version": "10.0.0-alpha.4",
3
+ "version": "10.0.0-alpha.40",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -108,7 +108,7 @@
108
108
  "knex": ">=3.3.0",
109
109
  "objection": ">=3.1.5",
110
110
  "db-errors": "~0.2.3",
111
- "vitest": "~3.2.4"
111
+ "vitest": "~5.0.2"
112
112
  },
113
113
  "scripts": {
114
114
  "ts": "tsc --noEmit --skipLibCheck src/**/*.ts"
@@ -1,69 +0,0 @@
1
- //#region src/errors.ts
2
- /**
3
- * Database URL errors and redaction — engine-neutral.
4
- *
5
- * Nothing here is Postgres-specific: every engine addresses itself as a URL
6
- * carrying a password, and "must name a host" holds for all of them. The parts
7
- * that differ — libpq's `options=-c search_path=…`, which no other engine
8
- * accepts — live under the driver they belong to.
9
- *
10
- * Messages state the rule, which is constant; the offending value is a field.
11
- * An interpolated message cannot be matched on and carries user input into
12
- * every log line that touches it.
13
- */
14
- /**
15
- * Replace the password in a connection string with `***`.
16
- *
17
- * A connection string is a secret, and the places it is most likely to be
18
- * written down — a log line, an error, a crash report shipped to a third party
19
- * — are the places nobody is reading carefully. This exists so that redacting
20
- * is the easy thing to do at each of them.
21
- *
22
- * Safe on input that is not a URL: it returns a constant rather than throwing,
23
- * because this runs on paths that must not fail. Anything unparseable is
24
- * treated as potentially secret and withheld entirely rather than echoed.
25
- */
26
- function redactDatabaseUrl(url) {
27
- try {
28
- const parsed = new URL(url);
29
- if (!parsed.password) return url;
30
- parsed.password = "***";
31
- return parsed.toString();
32
- } catch {
33
- return "<unparseable url>";
34
- }
35
- }
36
- /**
37
- * Base for an address that cannot become a connection string.
38
- *
39
- * Deliberately carries no URL field: these are thrown while *composing* one, so
40
- * there is no URL yet — only the parts, which include the password.
41
- */
42
- var DatabaseAddressError = class extends Error {
43
- constructor(message) {
44
- super(message);
45
- this.name = new.target.name;
46
- }
47
- };
48
- /** An address with no host to connect to. */
49
- var MissingDatabaseHost = class extends DatabaseAddressError {
50
- constructor() {
51
- super("A database address must name a host");
52
- }
53
- };
54
- /**
55
- * An address with no database name.
56
- *
57
- * Postgres silently falls back to the connecting role's name, so the app
58
- * connects successfully to the wrong database and fails later with confusing
59
- * "relation does not exist" errors. Rejecting it here is much cheaper.
60
- */
61
- var MissingDatabaseName = class extends DatabaseAddressError {
62
- constructor() {
63
- super("A database address must name a database");
64
- }
65
- };
66
-
67
- //#endregion
68
- export { DatabaseAddressError, MissingDatabaseHost, MissingDatabaseName, redactDatabaseUrl };
69
- //# sourceMappingURL=errors-CfFCan-X.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors-CfFCan-X.mjs","names":["url: string","message: string"],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Database URL errors and redaction — engine-neutral.\n *\n * Nothing here is Postgres-specific: every engine addresses itself as a URL\n * carrying a password, and \"must name a host\" holds for all of them. The parts\n * that differ — libpq's `options=-c search_path=…`, which no other engine\n * accepts — live under the driver they belong to.\n *\n * Messages state the rule, which is constant; the offending value is a field.\n * An interpolated message cannot be matched on and carries user input into\n * every log line that touches it.\n */\n\n/**\n * Replace the password in a connection string with `***`.\n *\n * A connection string is a secret, and the places it is most likely to be\n * written down — a log line, an error, a crash report shipped to a third party\n * — are the places nobody is reading carefully. This exists so that redacting\n * is the easy thing to do at each of them.\n *\n * Safe on input that is not a URL: it returns a constant rather than throwing,\n * because this runs on paths that must not fail. Anything unparseable is\n * treated as potentially secret and withheld entirely rather than echoed.\n */\nexport function redactDatabaseUrl(url: string): string {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\tif (!parsed.password) return url;\n\t\tparsed.password = '***';\n\t\treturn parsed.toString();\n\t} catch {\n\t\treturn '<unparseable url>';\n\t}\n}\n\n/**\n * Base for an address that cannot become a connection string.\n *\n * Deliberately carries no URL field: these are thrown while *composing* one, so\n * there is no URL yet — only the parts, which include the password.\n */\nexport abstract class DatabaseAddressError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = new.target.name;\n\t}\n}\n\n/** An address with no host to connect to. */\nexport class MissingDatabaseHost extends DatabaseAddressError {\n\tconstructor() {\n\t\tsuper('A database address must name a host');\n\t}\n}\n\n/**\n * An address with no database name.\n *\n * Postgres silently falls back to the connecting role's name, so the app\n * connects successfully to the wrong database and fails later with confusing\n * \"relation does not exist\" errors. Rejecting it here is much cheaper.\n */\nexport class MissingDatabaseName extends DatabaseAddressError {\n\tconstructor() {\n\t\tsuper('A database address must name a database');\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,kBAAkBA,KAAqB;AACtD,KAAI;EACH,MAAM,SAAS,IAAI,IAAI;AACvB,OAAK,OAAO,SAAU,QAAO;AAC7B,SAAO,WAAW;AAClB,SAAO,OAAO,UAAU;CACxB,QAAO;AACP,SAAO;CACP;AACD;;;;;;;AAQD,IAAsB,uBAAtB,cAAmD,MAAM;CACxD,YAAYC,SAAiB;AAC5B,QAAM,QAAQ;AACd,OAAK,OAAO,IAAI,OAAO;CACvB;AACD;;AAGD,IAAa,sBAAb,cAAyC,qBAAqB;CAC7D,cAAc;AACb,QAAM,sCAAsC;CAC5C;AACD;;;;;;;;AASD,IAAa,sBAAb,cAAyC,qBAAqB;CAC7D,cAAc;AACb,QAAM,0CAA0C;CAChD;AACD"}