@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.
Files changed (104) hide show
  1. package/dist/bin/palbase-backend.cjs +2432 -1039
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +87 -51
  4. package/dist/bin/palbase-backend.js.map +1 -1
  5. package/dist/chunk-BQN723PL.js +930 -0
  6. package/dist/chunk-BQN723PL.js.map +1 -0
  7. package/dist/chunk-CGNN2PUH.js +213 -0
  8. package/dist/chunk-CGNN2PUH.js.map +1 -0
  9. package/dist/{chunk-VDF2T4AS.js → chunk-EB3TUX5J.js} +1228 -591
  10. package/dist/chunk-EB3TUX5J.js.map +1 -0
  11. package/dist/chunk-JVZQCC77.js +728 -0
  12. package/dist/chunk-JVZQCC77.js.map +1 -0
  13. package/dist/chunk-OZKSM3JW.js +370 -0
  14. package/dist/chunk-OZKSM3JW.js.map +1 -0
  15. package/dist/{chunk-YOY5DFQS.js → chunk-TWX6JTGJ.js} +76 -34
  16. package/dist/{chunk-YOY5DFQS.js.map → chunk-TWX6JTGJ.js.map} +1 -1
  17. package/dist/{chunk-35PNTIRN.js → chunk-VVMJEVQP.js} +63 -162
  18. package/dist/chunk-VVMJEVQP.js.map +1 -0
  19. package/dist/{chunk-7D4SUZUM.js → chunk-VXPNPVAG.js} +3 -1
  20. package/dist/chunk-XABHGMUT.js +885 -0
  21. package/dist/chunk-XABHGMUT.js.map +1 -0
  22. package/dist/db/env.cjs.map +1 -1
  23. package/dist/db/env.d.cts +2 -2
  24. package/dist/db/env.d.ts +2 -2
  25. package/dist/db/index.cjs +780 -344
  26. package/dist/db/index.cjs.map +1 -1
  27. package/dist/db/index.d.cts +2 -2
  28. package/dist/db/index.d.ts +2 -2
  29. package/dist/db/index.js +7 -4
  30. package/dist/engine/index.cjs +2366 -1002
  31. package/dist/engine/index.cjs.map +1 -1
  32. package/dist/engine/index.d.cts +6 -6
  33. package/dist/engine/index.d.ts +6 -6
  34. package/dist/engine/index.js +7 -6
  35. package/dist/{index-CUomTA3e.d.ts → index-CAKOgAlP.d.ts} +171 -296
  36. package/dist/index-CgE4sVhg.d.cts +4864 -0
  37. package/dist/{index-ClpDeSos.d.cts → index-H-0qv5d4.d.cts} +171 -296
  38. package/dist/index-V7QRh1wg.d.ts +4864 -0
  39. package/dist/index.cjs +2720 -1169
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +165 -19
  42. package/dist/index.d.ts +165 -19
  43. package/dist/index.js +738 -477
  44. package/dist/index.js.map +1 -1
  45. package/dist/module-Dl1KFVtc.d.cts +54 -0
  46. package/dist/module-Dl1KFVtc.d.ts +54 -0
  47. package/dist/openapi/index.cjs +1330 -484
  48. package/dist/openapi/index.cjs.map +1 -1
  49. package/dist/openapi/index.d.cts +4 -2
  50. package/dist/openapi/index.d.ts +4 -2
  51. package/dist/openapi/index.js +1264 -474
  52. package/dist/openapi/index.js.map +1 -1
  53. package/dist/{registry-dZZ5JKYg.d.ts → registry-4EI8aaFs.d.ts} +1 -1
  54. package/dist/{registry-CC0WBQq6.d.cts → registry-DHsPDY0_.d.cts} +1 -1
  55. package/dist/stack.cjs.map +1 -1
  56. package/dist/test/index.cjs +732 -141
  57. package/dist/test/index.cjs.map +1 -1
  58. package/dist/test/index.d.cts +30 -4
  59. package/dist/test/index.d.ts +30 -4
  60. package/dist/test/index.js +490 -124
  61. package/dist/test/index.js.map +1 -1
  62. package/docs/README.md +33 -18
  63. package/docs/auth.md +1 -1
  64. package/docs/background.md +2 -2
  65. package/docs/database.md +255 -50
  66. package/docs/endpoints.md +3 -4
  67. package/docs/events.md +3 -3
  68. package/docs/getting-started.md +1 -1
  69. package/docs/llms-full.txt +435 -117
  70. package/docs/migrations.md +2 -2
  71. package/docs/schema.md +19 -10
  72. package/docs/services.md +116 -26
  73. package/package.json +8 -4
  74. package/stager/generics.js +205 -0
  75. package/stager/stage.js +39 -3
  76. package/template/AGENTS.md +110 -72
  77. package/template/db/public.ts +1 -1
  78. package/template/{controllers → modules/health}/health.controller.ts +1 -1
  79. package/template/modules/health/health.module.ts +24 -0
  80. package/template/modules/notes/note.service.test.ts +49 -0
  81. package/template/modules/notes/note.service.ts +108 -0
  82. package/template/{controllers → modules/notes}/notes.controller.ts +17 -11
  83. package/template/modules/notes/notes.module.ts +37 -0
  84. package/template/package.json +5 -3
  85. package/template/scripts/test.sh +33 -0
  86. package/template/tsconfig.json +29 -30
  87. package/dist/chunk-35PNTIRN.js.map +0 -1
  88. package/dist/chunk-CJSKYY76.js +0 -627
  89. package/dist/chunk-CJSKYY76.js.map +0 -1
  90. package/dist/chunk-CRQKCRGF.js +0 -276
  91. package/dist/chunk-CRQKCRGF.js.map +0 -1
  92. package/dist/chunk-G4R6BTLV.js +0 -662
  93. package/dist/chunk-G4R6BTLV.js.map +0 -1
  94. package/dist/chunk-VDF2T4AS.js.map +0 -1
  95. package/dist/chunk-XABBC7JP.js +0 -55
  96. package/dist/chunk-XABBC7JP.js.map +0 -1
  97. package/dist/endpoint-CTEHhb7A.d.ts +0 -2386
  98. package/dist/endpoint-DYHMo6cC.d.cts +0 -2386
  99. package/dist/index-CW21M9Z3.d.ts +0 -1222
  100. package/dist/index-CmBK76nx.d.cts +0 -1222
  101. package/template/services/note.service.test.ts +0 -45
  102. package/template/services/note.service.ts +0 -76
  103. /package/dist/{chunk-7D4SUZUM.js.map → chunk-VXPNPVAG.js.map} +0 -0
  104. /package/template/{models/notes → modules/notes/dto}/create.ts +0 -0
