graphile-plugin-utils 1.12.0 → 1.12.2

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const system_lane_1 = require("../system-lane");
4
+ /**
5
+ * A client whose `withTransaction` behaves like grafast's: it brackets the
6
+ * callback with BEGIN and COMMIT, and rolls back when the callback throws.
7
+ */
8
+ const makeClient = () => {
9
+ const calls = [];
10
+ const client = {
11
+ query: async (opts) => {
12
+ calls.push(opts);
13
+ return { rows: [] };
14
+ },
15
+ withTransaction: async (cb) => {
16
+ calls.push({ text: 'BEGIN' });
17
+ try {
18
+ const result = await cb(client);
19
+ calls.push({ text: 'COMMIT' });
20
+ return result;
21
+ }
22
+ catch (err) {
23
+ calls.push({ text: 'ROLLBACK' });
24
+ throw err;
25
+ }
26
+ }
27
+ };
28
+ return { client, calls };
29
+ };
30
+ const makeWithPgClient = (client, settings) => ((pgSettings, cb) => {
31
+ settings.push(pgSettings);
32
+ return cb(client);
33
+ });
34
+ describe('withSystemLaneClient', () => {
35
+ it('runs the callback in one transaction under a transaction-local role', async () => {
36
+ const { client, calls } = makeClient();
37
+ const settings = [];
38
+ const result = await (0, system_lane_1.withSystemLaneClient)(makeWithPgClient(client, settings), async (tx) => {
39
+ await tx.query({ text: 'SELECT 1' });
40
+ return 'ok';
41
+ });
42
+ expect(result).toBe('ok');
43
+ expect(settings).toEqual([null]);
44
+ expect(calls.map((c) => c.text)).toEqual([
45
+ 'BEGIN',
46
+ 'SELECT set_config($1, $2, true)',
47
+ 'SELECT 1',
48
+ 'COMMIT'
49
+ ]);
50
+ expect(calls[1].values).toEqual(['role', system_lane_1.SYSTEM_LANE_ROLE]);
51
+ });
52
+ it('honours an explicit role', async () => {
53
+ const { client, calls } = makeClient();
54
+ await (0, system_lane_1.withSystemLaneClient)(makeWithPgClient(client, []), async () => null, {
55
+ role: 'anonymous'
56
+ });
57
+ expect(calls[1].values).toEqual(['role', 'anonymous']);
58
+ });
59
+ it('leaves the rollback to the client and rethrows the callback failure', async () => {
60
+ const { client, calls } = makeClient();
61
+ await expect((0, system_lane_1.withSystemLaneClient)(makeWithPgClient(client, []), async () => {
62
+ throw new Error('boom');
63
+ })).rejects.toThrow('boom');
64
+ expect(calls.map((c) => c.text)).toEqual([
65
+ 'BEGIN',
66
+ 'SELECT set_config($1, $2, true)',
67
+ 'ROLLBACK'
68
+ ]);
69
+ });
70
+ });
@@ -0,0 +1,68 @@
1
+ import { SYSTEM_LANE_ROLE, withSystemLaneClient } from '../system-lane';
2
+ /**
3
+ * A client whose `withTransaction` behaves like grafast's: it brackets the
4
+ * callback with BEGIN and COMMIT, and rolls back when the callback throws.
5
+ */
6
+ const makeClient = () => {
7
+ const calls = [];
8
+ const client = {
9
+ query: async (opts) => {
10
+ calls.push(opts);
11
+ return { rows: [] };
12
+ },
13
+ withTransaction: async (cb) => {
14
+ calls.push({ text: 'BEGIN' });
15
+ try {
16
+ const result = await cb(client);
17
+ calls.push({ text: 'COMMIT' });
18
+ return result;
19
+ }
20
+ catch (err) {
21
+ calls.push({ text: 'ROLLBACK' });
22
+ throw err;
23
+ }
24
+ }
25
+ };
26
+ return { client, calls };
27
+ };
28
+ const makeWithPgClient = (client, settings) => ((pgSettings, cb) => {
29
+ settings.push(pgSettings);
30
+ return cb(client);
31
+ });
32
+ describe('withSystemLaneClient', () => {
33
+ it('runs the callback in one transaction under a transaction-local role', async () => {
34
+ const { client, calls } = makeClient();
35
+ const settings = [];
36
+ const result = await withSystemLaneClient(makeWithPgClient(client, settings), async (tx) => {
37
+ await tx.query({ text: 'SELECT 1' });
38
+ return 'ok';
39
+ });
40
+ expect(result).toBe('ok');
41
+ expect(settings).toEqual([null]);
42
+ expect(calls.map((c) => c.text)).toEqual([
43
+ 'BEGIN',
44
+ 'SELECT set_config($1, $2, true)',
45
+ 'SELECT 1',
46
+ 'COMMIT'
47
+ ]);
48
+ expect(calls[1].values).toEqual(['role', SYSTEM_LANE_ROLE]);
49
+ });
50
+ it('honours an explicit role', async () => {
51
+ const { client, calls } = makeClient();
52
+ await withSystemLaneClient(makeWithPgClient(client, []), async () => null, {
53
+ role: 'anonymous'
54
+ });
55
+ expect(calls[1].values).toEqual(['role', 'anonymous']);
56
+ });
57
+ it('leaves the rollback to the client and rethrows the callback failure', async () => {
58
+ const { client, calls } = makeClient();
59
+ await expect(withSystemLaneClient(makeWithPgClient(client, []), async () => {
60
+ throw new Error('boom');
61
+ })).rejects.toThrow('boom');
62
+ expect(calls.map((c) => c.text)).toEqual([
63
+ 'BEGIN',
64
+ 'SELECT set_config($1, $2, true)',
65
+ 'ROLLBACK'
66
+ ]);
67
+ });
68
+ });
package/esm/index.js CHANGED
@@ -3,4 +3,5 @@
3
3
  export { getQueryBuilder } from './query-builder';
