@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.cjs
CHANGED
|
@@ -3,6 +3,7 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
7
|
var __export = (target, all) => {
|
|
7
8
|
for (var name in all)
|
|
8
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -23,7 +24,8 @@ __export(test_exports, {
|
|
|
23
24
|
TestApiError: () => TestApiError,
|
|
24
25
|
api: () => api,
|
|
25
26
|
createTestApi: () => createTestApi,
|
|
26
|
-
fakeDatabase: () => fakeDatabase
|
|
27
|
+
fakeDatabase: () => fakeDatabase,
|
|
28
|
+
isolated: () => isolated
|
|
27
29
|
});
|
|
28
30
|
module.exports = __toCommonJS(test_exports);
|
|
29
31
|
|
|
@@ -40,8 +42,13 @@ function asPowChallenge(details) {
|
|
|
40
42
|
const { id, prefix, difficulty } = c;
|
|
41
43
|
if (typeof id !== "string" || typeof prefix !== "string") return null;
|
|
42
44
|
if (typeof difficulty !== "number" || !Number.isInteger(difficulty) || difficulty < 0) return null;
|
|
43
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
id,
|
|
47
|
+
prefix,
|
|
48
|
+
difficulty
|
|
49
|
+
};
|
|
44
50
|
}
|
|
51
|
+
__name(asPowChallenge, "asPowChallenge");
|
|
45
52
|
var encoder = new TextEncoder();
|
|
46
53
|
var hasher = null;
|
|
47
54
|
async function digester() {
|
|
@@ -56,16 +63,17 @@ async function digester() {
|
|
|
56
63
|
);
|
|
57
64
|
if (typeof mod.createHash === "function") {
|
|
58
65
|
const createHash = mod.createHash;
|
|
59
|
-
hasher = (input) => new Uint8Array(createHash("sha256").update(input).digest());
|
|
66
|
+
hasher = /* @__PURE__ */ __name((input) => new Uint8Array(createHash("sha256").update(input).digest()), "hasher");
|
|
60
67
|
return hasher;
|
|
61
68
|
}
|
|
62
69
|
} catch {
|
|
63
70
|
}
|
|
64
71
|
}
|
|
65
|
-
hasher = async (input) => new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(input)));
|
|
72
|
+
hasher = /* @__PURE__ */ __name(async (input) => new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(input))), "hasher");
|
|
66
73
|
return hasher;
|
|
67
74
|
}
|
|
68
|
-
|
|
75
|
+
__name(digester, "digester");
|
|
76
|
+
var now = /* @__PURE__ */ __name(() => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now(), "now");
|
|
69
77
|
function leadingZeroBits(hash) {
|
|
70
78
|
let bits = 0;
|
|
71
79
|
for (const byte of hash) {
|
|
@@ -77,18 +85,18 @@ function leadingZeroBits(hash) {
|
|
|
77
85
|
}
|
|
78
86
|
return bits;
|
|
79
87
|
}
|
|
88
|
+
__name(leadingZeroBits, "leadingZeroBits");
|
|
80
89
|
var MAX_POW_DIFFICULTY = 24;
|
|
81
90
|
function powBudget(difficulty) {
|
|
82
91
|
return 8 * 2 ** difficulty;
|
|
83
92
|
}
|
|
93
|
+
__name(powBudget, "powBudget");
|
|
84
94
|
var POW_TIME_BUDGET_MS = 12e4;
|
|
85
95
|
var CALIBRATION_WARMUP = 1024;
|
|
86
96
|
var CALIBRATION_END = 9216;
|
|
87
97
|
async function solvePowChallenge(challenge, maxIterations = powBudget(challenge.difficulty), signal) {
|
|
88
98
|
if (challenge.difficulty > MAX_POW_DIFFICULTY) {
|
|
89
|
-
throw new Error(
|
|
90
|
-
`proof-of-work: refusing difficulty ${challenge.difficulty}; this client attempts at most ${MAX_POW_DIFFICULTY}, which is the highest a Palbase stack issues`
|
|
91
|
-
);
|
|
99
|
+
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`);
|
|
92
100
|
}
|
|
93
101
|
const digest = await digester();
|
|
94
102
|
let warmedAt = 0;
|
|
@@ -100,9 +108,7 @@ async function solvePowChallenge(challenge, maxIterations = powBudget(challenge.
|
|
|
100
108
|
throw new DOMException("proof-of-work solve aborted", "AbortError");
|
|
101
109
|
}
|
|
102
110
|
if (now() > deadline) {
|
|
103
|
-
throw new Error(
|
|
104
|
-
`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`
|
|
105
|
-
);
|
|
111
|
+
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`);
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
if (nonce === CALIBRATION_WARMUP) {
|
|
@@ -114,9 +120,7 @@ async function solvePowChallenge(challenge, maxIterations = powBudget(challenge.
|
|
|
114
120
|
const rate = (CALIBRATION_END - CALIBRATION_WARMUP) / (elapsed / 1e3);
|
|
115
121
|
const expectedMs = 2 ** challenge.difficulty / rate * 1e3;
|
|
116
122
|
if (expectedMs > POW_TIME_BUDGET_MS) {
|
|
117
|
-
throw new Error(
|
|
118
|
-
`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`
|
|
119
|
-
);
|
|
123
|
+
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`);
|
|
120
124
|
}
|
|
121
125
|
deadline = now() + (POW_TIME_BUDGET_MS - (now() - warmedAt));
|
|
122
126
|
}
|
|
@@ -128,13 +132,15 @@ async function solvePowChallenge(challenge, maxIterations = powBudget(challenge.
|
|
|
128
132
|
};
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
|
-
throw new Error(
|
|
132
|
-
`proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`
|
|
133
|
-
);
|
|
135
|
+
throw new Error(`proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`);
|
|
134
136
|
}
|
|
137
|
+
__name(solvePowChallenge, "solvePowChallenge");
|
|
135
138
|
|
|
136
139
|
// src/test/api.ts
|
|
137
140
|
var TestApiError = class extends Error {
|
|
141
|
+
static {
|
|
142
|
+
__name(this, "TestApiError");
|
|
143
|
+
}
|
|
138
144
|
status;
|
|
139
145
|
error;
|
|
140
146
|
/** Payload of an error your code threw — `new BadRequest({ fields })` arrives here. */
|
|
@@ -154,12 +160,11 @@ var TestApiError = class extends Error {
|
|
|
154
160
|
};
|
|
155
161
|
function required(value, envName) {
|
|
156
162
|
if (!value) {
|
|
157
|
-
throw new Error(
|
|
158
|
-
`${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.`
|
|
159
|
-
);
|
|
163
|
+
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.`);
|
|
160
164
|
}
|
|
161
165
|
return value;
|
|
162
166
|
}
|
|
167
|
+
__name(required, "required");
|
|
163
168
|
function isLocalTarget(baseUrl) {
|
|
164
169
|
try {
|
|
165
170
|
const { hostname } = new URL(baseUrl);
|
|
@@ -168,6 +173,7 @@ function isLocalTarget(baseUrl) {
|
|
|
168
173
|
return false;
|
|
169
174
|
}
|
|
170
175
|
}
|
|
176
|
+
__name(isLocalTarget, "isLocalTarget");
|
|
171
177
|
function secondsUntilExpiry(token) {
|
|
172
178
|
const body = token.split(".")[1];
|
|
173
179
|
if (!body) return null;
|
|
@@ -178,6 +184,7 @@ function secondsUntilExpiry(token) {
|
|
|
178
184
|
return null;
|
|
179
185
|
}
|
|
180
186
|
}
|
|
187
|
+
__name(secondsUntilExpiry, "secondsUntilExpiry");
|
|
181
188
|
function createTestApi(config) {
|
|
182
189
|
const baseUrl = required(config.baseUrl, "PALBASE_TEST_BASE_URL").replace(/\/$/, "");
|
|
183
190
|
const apiKey = required(config.apiKey, "PALBASE_TEST_API_KEY");
|
|
@@ -192,7 +199,9 @@ function createTestApi(config) {
|
|
|
192
199
|
// Selects the release under test. Omit it and the gateway serves the LIVE
|
|
193
200
|
// one, which would make the whole suite grade the wrong code. Absent only
|
|
194
201
|
// against a local stack, which has a single version.
|
|
195
|
-
...candidateToken ? {
|
|
202
|
+
...candidateToken ? {
|
|
203
|
+
"x-palbase-candidate": candidateToken
|
|
204
|
+
} : {},
|
|
196
205
|
...opts.headers
|
|
197
206
|
};
|
|
198
207
|
if (bearer) headers.authorization = `Bearer ${bearer}`;
|
|
@@ -205,7 +214,12 @@ function createTestApi(config) {
|
|
|
205
214
|
});
|
|
206
215
|
const text = await res.text();
|
|
207
216
|
const parsed = text ? safeParse(text) : void 0;
|
|
208
|
-
requests.push({
|
|
217
|
+
requests.push({
|
|
218
|
+
method,
|
|
219
|
+
path,
|
|
220
|
+
status: res.status,
|
|
221
|
+
ms: Date.now() - startedAt
|
|
222
|
+
});
|
|
209
223
|
if (!res.ok) {
|
|
210
224
|
if (res.status === 401 && bearer) {
|
|
211
225
|
const left = secondsUntilExpiry(bearer);
|
|
@@ -220,41 +234,39 @@ function createTestApi(config) {
|
|
|
220
234
|
}
|
|
221
235
|
return parsed;
|
|
222
236
|
}
|
|
237
|
+
__name(call, "call");
|
|
223
238
|
return {
|
|
224
239
|
requests,
|
|
225
|
-
get: (path, opts) => call("GET", path, void 0, opts),
|
|
226
|
-
post: (path, body, opts) => call("POST", path, body, opts),
|
|
227
|
-
patch: (path, body, opts) => call("PATCH", path, body, opts),
|
|
228
|
-
put: (path, body, opts) => call("PUT", path, body, opts),
|
|
229
|
-
delete: (path, opts) => call("DELETE", path, void 0, opts),
|
|
230
|
-
query: (path, body, opts) => call("QUERY", path, body, opts),
|
|
240
|
+
get: /* @__PURE__ */ __name((path, opts) => call("GET", path, void 0, opts), "get"),
|
|
241
|
+
post: /* @__PURE__ */ __name((path, body, opts) => call("POST", path, body, opts), "post"),
|
|
242
|
+
patch: /* @__PURE__ */ __name((path, body, opts) => call("PATCH", path, body, opts), "patch"),
|
|
243
|
+
put: /* @__PURE__ */ __name((path, body, opts) => call("PUT", path, body, opts), "put"),
|
|
244
|
+
delete: /* @__PURE__ */ __name((path, opts) => call("DELETE", path, void 0, opts), "delete"),
|
|
245
|
+
query: /* @__PURE__ */ __name((path, body, opts) => call("QUERY", path, body, opts), "query"),
|
|
231
246
|
async signInAs(name) {
|
|
232
247
|
const identity = (config.identities ?? {})[name];
|
|
233
248
|
if (!identity) {
|
|
234
249
|
const declared = Object.keys(config.identities ?? {});
|
|
235
|
-
throw new Error(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
", and this run has none \u2014 either your config declares no users or the deploy could not mint them; the deploy log says which"
|
|
242
|
-
))
|
|
243
|
-
);
|
|
250
|
+
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(", ")}` : (
|
|
251
|
+
// indistinguishable, and blaming the customer's config for a
|
|
252
|
+
// platform failure sends them to look in the wrong file. The
|
|
253
|
+
// deploy log names which one it was.
|
|
254
|
+
", and this run has none \u2014 either your config declares no users or the deploy could not mint them; the deploy log says which"
|
|
255
|
+
)));
|
|
244
256
|
}
|
|
245
257
|
if (identity.accessToken) {
|
|
246
258
|
bearer = identity.accessToken;
|
|
247
|
-
return {
|
|
259
|
+
return {
|
|
260
|
+
id: identity.id ?? "",
|
|
261
|
+
email: identity.email
|
|
262
|
+
};
|
|
248
263
|
}
|
|
249
264
|
return this.signIn(identity);
|
|
250
265
|
},
|
|
251
266
|
async signIn(credentials) {
|
|
252
|
-
const attempt = async (extra) => call(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
credentials,
|
|
256
|
-
extra ? { headers: extra } : {}
|
|
257
|
-
);
|
|
267
|
+
const attempt = /* @__PURE__ */ __name(async (extra) => call("POST", "/auth/login", credentials, extra ? {
|
|
268
|
+
headers: extra
|
|
269
|
+
} : {}), "attempt");
|
|
258
270
|
let result;
|
|
259
271
|
try {
|
|
260
272
|
result = await attempt();
|
|
@@ -265,7 +277,9 @@ function createTestApi(config) {
|
|
|
265
277
|
result = await attempt(await solvePowChallenge(challenge));
|
|
266
278
|
}
|
|
267
279
|
bearer = result.access_token;
|
|
268
|
-
return result.user ?? {
|
|
280
|
+
return result.user ?? {
|
|
281
|
+
id: ""
|
|
282
|
+
};
|
|
269
283
|
},
|
|
270
284
|
async signOut() {
|
|
271
285
|
await call("POST", "/auth/logout", void 0);
|
|
@@ -276,6 +290,7 @@ function createTestApi(config) {
|
|
|
276
290
|
}
|
|
277
291
|
};
|
|
278
292
|
}
|
|
293
|
+
__name(createTestApi, "createTestApi");
|
|
279
294
|
function parseIdentities(raw) {
|
|
280
295
|
if (!raw) return {};
|
|
281
296
|
try {
|
|
@@ -284,6 +299,7 @@ function parseIdentities(raw) {
|
|
|
284
299
|
return {};
|
|
285
300
|
}
|
|
286
301
|
}
|
|
302
|
+
__name(parseIdentities, "parseIdentities");
|
|
287
303
|
function safeParse(text) {
|
|
288
304
|
try {
|
|
289
305
|
return JSON.parse(text);
|
|
@@ -291,6 +307,7 @@ function safeParse(text) {
|
|
|
291
307
|
return text;
|
|
292
308
|
}
|
|
293
309
|
}
|
|
310
|
+
__name(safeParse, "safeParse");
|
|
294
311
|
var configured = null;
|
|
295
312
|
var api = new Proxy({}, {
|
|
296
313
|
get(_target, prop) {
|
|
@@ -304,57 +321,465 @@ var api = new Proxy({}, {
|
|
|
304
321
|
}
|
|
305
322
|
});
|
|
306
323
|
|
|
324
|
+
// src/test/container.ts
|
|
325
|
+
var import_reflect_metadata = require("reflect-metadata");
|
|
326
|
+
function isolated() {
|
|
327
|
+
const over = /* @__PURE__ */ new Map();
|
|
328
|
+
const local = /* @__PURE__ */ new Map();
|
|
329
|
+
const make = /* @__PURE__ */ __name((c) => {
|
|
330
|
+
if (over.has(c)) return over.get(c);
|
|
331
|
+
const hit = local.get(c);
|
|
332
|
+
if (hit !== void 0) return hit;
|
|
333
|
+
const meta = Reflect.getMetadata("design:paramtypes", c) ?? [];
|
|
334
|
+
const inst = new c(...meta.map((d) => make(d)));
|
|
335
|
+
local.set(c, inst);
|
|
336
|
+
return inst;
|
|
337
|
+
}, "make");
|
|
338
|
+
const api2 = {
|
|
339
|
+
with(t, v) {
|
|
340
|
+
over.set(t, v);
|
|
341
|
+
return api2;
|
|
342
|
+
},
|
|
343
|
+
get(t) {
|
|
344
|
+
return make(t);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
return api2;
|
|
348
|
+
}
|
|
349
|
+
__name(isolated, "isolated");
|
|
350
|
+
|
|
307
351
|
// src/db/input-guards.ts
|
|
308
|
-
var
|
|
352
|
+
var REF_BRAND = /* @__PURE__ */ Symbol.for("palbase.db.ref");
|
|
353
|
+
function brandOf(v) {
|
|
354
|
+
if (typeof v !== "object" || v === null) return void 0;
|
|
355
|
+
return Object.hasOwn(v, REF_BRAND) ? v[REF_BRAND] : void 0;
|
|
356
|
+
}
|
|
357
|
+
__name(brandOf, "brandOf");
|
|
358
|
+
function isColRef(v) {
|
|
359
|
+
return brandOf(v) === "col" && typeof v.$col === "string";
|
|
360
|
+
}
|
|
361
|
+
__name(isColRef, "isColRef");
|
|
362
|
+
function isSqlFragment(v) {
|
|
363
|
+
if (brandOf(v) !== "sql") return false;
|
|
364
|
+
const f = v.$sql;
|
|
365
|
+
return f !== void 0 && Array.isArray(f.text) && Array.isArray(f.values);
|
|
366
|
+
}
|
|
367
|
+
__name(isSqlFragment, "isSqlFragment");
|
|
368
|
+
var TX_EXPR = /* @__PURE__ */ Symbol.for("palbase.tx.expr");
|
|
369
|
+
function isNowExpr(v) {
|
|
370
|
+
if (!isColumnExpr(v)) return false;
|
|
371
|
+
try {
|
|
372
|
+
const e = v[TX_EXPR];
|
|
373
|
+
return typeof e === "object" && e !== null && e.fn === "now";
|
|
374
|
+
} catch {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
__name(isNowExpr, "isNowExpr");
|
|
379
|
+
function isColumnExpr(v) {
|
|
380
|
+
if (typeof v !== "object" && typeof v !== "function") return false;
|
|
381
|
+
if (v === null) return false;
|
|
382
|
+
try {
|
|
383
|
+
return v[TX_EXPR] !== void 0;
|
|
384
|
+
} catch {
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
__name(isColumnExpr, "isColumnExpr");
|
|
389
|
+
function assertNoExpressionHandles(caller, table, cols, data) {
|
|
390
|
+
for (const c of cols) {
|
|
391
|
+
const v = data[c];
|
|
392
|
+
if (isColumnExpr(v)) {
|
|
393
|
+
throw new Error(`${caller}(${table}): "${c}" bir ifade tutama\u011F\u0131 ald\u0131 (increment()/decrement()/now()). Bu yolda de\u011Fer beklenir. Saya\xE7 art\u0131\u015F\u0131 i\xE7in updateMany(where, { ${c}: increment(n) }) ya da $transaction i\xE7inde tx.tables.${table}.updateWhere(where, { ${c}: increment(n) }) kullan\u0131n.`);
|
|
394
|
+
}
|
|
395
|
+
if (isColRef(v)) {
|
|
396
|
+
throw new Error(`${caller}(${table}): "${c}" bir col() ald\u0131. Kolon referans\u0131 yaln\u0131z F\u0130LTREDE durabilir; bir kolonun de\u011Ferini ba\u015Fka bir kolona yazmak i\xE7in $query kullan\u0131n.`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
__name(assertNoExpressionHandles, "assertNoExpressionHandles");
|
|
401
|
+
var KNOWN_OPS = /* @__PURE__ */ new Set([
|
|
402
|
+
"gt",
|
|
403
|
+
"gte",
|
|
404
|
+
"lt",
|
|
405
|
+
"lte",
|
|
406
|
+
"neq",
|
|
407
|
+
"in",
|
|
408
|
+
// K1 metin operatörleri (FR-005) ve null testi (FR-006). Bu küme
|
|
409
|
+
// `fakeDatabase()` ile ORTAK kaynaktır: fake bir çağrıyı motorun reddettiği
|
|
410
|
+
// yerde kabul ederse, yazarın testi üretimde patlayan koda karşı yeşil verir.
|
|
411
|
+
"contains",
|
|
412
|
+
"icontains",
|
|
413
|
+
"startsWith",
|
|
414
|
+
"endsWith",
|
|
415
|
+
"isNull"
|
|
416
|
+
]);
|
|
417
|
+
var REFUSED_OPS = {
|
|
418
|
+
eq: 'e\u015Fitlik \xC7IPLAK yaz\u0131l\u0131r: { <kolon>: <de\u011Fer> } (ya da kolon kar\u015F\u0131la\u015Ft\u0131rmas\u0131 i\xE7in { <kolon>: col("\u2026") })'
|
|
419
|
+
};
|
|
309
420
|
function assertUsableFilter(caller, table, where) {
|
|
421
|
+
const COMPOSITES = /* @__PURE__ */ new Set([
|
|
422
|
+
"OR",
|
|
423
|
+
"AND",
|
|
424
|
+
"NOT"
|
|
425
|
+
]);
|
|
310
426
|
if (!where) return;
|
|
427
|
+
if (isSqlFragment(where)) return;
|
|
311
428
|
for (const [col, cond] of Object.entries(where)) {
|
|
429
|
+
if (COMPOSITES.has(col)) {
|
|
430
|
+
const branches = col === "NOT" ? [
|
|
431
|
+
cond
|
|
432
|
+
] : cond;
|
|
433
|
+
if (!Array.isArray(branches) && col !== "NOT") {
|
|
434
|
+
throw new Error(`${caller}(${table}): where.${col} bir dizi olmal\u0131`);
|
|
435
|
+
}
|
|
436
|
+
for (const b of branches) {
|
|
437
|
+
if (b === null || typeof b !== "object") {
|
|
438
|
+
throw new Error(`${caller}(${table}): where.${col} dallar\u0131 filtre nesnesi olmal\u0131`);
|
|
439
|
+
}
|
|
440
|
+
assertUsableFilter(caller, table, b);
|
|
441
|
+
}
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (col === "has") {
|
|
445
|
+
if (cond === null || typeof cond !== "object" || Array.isArray(cond)) {
|
|
446
|
+
throw new Error(`${caller}(${table}): where.has bir ili\u015Fki haritas\u0131 olmal\u0131 ({ <ili\u015Fki>: { \u2026 } })`);
|
|
447
|
+
}
|
|
448
|
+
for (const [rel, inner] of Object.entries(cond)) {
|
|
449
|
+
if (inner === null || typeof inner !== "object" || Array.isArray(inner)) {
|
|
450
|
+
throw new Error(`${caller}(${table}): where.has.${rel} bir filtre nesnesi olmal\u0131`);
|
|
451
|
+
}
|
|
452
|
+
assertUsableFilter(caller, table, inner);
|
|
453
|
+
}
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
312
456
|
if (cond === void 0) {
|
|
313
|
-
throw new Error(
|
|
314
|
-
`${caller}(${table}): where.${col} de\u011Feri undefined \u2014 bu bir filtre de\u011Feri de\u011Fil. Ba\u011Flan\u0131nca NULL olur ve '= NULL' hi\xE7bir sat\u0131ra uymaz, yani sorgu sessizce bo\u015F sonu\xE7 d\xF6nerdi. De\u011Fer yoksa anahtar\u0131 filtreye hi\xE7 koymay\u0131n.`
|
|
315
|
-
);
|
|
457
|
+
throw new Error(`${caller}(${table}): where.${col} de\u011Feri undefined \u2014 bu bir filtre de\u011Feri de\u011Fil. Ba\u011Flan\u0131nca NULL olur ve '= NULL' hi\xE7bir sat\u0131ra uymaz, yani sorgu sessizce bo\u015F sonu\xE7 d\xF6nerdi. De\u011Fer yoksa anahtar\u0131 filtreye hi\xE7 koymay\u0131n.`);
|
|
316
458
|
}
|
|
317
459
|
if (cond === null || typeof cond !== "object" || Array.isArray(cond)) continue;
|
|
460
|
+
if (isColRef(cond)) continue;
|
|
461
|
+
if (isNowExpr(cond)) continue;
|
|
318
462
|
const entries = Object.entries(cond);
|
|
319
463
|
if (entries.length === 0) {
|
|
320
|
-
throw new Error(
|
|
321
|
-
`${caller}(${table}): where.${col} bo\u015F bir operat\xF6r nesnesi ({}) \u2014 hi\xE7bir ko\u015Ful \xFCretmez, yani bu alan filtreden sessizce D\xDC\u015EERD\u0130. Ko\u015Ful kurulmayacaksa anahtar\u0131 filtreye hi\xE7 koymay\u0131n (D-21).`
|
|
322
|
-
);
|
|
464
|
+
throw new Error(`${caller}(${table}): where.${col} bo\u015F bir operat\xF6r nesnesi ({}) \u2014 hi\xE7bir ko\u015Ful \xFCretmez, yani bu alan filtreden sessizce D\xDC\u015EERD\u0130. Ko\u015Ful kurulmayacaksa anahtar\u0131 filtreye hi\xE7 koymay\u0131n (D-21).`);
|
|
323
465
|
}
|
|
324
466
|
for (const [op, v] of entries) {
|
|
325
467
|
if (op === "in") {
|
|
326
468
|
if (!Array.isArray(v)) throw new Error(`${caller}(${table}): where.${col}.in bir dizi olmal\u0131`);
|
|
327
469
|
if (v.some((x) => x === void 0)) {
|
|
328
|
-
throw new Error(
|
|
329
|
-
`${caller}(${table}): where.${col}.in listesinde undefined var \u2014 sessizce NULL'a ba\u011Flan\u0131r ve o eleman hi\xE7bir sat\u0131rla e\u015Fle\u015Fmez. Listeyi kurarken eleyin.`
|
|
330
|
-
);
|
|
470
|
+
throw new Error(`${caller}(${table}): where.${col}.in listesinde undefined var \u2014 sessizce NULL'a ba\u011Flan\u0131r ve o eleman hi\xE7bir sat\u0131rla e\u015Fle\u015Fmez. Listeyi kurarken eleyin.`);
|
|
331
471
|
}
|
|
332
472
|
continue;
|
|
333
473
|
}
|
|
474
|
+
if (REFUSED_OPS[op] !== void 0) {
|
|
475
|
+
throw new Error(`${caller}(${table}): where.${col}.${op} bu filtre dilinde yok \u2014 ${REFUSED_OPS[op]}`);
|
|
476
|
+
}
|
|
334
477
|
if (!KNOWN_OPS.has(op)) {
|
|
335
|
-
throw new Error(
|
|
336
|
-
`${caller}(${table}): where.${col} bilinmeyen operat\xF6r "${op}" (gt/gte/lt/lte/neq/in)`
|
|
337
|
-
);
|
|
478
|
+
throw new Error(`${caller}(${table}): where.${col} bilinmeyen operat\xF6r "${op}" (gt/gte/lt/lte/neq/in/contains/icontains/startsWith/endsWith/isNull)`);
|
|
338
479
|
}
|
|
339
480
|
if (v === void 0) {
|
|
340
|
-
throw new Error(
|
|
341
|
-
`${caller}(${table}): where.${col}.${op} de\u011Feri undefined \u2014 kar\u015F\u0131la\u015Ft\u0131rman\u0131n sa\u011F taraf\u0131 NULL olur ve sonu\xE7 hi\xE7bir sat\u0131ra uymaz. Ko\u015Fulu kurmay\u0131n.`
|
|
342
|
-
);
|
|
481
|
+
throw new Error(`${caller}(${table}): where.${col}.${op} de\u011Feri undefined \u2014 kar\u015F\u0131la\u015Ft\u0131rman\u0131n sa\u011F taraf\u0131 NULL olur ve sonu\xE7 hi\xE7bir sat\u0131ra uymaz. Ko\u015Fulu kurmay\u0131n.`);
|
|
343
482
|
}
|
|
344
483
|
}
|
|
345
484
|
}
|
|
346
485
|
}
|
|
486
|
+
__name(assertUsableFilter, "assertUsableFilter");
|
|
347
487
|
function assertUsableWriteValues(caller, table, cols, data) {
|
|
348
488
|
for (const c of cols) {
|
|
349
489
|
if (data[c] === void 0) {
|
|
350
|
-
throw new Error(
|
|
351
|
-
`${caller}(${table}): "${c}" de\u011Feri undefined \u2014 bu bir yazma de\u011Feri de\u011Fil. Kolonu bo\u015Faltmak istiyorsan null yaz; kolonu de\u011Fi\u015Ftirmek istemiyorsan nesneye hi\xE7 koyma (bir eksik istek alan\u0131 sessizce NULL yaz\u0131yordu \u2014 FR-016).`
|
|
352
|
-
);
|
|
490
|
+
throw new Error(`${caller}(${table}): "${c}" de\u011Feri undefined \u2014 bu bir yazma de\u011Feri de\u011Fil. Kolonu bo\u015Faltmak istiyorsan null yaz; kolonu de\u011Fi\u015Ftirmek istemiyorsan nesneye hi\xE7 koyma (bir eksik istek alan\u0131 sessizce NULL yaz\u0131yordu \u2014 FR-016).`);
|
|
353
491
|
}
|
|
354
492
|
}
|
|
355
493
|
}
|
|
494
|
+
__name(assertUsableWriteValues, "assertUsableWriteValues");
|
|
495
|
+
|
|
496
|
+
// src/db/tx-plan.ts
|
|
497
|
+
var TxRefError = class extends Error {
|
|
498
|
+
static {
|
|
499
|
+
__name(this, "TxRefError");
|
|
500
|
+
}
|
|
501
|
+
constructor(message) {
|
|
502
|
+
super(message);
|
|
503
|
+
this.name = "TxRefError";
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
var TxPlanError = class extends Error {
|
|
507
|
+
static {
|
|
508
|
+
__name(this, "TxPlanError");
|
|
509
|
+
}
|
|
510
|
+
constructor(message) {
|
|
511
|
+
super(message);
|
|
512
|
+
this.name = "TxPlanError";
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
var EXPR = /* @__PURE__ */ Symbol.for("palbase.tx.expr");
|
|
516
|
+
var REF = /* @__PURE__ */ Symbol.for("palbase.tx.ref");
|
|
517
|
+
var ROW = /* @__PURE__ */ Symbol.for("palbase.tx.row");
|
|
518
|
+
var ROWS = /* @__PURE__ */ Symbol.for("palbase.tx.rows");
|
|
519
|
+
var TRAPPED_PROPS = [
|
|
520
|
+
"then",
|
|
521
|
+
"valueOf",
|
|
522
|
+
"toString",
|
|
523
|
+
"toJSON",
|
|
524
|
+
Symbol.toPrimitive
|
|
525
|
+
];
|
|
526
|
+
function trap(prop, what, hint) {
|
|
527
|
+
const name = typeof prop === "symbol" ? prop.description ?? String(prop) : prop;
|
|
528
|
+
throw new TxRefError(`${what} was used as a value (via \`${name}\`). Nothing in a transaction callback has run yet, so there is no value to read. ${hint}`);
|
|
529
|
+
}
|
|
530
|
+
__name(trap, "trap");
|
|
531
|
+
function columnExprOf(v) {
|
|
532
|
+
return exprOf(v);
|
|
533
|
+
}
|
|
534
|
+
__name(columnExprOf, "columnExprOf");
|
|
535
|
+
function makeRef(op, field) {
|
|
536
|
+
const target = {
|
|
537
|
+
[REF]: {
|
|
538
|
+
op,
|
|
539
|
+
field
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
return new Proxy(target, {
|
|
543
|
+
get(t, prop) {
|
|
544
|
+
if (prop === REF) return t[REF];
|
|
545
|
+
if (TRAPPED_PROPS.includes(prop)) {
|
|
546
|
+
trap(prop, `\`${field}\` of a row this transaction has not written yet`, "Pass it to another operation in the same plan, or return it from the callback and read it after `transaction()` resolves.");
|
|
547
|
+
}
|
|
548
|
+
return void 0;
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
__name(makeRef, "makeRef");
|
|
553
|
+
function makeRowHandle(op) {
|
|
554
|
+
const target = {
|
|
555
|
+
[ROW]: op
|
|
556
|
+
};
|
|
557
|
+
return new Proxy(target, {
|
|
558
|
+
get(t, prop) {
|
|
559
|
+
if (prop === ROW) return t[ROW];
|
|
560
|
+
if (TRAPPED_PROPS.includes(prop)) {
|
|
561
|
+
trap(prop, "A row this transaction has not written yet", "Read one of its columns to reference it, or return the row from the callback and read it after `transaction()` resolves.");
|
|
562
|
+
}
|
|
563
|
+
if (typeof prop === "symbol") return void 0;
|
|
564
|
+
return makeRef(op, prop);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
__name(makeRowHandle, "makeRowHandle");
|
|
569
|
+
function exprOf(v) {
|
|
570
|
+
if (typeof v !== "object" || v === null) return null;
|
|
571
|
+
const e = v[EXPR];
|
|
572
|
+
return typeof e === "object" && e !== null ? e : null;
|
|
573
|
+
}
|
|
574
|
+
__name(exprOf, "exprOf");
|
|
575
|
+
var SKIPPED_OP = -1;
|
|
576
|
+
var TxRowsImpl = class TxRowsImpl2 {
|
|
577
|
+
static {
|
|
578
|
+
__name(this, "TxRowsImpl");
|
|
579
|
+
}
|
|
580
|
+
builder;
|
|
581
|
+
opIndex;
|
|
582
|
+
what;
|
|
583
|
+
// Present so `isRowsHandle` recognises the object; never read for its value.
|
|
584
|
+
[ROWS] = true;
|
|
585
|
+
guarded = false;
|
|
586
|
+
constructor(builder, opIndex, what) {
|
|
587
|
+
this.builder = builder;
|
|
588
|
+
this.opIndex = opIndex;
|
|
589
|
+
this.what = what;
|
|
590
|
+
}
|
|
591
|
+
// The type-level `await` guard made real: TS rejects `await rows` at compile
|
|
592
|
+
// time, and reaching this means someone called `.then(...)` by hand.
|
|
593
|
+
then() {
|
|
594
|
+
throw new TxRefError(`${this.what} cannot be awaited: a transaction callback builds a plan, it does not run statements. Remove the \`await\`.`);
|
|
595
|
+
}
|
|
596
|
+
expectOne(error) {
|
|
597
|
+
this.declareGuard("one", 1, error);
|
|
598
|
+
if (this.opIndex === SKIPPED_OP) throw error;
|
|
599
|
+
return makeRowHandle(this.opIndex);
|
|
600
|
+
}
|
|
601
|
+
expectNone(error) {
|
|
602
|
+
this.declareGuard("none", 0, error);
|
|
603
|
+
}
|
|
604
|
+
expectAtLeast(n, error) {
|
|
605
|
+
assertGuardCount(n, "expectAtLeast");
|
|
606
|
+
this.declareGuard("atLeast", n, error);
|
|
607
|
+
if (this.opIndex === SKIPPED_OP && n > 0) throw error;
|
|
608
|
+
}
|
|
609
|
+
expectAtMost(n, error) {
|
|
610
|
+
assertGuardCount(n, "expectAtMost");
|
|
611
|
+
this.declareGuard("atMost", n, error);
|
|
612
|
+
}
|
|
613
|
+
declareGuard(kind, n, error) {
|
|
614
|
+
if (!(error instanceof Error)) {
|
|
615
|
+
throw new TxPlanError(`${this.what}: an expectation needs the Error to throw when it does not hold (e.g. \`.expect\u2026(new Conflict("already accepted"))\`).`);
|
|
616
|
+
}
|
|
617
|
+
if (this.guarded) {
|
|
618
|
+
throw new TxPlanError(`${this.what} already has an expectation. One operation carries one expectation; declare the second one on its own operation.`);
|
|
619
|
+
}
|
|
620
|
+
this.guarded = true;
|
|
621
|
+
if (this.opIndex === SKIPPED_OP) return;
|
|
622
|
+
this.builder.attachGuard(this.opIndex, kind, n, error);
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
function assertGuardCount(n, fn) {
|
|
626
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
627
|
+
throw new TxPlanError(`${fn}(n) needs a non-negative integer, got ${String(n)}`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
__name(assertGuardCount, "assertGuardCount");
|
|
356
631
|
|
|
357
632
|
// src/__tests__/helpers/mock-db.ts
|
|
633
|
+
function refuseFragment(caller, table, where) {
|
|
634
|
+
if (isSqlFragment(where)) {
|
|
635
|
+
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.`);
|
|
636
|
+
}
|
|
637
|
+
if (where === null || typeof where !== "object") return;
|
|
638
|
+
for (const [k, v] of Object.entries(where)) {
|
|
639
|
+
if (k === "OR" || k === "AND") {
|
|
640
|
+
for (const branch of Array.isArray(v) ? v : []) refuseFragment(caller, table, branch);
|
|
641
|
+
} else if (k === "NOT") {
|
|
642
|
+
refuseFragment(caller, table, v);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
__name(refuseFragment, "refuseFragment");
|
|
647
|
+
function addDecimal(cell, by, sign) {
|
|
648
|
+
if (cell === null || cell === void 0) return null;
|
|
649
|
+
if (typeof cell === "number" && typeof by === "number") return cell + sign * by;
|
|
650
|
+
const a = String(cell);
|
|
651
|
+
const b = String(by);
|
|
652
|
+
const parse = /* @__PURE__ */ __name((x) => {
|
|
653
|
+
const m = /^([+-]?)(\d*)(?:\.(\d*))?$/.exec(x.trim());
|
|
654
|
+
if (m === null || m[2] === "" && (m[3] ?? "") === "") return null;
|
|
655
|
+
const frac = m[3] ?? "";
|
|
656
|
+
const unit = BigInt(`${m[1] === "-" ? "-" : ""}${m[2] === "" ? "0" : m[2]}${frac}`);
|
|
657
|
+
return {
|
|
658
|
+
unit,
|
|
659
|
+
scale: frac.length
|
|
660
|
+
};
|
|
661
|
+
}, "parse");
|
|
662
|
+
const pa = parse(a);
|
|
663
|
+
const pb = parse(b);
|
|
664
|
+
if (pa === null || pb === null) {
|
|
665
|
+
throw new Error(`fakeDatabase: increment()/decrement() say\u0131sal olmayan bir de\u011Fere uyguland\u0131 ("${a}") \u2014 Postgres bunu "operator does not exist" ile reddeder.`);
|
|
666
|
+
}
|
|
667
|
+
const scale = Math.max(pa.scale, pb.scale);
|
|
668
|
+
const lift = /* @__PURE__ */ __name((v) => v.unit * 10n ** BigInt(scale - v.scale), "lift");
|
|
669
|
+
const total = lift(pa) + BigInt(sign) * lift(pb);
|
|
670
|
+
if (scale === 0) return typeof cell === "number" ? Number(total) : total.toString();
|
|
671
|
+
const neg = total < 0n;
|
|
672
|
+
const digits = (neg ? -total : total).toString().padStart(scale + 1, "0");
|
|
673
|
+
const out = `${neg ? "-" : ""}${digits.slice(0, -scale)}.${digits.slice(-scale)}`;
|
|
674
|
+
return typeof cell === "number" ? Number(out) : out;
|
|
675
|
+
}
|
|
676
|
+
__name(addDecimal, "addDecimal");
|
|
677
|
+
function rowMatchesFilter(caller, table, row, f) {
|
|
678
|
+
return Object.entries(f).every(([k, c]) => {
|
|
679
|
+
if (k === "OR") return c.some((b) => rowMatchesFilter(caller, table, row, b));
|
|
680
|
+
if (k === "AND") return c.every((b) => rowMatchesFilter(caller, table, row, b));
|
|
681
|
+
if (k === "NOT") return !rowMatchesFilter(caller, table, row, c);
|
|
682
|
+
if (k === "has") {
|
|
683
|
+
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).`);
|
|
684
|
+
}
|
|
685
|
+
return matchesCell(caller, table, row, k, c);
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
__name(rowMatchesFilter, "rowMatchesFilter");
|
|
689
|
+
function cmp(a, b, op) {
|
|
690
|
+
if (a === null || a === void 0 || b === null || b === void 0) return false;
|
|
691
|
+
const l = a instanceof Date ? a.getTime() : a;
|
|
692
|
+
const r = b instanceof Date ? b.getTime() : b;
|
|
693
|
+
switch (op) {
|
|
694
|
+
case "eq":
|
|
695
|
+
return l === r;
|
|
696
|
+
case "neq":
|
|
697
|
+
return l !== r;
|
|
698
|
+
case "gt":
|
|
699
|
+
return l > r;
|
|
700
|
+
case "gte":
|
|
701
|
+
return l >= r;
|
|
702
|
+
case "lt":
|
|
703
|
+
return l < r;
|
|
704
|
+
case "lte":
|
|
705
|
+
return l <= r;
|
|
706
|
+
default:
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
__name(cmp, "cmp");
|
|
711
|
+
function matchesCell(caller, table, row, key, cond) {
|
|
712
|
+
if (isColRef(cond)) return cmp(row[key], row[cond.$col], "eq");
|
|
713
|
+
if (isNowExpr(cond)) return cmp(row[key], (/* @__PURE__ */ new Date()).toISOString(), "eq");
|
|
714
|
+
if (cond !== null && typeof cond === "object" && !Array.isArray(cond)) {
|
|
715
|
+
return Object.entries(cond).every(([op, v]) => {
|
|
716
|
+
const cell = row[key];
|
|
717
|
+
if (isNowExpr(v)) {
|
|
718
|
+
if (![
|
|
719
|
+
"neq",
|
|
720
|
+
"gt",
|
|
721
|
+
"gte",
|
|
722
|
+
"lt",
|
|
723
|
+
"lte"
|
|
724
|
+
].includes(op)) {
|
|
725
|
+
throw new Error(`${caller}(${table}): where.${key}.${op} now() ile kullan\u0131lamaz`);
|
|
726
|
+
}
|
|
727
|
+
return cmp(cell, (/* @__PURE__ */ new Date()).toISOString(), op);
|
|
728
|
+
}
|
|
729
|
+
if (isColumnExpr(v)) {
|
|
730
|
+
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.`);
|
|
731
|
+
}
|
|
732
|
+
if (isColRef(v)) {
|
|
733
|
+
if (![
|
|
734
|
+
"neq",
|
|
735
|
+
"gt",
|
|
736
|
+
"gte",
|
|
737
|
+
"lt",
|
|
738
|
+
"lte"
|
|
739
|
+
].includes(op)) {
|
|
740
|
+
throw new Error(`${caller}(${table}): where.${key}.${op} col() ile kullan\u0131lamaz`);
|
|
741
|
+
}
|
|
742
|
+
return cmp(cell, row[v.$col], op);
|
|
743
|
+
}
|
|
744
|
+
switch (op) {
|
|
745
|
+
case "in":
|
|
746
|
+
if (!Array.isArray(v)) throw new Error(`${caller}(${table}): where.${key}.in bir dizi olmal\u0131`);
|
|
747
|
+
if (v.some(isColRef)) {
|
|
748
|
+
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`);
|
|
749
|
+
}
|
|
750
|
+
return v.includes(cell);
|
|
751
|
+
case "neq":
|
|
752
|
+
case "gt":
|
|
753
|
+
case "gte":
|
|
754
|
+
case "lt":
|
|
755
|
+
case "lte":
|
|
756
|
+
return cmp(cell, v, op);
|
|
757
|
+
// K1 metin operatörleri (FR-005) ve null testi (FR-006). Bunlar BURADA
|
|
758
|
+
// da olmak zorunda: guard onları KABUL ettiği anda fake sessizce yanlış
|
|
759
|
+
// cevap verir ve yazarın testi, üretimde farklı davranan koda karşı
|
|
760
|
+
// yeşil verirdi (input-guards.ts'in uyardığı tam sınıf).
|
|
761
|
+
case "isNull":
|
|
762
|
+
if (typeof v !== "boolean") throw new Error(`${caller}(${table}): where.${key}.isNull bir boolean olmal\u0131`);
|
|
763
|
+
return v ? cell == null : cell != null;
|
|
764
|
+
case "contains":
|
|
765
|
+
case "icontains":
|
|
766
|
+
case "startsWith":
|
|
767
|
+
case "endsWith": {
|
|
768
|
+
if (typeof v !== "string") throw new Error(`${caller}(${table}): where.${key}.${op} bir string olmal\u0131`);
|
|
769
|
+
if (typeof cell !== "string") return false;
|
|
770
|
+
if (op === "contains") return cell.includes(v);
|
|
771
|
+
if (op === "icontains") return cell.toLowerCase().includes(v.toLowerCase());
|
|
772
|
+
if (op === "startsWith") return cell.startsWith(v);
|
|
773
|
+
return cell.endsWith(v);
|
|
774
|
+
}
|
|
775
|
+
default:
|
|
776
|
+
throw new Error(`${caller}(${table}): where.${key} bilinmeyen operat\xF6r "${op}"`);
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
return row[key] === cond;
|
|
781
|
+
}
|
|
782
|
+
__name(matchesCell, "matchesCell");
|
|
358
783
|
function createMockDB() {
|
|
359
784
|
const store = /* @__PURE__ */ new Map();
|
|
360
785
|
const tracked = {
|
|
@@ -370,11 +795,15 @@ function createMockDB() {
|
|
|
370
795
|
}
|
|
371
796
|
return rows;
|
|
372
797
|
}
|
|
798
|
+
__name(rowsOf, "rowsOf");
|
|
373
799
|
function track(map, table, row) {
|
|
374
800
|
const list = map.get(table);
|
|
375
801
|
if (list) list.push(row);
|
|
376
|
-
else map.set(table, [
|
|
802
|
+
else map.set(table, [
|
|
803
|
+
row
|
|
804
|
+
]);
|
|
377
805
|
}
|
|
806
|
+
__name(track, "track");
|
|
378
807
|
const ops = {
|
|
379
808
|
// The bulk ops and count run against the SAME in-memory store the direct
|
|
380
809
|
// ops write to, so a test that writes three rows and counts them gets 3 —
|
|
@@ -383,26 +812,38 @@ function createMockDB() {
|
|
|
383
812
|
async updateMany(table, where, set) {
|
|
384
813
|
if (Object.keys(where).length === 0) throw new Error(`updateMany(${table}): bo\u015F filtre`);
|
|
385
814
|
assertUsableFilter("updateMany", table, where);
|
|
815
|
+
refuseFragment("updateMany", table, where);
|
|
386
816
|
assertUsableWriteValues("updateMany", table, Object.keys(set), set);
|
|
387
|
-
const hit = (store.get(table) ?? []).filter(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
817
|
+
const hit = (store.get(table) ?? []).filter((r) => rowMatchesFilter("updateMany", table, r, where));
|
|
818
|
+
for (const row of hit) {
|
|
819
|
+
for (const [k, v] of Object.entries(set)) {
|
|
820
|
+
const expr = columnExprOf(v);
|
|
821
|
+
if (expr !== null && expr.fn === "now") {
|
|
822
|
+
row[k] = /* @__PURE__ */ new Date();
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
if (expr !== null) {
|
|
826
|
+
row[k] = addDecimal(row[k], expr.by, expr.fn === "inc" ? 1 : -1);
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
row[k] = v;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
391
832
|
return hit;
|
|
392
833
|
},
|
|
393
834
|
async deleteMany(table, where) {
|
|
394
835
|
if (Object.keys(where).length === 0) throw new Error(`deleteMany(${table}): bo\u015F filtre`);
|
|
395
836
|
assertUsableFilter("deleteMany", table, where);
|
|
837
|
+
refuseFragment("deleteMany", table, where);
|
|
396
838
|
const list = store.get(table) ?? [];
|
|
397
|
-
const keep = list.filter((r) => !
|
|
839
|
+
const keep = list.filter((r) => !rowMatchesFilter("deleteMany", table, r, where));
|
|
398
840
|
store.set(table, keep);
|
|
399
841
|
return list.length - keep.length;
|
|
400
842
|
},
|
|
401
843
|
async count(table, where = {}) {
|
|
402
844
|
assertUsableFilter("count", table, where);
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
).length;
|
|
845
|
+
refuseFragment("count", table, where);
|
|
846
|
+
return (store.get(table) ?? []).filter((r) => rowMatchesFilter("count", table, r, where)).length;
|
|
406
847
|
},
|
|
407
848
|
async search(_table, _params) {
|
|
408
849
|
return [];
|
|
@@ -417,25 +858,90 @@ function createMockDB() {
|
|
|
417
858
|
return [];
|
|
418
859
|
},
|
|
419
860
|
async supersede(_table, _id, row) {
|
|
420
|
-
return {
|
|
861
|
+
return {
|
|
862
|
+
id: crypto.randomUUID(),
|
|
863
|
+
...row
|
|
864
|
+
};
|
|
421
865
|
},
|
|
422
866
|
async query(_sql, _params) {
|
|
423
867
|
return [];
|
|
424
868
|
},
|
|
425
869
|
async insert(table, data) {
|
|
426
870
|
assertUsableWriteValues("insert", table, Object.keys(data), data);
|
|
427
|
-
|
|
871
|
+
assertNoExpressionHandles("insert", table, Object.keys(data), data);
|
|
872
|
+
const record = {
|
|
873
|
+
id: crypto.randomUUID(),
|
|
874
|
+
...data
|
|
875
|
+
};
|
|
428
876
|
rowsOf(table).push(record);
|
|
429
877
|
track(tracked.inserted, table, record);
|
|
430
878
|
return record;
|
|
431
879
|
},
|
|
880
|
+
/**
|
|
881
|
+
* `claim` — motorla PARİTE (FR-033).
|
|
882
|
+
*
|
|
883
|
+
* Fake bunu sunmasaydı `claim` ile yazılmış bir servis, iskelenin yazarlara
|
|
884
|
+
* önerdiği test yolunda `undefined is not a function` verirdi; sunup da
|
|
885
|
+
* FARKLI davransaydı (ör. hep `inserted: true`) idempotency testi üretimde
|
|
886
|
+
* çalışmayan koda karşı yeşil olurdu.
|
|
887
|
+
*
|
|
888
|
+
* Anahtar ZATEN VARSA var olan satır dönüyor ve hiçbir şey yazılmıyor —
|
|
889
|
+
* motorun 23505 dalının aynısı.
|
|
890
|
+
*/
|
|
891
|
+
/**
|
|
892
|
+
* `lockRows` — sahte depoda kilit YOKTUR, ama çağrı da patlamamalı.
|
|
893
|
+
*
|
|
894
|
+
* Motorla parite burada "aynı SQL" değil, "aynı SÖZLEŞME": boş liste
|
|
895
|
+
* no-op, tekrar edenler tekilleşir, ve PK'sı olmayan tablo adıyla
|
|
896
|
+
* reddedilir. Kilidin kendisi tek işlemli bir sahte depoda anlamsız —
|
|
897
|
+
* ama sözleşmeyi bozan bir çağrı burada da hata almalı.
|
|
898
|
+
*/
|
|
899
|
+
async lockRows(table, ids) {
|
|
900
|
+
if (ids.length === 0) return;
|
|
901
|
+
const rows = store.get(table) ?? [];
|
|
902
|
+
const unique = [
|
|
903
|
+
...new Set(ids)
|
|
904
|
+
].sort();
|
|
905
|
+
const missing = unique.filter((id) => !rows.some((r) => r["id"] === id));
|
|
906
|
+
if (missing.length > 0 && rows.length > 0) {
|
|
907
|
+
throw new Error(`lockRows(${table}): \u015Fu id'ler yok: ${missing.join(", ")} \u2014 kilitlenecek sat\u0131r bulunamad\u0131.`);
|
|
908
|
+
}
|
|
909
|
+
},
|
|
910
|
+
/** Sahte depoda kilit yok; sözleşme (çağrı patlamaz) korunuyor. */
|
|
911
|
+
async advisoryXactLock(_key) {
|
|
912
|
+
return void 0;
|
|
913
|
+
},
|
|
914
|
+
async claim(table, unique, extra = {}) {
|
|
915
|
+
const keyCols = Object.keys(unique);
|
|
916
|
+
if (keyCols.length === 0) {
|
|
917
|
+
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.`);
|
|
918
|
+
}
|
|
919
|
+
assertUsableWriteValues("claim", table, keyCols, unique);
|
|
920
|
+
const existing = (store.get(table) ?? []).find((r) => keyCols.every((c) => r[c] === unique[c]));
|
|
921
|
+
if (existing) return {
|
|
922
|
+
inserted: false,
|
|
923
|
+
row: existing
|
|
924
|
+
};
|
|
925
|
+
const record = {
|
|
926
|
+
id: crypto.randomUUID(),
|
|
927
|
+
...unique,
|
|
928
|
+
...extra
|
|
929
|
+
};
|
|
930
|
+
rowsOf(table).push(record);
|
|
931
|
+
track(tracked.inserted, table, record);
|
|
932
|
+
return {
|
|
933
|
+
inserted: true,
|
|
934
|
+
row: record
|
|
935
|
+
};
|
|
936
|
+
},
|
|
432
937
|
// Same semantics the engine's SQL has: match on the conflict columns, update
|
|
433
938
|
// everything else, and return the resulting row either way.
|
|
434
|
-
async
|
|
939
|
+
async put(table, data, opts) {
|
|
435
940
|
if (opts.onConflict.length === 0) {
|
|
436
|
-
throw new Error(`
|
|
941
|
+
throw new Error(`put into ${table}: onConflict en az bir kolon ad\u0131 ister`);
|
|
437
942
|
}
|
|
438
943
|
assertUsableWriteValues("upsert", table, Object.keys(data), data);
|
|
944
|
+
assertNoExpressionHandles("upsert", table, Object.keys(data), data);
|
|
439
945
|
const rows = rowsOf(table);
|
|
440
946
|
const existing = rows.find((r) => opts.onConflict.every((c) => r[c] === data[c]));
|
|
441
947
|
if (existing) {
|
|
@@ -444,16 +950,26 @@ function createMockDB() {
|
|
|
444
950
|
}
|
|
445
951
|
return existing;
|
|
446
952
|
}
|
|
447
|
-
const record = {
|
|
953
|
+
const record = {
|
|
954
|
+
id: crypto.randomUUID(),
|
|
955
|
+
...data
|
|
956
|
+
};
|
|
448
957
|
rows.push(record);
|
|
449
958
|
track(tracked.inserted, table, record);
|
|
450
959
|
return record;
|
|
451
960
|
},
|
|
452
961
|
async update(table, id, data) {
|
|
453
962
|
assertUsableWriteValues("update", table, Object.keys(data), data);
|
|
963
|
+
assertNoExpressionHandles("update", table, Object.keys(data), data);
|
|
454
964
|
const rows = store.get(table) ?? [];
|
|
455
965
|
const idx = rows.findIndex((r) => r["id"] === id);
|
|
456
|
-
const updated = idx >= 0 ? {
|
|
966
|
+
const updated = idx >= 0 ? {
|
|
967
|
+
...rows[idx],
|
|
968
|
+
...data
|
|
969
|
+
} : {
|
|
970
|
+
id,
|
|
971
|
+
...data
|
|
972
|
+
};
|
|
457
973
|
if (idx >= 0) {
|
|
458
974
|
rows[idx] = updated;
|
|
459
975
|
}
|
|
@@ -466,7 +982,9 @@ function createMockDB() {
|
|
|
466
982
|
if (idx >= 0) rows.splice(idx, 1);
|
|
467
983
|
const list = tracked.deleted.get(table);
|
|
468
984
|
if (list) list.push(id);
|
|
469
|
-
else tracked.deleted.set(table, [
|
|
985
|
+
else tracked.deleted.set(table, [
|
|
986
|
+
id
|
|
987
|
+
]);
|
|
470
988
|
},
|
|
471
989
|
async findById(table, id) {
|
|
472
990
|
const rows = store.get(table) ?? [];
|
|
@@ -478,51 +996,60 @@ function createMockDB() {
|
|
|
478
996
|
// thing a stand-in must never do.
|
|
479
997
|
async findMany(table, query, opts) {
|
|
480
998
|
assertUsableFilter("findMany", table, query);
|
|
999
|
+
refuseFragment("findMany", table, query);
|
|
481
1000
|
const rows = store.get(table) ?? [];
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
1001
|
+
let out = query ? rows.filter((row) => rowMatchesFilter("findMany", table, row, query)) : [
|
|
1002
|
+
...rows
|
|
1003
|
+
];
|
|
1004
|
+
const orderSpecs = opts?.orderBy === void 0 ? [] : Array.isArray(opts.orderBy) ? opts.orderBy : [
|
|
1005
|
+
opts.orderBy
|
|
1006
|
+
];
|
|
1007
|
+
if (orderSpecs.length > 0) {
|
|
1008
|
+
out = [
|
|
1009
|
+
...out
|
|
1010
|
+
].sort((a, b) => {
|
|
1011
|
+
for (const o of orderSpecs) {
|
|
1012
|
+
const dir = o.direction === "desc" ? -1 : 1;
|
|
1013
|
+
const x = a[o.column];
|
|
1014
|
+
const y = b[o.column];
|
|
1015
|
+
const xNull = x === null || x === void 0;
|
|
1016
|
+
const yNull = y === null || y === void 0;
|
|
1017
|
+
if (xNull || yNull) {
|
|
1018
|
+
if (xNull && yNull) continue;
|
|
1019
|
+
const nullsFirst = o.nulls === void 0 ? dir === -1 : o.nulls === "first";
|
|
1020
|
+
return (xNull ? 1 : -1) * (nullsFirst ? -1 : 1);
|
|
501
1021
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
let out = query ? rows.filter((row) => Object.entries(query).every(([k, c]) => matches2(row, k, c))) : [...rows];
|
|
507
|
-
const order = opts?.orderBy;
|
|
508
|
-
if (order) {
|
|
509
|
-
const dir = order.direction === "desc" ? -1 : 1;
|
|
510
|
-
out = [...out].sort((a, b) => {
|
|
511
|
-
const x = a[order.column];
|
|
512
|
-
const y = b[order.column];
|
|
513
|
-
return x === y ? 0 : (x < y ? -1 : 1) * dir;
|
|
1022
|
+
if (x === y) continue;
|
|
1023
|
+
return (x < y ? -1 : 1) * dir;
|
|
1024
|
+
}
|
|
1025
|
+
return 0;
|
|
514
1026
|
});
|
|
515
1027
|
}
|
|
516
|
-
|
|
1028
|
+
const page = opts?.limit === void 0 ? out : out.slice(0, opts.limit);
|
|
1029
|
+
const cols = opts?.select;
|
|
1030
|
+
if (cols === void 0 || cols.length === 0) return page;
|
|
1031
|
+
return page.map((row) => Object.fromEntries(cols.map((c) => [
|
|
1032
|
+
c,
|
|
1033
|
+
row[c]
|
|
1034
|
+
])));
|
|
517
1035
|
}
|
|
518
1036
|
};
|
|
519
1037
|
async function txPlan(plan) {
|
|
520
1038
|
const snapshot = /* @__PURE__ */ new Map();
|
|
521
|
-
for (const [table, rows] of store) snapshot.set(table, [
|
|
1039
|
+
for (const [table, rows] of store) snapshot.set(table, [
|
|
1040
|
+
...rows
|
|
1041
|
+
]);
|
|
522
1042
|
const trackedSnapshot = {
|
|
523
1043
|
inserted: cloneTracked(tracked.inserted),
|
|
524
1044
|
updated: cloneTracked(tracked.updated),
|
|
525
|
-
deleted: new Map([
|
|
1045
|
+
deleted: new Map([
|
|
1046
|
+
...tracked.deleted
|
|
1047
|
+
].map(([k, v]) => [
|
|
1048
|
+
k,
|
|
1049
|
+
[
|
|
1050
|
+
...v
|
|
1051
|
+
]
|
|
1052
|
+
]))
|
|
526
1053
|
};
|
|
527
1054
|
const results = [];
|
|
528
1055
|
try {
|
|
@@ -540,8 +1067,11 @@ function createMockDB() {
|
|
|
540
1067
|
tracked.deleted = trackedSnapshot.deleted;
|
|
541
1068
|
throw err;
|
|
542
1069
|
}
|
|
543
|
-
return {
|
|
1070
|
+
return {
|
|
1071
|
+
results
|
|
1072
|
+
};
|
|
544
1073
|
}
|
|
1074
|
+
__name(txPlan, "txPlan");
|
|
545
1075
|
function applyOp(op, results) {
|
|
546
1076
|
switch (op.op) {
|
|
547
1077
|
case "upsert": {
|
|
@@ -553,27 +1083,54 @@ function createMockDB() {
|
|
|
553
1083
|
for (const [key, value] of Object.entries(values)) {
|
|
554
1084
|
if (!conflict.includes(key)) hit[key] = value;
|
|
555
1085
|
}
|
|
556
|
-
return {
|
|
1086
|
+
return {
|
|
1087
|
+
rows: [
|
|
1088
|
+
hit
|
|
1089
|
+
],
|
|
1090
|
+
rows_affected: 1
|
|
1091
|
+
};
|
|
557
1092
|
}
|
|
558
|
-
const created = {
|
|
1093
|
+
const created = {
|
|
1094
|
+
id: crypto.randomUUID(),
|
|
1095
|
+
...values
|
|
1096
|
+
};
|
|
559
1097
|
rows.push(created);
|
|
560
1098
|
track(tracked.inserted, op.table, created);
|
|
561
|
-
return {
|
|
1099
|
+
return {
|
|
1100
|
+
rows: [
|
|
1101
|
+
created
|
|
1102
|
+
],
|
|
1103
|
+
rows_affected: 1
|
|
1104
|
+
};
|
|
562
1105
|
}
|
|
563
1106
|
case "insert": {
|
|
564
|
-
const record = {
|
|
1107
|
+
const record = {
|
|
1108
|
+
id: crypto.randomUUID(),
|
|
1109
|
+
...resolveMap(op.values ?? {}, results, null)
|
|
1110
|
+
};
|
|
565
1111
|
rowsOf(op.table).push(record);
|
|
566
1112
|
track(tracked.inserted, op.table, record);
|
|
567
|
-
return {
|
|
1113
|
+
return {
|
|
1114
|
+
rows: [
|
|
1115
|
+
record
|
|
1116
|
+
],
|
|
1117
|
+
rows_affected: 1
|
|
1118
|
+
};
|
|
568
1119
|
}
|
|
569
1120
|
case "insertMany": {
|
|
570
1121
|
const written = (op.rows ?? []).map((row) => {
|
|
571
|
-
const record = {
|
|
1122
|
+
const record = {
|
|
1123
|
+
id: crypto.randomUUID(),
|
|
1124
|
+
...resolveMap(row, results, null)
|
|
1125
|
+
};
|
|
572
1126
|
rowsOf(op.table).push(record);
|
|
573
1127
|
track(tracked.inserted, op.table, record);
|
|
574
1128
|
return record;
|
|
575
1129
|
});
|
|
576
|
-
return {
|
|
1130
|
+
return {
|
|
1131
|
+
rows: written,
|
|
1132
|
+
rows_affected: written.length
|
|
1133
|
+
};
|
|
577
1134
|
}
|
|
578
1135
|
case "update": {
|
|
579
1136
|
const rows = rowsOf(op.table);
|
|
@@ -582,12 +1139,18 @@ function createMockDB() {
|
|
|
582
1139
|
for (let i = 0; i < rows.length; i++) {
|
|
583
1140
|
const row = rows[i];
|
|
584
1141
|
if (!row || !matches(row, where)) continue;
|
|
585
|
-
const next = {
|
|
1142
|
+
const next = {
|
|
1143
|
+
...row,
|
|
1144
|
+
...resolveMap(op.set ?? {}, results, row)
|
|
1145
|
+
};
|
|
586
1146
|
rows[i] = next;
|
|
587
1147
|
track(tracked.updated, op.table, next);
|
|
588
1148
|
written.push(next);
|
|
589
1149
|
}
|
|
590
|
-
return {
|
|
1150
|
+
return {
|
|
1151
|
+
rows: written,
|
|
1152
|
+
rows_affected: written.length
|
|
1153
|
+
};
|
|
591
1154
|
}
|
|
592
1155
|
case "delete": {
|
|
593
1156
|
const rows = rowsOf(op.table);
|
|
@@ -599,23 +1162,32 @@ function createMockDB() {
|
|
|
599
1162
|
const list = tracked.deleted.get(op.table);
|
|
600
1163
|
const key = typeof id === "string" ? id : String(id);
|
|
601
1164
|
if (list) list.push(key);
|
|
602
|
-
else tracked.deleted.set(op.table, [
|
|
1165
|
+
else tracked.deleted.set(op.table, [
|
|
1166
|
+
key
|
|
1167
|
+
]);
|
|
603
1168
|
}
|
|
604
|
-
return {
|
|
1169
|
+
return {
|
|
1170
|
+
rows: removed,
|
|
1171
|
+
rows_affected: removed.length
|
|
1172
|
+
};
|
|
605
1173
|
}
|
|
606
1174
|
case "select": {
|
|
607
1175
|
const where = resolveMap(op.where ?? {}, results, null);
|
|
608
1176
|
let found = rowsOf(op.table).filter((row) => matches(row, where));
|
|
609
1177
|
if (op.limit !== void 0) found = found.slice(0, op.limit);
|
|
610
|
-
return {
|
|
1178
|
+
return {
|
|
1179
|
+
rows: found,
|
|
1180
|
+
rows_affected: found.length
|
|
1181
|
+
};
|
|
611
1182
|
}
|
|
612
1183
|
}
|
|
613
1184
|
}
|
|
1185
|
+
__name(applyOp, "applyOp");
|
|
614
1186
|
const client = {
|
|
615
1187
|
...ops,
|
|
616
1188
|
// No real savepoint in memory: the fake runs the callback against the SAME
|
|
617
1189
|
// store. An assertion about rollback here would be asserting the fake.
|
|
618
|
-
attempt: (fn) => fn(ops),
|
|
1190
|
+
attempt: /* @__PURE__ */ __name((fn) => fn(ops), "attempt"),
|
|
619
1191
|
txPlan,
|
|
620
1192
|
// In tests there is no real DB role; `asService()` returns the same
|
|
621
1193
|
// in-memory client so RLS-bypass code paths still hit the same store and
|
|
@@ -634,14 +1206,25 @@ function createMockDB() {
|
|
|
634
1206
|
return tracked.deleted.get(table) ?? [];
|
|
635
1207
|
},
|
|
636
1208
|
seed(table, data) {
|
|
637
|
-
store.set(table, [
|
|
1209
|
+
store.set(table, [
|
|
1210
|
+
...data
|
|
1211
|
+
]);
|
|
638
1212
|
}
|
|
639
1213
|
};
|
|
640
1214
|
return client;
|
|
641
1215
|
}
|
|
1216
|
+
__name(createMockDB, "createMockDB");
|
|
642
1217
|
function cloneTracked(map) {
|
|
643
|
-
return new Map([
|
|
1218
|
+
return new Map([
|
|
1219
|
+
...map
|
|
1220
|
+
].map(([k, v]) => [
|
|
1221
|
+
k,
|
|
1222
|
+
[
|
|
1223
|
+
...v
|
|
1224
|
+
]
|
|
1225
|
+
]));
|
|
644
1226
|
}
|
|
1227
|
+
__name(cloneTracked, "cloneTracked");
|
|
645
1228
|
function resolveValue(value, results, current, column) {
|
|
646
1229
|
if (typeof value !== "object" || value === null) return value;
|
|
647
1230
|
const tagged = value;
|
|
@@ -663,6 +1246,7 @@ function resolveValue(value, results, current, column) {
|
|
|
663
1246
|
}
|
|
664
1247
|
return value;
|
|
665
1248
|
}
|
|
1249
|
+
__name(resolveValue, "resolveValue");
|
|
666
1250
|
function resolveMap(map, results, current) {
|
|
667
1251
|
const out = {};
|
|
668
1252
|
for (const [key, value] of Object.entries(map)) {
|
|
@@ -670,11 +1254,11 @@ function resolveMap(map, results, current) {
|
|
|
670
1254
|
}
|
|
671
1255
|
return out;
|
|
672
1256
|
}
|
|
1257
|
+
__name(resolveMap, "resolveMap");
|
|
673
1258
|
function matches(row, where) {
|
|
674
|
-
return Object.entries(where).every(
|
|
675
|
-
([key, value]) => value === null ? row[key] === null || row[key] === void 0 : row[key] === value
|
|
676
|
-
);
|
|
1259
|
+
return Object.entries(where).every(([key, value]) => value === null ? row[key] === null || row[key] === void 0 : row[key] === value);
|
|
677
1260
|
}
|
|
1261
|
+
__name(matches, "matches");
|
|
678
1262
|
function guardFailure(guard, count) {
|
|
679
1263
|
if (!guard) return null;
|
|
680
1264
|
const ok = guard.kind === "one" ? count === 1 : guard.kind === "none" ? count === 0 : guard.kind === "atLeast" ? count >= guard.n : count <= guard.n;
|
|
@@ -684,6 +1268,7 @@ function guardFailure(guard, count) {
|
|
|
684
1268
|
message: `expected ${guard.kind} ${guard.n} row(s), got ${count}`
|
|
685
1269
|
});
|
|
686
1270
|
}
|
|
1271
|
+
__name(guardFailure, "guardFailure");
|
|
687
1272
|
function txRejection(status, code, extra) {
|
|
688
1273
|
const err = new Error(extra.message);
|
|
689
1274
|
err.status = status;
|
|
@@ -691,31 +1276,37 @@ function txRejection(status, code, extra) {
|
|
|
691
1276
|
if (extra.slot !== void 0) err.slot = extra.slot;
|
|
692
1277
|
return err;
|
|
693
1278
|
}
|
|
1279
|
+
__name(txRejection, "txRejection");
|
|
694
1280
|
|
|
695
1281
|
// src/test/fake-db.ts
|
|
696
1282
|
function fakeDatabase() {
|
|
697
1283
|
const mock = createMockDB();
|
|
698
1284
|
const queries = [];
|
|
699
1285
|
const db = Object.assign(Object.create(Object.getPrototypeOf(mock)), mock, {
|
|
700
|
-
query: async (sql, params = []) => {
|
|
701
|
-
queries.push({
|
|
1286
|
+
query: /* @__PURE__ */ __name(async (sql, params = []) => {
|
|
1287
|
+
queries.push({
|
|
1288
|
+
sql,
|
|
1289
|
+
params
|
|
1290
|
+
});
|
|
702
1291
|
return mock.query(sql, params);
|
|
703
|
-
}
|
|
1292
|
+
}, "query")
|
|
704
1293
|
});
|
|
705
1294
|
return {
|
|
706
1295
|
db,
|
|
707
1296
|
queries,
|
|
708
|
-
seed: (table, rows) => mock.seed(table, rows),
|
|
709
|
-
inserted: (table) => mock.inserted(table),
|
|
710
|
-
updated: (table) => mock.updated(table),
|
|
711
|
-
deleted: (table) => mock.deleted(table)
|
|
1297
|
+
seed: /* @__PURE__ */ __name((table, rows) => mock.seed(table, rows), "seed"),
|
|
1298
|
+
inserted: /* @__PURE__ */ __name((table) => mock.inserted(table), "inserted"),
|
|
1299
|
+
updated: /* @__PURE__ */ __name((table) => mock.updated(table), "updated"),
|
|
1300
|
+
deleted: /* @__PURE__ */ __name((table) => mock.deleted(table), "deleted")
|
|
712
1301
|
};
|
|
713
1302
|
}
|
|
1303
|
+
__name(fakeDatabase, "fakeDatabase");
|
|
714
1304
|
// Annotate the CommonJS export names for ESM import in node:
|
|
715
1305
|
0 && (module.exports = {
|
|
716
1306
|
TestApiError,
|
|
717
1307
|
api,
|
|
718
1308
|
createTestApi,
|
|
719
|
-
fakeDatabase
|
|
1309
|
+
fakeDatabase,
|
|
1310
|
+
isolated
|
|
720
1311
|
});
|
|
721
1312
|
//# sourceMappingURL=index.cjs.map
|