@dalgo/dalgo2postgrest 0.0.0-bootstrap.0 → 0.2.1

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,60 @@
1
+ import { UnsupportedError } from "@dalgo/core";
2
+ /** Stable, adapter-owned cursor identity. A cursor from another adapter is foreign. */
3
+ export declare const POSTGREST_CURSOR_ADAPTER = "@dalgo/dalgo2postgrest";
4
+ export declare const POSTGREST_CURSOR_VERSION = 1;
5
+ export type PostgrestCursorValue = string | number | boolean | null;
6
+ export type PostgrestOrderDirection = "asc" | "desc";
7
+ export interface PostgrestCursorOrder {
8
+ /** Physical PostgREST column, already mapped from a DALgo field. */
9
+ readonly field: string;
10
+ readonly direction: PostgrestOrderDirection;
11
+ }
12
+ /**
13
+ * The immutable part of a cursor. Every value that can change the meaning of a
14
+ * continuation is bound here so a cursor from another endpoint, relation, role
15
+ * scope, filter, order, projection or page size is refused instead of silently
16
+ * returning a different result set.
17
+ */
18
+ export interface PostgrestCursorBinding {
19
+ readonly endpoint: string;
20
+ readonly collection: string;
21
+ readonly relation: string;
22
+ readonly schema?: string;
23
+ readonly idColumn: string;
24
+ readonly select: "*";
25
+ readonly filters: readonly (readonly [string, string])[];
26
+ readonly orders: readonly PostgrestCursorOrder[];
27
+ readonly limit: number;
28
+ readonly identityScope?: string;
29
+ }
30
+ export type PostgrestCursorErrorReason = "malformed" | "foreign" | "changed-query";
31
+ /**
32
+ * A refused cursor. It extends `UnsupportedError` so callers that already guard
33
+ * deliberate adapter refusals keep working. The message never contains row
34
+ * values, headers, tokens or a transport error.
35
+ */
36
+ export declare class PostgrestCursorError extends UnsupportedError {
37
+ readonly reason: PostgrestCursorErrorReason;
38
+ constructor(reason: PostgrestCursorErrorReason);
39
+ }
40
+ /**
41
+ * Encodes the next-page position as one opaque base64url token. The token is
42
+ * integrity-checked by comparing its complete binding with the current query;
43
+ * it is not a capability and must never be treated as an authorization token.
44
+ */
45
+ export declare function encodePostgrestCursor(binding: PostgrestCursorBinding, page: number, values: readonly unknown[], maxBytes: number): string;
46
+ export interface DecodedPostgrestCursor {
47
+ readonly page: number;
48
+ readonly values: readonly PostgrestCursorValue[];
49
+ }
50
+ /** Decodes a cursor and proves it belongs to the exact current query. */
51
+ export declare function decodePostgrestCursor(cursor: string, binding: PostgrestCursorBinding, maxBytes: number): DecodedPostgrestCursor;
52
+ /**
53
+ * Builds the PostgREST `or` expression for keyset continuation after one row.
54
+ *
55
+ * PostgREST/PostgreSQL defaults are used: `asc` sorts nulls last and `desc`
56
+ * sorts nulls first. The caller must append the configured primary key as a
57
+ * final non-null tie-breaker; this function relies on that final key to make a
58
+ * continuation expressible even when a user order column is null.
59
+ */
60
+ export declare function postgrestKeysetFilter(orders: readonly PostgrestCursorOrder[], values: readonly PostgrestCursorValue[]): string;
package/dist/cursor.js ADDED
@@ -0,0 +1,261 @@
1
+ import { UnsupportedError } from "@dalgo/core";
2
+ import { postgrestFieldKey } from "./fields.js";
3
+ import { isUnsafeNativeNumber, parseLosslessJson } from "./json.js";
4
+ /** Stable, adapter-owned cursor identity. A cursor from another adapter is foreign. */
5
+ export const POSTGREST_CURSOR_ADAPTER = "@dalgo/dalgo2postgrest";
6
+ export const POSTGREST_CURSOR_VERSION = 1;
7
+ /**
8
+ * A refused cursor. It extends `UnsupportedError` so callers that already guard
9
+ * deliberate adapter refusals keep working. The message never contains row
10
+ * values, headers, tokens or a transport error.
11
+ */
12
+ export class PostgrestCursorError extends UnsupportedError {
13
+ reason;
14
+ constructor(reason) {
15
+ super(`PostgREST query cursor (${reason})`);
16
+ this.reason = reason;
17
+ }
18
+ }
19
+ function isPlainObject(value) {
20
+ return typeof value === "object" && value !== null && !Array.isArray(value)
21
+ && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
22
+ }
23
+ function cursorValue(value) {
24
+ if (value === null || typeof value === "string" || typeof value === "boolean")
25
+ return value;
26
+ if (typeof value === "number") {
27
+ if (!Number.isFinite(value) || Object.is(value, -0) || isUnsafeNativeNumber(value)) {
28
+ throw new TypeError("PostgREST query cursor numbers must be finite, lossless and not -0");
29
+ }
30
+ return value;
31
+ }
32
+ throw new UnsupportedError("PostgREST query cursor values");
33
+ }
34
+ function scalar(value) {
35
+ if (value === null)
36
+ return "null";
37
+ // Keyset terms live inside the `or=(...)` expression, a PostgREST list
38
+ // context, so a string operand must be quoted and escaped there.
39
+ if (typeof value === "string")
40
+ return `"${value.replaceAll(/(["\\])/gu, "\\$1")}"`;
41
+ if (typeof value === "number") {
42
+ if (!Number.isFinite(value) || Object.is(value, -0) || isUnsafeNativeNumber(value)) {
43
+ throw new TypeError("PostgREST query cursor numbers must be finite, lossless and not -0");
44
+ }
45
+ return String(value);
46
+ }
47
+ return String(value);
48
+ }
49
+ function equality(order, value) {
50
+ const field = postgrestFieldKey(order.field);
51
+ return value === null ? `${field}.is.null` : `${field}.eq.${scalar(value)}`;
52
+ }
53
+ function encodedTextLength(value) {
54
+ return new TextEncoder().encode(value).byteLength;
55
+ }
56
+ function base64UrlEncode(value) {
57
+ const bytes = new TextEncoder().encode(value);
58
+ let binary = "";
59
+ for (const byte of bytes)
60
+ binary += String.fromCharCode(byte);
61
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
62
+ }
63
+ function base64UrlDecode(value) {
64
+ if (!/^[A-Za-z0-9_-]+$/u.test(value))
65
+ throw new PostgrestCursorError("malformed");
66
+ const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
67
+ let binary;
68
+ try {
69
+ binary = atob(`${base64}${"=".repeat((4 - (base64.length % 4)) % 4)}`);
70
+ }
71
+ catch {
72
+ throw new PostgrestCursorError("malformed");
73
+ }
74
+ const bytes = new Uint8Array(binary.length);
75
+ for (let index = 0; index < binary.length; index += 1)
76
+ bytes[index] = binary.charCodeAt(index);
77
+ try {
78
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
79
+ }
80
+ catch {
81
+ throw new PostgrestCursorError("malformed");
82
+ }
83
+ }
84
+ function sameStrings(left, right) {
85
+ return left.length === right.length && left.every((value, index) => value === right[index]);
86
+ }
87
+ function sameBinding(left, right) {
88
+ return left.endpoint === right.endpoint
89
+ && left.collection === right.collection
90
+ && left.relation === right.relation
91
+ && left.schema === right.schema
92
+ && left.idColumn === right.idColumn
93
+ && left.select === right.select
94
+ && left.limit === right.limit
95
+ && left.identityScope === right.identityScope
96
+ && left.filters.length === right.filters.length
97
+ && left.filters.every((filter, index) => {
98
+ const other = right.filters[index];
99
+ return other !== undefined && filter[0] === other[0] && filter[1] === other[1];
100
+ })
101
+ && left.orders.length === right.orders.length
102
+ && left.orders.every((order, index) => {
103
+ const other = right.orders[index];
104
+ return other !== undefined && order.field === other.field && order.direction === other.direction;
105
+ });
106
+ }
107
+ function parseOrders(value) {
108
+ if (!Array.isArray(value) || value.length === 0)
109
+ throw new PostgrestCursorError("malformed");
110
+ return value.map((order) => {
111
+ if (!isPlainObject(order) || typeof order.field !== "string" || (order.direction !== "asc" && order.direction !== "desc")) {
112
+ throw new PostgrestCursorError("malformed");
113
+ }
114
+ return { field: order.field, direction: order.direction };
115
+ });
116
+ }
117
+ function parseFilters(value) {
118
+ if (!Array.isArray(value))
119
+ throw new PostgrestCursorError("malformed");
120
+ return value.map((filter) => {
121
+ if (!Array.isArray(filter) || filter.length !== 2 || typeof filter[0] !== "string" || typeof filter[1] !== "string") {
122
+ throw new PostgrestCursorError("malformed");
123
+ }
124
+ return [filter[0], filter[1]];
125
+ });
126
+ }
127
+ function parseValues(value, expected) {
128
+ if (!Array.isArray(value) || value.length !== expected)
129
+ throw new PostgrestCursorError("malformed");
130
+ return value.map((item) => {
131
+ if (item === null || typeof item === "string" || typeof item === "boolean")
132
+ return item;
133
+ if (typeof item === "number" && Number.isFinite(item) && !Object.is(item, -0) && !isUnsafeNativeNumber(item))
134
+ return item;
135
+ throw new PostgrestCursorError("malformed");
136
+ });
137
+ }
138
+ /**
139
+ * Encodes the next-page position as one opaque base64url token. The token is
140
+ * integrity-checked by comparing its complete binding with the current query;
141
+ * it is not a capability and must never be treated as an authorization token.
142
+ */
143
+ export function encodePostgrestCursor(binding, page, values, maxBytes) {
144
+ if (!Number.isSafeInteger(page) || page < 2)
145
+ throw new TypeError("PostgREST query cursor page must be at least 2");
146
+ const encodedValues = values.map(cursorValue);
147
+ if (encodedValues.length !== binding.orders.length)
148
+ throw new TypeError("PostgREST query cursor value count does not match its order");
149
+ const payload = {
150
+ adapter: POSTGREST_CURSOR_ADAPTER,
151
+ version: POSTGREST_CURSOR_VERSION,
152
+ ...binding,
153
+ page,
154
+ values: encodedValues,
155
+ };
156
+ const encoded = base64UrlEncode(JSON.stringify(payload));
157
+ if (encodedTextLength(encoded) > maxBytes)
158
+ throw new RangeError("PostgREST query cursor exceeds maxCursorBytes");
159
+ return encoded;
160
+ }
161
+ /** Decodes a cursor and proves it belongs to the exact current query. */
162
+ export function decodePostgrestCursor(cursor, binding, maxBytes) {
163
+ if (encodedTextLength(cursor) > maxBytes)
164
+ throw new PostgrestCursorError("malformed");
165
+ let value;
166
+ try {
167
+ value = parseLosslessJson(base64UrlDecode(cursor));
168
+ }
169
+ catch (error) {
170
+ if (error instanceof PostgrestCursorError)
171
+ throw error;
172
+ throw new PostgrestCursorError("malformed");
173
+ }
174
+ if (!isPlainObject(value) || value.adapter !== POSTGREST_CURSOR_ADAPTER)
175
+ throw new PostgrestCursorError("foreign");
176
+ if (value.version !== POSTGREST_CURSOR_VERSION)
177
+ throw new PostgrestCursorError("foreign");
178
+ if (typeof value.endpoint !== "string"
179
+ || typeof value.collection !== "string"
180
+ || typeof value.relation !== "string"
181
+ || (value.schema !== undefined && typeof value.schema !== "string")
182
+ || typeof value.idColumn !== "string"
183
+ || value.select !== "*"
184
+ || !Number.isSafeInteger(value.limit)
185
+ || (value.identityScope !== undefined && typeof value.identityScope !== "string")
186
+ || !Number.isSafeInteger(value.page)
187
+ || value.page < 2) {
188
+ throw new PostgrestCursorError("malformed");
189
+ }
190
+ const parsedBinding = {
191
+ endpoint: value.endpoint,
192
+ collection: value.collection,
193
+ relation: value.relation,
194
+ ...(value.schema === undefined ? {} : { schema: value.schema }),
195
+ idColumn: value.idColumn,
196
+ select: "*",
197
+ filters: parseFilters(value.filters),
198
+ orders: parseOrders(value.orders),
199
+ limit: value.limit,
200
+ ...(value.identityScope === undefined ? {} : { identityScope: value.identityScope }),
201
+ };
202
+ if (!sameStrings([parsedBinding.relation], [binding.relation])
203
+ || parsedBinding.collection !== binding.collection
204
+ || parsedBinding.endpoint !== binding.endpoint) {
205
+ throw new PostgrestCursorError("foreign");
206
+ }
207
+ if (!sameBinding(parsedBinding, binding))
208
+ throw new PostgrestCursorError("changed-query");
209
+ return {
210
+ page: value.page,
211
+ values: parseValues(value.values, binding.orders.length),
212
+ };
213
+ }
214
+ /**
215
+ * Builds the PostgREST `or` expression for keyset continuation after one row.
216
+ *
217
+ * PostgREST/PostgreSQL defaults are used: `asc` sorts nulls last and `desc`
218
+ * sorts nulls first. The caller must append the configured primary key as a
219
+ * final non-null tie-breaker; this function relies on that final key to make a
220
+ * continuation expressible even when a user order column is null.
221
+ */
222
+ export function postgrestKeysetFilter(orders, values) {
223
+ if (orders.length === 0 || values.length !== orders.length) {
224
+ throw new PostgrestCursorError("malformed");
225
+ }
226
+ const terms = [];
227
+ for (let index = 0; index < orders.length; index += 1) {
228
+ const order = orders[index];
229
+ const value = values[index];
230
+ if (order === undefined || value === undefined)
231
+ throw new PostgrestCursorError("malformed");
232
+ const field = postgrestFieldKey(order.field);
233
+ const prefix = orders.slice(0, index).map((item, prefixIndex) => {
234
+ const prefixValue = values[prefixIndex];
235
+ if (prefixValue === undefined)
236
+ throw new PostgrestCursorError("malformed");
237
+ return equality(item, prefixValue);
238
+ });
239
+ const nullsLast = order.direction === "asc";
240
+ const after = [];
241
+ if (value === null) {
242
+ if (!nullsLast)
243
+ after.push(`${field}.not.is.null`);
244
+ }
245
+ else {
246
+ after.push(`${field}.${order.direction === "asc" ? "gt" : "lt"}.${scalar(value)}`);
247
+ // The final order key is the non-null primary-key tie-breaker, so a
248
+ // null branch would only add a term that can never match.
249
+ if (nullsLast && index < orders.length - 1)
250
+ after.push(`${field}.is.null`);
251
+ }
252
+ for (const condition of after) {
253
+ const term = [...prefix, condition];
254
+ terms.push(term.length === 1 ? condition : `and(${term.join(",")})`);
255
+ }
256
+ }
257
+ if (terms.length === 0)
258
+ throw new PostgrestCursorError("malformed");
259
+ return `(${terms.join(",")})`;
260
+ }
261
+ //# sourceMappingURL=cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../src/cursor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ import { Key, type Codec, type Database, type QueryPage, type ReadwriteTransaction, type RecordSnapshot, type StructuredQuery, type UpdateData, type WriteSession } from "@dalgo/core";
2
+ import { type PostgrestConnectionStatus, type PostgrestDiscovery } from "./discovery.js";
3
+ export type PostgrestFetch = typeof globalThis.fetch;
4
+ export type PostgrestHeaders = Readonly<Record<string, string>>;
5
+ export type PostgrestHeaderProvider = PostgrestHeaders | (() => PostgrestHeaders | Promise<PostgrestHeaders>);
6
+ export interface PostgrestDatabaseOptions {
7
+ /** PostgREST API root, normally the configured server origin or a proxy subpath. */
8
+ readonly baseUrl: string;
9
+ /**
10
+ * Allows plain HTTP only for `localhost`, `127.0.0.1` or `[::1]`. HTTPS is
11
+ * the default and remains the only option for every hosted origin.
12
+ */
13
+ readonly allowInsecureLoopbackHttp?: boolean;
14
+ /** PostgREST schema/profile selected with `Accept-Profile`/`Content-Profile`. */
15
+ readonly schema?: string;
16
+ /** Alias for `schema`; supplying both must resolve to the same profile. */
17
+ readonly profile?: string;
18
+ /** Name of the primary-key column shared by exposed adapter relations. Defaults to `id`. */
19
+ readonly idColumn?: string;
20
+ /** Maps a top-level DALgo collection name to an exposed PostgREST relation. */
21
+ readonly relationName?: (collection: string) => string;
22
+ /** Re-evaluated for every request; use it for refreshed Bearer or cookie headers. */
23
+ readonly headers?: PostgrestHeaderProvider;
24
+ /**
25
+ * Optional stable, non-secret tenant/user/workspace identity. It is bound
26
+ * into opaque cursors so a cursor cannot be replayed under another role
27
+ * scope. Never put a token or password here.
28
+ */
29
+ readonly identityScope?: string;
30
+ readonly fetch?: PostgrestFetch;
31
+ readonly timeoutMs?: number;
32
+ readonly maxRequestBytes?: number;
33
+ readonly maxResponseBytes?: number;
34
+ readonly maxGetManyKeys?: number;
35
+ readonly maxParallelReads?: number;
36
+ readonly maxQueryLimit?: number;
37
+ readonly maxQueryOffset?: number;
38
+ readonly maxQueryPages?: number;
39
+ readonly maxDiscoveryRelations?: number;
40
+ readonly maxDiscoveryFieldsPerRelation?: number;
41
+ readonly maxDiscoveryBytes?: number;
42
+ readonly maxCursorBytes?: number;
43
+ readonly maxSchemaDepth?: number;
44
+ }
45
+ /** Deliberately excludes PostgREST/PostgreSQL error bodies, which can contain row values. */
46
+ export declare class PostgrestHttpError extends Error {
47
+ readonly status: number;
48
+ constructor(status: number);
49
+ }
50
+ /** A redacted failure while obtaining headers, following a request, or consuming a response. */
51
+ export declare class PostgrestRequestError extends Error {
52
+ constructor();
53
+ }
54
+ export declare class PostgrestDatabase implements Database, WriteSession {
55
+ #private;
56
+ constructor(options: PostgrestDatabaseOptions);
57
+ get<T>(key: Key, codec?: Codec<T>): Promise<RecordSnapshot<T>>;
58
+ getMany<T>(keys: readonly Key[], codec?: Codec<T>): Promise<readonly RecordSnapshot<T>[]>;
59
+ insert<T>(key: Key, data: T, codec?: Codec<T>): Promise<void>;
60
+ set<T>(key: Key, data: T, codec?: Codec<T>): Promise<void>;
61
+ update(key: Key, data: UpdateData): Promise<void>;
62
+ delete(key: Key): Promise<void>;
63
+ query<T>(query: StructuredQuery<T>): Promise<QueryPage<T>>;
64
+ /**
65
+ * Cancellable query entry point with an optional adapter-owned keyset cursor.
66
+ * The caller signal covers header acquisition, fetch and response-body
67
+ * reading, and shares one deadline with `query`.
68
+ */
69
+ queryWithSignal<T>(query: StructuredQuery<T>, signal: AbortSignal): Promise<QueryPage<T>>;
70
+ private queryPage;
71
+ private prepareQuery;
72
+ private cursorOrders;
73
+ private legacyOrders;
74
+ private cursorBinding;
75
+ /** Read-only provider metadata. It never calls an administration endpoint. */
76
+ discover(signal?: AbortSignal): Promise<PostgrestDiscovery>;
77
+ /** A safe UI-factory preflight: proves transport, headers and metadata access. */
78
+ testConnection(signal?: AbortSignal): Promise<PostgrestConnectionStatus>;
79
+ private get discoveryBounds();
80
+ private discoveryUnavailable;
81
+ private readonly metadataBody;
82
+ runReadwriteTransaction<Result>(callback: (transaction: ReadwriteTransaction) => Promise<Result>): Promise<Result>;
83
+ private relationUrl;
84
+ private relationForCollection;
85
+ private relationUrlForCollection;
86
+ private keyUrl;
87
+ private payload;
88
+ private writeHeaders;
89
+ private profileHeaders;
90
+ private readonly discard;
91
+ private request;
92
+ private rows;
93
+ private assertExactlyOne;
94
+ private record;
95
+ }