@nodii/db-rls 0.0.1 → 0.2.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.
Files changed (50) hide show
  1. package/bin/migrate-gen.mjs +12 -0
  2. package/dist/auth-schema.d.ts +45 -0
  3. package/dist/auth-schema.d.ts.map +1 -0
  4. package/dist/auth-schema.js +101 -0
  5. package/dist/auth-schema.js.map +1 -0
  6. package/dist/configure.d.ts +45 -0
  7. package/dist/configure.d.ts.map +1 -0
  8. package/dist/configure.js +85 -0
  9. package/dist/configure.js.map +1 -0
  10. package/dist/context.d.ts +43 -0
  11. package/dist/context.d.ts.map +1 -0
  12. package/dist/context.js +98 -0
  13. package/dist/context.js.map +1 -0
  14. package/dist/drizzle-client.d.ts +22 -0
  15. package/dist/drizzle-client.d.ts.map +1 -0
  16. package/dist/drizzle-client.js +109 -0
  17. package/dist/drizzle-client.js.map +1 -0
  18. package/dist/in-memory-pool.d.ts +31 -0
  19. package/dist/in-memory-pool.d.ts.map +1 -0
  20. package/dist/in-memory-pool.js +53 -0
  21. package/dist/in-memory-pool.js.map +1 -0
  22. package/dist/index.d.ts +21 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +35 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/init.d.ts +18 -0
  27. package/dist/init.d.ts.map +1 -0
  28. package/dist/init.js +40 -0
  29. package/dist/init.js.map +1 -0
  30. package/dist/migrate-gen.d.ts +41 -0
  31. package/dist/migrate-gen.d.ts.map +1 -0
  32. package/dist/migrate-gen.js +123 -0
  33. package/dist/migrate-gen.js.map +1 -0
  34. package/dist/pg-pool-adapter.d.ts +44 -0
  35. package/dist/pg-pool-adapter.d.ts.map +1 -0
  36. package/dist/pg-pool-adapter.js +111 -0
  37. package/dist/pg-pool-adapter.js.map +1 -0
  38. package/dist/rls-policy-gen.d.ts +26 -0
  39. package/dist/rls-policy-gen.d.ts.map +1 -0
  40. package/dist/rls-policy-gen.js +81 -0
  41. package/dist/rls-policy-gen.js.map +1 -0
  42. package/dist/rls-policy.d.ts +30 -0
  43. package/dist/rls-policy.d.ts.map +1 -0
  44. package/dist/rls-policy.js +81 -0
  45. package/dist/rls-policy.js.map +1 -0
  46. package/dist/types.d.ts +71 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +56 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +31 -7
