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