4
4
  // pg-resource introspection helpers (connection-filter / pg-aggregates)
5
5
  export { isComputedScalarAttributeResource } from './pg-resources';
6
+ export { SYSTEM_LANE_ROLE, withSystemLaneClient } from './system-lane';
6
7
  export { JSON_SCHEMA_SCALARS, PG_TYPE_SCALARS } from './scalar-maps';
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The system lane: server-owned reads and writes that are not a tenant
3
+ * session — schema metadata, module registration, storage config.
4
+ *
5
+ * Grafast's `withPgClient(null, cb)` acquires a client with NO settings, so
6
+ * `cb` runs with whatever role the pool connected as (in practice `postgres`,
7
+ * a superuser that bypasses RLS) and each statement lands in its own implicit
8
+ * transaction. This helper names the role instead: one transaction with a
9
+ * transaction-local `role`, so the lane's reach is the grants of a named role
10
+ * rather than the connection's. It is the server-owned counterpart to the
11
+ * request lane's `withRequestPgClient`.
12
+ */
13
+ /**
14
+ * Role the system lane runs as: a named role instead of whatever the pool
15
+ * connected as.
16
+ *
17
+ * This is NOT an RLS-subject role. `ROLES.md` documents `administrator` as the
18
+ * BYPASSRLS role, so the lane still bypasses RLS — what changes is that it no
19
+ * longer inherits the connection's superuser and its work is one transaction.
20
+ * Narrowing it further needs grants that do not exist yet.
21
+ */
22
+ export const SYSTEM_LANE_ROLE = 'administrator';
23
+ /**
24
+ * Run `cb` in one transaction under the system lane's role.
25
+ *
26
+ * The role is set with a transaction-local `set_config`, so it is discarded
27
+ * with the transaction and cannot leak to the next borrower of the pooled
28
+ * connection the way a session-level `SET ROLE` would. The client's own
29
+ * `withTransaction` owns commit and rollback.
30
+ */
31
+ export function withSystemLaneClient(withPgClient, cb, opts = {}) {
32
+ const role = opts.role ?? SYSTEM_LANE_ROLE;
33
+ return withPgClient(null, (client) => client.withTransaction(async (tx) => {
34
+ await tx.query({
35
+ text: 'SELECT set_config($1, $2, true)',
36
+ values: ['role', role],
37
+ });
38
+ return cb(tx);
39
+ }));
40
+ }
package/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export { getQueryBuilder } from './query-builder';
2
2
  export { isComputedScalarAttributeResource } from './pg-resources';