@@ -0,0 +1,81 @@
1
+ // withRls — emits the 3-policy DDL for a tenant-scoped table per § 5.5.
2
+ //
3
+ // One policy per role: nodii_owner (FOR ALL bypass), authenticated (FOR
4
+ // ALL gated on auth.user_tenant_ids), nodii_services (FOR ALL gated on
5
+ // app.tenant_id GUC). Per § 5.5 the `{ allow }` option restricts which
6
+ // CRUD verbs the non-owner roles get; owner ALWAYS gets FOR ALL.
7
+ //
8
+ // Output byte-equal across TS / Python / Go ports.
9
+ const DEFAULT_ALLOW = ["SELECT", "INSERT"];
10
+ /**
11
+ * Emits the policy DDL block for `table`.
12
+ *
13
+ * Returned string ends with `\n` for file-friendliness. Single
14
+ * deterministic output (no whitespace drift, stable ordering) so the
15
+ * parity-fence test is byte-equal.
16
+ *
17
+ * If `table` is schema-qualified (`schema.tbl`), the ALTER + ON clauses
18
+ * use the qualified form; the policy NAME uses only the unqualified
19
+ * tail (Postgres policy names live in the table's namespace and must
20
+ * be a plain identifier, not a dotted path).
21
+ */
22
+ export function withRls(table, opts = {}) {
23
+ const tenantColumn = opts.tenantColumn ?? "tenant_id";
24
+ const verbs = opts.allow && opts.allow.length > 0 ? opts.allow : DEFAULT_ALLOW;
25
+ const policyBase = unqualifiedName(table);
26
+ const lines = [];
27
+ lines.push(`ALTER TABLE ${table} ENABLE ROW LEVEL SECURITY;`);
28
+ // 1. Owner policy: FOR ALL bypass.
29
+ lines.push(`CREATE POLICY ${policyBase}_owner_policy ON ${table} FOR ALL TO nodii_owner USING (true) WITH CHECK (true);`);
30
+ // 2. Authenticated policy: gated on auth.user_tenant_ids(auth.get_user_id()).
31
+ // One policy per verb so the surface honestly reflects sanctioned
32
+ // mutations.
33
+ for (const verb of verbs) {
34
+ lines.push(authPolicyFor(table, policyBase, tenantColumn, verb));
35
+ }
36
+ // 3. Services policy: gated on app.tenant_id GUC.
37
+ for (const verb of verbs) {
38
+ lines.push(servicesPolicyFor(table, policyBase, tenantColumn, verb));
39
+ }
40
+ return `${lines.join("\n")}\n`;
41
+ }
42
+ function unqualifiedName(table) {
43
+ // Strip optional schema prefix + any surrounding quotes.
44
+ const tail = table.includes(".") ? (table.split(".").pop() ?? table) : table;
45
+ return tail.replace(/^"|"$/g, "");
46
+ }
47
+ /**
48
+ * Per-table grants — non-owner roles need GRANT on the verbs in `allow`.
49
+ * Owner doesn't need a grant (BYPASSRLS + table-owner).
50
+ */
51
+ export function generateTableGrants(table, opts = {}) {
52
+ const verbs = opts.allow && opts.allow.length > 0 ? opts.allow : DEFAULT_ALLOW;
53
+ const verbList = verbs.join(", ");
54
+ const lines = [
55
+ `GRANT ${verbList} ON ${table} TO nodii_services;`,
56
+ `GRANT ${verbList} ON ${table} TO authenticated;`,
57
+ ];
58
+ return `${lines.join("\n")}\n`;
59
+ }
60
+ function authPolicyFor(table, policyBase, tenantColumn, verb) {
61
+ // Authenticated role: tenant_id must be one of auth.user_tenant_ids(auth.get_user_id()).
62
+ const cond = `${tenantColumn} IN (SELECT auth.user_tenant_ids(auth.get_user_id()))`;
63
+ return policyLine(table, policyBase, "auth", verb, "authenticated", cond);
64
+ }
65
+ function servicesPolicyFor(table, policyBase, tenantColumn, verb) {
66
+ // Services role: tenant_id must equal current_setting('app.tenant_id').
67
+ const cond = `${tenantColumn} = auth.get_tenant_id()`;
68
+ return policyLine(table, policyBase, "svc", verb, "nodii_services", cond);
69
+ }
70
+ function policyLine(table, policyBase, flavor, verb, role, cond) {
71
+ const lcVerb = verb.toLowerCase();
72
+ const policyName = `${policyBase}_${flavor}_${lcVerb}_policy`;
73
+ if (verb === "INSERT") {
74
+ return `CREATE POLICY ${policyName} ON ${table} FOR INSERT TO ${role} WITH CHECK (${cond});`;
75
+ }
76
+ if (verb === "UPDATE") {
77
+ return `CREATE POLICY ${policyName} ON ${table} FOR UPDATE TO ${role} USING (${cond}) WITH CHECK (${cond});`;
78
+ }
79
+ return `CREATE POLICY ${policyName} ON ${table} FOR ${verb} TO ${role} USING (${cond});`;
80
+ }
81
+ //# sourceMappingURL=rls-policy-gen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls-policy-gen.js","sourceRoot":"","sources":["../src/rls-policy-gen.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { GenerateRlsPolicyOpts } from "./types";
2
+ /**
3
+ * Generates the canonical 3-policy DDL block:
4
+ *
5
+ * 1. `<table>_owner_policy` — nodii_owner FOR ALL USING (true) WITH CHECK (true)
6
+ * 2. `<table>_auth_policy` — authenticated FOR ALL USING (<tenant_col> IN auth.user_tenant_ids(...))
7
+ * 3. `<table>_services_policy` — nodii_services FOR ALL USING (<tenant_col> = current_setting('app.tenant_id')::uuid)
8
+ *
9
+ * Plus the `ALTER TABLE ... ENABLE ROW LEVEL SECURITY;` prelude.
10
+ *
11
+ * Output is deterministic (no whitespace drift, stable column ordering)
12
+ * for cross-language byte-equality.
13
+ */
14
+ export declare function generateRlsPolicy(opts: GenerateRlsPolicyOpts): string;
15
+ /**
16
+ * authSchemaHelpers — returns the canonical SECURITY DEFINER SQL block
17
+ * defining the helper functions services rely on (`auth.set_user_id`,
18
+ * `auth.set_tenant_id`, `auth.set_source_ip`, `auth.get_user_id`,
19
+ * `auth.get_tenant_id`, `auth.user_tenant_ids`).
20
+ *
21
+ * Service-owned helpers (`auth.user_tenant_ids` joins
22
+ * `employees_mirror` + `customers_mirror`) — per spec § 5.7, the real
23
+ * version is generated by `migrate-gen` and JOINs the service's mirror
24
+ * tables. v0.1.0 ships the stub variant — returns the empty set so
25
+ * RLS fails closed if the service hasn't replaced the helper yet.
26
+ *
27
+ * Output is byte-equal across TS / Python / Go ports.
28
+ */
29
+ export declare function authSchemaHelpers(): string;
30
+ //# sourceMappingURL=rls-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls-policy.d.ts","sourceRoot":"","sources":["../src/rls-policy.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAErD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG,MAAM,CAWrE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAkC1C"}
@@ -0,0 +1,81 @@
1
+ // generateRlsPolicy — returns the canonical 3-policy DDL string for a
2
+ // tenant-scoped table per audit doctrine § 12 + PII doctrine § 8.
3
+ //
4
+ // Spec § 5.5 (A1 posture): every tenant-scoped table gets RLS via this
5
+ // helper. The caller writes the DDL into a migration. Output is
6
+ // byte-equal across TS / Python / Go ports — the polyglot parity-fence
7
+ // test asserts this directly.
8
+ /**
9
+ * Generates the canonical 3-policy DDL block:
10
+ *
11
+ * 1. `<table>_owner_policy` — nodii_owner FOR ALL USING (true) WITH CHECK (true)
12
+ * 2. `<table>_auth_policy` — authenticated FOR ALL USING (<tenant_col> IN auth.user_tenant_ids(...))
13
+ * 3. `<table>_services_policy` — nodii_services FOR ALL USING (<tenant_col> = current_setting('app.tenant_id')::uuid)
14
+ *
15
+ * Plus the `ALTER TABLE ... ENABLE ROW LEVEL SECURITY;` prelude.
16
+ *
17
+ * Output is deterministic (no whitespace drift, stable column ordering)
18
+ * for cross-language byte-equality.
19
+ */
20
+ export function generateRlsPolicy(opts) {
21
+ const table = opts.table;
22
+ const tenantColumn = opts.tenantColumn ?? "tenant_id";
23
+ // Stable single-string with trailing newline per SQL file convention.
24
+ const lines = [
25
+ `ALTER TABLE ${table} ENABLE ROW LEVEL SECURITY;`,
26
+ `CREATE POLICY ${table}_owner_policy ON ${table} FOR ALL TO nodii_owner USING (true) WITH CHECK (true);`,
27
+ `CREATE POLICY ${table}_auth_policy ON ${table} FOR ALL TO authenticated USING (${tenantColumn} IN (SELECT auth.user_tenant_ids(current_setting('app.user_id')::uuid))) WITH CHECK (${tenantColumn} IN (SELECT auth.user_tenant_ids(current_setting('app.user_id')::uuid)));`,
28
+ `CREATE POLICY ${table}_services_policy ON ${table} FOR ALL TO nodii_services USING (${tenantColumn} = current_setting('app.tenant_id')::uuid) WITH CHECK (${tenantColumn} = current_setting('app.tenant_id')::uuid);`,
29
+ ];
30
+ return `${lines.join("\n")}\n`;
31
+ }
32
+ /**
33
+ * authSchemaHelpers — returns the canonical SECURITY DEFINER SQL block
34
+ * defining the helper functions services rely on (`auth.set_user_id`,
35
+ * `auth.set_tenant_id`, `auth.set_source_ip`, `auth.get_user_id`,
36
+ * `auth.get_tenant_id`, `auth.user_tenant_ids`).
37
+ *
38
+ * Service-owned helpers (`auth.user_tenant_ids` joins
39
+ * `employees_mirror` + `customers_mirror`) — per spec § 5.7, the real
40
+ * version is generated by `migrate-gen` and JOINs the service's mirror
41
+ * tables. v0.1.0 ships the stub variant — returns the empty set so
42
+ * RLS fails closed if the service hasn't replaced the helper yet.
43
+ *
44
+ * Output is byte-equal across TS / Python / Go ports.
45
+ */
46
+ export function authSchemaHelpers() {
47
+ // Note: stable formatting (single space indent, no trailing whitespace)
48
+ // for cross-language byte-equality.
49
+ return [
50
+ "CREATE SCHEMA IF NOT EXISTS auth;",
51
+ "",
52
+ "CREATE OR REPLACE FUNCTION auth.set_user_id(p_user_id uuid) RETURNS void",
53
+ "LANGUAGE sql AS $$ SELECT set_config('app.user_id', p_user_id::text, true) $$;",
54
+ "",
55
+ "CREATE OR REPLACE FUNCTION auth.set_tenant_id(p_tenant_id uuid) RETURNS void",
56
+ "LANGUAGE sql AS $$ SELECT set_config('app.tenant_id', p_tenant_id::text, true) $$;",
57
+ "",
58
+ "CREATE OR REPLACE FUNCTION auth.set_source_ip(p_source_ip text) RETURNS void",
59
+ "LANGUAGE sql AS $$ SELECT set_config('app.source_ip', p_source_ip, true) $$;",
60
+ "",
61
+ "CREATE OR REPLACE FUNCTION auth.get_user_id() RETURNS uuid",
62
+ "LANGUAGE sql STABLE AS $$ SELECT nullif(current_setting('app.user_id', true), '')::uuid $$;",
63
+ "",
64
+ "CREATE OR REPLACE FUNCTION auth.get_tenant_id() RETURNS uuid",
65
+ "LANGUAGE sql STABLE AS $$ SELECT nullif(current_setting('app.tenant_id', true), '')::uuid $$;",
66
+ "",
67
+ "-- TODO db-rls-v0.1.x: migrate-gen replaces this stub with a UNION over",
68
+ "-- the service's employees_mirror + customers_mirror tables.",
69
+ "CREATE OR REPLACE FUNCTION auth.user_tenant_ids(p_user_id uuid) RETURNS SETOF uuid",
70
+ "LANGUAGE sql STABLE AS $$ SELECT NULL::uuid WHERE false $$;",
71
+ "",
72
+ "GRANT EXECUTE ON FUNCTION auth.set_user_id(uuid) TO nodii_services, authenticated;",
73
+ "GRANT EXECUTE ON FUNCTION auth.set_tenant_id(uuid) TO nodii_services, authenticated;",
74
+ "GRANT EXECUTE ON FUNCTION auth.set_source_ip(text) TO nodii_services, authenticated;",
75
+ "GRANT EXECUTE ON FUNCTION auth.get_user_id() TO nodii_services, authenticated;",
76
+ "GRANT EXECUTE ON FUNCTION auth.get_tenant_id() TO nodii_services, authenticated;",
77
+ "GRANT EXECUTE ON FUNCTION auth.user_tenant_ids(uuid) TO nodii_services, authenticated;",
78
+ "",
79
+ ].join("\n");
80
+ }
81
+ //# sourceMappingURL=rls-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls-policy.js","sourceRoot":"","sources":["../src/rls-policy.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,kEAAkE;AAClE,EAAE;AACF,uEAAuE;AACvE,gEAAgE;AAChE,uEAAuE;AACvE,8BAA8B;AAI9B;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAA2B;IAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzB,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,WAAW,CAAC;IACtD,sEAAsE;IACtE,MAAM,KAAK,GAAG;QACZ,eAAe,KAAK,6BAA6B;QACjD,iBAAiB,KAAK,oBAAoB,KAAK,yDAAyD;QACxG,iBAAiB,KAAK,mBAAmB,KAAK,oCAAoC,YAAY,wFAAwF,YAAY,2EAA2E;QAC7Q,iBAAiB,KAAK,uBAAuB,KAAK,qCAAqC,YAAY,0DAA0D,YAAY,6CAA6C;KACvN,CAAC;IACF,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB;IAC/B,wEAAwE;IACxE,oCAAoC;IACpC,OAAO;QACL,mCAAmC;QACnC,EAAE;QACF,0EAA0E;QAC1E,gFAAgF;QAChF,EAAE;QACF,8EAA8E;QAC9E,oFAAoF;QACpF,EAAE;QACF,8EAA8E;QAC9E,8EAA8E;QAC9E,EAAE;QACF,4DAA4D;QAC5D,6FAA6F;QAC7F,EAAE;QACF,8DAA8D;QAC9D,+FAA+F;QAC/F,EAAE;QACF,yEAAyE;QACzE,8DAA8D;QAC9D,oFAAoF;QACpF,6DAA6D;QAC7D,EAAE;QACF,oFAAoF;QACpF,sFAAsF;QACtF,sFAAsF;QACtF,gFAAgF;QAChF,kFAAkF;QAClF,wFAAwF;QACxF,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,71 @@
1
+ /** PoolKind names map 1:1 to the canonical three roles. */
2
+ export type PoolKind = "owner" | "services" | "authenticated";
3
+ /** Pluggable transaction handle — substrate-agnostic. v0.1.0 ships the
4
+ * in-memory transcript-recording test double; pg / psycopg / pgx adapters
5
+ * arrive in v0.1.x. */
6
+ export interface PgTx {
7
+ /** Execute a parameterized SQL statement. */
8
+ exec(sql: string, params?: readonly unknown[]): Promise<void>;
9
+ /** Set a per-tx GUC via `set_config($1, $2, true)`. */
10
+ setConfig(key: string, value: string): Promise<void>;
11
+ /** Internal — read the recorded transcript (test double only). */
12
+ readonly poolKind: PoolKind;
13
+ }
14
+ /** Pluggable Postgres pool — yields a tx for the lifetime of `fn`. */
15
+ export interface PgPool {
16
+ /** Identifier for telemetry + error messages. */
17
+ readonly kind: PoolKind;
18
+ /** Acquire a connection, BEGIN a tx, yield, and COMMIT/ROLLBACK. */
19
+ withTransaction<T>(fn: (tx: PgTx) => Promise<T>): Promise<T>;
20
+ }
21
+ /** Auth context bound by `@nodii/auth-sdk` middleware. Mirrors the
22
+ * fields autoDispatch reads — kept structural so the real auth-sdk
23
+ * context shape can grow without breaking us. */
24
+ export interface AuthContext {
25
+ /** Verified principal id (sub). */
26
+ userId: string;
27
+ /** Resolved tenant id (pinned by the JWT). */
28
+ tenantId: string;
29
+ /** Optional source IP for forensic GUC binding. */
30
+ sourceIp?: string;
31
+ }
32
+ /** withUserContext opts. */
33
+ export interface UserContextOpts {
34
+ userId: string;
35
+ tenantId: string;
36
+ sourceIp?: string;
37
+ }
38
+ /** withSystemContext opts. */
39
+ export interface SystemContextOpts {
40
+ tenantId: string;
41
+ }
42
+ /** autoDispatch opts. */
43
+ export interface AutoDispatchOpts {
44
+ authContext?: AuthContext;
45
+ tenantId?: string;
46
+ }
47
+ /** generateRlsPolicy opts. */
48
+ export interface GenerateRlsPolicyOpts {
49
+ /** Table name (unqualified). */
50
+ table: string;
51
+ /** Tenant column name. Defaults to `tenant_id`. */
52
+ tenantColumn?: string;
53
+ }
54
+ export declare class DbRlsError extends Error {
55
+ constructor(message: string);
56
+ }
57
+ export declare class DbRlsNotInitialized extends DbRlsError {
58
+ constructor();
59
+ }
60
+ export declare class PoolUnavailable extends DbRlsError {
61
+ readonly pool: PoolKind;
62
+ constructor(pool: PoolKind);
63
+ }
64
+ export declare class MissingTenantContext extends DbRlsError {
65
+ constructor();
66
+ }
67
+ export declare class TransactionRolledBack extends DbRlsError {
68
+ readonly cause: unknown;
69
+ constructor(cause: unknown);
70
+ }
71
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAoBA,2DAA2D;AAC3D,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,eAAe,CAAC;AAE9D;;wBAEwB;AACxB,MAAM,WAAW,IAAI;IACnB,6CAA6C;IAC7C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,uDAAuD;IACvD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED,sEAAsE;AACtE,MAAM,WAAW,MAAM;IACrB,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,oEAAoE;IACpE,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC9D;AAED;;kDAEkD;AAClD,MAAM,WAAW,WAAW;IAC1B,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,4BAA4B;AAC5B,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,8BAA8B;AAC9B,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,yBAAyB;AACzB,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,8BAA8B;AAC9B,MAAM,WAAW,qBAAqB;IACpC,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAKD,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,mBAAoB,SAAQ,UAAU;;CAOlD;AAED,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;gBACZ,IAAI,EAAE,QAAQ;CAK3B;AAED,qBAAa,oBAAqB,SAAQ,UAAU;;CAOnD;AAED,qBAAa,qBAAsB,SAAQ,UAAU;IACnD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;gBACZ,KAAK,EAAE,OAAO;CAS3B"}
package/dist/types.js ADDED
@@ -0,0 +1,56 @@
1
+ // Core type surface for @nodii/db-rls v0.1.0.
2
+ //
3
+ // Spec: planning hub feature_doc serviceId=nodii-libs docKey=db-rls
4
+ // (A1 posture — RLS on every tenant-scoped table — locked 2026-05-25).
5
+ //
6
+ // v0.1.0 ships:
7
+ // - initDbRls / requireDbRls + DbRlsNotInitialized
8
+ // - PgPool / PgTx pluggable Postgres surface (in-memory test double)
9
+ // - withUserContext / withSystemContext / withOwnerContext entry points
10
+ // - autoDispatch (auth-sdk-style context routing)
11
+ // - generateRlsPolicy (canonical 3-policy DDL per audit doctrine § 12 +
12
+ // PII doctrine § 8)
13
+ // - authSchemaHelpers (canonical SECURITY DEFINER SQL block)
14
+ // - Typed-error hierarchy rooted at DbRlsError
15
+ //
16
+ // Deferred (`// TODO db-rls-v0.1.x:`):
17
+ // - Real pg / psycopg / pgx pool wiring
18
+ // - Drizzle / SQLAlchemy / sqlc client wrappers
19
+ // - migrate-gen CLI that emits per-service auth.sql + per-table RLS DDL
20
+ // ----------------------------------------------------------------------------
21
+ // Typed-error hierarchy.
22
+ export class DbRlsError extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = "DbRlsError";
26
+ }
27
+ }
28
+ export class DbRlsNotInitialized extends DbRlsError {
29
+ constructor() {
30
+ super("@nodii/db-rls is not initialized — call initDbRls({ owner, services, authenticated }) at process start.");
31
+ this.name = "DbRlsNotInitialized";
32
+ }
33
+ }
34
+ export class PoolUnavailable extends DbRlsError {
35
+ pool;
36
+ constructor(pool) {
37
+ super(`pool_unavailable: the ${pool} pool was not supplied to initDbRls.`);
38
+ this.name = "PoolUnavailable";
39
+ this.pool = pool;
40
+ }
41
+ }
42
+ export class MissingTenantContext extends DbRlsError {
43
+ constructor() {
44
+ super("missing_tenant_context: autoDispatch requires either authContext or tenantId.");
45
+ this.name = "MissingTenantContext";
46
+ }
47
+ }
48
+ export class TransactionRolledBack extends DbRlsError {
49
+ cause;
50
+ constructor(cause) {
51
+ super(`transaction_rolled_back: ${cause instanceof Error ? cause.message : String(cause)}`);
52
+ this.name = "TransactionRolledBack";
53
+ this.cause = cause;
54
+ }
55
+ }
56
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,EAAE;AACF,oEAAoE;AACpE,uEAAuE;AACvE,EAAE;AACF,gBAAgB;AAChB,qDAAqD;AACrD,uEAAuE;AACvE,0EAA0E;AAC1E,oDAAoD;AACpD,0EAA0E;AAC1E,wBAAwB;AACxB,+DAA+D;AAC/D,iDAAiD;AACjD,EAAE;AACF,uCAAuC;AACvC,0CAA0C;AAC1C,kDAAkD;AAClD,0EAA0E;AA+D1E,+EAA+E;AAC/E,yBAAyB;AAEzB,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,UAAU;IACjD;QACE,KAAK,CACH,yGAAyG,CAC1G,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,UAAU;IACpC,IAAI,CAAW;IACxB,YAAY,IAAc;QACxB,KAAK,CAAC,yBAAyB,IAAI,sCAAsC,CAAC,CAAC;QAC3E,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,UAAU;IAClD;QACE,KAAK,CACH,+EAA+E,CAChF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,UAAU;IAC1C,KAAK,CAAU;IACxB,YAAY,KAAc;QACxB,KAAK,CACH,4BACE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,CACH,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF"}
package/package.json CHANGED
@@ -1,33 +1,57 @@
1
1
  {
2
2
  "name": "@nodii/db-rls",
3
- "version": "0.0.1",
4
- "description": "Placeholder for @nodii/db-rls — implementation lands at v0.1.0",
3
+ "version": "0.2.0",
4
+ "description": "Three-pool RLS dispatch + GUC-set + entry-point helpers + RLS policy DDL generator for the Nodii microservice stack. Polyglot per D159 (TS + Python + Go). Spec: planning hub docKey=db-rls.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "nodii-db-rls": "./bin/migrate-gen.mjs"
11
+ },
9
12
  "exports": {
10
13
  ".": {
11
14
  "types": "./dist/index.d.ts",
12
15
  "default": "./dist/index.js"
13
16
  }
14
17
  },