@@ -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 { id, prefix, difficulty };
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
- var now = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
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 ? { "x-palbase-candidate": 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({ method, path, status: res.status, ms: Date.now() - startedAt });
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
- `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(", ")}` : (
237
- // Not "you declared none": from here the two causes are
238
- // indistinguishable, and blaming the customer's config for a
239
- // platform failure sends them to look in the wrong file. The
240
- // deploy log names which one it was.
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 { id: identity.id ?? "", email: identity.email };
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
- "POST",
254
- "/auth/login",
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 ?? { id: "" };
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 KNOWN_OPS = /* @__PURE__ */ new Set(["gt", "gte", "lt", "lte", "neq", "eq", "in"]);
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, [row]);
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
- (r) => Object.entries(where).every(([k, v]) => r[k] === v)
389
- );
390
- for (const row of hit) Object.assign(row, set);
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) => !Object.entries(where).every(([k, v]) => r[k] === v));
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
- return (store.get(table) ?? []).filter(
404
- (r) => Object.entries(where).every(([k, v]) => r[k] === v)
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 { id: crypto.randomUUID(), ...row };
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
- const record = { id: crypto.randomUUID(), ...data };
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 upsert(table, data, opts) {
939
+ async put(table, data, opts) {
435
940
  if (opts.onConflict.length === 0) {
436
- throw new Error(`upsert into ${table}: onConflict en az bir kolon ad\u0131 ister`);
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 = { id: crypto.randomUUID(), ...data };
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 ? { ...rows[idx], ...data } : { id, ...data };
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, [id]);
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
- const matches2 = (row, key, cond) => {
483
- if (cond !== null && typeof cond === "object" && !Array.isArray(cond)) {
484
- return Object.entries(cond).every(([op, v]) => {
485
- const cell = row[key];
486
- switch (op) {
487
- case "in":
488
- return Array.isArray(v) && v.includes(cell);
489
- case "neq":
490
- return cell !== v;
491
- case "gt":
492
- return cell > v;
493
- case "gte":
494
- return cell >= v;
495
- case "lt":
496
- return cell < v;
497
- case "lte":
498
- return cell <= v;
499
- default:
500
- throw new Error(`findMany(${table}): where.${key} bilinmeyen operat\xF6r "${op}"`);
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
- return row[key] === cond;
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
- return opts?.limit === void 0 ? out : out.slice(0, opts.limit);
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, [...rows]);
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([...tracked.deleted].map(([k, v]) => [k, [...v]]))
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 { results };
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 { rows: [hit], rows_affected: 1 };
1086
+ return {
1087
+ rows: [
1088
+ hit
1089
+ ],
1090
+ rows_affected: 1
1091
+ };
557
1092
  }
558
- const created = { id: crypto.randomUUID(), ...values };
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 { rows: [created], rows_affected: 1 };
1099
+ return {
1100
+ rows: [
1101
+ created
1102
+ ],
1103
+ rows_affected: 1
1104
+ };
562
1105
  }
563
1106
  case "insert": {
564
- const record = { id: crypto.randomUUID(), ...resolveMap(op.values ?? {}, results, null) };
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 { rows: [record], rows_affected: 1 };
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 = { id: crypto.randomUUID(), ...resolveMap(row, results, null) };
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 { rows: written, rows_affected: written.length };
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 = { ...row, ...resolveMap(op.set ?? {}, results, row) };
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 { rows: written, rows_affected: written.length };
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, [key]);
1165
+ else tracked.deleted.set(op.table, [
1166
+ key
1167
+ ]);
603
1168
  }
604
- return { rows: removed, rows_affected: removed.length };
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 { rows: found, rows_affected: found.length };
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, [...data]);
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([...map].map(([k, v]) => [k, [...v]]));
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({ sql, params });
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