@rayfold/postgres 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/NOTICE ADDED
@@ -0,0 +1,10 @@
1
+ Rayfold
2
+ Copyright 2026 The Rayfold Authors
3
+
4
+ This product is licensed under the Apache License, Version 2.0 (see LICENSE).
5
+
6
+ The data in data/gutenberg.json.gz is derived from the Project Gutenberg catalogue
7
+ (https://www.gutenberg.org/ebooks/offline_catalogs.html) and is not covered by this
8
+ project's copyright. See data/README.md for its source. Project Gutenberg is a
9
+ registered trademark of the Project Gutenberg Literary Archive Foundation; Rayfold is
10
+ not affiliated with or endorsed by Project Gutenberg.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @rayfold/postgres
2
+
3
+ Postgres for [Rayfold](https://github.com/eddyboutros/rayfold) resolvers: batch loads by id, keyset pages, one query
4
+ per level of a nested shape, and read policies pushed into the SQL `WHERE`, so a list never fetches rows its viewer
5
+ may not see.
6
+
7
+ ```sh
8
+ npm install @rayfold/postgres pg
9
+ ```
10
+
11
+ ```ts
12
+ import pg from "pg";
13
+ import { createRayfoldServer } from "@rayfold/server";
14
+ import { createPgStore } from "@rayfold/postgres";
15
+
16
+ const db = new pg.Pool({ connectionString: process.env.DATABASE_URL });
17
+ const schema = `
18
+ entity Author { id: ID name: String books(page: PageArgs = { first: 10 }): Page<Book> }
19
+ entity Book { id: ID title: String author: Author }
20
+ entity Order @allow(read: viewer.id == customerId) { id: ID customerId: ID total: Decimal }
21
+ query books(page: PageArgs = { first: 20 }): Page<Book>
22
+ query myOrders: [Order]
23
+ `;
24
+ const server = createRayfoldServer({ schema, resolvers: {} }); // for its IR; see below
25
+ const store = createPgStore(db, {
26
+ ir: server.ir,
27
+ naming: "snake", // authorId -> author_id
28
+ tables: { Author: { table: "authors" }, Book: { table: "books", columns: { authorId: "author_id" } }, Order: { table: "orders" } },
29
+ });
30
+
31
+ export const resolvers = {
32
+ Query: {
33
+ books: (args, ctx) => store.page("Book", args.page, {}, ctx),
34
+ myOrders: (_args, ctx) => store.find("Order", {}, ctx), // the policy becomes WHERE customer_id = $viewer
35
+ },
36
+ Book: { author: (books, _args, ctx) => store.byIds("Author", books.map((b) => b.authorId), ctx) },
37
+ Author: { books: (authors, args, ctx) => store.pagesByField("Book", "authorId", authors.map((a) => a.id), args.page, ctx) },
38
+ };
39
+ ```
40
+
41
+ Any client with `query(text, params)` works: `pg.Pool`, `pg.Client`, [PGlite](https://pglite.dev), or your own
42
+ wrapper (for logging or transactions).
43
+
44
+ ## What each call does
45
+
46
+ | Call | SQL | Use it for |
47
+ |---|---|---|
48
+ | `byIds(type, ids, ctx)` | one `SELECT ... WHERE id = ANY($1)` | a to-one field's batch loader; returns rows in the order asked, `null` for a missing or hidden row |
49
+ | `find(type, where, ctx)` | one `SELECT ... WHERE field = $n ... ORDER BY id` | a short list |
50
+ | `page(type, { first, after }, where, ctx)` | one query with `count(*) OVER ()` | a `Page<T>` in key order: `items`, `cursor`, `hasMore`, `total` |
51
+ | `pagesByField(type, field, parents, { first, after }, ctx)` | one query with window functions | a paged one-to-many field for a whole level of parents at once |
52
+
53
+ ## Read policies in SQL
54
+
55
+ When a resolver loads an entity whose read policy is pushable (it reads only the viewer, the arguments, literals and
56
+ the row's own scalar fields; spec 06 §4), the runtime hands it to the resolver as `ctx.policy.filter`. Pass `ctx` to the
57
+ store and the policy becomes part of the `WHERE` clause: a customer's list query reads only their rows, and a page's
58
+ `total` counts only what they may see.
59
+
60
+ The translation never drops a row the policy allows. Where SQL cannot match the runtime exactly (ordering text,
61
+ comparing values of different types, `Decimal` inequality) it lets the row through and the runtime's own check, which
62
+ always runs, removes it. Without the pushdown, a list that returns rows the viewer may not see is refused as a whole
63
+ (spec 06 §3); with it, the list holds exactly the permitted rows.
64
+
65
+ ## License
66
+
67
+ Apache-2.0
package/index.d.ts ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Postgres for Rayfold resolvers: batch loads by id, keyset pages, one-to-many pages for a whole level in one query,
3
+ * and read policies pushed into the SQL `WHERE` (spec 06 §4), so a list never fetches rows its viewer may not see.
4
+ *
5
+ * Works with any client that has `query(text, params)`: pg's `Pool` and `Client`, PGlite, or a wrapper of your own.
6
+ * Every value travels as a bound parameter and every identifier is quoted.
7
+ *
8
+ * The pushed-down filter is a superset of the policy: it may let through a row the policy denies (the runtime's own
9
+ * check then removes it), but it never drops a row the policy allows. Comparisons it cannot translate exactly, such as
10
+ * ordering text or comparing across types, are left to the runtime.
11
+ */
12
+ import { type Expr, type ExprEnv, type RayfoldSchemaIR, type Shape } from "@rayfold/schema";
13
+ /** Anything with pg's `query(text, params)`: `pg.Pool`, `pg.Client`, `PGlite`. */
14
+ export interface Queryable {
15
+ query<R = Record<string, unknown>>(text: string, params?: unknown[]): Promise<{
16
+ rows: R[];
17
+ }>;
18
+ }
19
+ export interface TableMapping {
20
+ /** The table, optionally schema-qualified (`"public.books"`). */
21
+ table: string;
22
+ /** The key column; default `"id"`. Pages are ordered by it. */
23
+ id?: string;
24
+ /** Field name to column name, for fields whose column is named otherwise (after `naming`). */
25
+ columns?: Record<string, string>;
26
+ /** Fields that lead to another mapped type. `screen` needs these; the batch loaders do not. */
27
+ relations?: Record<string, Relation>;
28
+ }
29
+ /**
30
+ * How a field reaches another table. `one`: `key` is the field on *this* type holding the other row's key
31
+ * (`Book.authorId`). `page`: `key` is the field on the *other* type holding this row's key (`Book.authorId`, as seen
32
+ * from `Author.books`).
33
+ */
34
+ export interface Relation {
35
+ type: string;
36
+ kind: "one" | "page";
37
+ key: string;
38
+ }
39
+ export interface PgStoreOptions {
40
+ ir: RayfoldSchemaIR;
41
+ /** One mapping per entity type the store serves. */
42
+ tables: Record<string, TableMapping>;
43
+ /** How field names become column names when `columns` does not say: as they are (default) or snake_case. */
44
+ naming?: "same" | "snake";
45
+ }
46
+ /** What the store needs from a resolver's context: the viewer and the read policy the runtime pushed down (`ctx.policy`). */
47
+ export interface PolicyContext {
48
+ viewer?: unknown;
49
+ policy?: {
50
+ filter?: Expr;
51
+ };
52
+ now?: () => number;
53
+ }
54
+ export type Row = Record<string, unknown>;
55
+ export interface Page<T = Row> {
56
+ items: T[];
57
+ cursor: string | null;
58
+ hasMore: boolean;
59
+ total: number;
60
+ }
61
+ export interface PageRequest {
62
+ first: number;
63
+ after?: string | null;
64
+ }
65
+ /** A SQL fragment with its parameters numbered from 1. */
66
+ export interface Fragment {
67
+ sql: string;
68
+ exact: boolean;
69
+ }
70
+ export declare function createPgStore(db: Queryable, opts: PgStoreOptions): PgStore;
71
+ export declare class PgStore {
72
+ private readonly db;
73
+ private readonly opts;
74
+ private readonly tables;
75
+ constructor(db: Queryable, opts: PgStoreOptions);
76
+ /**
77
+ * Rows of `type` for `ids`, in the order asked: `null` where a row is missing or hidden by the pushed-down read policy.
78
+ * One query for any number of ids, which makes it the batch loader for a to-one field.
79
+ */
80
+ byIds(type: string, ids: readonly unknown[], ctx?: PolicyContext, args?: Record<string, unknown>): Promise<Array<Row | null>>;
81
+ /** Rows of `type` whose fields equal `where` (a `null` value matches NULL), in key order. One query. */
82
+ find(type: string, where?: Record<string, unknown>, ctx?: PolicyContext, args?: Record<string, unknown>): Promise<Row[]>;
83
+ /**
84
+ * One page of `type` in key order, after `page.after` (a key), with `total` counting every visible matching row.
85
+ * One query; a second one counts only when the page comes back empty.
86
+ */
87
+ page(type: string, page: PageRequest, where?: Record<string, unknown>, ctx?: PolicyContext, args?: Record<string, unknown>): Promise<Page>;
88
+ /**
89
+ * For each parent key in `values`, the page of `type` rows whose `field` equals it: the batch loader for a paged
90
+ * one-to-many field (an author's books). One query for the whole level, whatever the number of parents.
91
+ */
92
+ pagesByField(type: string, field: string, values: readonly unknown[], page: PageRequest, ctx?: PolicyContext, args?: Record<string, unknown>): Promise<Page[]>;
93
+ /**
94
+ * One statement for a whole screen: a page of `type` with every nested field the shape asks for, gathered as JSON
95
+ * by correlated subqueries. Depth costs no extra round trip, and each level's read policy is pushed into its own
96
+ * `WHERE`, so a nested list never carries rows its viewer may not see.
97
+ *
98
+ * Nested pages are first pages (`first`), which is what a screen shows; the root page takes a cursor as usual.
99
+ * Every selected field must be a mapped column or a declared relation.
100
+ */
101
+ screen(type: string, shape: Shape, page: PageRequest, where?: Record<string, unknown>, ctx?: PolicyContext, args?: Record<string, unknown>): Promise<Page>;
102
+ /** The JSON projection of one level of a screen: scalars from columns, relations from correlated subqueries. */
103
+ private project;
104
+ /** The WHERE fragment for the read policy the runtime pushed down, or nothing when there is none to push. */
105
+ private policyWhere;
106
+ /** The read policy declared on a type, for the levels of a screen the runtime handed no hint for. */
107
+ private filterFor;
108
+ private equalities;
109
+ private select;
110
+ private row;
111
+ private table;
112
+ private scalarOf;
113
+ }
114
+ /** A column a policy may read, with the Rayfold scalar type of its field. */
115
+ export interface PolicyColumn {
116
+ column: string;
117
+ scalar: string;
118
+ }
119
+ /**
120
+ * Translates a pushable read policy (spec 06 §4) into a WHERE fragment under `env` (the viewer and args), appending
121
+ * its values to `params`. Parts that read no field of the row are evaluated here, as the runtime would. `exact` is true
122
+ * when the fragment selects exactly the rows the policy allows; otherwise it selects more, never fewer.
123
+ */
124
+ export declare function compilePolicy(e: Expr, env: ExprEnv, columnOf: (field: string) => PolicyColumn | undefined, params: unknown[]): Fragment;
package/index.js ADDED
@@ -0,0 +1,435 @@
1
+ /**
2
+ * Postgres for Rayfold resolvers: batch loads by id, keyset pages, one-to-many pages for a whole level in one query,
3
+ * and read policies pushed into the SQL `WHERE` (spec 06 §4), so a list never fetches rows its viewer may not see.
4
+ *
5
+ * Works with any client that has `query(text, params)`: pg's `Pool` and `Client`, PGlite, or a wrapper of your own.
6
+ * Every value travels as a bound parameter and every identifier is quoted.
7
+ *
8
+ * The pushed-down filter is a superset of the policy: it may let through a row the policy denies (the runtime's own
9
+ * check then removes it), but it never drops a row the policy allows. Comparisons it cannot translate exactly, such as
10
+ * ordering text or comparing across types, are left to the runtime.
11
+ */
12
+ import { evalExpr } from "@rayfold/schema";
13
+ export function createPgStore(db, opts) {
14
+ return new PgStore(db, opts);
15
+ }
16
+ export class PgStore {
17
+ db;
18
+ opts;
19
+ tables = new Map();
20
+ constructor(db, opts) {
21
+ this.db = db;
22
+ this.opts = opts;
23
+ for (const [type, m] of Object.entries(opts.tables)) {
24
+ const def = opts.ir.types[type];
25
+ if (!def || !("fields" in def))
26
+ throw new Error(`@rayfold/postgres: ${type} is not an entity or object of the schema`);
27
+ const columns = new Map();
28
+ for (const f of def.fields)
29
+ columns.set(f.name, m.columns?.[f.name] ?? (opts.naming === "snake" ? snake(f.name) : f.name));
30
+ for (const [f, c] of Object.entries(m.columns ?? {}))
31
+ columns.set(f, c);
32
+ const id = m.id ?? "id";
33
+ const idField = [...columns].find(([, c]) => c === id)?.[0] ?? "id";
34
+ const fields = new Map([...columns].map(([f, c]) => [c, f]));
35
+ this.tables.set(type, {
36
+ name: m.table.split(".").map(quote).join("."),
37
+ id: quote(id),
38
+ idField,
39
+ column: (f) => (columns.has(f) ? quote(columns.get(f)) : undefined),
40
+ field: (c) => fields.get(c) ?? c,
41
+ def: { name: def.name, fields: def.fields },
42
+ });
43
+ }
44
+ }
45
+ /**
46
+ * Rows of `type` for `ids`, in the order asked: `null` where a row is missing or hidden by the pushed-down read policy.
47
+ * One query for any number of ids, which makes it the batch loader for a to-one field.
48
+ */
49
+ async byIds(type, ids, ctx = {}, args = {}) {
50
+ if (!ids.length)
51
+ return [];
52
+ const t = this.table(type);
53
+ const params = [[...new Set(ids.filter((x) => x !== null && x !== undefined).map(String))]];
54
+ const where = [`${t.id}::text = ANY($1::text[])`, ...this.policyWhere(t, ctx, args, params)];
55
+ const rows = await this.select(t, `SELECT * FROM ${t.name} WHERE ${where.join(" AND ")}`, params);
56
+ const byId = new Map(rows.map((r) => [String(r[t.idField]), r]));
57
+ return ids.map((id) => (id === null || id === undefined ? null : (byId.get(String(id)) ?? null)));
58
+ }
59
+ /** Rows of `type` whose fields equal `where` (a `null` value matches NULL), in key order. One query. */
60
+ async find(type, where = {}, ctx = {}, args = {}) {
61
+ const t = this.table(type);
62
+ const params = [];
63
+ const conds = [...this.equalities(t, where, params), ...this.policyWhere(t, ctx, args, params)];
64
+ return this.select(t, `SELECT * FROM ${t.name}${conds.length ? ` WHERE ${conds.join(" AND ")}` : ""} ORDER BY ${t.id}`, params);
65
+ }
66
+ /**
67
+ * One page of `type` in key order, after `page.after` (a key), with `total` counting every visible matching row.
68
+ * One query; a second one counts only when the page comes back empty.
69
+ */
70
+ async page(type, page, where = {}, ctx = {}, args = {}) {
71
+ const t = this.table(type);
72
+ const params = [];
73
+ const conds = [...this.equalities(t, where, params), ...this.policyWhere(t, ctx, args, params)];
74
+ const filtered = `SELECT *, count(*) OVER () AS "__total" FROM ${t.name}${conds.length ? ` WHERE ${conds.join(" AND ")}` : ""}`;
75
+ let after = "";
76
+ if (page.after) {
77
+ params.push(page.after);
78
+ after = ` WHERE "__s".${t.id}::text > $${params.length}::text`;
79
+ }
80
+ params.push(page.first + 1);
81
+ const raw = (await this.db.query(`SELECT * FROM (${filtered}) AS "__s"${after} ORDER BY "__s".${t.id}::text LIMIT $${params.length}`, params)).rows;
82
+ let total = raw.length ? Number(raw[0]["__total"]) : 0;
83
+ if (!raw.length && page.after) {
84
+ const count = await this.db.query(`SELECT count(*)::int AS n FROM ${t.name}${conds.length ? ` WHERE ${conds.join(" AND ")}` : ""}`, params.slice(0, params.length - 2));
85
+ total = Number(count.rows[0]?.n ?? 0);
86
+ }
87
+ const rows = raw.map((r) => this.row(t, r));
88
+ const items = rows.slice(0, page.first);
89
+ return { items, cursor: items.length ? String(items[items.length - 1][t.idField]) : null, hasMore: rows.length > page.first, total };
90
+ }
91
+ /**
92
+ * For each parent key in `values`, the page of `type` rows whose `field` equals it: the batch loader for a paged
93
+ * one-to-many field (an author's books). One query for the whole level, whatever the number of parents.
94
+ */
95
+ async pagesByField(type, field, values, page, ctx = {}, args = {}) {
96
+ if (!values.length)
97
+ return [];
98
+ const t = this.table(type);
99
+ const col = t.column(field);
100
+ if (!col)
101
+ throw new Error(`@rayfold/postgres: ${type} has no field ${field}`);
102
+ const params = [[...new Set(values.map(String))]];
103
+ const conds = [`${col}::text = ANY($1::text[])`, ...this.policyWhere(t, ctx, args, params)];
104
+ let after = "";
105
+ if (page.after) {
106
+ params.push(page.after);
107
+ after = ` AND "__s".${t.id}::text > $${params.length}::text`;
108
+ }
109
+ params.push(page.first + 1);
110
+ const sql = `SELECT * FROM (SELECT *, count(*) OVER (PARTITION BY ${col}) AS "__total", ` +
111
+ `row_number() OVER (PARTITION BY ${col} ORDER BY ${t.id}::text) AS "__n" FROM ${t.name} WHERE ${conds.join(" AND ")}) AS "__s" ` +
112
+ `WHERE TRUE${after} ORDER BY "__s".${t.id}::text`;
113
+ const raw = (await this.db.query(sql, params.slice(0, -1))).rows;
114
+ const groups = new Map();
115
+ for (const r of raw) {
116
+ const key = String(r[col.slice(1, -1).replace(/""/g, '"')]);
117
+ const g = groups.get(key) ?? { rows: [], total: Number(r["__total"]) };
118
+ g.rows.push(this.row(t, r));
119
+ groups.set(key, g);
120
+ }
121
+ return values.map((v) => {
122
+ const g = groups.get(String(v));
123
+ if (!g)
124
+ return { items: [], cursor: null, hasMore: false, total: 0 };
125
+ const items = g.rows.slice(0, page.first);
126
+ return { items, cursor: items.length ? String(items[items.length - 1][t.idField]) : null, hasMore: g.rows.length > page.first, total: g.total };
127
+ });
128
+ }
129
+ /**
130
+ * One statement for a whole screen: a page of `type` with every nested field the shape asks for, gathered as JSON
131
+ * by correlated subqueries. Depth costs no extra round trip, and each level's read policy is pushed into its own
132
+ * `WHERE`, so a nested list never carries rows its viewer may not see.
133
+ *
134
+ * Nested pages are first pages (`first`), which is what a screen shows; the root page takes a cursor as usual.
135
+ * Every selected field must be a mapped column or a declared relation.
136
+ */
137
+ async screen(type, shape, page, where = {}, ctx = {}, args = {}) {
138
+ const t = this.table(type);
139
+ const params = [];
140
+ // The caller may pass the shape of the Page the query returns, or the shape of one row.
141
+ const rowShape = t.def.fields.some((f) => f.name === "items") ? shape : itemsOf(shape);
142
+ const projection = this.project(t, "__r0", rowShape, ctx, args, params, 0);
143
+ const conds = [...this.equalities(t, where, params), ...this.policyWhere(t, ctx, args, params, "__r0", ctx.policy?.filter ?? this.filterFor(type))];
144
+ const filtered = `SELECT ${projection} AS "__row", __r0.${t.id}::text AS "__key", count(*) OVER () AS "__total" ` +
145
+ `FROM ${t.name} AS __r0${conds.length ? ` WHERE ${conds.join(" AND ")}` : ""}`;
146
+ let after = "";
147
+ if (page.after) {
148
+ params.push(page.after);
149
+ after = ` WHERE "__s"."__key" > $${params.length}::text`;
150
+ }
151
+ params.push(page.first + 1);
152
+ const raw = (await this.db.query(`SELECT * FROM (${filtered}) AS "__s"${after} ORDER BY "__s"."__key" LIMIT $${params.length}`, params)).rows;
153
+ let total = raw.length ? Number(raw[0]["__total"]) : 0;
154
+ if (!raw.length && page.after) {
155
+ const cp = [];
156
+ const cc = [...this.equalities(t, where, cp), ...this.policyWhere(t, ctx, args, cp, "__r0", ctx.policy?.filter ?? this.filterFor(type))];
157
+ const counted = await this.db.query(`SELECT count(*)::int AS n FROM ${t.name} AS __r0${cc.length ? ` WHERE ${cc.join(" AND ")}` : ""}`, cp);
158
+ total = Number(counted.rows[0]?.n ?? 0);
159
+ }
160
+ const rows = raw.slice(0, page.first);
161
+ return {
162
+ items: rows.map((r) => r["__row"]),
163
+ cursor: rows.length ? String(rows[rows.length - 1]["__key"]) : null,
164
+ hasMore: raw.length > page.first,
165
+ total,
166
+ };
167
+ }
168
+ /** The JSON projection of one level of a screen: scalars from columns, relations from correlated subqueries. */
169
+ project(t, alias, shape, ctx, args, params, depth) {
170
+ if (depth > 8)
171
+ throw new Error("@rayfold/postgres: a screen nested deeper than 8 levels");
172
+ const relations = this.opts.tables[t.def.name]?.relations ?? {};
173
+ const parts = [];
174
+ for (const item of shape.items) {
175
+ if (item.kind !== "field")
176
+ continue; // views and type conditions are expanded before a shape reaches the store
177
+ const name = item.name;
178
+ const out = item.alias ?? name;
179
+ const def = t.def.fields.find((f) => f.name === name);
180
+ if (!def)
181
+ throw new Error(`@rayfold/postgres: ${t.def.name} has no field ${name}`);
182
+ const rel = relations[name];
183
+ if (!rel) {
184
+ const col = t.column(name);
185
+ if (!col)
186
+ throw new Error(`@rayfold/postgres: ${t.def.name}.${name} has no column and no relation`);
187
+ const scalar = def.type.kind === "named" ? this.scalarOf(def.type.name) : "";
188
+ parts.push(`'${out}', ${alias}.${col}${AS_TEXT.has(scalar) ? "::text" : ""}`);
189
+ continue;
190
+ }
191
+ const child = this.table(rel.type);
192
+ const ca = `__r${depth + 1}`;
193
+ // A paged field is selected through its Page envelope, so the rows are described by its `items`.
194
+ const selected = item.shape ?? { items: [] };
195
+ const childProjection = this.project(child, ca, rel.kind === "page" ? itemsOf(selected) : selected, ctx, args, params, depth + 1);
196
+ const childPolicy = this.policyWhere(child, ctx, args, params, ca, this.filterFor(rel.type));
197
+ if (rel.kind === "one") {
198
+ const parentCol = t.column(rel.key);
199
+ if (!parentCol)
200
+ throw new Error(`@rayfold/postgres: ${t.def.name} has no field ${rel.key}`);
201
+ const conds = [`${ca}.${child.id} = ${alias}.${parentCol}`, ...childPolicy];
202
+ parts.push(`'${out}', (SELECT ${childProjection} FROM ${child.name} AS ${ca} WHERE ${conds.join(" AND ")} LIMIT 1)`);
203
+ continue;
204
+ }
205
+ const childCol = child.column(rel.key);
206
+ if (!childCol)
207
+ throw new Error(`@rayfold/postgres: ${rel.type} has no field ${rel.key}`);
208
+ params.push(firstOf(item.args) ?? 10);
209
+ const conds = [`${ca}.${childCol} = ${alias}.${t.id}`, ...childPolicy];
210
+ const rows = `SELECT ${childProjection} AS "__row", ${ca}.${child.id}::text AS "__key", count(*) OVER () AS "__total" ` +
211
+ `FROM ${child.name} AS ${ca} WHERE ${conds.join(" AND ")} ORDER BY ${ca}.${child.id}::text LIMIT $${params.length}`;
212
+ parts.push(`'${out}', (SELECT json_build_object(` +
213
+ `'items', coalesce(json_agg("__p"."__row" ORDER BY "__p"."__key"), '[]'::json), ` +
214
+ `'total', coalesce(max("__p"."__total"), 0)::int, ` +
215
+ `'hasMore', coalesce(count(*) < max("__p"."__total"), false), ` +
216
+ `'cursor', max("__p"."__key")` +
217
+ `) FROM (${rows}) AS "__p")`);
218
+ }
219
+ return `json_build_object(${parts.join(", ")})`;
220
+ }
221
+ /** The WHERE fragment for the read policy the runtime pushed down, or nothing when there is none to push. */
222
+ policyWhere(t, ctx, args, params, alias, own) {
223
+ const filter = own ?? ctx.policy?.filter;
224
+ if (!filter)
225
+ return [];
226
+ const env = { viewer: ctx.viewer ?? null, args, this: null, ...(ctx.now ? { now: ctx.now } : {}) };
227
+ const f = compilePolicy(filter, env, (name) => {
228
+ const col = t.column(name);
229
+ const def = t.def.fields.find((x) => x.name === name);
230
+ const qualified = col && alias ? `${alias}.${col}` : col;
231
+ return qualified && def && def.type.kind === "named" ? { column: qualified, scalar: this.scalarOf(def.type.name) } : undefined;
232
+ }, params);
233
+ return f.sql === "TRUE" ? [] : [f.sql];
234
+ }
235
+ /** The read policy declared on a type, for the levels of a screen the runtime handed no hint for. */
236
+ filterFor(type) {
237
+ const def = this.opts.ir.types[type];
238
+ return def ? pushableFilterOf(def.annotations) : undefined;
239
+ }
240
+ equalities(t, where, params) {
241
+ return Object.entries(where).map(([f, v]) => {
242
+ const col = t.column(f);
243
+ if (!col)
244
+ throw new Error(`@rayfold/postgres: ${t.def.name} has no field ${f}`);
245
+ if (v === null || v === undefined)
246
+ return `${col} IS NULL`;
247
+ params.push(v);
248
+ return `${col} = $${params.length}`;
249
+ });
250
+ }
251
+ async select(t, sql, params) {
252
+ return (await this.db.query(sql, params)).rows.map((r) => this.row(t, r));
253
+ }
254
+ row(t, r) {
255
+ const out = {};
256
+ for (const [k, v] of Object.entries(r))
257
+ if (k !== "__total" && k !== "__n")
258
+ out[t.field(k)] = v;
259
+ return out;
260
+ }
261
+ table(type) {
262
+ const t = this.tables.get(type);
263
+ if (!t)
264
+ throw new Error(`@rayfold/postgres: no table mapped for ${type}`);
265
+ return t;
266
+ }
267
+ scalarOf(name) {
268
+ const def = this.opts.ir.types[name];
269
+ return def?.kind === "enum" ? "String" : name;
270
+ }
271
+ }
272
+ // ---------------------------------------------------------------- policies to SQL
273
+ /** The rows of a `Page<T>` selection: the sub-shape of its `items`, or the shape itself when it selects rows. */
274
+ function itemsOf(shape) {
275
+ const items = shape.items.find((i) => i.kind === "field" && i.name === "items");
276
+ return items && items.kind === "field" && items.shape ? items.shape : shape;
277
+ }
278
+ /** Scalars Postgres would render as a JSON number, but Rayfold carries as text. */
279
+ const AS_TEXT = new Set(["Decimal", "Long"]);
280
+ /** The page size a shape asked for (`page: { first: n }` or `first: n`), when it is a plain number. */
281
+ function firstOf(a) {
282
+ const page = a?.["page"];
283
+ const n = page && typeof page === "object" && !Array.isArray(page) ? page["first"] : a?.["first"];
284
+ return typeof n === "number" && Number.isInteger(n) && n >= 0 ? n : undefined;
285
+ }
286
+ /** The pushable part of a type's `@allow(read:)`, for the levels of a screen the runtime gave no hint for. */
287
+ function pushableFilterOf(annotations) {
288
+ const allow = annotations.find((a) => a.name === "allow");
289
+ const read = allow?.args["read"];
290
+ return read && typeof read === "object" && "$expr" in read ? read.$expr : undefined;
291
+ }
292
+ const LOOSE = { sql: "TRUE", exact: false };
293
+ const constant = (b) => ({ sql: b ? "TRUE" : "FALSE", exact: true });
294
+ const TEXT = new Set(["String", "ID"]);
295
+ const NUMBER = new Set(["Int", "Float"]);
296
+ const DECIMAL = new Set(["Decimal", "Long"]);
297
+ const DECIMAL_TEXT = /^-?\d+(\.\d+)?$/;
298
+ /**
299
+ * Translates a pushable read policy (spec 06 §4) into a WHERE fragment under `env` (the viewer and args), appending
300
+ * its values to `params`. Parts that read no field of the row are evaluated here, as the runtime would. `exact` is true
301
+ * when the fragment selects exactly the rows the policy allows; otherwise it selects more, never fewer.
302
+ */
303
+ export function compilePolicy(e, env, columnOf, params) {
304
+ if (!readsRow(e)) {
305
+ try {
306
+ return constant(truthy(evalExpr(e, env)));
307
+ }
308
+ catch {
309
+ return LOOSE; // an expression that cannot be evaluated denies, but maybe only where it is reached: leave it to the runtime
310
+ }
311
+ }
312
+ switch (e.k) {
313
+ case "path": {
314
+ const c = e.path.length === 1 ? columnOf(e.path[0]) : undefined;
315
+ if (!c)
316
+ return LOOSE;
317
+ return { sql: c.scalar === "Boolean" ? `(${c.column} IS TRUE)` : `(${c.column} IS NOT NULL)`, exact: true };
318
+ }
319
+ case "not": {
320
+ const f = compilePolicy(e.e, env, columnOf, params);
321
+ // negating a superset would give a subset, so only an exact fragment can be negated
322
+ return f.exact ? { sql: `(NOT ${f.sql})`, exact: true } : LOOSE;
323
+ }
324
+ case "bin": {
325
+ if (e.op === "&&" || e.op === "||") {
326
+ const l = compilePolicy(e.l, env, columnOf, params);
327
+ const r = compilePolicy(e.r, env, columnOf, params);
328
+ return { sql: `(${l.sql} ${e.op === "&&" ? "AND" : "OR"} ${r.sql})`, exact: l.exact && r.exact };
329
+ }
330
+ const left = rowField(e.l);
331
+ const right = rowField(e.r);
332
+ if ((left === undefined) === (right === undefined))
333
+ return LOOSE; // both sides read the row, or neither is a plain field
334
+ const field = (left ?? right);
335
+ const other = left !== undefined ? e.r : e.l;
336
+ if (readsRow(other))
337
+ return LOOSE;
338
+ const c = columnOf(field);
339
+ if (!c)
340
+ return LOOSE;
341
+ let v;
342
+ try {
343
+ v = evalExpr(other, env);
344
+ }
345
+ catch {
346
+ return LOOSE;
347
+ }
348
+ const op = left !== undefined ? e.op : flip(e.op);
349
+ return compare(c, op, v, params);
350
+ }
351
+ default:
352
+ return LOOSE;
353
+ }
354
+ }
355
+ function compare(c, op, v, params) {
356
+ const add = (x) => `$${params.push(x)}`;
357
+ if (op === "in") {
358
+ if (!Array.isArray(v))
359
+ return constant(false);
360
+ if (!v.every((x) => kindMatches(c.scalar, x)))
361
+ return LOOSE;
362
+ if (TEXT.has(c.scalar))
363
+ return { sql: `COALESCE(${c.column}::text = ANY(${add(v.map(String))}::text[]), FALSE)`, exact: true };
364
+ if (NUMBER.has(c.scalar))
365
+ return { sql: `COALESCE(${c.column} = ANY(${add(v)}::numeric[]), FALSE)`, exact: true };
366
+ return LOOSE;
367
+ }
368
+ if (op === "==" || op === "!=") {
369
+ if (v === null || v === undefined)
370
+ return { sql: `(${c.column} IS ${op === "==" ? "" : "NOT "}NULL)`, exact: true };
371
+ if (!kindMatches(c.scalar, v))
372
+ return LOOSE;
373
+ const not = op === "==" ? "NOT " : "";
374
+ if (TEXT.has(c.scalar))
375
+ return { sql: `(${c.column}::text IS ${not}DISTINCT FROM ${add(String(v))}::text)`, exact: true };
376
+ if (NUMBER.has(c.scalar))
377
+ return { sql: `(${c.column} IS ${not}DISTINCT FROM ${add(v)}::numeric)`, exact: true };
378
+ if (c.scalar === "Boolean")
379
+ return { sql: `(${c.column} IS ${not}DISTINCT FROM ${add(v)}::boolean)`, exact: true };
380
+ // Decimal and Long travel as text: SQL compares by value, the runtime may compare the text, so equality selects
381
+ // more rows than the policy and inequality could select fewer; only equality is pushed
382
+ if (DECIMAL.has(c.scalar) && op === "==")
383
+ return { sql: `(${c.column} = ${add(String(v))}::numeric)`, exact: false };
384
+ return LOOSE;
385
+ }
386
+ if (["<", "<=", ">", ">="].includes(op) && typeof v === "number" && NUMBER.has(c.scalar)) {
387
+ return { sql: `COALESCE(${c.column} ${op} ${add(v)}::numeric, FALSE)`, exact: true };
388
+ }
389
+ return LOOSE;
390
+ }
391
+ /** Whether `v` is a value the runtime could find equal to a field of this scalar type. */
392
+ function kindMatches(scalar, v) {
393
+ if (TEXT.has(scalar))
394
+ return typeof v === "string";
395
+ if (NUMBER.has(scalar))
396
+ return typeof v === "number" && Number.isFinite(v);
397
+ if (scalar === "Boolean")
398
+ return typeof v === "boolean";
399
+ if (DECIMAL.has(scalar))
400
+ return (typeof v === "string" && DECIMAL_TEXT.test(v)) || (typeof v === "number" && Number.isFinite(v));
401
+ return false;
402
+ }
403
+ function flip(op) {
404
+ return op === "<" ? ">" : op === ">" ? "<" : op === "<=" ? ">=" : op === ">=" ? "<=" : op;
405
+ }
406
+ /** The field name when `e` is `this.<field>`. */
407
+ function rowField(e) {
408
+ return e.k === "path" && e.root === "this" && e.path.length === 1 ? e.path[0] : undefined;
409
+ }
410
+ function readsRow(e) {
411
+ switch (e.k) {
412
+ case "lit":
413
+ return false;
414
+ case "path":
415
+ return e.root === "this";
416
+ case "not":
417
+ return readsRow(e.e);
418
+ case "bin":
419
+ return readsRow(e.l) || readsRow(e.r);
420
+ case "list":
421
+ return e.items.some(readsRow);
422
+ case "call":
423
+ return e.args.some(readsRow);
424
+ }
425
+ }
426
+ function truthy(v) {
427
+ return v !== null && v !== undefined && v !== false;
428
+ }
429
+ function quote(name) {
430
+ return `"${name.replace(/"/g, '""')}"`;
431
+ }
432
+ function snake(name) {
433
+ return name.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
434
+ }
435
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Postgres for Rayfold resolvers: batch loads by id, keyset pages, one-to-many pages for a whole level in one query,\n * and read policies pushed into the SQL `WHERE` (spec 06 §4), so a list never fetches rows its viewer may not see.\n *\n * Works with any client that has `query(text, params)`: pg's `Pool` and `Client`, PGlite, or a wrapper of your own.\n * Every value travels as a bound parameter and every identifier is quoted.\n *\n * The pushed-down filter is a superset of the policy: it may let through a row the policy denies (the runtime's own\n * check then removes it), but it never drops a row the policy allows. Comparisons it cannot translate exactly, such as\n * ordering text or comparing across types, are left to the runtime.\n */\nimport { evalExpr, type Expr, type ExprEnv, type FieldDef, type RayfoldSchemaIR, type Shape } from \"@rayfold/schema\";\n\n/** Anything with pg's `query(text, params)`: `pg.Pool`, `pg.Client`, `PGlite`. */\nexport interface Queryable {\n query<R = Record<string, unknown>>(text: string, params?: unknown[]): Promise<{ rows: R[] }>;\n}\n\nexport interface TableMapping {\n /** The table, optionally schema-qualified (`\"public.books\"`). */\n table: string;\n /** The key column; default `\"id\"`. Pages are ordered by it. */\n id?: string;\n /** Field name to column name, for fields whose column is named otherwise (after `naming`). */\n columns?: Record<string, string>;\n /** Fields that lead to another mapped type. `screen` needs these; the batch loaders do not. */\n relations?: Record<string, Relation>;\n}\n\n/**\n * How a field reaches another table. `one`: `key` is the field on *this* type holding the other row's key\n * (`Book.authorId`). `page`: `key` is the field on the *other* type holding this row's key (`Book.authorId`, as seen\n * from `Author.books`).\n */\nexport interface Relation {\n type: string;\n kind: \"one\" | \"page\";\n key: string;\n}\n\nexport interface PgStoreOptions {\n ir: RayfoldSchemaIR;\n /** One mapping per entity type the store serves. */\n tables: Record<string, TableMapping>;\n /** How field names become column names when `columns` does not say: as they are (default) or snake_case. */\n naming?: \"same\" | \"snake\";\n}\n\n/** What the store needs from a resolver's context: the viewer and the read policy the runtime pushed down (`ctx.policy`). */\nexport interface PolicyContext {\n viewer?: unknown;\n policy?: { filter?: Expr };\n now?: () => number;\n}\n\nexport type Row = Record<string, unknown>;\nexport interface Page<T = Row> { items: T[]; cursor: string | null; hasMore: boolean; total: number }\nexport interface PageRequest { first: number; after?: string | null }\n\n/** A SQL fragment with its parameters numbered from 1. */\nexport interface Fragment { sql: string; exact: boolean }\n\nexport function createPgStore(db: Queryable, opts: PgStoreOptions): PgStore {\n return new PgStore(db, opts);\n}\n\ninterface Table { name: string; id: string; idField: string; column(field: string): string | undefined; field(column: string): string; def: { name: string; fields: FieldDef[] } }\n\nexport class PgStore {\n private readonly tables = new Map<string, Table>();\n\n constructor(private readonly db: Queryable, private readonly opts: PgStoreOptions) {\n for (const [type, m] of Object.entries(opts.tables)) {\n const def = opts.ir.types[type];\n if (!def || !(\"fields\" in def)) throw new Error(`@rayfold/postgres: ${type} is not an entity or object of the schema`);\n const columns = new Map<string, string>();\n for (const f of def.fields) columns.set(f.name, m.columns?.[f.name] ?? (opts.naming === \"snake\" ? snake(f.name) : f.name));\n for (const [f, c] of Object.entries(m.columns ?? {})) columns.set(f, c);\n const id = m.id ?? \"id\";\n const idField = [...columns].find(([, c]) => c === id)?.[0] ?? \"id\";\n const fields = new Map([...columns].map(([f, c]) => [c, f]));\n this.tables.set(type, {\n name: m.table.split(\".\").map(quote).join(\".\"),\n id: quote(id),\n idField,\n column: (f) => (columns.has(f) ? quote(columns.get(f)!) : undefined),\n field: (c) => fields.get(c) ?? c,\n def: { name: def.name, fields: def.fields },\n });\n }\n }\n\n /**\n * Rows of `type` for `ids`, in the order asked: `null` where a row is missing or hidden by the pushed-down read policy.\n * One query for any number of ids, which makes it the batch loader for a to-one field.\n */\n async byIds(type: string, ids: readonly unknown[], ctx: PolicyContext = {}, args: Record<string, unknown> = {}): Promise<Array<Row | null>> {\n if (!ids.length) return [];\n const t = this.table(type);\n const params: unknown[] = [[...new Set(ids.filter((x) => x !== null && x !== undefined).map(String))]];\n const where = [`${t.id}::text = ANY($1::text[])`, ...this.policyWhere(t, ctx, args, params)];\n const rows = await this.select(t, `SELECT * FROM ${t.name} WHERE ${where.join(\" AND \")}`, params);\n const byId = new Map(rows.map((r) => [String(r[t.idField]), r]));\n return ids.map((id) => (id === null || id === undefined ? null : (byId.get(String(id)) ?? null)));\n }\n\n /** Rows of `type` whose fields equal `where` (a `null` value matches NULL), in key order. One query. */\n async find(type: string, where: Record<string, unknown> = {}, ctx: PolicyContext = {}, args: Record<string, unknown> = {}): Promise<Row[]> {\n const t = this.table(type);\n const params: unknown[] = [];\n const conds = [...this.equalities(t, where, params), ...this.policyWhere(t, ctx, args, params)];\n return this.select(t, `SELECT * FROM ${t.name}${conds.length ? ` WHERE ${conds.join(\" AND \")}` : \"\"} ORDER BY ${t.id}`, params);\n }\n\n /**\n * One page of `type` in key order, after `page.after` (a key), with `total` counting every visible matching row.\n * One query; a second one counts only when the page comes back empty.\n */\n async page(type: string, page: PageRequest, where: Record<string, unknown> = {}, ctx: PolicyContext = {}, args: Record<string, unknown> = {}): Promise<Page> {\n const t = this.table(type);\n const params: unknown[] = [];\n const conds = [...this.equalities(t, where, params), ...this.policyWhere(t, ctx, args, params)];\n const filtered = `SELECT *, count(*) OVER () AS \"__total\" FROM ${t.name}${conds.length ? ` WHERE ${conds.join(\" AND \")}` : \"\"}`;\n let after = \"\";\n if (page.after) {\n params.push(page.after);\n after = ` WHERE \"__s\".${t.id}::text > $${params.length}::text`;\n }\n params.push(page.first + 1);\n const raw = (await this.db.query<Row>(`SELECT * FROM (${filtered}) AS \"__s\"${after} ORDER BY \"__s\".${t.id}::text LIMIT $${params.length}`, params)).rows;\n let total = raw.length ? Number(raw[0]![\"__total\"]) : 0;\n if (!raw.length && page.after) {\n const count = await this.db.query<{ n: number }>(`SELECT count(*)::int AS n FROM ${t.name}${conds.length ? ` WHERE ${conds.join(\" AND \")}` : \"\"}`, params.slice(0, params.length - 2));\n total = Number(count.rows[0]?.n ?? 0);\n }\n const rows = raw.map((r) => this.row(t, r));\n const items = rows.slice(0, page.first);\n return { items, cursor: items.length ? String(items[items.length - 1]![t.idField]) : null, hasMore: rows.length > page.first, total };\n }\n\n /**\n * For each parent key in `values`, the page of `type` rows whose `field` equals it: the batch loader for a paged\n * one-to-many field (an author's books). One query for the whole level, whatever the number of parents.\n */\n async pagesByField(type: string, field: string, values: readonly unknown[], page: PageRequest, ctx: PolicyContext = {}, args: Record<string, unknown> = {}): Promise<Page[]> {\n if (!values.length) return [];\n const t = this.table(type);\n const col = t.column(field);\n if (!col) throw new Error(`@rayfold/postgres: ${type} has no field ${field}`);\n const params: unknown[] = [[...new Set(values.map(String))]];\n const conds = [`${col}::text = ANY($1::text[])`, ...this.policyWhere(t, ctx, args, params)];\n let after = \"\";\n if (page.after) {\n params.push(page.after);\n after = ` AND \"__s\".${t.id}::text > $${params.length}::text`;\n }\n params.push(page.first + 1);\n const sql =\n `SELECT * FROM (SELECT *, count(*) OVER (PARTITION BY ${col}) AS \"__total\", ` +\n `row_number() OVER (PARTITION BY ${col} ORDER BY ${t.id}::text) AS \"__n\" FROM ${t.name} WHERE ${conds.join(\" AND \")}) AS \"__s\" ` +\n `WHERE TRUE${after} ORDER BY \"__s\".${t.id}::text`;\n const raw = (await this.db.query<Row>(sql, params.slice(0, -1))).rows;\n const groups = new Map<string, { rows: Row[]; total: number }>();\n for (const r of raw) {\n const key = String(r[col.slice(1, -1).replace(/\"\"/g, '\"')]);\n const g = groups.get(key) ?? { rows: [], total: Number(r[\"__total\"]) };\n g.rows.push(this.row(t, r));\n groups.set(key, g);\n }\n return values.map((v) => {\n const g = groups.get(String(v));\n if (!g) return { items: [], cursor: null, hasMore: false, total: 0 };\n const items = g.rows.slice(0, page.first);\n return { items, cursor: items.length ? String(items[items.length - 1]![t.idField]) : null, hasMore: g.rows.length > page.first, total: g.total };\n });\n }\n\n /**\n * One statement for a whole screen: a page of `type` with every nested field the shape asks for, gathered as JSON\n * by correlated subqueries. Depth costs no extra round trip, and each level's read policy is pushed into its own\n * `WHERE`, so a nested list never carries rows its viewer may not see.\n *\n * Nested pages are first pages (`first`), which is what a screen shows; the root page takes a cursor as usual.\n * Every selected field must be a mapped column or a declared relation.\n */\n async screen(type: string, shape: Shape, page: PageRequest, where: Record<string, unknown> = {}, ctx: PolicyContext = {}, args: Record<string, unknown> = {}): Promise<Page> {\n const t = this.table(type);\n const params: unknown[] = [];\n // The caller may pass the shape of the Page the query returns, or the shape of one row.\n const rowShape = t.def.fields.some((f) => f.name === \"items\") ? shape : itemsOf(shape);\n const projection = this.project(t, \"__r0\", rowShape, ctx, args, params, 0);\n const conds = [...this.equalities(t, where, params), ...this.policyWhere(t, ctx, args, params, \"__r0\", ctx.policy?.filter ?? this.filterFor(type))];\n const filtered =\n `SELECT ${projection} AS \"__row\", __r0.${t.id}::text AS \"__key\", count(*) OVER () AS \"__total\" ` +\n `FROM ${t.name} AS __r0${conds.length ? ` WHERE ${conds.join(\" AND \")}` : \"\"}`;\n let after = \"\";\n if (page.after) {\n params.push(page.after);\n after = ` WHERE \"__s\".\"__key\" > $${params.length}::text`;\n }\n params.push(page.first + 1);\n const raw = (await this.db.query<Row>(`SELECT * FROM (${filtered}) AS \"__s\"${after} ORDER BY \"__s\".\"__key\" LIMIT $${params.length}`, params)).rows;\n let total = raw.length ? Number(raw[0]![\"__total\"]) : 0;\n if (!raw.length && page.after) {\n const cp: unknown[] = [];\n const cc = [...this.equalities(t, where, cp), ...this.policyWhere(t, ctx, args, cp, \"__r0\", ctx.policy?.filter ?? this.filterFor(type))];\n const counted = await this.db.query<{ n: number }>(`SELECT count(*)::int AS n FROM ${t.name} AS __r0${cc.length ? ` WHERE ${cc.join(\" AND \")}` : \"\"}`, cp);\n total = Number(counted.rows[0]?.n ?? 0);\n }\n const rows = raw.slice(0, page.first);\n return {\n items: rows.map((r) => r[\"__row\"] as Row),\n cursor: rows.length ? String(rows[rows.length - 1]![\"__key\"]) : null,\n hasMore: raw.length > page.first,\n total,\n };\n }\n\n /** The JSON projection of one level of a screen: scalars from columns, relations from correlated subqueries. */\n private project(t: Table, alias: string, shape: Shape, ctx: PolicyContext, args: Record<string, unknown>, params: unknown[], depth: number): string {\n if (depth > 8) throw new Error(\"@rayfold/postgres: a screen nested deeper than 8 levels\");\n const relations = this.opts.tables[t.def.name]?.relations ?? {};\n const parts: string[] = [];\n for (const item of shape.items) {\n if (item.kind !== \"field\") continue; // views and type conditions are expanded before a shape reaches the store\n const name = item.name;\n const out = item.alias ?? name;\n const def = t.def.fields.find((f) => f.name === name);\n if (!def) throw new Error(`@rayfold/postgres: ${t.def.name} has no field ${name}`);\n const rel = relations[name];\n if (!rel) {\n const col = t.column(name);\n if (!col) throw new Error(`@rayfold/postgres: ${t.def.name}.${name} has no column and no relation`);\n const scalar = def.type.kind === \"named\" ? this.scalarOf(def.type.name) : \"\";\n parts.push(`'${out}', ${alias}.${col}${AS_TEXT.has(scalar) ? \"::text\" : \"\"}`);\n continue;\n }\n const child = this.table(rel.type);\n const ca = `__r${depth + 1}`;\n // A paged field is selected through its Page envelope, so the rows are described by its `items`.\n const selected = item.shape ?? { items: [] };\n const childProjection = this.project(child, ca, rel.kind === \"page\" ? itemsOf(selected) : selected, ctx, args, params, depth + 1);\n const childPolicy = this.policyWhere(child, ctx, args, params, ca, this.filterFor(rel.type));\n if (rel.kind === \"one\") {\n const parentCol = t.column(rel.key);\n if (!parentCol) throw new Error(`@rayfold/postgres: ${t.def.name} has no field ${rel.key}`);\n const conds = [`${ca}.${child.id} = ${alias}.${parentCol}`, ...childPolicy];\n parts.push(`'${out}', (SELECT ${childProjection} FROM ${child.name} AS ${ca} WHERE ${conds.join(\" AND \")} LIMIT 1)`);\n continue;\n }\n const childCol = child.column(rel.key);\n if (!childCol) throw new Error(`@rayfold/postgres: ${rel.type} has no field ${rel.key}`);\n params.push(firstOf(item.args) ?? 10);\n const conds = [`${ca}.${childCol} = ${alias}.${t.id}`, ...childPolicy];\n const rows =\n `SELECT ${childProjection} AS \"__row\", ${ca}.${child.id}::text AS \"__key\", count(*) OVER () AS \"__total\" ` +\n `FROM ${child.name} AS ${ca} WHERE ${conds.join(\" AND \")} ORDER BY ${ca}.${child.id}::text LIMIT $${params.length}`;\n parts.push(\n `'${out}', (SELECT json_build_object(` +\n `'items', coalesce(json_agg(\"__p\".\"__row\" ORDER BY \"__p\".\"__key\"), '[]'::json), ` +\n `'total', coalesce(max(\"__p\".\"__total\"), 0)::int, ` +\n `'hasMore', coalesce(count(*) < max(\"__p\".\"__total\"), false), ` +\n `'cursor', max(\"__p\".\"__key\")` +\n `) FROM (${rows}) AS \"__p\")`,\n );\n }\n return `json_build_object(${parts.join(\", \")})`;\n }\n\n /** The WHERE fragment for the read policy the runtime pushed down, or nothing when there is none to push. */\n private policyWhere(t: Table, ctx: PolicyContext, args: Record<string, unknown>, params: unknown[], alias?: string, own?: Expr): string[] {\n const filter = own ?? ctx.policy?.filter;\n if (!filter) return [];\n const env: ExprEnv = { viewer: ctx.viewer ?? null, args, this: null, ...(ctx.now ? { now: ctx.now } : {}) };\n const f = compilePolicy(filter, env, (name) => {\n const col = t.column(name);\n const def = t.def.fields.find((x) => x.name === name);\n const qualified = col && alias ? `${alias}.${col}` : col;\n return qualified && def && def.type.kind === \"named\" ? { column: qualified, scalar: this.scalarOf(def.type.name) } : undefined;\n }, params);\n return f.sql === \"TRUE\" ? [] : [f.sql];\n }\n\n /** The read policy declared on a type, for the levels of a screen the runtime handed no hint for. */\n private filterFor(type: string): Expr | undefined {\n const def = this.opts.ir.types[type];\n return def ? pushableFilterOf(def.annotations) : undefined;\n }\n\n private equalities(t: Table, where: Record<string, unknown>, params: unknown[]): string[] {\n return Object.entries(where).map(([f, v]) => {\n const col = t.column(f);\n if (!col) throw new Error(`@rayfold/postgres: ${t.def.name} has no field ${f}`);\n if (v === null || v === undefined) return `${col} IS NULL`;\n params.push(v);\n return `${col} = $${params.length}`;\n });\n }\n\n private async select(t: Table, sql: string, params: unknown[]): Promise<Row[]> {\n return (await this.db.query<Row>(sql, params)).rows.map((r) => this.row(t, r));\n }\n\n private row(t: Table, r: Row): Row {\n const out: Row = {};\n for (const [k, v] of Object.entries(r)) if (k !== \"__total\" && k !== \"__n\") out[t.field(k)] = v;\n return out;\n }\n\n private table(type: string): Table {\n const t = this.tables.get(type);\n if (!t) throw new Error(`@rayfold/postgres: no table mapped for ${type}`);\n return t;\n }\n\n private scalarOf(name: string): string {\n const def = this.opts.ir.types[name];\n return def?.kind === \"enum\" ? \"String\" : name;\n }\n}\n\n// ---------------------------------------------------------------- policies to SQL\n\n/** The rows of a `Page<T>` selection: the sub-shape of its `items`, or the shape itself when it selects rows. */\nfunction itemsOf(shape: Shape): Shape {\n const items = shape.items.find((i) => i.kind === \"field\" && i.name === \"items\");\n return items && items.kind === \"field\" && items.shape ? items.shape : shape;\n}\n\n/** Scalars Postgres would render as a JSON number, but Rayfold carries as text. */\nconst AS_TEXT = new Set([\"Decimal\", \"Long\"]);\n\n/** The page size a shape asked for (`page: { first: n }` or `first: n`), when it is a plain number. */\nfunction firstOf(a: Record<string, unknown> | undefined): number | undefined {\n const page = a?.[\"page\"];\n const n = page && typeof page === \"object\" && !Array.isArray(page) ? (page as Record<string, unknown>)[\"first\"] : a?.[\"first\"];\n return typeof n === \"number\" && Number.isInteger(n) && n >= 0 ? n : undefined;\n}\n\n/** The pushable part of a type's `@allow(read:)`, for the levels of a screen the runtime gave no hint for. */\nfunction pushableFilterOf(annotations: Array<{ name: string; args: Record<string, unknown> }>): Expr | undefined {\n const allow = annotations.find((a) => a.name === \"allow\");\n const read = allow?.args[\"read\"];\n return read && typeof read === \"object\" && \"$expr\" in read ? (read as { $expr: Expr }).$expr : undefined;\n}\n\n/** A column a policy may read, with the Rayfold scalar type of its field. */\nexport interface PolicyColumn { column: string; scalar: string }\n\nconst LOOSE: Fragment = { sql: \"TRUE\", exact: false };\nconst constant = (b: boolean): Fragment => ({ sql: b ? \"TRUE\" : \"FALSE\", exact: true });\nconst TEXT = new Set([\"String\", \"ID\"]);\nconst NUMBER = new Set([\"Int\", \"Float\"]);\nconst DECIMAL = new Set([\"Decimal\", \"Long\"]);\nconst DECIMAL_TEXT = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Translates a pushable read policy (spec 06 §4) into a WHERE fragment under `env` (the viewer and args), appending\n * its values to `params`. Parts that read no field of the row are evaluated here, as the runtime would. `exact` is true\n * when the fragment selects exactly the rows the policy allows; otherwise it selects more, never fewer.\n */\nexport function compilePolicy(e: Expr, env: ExprEnv, columnOf: (field: string) => PolicyColumn | undefined, params: unknown[]): Fragment {\n if (!readsRow(e)) {\n try {\n return constant(truthy(evalExpr(e, env)));\n } catch {\n return LOOSE; // an expression that cannot be evaluated denies, but maybe only where it is reached: leave it to the runtime\n }\n }\n switch (e.k) {\n case \"path\": {\n const c = e.path.length === 1 ? columnOf(e.path[0]!) : undefined;\n if (!c) return LOOSE;\n return { sql: c.scalar === \"Boolean\" ? `(${c.column} IS TRUE)` : `(${c.column} IS NOT NULL)`, exact: true };\n }\n case \"not\": {\n const f = compilePolicy(e.e, env, columnOf, params);\n // negating a superset would give a subset, so only an exact fragment can be negated\n return f.exact ? { sql: `(NOT ${f.sql})`, exact: true } : LOOSE;\n }\n case \"bin\": {\n if (e.op === \"&&\" || e.op === \"||\") {\n const l = compilePolicy(e.l, env, columnOf, params);\n const r = compilePolicy(e.r, env, columnOf, params);\n return { sql: `(${l.sql} ${e.op === \"&&\" ? \"AND\" : \"OR\"} ${r.sql})`, exact: l.exact && r.exact };\n }\n const left = rowField(e.l);\n const right = rowField(e.r);\n if ((left === undefined) === (right === undefined)) return LOOSE; // both sides read the row, or neither is a plain field\n const field = (left ?? right)!;\n const other = left !== undefined ? e.r : e.l;\n if (readsRow(other)) return LOOSE;\n const c = columnOf(field);\n if (!c) return LOOSE;\n let v: unknown;\n try {\n v = evalExpr(other, env);\n } catch {\n return LOOSE;\n }\n const op = left !== undefined ? e.op : flip(e.op);\n return compare(c, op, v, params);\n }\n default:\n return LOOSE;\n }\n}\n\nfunction compare(c: PolicyColumn, op: string, v: unknown, params: unknown[]): Fragment {\n const add = (x: unknown) => `$${params.push(x)}`;\n if (op === \"in\") {\n if (!Array.isArray(v)) return constant(false);\n if (!v.every((x) => kindMatches(c.scalar, x))) return LOOSE;\n if (TEXT.has(c.scalar)) return { sql: `COALESCE(${c.column}::text = ANY(${add(v.map(String))}::text[]), FALSE)`, exact: true };\n if (NUMBER.has(c.scalar)) return { sql: `COALESCE(${c.column} = ANY(${add(v)}::numeric[]), FALSE)`, exact: true };\n return LOOSE;\n }\n if (op === \"==\" || op === \"!=\") {\n if (v === null || v === undefined) return { sql: `(${c.column} IS ${op === \"==\" ? \"\" : \"NOT \"}NULL)`, exact: true };\n if (!kindMatches(c.scalar, v)) return LOOSE;\n const not = op === \"==\" ? \"NOT \" : \"\";\n if (TEXT.has(c.scalar)) return { sql: `(${c.column}::text IS ${not}DISTINCT FROM ${add(String(v))}::text)`, exact: true };\n if (NUMBER.has(c.scalar)) return { sql: `(${c.column} IS ${not}DISTINCT FROM ${add(v)}::numeric)`, exact: true };\n if (c.scalar === \"Boolean\") return { sql: `(${c.column} IS ${not}DISTINCT FROM ${add(v)}::boolean)`, exact: true };\n // Decimal and Long travel as text: SQL compares by value, the runtime may compare the text, so equality selects\n // more rows than the policy and inequality could select fewer; only equality is pushed\n if (DECIMAL.has(c.scalar) && op === \"==\") return { sql: `(${c.column} = ${add(String(v))}::numeric)`, exact: false };\n return LOOSE;\n }\n if ([\"<\", \"<=\", \">\", \">=\"].includes(op) && typeof v === \"number\" && NUMBER.has(c.scalar)) {\n return { sql: `COALESCE(${c.column} ${op} ${add(v)}::numeric, FALSE)`, exact: true };\n }\n return LOOSE;\n}\n\n/** Whether `v` is a value the runtime could find equal to a field of this scalar type. */\nfunction kindMatches(scalar: string, v: unknown): boolean {\n if (TEXT.has(scalar)) return typeof v === \"string\";\n if (NUMBER.has(scalar)) return typeof v === \"number\" && Number.isFinite(v);\n if (scalar === \"Boolean\") return typeof v === \"boolean\";\n if (DECIMAL.has(scalar)) return (typeof v === \"string\" && DECIMAL_TEXT.test(v)) || (typeof v === \"number\" && Number.isFinite(v));\n return false;\n}\n\nfunction flip(op: string): string {\n return op === \"<\" ? \">\" : op === \">\" ? \"<\" : op === \"<=\" ? \">=\" : op === \">=\" ? \"<=\" : op;\n}\n\n/** The field name when `e` is `this.<field>`. */\nfunction rowField(e: Expr): string | undefined {\n return e.k === \"path\" && e.root === \"this\" && e.path.length === 1 ? e.path[0] : undefined;\n}\n\nfunction readsRow(e: Expr): boolean {\n switch (e.k) {\n case \"lit\":\n return false;\n case \"path\":\n return e.root === \"this\";\n case \"not\":\n return readsRow(e.e);\n case \"bin\":\n return readsRow(e.l) || readsRow(e.r);\n case \"list\":\n return e.items.some(readsRow);\n case \"call\":\n return e.args.some(readsRow);\n }\n}\n\nfunction truthy(v: unknown): boolean {\n return v !== null && v !== undefined && v !== false;\n}\n\nfunction quote(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\nfunction snake(name: string): string {\n return name.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@rayfold/postgres",
3
+ "version": "0.1.0",
4
+ "description": "Postgres for Rayfold resolvers: batch loads, keyset pages, one query per level, and read policies pushed into SQL.",
5
+ "keywords": [
6
+ "rayfold",
7
+ "api",
8
+ "protocol",
9
+ "postgres",
10
+ "sql",
11
+ "batch",
12
+ "authorization"
13
+ ],
14
+ "license": "Apache-2.0",
15
+ "homepage": "https://eddyboutros.github.io/rayfold/",
16
+ "bugs": {
17
+ "url": "https://github.com/eddyboutros/rayfold/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/eddyboutros/rayfold.git",
22
+ "directory": "packages/postgres"
23
+ },
24
+ "type": "module",
25
+ "sideEffects": false,
26
+ "engines": {
27
+ "node": ">=22"
28
+ },
29
+ "main": "./index.js",
30
+ "types": "./index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./index.d.ts",
34
+ "default": "./index.js"
35
+ }
36
+ },
37
+ "peerDependencies": {
38
+ "@rayfold/schema": "^0.1.0"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ }
43
+ }