3
+ export type { SystemLaneOptions, SystemLanePgClient, SystemLaneWithPgClient } from './system-lane';
4
+ export { SYSTEM_LANE_ROLE, withSystemLaneClient } from './system-lane';
3
5
  export type { GraphQLScalarName } from './scalar-maps';
4
6
  export { JSON_SCHEMA_SCALARS, PG_TYPE_SCALARS } from './scalar-maps';
package/index.js CHANGED
@@ -1,13 +1,16 @@
1
1
  "use strict";
2
2
  // Shared internals for Constructive PostGraphile v5 plugins.
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.PG_TYPE_SCALARS = exports.JSON_SCHEMA_SCALARS = exports.isComputedScalarAttributeResource = exports.getQueryBuilder = void 0;
4
+ exports.PG_TYPE_SCALARS = exports.JSON_SCHEMA_SCALARS = exports.withSystemLaneClient = exports.SYSTEM_LANE_ROLE = exports.isComputedScalarAttributeResource = exports.getQueryBuilder = void 0;
5
5
  // PgCondition → PgSelectQueryBuilder walker (search / BM25 / pgvector)
6
6
  var query_builder_1 = require("./query-builder");
7
7
  Object.defineProperty(exports, "getQueryBuilder", { enumerable: true, get: function () { return query_builder_1.getQueryBuilder; } });
8
8
  // pg-resource introspection helpers (connection-filter / pg-aggregates)
9
9
  var pg_resources_1 = require("./pg-resources");
10
10
  Object.defineProperty(exports, "isComputedScalarAttributeResource", { enumerable: true, get: function () { return pg_resources_1.isComputedScalarAttributeResource; } });
11
+ var system_lane_1 = require("./system-lane");
12
+ Object.defineProperty(exports, "SYSTEM_LANE_ROLE", { enumerable: true, get: function () { return system_lane_1.SYSTEM_LANE_ROLE; } });
13
+ Object.defineProperty(exports, "withSystemLaneClient", { enumerable: true, get: function () { return system_lane_1.withSystemLaneClient; } });
11
14
  var scalar_maps_1 = require("./scalar-maps");
12
15
  Object.defineProperty(exports, "JSON_SCHEMA_SCALARS", { enumerable: true, get: function () { return scalar_maps_1.JSON_SCHEMA_SCALARS; } });
