@palbase/backend 25.0.4 → 27.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/bin/palbase-backend.cjs +2622 -1012
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +87 -51
- package/dist/bin/palbase-backend.js.map +1 -1
- package/dist/chunk-CGNN2PUH.js +213 -0
- package/dist/chunk-CGNN2PUH.js.map +1 -0
- package/dist/chunk-DRZFQRJI.js +370 -0
- package/dist/chunk-DRZFQRJI.js.map +1 -0
- package/dist/chunk-GYK6QYS4.js +708 -0
- package/dist/chunk-GYK6QYS4.js.map +1 -0
- package/dist/chunk-I3C4PFIW.js +930 -0
- package/dist/chunk-I3C4PFIW.js.map +1 -0
- package/dist/{chunk-AILPKEK5.js → chunk-OO7R25AI.js} +1433 -563
- package/dist/chunk-OO7R25AI.js.map +1 -0
- package/dist/chunk-TS4U7NBD.js +885 -0
- package/dist/chunk-TS4U7NBD.js.map +1 -0
- package/dist/{chunk-YOY5DFQS.js → chunk-TWX6JTGJ.js} +76 -34
- package/dist/{chunk-YOY5DFQS.js.map → chunk-TWX6JTGJ.js.map} +1 -1
- package/dist/{chunk-35PNTIRN.js → chunk-VVMJEVQP.js} +63 -162
- package/dist/chunk-VVMJEVQP.js.map +1 -0
- package/dist/{chunk-7D4SUZUM.js → chunk-VXPNPVAG.js} +3 -1
- package/dist/db/env.cjs.map +1 -1
- package/dist/db/env.d.cts +2 -2
- package/dist/db/env.d.ts +2 -2
- package/dist/db/index.cjs +754 -344
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +7 -4
- package/dist/engine/index.cjs +2562 -981
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +6 -6
- package/dist/engine/index.d.ts +6 -6
- package/dist/engine/index.js +7 -6
- package/dist/index-BrvvxSpn.d.ts +4844 -0
- package/dist/index-Bve7BBTL.d.cts +4844 -0
- package/dist/{index-CUomTA3e.d.ts → index-NuzRCuxe.d.ts} +171 -296
- package/dist/{index-ClpDeSos.d.cts → index-VtToZmUm.d.cts} +171 -296
- package/dist/index.cjs +2694 -1169
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -19
- package/dist/index.d.ts +165 -19
- package/dist/index.js +738 -477
- package/dist/index.js.map +1 -1
- package/dist/module-Dl1KFVtc.d.cts +54 -0
- package/dist/module-Dl1KFVtc.d.ts +54 -0
- package/dist/openapi/index.cjs +1330 -484
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.d.cts +4 -2
- package/dist/openapi/index.d.ts +4 -2
- package/dist/openapi/index.js +1264 -474
- package/dist/openapi/index.js.map +1 -1
- package/dist/{registry-dZZ5JKYg.d.ts → registry-B0eyOF9x.d.ts} +1 -1
- package/dist/{registry-CC0WBQq6.d.cts → registry-Bk9_rbNd.d.cts} +1 -1
- package/dist/stack.cjs.map +1 -1
- package/dist/test/index.cjs +705 -141
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +30 -4
- package/dist/test/index.d.ts +30 -4
- package/dist/test/index.js +472 -124
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +34 -18
- package/docs/auth.md +1 -1
- package/docs/background.md +2 -2
- package/docs/database.md +221 -50
- package/docs/endpoints.md +3 -4
- package/docs/events.md +3 -3
- package/docs/getting-started.md +1 -1
- package/docs/llms-full.txt +402 -117
- package/docs/migrations.md +2 -2
- package/docs/schema.md +19 -10
- package/docs/services.md +116 -26
- package/package.json +8 -4
- package/stager/generics.js +205 -0
- package/stager/stage.js +39 -3
- package/template/AGENTS.md +110 -70
- package/template/db/public.ts +1 -1
- package/template/{controllers → modules/health}/health.controller.ts +1 -1
- package/template/modules/health/health.module.ts +24 -0
- package/template/modules/notes/note.service.test.ts +49 -0
- package/template/modules/notes/note.service.ts +108 -0
- package/template/{controllers → modules/notes}/notes.controller.ts +17 -11
- package/template/modules/notes/notes.module.ts +37 -0
- package/template/package.json +5 -3
- package/template/scripts/test.sh +33 -0
- package/template/tsconfig.json +29 -30
- package/dist/chunk-35PNTIRN.js.map +0 -1
- package/dist/chunk-AILPKEK5.js.map +0 -1
- package/dist/chunk-CJSKYY76.js +0 -627
- package/dist/chunk-CJSKYY76.js.map +0 -1
- package/dist/chunk-CRQKCRGF.js +0 -276
- package/dist/chunk-CRQKCRGF.js.map +0 -1
- package/dist/chunk-G4R6BTLV.js +0 -662
- package/dist/chunk-G4R6BTLV.js.map +0 -1
- package/dist/chunk-XABBC7JP.js +0 -55
- package/dist/chunk-XABBC7JP.js.map +0 -1
- package/dist/endpoint-CTEHhb7A.d.ts +0 -2386
- package/dist/endpoint-DYHMo6cC.d.cts +0 -2386
- package/dist/index-CW21M9Z3.d.ts +0 -1222
- package/dist/index-CmBK76nx.d.cts +0 -1222
- package/template/services/note.service.test.ts +0 -45
- package/template/services/note.service.ts +0 -74
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-VXPNPVAG.js.map} +0 -0
- /package/template/{models/notes → modules/notes/dto}/create.ts +0 -0
package/dist/test/index.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
assertNoExpressionHandles,
|
|
2
3
|
assertUsableFilter,
|
|
3
|
-
assertUsableWriteValues
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
assertUsableWriteValues,
|
|
5
|
+
columnExprOf,
|
|
6
|
+
isColRef,
|
|
7
|
+
isSqlFragment
|
|
8
|
+
} from "../chunk-GYK6QYS4.js";
|
|
9
|
+
import {
|
|
10
|
+
__name
|
|
11
|
+
} from "../chunk-VXPNPVAG.js";
|
|
6
12
|
|
|
7
13
|
// ../core/dist/index.js
|
|
8
14
|
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -17,8 +23,13 @@ function asPowChallenge(details) {
|
|
|
17
23
|
const { id, prefix, difficulty } = c;
|
|
18
24
|
if (typeof id !== "string" || typeof prefix !== "string") return null;
|
|
19
25
|
if (typeof difficulty !== "number" || !Number.isInteger(difficulty) || difficulty < 0) return null;
|
|
20
|
-
return {
|
|
26
|
+
return {
|
|
27
|
+
id,
|
|
28
|
+
prefix,
|
|
29
|
+
difficulty
|
|
30
|
+
};
|
|
21
31
|
}
|
|
32
|
+
__name(asPowChallenge, "asPowChallenge");
|
|
22
33
|
var encoder = new TextEncoder();
|
|
23
34
|
var hasher = null;
|
|
24
35
|
async function digester() {
|
|
@@ -33,16 +44,17 @@ async function digester() {
|
|
|
33
44
|
);
|
|
34
45
|
if (typeof mod.createHash === "function") {
|
|
35
46
|
const createHash = mod.createHash;
|
|
36
|
-
hasher = (input) => new Uint8Array(createHash("sha256").update(input).digest());
|
|
47
|
+
hasher = /* @__PURE__ */ __name((input) => new Uint8Array(createHash("sha256").update(input).digest()), "hasher");
|
|
37
48
|
return hasher;
|
|
38
49
|
}
|
|
39
50
|
} catch {
|
|
40
51
|
}
|
|
41
52
|
}
|
|
42
|
-
hasher = async (input) => new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(input)));
|
|
53
|
+
hasher = /* @__PURE__ */ __name(async (input) => new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(input))), "hasher");
|
|
43
54
|
return hasher;
|
|
44
55
|
}
|
|
45
|
-
|
|
56
|
+
__name(digester, "digester");
|
|
57
|
+
var now = /* @__PURE__ */ __name(() => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now(), "now");
|
|
46
58
|
function leadingZeroBits(hash) {
|
|
47
59
|
let bits = 0;
|
|
48
60
|
for (const byte of hash) {
|
|
@@ -54,18 +66,18 @@ function leadingZeroBits(hash) {
|
|
|
54
66
|
}
|
|
55
67
|
return bits;
|
|
56
68
|
}
|
|
69
|
+
__name(leadingZeroBits, "leadingZeroBits");
|
|
57
70
|
var MAX_POW_DIFFICULTY = 24;
|
|
58
71
|
function powBudget(difficulty) {
|
|
59
72
|
return 8 * 2 ** difficulty;
|
|
60
73
|
}
|
|
74
|
+
__name(powBudget, "powBudget");
|
|
61
75
|
var POW_TIME_BUDGET_MS = 12e4;
|
|
62
76
|
var CALIBRATION_WARMUP = 1024;
|
|
63
77
|
var CALIBRATION_END = 9216;
|
|
64
78
|
async function solvePowChallenge(challenge, maxIterations = powBudget(challenge.difficulty), signal) {
|
|
65
79
|
if (challenge.difficulty > MAX_POW_DIFFICULTY) {
|
|
66
|
-
throw new Error(
|
|
67
|
-
`proof-of-work: refusing difficulty ${challenge.difficulty}; this client attempts at most ${MAX_POW_DIFFICULTY}, which is the highest a Palbase stack issues`
|
|
68
|
-
);
|
|
80
|
+
throw new Error(`proof-of-work: refusing difficulty ${challenge.difficulty}; this client attempts at most ${MAX_POW_DIFFICULTY}, which is the highest a Palbase stack issues`);
|
|
69
81
|
}
|
|
70
82
|
const digest = await digester();
|
|
71
83
|
let warmedAt = 0;
|
|
@@ -77,9 +89,7 @@ async function solvePowChallenge(challenge, maxIterations = powBudget(challenge.
|
|
|
77
89
|
throw new DOMException("proof-of-work solve aborted", "AbortError");
|
|
78
90
|
}
|
|
79
91
|
if (now() > deadline) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
`proof-of-work: gave up on difficulty ${challenge.difficulty} after ${POW_TIME_BUDGET_MS / 1e3}s and ${nonce.toLocaleString()} attempts \u2014 the tail this run drew is longer than the allowance`
|
|
82
|
-
);
|
|
92
|
+
throw new Error(`proof-of-work: gave up on difficulty ${challenge.difficulty} after ${POW_TIME_BUDGET_MS / 1e3}s and ${nonce.toLocaleString()} attempts \u2014 the tail this run drew is longer than the allowance`);
|
|
83
93
|
}
|
|
84
94
|
}
|
|
85
95
|
if (nonce === CALIBRATION_WARMUP) {
|
|
@@ -91,9 +101,7 @@ async function solvePowChallenge(challenge, maxIterations = powBudget(challenge.
|
|
|
91
101
|
const rate = (CALIBRATION_END - CALIBRATION_WARMUP) / (elapsed / 1e3);
|
|
92
102
|
const expectedMs = 2 ** challenge.difficulty / rate * 1e3;
|
|
93
103
|
if (expectedMs > POW_TIME_BUDGET_MS) {
|
|
94
|
-
throw new Error(
|
|
95
|
-
`proof-of-work: difficulty ${challenge.difficulty} needs about ${Math.round(expectedMs / 1e3)}s here (${Math.round(rate).toLocaleString()} digests/s) and this client allows ${POW_TIME_BUDGET_MS / 1e3}s; refusing before spending the time rather than after`
|
|
96
|
-
);
|
|
104
|
+
throw new Error(`proof-of-work: difficulty ${challenge.difficulty} needs about ${Math.round(expectedMs / 1e3)}s here (${Math.round(rate).toLocaleString()} digests/s) and this client allows ${POW_TIME_BUDGET_MS / 1e3}s; refusing before spending the time rather than after`);
|
|
97
105
|
}
|
|
98
106
|
deadline = now() + (POW_TIME_BUDGET_MS - (now() - warmedAt));
|
|
99
107
|
}
|
|
@@ -105,13 +113,15 @@ async function solvePowChallenge(challenge, maxIterations = powBudget(challenge.
|
|
|
105
113
|
};
|
|
106
114
|
}
|
|
107
115
|
}
|
|
108
|
-
throw new Error(
|
|
109
|
-
`proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`
|
|
110
|
-
);
|
|
116
|
+
throw new Error(`proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`);
|
|
111
117
|
}
|
|
118
|
+
__name(solvePowChallenge, "solvePowChallenge");
|
|
112
119
|
|
|
113
120
|
// src/test/api.ts
|
|
114
121
|
var TestApiError = class extends Error {
|
|
122
|
+
static {
|
|
123
|
+
__name(this, "TestApiError");
|
|
124
|
+
}
|
|
115
125
|
status;
|
|
116
126
|
error;
|
|
117
127
|
/** Payload of an error your code threw — `new BadRequest({ fields })` arrives here. */
|
|
@@ -131,12 +141,11 @@ var TestApiError = class extends Error {
|
|
|
131
141
|
};
|
|
132
142
|
function required(value, envName) {
|
|
133
143
|
if (!value) {
|
|
134
|
-
throw new Error(
|
|
135
|
-
`${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.`
|
|
136
|
-
);
|
|
144
|
+
throw new Error(`${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.`);
|
|
137
145
|
}
|
|
138
146
|
return value;
|
|
139
147
|
}
|
|
148
|
+
__name(required, "required");
|
|
140
149
|
function isLocalTarget(baseUrl) {
|
|
141
150
|
try {
|
|
142
151
|
const { hostname } = new URL(baseUrl);
|
|
@@ -145,6 +154,7 @@ function isLocalTarget(baseUrl) {
|
|
|
145
154
|
return false;
|
|
146
155
|
}
|
|
147
156
|
}
|
|
157
|
+
__name(isLocalTarget, "isLocalTarget");
|
|
148
158
|
function secondsUntilExpiry(token) {
|
|
149
159
|
const body = token.split(".")[1];
|
|
150
160
|
if (!body) return null;
|
|
@@ -155,6 +165,7 @@ function secondsUntilExpiry(token) {
|
|
|
155
165
|
return null;
|
|
156
166
|
}
|
|
157
167
|
}
|
|
168
|
+
__name(secondsUntilExpiry, "secondsUntilExpiry");
|
|
158
169
|
function createTestApi(config) {
|
|
159
170
|
const baseUrl = required(config.baseUrl, "PALBASE_TEST_BASE_URL").replace(/\/$/, "");
|
|
160
171
|
const apiKey = required(config.apiKey, "PALBASE_TEST_API_KEY");
|
|
@@ -169,7 +180,9 @@ function createTestApi(config) {
|
|
|
169
180
|
// Selects the release under test. Omit it and the gateway serves the LIVE
|
|
170
181
|
// one, which would make the whole suite grade the wrong code. Absent only
|
|
171
182
|
// against a local stack, which has a single version.
|
|
172
|
-
...candidateToken ? {
|
|
183
|
+
...candidateToken ? {
|
|
184
|
+
"x-palbase-candidate": candidateToken
|
|
185
|
+
} : {},
|
|
173
186
|
...opts.headers
|
|
174
187
|
};
|
|
175
188
|
if (bearer) headers.authorization = `Bearer ${bearer}`;
|
|
@@ -182,7 +195,12 @@ function createTestApi(config) {
|
|
|
182
195
|
});
|
|
183
196
|
const text = await res.text();
|
|
184
197
|
const parsed = text ? safeParse(text) : void 0;
|
|
185
|
-
requests.push({
|
|
198
|
+
requests.push({
|
|
199
|
+
method,
|
|
200
|
+
path,
|
|
201
|
+
status: res.status,
|
|
202
|
+
ms: Date.now() - startedAt
|
|
203
|
+
});
|
|
186
204
|
if (!res.ok) {
|
|
187
205
|
if (res.status === 401 && bearer) {
|
|
188
206
|
const left = secondsUntilExpiry(bearer);
|
|
@@ -197,41 +215,39 @@ function createTestApi(config) {
|
|
|
197
215
|
}
|
|
198
216
|
return parsed;
|
|
199
217
|
}
|
|
218
|
+
__name(call, "call");
|
|
200
219
|
return {
|
|
201
220
|
requests,
|
|
202
|
-
get: (path, opts) => call("GET", path, void 0, opts),
|
|
203
|
-
post: (path, body, opts) => call("POST", path, body, opts),
|
|
204
|
-
patch: (path, body, opts) => call("PATCH", path, body, opts),
|
|
205
|
-
put: (path, body, opts) => call("PUT", path, body, opts),
|
|
206
|
-
delete: (path, opts) => call("DELETE", path, void 0, opts),
|
|
207
|
-
query: (path, body, opts) => call("QUERY", path, body, opts),
|
|
221
|
+
get: /* @__PURE__ */ __name((path, opts) => call("GET", path, void 0, opts), "get"),
|
|
222
|
+
post: /* @__PURE__ */ __name((path, body, opts) => call("POST", path, body, opts), "post"),
|
|
223
|
+
patch: /* @__PURE__ */ __name((path, body, opts) => call("PATCH", path, body, opts), "patch"),
|
|
224
|
+
put: /* @__PURE__ */ __name((path, body, opts) => call("PUT", path, body, opts), "put"),
|
|
225
|
+
delete: /* @__PURE__ */ __name((path, opts) => call("DELETE", path, void 0, opts), "delete"),
|
|
226
|
+
query: /* @__PURE__ */ __name((path, body, opts) => call("QUERY", path, body, opts), "query"),
|
|
208
227
|
async signInAs(name) {
|
|
209
228
|
const identity = (config.identities ?? {})[name];
|
|
210
229
|
if (!identity) {
|
|
211
230
|
const declared = Object.keys(config.identities ?? {});
|
|
212
|
-
throw new Error(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
", and this run has none \u2014 either your config declares no users or the deploy could not mint them; the deploy log says which"
|
|
219
|
-
))
|
|
220
|
-
);
|
|
231
|
+
throw new Error(`no test identity named ${JSON.stringify(name)} \u2014 the deploy mints one per user declared in config/test-users.ts` + (declared.length ? `; this run has: ${declared.join(", ")}` : (
|
|
232
|
+
// indistinguishable, and blaming the customer's config for a
|
|
233
|
+
// platform failure sends them to look in the wrong file. The
|
|
234
|
+
// deploy log names which one it was.
|
|
235
|
+
", and this run has none \u2014 either your config declares no users or the deploy could not mint them; the deploy log says which"
|
|
236
|
+
)));
|
|
221
237
|
}
|
|
222
238
|
if (identity.accessToken) {
|
|
223
239
|
bearer = identity.accessToken;
|
|
224
|
-
return {
|
|
240
|
+
return {
|
|
241
|
+
id: identity.id ?? "",
|
|
242
|
+
email: identity.email
|
|
243
|
+
};
|
|
225
244
|
}
|
|
226
245
|
return this.signIn(identity);
|
|
227
246
|
},
|
|
228
247
|
async signIn(credentials) {
|
|
229
|
-
const attempt = async (extra) => call(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
credentials,
|
|
233
|
-
extra ? { headers: extra } : {}
|
|
234
|
-
);
|
|
248
|
+
const attempt = /* @__PURE__ */ __name(async (extra) => call("POST", "/auth/login", credentials, extra ? {
|
|
249
|
+
headers: extra
|
|
250
|
+
} : {}), "attempt");
|
|
235
251
|
let result;
|
|
236
252
|
try {
|
|
237
253
|
result = await attempt();
|
|
@@ -242,7 +258,9 @@ function createTestApi(config) {
|
|
|
242
258
|
result = await attempt(await solvePowChallenge(challenge));
|
|
243
259
|
}
|
|
244
260
|
bearer = result.access_token;
|
|
245
|
-
return result.user ?? {
|
|
261
|
+
return result.user ?? {
|
|
262
|
+
id: ""
|
|
263
|
+
};
|
|
246
264
|
},
|
|
247
265
|
async signOut() {
|
|
248
266
|
await call("POST", "/auth/logout", void 0);
|
|
@@ -253,6 +271,7 @@ function createTestApi(config) {
|
|
|
253
271
|
}
|
|
254
272
|
};
|
|
255
273
|
}
|
|
274
|
+
__name(createTestApi, "createTestApi");
|
|
256
275
|
function parseIdentities(raw) {
|
|
257
276
|
if (!raw) return {};
|
|
258
277
|
try {
|
|
@@ -261,6 +280,7 @@ function parseIdentities(raw) {
|
|
|
261
280
|
return {};
|
|
262
281
|
}
|
|
263
282
|
}
|
|
283
|
+
__name(parseIdentities, "parseIdentities");
|
|
264
284
|
function safeParse(text) {
|
|
265
285
|
try {
|
|
266
286
|
return JSON.parse(text);
|
|
@@ -268,6 +288,7 @@ function safeParse(text) {
|
|
|
268
288
|
return text;
|
|
269
289
|
}
|
|
270
290
|
}
|
|
291
|
+
__name(safeParse, "safeParse");
|
|
271
292
|
var configured = null;
|
|
272
293
|
var api = new Proxy({}, {
|
|
273
294
|
get(_target, prop) {
|
|
@@ -281,7 +302,168 @@ var api = new Proxy({}, {
|
|
|
281
302
|
}
|
|
282
303
|
});
|
|
283
304
|
|
|
305
|
+
// src/test/container.ts
|
|
306
|
+
import "reflect-metadata";
|
|
307
|
+
function isolated() {
|
|
308
|
+
const over = /* @__PURE__ */ new Map();
|
|
309
|
+
const local = /* @__PURE__ */ new Map();
|
|
310
|
+
const make = /* @__PURE__ */ __name((c) => {
|
|
311
|
+
if (over.has(c)) return over.get(c);
|
|
312
|
+
const hit = local.get(c);
|
|
313
|
+
if (hit !== void 0) return hit;
|
|
314
|
+
const meta = Reflect.getMetadata("design:paramtypes", c) ?? [];
|
|
315
|
+
const inst = new c(...meta.map((d) => make(d)));
|
|
316
|
+
local.set(c, inst);
|
|
317
|
+
return inst;
|
|
318
|
+
}, "make");
|
|
319
|
+
const api2 = {
|
|
320
|
+
with(t, v) {
|
|
321
|
+
over.set(t, v);
|
|
322
|
+
return api2;
|
|
323
|
+
},
|
|
324
|
+
get(t) {
|
|
325
|
+
return make(t);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
return api2;
|
|
329
|
+
}
|
|
330
|
+
__name(isolated, "isolated");
|
|
331
|
+
|
|
284
332
|
// src/__tests__/helpers/mock-db.ts
|
|
333
|
+
function refuseFragment(caller, table, where) {
|
|
334
|
+
if (isSqlFragment(where)) {
|
|
335
|
+
throw new Error(`${caller}(${table}): fakeDatabase bir sqlFragment'i de\u011Ferlendiremez \u2014 sahte depo SQL \xE7al\u0131\u015Ft\u0131rmaz. Filtreyi tipli filtre diliyle kurun (gt/gte/lt/lte/neq/in/contains/isNull, OR/AND/NOT), ya da bu testi ger\xE7ek bir Postgres'e kar\u015F\u0131 yaz\u0131n.`);
|
|
336
|
+
}
|
|
337
|
+
if (where === null || typeof where !== "object") return;
|
|
338
|
+
for (const [k, v] of Object.entries(where)) {
|
|
339
|
+
if (k === "OR" || k === "AND") {
|
|
340
|
+
for (const branch of Array.isArray(v) ? v : []) refuseFragment(caller, table, branch);
|
|
341
|
+
} else if (k === "NOT") {
|
|
342
|
+
refuseFragment(caller, table, v);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
__name(refuseFragment, "refuseFragment");
|
|
347
|
+
function addDecimal(cell, by, sign) {
|
|
348
|
+
if (cell === null || cell === void 0) return null;
|
|
349
|
+
if (typeof cell === "number" && typeof by === "number") return cell + sign * by;
|
|
350
|
+
const a = String(cell);
|
|
351
|
+
const b = String(by);
|
|
352
|
+
const parse = /* @__PURE__ */ __name((x) => {
|
|
353
|
+
const m = /^([+-]?)(\d*)(?:\.(\d*))?$/.exec(x.trim());
|
|
354
|
+
if (m === null || m[2] === "" && (m[3] ?? "") === "") return null;
|
|
355
|
+
const frac = m[3] ?? "";
|
|
356
|
+
const unit = BigInt(`${m[1] === "-" ? "-" : ""}${m[2] === "" ? "0" : m[2]}${frac}`);
|
|
357
|
+
return {
|
|
358
|
+
unit,
|
|
359
|
+
scale: frac.length
|
|
360
|
+
};
|
|
361
|
+
}, "parse");
|
|
362
|
+
const pa = parse(a);
|
|
363
|
+
const pb = parse(b);
|
|
364
|
+
if (pa === null || pb === null) {
|
|
365
|
+
throw new Error(`fakeDatabase: increment()/decrement() say\u0131sal olmayan bir de\u011Fere uyguland\u0131 ("${a}") \u2014 Postgres bunu "operator does not exist" ile reddeder.`);
|
|
366
|
+
}
|
|
367
|
+
const scale = Math.max(pa.scale, pb.scale);
|
|
368
|
+
const lift = /* @__PURE__ */ __name((v) => v.unit * 10n ** BigInt(scale - v.scale), "lift");
|
|
369
|
+
const total = lift(pa) + BigInt(sign) * lift(pb);
|
|
370
|
+
if (scale === 0) return typeof cell === "number" ? Number(total) : total.toString();
|
|
371
|
+
const neg = total < 0n;
|
|
372
|
+
const digits = (neg ? -total : total).toString().padStart(scale + 1, "0");
|
|
373
|
+
const out = `${neg ? "-" : ""}${digits.slice(0, -scale)}.${digits.slice(-scale)}`;
|
|
374
|
+
return typeof cell === "number" ? Number(out) : out;
|
|
375
|
+
}
|
|
376
|
+
__name(addDecimal, "addDecimal");
|
|
377
|
+
function rowMatchesFilter(caller, table, row, f) {
|
|
378
|
+
return Object.entries(f).every(([k, c]) => {
|
|
379
|
+
if (k === "OR") return c.some((b) => rowMatchesFilter(caller, table, row, b));
|
|
380
|
+
if (k === "AND") return c.every((b) => rowMatchesFilter(caller, table, row, b));
|
|
381
|
+
if (k === "NOT") return !rowMatchesFilter(caller, table, row, c);
|
|
382
|
+
if (k === "has") {
|
|
383
|
+
throw new Error(`${caller}(${table}): fakeDatabase bir \`has\` filtresini \xE7\xF6zemez \u2014 ili\u015Fki grafi\u011Fi bildirimden t\xFCretiliyor ve sahte deponun bildirimi yok. Bu testi ger\xE7ek bir Postgres'e kar\u015F\u0131 yaz\u0131n, ya da ili\u015Fkiyi filtrede A\xC7IK\xC7A kurun (\xF6nce ili\u015Fki tablosunu okuyup { id: { in: [...] } } ile s\xFCz\xFCn).`);
|
|
384
|
+
}
|
|
385
|
+
return matchesCell(caller, table, row, k, c);
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
__name(rowMatchesFilter, "rowMatchesFilter");
|
|
389
|
+
function cmp(a, b, op) {
|
|
390
|
+
if (a === null || a === void 0 || b === null || b === void 0) return false;
|
|
391
|
+
const l = a instanceof Date ? a.getTime() : a;
|
|
392
|
+
const r = b instanceof Date ? b.getTime() : b;
|
|
393
|
+
switch (op) {
|
|
394
|
+
case "eq":
|
|
395
|
+
return l === r;
|
|
396
|
+
case "neq":
|
|
397
|
+
return l !== r;
|
|
398
|
+
case "gt":
|
|
399
|
+
return l > r;
|
|
400
|
+
case "gte":
|
|
401
|
+
return l >= r;
|
|
402
|
+
case "lt":
|
|
403
|
+
return l < r;
|
|
404
|
+
case "lte":
|
|
405
|
+
return l <= r;
|
|
406
|
+
default:
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
__name(cmp, "cmp");
|
|
411
|
+
function matchesCell(caller, table, row, key, cond) {
|
|
412
|
+
if (isColRef(cond)) return cmp(row[key], row[cond.$col], "eq");
|
|
413
|
+
if (cond !== null && typeof cond === "object" && !Array.isArray(cond)) {
|
|
414
|
+
return Object.entries(cond).every(([op, v]) => {
|
|
415
|
+
const cell = row[key];
|
|
416
|
+
if (isColRef(v)) {
|
|
417
|
+
if (![
|
|
418
|
+
"neq",
|
|
419
|
+
"gt",
|
|
420
|
+
"gte",
|
|
421
|
+
"lt",
|
|
422
|
+
"lte"
|
|
423
|
+
].includes(op)) {
|
|
424
|
+
throw new Error(`${caller}(${table}): where.${key}.${op} col() ile kullan\u0131lamaz`);
|
|
425
|
+
}
|
|
426
|
+
return cmp(cell, row[v.$col], op);
|
|
427
|
+
}
|
|
428
|
+
switch (op) {
|
|
429
|
+
case "in":
|
|
430
|
+
if (!Array.isArray(v)) throw new Error(`${caller}(${table}): where.${key}.in bir dizi olmal\u0131`);
|
|
431
|
+
if (v.some(isColRef)) {
|
|
432
|
+
throw new Error(`${caller}(${table}): where.${key}.in col() ile kullan\u0131lamaz \u2014 kolon kar\u015F\u0131la\u015Ft\u0131rmas\u0131 i\xE7in gt/gte/lt/lte/neq kullan\u0131n`);
|
|
433
|
+
}
|
|
434
|
+
return v.includes(cell);
|
|
435
|
+
case "neq":
|
|
436
|
+
case "gt":
|
|
437
|
+
case "gte":
|
|
438
|
+
case "lt":
|
|
439
|
+
case "lte":
|
|
440
|
+
return cmp(cell, v, op);
|
|
441
|
+
// K1 metin operatörleri (FR-005) ve null testi (FR-006). Bunlar BURADA
|
|
442
|
+
// da olmak zorunda: guard onları KABUL ettiği anda fake sessizce yanlış
|
|
443
|
+
// cevap verir ve yazarın testi, üretimde farklı davranan koda karşı
|
|
444
|
+
// yeşil verirdi (input-guards.ts'in uyardığı tam sınıf).
|
|
445
|
+
case "isNull":
|
|
446
|
+
if (typeof v !== "boolean") throw new Error(`${caller}(${table}): where.${key}.isNull bir boolean olmal\u0131`);
|
|
447
|
+
return v ? cell == null : cell != null;
|
|
448
|
+
case "contains":
|
|
449
|
+
case "icontains":
|
|
450
|
+
case "startsWith":
|
|
451
|
+
case "endsWith": {
|
|
452
|
+
if (typeof v !== "string") throw new Error(`${caller}(${table}): where.${key}.${op} bir string olmal\u0131`);
|
|
453
|
+
if (typeof cell !== "string") return false;
|
|
454
|
+
if (op === "contains") return cell.includes(v);
|
|
455
|
+
if (op === "icontains") return cell.toLowerCase().includes(v.toLowerCase());
|
|
456
|
+
if (op === "startsWith") return cell.startsWith(v);
|
|
457
|
+
return cell.endsWith(v);
|
|
458
|
+
}
|
|
459
|
+
default:
|
|
460
|
+
throw new Error(`${caller}(${table}): where.${key} bilinmeyen operat\xF6r "${op}"`);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
return row[key] === cond;
|
|
465
|
+
}
|
|
466
|
+
__name(matchesCell, "matchesCell");
|
|
285
467
|
function createMockDB() {
|
|
286
468
|
const store = /* @__PURE__ */ new Map();
|
|
287
469
|
const tracked = {
|
|
@@ -297,11 +479,15 @@ function createMockDB() {
|
|
|
297
479
|
}
|
|
298
480
|
return rows;
|
|
299
481
|
}
|
|
482
|
+
__name(rowsOf, "rowsOf");
|
|
300
483
|
function track(map, table, row) {
|
|
301
484
|
const list = map.get(table);
|
|
302
485
|
if (list) list.push(row);
|
|
303
|
-
else map.set(table, [
|
|
486
|
+
else map.set(table, [
|
|
487
|
+
row
|
|
488
|
+
]);
|
|
304
489
|
}
|
|
490
|
+
__name(track, "track");
|
|
305
491
|
const ops = {
|
|
306
492
|
// The bulk ops and count run against the SAME in-memory store the direct
|
|
307
493
|
// ops write to, so a test that writes three rows and counts them gets 3 —
|
|
@@ -310,26 +496,38 @@ function createMockDB() {
|
|
|
310
496
|
async updateMany(table, where, set) {
|
|
311
497
|
if (Object.keys(where).length === 0) throw new Error(`updateMany(${table}): bo\u015F filtre`);
|
|
312
498
|
assertUsableFilter("updateMany", table, where);
|
|
499
|
+
refuseFragment("updateMany", table, where);
|
|
313
500
|
assertUsableWriteValues("updateMany", table, Object.keys(set), set);
|
|
314
|
-
const hit = (store.get(table) ?? []).filter(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
501
|
+
const hit = (store.get(table) ?? []).filter((r) => rowMatchesFilter("updateMany", table, r, where));
|
|
502
|
+
for (const row of hit) {
|
|
503
|
+
for (const [k, v] of Object.entries(set)) {
|
|
504
|
+
const expr = columnExprOf(v);
|
|
505
|
+
if (expr !== null && expr.fn === "now") {
|
|
506
|
+
row[k] = /* @__PURE__ */ new Date();
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (expr !== null) {
|
|
510
|
+
row[k] = addDecimal(row[k], expr.by, expr.fn === "inc" ? 1 : -1);
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
row[k] = v;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
318
516
|
return hit;
|
|
319
517
|
},
|
|
320
518
|
async deleteMany(table, where) {
|
|
321
519
|
if (Object.keys(where).length === 0) throw new Error(`deleteMany(${table}): bo\u015F filtre`);
|
|
322
520
|
assertUsableFilter("deleteMany", table, where);
|
|
521
|
+
refuseFragment("deleteMany", table, where);
|
|
323
522
|
const list = store.get(table) ?? [];
|
|
324
|
-
const keep = list.filter((r) => !
|
|
523
|
+
const keep = list.filter((r) => !rowMatchesFilter("deleteMany", table, r, where));
|
|
325
524
|
store.set(table, keep);
|
|
326
525
|
return list.length - keep.length;
|
|
327
526
|
},
|
|
328
527
|
async count(table, where = {}) {
|
|
329
528
|
assertUsableFilter("count", table, where);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
).length;
|
|
529
|
+
refuseFragment("count", table, where);
|
|
530
|
+
return (store.get(table) ?? []).filter((r) => rowMatchesFilter("count", table, r, where)).length;
|
|
333
531
|
},
|
|
334
532
|
async search(_table, _params) {
|
|
335
533
|
return [];
|
|
@@ -344,25 +542,90 @@ function createMockDB() {
|
|
|
344
542
|
return [];
|
|
345
543
|
},
|
|
346
544
|
async supersede(_table, _id, row) {
|
|
347
|
-
return {
|
|
545
|
+
return {
|
|
546
|
+
id: crypto.randomUUID(),
|
|
547
|
+
...row
|
|
548
|
+
};
|
|
348
549
|
},
|
|
349
550
|
async query(_sql, _params) {
|
|
350
551
|
return [];
|
|
351
552
|
},
|
|
352
553
|
async insert(table, data) {
|
|
353
554
|
assertUsableWriteValues("insert", table, Object.keys(data), data);
|
|
354
|
-
|
|
555
|
+
assertNoExpressionHandles("insert", table, Object.keys(data), data);
|
|
556
|
+
const record = {
|
|
557
|
+
id: crypto.randomUUID(),
|
|
558
|
+
...data
|
|
559
|
+
};
|
|
355
560
|
rowsOf(table).push(record);
|
|
356
561
|
track(tracked.inserted, table, record);
|
|
357
562
|
return record;
|
|
358
563
|
},
|
|
564
|
+
/**
|
|
565
|
+
* `claim` — motorla PARİTE (FR-033).
|
|
566
|
+
*
|
|
567
|
+
* Fake bunu sunmasaydı `claim` ile yazılmış bir servis, iskelenin yazarlara
|
|
568
|
+
* önerdiği test yolunda `undefined is not a function` verirdi; sunup da
|
|
569
|
+
* FARKLI davransaydı (ör. hep `inserted: true`) idempotency testi üretimde
|
|
570
|
+
* çalışmayan koda karşı yeşil olurdu.
|
|
571
|
+
*
|
|
572
|
+
* Anahtar ZATEN VARSA var olan satır dönüyor ve hiçbir şey yazılmıyor —
|
|
573
|
+
* motorun 23505 dalının aynısı.
|
|
574
|
+
*/
|
|
575
|
+
/**
|
|
576
|
+
* `lockRows` — sahte depoda kilit YOKTUR, ama çağrı da patlamamalı.
|
|
577
|
+
*
|
|
578
|
+
* Motorla parite burada "aynı SQL" değil, "aynı SÖZLEŞME": boş liste
|
|
579
|
+
* no-op, tekrar edenler tekilleşir, ve PK'sı olmayan tablo adıyla
|
|
580
|
+
* reddedilir. Kilidin kendisi tek işlemli bir sahte depoda anlamsız —
|
|
581
|
+
* ama sözleşmeyi bozan bir çağrı burada da hata almalı.
|
|
582
|
+
*/
|
|
583
|
+
async lockRows(table, ids) {
|
|
584
|
+
if (ids.length === 0) return;
|
|
585
|
+
const rows = store.get(table) ?? [];
|
|
586
|
+
const unique = [
|
|
587
|
+
...new Set(ids)
|
|
588
|
+
].sort();
|
|
589
|
+
const missing = unique.filter((id) => !rows.some((r) => r["id"] === id));
|
|
590
|
+
if (missing.length > 0 && rows.length > 0) {
|
|
591
|
+
throw new Error(`lockRows(${table}): \u015Fu id'ler yok: ${missing.join(", ")} \u2014 kilitlenecek sat\u0131r bulunamad\u0131.`);
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
/** Sahte depoda kilit yok; sözleşme (çağrı patlamaz) korunuyor. */
|
|
595
|
+
async advisoryXactLock(_key) {
|
|
596
|
+
return void 0;
|
|
597
|
+
},
|
|
598
|
+
async claim(table, unique, extra = {}) {
|
|
599
|
+
const keyCols = Object.keys(unique);
|
|
600
|
+
if (keyCols.length === 0) {
|
|
601
|
+
throw new Error(`claim(${table}): benzersiz alan verilmedi. claim, bir anahtar\u0131 sahiplenmektir; anahtar yoksa sahiplenecek bir \u015Fey de yok \u2014 insert(${table}, \u2026) kullan\u0131n.`);
|
|
602
|
+
}
|
|
603
|
+
assertUsableWriteValues("claim", table, keyCols, unique);
|
|
604
|
+
const existing = (store.get(table) ?? []).find((r) => keyCols.every((c) => r[c] === unique[c]));
|
|
605
|
+
if (existing) return {
|
|
606
|
+
inserted: false,
|
|
607
|
+
row: existing
|
|
608
|
+
};
|
|
609
|
+
const record = {
|
|
610
|
+
id: crypto.randomUUID(),
|
|
611
|
+
...unique,
|
|
612
|
+
...extra
|
|
613
|
+
};
|
|
614
|
+
rowsOf(table).push(record);
|
|
615
|
+
track(tracked.inserted, table, record);
|
|
616
|
+
return {
|
|
617
|
+
inserted: true,
|
|
618
|
+
row: record
|
|
619
|
+
};
|
|
620
|
+
},
|
|
359
621
|
// Same semantics the engine's SQL has: match on the conflict columns, update
|
|
360
622
|
// everything else, and return the resulting row either way.
|
|
361
|
-
async
|
|
623
|
+
async put(table, data, opts) {
|
|
362
624
|
if (opts.onConflict.length === 0) {
|
|
363
|
-
throw new Error(`
|
|
625
|
+
throw new Error(`put into ${table}: onConflict en az bir kolon ad\u0131 ister`);
|
|
364
626
|
}
|
|
365
627
|
assertUsableWriteValues("upsert", table, Object.keys(data), data);
|
|
628
|
+
assertNoExpressionHandles("upsert", table, Object.keys(data), data);
|
|
366
629
|
const rows = rowsOf(table);
|
|
367
630
|
const existing = rows.find((r) => opts.onConflict.every((c) => r[c] === data[c]));
|
|
368
631
|
if (existing) {
|
|
@@ -371,16 +634,26 @@ function createMockDB() {
|
|
|
371
634
|
}
|
|
372
635
|
return existing;
|
|
373
636
|
}
|
|
374
|
-
const record = {
|
|
637
|
+
const record = {
|
|
638
|
+
id: crypto.randomUUID(),
|
|
639
|
+
...data
|
|
640
|
+
};
|
|
375
641
|
rows.push(record);
|
|
376
642
|
track(tracked.inserted, table, record);
|
|
377
643
|
return record;
|
|
378
644
|
},
|
|
379
645
|
async update(table, id, data) {
|
|
380
646
|
assertUsableWriteValues("update", table, Object.keys(data), data);
|
|
647
|
+
assertNoExpressionHandles("update", table, Object.keys(data), data);
|
|
381
648
|
const rows = store.get(table) ?? [];
|
|
382
649
|
const idx = rows.findIndex((r) => r["id"] === id);
|
|
383
|
-
const updated = idx >= 0 ? {
|
|
650
|
+
const updated = idx >= 0 ? {
|
|
651
|
+
...rows[idx],
|
|
652
|
+
...data
|
|
653
|
+
} : {
|
|
654
|
+
id,
|
|
655
|
+
...data
|
|
656
|
+
};
|
|
384
657
|
if (idx >= 0) {
|
|
385
658
|
rows[idx] = updated;
|
|
386
659
|
}
|
|
@@ -393,7 +666,9 @@ function createMockDB() {
|
|
|
393
666
|
if (idx >= 0) rows.splice(idx, 1);
|
|
394
667
|
const list = tracked.deleted.get(table);
|
|
395
668
|
if (list) list.push(id);
|
|
396
|
-
else tracked.deleted.set(table, [
|
|
669
|
+
else tracked.deleted.set(table, [
|
|
670
|
+
id
|
|
671
|
+
]);
|
|
397
672
|
},
|
|
398
673
|
async findById(table, id) {
|
|
399
674
|
const rows = store.get(table) ?? [];
|
|
@@ -405,51 +680,60 @@ function createMockDB() {
|
|
|
405
680
|
// thing a stand-in must never do.
|
|
406
681
|
async findMany(table, query, opts) {
|
|
407
682
|
assertUsableFilter("findMany", table, query);
|
|
683
|
+
refuseFragment("findMany", table, query);
|
|
408
684
|
const rows = store.get(table) ?? [];
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
685
|
+
let out = query ? rows.filter((row) => rowMatchesFilter("findMany", table, row, query)) : [
|
|
686
|
+
...rows
|
|
687
|
+
];
|
|
688
|
+
const orderSpecs = opts?.orderBy === void 0 ? [] : Array.isArray(opts.orderBy) ? opts.orderBy : [
|
|
689
|
+
opts.orderBy
|
|
690
|
+
];
|
|
691
|
+
if (orderSpecs.length > 0) {
|
|
692
|
+
out = [
|
|
693
|
+
...out
|
|
694
|
+
].sort((a, b) => {
|
|
695
|
+
for (const o of orderSpecs) {
|
|
696
|
+
const dir = o.direction === "desc" ? -1 : 1;
|
|
697
|
+
const x = a[o.column];
|
|
698
|
+
const y = b[o.column];
|
|
699
|
+
const xNull = x === null || x === void 0;
|
|
700
|
+
const yNull = y === null || y === void 0;
|
|
701
|
+
if (xNull || yNull) {
|
|
702
|
+
if (xNull && yNull) continue;
|
|
703
|
+
const nullsFirst = o.nulls === void 0 ? dir === -1 : o.nulls === "first";
|
|
704
|
+
return (xNull ? 1 : -1) * (nullsFirst ? -1 : 1);
|
|
428
705
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
let out = query ? rows.filter((row) => Object.entries(query).every(([k, c]) => matches2(row, k, c))) : [...rows];
|
|
434
|
-
const order = opts?.orderBy;
|
|
435
|
-
if (order) {
|
|
436
|
-
const dir = order.direction === "desc" ? -1 : 1;
|
|
437
|
-
out = [...out].sort((a, b) => {
|
|
438
|
-
const x = a[order.column];
|
|
439
|
-
const y = b[order.column];
|
|
440
|
-
return x === y ? 0 : (x < y ? -1 : 1) * dir;
|
|
706
|
+
if (x === y) continue;
|
|
707
|
+
return (x < y ? -1 : 1) * dir;
|
|
708
|
+
}
|
|
709
|
+
return 0;
|
|
441
710
|
});
|
|
442
711
|
}
|
|
443
|
-
|
|
712
|
+
const page = opts?.limit === void 0 ? out : out.slice(0, opts.limit);
|
|
713
|
+
const cols = opts?.select;
|
|
714
|
+
if (cols === void 0 || cols.length === 0) return page;
|
|
715
|
+
return page.map((row) => Object.fromEntries(cols.map((c) => [
|
|
716
|
+
c,
|
|
717
|
+
row[c]
|
|
718
|
+
])));
|
|
444
719
|
}
|
|
445
720
|
};
|
|
446
721
|
async function txPlan(plan) {
|
|
447
722
|
const snapshot = /* @__PURE__ */ new Map();
|
|
448
|
-
for (const [table, rows] of store) snapshot.set(table, [
|
|
723
|
+
for (const [table, rows] of store) snapshot.set(table, [
|
|
724
|
+
...rows
|
|
725
|
+
]);
|
|
449
726
|
const trackedSnapshot = {
|
|
450
727
|
inserted: cloneTracked(tracked.inserted),
|
|
451
728
|
updated: cloneTracked(tracked.updated),
|
|
452
|
-
deleted: new Map([
|
|
729
|
+
deleted: new Map([
|
|
730
|
+
...tracked.deleted
|
|
731
|
+
].map(([k, v]) => [
|
|
732
|
+
k,
|
|
733
|
+
[
|
|
734
|
+
...v
|
|
735
|
+
]
|
|
736
|
+
]))
|
|
453
737
|
};
|
|
454
738
|
const results = [];
|
|
455
739
|
try {
|
|
@@ -467,8 +751,11 @@ function createMockDB() {
|
|
|
467
751
|
tracked.deleted = trackedSnapshot.deleted;
|
|
468
752
|
throw err;
|
|
469
753
|
}
|
|
470
|
-
return {
|
|
754
|
+
return {
|
|
755
|
+
results
|
|
756
|
+
};
|
|
471
757
|
}
|
|
758
|
+
__name(txPlan, "txPlan");
|
|
472
759
|
function applyOp(op, results) {
|
|
473
760
|
switch (op.op) {
|
|
474
761
|
case "upsert": {
|
|
@@ -480,27 +767,54 @@ function createMockDB() {
|
|
|
480
767
|
for (const [key, value] of Object.entries(values)) {
|
|
481
768
|
if (!conflict.includes(key)) hit[key] = value;
|
|
482
769
|
}
|
|
483
|
-
return {
|
|
770
|
+
return {
|
|
771
|
+
rows: [
|
|
772
|
+
hit
|
|
773
|
+
],
|
|
774
|
+
rows_affected: 1
|
|
775
|
+
};
|
|
484
776
|
}
|
|
485
|
-
const created = {
|
|
777
|
+
const created = {
|
|
778
|
+
id: crypto.randomUUID(),
|
|
779
|
+
...values
|
|
780
|
+
};
|
|
486
781
|
rows.push(created);
|
|
487
782
|
track(tracked.inserted, op.table, created);
|
|
488
|
-
return {
|
|
783
|
+
return {
|
|
784
|
+
rows: [
|
|
785
|
+
created
|
|
786
|
+
],
|
|
787
|
+
rows_affected: 1
|
|
788
|
+
};
|
|
489
789
|
}
|
|
490
790
|
case "insert": {
|
|
491
|
-
const record = {
|
|
791
|
+
const record = {
|
|
792
|
+
id: crypto.randomUUID(),
|
|
793
|
+
...resolveMap(op.values ?? {}, results, null)
|
|
794
|
+
};
|
|
492
795
|
rowsOf(op.table).push(record);
|
|
493
796
|
track(tracked.inserted, op.table, record);
|
|
494
|
-
return {
|
|
797
|
+
return {
|
|
798
|
+
rows: [
|
|
799
|
+
record
|
|
800
|
+
],
|
|
801
|
+
rows_affected: 1
|
|
802
|
+
};
|
|
495
803
|
}
|
|
496
804
|
case "insertMany": {
|
|
497
805
|
const written = (op.rows ?? []).map((row) => {
|
|
498
|
-
const record = {
|
|
806
|
+
const record = {
|
|
807
|
+
id: crypto.randomUUID(),
|
|
808
|
+
...resolveMap(row, results, null)
|
|
809
|
+
};
|
|
499
810
|
rowsOf(op.table).push(record);
|
|
500
811
|
track(tracked.inserted, op.table, record);
|
|
501
812
|
return record;
|
|
502
813
|
});
|
|
503
|
-
return {
|
|
814
|
+
return {
|
|
815
|
+
rows: written,
|
|
816
|
+
rows_affected: written.length
|
|
817
|
+
};
|
|
504
818
|
}
|
|
505
819
|
case "update": {
|
|
506
820
|
const rows = rowsOf(op.table);
|
|
@@ -509,12 +823,18 @@ function createMockDB() {
|
|
|
509
823
|
for (let i = 0; i < rows.length; i++) {
|
|
510
824
|
const row = rows[i];
|
|
511
825
|
if (!row || !matches(row, where)) continue;
|
|
512
|
-
const next = {
|
|
826
|
+
const next = {
|
|
827
|
+
...row,
|
|
828
|
+
...resolveMap(op.set ?? {}, results, row)
|
|
829
|
+
};
|
|
513
830
|
rows[i] = next;
|
|
514
831
|
track(tracked.updated, op.table, next);
|
|
515
832
|
written.push(next);
|
|
516
833
|
}
|
|
517
|
-
return {
|
|
834
|
+
return {
|
|
835
|
+
rows: written,
|
|
836
|
+
rows_affected: written.length
|
|
837
|
+
};
|
|
518
838
|
}
|
|
519
839
|
case "delete": {
|
|
520
840
|
const rows = rowsOf(op.table);
|
|
@@ -526,23 +846,32 @@ function createMockDB() {
|
|
|
526
846
|
const list = tracked.deleted.get(op.table);
|
|
527
847
|
const key = typeof id === "string" ? id : String(id);
|
|
528
848
|
if (list) list.push(key);
|
|
529
|
-
else tracked.deleted.set(op.table, [
|
|
849
|
+
else tracked.deleted.set(op.table, [
|
|
850
|
+
key
|
|
851
|
+
]);
|
|
530
852
|
}
|
|
531
|
-
return {
|
|
853
|
+
return {
|
|
854
|
+
rows: removed,
|
|
855
|
+
rows_affected: removed.length
|
|
856
|
+
};
|
|
532
857
|
}
|
|
533
858
|
case "select": {
|
|
534
859
|
const where = resolveMap(op.where ?? {}, results, null);
|
|
535
860
|
let found = rowsOf(op.table).filter((row) => matches(row, where));
|
|
536
861
|
if (op.limit !== void 0) found = found.slice(0, op.limit);
|
|
537
|
-
return {
|
|
862
|
+
return {
|
|
863
|
+
rows: found,
|
|
864
|
+
rows_affected: found.length
|
|
865
|
+
};
|
|
538
866
|
}
|
|
539
867
|
}
|
|
540
868
|
}
|
|
869
|
+
__name(applyOp, "applyOp");
|
|
541
870
|
const client = {
|
|
542
871
|
...ops,
|
|
543
872
|
// No real savepoint in memory: the fake runs the callback against the SAME
|
|
544
873
|
// store. An assertion about rollback here would be asserting the fake.
|
|
545
|
-
attempt: (fn) => fn(ops),
|
|
874
|
+
attempt: /* @__PURE__ */ __name((fn) => fn(ops), "attempt"),
|
|
546
875
|
txPlan,
|
|
547
876
|
// In tests there is no real DB role; `asService()` returns the same
|
|
548
877
|
// in-memory client so RLS-bypass code paths still hit the same store and
|
|
@@ -561,14 +890,25 @@ function createMockDB() {
|
|
|
561
890
|
return tracked.deleted.get(table) ?? [];
|
|
562
891
|
},
|
|
563
892
|
seed(table, data) {
|
|
564
|
-
store.set(table, [
|
|
893
|
+
store.set(table, [
|
|
894
|
+
...data
|
|
895
|
+
]);
|
|
565
896
|
}
|
|
566
897
|
};
|
|
567
898
|
return client;
|
|
568
899
|
}
|
|
900
|
+
__name(createMockDB, "createMockDB");
|
|
569
901
|
function cloneTracked(map) {
|
|
570
|
-
return new Map([
|
|
902
|
+
return new Map([
|
|
903
|
+
...map
|
|
904
|
+
].map(([k, v]) => [
|
|
905
|
+
k,
|
|
906
|
+
[
|
|
907
|
+
...v
|
|
908
|
+
]
|
|
909
|
+
]));
|
|
571
910
|
}
|
|
911
|
+
__name(cloneTracked, "cloneTracked");
|
|
572
912
|
function resolveValue(value, results, current, column) {
|
|
573
913
|
if (typeof value !== "object" || value === null) return value;
|
|
574
914
|
const tagged = value;
|
|
@@ -590,6 +930,7 @@ function resolveValue(value, results, current, column) {
|
|
|
590
930
|
}
|
|
591
931
|
return value;
|
|
592
932
|
}
|
|
933
|
+
__name(resolveValue, "resolveValue");
|
|
593
934
|
function resolveMap(map, results, current) {
|
|
594
935
|
const out = {};
|
|
595
936
|
for (const [key, value] of Object.entries(map)) {
|
|
@@ -597,11 +938,11 @@ function resolveMap(map, results, current) {
|
|
|
597
938
|
}
|
|
598
939
|
return out;
|
|
599
940
|
}
|
|
941
|
+
__name(resolveMap, "resolveMap");
|
|
600
942
|
function matches(row, where) {
|
|
601
|
-
return Object.entries(where).every(
|
|
602
|
-
([key, value]) => value === null ? row[key] === null || row[key] === void 0 : row[key] === value
|
|
603
|
-
);
|
|
943
|
+
return Object.entries(where).every(([key, value]) => value === null ? row[key] === null || row[key] === void 0 : row[key] === value);
|
|
604
944
|
}
|
|
945
|
+
__name(matches, "matches");
|
|
605
946
|
function guardFailure(guard, count) {
|
|
606
947
|
if (!guard) return null;
|
|
607
948
|
const ok = guard.kind === "one" ? count === 1 : guard.kind === "none" ? count === 0 : guard.kind === "atLeast" ? count >= guard.n : count <= guard.n;
|
|
@@ -611,6 +952,7 @@ function guardFailure(guard, count) {
|
|
|
611
952
|
message: `expected ${guard.kind} ${guard.n} row(s), got ${count}`
|
|
612
953
|
});
|
|
613
954
|
}
|
|
955
|
+
__name(guardFailure, "guardFailure");
|
|
614
956
|
function txRejection(status, code, extra) {
|
|
615
957
|
const err = new Error(extra.message);
|
|
616
958
|
err.status = status;
|
|
@@ -618,30 +960,36 @@ function txRejection(status, code, extra) {
|
|
|
618
960
|
if (extra.slot !== void 0) err.slot = extra.slot;
|
|
619
961
|
return err;
|
|
620
962
|
}
|
|
963
|
+
__name(txRejection, "txRejection");
|
|
621
964
|
|
|
622
965
|
// src/test/fake-db.ts
|
|
623
966
|
function fakeDatabase() {
|
|
624
967
|
const mock = createMockDB();
|
|
625
968
|
const queries = [];
|
|
626
969
|
const db = Object.assign(Object.create(Object.getPrototypeOf(mock)), mock, {
|
|
627
|
-
query: async (sql, params = []) => {
|
|
628
|
-
queries.push({
|
|
970
|
+
query: /* @__PURE__ */ __name(async (sql, params = []) => {
|
|
971
|
+
queries.push({
|
|
972
|
+
sql,
|
|
973
|
+
params
|
|
974
|
+
});
|
|
629
975
|
return mock.query(sql, params);
|
|
630
|
-
}
|
|
976
|
+
}, "query")
|
|
631
977
|
});
|
|
632
978
|
return {
|
|
633
979
|
db,
|
|
634
980
|
queries,
|
|
635
|
-
seed: (table, rows) => mock.seed(table, rows),
|
|
636
|
-
inserted: (table) => mock.inserted(table),
|
|
637
|
-
updated: (table) => mock.updated(table),
|
|
638
|
-
deleted: (table) => mock.deleted(table)
|
|
981
|
+
seed: /* @__PURE__ */ __name((table, rows) => mock.seed(table, rows), "seed"),
|
|
982
|
+
inserted: /* @__PURE__ */ __name((table) => mock.inserted(table), "inserted"),
|
|
983
|
+
updated: /* @__PURE__ */ __name((table) => mock.updated(table), "updated"),
|
|
984
|
+
deleted: /* @__PURE__ */ __name((table) => mock.deleted(table), "deleted")
|
|
639
985
|
};
|
|
640
986
|
}
|
|
987
|
+
__name(fakeDatabase, "fakeDatabase");
|
|
641
988
|
export {
|
|
642
989
|
TestApiError,
|
|
643
990
|
api,
|
|
644
991
|
createTestApi,
|
|
645
|
-
fakeDatabase
|
|
992
|
+
fakeDatabase,
|
|
993
|
+
isolated
|
|
646
994
|
};
|
|
647
995
|
//# sourceMappingURL=index.js.map
|