15
- "files": ["dist"],
18
+ "files": [
19
+ "dist",
20
+ "bin",
21
+ "README.md"
22
+ ],
16
23
  "publishConfig": {
17
- "access": "public",
18
- "provenance": true
24
+ "access": "public"
19
25
  },
20
26
  "scripts": {
21
27
  "build": "tsc",
22
28
  "typecheck": "tsc --noEmit",
23
- "test": "echo 'no tests yet; placeholder package'"
29
+ "test": "bun test"
30
+ },
31
+ "peerDependencies": {
32
+ "@nodii/telemetry": "workspace:*",
33
+ "drizzle-orm": "^0.36.0",
34
+ "pg": "^8.13.0"
35
+ },
36
+ "peerDependenciesMeta": {
37
+ "drizzle-orm": {
38
+ "optional": false
39
+ },
40
+ "pg": {
41
+ "optional": false
42
+ }
24
43
  },
25
44
  "devDependencies": {
45
+ "@nodii/telemetry": "workspace:*",
46
+ "@types/bun": "^1.3.13",
47
+ "@types/pg": "^8.11.10",
48
+ "drizzle-orm": "^0.36.4",
49
+ "pg": "^8.13.1",
26
50
  "typescript": "^5.9.3"
27
51
  },
28
52
  "repository": {
29
53
  "type": "git",
30
- "url": "https://github.com/cognion-nucleus/nodii-libs.git",
54
+ "url": "git+https://github.com/cognion-nucleus/nodii-libs.git",
31
55
  "directory": "ts/db-rls"
32
56
  }
33
57
  }