13
16
  Object.defineProperty(exports, "PG_TYPE_SCALARS", { enumerable: true, get: function () { return scalar_maps_1.PG_TYPE_SCALARS; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphile-plugin-utils",
3
- "version": "1.12.0",
3
+ "version": "1.12.2",
4
4
  "description": "Shared internals for Constructive PostGraphile v5 plugins - PgCondition query-builder walker and pg-resource introspection helpers",
5
5
  "author": "Constructive <developers@constructive.io>",
6
6
  "homepage": "https://github.com/constructive-io/constructive",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^22.19.11",
43
- "makage": "^0.3.0"
43
+ "makage": "^0.8.0"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@dataplan/pg": "^1.1.1",
@@ -51,5 +51,5 @@
51
51
  "pg-sql2": "^5.0.1",
52
52
  "postgraphile": "^5.1.3"
53
53
  },
54
- "gitHead": "b5b6b9f05276b7e9cbd3d1f4acd31dd315c353ea"
54
+ "gitHead": "01e073830677c0587bab564b7d575a06b5156f0c"
55
55
  }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The system lane: server-owned reads and writes that are not a tenant
3
+ * session — schema metadata, module registration, storage config.
4
+ *
5
+ * Grafast's `withPgClient(null, cb)` acquires a client with NO settings, so
6
+ * `cb` runs with whatever role the pool connected as (in practice `postgres`,
7
+ * a superuser that bypasses RLS) and each statement lands in its own implicit
8
+ * transaction. This helper names the role instead: one transaction with a
9
+ * transaction-local `role`, so the lane's reach is the grants of a named role
10
+ * rather than the connection's. It is the server-owned counterpart to the
11
+ * request lane's `withRequestPgClient`.
12
+ */
13
+ /** The subset of grafast's PgClient the system lane needs. */
14
+ export interface SystemLanePgClient {
15
+ query(opts: {
16
+ text: string;
17
+ values?: unknown[];
18
+ }): Promise<{
19
+ rows: Array<Record<string, unknown>>;
20
+ }>;
21
+ withTransaction<T>(cb: (tx: SystemLanePgClient) => Promise<T>): Promise<T>;
22
+ }
23
+ export type SystemLaneWithPgClient = <T>(pgSettings: Record<string, string> | null, cb: (client: SystemLanePgClient) => Promise<T>) => Promise<T>;
24
+ /**
25
+ * Role the system lane runs as: a named role instead of whatever the pool
26
+ * connected as.
27
+ *
28
+ * This is NOT an RLS-subject role. `ROLES.md` documents `administrator` as the
29
+ * BYPASSRLS role, so the lane still bypasses RLS — what changes is that it no
30
+ * longer inherits the connection's superuser and its work is one transaction.
31
+ * Narrowing it further needs grants that do not exist yet.
32
+ */
33
+ export declare const SYSTEM_LANE_ROLE = "administrator";
34
+ export interface SystemLaneOptions {
35
+ /** Role to run as. Defaults to {@link SYSTEM_LANE_ROLE}. */
36
+ role?: string;
37
+ }
38
+ /**
39
+ * Run `cb` in one transaction under the system lane's role.
40
+ *
41
+ * The role is set with a transaction-local `set_config`, so it is discarded
42
+ * with the transaction and cannot leak to the next borrower of the pooled
43
+ * connection the way a session-level `SET ROLE` would. The client's own
44
+ * `withTransaction` owns commit and rollback.
45
+ */
46
+ export declare function withSystemLaneClient<T>(withPgClient: SystemLaneWithPgClient, cb: (client: SystemLanePgClient) => Promise<T>, opts?: SystemLaneOptions): Promise<T>;
package/system-lane.js ADDED
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ /**
3
+ * The system lane: server-owned reads and writes that are not a tenant
4
+ * session — schema metadata, module registration, storage config.
5
+ *
6
+ * Grafast's `withPgClient(null, cb)` acquires a client with NO settings, so
7
+ * `cb` runs with whatever role the pool connected as (in practice `postgres`,
8
+ * a superuser that bypasses RLS) and each statement lands in its own implicit
9
+ * transaction. This helper names the role instead: one transaction with a
10
+ * transaction-local `role`, so the lane's reach is the grants of a named role
11
+ * rather than the connection's. It is the server-owned counterpart to the
12
+ * request lane's `withRequestPgClient`.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.SYSTEM_LANE_ROLE = void 0;
16
+ exports.withSystemLaneClient = withSystemLaneClient;
17
+ /**
18
+ * Role the system lane runs as: a named role instead of whatever the pool
19
+ * connected as.
20
+ *
21
+ * This is NOT an RLS-subject role. `ROLES.md` documents `administrator` as the
22
+ * BYPASSRLS role, so the lane still bypasses RLS — what changes is that it no
23
+ * longer inherits the connection's superuser and its work is one transaction.
24
+ * Narrowing it further needs grants that do not exist yet.
25
+ */
26
+ exports.SYSTEM_LANE_ROLE = 'administrator';
27
+ /**
28
+ * Run `cb` in one transaction under the system lane's role.
29
+ *
30
+ * The role is set with a transaction-local `set_config`, so it is discarded
31
+ * with the transaction and cannot leak to the next borrower of the pooled
32
+ * connection the way a session-level `SET ROLE` would. The client's own
33
+ * `withTransaction` owns commit and rollback.
34
+ */
35
+ function withSystemLaneClient(withPgClient, cb, opts = {}) {
36
+ const role = opts.role ?? exports.SYSTEM_LANE_ROLE;
37
+ return withPgClient(null, (client) => client.withTransaction(async (tx) => {
38
+ await tx.query({
39
+ text: 'SELECT set_config($1, $2, true)',
40
+ values: ['role', role],
41
+ });
42
+ return cb(tx);
43
+ }));
44
+ }