@burojs/hasura 0.2.0 → 0.5.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/README.md +15 -16
- package/dist/index.cjs +912 -772
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +594 -285
- package/dist/index.d.ts +594 -285
- package/dist/index.js +899 -770
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -1,854 +1,994 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _burojs_core = require("@burojs/core");
|
|
3
|
+
let graphql_ws = require("graphql-ws");
|
|
4
|
+
//#region src/cursor.ts
|
|
5
|
+
const HASURA_CURSOR_CODES = Object.freeze({
|
|
6
|
+
unorderable: "cursor-unorderable",
|
|
7
|
+
backward: "cursor-backward-unsupported",
|
|
8
|
+
limit: "cursor-limit-invalid",
|
|
9
|
+
malformed: "cursor-malformed",
|
|
10
|
+
mismatch: "cursor-ordering-mismatch",
|
|
11
|
+
boundaryNull: "cursor-boundary-null"
|
|
12
|
+
});
|
|
13
|
+
const CURSOR_VERSION = 1;
|
|
14
|
+
function cursorError(code, kind, message) {
|
|
15
|
+
return {
|
|
16
|
+
kind,
|
|
17
|
+
code,
|
|
18
|
+
message
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function orderingSignature(ordering) {
|
|
22
|
+
return ordering.map((column) => `${column.field}:${column.direction}`).join(",");
|
|
23
|
+
}
|
|
24
|
+
function effectiveCursorOrdering(sort, mapping) {
|
|
25
|
+
const pkField = mapping.pkField ?? "id";
|
|
26
|
+
const requested = (sort ?? []).filter((entry) => Boolean(entry.field));
|
|
27
|
+
const columns = requested.length > 0 ? requested.map((entry) => ({
|
|
28
|
+
field: entry.field,
|
|
29
|
+
direction: entry.direction
|
|
30
|
+
})) : [{
|
|
31
|
+
field: mapping.orderByField ?? pkField,
|
|
32
|
+
direction: "desc"
|
|
33
|
+
}];
|
|
34
|
+
if (columns.some((column) => column.field === pkField)) return columns;
|
|
35
|
+
const last = columns[columns.length - 1];
|
|
36
|
+
return [...columns, {
|
|
37
|
+
field: pkField,
|
|
38
|
+
direction: last?.direction ?? "asc"
|
|
39
|
+
}];
|
|
40
|
+
}
|
|
41
|
+
function assertCursorOrderable(ordering, sort, mapping) {
|
|
42
|
+
const nulled = (sort ?? []).find((entry) => entry.nulls !== void 0);
|
|
43
|
+
if (nulled !== void 0) throw cursorError(HASURA_CURSOR_CODES.unorderable, "capability", `@burojs/hasura cannot cursor-paginate '${mapping.table}' ordered by '${nulled.field}' with an explicit nulls placement: a keyset predicate over NULLs is not expressible here. Sort without \`nulls\`, or use offset pagination.`);
|
|
44
|
+
const selected = new Set(mapping.selectFields);
|
|
45
|
+
const missing = ordering.filter((column) => !selected.has(column.field));
|
|
46
|
+
if (missing.length > 0) throw cursorError(HASURA_CURSOR_CODES.unorderable, "capability", `@burojs/hasura cannot cursor-paginate '${mapping.table}': the ordering needs ${missing.map((column) => `'${column.field}'`).join(", ")}, which the mapping does not select, so no page can carry the value the next cursor is made of. Cursor pagination orders by the sort columns plus the primary key ('${ordering[ordering.length - 1]?.field ?? "id"}'), and every one of them must be in \`selectFields\`.`);
|
|
47
|
+
}
|
|
48
|
+
function assertCursorLimit(limit, table) {
|
|
49
|
+
if (typeof limit !== "number" || !Number.isInteger(limit) || limit <= 0) throw cursorError(HASURA_CURSOR_CODES.limit, "validation", `@burojs/hasura cursor pagination on '${table}' needs a positive integer \`limit\`, got ${JSON.stringify(limit)}.`);
|
|
50
|
+
}
|
|
51
|
+
function base64UrlEncode(text) {
|
|
52
|
+
const bytes = new TextEncoder().encode(text);
|
|
53
|
+
let binary = "";
|
|
54
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
55
|
+
return (typeof btoa === "function" ? btoa(binary) : Buffer.from(bytes).toString("base64")).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
56
|
+
}
|
|
57
|
+
function base64UrlDecode(text) {
|
|
58
|
+
const base64 = text.replace(/-/g, "+").replace(/_/g, "/");
|
|
59
|
+
if (typeof atob === "function") {
|
|
60
|
+
const binary = atob(base64);
|
|
61
|
+
const bytes = new Uint8Array(binary.length);
|
|
62
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
63
|
+
return new TextDecoder().decode(bytes);
|
|
64
|
+
}
|
|
65
|
+
return Buffer.from(base64, "base64").toString("utf8");
|
|
66
|
+
}
|
|
67
|
+
function encodeCursor(ordering, row, table) {
|
|
68
|
+
const values = [];
|
|
69
|
+
for (const column of ordering) {
|
|
70
|
+
const value = row[column.field];
|
|
71
|
+
if (value === null || value === void 0) throw cursorError(HASURA_CURSOR_CODES.boundaryNull, "capability", `@burojs/hasura cannot mint a cursor for '${table}': the last row of the page has no value for the ordering column '${column.field}', and a keyset predicate over NULL matches nothing. Order by a non-null column, or use offset pagination.`);
|
|
72
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") throw cursorError(HASURA_CURSOR_CODES.boundaryNull, "capability", `@burojs/hasura cannot mint a cursor for '${table}': ordering column '${column.field}' holds ${typeof value}, which has no total order to seek on.`);
|
|
73
|
+
values.push(value);
|
|
74
|
+
}
|
|
75
|
+
const envelope = {
|
|
76
|
+
v: CURSOR_VERSION,
|
|
77
|
+
o: orderingSignature(ordering),
|
|
78
|
+
k: values
|
|
79
|
+
};
|
|
80
|
+
return base64UrlEncode(JSON.stringify(envelope));
|
|
81
|
+
}
|
|
82
|
+
function decodeCursor(raw, ordering, table) {
|
|
83
|
+
if (typeof raw !== "string" || raw.length === 0) throw cursorError(HASURA_CURSOR_CODES.malformed, "validation", `@burojs/hasura got a non-string cursor for '${table}': ${JSON.stringify(raw)}. A cursor is opaque — pass back exactly what \`pageInfo.nextCursor\` returned.`);
|
|
84
|
+
let envelope;
|
|
85
|
+
try {
|
|
86
|
+
envelope = JSON.parse(base64UrlDecode(raw));
|
|
87
|
+
} catch (cause) {
|
|
88
|
+
throw {
|
|
89
|
+
...cursorError(HASURA_CURSOR_CODES.malformed, "validation", `@burojs/hasura could not decode the cursor for '${table}'. A cursor is opaque — pass back exactly what \`pageInfo.nextCursor\` returned.`),
|
|
90
|
+
cause
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (envelope === null || typeof envelope !== "object" || envelope.v !== CURSOR_VERSION || typeof envelope.o !== "string" || !Array.isArray(envelope.k)) throw cursorError(HASURA_CURSOR_CODES.malformed, "validation", `@burojs/hasura got a cursor for '${table}' it did not mint (envelope version ${String(envelope?.v)}).`);
|
|
94
|
+
const signature = orderingSignature(ordering);
|
|
95
|
+
if (envelope.o !== signature) throw cursorError(HASURA_CURSOR_CODES.mismatch, "validation", `@burojs/hasura refuses a cursor for '${table}' minted under ordering '${envelope.o}' against a request ordered '${signature}': the page it addressed no longer exists. Restart from the first page when the sort changes.`);
|
|
96
|
+
if (envelope.k.length !== ordering.length) throw cursorError(HASURA_CURSOR_CODES.malformed, "validation", `@burojs/hasura got a cursor for '${table}' carrying ${envelope.k.length} boundary values for ${ordering.length} ordering columns.`);
|
|
97
|
+
return envelope.k;
|
|
98
|
+
}
|
|
99
|
+
function keysetWhere(ordering, boundary, int64Fields) {
|
|
100
|
+
const int64 = new Set(int64Fields ?? []);
|
|
101
|
+
const value = (index, field) => {
|
|
102
|
+
const raw = boundary[index];
|
|
103
|
+
return int64.has(field) ? String(raw) : raw;
|
|
104
|
+
};
|
|
105
|
+
const branches = [];
|
|
106
|
+
for (let index = 0; index < ordering.length; index += 1) {
|
|
107
|
+
const column = ordering[index];
|
|
108
|
+
if (column === void 0) continue;
|
|
109
|
+
const strict = column.direction === "desc" ? "_lt" : "_gt";
|
|
110
|
+
const tail = { [column.field]: { [strict]: value(index, column.field) } };
|
|
111
|
+
if (index === 0) {
|
|
112
|
+
branches.push(tail);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const clauses = [];
|
|
116
|
+
for (let prior = 0; prior < index; prior += 1) {
|
|
117
|
+
const priorColumn = ordering[prior];
|
|
118
|
+
if (priorColumn === void 0) continue;
|
|
119
|
+
clauses.push({ [priorColumn.field]: { _eq: value(prior, priorColumn.field) } });
|
|
120
|
+
}
|
|
121
|
+
clauses.push(tail);
|
|
122
|
+
branches.push({ _and: clauses });
|
|
123
|
+
}
|
|
124
|
+
return { _or: branches };
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/endpoint.ts
|
|
128
|
+
const DEFAULT_HASURA_ENDPOINT = "/gql/v1/graphql";
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/live-provider.ts
|
|
10
131
|
function hasuraWsUrl(endpoint) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
132
|
+
if (typeof window === "undefined") return endpoint;
|
|
133
|
+
const url = new URL(endpoint, window.location.origin);
|
|
134
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
135
|
+
return url.toString();
|
|
15
136
|
}
|
|
16
137
|
function listSubscription(mapping) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
138
|
+
const where = mapping.fixedFilter;
|
|
139
|
+
return {
|
|
140
|
+
query: where ? `subscription Live_${mapping.table}_list($where: ${mapping.table}_bool_exp) {
|
|
20
141
|
${mapping.table}_aggregate(where: $where) { aggregate { count max { updated_at } } }
|
|
21
142
|
}` : `subscription Live_${mapping.table}_list {
|
|
22
143
|
${mapping.table}_aggregate { aggregate { count max { updated_at } } }
|
|
23
144
|
}`,
|
|
24
|
-
|
|
25
|
-
|
|
145
|
+
...where === void 0 ? {} : { variables: { where } }
|
|
146
|
+
};
|
|
26
147
|
}
|
|
27
148
|
function oneSubscription(mapping, id) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
149
|
+
const pkField = mapping.pkField ?? "id";
|
|
150
|
+
const pkType = mapping.pkType ?? "bigint";
|
|
151
|
+
const root = mapping.byPkRoot === false ? null : mapping.byPkRoot ?? `${mapping.table}_by_pk`;
|
|
152
|
+
if (root === null) return null;
|
|
153
|
+
return {
|
|
154
|
+
query: `subscription Live_${mapping.table}_one($id: ${pkType}!) {
|
|
34
155
|
${root}(${pkField}: $id) { ${pkField} updated_at }
|
|
35
156
|
}`,
|
|
36
|
-
|
|
37
|
-
|
|
157
|
+
variables: { id }
|
|
158
|
+
};
|
|
38
159
|
}
|
|
39
160
|
function createHasuraLiveProvider(config) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
161
|
+
const listeners = new Set();
|
|
162
|
+
const endpoint = config.endpoint ?? "/gql/v1/graphql";
|
|
163
|
+
const role = config.role ?? null;
|
|
164
|
+
let client;
|
|
165
|
+
const status = (value) => {
|
|
166
|
+
for (const listener of listeners) listener(value);
|
|
167
|
+
};
|
|
168
|
+
function getClient() {
|
|
169
|
+
if (client !== void 0) return client;
|
|
170
|
+
const options = {
|
|
171
|
+
url: hasuraWsUrl(endpoint),
|
|
172
|
+
lazy: true,
|
|
173
|
+
keepAlive: 1e4,
|
|
174
|
+
retryAttempts: Number.POSITIVE_INFINITY,
|
|
175
|
+
shouldRetry: () => true,
|
|
176
|
+
connectionParams: async () => {
|
|
177
|
+
const token = await config.getToken();
|
|
178
|
+
return { headers: {
|
|
179
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
180
|
+
...role ? { "X-Hasura-Role": role } : {}
|
|
181
|
+
} };
|
|
182
|
+
},
|
|
183
|
+
on: {
|
|
184
|
+
connecting: () => status("connecting"),
|
|
185
|
+
connected: () => status("connected"),
|
|
186
|
+
closed: () => status("degraded")
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
client = config.clientFactory?.(options) ?? (0, graphql_ws.createClient)(options);
|
|
190
|
+
return client;
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
declaredPushInvalidation() {
|
|
194
|
+
return Object.entries(config.resources).filter(([, mapping]) => mapping?.live === true).map(([resource]) => resource);
|
|
195
|
+
},
|
|
196
|
+
async open(descriptor, openContext) {
|
|
197
|
+
if (openContext.signal.aborted) return null;
|
|
198
|
+
const mapping = config.resources[descriptor.resource];
|
|
199
|
+
if (mapping?.live !== true) return null;
|
|
200
|
+
const payload = descriptor.kind === "list" ? listSubscription(mapping) : descriptor.kind === "one" ? oneSubscription(mapping, descriptor.id) : null;
|
|
201
|
+
if (payload === null) return null;
|
|
202
|
+
return {
|
|
203
|
+
kind: "invalidate",
|
|
204
|
+
initial: "query",
|
|
205
|
+
subscribe(observer, subscribeContext) {
|
|
206
|
+
if (openContext.signal.aborted || subscribeContext.signal.aborted) return () => void 0;
|
|
207
|
+
let primed = false;
|
|
208
|
+
let closed = false;
|
|
209
|
+
let upstream = void 0;
|
|
210
|
+
const close = () => {
|
|
211
|
+
if (closed) return;
|
|
212
|
+
closed = true;
|
|
213
|
+
listeners.delete(onStatus);
|
|
214
|
+
openContext.signal.removeEventListener("abort", close);
|
|
215
|
+
subscribeContext.signal.removeEventListener("abort", close);
|
|
216
|
+
upstream?.();
|
|
217
|
+
};
|
|
218
|
+
const onStatus = (value) => {
|
|
219
|
+
if (!closed) observer.status(value);
|
|
220
|
+
};
|
|
221
|
+
const sink = {
|
|
222
|
+
next: () => {
|
|
223
|
+
if (closed) return;
|
|
224
|
+
if (!primed) {
|
|
225
|
+
primed = true;
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
observer.next(void 0);
|
|
229
|
+
},
|
|
230
|
+
error: (error) => {
|
|
231
|
+
if (closed) return;
|
|
232
|
+
observer.error((0, _burojs_core.normalizeDataError)(error));
|
|
233
|
+
close();
|
|
234
|
+
},
|
|
235
|
+
complete: () => {
|
|
236
|
+
if (!closed) observer.status("degraded");
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
listeners.add(onStatus);
|
|
240
|
+
openContext.signal.addEventListener("abort", close, { once: true });
|
|
241
|
+
subscribeContext.signal.addEventListener("abort", close, { once: true });
|
|
242
|
+
upstream = getClient().subscribe(payload, sink);
|
|
243
|
+
if (closed) upstream();
|
|
244
|
+
if (openContext.signal.aborted || subscribeContext.signal.aborted) close();
|
|
245
|
+
return Promise.resolve(close);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
};
|
|
128
250
|
}
|
|
129
|
-
|
|
130
|
-
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/data-provider.ts
|
|
131
253
|
function setNestedPath(obj, path, value) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
254
|
+
const parts = path.split(".");
|
|
255
|
+
const leaf = parts.pop();
|
|
256
|
+
if (leaf === void 0) return;
|
|
257
|
+
let cur = obj;
|
|
258
|
+
for (const p of parts) {
|
|
259
|
+
const next = cur[p];
|
|
260
|
+
if (typeof next !== "object" || next === null) cur[p] = {};
|
|
261
|
+
cur = cur[p];
|
|
262
|
+
}
|
|
263
|
+
cur[leaf] = value;
|
|
142
264
|
}
|
|
143
265
|
function getNestedPath(obj, path) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
266
|
+
let cur = obj;
|
|
267
|
+
for (const p of path.split(".")) {
|
|
268
|
+
if (cur == null || typeof cur !== "object") return void 0;
|
|
269
|
+
cur = cur[p];
|
|
270
|
+
}
|
|
271
|
+
return cur;
|
|
150
272
|
}
|
|
151
273
|
function partitionDataBody(values, cfg) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
274
|
+
const keySet = new Set(cfg.keys);
|
|
275
|
+
const dataBody = {};
|
|
276
|
+
const rest = {};
|
|
277
|
+
let hasDataBody = false;
|
|
278
|
+
for (const [key, value] of Object.entries(values)) {
|
|
279
|
+
if (key === cfg.relationship) continue;
|
|
280
|
+
if (keySet.has(key)) {
|
|
281
|
+
if (value !== void 0) {
|
|
282
|
+
dataBody[key] = value;
|
|
283
|
+
hasDataBody = true;
|
|
284
|
+
}
|
|
285
|
+
} else rest[key] = value;
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
dataBody,
|
|
289
|
+
rest,
|
|
290
|
+
hasDataBody
|
|
291
|
+
};
|
|
168
292
|
}
|
|
169
293
|
function partitionJsonbSpill(values, spill, baseColumns) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return out;
|
|
294
|
+
const baseSet = new Set(baseColumns);
|
|
295
|
+
const metaSet = new Set(spill.metaKeys);
|
|
296
|
+
const nestedKeys = spill.nestedKeys ?? {};
|
|
297
|
+
const base = {};
|
|
298
|
+
const headExplicit = {};
|
|
299
|
+
const metaExplicit = {};
|
|
300
|
+
const headSpill = {};
|
|
301
|
+
const metaSpill = {};
|
|
302
|
+
const nestedSpill = [];
|
|
303
|
+
for (const [key, value] of Object.entries(values)) {
|
|
304
|
+
if (value === void 0) continue;
|
|
305
|
+
const nestedPath = nestedKeys[key];
|
|
306
|
+
if (key === spill.column) {
|
|
307
|
+
if (value && typeof value === "object") Object.assign(headExplicit, value);
|
|
308
|
+
} else if (key === spill.metaColumn) {
|
|
309
|
+
if (value && typeof value === "object") Object.assign(metaExplicit, value);
|
|
310
|
+
} else if (metaSet.has(key)) metaSpill[key] = value;
|
|
311
|
+
else if (nestedPath) nestedSpill.push([nestedPath, value]);
|
|
312
|
+
else if (baseSet.has(key)) base[key] = value;
|
|
313
|
+
else headSpill[key] = value;
|
|
314
|
+
}
|
|
315
|
+
const head = {
|
|
316
|
+
...headExplicit,
|
|
317
|
+
...headSpill
|
|
318
|
+
};
|
|
319
|
+
for (const [path, value] of nestedSpill) setNestedPath(head, path, value);
|
|
320
|
+
const meta = {
|
|
321
|
+
...metaExplicit,
|
|
322
|
+
...metaSpill
|
|
323
|
+
};
|
|
324
|
+
const out = { ...base };
|
|
325
|
+
if (Object.keys(head).length > 0) out[spill.column] = head;
|
|
326
|
+
if (Object.keys(meta).length > 0) out[spill.metaColumn] = meta;
|
|
327
|
+
return out;
|
|
205
328
|
}
|
|
206
329
|
function flattenJsonbSpill(item, spill) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
330
|
+
if (!item || typeof item !== "object") return item;
|
|
331
|
+
const rec = item;
|
|
332
|
+
const headObj = rec[spill.column];
|
|
333
|
+
const metaObj = rec[spill.metaColumn];
|
|
334
|
+
const out = {
|
|
335
|
+
...headObj && typeof headObj === "object" ? headObj : {},
|
|
336
|
+
...metaObj && typeof metaObj === "object" ? metaObj : {},
|
|
337
|
+
...rec
|
|
338
|
+
};
|
|
339
|
+
if (spill.nestedKeys && headObj && typeof headObj === "object") for (const [flatKey, path] of Object.entries(spill.nestedKeys)) {
|
|
340
|
+
const v = getNestedPath(headObj, path);
|
|
341
|
+
if (v !== void 0) out[flatKey] = v;
|
|
342
|
+
}
|
|
343
|
+
return out;
|
|
344
|
+
}
|
|
345
|
+
const NOT_AUTHENTICATED_CODE = "not-authenticated";
|
|
346
|
+
const FORBIDDEN_CODES = new Set(["access-denied", "permission-error"]);
|
|
347
|
+
const HASURA_EXTERNAL_FAILURE_CODES = new Set([
|
|
348
|
+
"unexpected",
|
|
349
|
+
"remote-schema-error",
|
|
350
|
+
"parse-failed"
|
|
351
|
+
]);
|
|
352
|
+
const MUTATION_REFUSAL_CODES = new Map([["constraint-violation", "conflict"], ["validation-failed", "validation"]]);
|
|
353
|
+
function graphqlError(errors, operation, external) {
|
|
354
|
+
const message = errors.map((e) => e.message).join("; ");
|
|
355
|
+
const codes = errors.flatMap((e) => typeof e.extensions?.code === "string" ? [e.extensions.code] : []);
|
|
356
|
+
if (codes.includes("invalid-jwt")) return {
|
|
357
|
+
kind: "auth",
|
|
358
|
+
code: "invalid-jwt",
|
|
359
|
+
message,
|
|
360
|
+
details: errors
|
|
361
|
+
};
|
|
362
|
+
const outside = operation === "mutation" && external ? codes.find((code) => HASURA_EXTERNAL_FAILURE_CODES.has(code) || MUTATION_REFUSAL_CODES.has(code) || FORBIDDEN_CODES.has(code)) : void 0;
|
|
363
|
+
if (outside !== void 0) return {
|
|
364
|
+
kind: "sent",
|
|
365
|
+
code: outside,
|
|
366
|
+
message,
|
|
367
|
+
retryable: false,
|
|
368
|
+
details: errors
|
|
369
|
+
};
|
|
370
|
+
const forbidden = codes.find((code) => FORBIDDEN_CODES.has(code));
|
|
371
|
+
if (forbidden !== void 0) return {
|
|
372
|
+
kind: "forbidden",
|
|
373
|
+
code: forbidden,
|
|
374
|
+
message,
|
|
375
|
+
details: errors
|
|
376
|
+
};
|
|
377
|
+
if (operation === "mutation") for (const code of codes) {
|
|
378
|
+
const kind = MUTATION_REFUSAL_CODES.get(code);
|
|
379
|
+
if (kind !== void 0) return {
|
|
380
|
+
kind,
|
|
381
|
+
code,
|
|
382
|
+
message,
|
|
383
|
+
details: errors
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
kind: "protocol",
|
|
388
|
+
code: "graphql-error",
|
|
389
|
+
message,
|
|
390
|
+
details: errors
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function answerLost(code, message, status, cause) {
|
|
394
|
+
return {
|
|
395
|
+
kind: "sent",
|
|
396
|
+
code,
|
|
397
|
+
message,
|
|
398
|
+
status,
|
|
399
|
+
retryable: false,
|
|
400
|
+
...cause === void 0 ? {} : { cause }
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
const LOCALE_REQUIRED_CODE = "locale-required";
|
|
404
|
+
function localeWhere(m, locale) {
|
|
405
|
+
if (m.localeField === void 0) return void 0;
|
|
406
|
+
if (typeof locale !== "string" || locale.length === 0) throw {
|
|
407
|
+
kind: "capability",
|
|
408
|
+
code: LOCALE_REQUIRED_CODE,
|
|
409
|
+
message: `@burojs/hasura: '${m.table}' maps a localeField ('${m.localeField}') but this read carries no locale. Declare \`data: { localeVarying: true }\` on the resource so @burojs/core puts the active locale on the descriptor, or drop the localeField. Serving the request anyway would return every language's rows at once.`
|
|
410
|
+
};
|
|
411
|
+
return { [m.localeField]: { _eq: locale } };
|
|
412
|
+
}
|
|
413
|
+
const OP_MAP = {
|
|
414
|
+
eq: "_eq",
|
|
415
|
+
neq: "_neq",
|
|
416
|
+
ne: "_neq",
|
|
417
|
+
lt: "_lt",
|
|
418
|
+
lte: "_lte",
|
|
419
|
+
gt: "_gt",
|
|
420
|
+
gte: "_gte",
|
|
421
|
+
in: "_in",
|
|
422
|
+
notIn: "_nin",
|
|
423
|
+
nin: "_nin",
|
|
424
|
+
contains: "_ilike",
|
|
425
|
+
containss: "_like",
|
|
426
|
+
startsWith: "_ilike",
|
|
427
|
+
endsWith: "_ilike",
|
|
428
|
+
startswith: "_ilike",
|
|
429
|
+
endswith: "_ilike",
|
|
430
|
+
null: "_is_null"
|
|
243
431
|
};
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
432
|
+
const FILTER_OPERATOR_UNSUPPORTED_CODE = "filter-operator-unsupported";
|
|
433
|
+
const UNEXPRESSIBLE_OPS = {
|
|
434
|
+
has: "a collection-membership test",
|
|
435
|
+
hasAny: "a collection-membership test",
|
|
436
|
+
hasAll: "a collection-membership test"
|
|
249
437
|
};
|
|
250
438
|
function unsupportedOperator(field, op) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
439
|
+
const why = UNEXPRESSIBLE_OPS[op];
|
|
440
|
+
return {
|
|
441
|
+
kind: "capability",
|
|
442
|
+
code: FILTER_OPERATOR_UNSUPPORTED_CODE,
|
|
443
|
+
message: why ? `@burojs/hasura cannot express the '${op}' filter operator on '${field}': ${why}, whose Hasura encoding depends on whether the column is jsonb or a Postgres array — something no ResourceMapping field declares. Declare it and translate explicitly, or filter on a scalar column.` : `@burojs/hasura does not support the filter operator '${op}' (on '${field}').`
|
|
444
|
+
};
|
|
257
445
|
}
|
|
258
446
|
function scopeToWhere(scope, scopeFields) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
447
|
+
const clauses = [];
|
|
448
|
+
for (const key of scopeFields) {
|
|
449
|
+
const v = scope[key];
|
|
450
|
+
if (v == null || v === "") continue;
|
|
451
|
+
clauses.push({ [key]: { _eq: v } });
|
|
452
|
+
}
|
|
453
|
+
if (clauses.length === 0) return void 0;
|
|
454
|
+
if (clauses.length === 1) return clauses[0];
|
|
455
|
+
return { _and: clauses };
|
|
268
456
|
}
|
|
269
457
|
function mergeWhere(a, b) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
458
|
+
if (!a) return b;
|
|
459
|
+
if (!b) return a;
|
|
460
|
+
return { _and: [a, b] };
|
|
461
|
+
}
|
|
462
|
+
function atFieldPath(field, predicate) {
|
|
463
|
+
const parts = field.split(".");
|
|
464
|
+
let out = predicate;
|
|
465
|
+
for (let i = parts.length - 1; i >= 0; i -= 1) out = { [parts[i]]: out };
|
|
466
|
+
return out;
|
|
273
467
|
}
|
|
274
468
|
function filtersToWhere(filter) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return { [field]: { [operator]: value } };
|
|
469
|
+
if (!filter || typeof filter !== "object") return void 0;
|
|
470
|
+
if ("and" in filter) {
|
|
471
|
+
if (filter.and.length === 0) return { _and: [] };
|
|
472
|
+
const clauses = filter.and.map(filtersToWhere).filter((item) => item !== void 0);
|
|
473
|
+
if (clauses.length === 0) return void 0;
|
|
474
|
+
return clauses.length === 1 ? clauses[0] : { _and: clauses };
|
|
475
|
+
}
|
|
476
|
+
if ("or" in filter) {
|
|
477
|
+
if (filter.or.length === 0) return { _or: [] };
|
|
478
|
+
const clauses = filter.or.map(filtersToWhere).filter((item) => item !== void 0);
|
|
479
|
+
if (clauses.length === 0) return void 0;
|
|
480
|
+
return clauses.length === 1 ? clauses[0] : { _or: clauses };
|
|
481
|
+
}
|
|
482
|
+
if ("not" in filter) {
|
|
483
|
+
const clause = filtersToWhere(filter.not);
|
|
484
|
+
return clause === void 0 ? void 0 : { _not: clause };
|
|
485
|
+
}
|
|
486
|
+
if (!("field" in filter) || typeof filter.field !== "string" || !("op" in filter)) return void 0;
|
|
487
|
+
const { field, op } = filter;
|
|
488
|
+
if (op === "isNull") return atFieldPath(field, { _is_null: true });
|
|
489
|
+
if (op === "isNotNull") return atFieldPath(field, { _is_null: false });
|
|
490
|
+
if (!("value" in filter) || filter.value === void 0) return void 0;
|
|
491
|
+
const operator = OP_MAP[op];
|
|
492
|
+
if (operator === void 0) throw unsupportedOperator(field, op);
|
|
493
|
+
let value = filter.value;
|
|
494
|
+
if (operator === "_ilike" || operator === "_like") {
|
|
495
|
+
const text = String(value);
|
|
496
|
+
value = op === "startsWith" ? `${text}%` : op === "endsWith" ? `%${text}` : `%${text}%`;
|
|
497
|
+
}
|
|
498
|
+
return atFieldPath(field, { [operator]: value });
|
|
306
499
|
}
|
|
307
500
|
function coerceInt64Filter(filter, fields) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
};
|
|
321
|
-
if ("not" in filter)
|
|
322
|
-
return { not: coerceInt64Filter(filter.not, fields) };
|
|
323
|
-
if ("field" in filter && fields.includes(filter.field) && "value" in filter && filter.value != null) {
|
|
324
|
-
const value = Array.isArray(filter.value) ? filter.value.map(String) : String(filter.value);
|
|
325
|
-
return { ...filter, value };
|
|
326
|
-
}
|
|
327
|
-
return filter;
|
|
501
|
+
if (!filter || !fields?.length) return filter;
|
|
502
|
+
if ("and" in filter) return { and: filter.and.map((entry) => coerceInt64Filter(entry, fields)) };
|
|
503
|
+
if ("or" in filter) return { or: filter.or.map((entry) => coerceInt64Filter(entry, fields)) };
|
|
504
|
+
if ("not" in filter) return { not: coerceInt64Filter(filter.not, fields) };
|
|
505
|
+
if ("field" in filter && fields.includes(filter.field) && "value" in filter && filter.value != null) {
|
|
506
|
+
const value = Array.isArray(filter.value) ? filter.value.map(String) : String(filter.value);
|
|
507
|
+
return {
|
|
508
|
+
...filter,
|
|
509
|
+
value
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
return filter;
|
|
328
513
|
}
|
|
329
514
|
function hasuraError(error, signal, fallback) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
};
|
|
515
|
+
if ((0, _burojs_core.isDataError)(error)) return error;
|
|
516
|
+
if (signal.aborted) return {
|
|
517
|
+
kind: "cancelled",
|
|
518
|
+
code: "request-aborted",
|
|
519
|
+
message: "Hasura request was aborted.",
|
|
520
|
+
cause: error
|
|
521
|
+
};
|
|
522
|
+
return {
|
|
523
|
+
kind: "transport",
|
|
524
|
+
code: "hasura-request-failed",
|
|
525
|
+
message: error instanceof Error && error.message ? error.message : fallback,
|
|
526
|
+
cause: error
|
|
527
|
+
};
|
|
344
528
|
}
|
|
345
529
|
function aggregateRoot(m) {
|
|
346
|
-
|
|
347
|
-
|
|
530
|
+
if (m.aggregate === false) return null;
|
|
531
|
+
return typeof m.aggregate === "string" ? m.aggregate : `${m.table}_aggregate`;
|
|
348
532
|
}
|
|
349
533
|
function byPkRoot(m) {
|
|
350
|
-
|
|
351
|
-
|
|
534
|
+
if (m.byPkRoot === false) return null;
|
|
535
|
+
return m.byPkRoot ?? `${m.table}_by_pk`;
|
|
352
536
|
}
|
|
353
537
|
function snapshotOperationRegistry(source) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
return Object.freeze(resources);
|
|
538
|
+
if (source === void 0) return Object.freeze({});
|
|
539
|
+
const resources = {};
|
|
540
|
+
for (const [resource, operations] of Object.entries(source)) {
|
|
541
|
+
const entries = {};
|
|
542
|
+
for (const [name, operation] of Object.entries(operations)) entries[name] = Object.freeze({ ...operation });
|
|
543
|
+
resources[resource] = Object.freeze(entries);
|
|
544
|
+
}
|
|
545
|
+
return Object.freeze(resources);
|
|
364
546
|
}
|
|
365
547
|
function hasRegisteredOperationKind(operations, kind) {
|
|
366
|
-
|
|
367
|
-
(byName) => Object.values(byName).some((operation) => operation.kind === kind)
|
|
368
|
-
);
|
|
548
|
+
return Object.values(operations).some((byName) => Object.values(byName).some((operation) => operation.kind === kind));
|
|
369
549
|
}
|
|
370
550
|
function normalizeRole(role) {
|
|
371
|
-
|
|
551
|
+
return role === void 0 || role === null || role === "" ? null : role;
|
|
372
552
|
}
|
|
373
553
|
function createHasuraDataProvider(config) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
554
|
+
const endpoint = config.endpoint ?? "/gql/v1/graphql";
|
|
555
|
+
const defaultRole = normalizeRole(config.defaultRole);
|
|
556
|
+
const operations = snapshotOperationRegistry(config.operations);
|
|
557
|
+
const hasCustomQueries = hasRegisteredOperationKind(operations, "query");
|
|
558
|
+
const hasCustomMutations = hasRegisteredOperationKind(operations, "mutation");
|
|
559
|
+
async function gql(query, variables, role, signal, operation, external = false) {
|
|
560
|
+
try {
|
|
561
|
+
const requestBody = JSON.stringify({
|
|
562
|
+
query,
|
|
563
|
+
variables
|
|
564
|
+
});
|
|
565
|
+
const request = async (token) => {
|
|
566
|
+
const headers = {
|
|
567
|
+
"Content-Type": "application/json",
|
|
568
|
+
Authorization: `Bearer ${token}`
|
|
569
|
+
};
|
|
570
|
+
const effectiveRole = role === void 0 ? defaultRole : normalizeRole(role);
|
|
571
|
+
if (effectiveRole !== null) headers["X-Hasura-Role"] = effectiveRole;
|
|
572
|
+
const fetchImpl = config.fetchImpl ?? fetch;
|
|
573
|
+
try {
|
|
574
|
+
return await fetchImpl(endpoint, {
|
|
575
|
+
method: "POST",
|
|
576
|
+
headers,
|
|
577
|
+
body: requestBody,
|
|
578
|
+
signal
|
|
579
|
+
});
|
|
580
|
+
} catch (cause) {
|
|
581
|
+
if (operation === "mutation" && !signal.aborted) throw {
|
|
582
|
+
kind: "sent",
|
|
583
|
+
code: "hasura-request-sent",
|
|
584
|
+
message: "Hasura did not answer a mutation; it may have been applied.",
|
|
585
|
+
retryable: false,
|
|
586
|
+
cause
|
|
587
|
+
};
|
|
588
|
+
throw cause;
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
let token = await config.getToken();
|
|
592
|
+
if (!token) throw {
|
|
593
|
+
kind: "auth",
|
|
594
|
+
code: NOT_AUTHENTICATED_CODE,
|
|
595
|
+
message: "Not authenticated."
|
|
596
|
+
};
|
|
597
|
+
let res = await request(token);
|
|
598
|
+
if (res.status === 401 && config.recoverAuth && await config.recoverAuth()) {
|
|
599
|
+
token = await config.getToken();
|
|
600
|
+
if (!token) throw {
|
|
601
|
+
kind: "auth",
|
|
602
|
+
code: NOT_AUTHENTICATED_CODE,
|
|
603
|
+
message: "Not authenticated."
|
|
604
|
+
};
|
|
605
|
+
res = await request(token);
|
|
606
|
+
}
|
|
607
|
+
if (!res.ok) {
|
|
608
|
+
if (operation === "mutation" && res.status >= 500) throw answerLost("hasura-answer-lost", `GraphQL HTTP ${res.status} after a mutation was sent; it may have been applied.`, res.status);
|
|
609
|
+
throw {
|
|
610
|
+
kind: "transport",
|
|
611
|
+
code: "graphql-http-error",
|
|
612
|
+
message: `GraphQL HTTP ${res.status}`,
|
|
613
|
+
status: res.status
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
let body;
|
|
617
|
+
try {
|
|
618
|
+
body = await res.json();
|
|
619
|
+
if (body === null || typeof body !== "object") throw new TypeError("The GraphQL answer is not an object.");
|
|
620
|
+
} catch (cause) {
|
|
621
|
+
if (operation === "mutation" && !signal.aborted) throw answerLost("hasura-answer-unreadable", "Hasura answered a mutation with a body that could not be read; it may have been applied.", res.status, cause);
|
|
622
|
+
throw cause;
|
|
623
|
+
}
|
|
624
|
+
if (body.errors?.length) throw graphqlError(body.errors, operation, external);
|
|
625
|
+
if (body.data === void 0) {
|
|
626
|
+
if (operation === "mutation") throw answerLost("hasura-answer-unreadable", "Hasura answered a mutation without data or errors; it may have been applied.", res.status);
|
|
627
|
+
throw {
|
|
628
|
+
kind: "protocol",
|
|
629
|
+
code: "empty-graphql-response",
|
|
630
|
+
message: "Empty GraphQL response."
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
return body.data;
|
|
634
|
+
} catch (error) {
|
|
635
|
+
throw hasuraError(error, signal, "Hasura request failed.");
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function mapping(resource) {
|
|
639
|
+
const m = config.resources[resource];
|
|
640
|
+
if (!m) throw {
|
|
641
|
+
kind: "configuration",
|
|
642
|
+
code: "unknown-resource",
|
|
643
|
+
message: `Unknown resource: ${resource}`
|
|
644
|
+
};
|
|
645
|
+
return m;
|
|
646
|
+
}
|
|
647
|
+
function registeredOperation(resource, operation, kind) {
|
|
648
|
+
const registered = operations[resource]?.[operation];
|
|
649
|
+
if (registered === void 0 || registered.kind !== kind) throw {
|
|
650
|
+
kind: "capability",
|
|
651
|
+
code: "hasura-custom-operation-unsupported",
|
|
652
|
+
message: `Hasura ${kind} operation '${operation}' is not declared for '${resource}'.`
|
|
653
|
+
};
|
|
654
|
+
return registered;
|
|
655
|
+
}
|
|
656
|
+
async function executeCustomOperation(resource, operationName, input, kind, signal) {
|
|
657
|
+
const operation = registeredOperation(resource, operationName, kind);
|
|
658
|
+
const result = await gql(operation.document, operation.variables(input), operation.role ?? config.resources[resource]?.role, signal, kind, kind === "mutation");
|
|
659
|
+
return operation.mapResult === void 0 ? result : operation.mapResult(result);
|
|
660
|
+
}
|
|
661
|
+
function composeListWhere(m, descriptor, context) {
|
|
662
|
+
const userWhere = filtersToWhere(coerceInt64Filter(descriptor.filter, m.int64Fields));
|
|
663
|
+
const ignoreScope = descriptor.providerOptions?.ignoreScope === true;
|
|
664
|
+
const sessionScope = context.session.scopeKey !== null && typeof context.session.scopeKey === "object" && !Array.isArray(context.session.scopeKey) ? context.session.scopeKey : {};
|
|
665
|
+
return mergeWhere(mergeWhere(mergeWhere(userWhere, scopeToWhere(ignoreScope ? {} : {
|
|
666
|
+
...sessionScope,
|
|
667
|
+
...config.getScope?.(context) ?? {}
|
|
668
|
+
}, m.scopeFields ?? [])), m.fixedFilter), localeWhere(m, descriptor.locale));
|
|
669
|
+
}
|
|
670
|
+
async function runAggregateCount(m, root, where, signal) {
|
|
671
|
+
const aggWhereArg = where ? `($where: ${m.table}_bool_exp)` : "";
|
|
672
|
+
const aggWhereParam = where ? "(where: $where)" : "";
|
|
673
|
+
const metaQuery = `query Count_${m.table} ${aggWhereArg} {
|
|
483
674
|
meta: ${root} ${aggWhereParam} { aggregate { count } }
|
|
484
675
|
}`;
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
signal
|
|
492
|
-
);
|
|
493
|
-
return metaData.meta.aggregate.count;
|
|
494
|
-
}
|
|
495
|
-
async function upsertDataBody(db, parentId, body, role, signal) {
|
|
496
|
-
const mutation = `mutation UpsertDataBody($object: ${db.table}_insert_input!) {
|
|
676
|
+
const aggVars = {};
|
|
677
|
+
if (where) aggVars.where = where;
|
|
678
|
+
return (await gql(metaQuery, where ? aggVars : void 0, m.role, signal, "query")).meta.aggregate.count;
|
|
679
|
+
}
|
|
680
|
+
async function upsertDataBody(db, parentId, body, role, signal) {
|
|
681
|
+
await gql(`mutation UpsertDataBody($object: ${db.table}_insert_input!) {
|
|
497
682
|
result: insert_${db.table}_one(
|
|
498
683
|
object: $object,
|
|
499
684
|
on_conflict: { constraint: ${db.constraint}, update_columns: [${db.column}] }
|
|
500
685
|
) { ${db.fk} }
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
686
|
+
}`, { object: {
|
|
687
|
+
[db.fk]: parentId,
|
|
688
|
+
[db.column]: body
|
|
689
|
+
} }, role, signal, "mutation");
|
|
690
|
+
}
|
|
691
|
+
function mutationRecord(m, idField, item, resource, expectedId) {
|
|
692
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) throw {
|
|
693
|
+
kind: "not-found",
|
|
694
|
+
code: "record-not-found",
|
|
695
|
+
message: `Not found: ${resource}`
|
|
696
|
+
};
|
|
697
|
+
const id = item[m.pkField ?? idField];
|
|
698
|
+
if (typeof id !== "string" && (typeof id !== "number" || !Number.isFinite(id))) throw {
|
|
699
|
+
kind: "protocol",
|
|
700
|
+
code: "mutation-record-id-missing",
|
|
701
|
+
message: `Mutation for '${resource}' returned no valid '${m.pkField ?? idField}'.`
|
|
702
|
+
};
|
|
703
|
+
if (expectedId !== void 0 && id !== expectedId) throw {
|
|
704
|
+
kind: "protocol",
|
|
705
|
+
code: "unexpected-record-id",
|
|
706
|
+
message: `Mutation for '${resource}' returned '${String(id)}' instead of '${String(expectedId)}'.`
|
|
707
|
+
};
|
|
708
|
+
return {
|
|
709
|
+
kind: "record",
|
|
710
|
+
id,
|
|
711
|
+
record: item,
|
|
712
|
+
completeness: "partial"
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
async function readMutationRecord(m, id, signal) {
|
|
716
|
+
const fields = m.selectFields.join(" ");
|
|
717
|
+
const pkType = m.pkType ?? "bigint";
|
|
718
|
+
const pkField = m.pkField ?? "id";
|
|
719
|
+
const root = byPkRoot(m);
|
|
720
|
+
if (m.fixedFilter || root === null) {
|
|
721
|
+
const where = mergeWhere({ [pkField]: { _eq: id } }, m.fixedFilter);
|
|
722
|
+
return (await gql(`query One_${m.table}($where: ${m.table}_bool_exp) {
|
|
538
723
|
items: ${m.table}(where: $where, limit: 1) { ${fields} }
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
const query = `query One_${m.table}($id: ${pkType}!) {
|
|
724
|
+
}`, { where }, m.role, signal, "query")).items?.[0] ?? null;
|
|
725
|
+
}
|
|
726
|
+
return (await gql(`query One_${m.table}($id: ${pkType}!) {
|
|
544
727
|
item: ${root}(${pkField}: $id) { ${fields} }
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
728
|
+
}`, { id }, m.role, signal, "query")).item;
|
|
729
|
+
}
|
|
730
|
+
async function listByCursor(m, descriptor, pagination, context) {
|
|
731
|
+
if (pagination.before !== void 0) throw {
|
|
732
|
+
kind: "capability",
|
|
733
|
+
code: HASURA_CURSOR_CODES.backward,
|
|
734
|
+
message: `@burojs/hasura implements forward cursor pagination only; '${m.table}' was asked to page backwards with \`before\`. Nothing in this framework drives backward keyset paging today, and an unexercised implementation of it would be a worse answer than a refusal.`
|
|
735
|
+
};
|
|
736
|
+
assertCursorLimit(pagination.limit, m.table);
|
|
737
|
+
const sorters = descriptor.sort?.filter((s) => s.field);
|
|
738
|
+
const ordering = effectiveCursorOrdering(sorters, m);
|
|
739
|
+
assertCursorOrderable(ordering, sorters, m);
|
|
740
|
+
const baseWhere = composeListWhere(m, descriptor, context);
|
|
741
|
+
const where = pagination.after === void 0 ? baseWhere : mergeWhere(baseWhere, keysetWhere(ordering, decodeCursor(pagination.after, ordering, m.table), m.int64Fields));
|
|
742
|
+
const fields = m.selectFields.join(" ");
|
|
743
|
+
const orderByExpr = `[${ordering.map((c) => `{${c.field}: ${c.direction}}`).join(", ")}]`;
|
|
744
|
+
const whereArg = where ? `, $where: ${m.table}_bool_exp` : "";
|
|
745
|
+
const whereParam = where ? ", where: $where" : "";
|
|
746
|
+
const itemsQuery = `query ListCursor_${m.table}($limit: Int${whereArg}) {
|
|
747
|
+
items: ${m.table}(limit: $limit, order_by: ${orderByExpr}${whereParam}) { ${fields} }
|
|
748
|
+
}`;
|
|
749
|
+
const itemsVars = { limit: pagination.limit + 1 };
|
|
750
|
+
if (where) itemsVars.where = where;
|
|
751
|
+
const fetched = (await gql(itemsQuery, itemsVars, m.role, context.signal, "query")).items ?? [];
|
|
752
|
+
const hasNextPage = fetched.length > pagination.limit;
|
|
753
|
+
const rows = hasNextPage ? fetched.slice(0, pagination.limit) : fetched;
|
|
754
|
+
let total;
|
|
755
|
+
const listAggRoot = aggregateRoot(m);
|
|
756
|
+
if (!m.noAggregate && listAggRoot) try {
|
|
757
|
+
total = await runAggregateCount(m, listAggRoot, baseWhere, context.signal);
|
|
758
|
+
} catch (err) {
|
|
759
|
+
console.warn(`[hasura] count failed for ${m.table}; rendering unknown-total`, err);
|
|
760
|
+
}
|
|
761
|
+
const boundary = rows[rows.length - 1];
|
|
762
|
+
const nextCursor = hasNextPage && boundary !== null && typeof boundary === "object" ? encodeCursor(ordering, boundary, m.table) : void 0;
|
|
763
|
+
const spill = m.jsonbSpill;
|
|
764
|
+
return {
|
|
765
|
+
data: spill ? rows.map((it) => flattenJsonbSpill(it, spill)) : rows,
|
|
766
|
+
total,
|
|
767
|
+
pageInfo: {
|
|
768
|
+
nextCursor,
|
|
769
|
+
hasNextPage
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
return (0, _burojs_core.defineDataProvider)({
|
|
774
|
+
queries: {
|
|
775
|
+
async list(descriptor, context) {
|
|
776
|
+
const m = mapping(descriptor.resource);
|
|
777
|
+
if (descriptor.pagination?.kind === "cursor") return await listByCursor(m, descriptor, descriptor.pagination, context);
|
|
778
|
+
const limit = descriptor.pagination?.kind === "offset" ? descriptor.pagination.pageSize : 25;
|
|
779
|
+
const offset = ((descriptor.pagination?.kind === "offset" ? descriptor.pagination.page : 1) - 1) * limit;
|
|
780
|
+
const where = composeListWhere(m, descriptor, context);
|
|
781
|
+
const sorters = descriptor.sort?.filter((s) => s.field);
|
|
782
|
+
let orderByExpr;
|
|
783
|
+
if (sorters && sorters.length > 0) orderByExpr = `[${sorters.map((s) => `{${s.field}: ${s.direction}}`).join(", ")}]`;
|
|
784
|
+
else orderByExpr = `{${m.orderByField ?? "id"}: desc}`;
|
|
785
|
+
const fields = m.selectFields.join(" ");
|
|
786
|
+
const whereArg = where ? `, $where: ${m.table}_bool_exp` : "";
|
|
787
|
+
const whereParam = where ? ", where: $where" : "";
|
|
788
|
+
const itemsQuery = `query List_${m.table}($limit: Int, $offset: Int${whereArg}) {
|
|
576
789
|
items: ${m.table}(limit: $limit, offset: $offset, order_by: ${orderByExpr}${whereParam}) { ${fields} }
|
|
577
790
|
}`;
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
* The `where` comes from the same {@link composeListWhere} `getList` uses —
|
|
612
|
-
* same filters, same scope, same `fixedFilter` — so the number always
|
|
613
|
-
* describes the rows the list is paging through.
|
|
614
|
-
*/
|
|
615
|
-
async count(descriptor, context) {
|
|
616
|
-
const m = config.resources[descriptor.resource];
|
|
617
|
-
if (!m) return { total: null };
|
|
618
|
-
const root = aggregateRoot(m);
|
|
619
|
-
if (!root) return { total: null };
|
|
620
|
-
return {
|
|
621
|
-
total: await runAggregateCount(
|
|
622
|
-
m,
|
|
623
|
-
root,
|
|
624
|
-
composeListWhere(m, descriptor, context),
|
|
625
|
-
context.signal
|
|
626
|
-
)
|
|
627
|
-
};
|
|
628
|
-
},
|
|
629
|
-
async many(descriptor, context) {
|
|
630
|
-
const m = mapping(descriptor.resource);
|
|
631
|
-
if (descriptor.ids.length === 0) return { data: [] };
|
|
632
|
-
const pkField = m.pkField ?? "id";
|
|
633
|
-
const idsWhere = { [pkField]: { _in: descriptor.ids } };
|
|
634
|
-
const where = mergeWhere(
|
|
635
|
-
idsWhere,
|
|
636
|
-
composeListWhere(m, descriptor, context)
|
|
637
|
-
);
|
|
638
|
-
const fields = m.selectFields.join(" ");
|
|
639
|
-
const query = `query Many_${m.table}($where: ${m.table}_bool_exp) {
|
|
791
|
+
const itemsVars = {
|
|
792
|
+
limit,
|
|
793
|
+
offset
|
|
794
|
+
};
|
|
795
|
+
if (where) itemsVars.where = where;
|
|
796
|
+
const itemsData = await gql(itemsQuery, itemsVars, m.role, context.signal, "query");
|
|
797
|
+
let total;
|
|
798
|
+
const listAggRoot = aggregateRoot(m);
|
|
799
|
+
if (!m.noAggregate && listAggRoot) try {
|
|
800
|
+
total = await runAggregateCount(m, listAggRoot, where, context.signal);
|
|
801
|
+
} catch (err) {
|
|
802
|
+
console.warn(`[hasura] count failed for ${m.table}; rendering unknown-total`, err);
|
|
803
|
+
}
|
|
804
|
+
const spill = m.jsonbSpill;
|
|
805
|
+
return {
|
|
806
|
+
data: spill ? itemsData.items.map((it) => flattenJsonbSpill(it, spill)) : itemsData.items,
|
|
807
|
+
total
|
|
808
|
+
};
|
|
809
|
+
},
|
|
810
|
+
async count(descriptor, context) {
|
|
811
|
+
const m = config.resources[descriptor.resource];
|
|
812
|
+
if (!m) return { total: null };
|
|
813
|
+
const root = aggregateRoot(m);
|
|
814
|
+
if (!root) return { total: null };
|
|
815
|
+
return { total: await runAggregateCount(m, root, composeListWhere(m, descriptor, context), context.signal) };
|
|
816
|
+
},
|
|
817
|
+
async many(descriptor, context) {
|
|
818
|
+
const m = mapping(descriptor.resource);
|
|
819
|
+
localeWhere(m, descriptor.locale);
|
|
820
|
+
if (descriptor.ids.length === 0) return { data: [] };
|
|
821
|
+
const where = mergeWhere({ [m.pkField ?? "id"]: { _in: descriptor.ids } }, composeListWhere(m, descriptor, context));
|
|
822
|
+
const fields = m.selectFields.join(" ");
|
|
823
|
+
const data = await gql(`query Many_${m.table}($where: ${m.table}_bool_exp) {
|
|
640
824
|
items: ${m.table}(where: $where) { ${fields} }
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const query = `query One_${m.table}($where: ${m.table}_bool_exp) {
|
|
825
|
+
}`, { where }, m.role, context.signal, "query");
|
|
826
|
+
const spill = m.jsonbSpill;
|
|
827
|
+
return { data: spill ? data.items.map((item) => flattenJsonbSpill(item, spill)) : data.items };
|
|
828
|
+
},
|
|
829
|
+
async one(descriptor, context) {
|
|
830
|
+
const m = mapping(descriptor.resource);
|
|
831
|
+
const dbSelect = m.dataBody ? ` ${m.dataBody.relationship} { ${m.dataBody.column} }` : "";
|
|
832
|
+
const fields = m.selectFields.join(" ") + dbSelect;
|
|
833
|
+
const pkType = m.pkType ?? "bigint";
|
|
834
|
+
const pkField = m.pkField ?? "id";
|
|
835
|
+
let item;
|
|
836
|
+
const root = byPkRoot(m);
|
|
837
|
+
const localeSlice = localeWhere(m, descriptor.locale);
|
|
838
|
+
if (m.fixedFilter || localeSlice || root === null) {
|
|
839
|
+
const where = mergeWhere(mergeWhere({ [pkField]: { _eq: descriptor.id } }, m.fixedFilter), localeSlice);
|
|
840
|
+
item = (await gql(`query One_${m.table}($where: ${m.table}_bool_exp) {
|
|
658
841
|
items: ${m.table}(where: $where, limit: 1) { ${fields} }
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
item = data.items?.[0] ?? null;
|
|
662
|
-
} else {
|
|
663
|
-
const query = `query One_${m.table}($id: ${pkType}!) {
|
|
842
|
+
}`, { where }, m.role, context.signal, "query")).items?.[0] ?? null;
|
|
843
|
+
} else item = (await gql(`query One_${m.table}($id: ${pkType}!) {
|
|
664
844
|
item: ${root}(${pkField}: $id) { ${fields} }
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
mutations: {
|
|
703
|
-
async createOne(descriptor, context) {
|
|
704
|
-
const m = mapping(descriptor.resource);
|
|
705
|
-
const fields = m.selectFields.join(" ");
|
|
706
|
-
const rawInput = descriptor.input;
|
|
707
|
-
let mainInput = rawInput;
|
|
708
|
-
let dataBody;
|
|
709
|
-
if (m.dataBody) {
|
|
710
|
-
const split = partitionDataBody(rawInput, m.dataBody);
|
|
711
|
-
mainInput = split.rest;
|
|
712
|
-
dataBody = split.hasDataBody ? split.dataBody : void 0;
|
|
713
|
-
}
|
|
714
|
-
const object = m.jsonbSpill ? partitionJsonbSpill(mainInput, m.jsonbSpill, m.selectFields) : mainInput;
|
|
715
|
-
const query = `mutation Create_${m.table}($object: ${m.table}_insert_input!) {
|
|
845
|
+
}`, { id: descriptor.id }, m.role, context.signal, "query")).item;
|
|
846
|
+
if (item == null) throw {
|
|
847
|
+
kind: "not-found",
|
|
848
|
+
code: "record-not-found",
|
|
849
|
+
message: `Not found: ${descriptor.resource}/${descriptor.id}`
|
|
850
|
+
};
|
|
851
|
+
let result = m.jsonbSpill ? flattenJsonbSpill(item, m.jsonbSpill) : item;
|
|
852
|
+
if (m.dataBody && result && typeof result === "object") {
|
|
853
|
+
const rec = { ...result };
|
|
854
|
+
const sibling = rec[m.dataBody.relationship];
|
|
855
|
+
const body = sibling && typeof sibling === "object" ? sibling[m.dataBody.column] : null;
|
|
856
|
+
delete rec[m.dataBody.relationship];
|
|
857
|
+
result = body && typeof body === "object" ? {
|
|
858
|
+
...rec,
|
|
859
|
+
...body
|
|
860
|
+
} : rec;
|
|
861
|
+
}
|
|
862
|
+
return { data: result };
|
|
863
|
+
},
|
|
864
|
+
...hasCustomQueries ? { async custom(descriptor, context) {
|
|
865
|
+
return executeCustomOperation(descriptor.resource, descriptor.operation, descriptor.input, "query", context.signal);
|
|
866
|
+
} } : {}
|
|
867
|
+
},
|
|
868
|
+
mutations: {
|
|
869
|
+
async createOne(descriptor, context) {
|
|
870
|
+
const m = mapping(descriptor.resource);
|
|
871
|
+
const fields = m.selectFields.join(" ");
|
|
872
|
+
const rawInput = descriptor.input;
|
|
873
|
+
let mainInput = rawInput;
|
|
874
|
+
let dataBody;
|
|
875
|
+
if (m.dataBody) {
|
|
876
|
+
const split = partitionDataBody(rawInput, m.dataBody);
|
|
877
|
+
mainInput = split.rest;
|
|
878
|
+
dataBody = split.hasDataBody ? split.dataBody : void 0;
|
|
879
|
+
}
|
|
880
|
+
const object = m.jsonbSpill ? partitionJsonbSpill(mainInput, m.jsonbSpill, m.selectFields) : mainInput;
|
|
881
|
+
const data = await gql(`mutation Create_${m.table}($object: ${m.table}_insert_input!) {
|
|
716
882
|
item: insert_${m.table}_one(object: $object) { ${fields} }
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
let where = mergeWhere({ [pkField]: { _eq: descriptor.id } }, m.fixedFilter);
|
|
743
|
-
if (ifUpdatedAt != null) where = mergeWhere(where, { updated_at: { _eq: ifUpdatedAt } });
|
|
744
|
-
const query = `mutation Update_${m.table}($where: ${m.table}_bool_exp!, $set: ${m.table}_set_input!) {
|
|
883
|
+
}`, { object }, m.role, context.signal, "mutation");
|
|
884
|
+
const result = mutationRecord(m, descriptor.idField, data.item, descriptor.resource);
|
|
885
|
+
if (m.dataBody && dataBody) await upsertDataBody(m.dataBody, result.id, dataBody, m.role, context.signal);
|
|
886
|
+
return result;
|
|
887
|
+
},
|
|
888
|
+
async updateOne(descriptor, context) {
|
|
889
|
+
const m = mapping(descriptor.resource);
|
|
890
|
+
const fields = m.selectFields.join(" ");
|
|
891
|
+
const rawInput = descriptor.input;
|
|
892
|
+
let mainInput = rawInput;
|
|
893
|
+
let dataBody;
|
|
894
|
+
if (m.dataBody) {
|
|
895
|
+
const split = partitionDataBody(rawInput, m.dataBody);
|
|
896
|
+
mainInput = split.rest;
|
|
897
|
+
dataBody = split.hasDataBody ? split.dataBody : void 0;
|
|
898
|
+
}
|
|
899
|
+
const set = m.jsonbSpill ? partitionJsonbSpill(mainInput, m.jsonbSpill, m.selectFields) : mainInput;
|
|
900
|
+
const pkField = m.pkField ?? descriptor.idField;
|
|
901
|
+
const ifUpdatedAt = descriptor.providerOptions?.ifUpdatedAt;
|
|
902
|
+
let item;
|
|
903
|
+
if (Object.keys(set).length === 0) item = await readMutationRecord(m, descriptor.id, context.signal);
|
|
904
|
+
else if (m.fixedFilter || ifUpdatedAt != null) {
|
|
905
|
+
let where = mergeWhere({ [pkField]: { _eq: descriptor.id } }, m.fixedFilter);
|
|
906
|
+
if (ifUpdatedAt != null) where = mergeWhere(where, { updated_at: { _eq: ifUpdatedAt } });
|
|
907
|
+
const data = await gql(`mutation Update_${m.table}($where: ${m.table}_bool_exp!, $set: ${m.table}_set_input!) {
|
|
745
908
|
result: update_${m.table}(where: $where, _set: $set) { affected_rows returning { ${fields} } }
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
}
|
|
760
|
-
item = data.result.returning?.[0] ?? null;
|
|
761
|
-
} else {
|
|
762
|
-
const pkType = m.pkType ?? "bigint";
|
|
763
|
-
const query = `mutation Update_${m.table}($id: ${pkType}!, $set: ${m.table}_set_input!) {
|
|
909
|
+
}`, {
|
|
910
|
+
where,
|
|
911
|
+
set
|
|
912
|
+
}, m.role, context.signal, "mutation");
|
|
913
|
+
if (ifUpdatedAt != null && ((data.result.affected_rows ?? 0) === 0 || (data.result.returning?.length ?? 0) === 0)) throw {
|
|
914
|
+
kind: "conflict",
|
|
915
|
+
code: "stale-record",
|
|
916
|
+
message: `Update for '${descriptor.resource}/${descriptor.id}' matched no row: the record was changed since it was loaded.`
|
|
917
|
+
};
|
|
918
|
+
item = data.result.returning?.[0] ?? null;
|
|
919
|
+
} else {
|
|
920
|
+
const pkType = m.pkType ?? "bigint";
|
|
921
|
+
item = (await gql(`mutation Update_${m.table}($id: ${pkType}!, $set: ${m.table}_set_input!) {
|
|
764
922
|
item: update_${m.table}_by_pk(pk_columns: {${pkField}: $id}, _set: $set) { ${fields} }
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
await upsertDataBody(m.dataBody, result.id, dataBody, m.role, context.signal);
|
|
783
|
-
return result;
|
|
784
|
-
},
|
|
785
|
-
async deleteOne(descriptor, context) {
|
|
786
|
-
const m = mapping(descriptor.resource);
|
|
787
|
-
const fields = m.selectFields.join(" ");
|
|
788
|
-
const pkField = m.pkField ?? descriptor.idField;
|
|
789
|
-
let item;
|
|
790
|
-
if (m.fixedFilter) {
|
|
791
|
-
const where = mergeWhere({ [pkField]: { _eq: descriptor.id } }, m.fixedFilter);
|
|
792
|
-
const query = `mutation Delete_${m.table}($where: ${m.table}_bool_exp!) {
|
|
923
|
+
}`, {
|
|
924
|
+
id: descriptor.id,
|
|
925
|
+
set
|
|
926
|
+
}, m.role, context.signal, "mutation")).item;
|
|
927
|
+
}
|
|
928
|
+
const result = mutationRecord(m, descriptor.idField, item, descriptor.resource, descriptor.id);
|
|
929
|
+
if (m.dataBody && dataBody) await upsertDataBody(m.dataBody, result.id, dataBody, m.role, context.signal);
|
|
930
|
+
return result;
|
|
931
|
+
},
|
|
932
|
+
async deleteOne(descriptor, context) {
|
|
933
|
+
const m = mapping(descriptor.resource);
|
|
934
|
+
const fields = m.selectFields.join(" ");
|
|
935
|
+
const pkField = m.pkField ?? descriptor.idField;
|
|
936
|
+
let item;
|
|
937
|
+
if (m.fixedFilter) {
|
|
938
|
+
const where = mergeWhere({ [pkField]: { _eq: descriptor.id } }, m.fixedFilter);
|
|
939
|
+
item = (await gql(`mutation Delete_${m.table}($where: ${m.table}_bool_exp!) {
|
|
793
940
|
result: delete_${m.table}(where: $where) { affected_rows returning { ${fields} } }
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
m.role,
|
|
799
|
-
context.signal
|
|
800
|
-
);
|
|
801
|
-
item = data.result.returning?.[0] ?? null;
|
|
802
|
-
} else {
|
|
803
|
-
const pkType = m.pkType ?? "bigint";
|
|
804
|
-
const query = `mutation Delete_${m.table}($id: ${pkType}!) {
|
|
941
|
+
}`, { where }, m.role, context.signal, "mutation")).result.returning?.[0] ?? null;
|
|
942
|
+
} else {
|
|
943
|
+
const pkType = m.pkType ?? "bigint";
|
|
944
|
+
item = (await gql(`mutation Delete_${m.table}($id: ${pkType}!) {
|
|
805
945
|
item: delete_${m.table}_by_pk(${pkField}: $id) { ${fields} }
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
context.signal
|
|
826
|
-
);
|
|
827
|
-
}
|
|
828
|
-
} : {}
|
|
829
|
-
},
|
|
830
|
-
live: createHasuraLiveProvider({
|
|
831
|
-
resources: config.resources,
|
|
832
|
-
getToken: config.getToken,
|
|
833
|
-
endpoint,
|
|
834
|
-
role: defaultRole
|
|
835
|
-
})
|
|
836
|
-
});
|
|
946
|
+
}`, { id: descriptor.id }, m.role, context.signal, "mutation")).item;
|
|
947
|
+
}
|
|
948
|
+
mutationRecord(m, descriptor.idField, item, descriptor.resource, descriptor.id);
|
|
949
|
+
return {
|
|
950
|
+
kind: "identity",
|
|
951
|
+
id: descriptor.id
|
|
952
|
+
};
|
|
953
|
+
},
|
|
954
|
+
...hasCustomMutations ? { async custom(descriptor, context) {
|
|
955
|
+
return executeCustomOperation(descriptor.resource, descriptor.operation, descriptor.input, "mutation", context.signal);
|
|
956
|
+
} } : {}
|
|
957
|
+
},
|
|
958
|
+
live: createHasuraLiveProvider({
|
|
959
|
+
resources: config.resources,
|
|
960
|
+
getToken: config.getToken,
|
|
961
|
+
endpoint,
|
|
962
|
+
role: defaultRole
|
|
963
|
+
})
|
|
964
|
+
});
|
|
837
965
|
}
|
|
838
|
-
|
|
966
|
+
//#endregion
|
|
839
967
|
exports.DEFAULT_HASURA_ENDPOINT = DEFAULT_HASURA_ENDPOINT;
|
|
840
968
|
exports.FILTER_OPERATOR_UNSUPPORTED_CODE = FILTER_OPERATOR_UNSUPPORTED_CODE;
|
|
969
|
+
exports.HASURA_CURSOR_CODES = HASURA_CURSOR_CODES;
|
|
970
|
+
exports.HASURA_EXTERNAL_FAILURE_CODES = HASURA_EXTERNAL_FAILURE_CODES;
|
|
971
|
+
exports.LOCALE_REQUIRED_CODE = LOCALE_REQUIRED_CODE;
|
|
841
972
|
exports.NOT_AUTHENTICATED_CODE = NOT_AUTHENTICATED_CODE;
|
|
842
973
|
exports.aggregateRoot = aggregateRoot;
|
|
974
|
+
exports.assertCursorLimit = assertCursorLimit;
|
|
975
|
+
exports.assertCursorOrderable = assertCursorOrderable;
|
|
843
976
|
exports.byPkRoot = byPkRoot;
|
|
977
|
+
exports.coerceInt64Filter = coerceInt64Filter;
|
|
844
978
|
exports.createHasuraDataProvider = createHasuraDataProvider;
|
|
845
979
|
exports.createHasuraLiveProvider = createHasuraLiveProvider;
|
|
980
|
+
exports.decodeCursor = decodeCursor;
|
|
981
|
+
exports.effectiveCursorOrdering = effectiveCursorOrdering;
|
|
982
|
+
exports.encodeCursor = encodeCursor;
|
|
846
983
|
exports.filtersToWhere = filtersToWhere;
|
|
847
984
|
exports.flattenJsonbSpill = flattenJsonbSpill;
|
|
848
985
|
exports.hasuraWsUrl = hasuraWsUrl;
|
|
986
|
+
exports.keysetWhere = keysetWhere;
|
|
987
|
+
exports.localeWhere = localeWhere;
|
|
849
988
|
exports.mergeWhere = mergeWhere;
|
|
989
|
+
exports.orderingSignature = orderingSignature;
|
|
850
990
|
exports.partitionDataBody = partitionDataBody;
|
|
851
991
|
exports.partitionJsonbSpill = partitionJsonbSpill;
|
|
852
992
|
exports.scopeToWhere = scopeToWhere;
|
|
853
|
-
|
|
993
|
+
|
|
854
994
|
//# sourceMappingURL=index.cjs.map
|