@palbase/backend 16.0.0 → 17.0.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/dist/{chunk-7LAXRLPG.js → chunk-IBIME7N2.js} +321 -2
- package/dist/chunk-IBIME7N2.js.map +1 -0
- package/dist/db/index.d.cts +1 -2
- package/dist/db/index.d.ts +1 -2
- package/dist/db/index.js +6 -8
- package/dist/{endpoint-BW8bn1y-.d.cts → index-D6mRh4yq.d.cts} +654 -1
- package/dist/{endpoint-BW8bn1y-.d.ts → index-J1tnAnFj.d.ts} +654 -1
- package/dist/index.d.cts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +135 -23
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +84 -962
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +64 -61
- package/dist/test/index.d.ts +64 -61
- package/dist/test/index.js +85 -465
- package/dist/test/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-7LAXRLPG.js.map +0 -1
- package/dist/chunk-I72YYSEI.js +0 -148
- package/dist/chunk-I72YYSEI.js.map +0 -1
- package/dist/chunk-ZZZHVPOQ.js +0 -327
- package/dist/chunk-ZZZHVPOQ.js.map +0 -1
- package/dist/index-Cww79LRh.d.cts +0 -656
- package/dist/index-IYODApZV.d.ts +0 -656
package/dist/test/index.cjs
CHANGED
|
@@ -20,989 +20,111 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/test/index.ts
|
|
21
21
|
var test_exports = {};
|
|
22
22
|
__export(test_exports, {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
TestApiError: () => TestApiError,
|
|
24
|
+
api: () => api,
|
|
25
|
+
createTestApi: () => createTestApi
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(test_exports);
|
|
27
28
|
|
|
28
|
-
// src/
|
|
29
|
-
var
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
constructor(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this.name = "TxPlanError";
|
|
29
|
+
// src/test/api.ts
|
|
30
|
+
var TestApiError = class extends Error {
|
|
31
|
+
status;
|
|
32
|
+
error;
|
|
33
|
+
data;
|
|
34
|
+
constructor(method, path, status, body) {
|
|
35
|
+
const envelope = body ?? {};
|
|
36
|
+
const code = envelope.error ?? String(status);
|
|
37
|
+
super(`${method} ${path} \u2192 ${status} ${code}${envelope.error_description ? `: ${envelope.error_description}` : ""}`);
|
|
38
|
+
this.name = "TestApiError";
|
|
39
|
+
this.status = status;
|
|
40
|
+
this.error = code;
|
|
41
|
+
this.data = envelope.data;
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
var TRAPPED_PROPS = [
|
|
49
|
-
"then",
|
|
50
|
-
"valueOf",
|
|
51
|
-
"toString",
|
|
52
|
-
"toJSON",
|
|
53
|
-
Symbol.toPrimitive
|
|
54
|
-
];
|
|
55
|
-
function trap(prop, what, hint) {
|
|
56
|
-
const name = typeof prop === "symbol" ? prop.description ?? String(prop) : prop;
|
|
57
|
-
throw new TxRefError(
|
|
58
|
-
`${what} was used as a value (via \`${name}\`). Nothing in a transaction callback has run yet, so there is no value to read. ${hint}`
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
function makeRef(op, field) {
|
|
62
|
-
const target = { [REF]: { op, field } };
|
|
63
|
-
return new Proxy(target, {
|
|
64
|
-
get(t, prop) {
|
|
65
|
-
if (prop === REF) return t[REF];
|
|
66
|
-
if (TRAPPED_PROPS.includes(prop)) {
|
|
67
|
-
trap(
|
|
68
|
-
prop,
|
|
69
|
-
`\`${field}\` of a row this transaction has not written yet`,
|
|
70
|
-
"Pass it to another operation in the same plan, or return it from the callback and read it after `transaction()` resolves."
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
return void 0;
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
function makeRowHandle(op) {
|
|
78
|
-
const target = { [ROW]: op };
|
|
79
|
-
return new Proxy(target, {
|
|
80
|
-
get(t, prop) {
|
|
81
|
-
if (prop === ROW) return t[ROW];
|
|
82
|
-
if (TRAPPED_PROPS.includes(prop)) {
|
|
83
|
-
trap(
|
|
84
|
-
prop,
|
|
85
|
-
"A row this transaction has not written yet",
|
|
86
|
-
"Read one of its columns to reference it, or return the row from the callback and read it after `transaction()` resolves."
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
if (typeof prop === "symbol") return void 0;
|
|
90
|
-
return makeRef(op, prop);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
function refDescriptor(v) {
|
|
95
|
-
if (typeof v !== "object" || v === null) return null;
|
|
96
|
-
const d = v[REF];
|
|
97
|
-
return isRefDescriptor(d) ? d : null;
|
|
98
|
-
}
|
|
99
|
-
function isRefDescriptor(d) {
|
|
100
|
-
return typeof d === "object" && d !== null && typeof d.op === "number" && typeof d.field === "string";
|
|
101
|
-
}
|
|
102
|
-
function rowOpIndex(v) {
|
|
103
|
-
if (typeof v !== "object" || v === null) return null;
|
|
104
|
-
const op = v[ROW];
|
|
105
|
-
return typeof op === "number" ? op : null;
|
|
106
|
-
}
|
|
107
|
-
function exprOf(v) {
|
|
108
|
-
if (typeof v !== "object" || v === null) return null;
|
|
109
|
-
const e = v[EXPR];
|
|
110
|
-
return typeof e === "object" && e !== null ? e : null;
|
|
111
|
-
}
|
|
112
|
-
function isRowsHandle(v) {
|
|
113
|
-
return typeof v === "object" && v !== null && v[ROWS] !== void 0;
|
|
114
|
-
}
|
|
115
|
-
function encodeValue(value, column, allowColumnExpr) {
|
|
116
|
-
const ref = refDescriptor(value);
|
|
117
|
-
if (ref) return { $ref: { op: ref.op, field: ref.field } };
|
|
118
|
-
const expr = exprOf(value);
|
|
119
|
-
if (expr) {
|
|
120
|
-
if (expr.fn !== "now" && !allowColumnExpr) {
|
|
121
|
-
throw new TxPlanError(
|
|
122
|
-
`\`${column}\`: ${expr.fn}() reads the column's current value, so it is only valid in updateWhere(where, set).`
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
return { $expr: expr };
|
|
126
|
-
}
|
|
127
|
-
if (rowOpIndex(value) !== null) {
|
|
128
|
-
throw new TxPlanError(
|
|
129
|
-
`\`${column}\`: a row handle is not a value. Read the column you meant (e.g. \`row.id\`).`
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
if (isRowsHandle(value)) {
|
|
133
|
-
throw new TxPlanError(
|
|
134
|
-
`\`${column}\`: an operation result is not a value. Declare an expectation first (\`.expectOne(err)\`) and read a column from the row.`
|
|
44
|
+
function required(value, envName) {
|
|
45
|
+
if (!value) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`${envName} is not set \u2014 the test client has nowhere to send requests. This is set by the deploy that runs your tests; if you are running them by hand, set it yourself.`
|
|
135
48
|
);
|
|
136
49
|
}
|
|
137
|
-
assertNoNestedHandles(value, column);
|
|
138
50
|
return value;
|
|
139
51
|
}
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
assertNoNestedHandles(item, column);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
function encodeMap(map, allowColumnExpr) {
|
|
157
|
-
const out = {};
|
|
158
|
-
for (const key of Object.keys(map).sort()) {
|
|
159
|
-
const value = map[key];
|
|
160
|
-
if (value === void 0) continue;
|
|
161
|
-
out[key] = encodeValue(value, key, allowColumnExpr);
|
|
162
|
-
}
|
|
163
|
-
return out;
|
|
164
|
-
}
|
|
165
|
-
var SKIPPED_OP = -1;
|
|
166
|
-
var TxRowsImpl = class {
|
|
167
|
-
constructor(builder, opIndex, what) {
|
|
168
|
-
this.builder = builder;
|
|
169
|
-
this.opIndex = opIndex;
|
|
170
|
-
this.what = what;
|
|
171
|
-
}
|
|
172
|
-
builder;
|
|
173
|
-
opIndex;
|
|
174
|
-
what;
|
|
175
|
-
// Present so `isRowsHandle` recognises the object; never read for its value.
|
|
176
|
-
[ROWS] = true;
|
|
177
|
-
guarded = false;
|
|
178
|
-
// The type-level `await` guard made real: TS rejects `await rows` at compile
|
|
179
|
-
// time, and reaching this means someone called `.then(...)` by hand.
|
|
180
|
-
then() {
|
|
181
|
-
throw new TxRefError(
|
|
182
|
-
`${this.what} cannot be awaited: a transaction callback builds a plan, it does not run statements. Remove the \`await\`.`
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
expectOne(error) {
|
|
186
|
-
this.declareGuard("one", 1, error);
|
|
187
|
-
if (this.opIndex === SKIPPED_OP) throw error;
|
|
188
|
-
return makeRowHandle(this.opIndex);
|
|
189
|
-
}
|
|
190
|
-
expectNone(error) {
|
|
191
|
-
this.declareGuard("none", 0, error);
|
|
192
|
-
}
|
|
193
|
-
expectAtLeast(n, error) {
|
|
194
|
-
assertGuardCount(n, "expectAtLeast");
|
|
195
|
-
this.declareGuard("atLeast", n, error);
|
|
196
|
-
if (this.opIndex === SKIPPED_OP && n > 0) throw error;
|
|
197
|
-
}
|
|
198
|
-
expectAtMost(n, error) {
|
|
199
|
-
assertGuardCount(n, "expectAtMost");
|
|
200
|
-
this.declareGuard("atMost", n, error);
|
|
201
|
-
}
|
|
202
|
-
declareGuard(kind, n, error) {
|
|
203
|
-
if (!(error instanceof Error)) {
|
|
204
|
-
throw new TxPlanError(
|
|
205
|
-
`${this.what}: an expectation needs the Error to throw when it does not hold (e.g. \`.expect\u2026(new Conflict("already accepted"))\`).`
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
if (this.guarded) {
|
|
209
|
-
throw new TxPlanError(
|
|
210
|
-
`${this.what} already has an expectation. One operation carries one expectation; declare the second one on its own operation.`
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
this.guarded = true;
|
|
214
|
-
if (this.opIndex === SKIPPED_OP) return;
|
|
215
|
-
this.builder.attachGuard(this.opIndex, kind, n, error);
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
function assertGuardCount(n, fn) {
|
|
219
|
-
if (!Number.isInteger(n) || n < 0) {
|
|
220
|
-
throw new TxPlanError(`${fn}(n) needs a non-negative integer, got ${String(n)}`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
var MAX_OPS = 1e3;
|
|
224
|
-
var MAX_ROWS = 5e3;
|
|
225
|
-
var TxPlanBuilder = class {
|
|
226
|
-
ops = [];
|
|
227
|
-
/** Errors handed to expectations, indexed by the `slot` the server echoes. */
|
|
228
|
-
slots = [];
|
|
229
|
-
/** The table surface handed to the callback. Untyped here; the public
|
|
230
|
-
* `transaction()` signatures put the schema types on top. */
|
|
231
|
-
table(name) {
|
|
232
|
-
return {
|
|
233
|
-
insert: (values) => {
|
|
234
|
-
const encoded = encodeMap(values, false);
|
|
235
|
-
if (Object.keys(encoded).length === 0) {
|
|
236
|
-
throw new TxPlanError(`${name}.insert() needs at least one column`);
|
|
237
|
-
}
|
|
238
|
-
return this.push({ op: "insert", table: name, values: encoded }, `${name}.insert()`);
|
|
239
|
-
},
|
|
240
|
-
insertMany: (rows) => {
|
|
241
|
-
if (rows.length === 0) {
|
|
242
|
-
return new TxRowsImpl(this, SKIPPED_OP, `${name}.insertMany()`);
|
|
243
|
-
}
|
|
244
|
-
if (rows.length > MAX_ROWS) {
|
|
245
|
-
throw new TxPlanError(
|
|
246
|
-
`${name}.insertMany() has ${rows.length} rows; the limit is ${MAX_ROWS}. Split the write across requests.`
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
const encoded = rows.map((row) => encodeMap(row, false));
|
|
250
|
-
assertUniformRows(encoded, name);
|
|
251
|
-
return this.push({ op: "insertMany", table: name, rows: encoded }, `${name}.insertMany()`);
|
|
252
|
-
},
|
|
253
|
-
updateWhere: (where, set) => {
|
|
254
|
-
const encodedWhere = encodeMap(where, false);
|
|
255
|
-
const encodedSet = encodeMap(set, true);
|
|
256
|
-
if (Object.keys(encodedWhere).length === 0) {
|
|
257
|
-
throw new TxPlanError(
|
|
258
|
-
`${name}.updateWhere() needs a filter. An update with no filter rewrites the whole table.`
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
if (Object.keys(encodedSet).length === 0) {
|
|
262
|
-
throw new TxPlanError(`${name}.updateWhere() needs at least one column to set`);
|
|
263
|
-
}
|
|
264
|
-
return this.push(
|
|
265
|
-
{ op: "update", table: name, set: encodedSet, where: encodedWhere },
|
|
266
|
-
`${name}.updateWhere()`
|
|
267
|
-
);
|
|
268
|
-
},
|
|
269
|
-
deleteWhere: (where) => {
|
|
270
|
-
const encodedWhere = encodeMap(where, false);
|
|
271
|
-
if (Object.keys(encodedWhere).length === 0) {
|
|
272
|
-
throw new TxPlanError(
|
|
273
|
-
`${name}.deleteWhere() needs a filter. A delete with no filter empties the table.`
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
return this.push(
|
|
277
|
-
{ op: "delete", table: name, where: encodedWhere },
|
|
278
|
-
`${name}.deleteWhere()`
|
|
279
|
-
);
|
|
280
|
-
},
|
|
281
|
-
select: (where, options) => {
|
|
282
|
-
const op = { op: "select", table: name };
|
|
283
|
-
const encodedWhere = encodeMap(where ?? {}, false);
|
|
284
|
-
if (Object.keys(encodedWhere).length > 0) op.where = encodedWhere;
|
|
285
|
-
if (options?.limit !== void 0) {
|
|
286
|
-
if (!Number.isInteger(options.limit) || options.limit < 0) {
|
|
287
|
-
throw new TxPlanError(
|
|
288
|
-
`${name}.select(): limit needs a non-negative integer, got ${String(options.limit)}`
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
op.limit = options.limit;
|
|
292
|
-
}
|
|
293
|
-
if (options?.lock !== void 0) op.lock = options.lock;
|
|
294
|
-
return this.push(op, `${name}.select()`);
|
|
295
|
-
}
|
|
52
|
+
function createTestApi(config) {
|
|
53
|
+
const baseUrl = required(config.baseUrl, "PALBASE_TEST_BASE_URL").replace(/\/$/, "");
|
|
54
|
+
const apiKey = required(config.apiKey, "PALBASE_TEST_API_KEY");
|
|
55
|
+
const candidateToken = required(config.candidateToken, "PALBASE_TEST_CANDIDATE");
|
|
56
|
+
const requests = [];
|
|
57
|
+
let bearer = null;
|
|
58
|
+
async function call(method, path, body, opts = {}) {
|
|
59
|
+
const headers = {
|
|
60
|
+
apikey: apiKey,
|
|
61
|
+
// Selects the release under test. Omit it and the gateway serves the LIVE
|
|
62
|
+
// one, which would make the whole suite grade the wrong code.
|
|
63
|
+
"x-palbase-candidate": candidateToken,
|
|
64
|
+
...opts.headers
|
|
296
65
|
};
|
|
66
|
+
if (bearer) headers.authorization = `Bearer ${bearer}`;
|
|
67
|
+
if (body !== void 0) headers["content-type"] = "application/json";
|
|
68
|
+
const startedAt = Date.now();
|
|
69
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
70
|
+
method,
|
|
71
|
+
headers,
|
|
72
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
73
|
+
});
|
|
74
|
+
const text = await res.text();
|
|
75
|
+
const parsed = text ? safeParse(text) : void 0;
|
|
76
|
+
requests.push({ method, path, status: res.status, ms: Date.now() - startedAt });
|
|
77
|
+
if (!res.ok) throw new TestApiError(method, path, res.status, parsed);
|
|
78
|
+
return parsed;
|
|
297
79
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (!op) throw new TxPlanError(`internal: expectation on unknown operation ${opIndex}`);
|
|
312
|
-
const slot = this.slots.length;
|
|
313
|
-
this.slots.push(error);
|
|
314
|
-
op.guard = { kind, n, slot };
|
|
315
|
-
}
|
|
316
|
-
/** The serialisable plan. Empty when the callback described no writes. */
|
|
317
|
-
body() {
|
|
318
|
-
return { ops: this.ops };
|
|
319
|
-
}
|
|
320
|
-
/** The error the server's `slot` selects, or `null` when it names one this
|
|
321
|
-
* plan never declared (a server/client disagreement, not a tenant error). */
|
|
322
|
-
errorForSlot(slot) {
|
|
323
|
-
return this.slots[slot] ?? null;
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
function assertUniformRows(rows, table) {
|
|
327
|
-
const first = rows[0];
|
|
328
|
-
if (!first) return;
|
|
329
|
-
const want = Object.keys(first);
|
|
330
|
-
const wantKey = want.join(",");
|
|
331
|
-
for (let i = 1; i < rows.length; i++) {
|
|
332
|
-
const got = Object.keys(rows[i]);
|
|
333
|
-
if (got.join(",") !== wantKey) {
|
|
334
|
-
throw new TxPlanError(
|
|
335
|
-
`${table}.insertMany(): every row must set the same columns. Row 0 sets [${want.join(", ")}] but row ${i} sets [${got.join(", ")}]. (A property set to \`undefined\` counts as absent \u2014 use \`null\`.)`
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
function materializeResult(value, results) {
|
|
341
|
-
const ref = refDescriptor(value);
|
|
342
|
-
if (ref) {
|
|
343
|
-
const row = rowOf(results, ref.op, `\`${ref.field}\``);
|
|
344
|
-
if (!(ref.field in row)) {
|
|
345
|
-
throw new TxPlanError(
|
|
346
|
-
`the transaction's operation ${ref.op} returned no column \`${ref.field}\`.`
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
return row[ref.field];
|
|
350
|
-
}
|
|
351
|
-
const rowOp = rowOpIndex(value);
|
|
352
|
-
if (rowOp !== null) return rowOf(results, rowOp, "a row");
|
|
353
|
-
if (isRowsHandle(value)) {
|
|
354
|
-
throw new TxPlanError(
|
|
355
|
-
"an operation result cannot be returned from a transaction callback: its row count is not known until the plan runs. Declare an expectation (`.expectOne(err)`) and return the row, or a column of it."
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
if (Array.isArray(value)) return value.map((item) => materializeResult(item, results));
|
|
359
|
-
if (isPlainObject(value)) {
|
|
360
|
-
const out = {};
|
|
361
|
-
for (const [key, item] of Object.entries(value)) out[key] = materializeResult(item, results);
|
|
362
|
-
return out;
|
|
363
|
-
}
|
|
364
|
-
return value;
|
|
365
|
-
}
|
|
366
|
-
function rowOf(results, opIndex, what) {
|
|
367
|
-
const result = results[opIndex];
|
|
368
|
-
if (!result) {
|
|
369
|
-
throw new TxPlanError(
|
|
370
|
-
`the transaction returned no result for operation ${opIndex}, so ${what} cannot be read.`
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
const row = result.rows[0];
|
|
374
|
-
if (!row) {
|
|
375
|
-
throw new TxPlanError(
|
|
376
|
-
`the transaction's operation ${opIndex} returned no row, so ${what} cannot be read.`
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
return row;
|
|
380
|
-
}
|
|
381
|
-
function isPlainObject(value) {
|
|
382
|
-
if (typeof value !== "object" || value === null) return false;
|
|
383
|
-
const proto = Object.getPrototypeOf(value);
|
|
384
|
-
return proto === Object.prototype || proto === null;
|
|
385
|
-
}
|
|
386
|
-
async function runTxPlan(transport, tables, builder, fn) {
|
|
387
|
-
const returned = fn({ tables });
|
|
388
|
-
const body = builder.body();
|
|
389
|
-
if (body.ops.length === 0) {
|
|
390
|
-
return materializeResult(returned, []);
|
|
391
|
-
}
|
|
392
|
-
let response;
|
|
393
|
-
try {
|
|
394
|
-
response = await transport.txPlan(body);
|
|
395
|
-
} catch (err) {
|
|
396
|
-
throw translateRejection(err, builder);
|
|
397
|
-
}
|
|
398
|
-
return materializeResult(returned, response.results);
|
|
399
|
-
}
|
|
400
|
-
function translateRejection(err, builder) {
|
|
401
|
-
if (typeof err !== "object" || err === null) return err;
|
|
402
|
-
const rejection = err;
|
|
403
|
-
if (rejection.error_code !== "tx_guard_failed" || typeof rejection.slot !== "number") {
|
|
404
|
-
return err;
|
|
405
|
-
}
|
|
406
|
-
return builder.errorForSlot(rejection.slot) ?? err;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// src/runtime.ts
|
|
410
|
-
var __requestALS = new import_node_async_hooks.AsyncLocalStorage();
|
|
411
|
-
var runtime = null;
|
|
412
|
-
function __setRuntime(services) {
|
|
413
|
-
runtime = services;
|
|
414
|
-
}
|
|
415
|
-
function __getRuntime() {
|
|
416
|
-
const scoped = __requestALS.getStore();
|
|
417
|
-
if (scoped) return scoped.runtime;
|
|
418
|
-
if (runtime === null) {
|
|
419
|
-
throw new Error(
|
|
420
|
-
"Palbase services accessed outside a request scope. The Database/Documents/\u2026 singletons are only available inside an endpoint handler (or after the runtime has called __runWithRuntime / __setRuntime)."
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
return runtime;
|
|
424
|
-
}
|
|
425
|
-
function makeServiceProxy(key) {
|
|
426
|
-
const handler = {
|
|
427
|
-
get(_target, prop, receiver) {
|
|
428
|
-
const client = __getRuntime()[key];
|
|
429
|
-
const value = Reflect.get(client, prop, receiver);
|
|
430
|
-
return typeof value === "function" ? value.bind(client) : value;
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
return new Proxy({}, handler);
|
|
434
|
-
}
|
|
435
|
-
function makeTablesAccessor(ops) {
|
|
436
|
-
const tablesProxy = new Proxy(
|
|
437
|
-
{},
|
|
438
|
-
{
|
|
439
|
-
get(_t, prop) {
|
|
440
|
-
if (typeof prop !== "string") return void 0;
|
|
441
|
-
const name = prop;
|
|
442
|
-
return {
|
|
443
|
-
insert: (data) => ops().insert(name, data),
|
|
444
|
-
update: (id, data) => ops().update(name, id, data),
|
|
445
|
-
delete: (id) => ops().delete(name, id),
|
|
446
|
-
findById: (id) => ops().findById(name, id),
|
|
447
|
-
findMany: (query) => ops().findMany(name, query)
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
);
|
|
452
|
-
return tablesProxy;
|
|
453
|
-
}
|
|
454
|
-
var rawDatabase = makeServiceProxy("Database");
|
|
455
|
-
function makeTypedSurface(raw) {
|
|
456
|
-
const ops = {
|
|
457
|
-
query: (sql, params) => raw.query(sql, params),
|
|
458
|
-
insert: (table, data) => raw.insert(table, data),
|
|
459
|
-
update: (table, id, data) => raw.update(table, id, data),
|
|
460
|
-
delete: (table, id) => raw.delete(table, id),
|
|
461
|
-
findById: (table, id) => raw.findById(table, id),
|
|
462
|
-
findMany: (table, query) => raw.findMany(table, query)
|
|
463
|
-
};
|
|
464
|
-
return Object.assign(ops, {
|
|
465
|
-
tables: makeTablesAccessor(() => raw),
|
|
466
|
-
transaction(fn) {
|
|
467
|
-
const builder = new TxPlanBuilder();
|
|
468
|
-
return runTxPlan(raw, makeTxTablesAccessor(builder), builder, fn);
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
function makeTxTablesAccessor(builder) {
|
|
473
|
-
const tablesProxy = new Proxy(
|
|
474
|
-
{},
|
|
475
|
-
{
|
|
476
|
-
get(_t, prop) {
|
|
477
|
-
if (typeof prop !== "string") return void 0;
|
|
478
|
-
return builder.table(prop);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
);
|
|
482
|
-
return tablesProxy;
|
|
483
|
-
}
|
|
484
|
-
var Database = Object.assign(makeTypedSurface(rawDatabase), {
|
|
485
|
-
/**
|
|
486
|
-
* Lazily resolve the runtime's service-role sibling on each call. We do NOT
|
|
487
|
-
* cache it: `rawDatabase.asService()` reads the CURRENT request scope through
|
|
488
|
-
* the runtime proxy, and the per-request runtime injects a service client
|
|
489
|
-
* bound to that request's identity headers — caching would leak one request's
|
|
490
|
-
* sibling into another concurrent request.
|
|
491
|
-
*/
|
|
492
|
-
asService() {
|
|
493
|
-
return makeTypedSurface(rawDatabase.asService());
|
|
494
|
-
}
|
|
495
|
-
});
|
|
496
|
-
var Documents = makeServiceProxy("Documents");
|
|
497
|
-
var Storage = makeServiceProxy("Storage");
|
|
498
|
-
var Cache = makeServiceProxy("Cache");
|
|
499
|
-
var Log = makeServiceProxy("Log");
|
|
500
|
-
var Notifications = makeServiceProxy("Notifications");
|
|
501
|
-
var Purchases = makeServiceProxy("Purchases");
|
|
502
|
-
var rawFlags = makeServiceProxy("Flags");
|
|
503
|
-
var Flags = Object.assign(
|
|
504
|
-
{
|
|
505
|
-
isEnabled(flagName, context) {
|
|
506
|
-
return rawFlags.isEnabled(flagName, context);
|
|
507
|
-
},
|
|
508
|
-
getVariant(flagName, context) {
|
|
509
|
-
return rawFlags.getVariant(flagName, context);
|
|
510
|
-
},
|
|
511
|
-
getAll(context) {
|
|
512
|
-
return rawFlags.getAll(context);
|
|
513
|
-
},
|
|
514
|
-
setOverride(key, value) {
|
|
515
|
-
return rawFlags.setOverride(key, value);
|
|
516
|
-
}
|
|
517
|
-
},
|
|
518
|
-
{
|
|
519
|
-
/**
|
|
520
|
-
* Lazily resolve the runtime's cross-user sibling on each call. We do NOT
|
|
521
|
-
* cache it: `rawFlags.asService()` reads the CURRENT request scope through
|
|
522
|
-
* the runtime proxy, so caching would leak one request's sibling into
|
|
523
|
-
* another concurrent request. Mirrors `Database.asService()`.
|
|
524
|
-
*/
|
|
525
|
-
asService() {
|
|
526
|
-
return rawFlags.asService();
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
);
|
|
530
|
-
var Realtime = makeServiceProxy("Realtime");
|
|
531
|
-
|
|
532
|
-
// src/test/mock-db.ts
|
|
533
|
-
function createMockDB() {
|
|
534
|
-
const store = /* @__PURE__ */ new Map();
|
|
535
|
-
const tracked = {
|
|
536
|
-
inserted: /* @__PURE__ */ new Map(),
|
|
537
|
-
updated: /* @__PURE__ */ new Map(),
|
|
538
|
-
deleted: /* @__PURE__ */ new Map()
|
|
539
|
-
};
|
|
540
|
-
function rowsOf(table) {
|
|
541
|
-
let rows = store.get(table);
|
|
542
|
-
if (!rows) {
|
|
543
|
-
rows = [];
|
|
544
|
-
store.set(table, rows);
|
|
545
|
-
}
|
|
546
|
-
return rows;
|
|
547
|
-
}
|
|
548
|
-
function track(map, table, row) {
|
|
549
|
-
const list = map.get(table);
|
|
550
|
-
if (list) list.push(row);
|
|
551
|
-
else map.set(table, [row]);
|
|
552
|
-
}
|
|
553
|
-
const ops = {
|
|
554
|
-
async query(_sql, _params) {
|
|
555
|
-
return [];
|
|
556
|
-
},
|
|
557
|
-
async insert(table, data) {
|
|
558
|
-
const record = { id: crypto.randomUUID(), ...data };
|
|
559
|
-
rowsOf(table).push(record);
|
|
560
|
-
track(tracked.inserted, table, record);
|
|
561
|
-
return record;
|
|
562
|
-
},
|
|
563
|
-
async update(table, id, data) {
|
|
564
|
-
const rows = store.get(table) ?? [];
|
|
565
|
-
const idx = rows.findIndex((r) => r["id"] === id);
|
|
566
|
-
const updated = idx >= 0 ? { ...rows[idx], ...data } : { id, ...data };
|
|
567
|
-
if (idx >= 0) {
|
|
568
|
-
rows[idx] = updated;
|
|
569
|
-
}
|
|
570
|
-
track(tracked.updated, table, updated);
|
|
571
|
-
return updated;
|
|
572
|
-
},
|
|
573
|
-
async delete(table, id) {
|
|
574
|
-
const rows = store.get(table) ?? [];
|
|
575
|
-
const idx = rows.findIndex((r) => r["id"] === id);
|
|
576
|
-
if (idx >= 0) rows.splice(idx, 1);
|
|
577
|
-
const list = tracked.deleted.get(table);
|
|
578
|
-
if (list) list.push(id);
|
|
579
|
-
else tracked.deleted.set(table, [id]);
|
|
580
|
-
},
|
|
581
|
-
async findById(table, id) {
|
|
582
|
-
const rows = store.get(table) ?? [];
|
|
583
|
-
return rows.find((r) => r["id"] === id) ?? null;
|
|
584
|
-
},
|
|
585
|
-
async findMany(table, query) {
|
|
586
|
-
const rows = store.get(table) ?? [];
|
|
587
|
-
if (!query) return rows;
|
|
588
|
-
return rows.filter(
|
|
589
|
-
(row) => Object.entries(query).every(([key, val]) => row[key] === val)
|
|
80
|
+
return {
|
|
81
|
+
requests,
|
|
82
|
+
get: (path, opts) => call("GET", path, void 0, opts),
|
|
83
|
+
post: (path, body, opts) => call("POST", path, body, opts),
|
|
84
|
+
patch: (path, body, opts) => call("PATCH", path, body, opts),
|
|
85
|
+
put: (path, body, opts) => call("PUT", path, body, opts),
|
|
86
|
+
delete: (path, opts) => call("DELETE", path, void 0, opts),
|
|
87
|
+
query: (path, body, opts) => call("QUERY", path, body, opts),
|
|
88
|
+
async signIn(credentials) {
|
|
89
|
+
const result = await call(
|
|
90
|
+
"POST",
|
|
91
|
+
"/auth/login",
|
|
92
|
+
credentials
|
|
590
93
|
);
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
async function txPlan(plan) {
|
|
594
|
-
const snapshot = /* @__PURE__ */ new Map();
|
|
595
|
-
for (const [table, rows] of store) snapshot.set(table, [...rows]);
|
|
596
|
-
const trackedSnapshot = {
|
|
597
|
-
inserted: cloneTracked(tracked.inserted),
|
|
598
|
-
updated: cloneTracked(tracked.updated),
|
|
599
|
-
deleted: new Map([...tracked.deleted].map(([k, v]) => [k, [...v]]))
|
|
600
|
-
};
|
|
601
|
-
const results = [];
|
|
602
|
-
try {
|
|
603
|
-
for (const op of plan.ops) {
|
|
604
|
-
const result = applyOp(op, results);
|
|
605
|
-
results.push(result);
|
|
606
|
-
const failure = guardFailure(op.guard, result.rows.length);
|
|
607
|
-
if (failure) throw failure;
|
|
608
|
-
}
|
|
609
|
-
} catch (err) {
|
|
610
|
-
store.clear();
|
|
611
|
-
for (const [table, rows] of snapshot) store.set(table, rows);
|
|
612
|
-
tracked.inserted = trackedSnapshot.inserted;
|
|
613
|
-
tracked.updated = trackedSnapshot.updated;
|
|
614
|
-
tracked.deleted = trackedSnapshot.deleted;
|
|
615
|
-
throw err;
|
|
616
|
-
}
|
|
617
|
-
return { results };
|
|
618
|
-
}
|
|
619
|
-
function applyOp(op, results) {
|
|
620
|
-
switch (op.op) {
|
|
621
|
-
case "insert": {
|
|
622
|
-
const record = { id: crypto.randomUUID(), ...resolveMap(op.values ?? {}, results, null) };
|
|
623
|
-
rowsOf(op.table).push(record);
|
|
624
|
-
track(tracked.inserted, op.table, record);
|
|
625
|
-
return { rows: [record], rows_affected: 1 };
|
|
626
|
-
}
|
|
627
|
-
case "insertMany": {
|
|
628
|
-
const written = (op.rows ?? []).map((row) => {
|
|
629
|
-
const record = { id: crypto.randomUUID(), ...resolveMap(row, results, null) };
|
|
630
|
-
rowsOf(op.table).push(record);
|
|
631
|
-
track(tracked.inserted, op.table, record);
|
|
632
|
-
return record;
|
|
633
|
-
});
|
|
634
|
-
return { rows: written, rows_affected: written.length };
|
|
635
|
-
}
|
|
636
|
-
case "update": {
|
|
637
|
-
const rows = rowsOf(op.table);
|
|
638
|
-
const where = resolveMap(op.where ?? {}, results, null);
|
|
639
|
-
const written = [];
|
|
640
|
-
for (let i = 0; i < rows.length; i++) {
|
|
641
|
-
const row = rows[i];
|
|
642
|
-
if (!row || !matches(row, where)) continue;
|
|
643
|
-
const next = { ...row, ...resolveMap(op.set ?? {}, results, row) };
|
|
644
|
-
rows[i] = next;
|
|
645
|
-
track(tracked.updated, op.table, next);
|
|
646
|
-
written.push(next);
|
|
647
|
-
}
|
|
648
|
-
return { rows: written, rows_affected: written.length };
|
|
649
|
-
}
|
|
650
|
-
case "delete": {
|
|
651
|
-
const rows = rowsOf(op.table);
|
|
652
|
-
const where = resolveMap(op.where ?? {}, results, null);
|
|
653
|
-
const removed = rows.filter((row) => matches(row, where));
|
|
654
|
-
for (const row of removed) {
|
|
655
|
-
rows.splice(rows.indexOf(row), 1);
|
|
656
|
-
const id = row["id"];
|
|
657
|
-
const list = tracked.deleted.get(op.table);
|
|
658
|
-
const key = typeof id === "string" ? id : String(id);
|
|
659
|
-
if (list) list.push(key);
|
|
660
|
-
else tracked.deleted.set(op.table, [key]);
|
|
661
|
-
}
|
|
662
|
-
return { rows: removed, rows_affected: removed.length };
|
|
663
|
-
}
|
|
664
|
-
case "select": {
|
|
665
|
-
const where = resolveMap(op.where ?? {}, results, null);
|
|
666
|
-
let found = rowsOf(op.table).filter((row) => matches(row, where));
|
|
667
|
-
if (op.limit !== void 0) found = found.slice(0, op.limit);
|
|
668
|
-
return { rows: found, rows_affected: found.length };
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
const client = {
|
|
673
|
-
...ops,
|
|
674
|
-
txPlan,
|
|
675
|
-
// In tests there is no real DB role; `asService()` returns the same
|
|
676
|
-
// in-memory client so RLS-bypass code paths still hit the same store and
|
|
677
|
-
// tracking maps. The omitted `asService` matches the contract (no
|
|
678
|
-
// double-bypass), so callers can't recurse.
|
|
679
|
-
asService() {
|
|
680
|
-
return client;
|
|
681
|
-
},
|
|
682
|
-
inserted(table) {
|
|
683
|
-
return tracked.inserted.get(table) ?? [];
|
|
94
|
+
bearer = result.access_token;
|
|
95
|
+
return result.user ?? { id: "" };
|
|
684
96
|
},
|
|
685
|
-
|
|
686
|
-
|
|
97
|
+
async signOut() {
|
|
98
|
+
await call("POST", "/auth/logout", void 0);
|
|
99
|
+
bearer = null;
|
|
687
100
|
},
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
},
|
|
691
|
-
seed(table, data) {
|
|
692
|
-
store.set(table, [...data]);
|
|
101
|
+
asAnonymous() {
|
|
102
|
+
bearer = null;
|
|
693
103
|
}
|
|
694
104
|
};
|
|
695
|
-
return client;
|
|
696
|
-
}
|
|
697
|
-
function cloneTracked(map) {
|
|
698
|
-
return new Map([...map].map(([k, v]) => [k, [...v]]));
|
|
699
105
|
}
|
|
700
|
-
function
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
if (!row) {
|
|
706
|
-
throw txRejection(409, "tx_ref_unresolved", {
|
|
707
|
-
message: `operation ${tagged.$ref.op} produced no row to reference`
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
return row[tagged.$ref.field];
|
|
711
|
-
}
|
|
712
|
-
if (tagged.$expr) {
|
|
713
|
-
const fn = tagged.$expr["fn"];
|
|
714
|
-
if (fn === "now") return (/* @__PURE__ */ new Date()).toISOString();
|
|
715
|
-
const by = Number(tagged.$expr["by"]);
|
|
716
|
-
const base = Number(current?.[column] ?? 0);
|
|
717
|
-
return fn === "dec" ? base - by : base + by;
|
|
718
|
-
}
|
|
719
|
-
return value;
|
|
720
|
-
}
|
|
721
|
-
function resolveMap(map, results, current) {
|
|
722
|
-
const out = {};
|
|
723
|
-
for (const [key, value] of Object.entries(map)) {
|
|
724
|
-
out[key] = resolveValue(value, results, current, key);
|
|
106
|
+
function safeParse(text) {
|
|
107
|
+
try {
|
|
108
|
+
return JSON.parse(text);
|
|
109
|
+
} catch {
|
|
110
|
+
return text;
|
|
725
111
|
}
|
|
726
|
-
return out;
|
|
727
|
-
}
|
|
728
|
-
function matches(row, where) {
|
|
729
|
-
return Object.entries(where).every(
|
|
730
|
-
([key, value]) => value === null ? row[key] === null || row[key] === void 0 : row[key] === value
|
|
731
|
-
);
|
|
732
|
-
}
|
|
733
|
-
function guardFailure(guard, count) {
|
|
734
|
-
if (!guard) return null;
|
|
735
|
-
const ok = guard.kind === "one" ? count === 1 : guard.kind === "none" ? count === 0 : guard.kind === "atLeast" ? count >= guard.n : count <= guard.n;
|
|
736
|
-
if (ok) return null;
|
|
737
|
-
return txRejection(409, "tx_guard_failed", {
|
|
738
|
-
slot: guard.slot,
|
|
739
|
-
message: `expected ${guard.kind} ${guard.n} row(s), got ${count}`
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
function txRejection(status, code, extra) {
|
|
743
|
-
const err = new Error(extra.message);
|
|
744
|
-
err.status = status;
|
|
745
|
-
err.error_code = code;
|
|
746
|
-
if (extra.slot !== void 0) err.slot = extra.slot;
|
|
747
|
-
return err;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// src/test/context.ts
|
|
751
|
-
function createMockLogger(logs) {
|
|
752
|
-
return {
|
|
753
|
-
info(message, ...args) {
|
|
754
|
-
logs.push({ level: "info", message, args });
|
|
755
|
-
},
|
|
756
|
-
warn(message, ...args) {
|
|
757
|
-
logs.push({ level: "warn", message, args });
|
|
758
|
-
},
|
|
759
|
-
error(message, ...args) {
|
|
760
|
-
logs.push({ level: "error", message, args });
|
|
761
|
-
},
|
|
762
|
-
debug(message, ...args) {
|
|
763
|
-
logs.push({ level: "debug", message, args });
|
|
764
|
-
}
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
function createMockCache() {
|
|
768
|
-
const store = /* @__PURE__ */ new Map();
|
|
769
|
-
const get = async (key) => {
|
|
770
|
-
return store.has(key) ? store.get(key) : null;
|
|
771
|
-
};
|
|
772
|
-
const set = async (key, value, _ttl) => {
|
|
773
|
-
store.set(key, value);
|
|
774
|
-
};
|
|
775
|
-
return {
|
|
776
|
-
get,
|
|
777
|
-
set,
|
|
778
|
-
async del(key) {
|
|
779
|
-
store.delete(key);
|
|
780
|
-
},
|
|
781
|
-
async incr(key) {
|
|
782
|
-
const raw = store.get(key);
|
|
783
|
-
const current = typeof raw === "number" ? raw : parseInt(String(raw ?? "0"), 10);
|
|
784
|
-
const next = current + 1;
|
|
785
|
-
store.set(key, next);
|
|
786
|
-
return next;
|
|
787
|
-
},
|
|
788
|
-
async getOrSet(key, ttl, fn) {
|
|
789
|
-
const hit = await get(key);
|
|
790
|
-
if (hit !== null) {
|
|
791
|
-
return hit;
|
|
792
|
-
}
|
|
793
|
-
const value = await fn();
|
|
794
|
-
await set(key, value, ttl);
|
|
795
|
-
return value;
|
|
796
|
-
}
|
|
797
|
-
};
|
|
798
112
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
};
|
|
809
|
-
const auth = {
|
|
810
|
-
verifyUserToken: () => notImpl("auth.verifyUserToken"),
|
|
811
|
-
getSession: () => notImpl("auth.getSession"),
|
|
812
|
-
mfa: {
|
|
813
|
-
enroll: () => notImpl("auth.mfa.enroll"),
|
|
814
|
-
verifyEnrollment: () => notImpl("auth.mfa.verifyEnrollment"),
|
|
815
|
-
challenge: () => notImpl("auth.mfa.challenge"),
|
|
816
|
-
recovery: () => notImpl("auth.mfa.recovery"),
|
|
817
|
-
listFactors: () => notImpl("auth.mfa.listFactors"),
|
|
818
|
-
removeFactor: () => notImpl("auth.mfa.removeFactor"),
|
|
819
|
-
regenerateRecoveryCodes: () => notImpl("auth.mfa.regenerateRecoveryCodes"),
|
|
820
|
-
emailEnroll: () => notImpl("auth.mfa.emailEnroll"),
|
|
821
|
-
emailChallenge: () => notImpl("auth.mfa.emailChallenge"),
|
|
822
|
-
emailVerify: () => notImpl("auth.mfa.emailVerify")
|
|
823
|
-
},
|
|
824
|
-
device: {
|
|
825
|
-
generateChallenge: () => notImpl("auth.device.generateChallenge"),
|
|
826
|
-
attestAndroid: () => notImpl("auth.device.attestAndroid"),
|
|
827
|
-
attestiOS: () => notImpl("auth.device.attestiOS"),
|
|
828
|
-
bind: () => notImpl("auth.device.bind"),
|
|
829
|
-
list: () => notImpl("auth.device.list"),
|
|
830
|
-
delete: () => notImpl("auth.device.delete"),
|
|
831
|
-
verifyRequestSignature: () => notImpl("auth.device.verifyRequestSignature"),
|
|
832
|
-
getToken: () => notImpl("auth.device.getToken"),
|
|
833
|
-
get isActive() {
|
|
834
|
-
return notImpl("auth.device.isActive");
|
|
835
|
-
},
|
|
836
|
-
setCachedToken: () => notImpl("auth.device.setCachedToken"),
|
|
837
|
-
dispose: () => notImpl("auth.device.dispose")
|
|
838
|
-
}
|
|
839
|
-
};
|
|
840
|
-
const storage = {
|
|
841
|
-
bucket: () => notImpl("storage.bucket")
|
|
842
|
-
};
|
|
843
|
-
const realtime = {
|
|
844
|
-
broadcast: async () => ({ data: void 0, error: null })
|
|
845
|
-
};
|
|
846
|
-
const functions = {
|
|
847
|
-
invoke: () => notImpl("functions.invoke")
|
|
848
|
-
};
|
|
849
|
-
const flags = {
|
|
850
|
-
isEnabled: () => notImpl("flags.isEnabled"),
|
|
851
|
-
getVariant: () => notImpl("flags.getVariant"),
|
|
852
|
-
getAll: () => notImpl("flags.getAll"),
|
|
853
|
-
setOverride: () => notImpl("flags.setOverride"),
|
|
854
|
-
asService: () => ({
|
|
855
|
-
setOverrideForUser: () => notImpl("flags.asService.setOverrideForUser"),
|
|
856
|
-
setOverridesForUser: () => notImpl("flags.asService.setOverridesForUser"),
|
|
857
|
-
clearOverrideForUser: () => notImpl("flags.asService.clearOverrideForUser"),
|
|
858
|
-
clearAllOverridesForUser: () => notImpl("flags.asService.clearAllOverridesForUser"),
|
|
859
|
-
batchSetOverrides: () => notImpl("flags.asService.batchSetOverrides")
|
|
860
|
-
})
|
|
861
|
-
};
|
|
862
|
-
const notifications = {
|
|
863
|
-
push: { send: () => notImpl("notifications.push.send") },
|
|
864
|
-
email: { send: () => notImpl("notifications.email.send") },
|
|
865
|
-
sms: { send: () => notImpl("notifications.sms.send") },
|
|
866
|
-
verifications: {
|
|
867
|
-
start: () => notImpl("notifications.verifications.start"),
|
|
868
|
-
check: () => notImpl("notifications.verifications.check")
|
|
869
|
-
},
|
|
870
|
-
inbox: {
|
|
871
|
-
send: () => notImpl("notifications.inbox.send"),
|
|
872
|
-
list: () => notImpl("notifications.inbox.list"),
|
|
873
|
-
unreadCount: () => notImpl("notifications.inbox.unreadCount"),
|
|
874
|
-
markRead: () => notImpl("notifications.inbox.markRead"),
|
|
875
|
-
markAllRead: () => notImpl("notifications.inbox.markAllRead"),
|
|
876
|
-
archive: () => notImpl("notifications.inbox.archive")
|
|
877
|
-
},
|
|
878
|
-
preferences: {
|
|
879
|
-
get: () => notImpl("notifications.preferences.get"),
|
|
880
|
-
update: () => notImpl("notifications.preferences.update")
|
|
881
|
-
},
|
|
882
|
-
templates: {
|
|
883
|
-
email: {
|
|
884
|
-
list: () => notImpl("notifications.templates.email.list"),
|
|
885
|
-
get: () => notImpl("notifications.templates.email.get"),
|
|
886
|
-
create: () => notImpl("notifications.templates.email.create"),
|
|
887
|
-
update: () => notImpl("notifications.templates.email.update"),
|
|
888
|
-
delete: () => notImpl("notifications.templates.email.delete")
|
|
889
|
-
},
|
|
890
|
-
sms: {
|
|
891
|
-
list: () => notImpl("notifications.templates.sms.list"),
|
|
892
|
-
get: () => notImpl("notifications.templates.sms.get"),
|
|
893
|
-
create: () => notImpl("notifications.templates.sms.create"),
|
|
894
|
-
update: () => notImpl("notifications.templates.sms.update"),
|
|
895
|
-
delete: () => notImpl("notifications.templates.sms.delete")
|
|
896
|
-
}
|
|
897
|
-
},
|
|
898
|
-
registerDevice: () => notImpl("notifications.registerDevice"),
|
|
899
|
-
unregisterDevice: () => notImpl("notifications.unregisterDevice")
|
|
900
|
-
};
|
|
901
|
-
const analytics = {
|
|
902
|
-
capture: () => notImpl("analytics.capture"),
|
|
903
|
-
identify: () => notImpl("analytics.identify"),
|
|
904
|
-
screen: () => notImpl("analytics.screen"),
|
|
905
|
-
query: {
|
|
906
|
-
count: () => notImpl("analytics.query.count"),
|
|
907
|
-
events: () => notImpl("analytics.query.events"),
|
|
908
|
-
properties: () => notImpl("analytics.query.properties"),
|
|
909
|
-
users: () => notImpl("analytics.query.users"),
|
|
910
|
-
funnel: () => notImpl("analytics.query.funnel"),
|
|
911
|
-
retention: () => notImpl("analytics.query.retention"),
|
|
912
|
-
cohort: () => notImpl("analytics.query.cohort")
|
|
913
|
-
},
|
|
914
|
-
management: {
|
|
915
|
-
overview: () => notImpl("analytics.management.overview"),
|
|
916
|
-
eventNames: () => notImpl("analytics.management.eventNames"),
|
|
917
|
-
userDetail: () => notImpl("analytics.management.userDetail"),
|
|
918
|
-
deleteUser: () => notImpl("analytics.management.deleteUser")
|
|
919
|
-
}
|
|
920
|
-
};
|
|
921
|
-
const links = {
|
|
922
|
-
create: () => notImpl("links.create"),
|
|
923
|
-
list: () => notImpl("links.list"),
|
|
924
|
-
get: () => notImpl("links.get"),
|
|
925
|
-
update: () => notImpl("links.update"),
|
|
926
|
-
delete: () => notImpl("links.delete"),
|
|
927
|
-
analytics: () => notImpl("links.analytics"),
|
|
928
|
-
qrCode: () => notImpl("links.qrCode"),
|
|
929
|
-
match: () => notImpl("links.match")
|
|
930
|
-
};
|
|
931
|
-
return {
|
|
932
|
-
auth,
|
|
933
|
-
storage,
|
|
934
|
-
docs,
|
|
935
|
-
realtime,
|
|
936
|
-
functions,
|
|
937
|
-
flags,
|
|
938
|
-
notifications,
|
|
939
|
-
analytics,
|
|
940
|
-
links
|
|
941
|
-
};
|
|
942
|
-
}
|
|
943
|
-
function createMockPurchases() {
|
|
944
|
-
return {
|
|
945
|
-
resolveSubject: async ({ userRef }) => ({ subjectId: `psj_test_${userRef}` }),
|
|
946
|
-
require: async () => void 0,
|
|
947
|
-
withSpend: async (_subjectId, _key, _opts, handler) => handler()
|
|
948
|
-
};
|
|
949
|
-
}
|
|
950
|
-
var NULL_CLIENT_INFO = {
|
|
951
|
-
sdkVersion: null,
|
|
952
|
-
appVersion: null,
|
|
953
|
-
platform: null,
|
|
954
|
-
osVersion: null
|
|
955
|
-
};
|
|
956
|
-
function createTestContext(options = {}) {
|
|
957
|
-
const logs = [];
|
|
958
|
-
const db = createMockDB();
|
|
959
|
-
if (options.db?.seed) {
|
|
960
|
-
for (const [table, data] of Object.entries(options.db.seed)) {
|
|
961
|
-
db.seed(table, data);
|
|
962
|
-
}
|
|
113
|
+
var configured = null;
|
|
114
|
+
var api = new Proxy({}, {
|
|
115
|
+
get(_target, prop) {
|
|
116
|
+
configured ??= createTestApi({
|
|
117
|
+
baseUrl: process.env.PALBASE_TEST_BASE_URL ?? "",
|
|
118
|
+
apiKey: process.env.PALBASE_TEST_API_KEY ?? "",
|
|
119
|
+
candidateToken: process.env.PALBASE_TEST_CANDIDATE ?? ""
|
|
120
|
+
});
|
|
121
|
+
return Reflect.get(configured, prop, configured);
|
|
963
122
|
}
|
|
964
|
-
|
|
965
|
-
const cache = createMockCache();
|
|
966
|
-
const moduleClients = createMockModuleClients();
|
|
967
|
-
__setRuntime({
|
|
968
|
-
Database: db,
|
|
969
|
-
Documents: moduleClients.docs,
|
|
970
|
-
Storage: moduleClients.storage,
|
|
971
|
-
Cache: cache,
|
|
972
|
-
Log: log,
|
|
973
|
-
Notifications: moduleClients.notifications,
|
|
974
|
-
Flags: moduleClients.flags,
|
|
975
|
-
Realtime: moduleClients.realtime,
|
|
976
|
-
Purchases: createMockPurchases()
|
|
977
|
-
});
|
|
978
|
-
const ctx = {
|
|
979
|
-
input: options.input ?? {},
|
|
980
|
-
params: options.params ?? {},
|
|
981
|
-
query: options.query ?? {},
|
|
982
|
-
headers: options.headers ?? {},
|
|
983
|
-
user: options.user ?? null,
|
|
984
|
-
client: NULL_CLIENT_INFO,
|
|
985
|
-
method: "POST",
|
|
986
|
-
file: null,
|
|
987
|
-
db,
|
|
988
|
-
env: options.env ?? {},
|
|
989
|
-
log,
|
|
990
|
-
cache,
|
|
991
|
-
...moduleClients,
|
|
992
|
-
// Empty errors map in tests by default. Tests that exercise an endpoint's
|
|
993
|
-
// declared errors construct their own throwers; this stub satisfies the
|
|
994
|
-
// PBRequest shape without forcing every test to declare `errors:`.
|
|
995
|
-
errors: {},
|
|
996
|
-
requestId: "req_test_000000000000",
|
|
997
|
-
traceId: "0".repeat(32),
|
|
998
|
-
spanId: "0".repeat(16),
|
|
999
|
-
logs
|
|
1000
|
-
};
|
|
1001
|
-
return ctx;
|
|
1002
|
-
}
|
|
123
|
+
});
|
|
1003
124
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1004
125
|
0 && (module.exports = {
|
|
1005
|
-
|
|
1006
|
-
|
|
126
|
+
TestApiError,
|
|
127
|
+
api,
|
|
128
|
+
createTestApi
|
|
1007
129
|
});
|
|
1008
130
|
//# sourceMappingURL=index.cjs.map
|