@farming-labs/orm-firestore 0.0.31

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/index.js ADDED
@@ -0,0 +1,843 @@
1
+ // src/index.ts
2
+ import { randomUUID } from "crypto";
3
+ import {
4
+ createDriverHandle,
5
+ createManifest,
6
+ equalValues,
7
+ isOperatorFilterObject,
8
+ mergeUniqueLookupCreateData,
9
+ requireUniqueLookup,
10
+ validateUniqueLookupUpdateData
11
+ } from "@farming-labs/orm";
12
+ var manifestCache = /* @__PURE__ */ new WeakMap();
13
+ function getManifest(schema) {
14
+ const cached = manifestCache.get(schema);
15
+ if (cached) return cached;
16
+ const next = createManifest(schema);
17
+ manifestCache.set(schema, next);
18
+ return next;
19
+ }
20
+ function normalizeDecimalString(value) {
21
+ const trimmed = value.trim();
22
+ const match = /^(-?\d+)(?:\.(\d+))?$/.exec(trimmed);
23
+ if (!match) {
24
+ return trimmed;
25
+ }
26
+ const [, integerPart, fractionalPart] = match;
27
+ if (!fractionalPart) {
28
+ return integerPart;
29
+ }
30
+ const normalizedFraction = fractionalPart.replace(/0+$/g, "");
31
+ return normalizedFraction.length ? `${integerPart}.${normalizedFraction}` : integerPart;
32
+ }
33
+ function isTimestampLike(value) {
34
+ return !!value && typeof value === "object" && "toDate" in value && typeof value.toDate === "function";
35
+ }
36
+ function applyDefault(value, field) {
37
+ if (value !== void 0) return value;
38
+ if (field.generated === "id") return randomUUID();
39
+ if (field.generated === "now") return /* @__PURE__ */ new Date();
40
+ if (typeof field.defaultValue === "function") {
41
+ return field.defaultValue();
42
+ }
43
+ return field.defaultValue;
44
+ }
45
+ function isComparable(value) {
46
+ return value instanceof Date || typeof value === "number" || typeof value === "string" || typeof value === "bigint";
47
+ }
48
+ function evaluateFilter(value, filter) {
49
+ if (!isOperatorFilterObject(filter)) {
50
+ return equalValues(value, filter);
51
+ }
52
+ if ("eq" in filter && !equalValues(value, filter.eq)) return false;
53
+ if ("not" in filter && equalValues(value, filter.not)) return false;
54
+ if ("in" in filter) {
55
+ const values = Array.isArray(filter.in) ? filter.in : [];
56
+ if (!values.some((candidate) => equalValues(candidate, value))) return false;
57
+ }
58
+ if ("contains" in filter) {
59
+ if (typeof value !== "string" || typeof filter.contains !== "string") return false;
60
+ if (!value.includes(filter.contains)) return false;
61
+ }
62
+ if ("gt" in filter) {
63
+ if (!isComparable(value) || value <= filter.gt) return false;
64
+ }
65
+ if ("gte" in filter) {
66
+ if (!isComparable(value) || value < filter.gte) return false;
67
+ }
68
+ if ("lt" in filter) {
69
+ if (!isComparable(value) || value >= filter.lt) return false;
70
+ }
71
+ if ("lte" in filter) {
72
+ if (!isComparable(value) || value > filter.lte) return false;
73
+ }
74
+ return true;
75
+ }
76
+ function matchesWhere(record, where) {
77
+ if (!where) return true;
78
+ if (where.AND && !where.AND.every((clause) => matchesWhere(record, clause))) {
79
+ return false;
80
+ }
81
+ if (where.OR && !where.OR.some((clause) => matchesWhere(record, clause))) {
82
+ return false;
83
+ }
84
+ if (where.NOT && matchesWhere(record, where.NOT)) {
85
+ return false;
86
+ }
87
+ for (const [key, filter] of Object.entries(where)) {
88
+ if (key === "AND" || key === "OR" || key === "NOT") continue;
89
+ if (!evaluateFilter(record[key], filter)) return false;
90
+ }
91
+ return true;
92
+ }
93
+ function sortRows(rows, orderBy) {
94
+ if (!orderBy) return rows;
95
+ const entries = Object.entries(orderBy);
96
+ if (!entries.length) return rows;
97
+ return [...rows].sort((left, right) => {
98
+ for (const [field, direction] of entries) {
99
+ const a = left.data[field];
100
+ const b = right.data[field];
101
+ if (equalValues(a, b)) continue;
102
+ if (a === void 0) return direction === "asc" ? -1 : 1;
103
+ if (b === void 0) return direction === "asc" ? 1 : -1;
104
+ if (a == null) return direction === "asc" ? -1 : 1;
105
+ if (b == null) return direction === "asc" ? 1 : -1;
106
+ if (a < b) return direction === "asc" ? -1 : 1;
107
+ if (a > b) return direction === "asc" ? 1 : -1;
108
+ }
109
+ return 0;
110
+ });
111
+ }
112
+ function pageRows(rows, skip, take) {
113
+ const start = skip ?? 0;
114
+ const end = take === void 0 ? void 0 : start + take;
115
+ return rows.slice(start, end);
116
+ }
117
+ function applyQuery(rows, args = {}) {
118
+ const filtered = rows.filter((row) => matchesWhere(row.data, args.where));
119
+ const sorted = sortRows(filtered, args.orderBy);
120
+ return pageRows(sorted, args.skip, args.take);
121
+ }
122
+ function firestoreConstraintError(target) {
123
+ const fields = Array.isArray(target) ? target.join(", ") : target;
124
+ const error = new Error(`Firestore unique constraint violation on ${fields}.`);
125
+ Object.assign(error, {
126
+ code: 6,
127
+ details: error.message,
128
+ target
129
+ });
130
+ return error;
131
+ }
132
+ function hasTransactionSupport(db) {
133
+ return typeof db.runTransaction === "function";
134
+ }
135
+ function createFirestoreDriverInternal(config, state = {}) {
136
+ function getSupportedManifest(schema) {
137
+ const manifest = getManifest(schema);
138
+ for (const model of Object.values(manifest.models)) {
139
+ if (model.schema) {
140
+ throw new Error(
141
+ `The Firestore runtime does not support schema-qualified tables for model "${model.name}". Use flat collection names instead.`
142
+ );
143
+ }
144
+ const idField = model.fields.id;
145
+ if (idField?.kind === "id" && idField.idType === "integer" && idField.generated === "increment") {
146
+ throw new Error(
147
+ `The Firestore runtime does not support generated integer ids for model "${model.name}". Use manual numeric ids or a string id instead.`
148
+ );
149
+ }
150
+ }
151
+ return manifest;
152
+ }
153
+ function getCollection(schema, modelName) {
154
+ const manifest = getSupportedManifest(schema);
155
+ return config.collections?.[modelName] ?? config.db.collection(manifest.models[modelName].table);
156
+ }
157
+ function fieldTransform(modelName, fieldName) {
158
+ return config.transforms?.[modelName]?.[fieldName];
159
+ }
160
+ function encodeValue(modelName, field, value) {
161
+ if (value === void 0) return value;
162
+ if (value === null) return null;
163
+ const transform = fieldTransform(modelName, field.name);
164
+ if (transform?.encode) {
165
+ return transform.encode(value);
166
+ }
167
+ if (field.kind === "id" && field.idType === "integer") {
168
+ return Number(value);
169
+ }
170
+ if (field.kind === "enum") {
171
+ return String(value);
172
+ }
173
+ if (field.kind === "boolean") {
174
+ return Boolean(value);
175
+ }
176
+ if (field.kind === "integer") {
177
+ return Number(value);
178
+ }
179
+ if (field.kind === "bigint") {
180
+ return typeof value === "bigint" ? value.toString() : BigInt(value).toString();
181
+ }
182
+ if (field.kind === "decimal") {
183
+ return typeof value === "string" ? normalizeDecimalString(value) : String(value);
184
+ }
185
+ if (field.kind === "datetime") {
186
+ if (value instanceof Date) return value;
187
+ if (isTimestampLike(value)) return value;
188
+ return new Date(value);
189
+ }
190
+ return value;
191
+ }
192
+ function decodeValue(modelName, field, value, docId) {
193
+ const transform = fieldTransform(modelName, field.name);
194
+ if (transform?.decode) {
195
+ return transform.decode(value);
196
+ }
197
+ if (value === void 0 && field.kind === "id" && docId !== void 0) {
198
+ if (field.idType === "integer") {
199
+ const numeric = Number(docId);
200
+ return Number.isFinite(numeric) ? numeric : void 0;
201
+ }
202
+ return docId;
203
+ }
204
+ if (value === void 0 || value === null) return value ?? null;
205
+ if (field.kind === "id" && field.idType === "integer") {
206
+ return Number(value);
207
+ }
208
+ if (field.kind === "enum") {
209
+ return String(value);
210
+ }
211
+ if (field.kind === "boolean") {
212
+ return Boolean(value);
213
+ }
214
+ if (field.kind === "integer") {
215
+ return Number(value);
216
+ }
217
+ if (field.kind === "bigint") {
218
+ return typeof value === "bigint" ? value : BigInt(value);
219
+ }
220
+ if (field.kind === "decimal") {
221
+ return normalizeDecimalString(String(value));
222
+ }
223
+ if (field.kind === "datetime") {
224
+ if (value instanceof Date) return value;
225
+ if (isTimestampLike(value)) return value.toDate();
226
+ return new Date(value);
227
+ }
228
+ return value;
229
+ }
230
+ function buildStoredRow(model, data) {
231
+ const stored = {};
232
+ const decoded = {};
233
+ for (const field of Object.values(model.fields)) {
234
+ const value = applyDefault(data[field.name], field);
235
+ if (value === void 0) continue;
236
+ const encoded = encodeValue(model.name, field, value);
237
+ stored[field.column] = encoded;
238
+ decoded[field.name] = decodeValue(model.name, field, encoded);
239
+ }
240
+ const idField = model.fields.id;
241
+ if (!idField) {
242
+ return {
243
+ docId: randomUUID(),
244
+ stored,
245
+ decoded
246
+ };
247
+ }
248
+ let idValue = decoded[idField.name];
249
+ if (idValue === void 0 || idValue === null) {
250
+ idValue = randomUUID();
251
+ const encodedId = encodeValue(model.name, idField, idValue);
252
+ stored[idField.column] = encodedId;
253
+ decoded[idField.name] = decodeValue(model.name, idField, encodedId);
254
+ idValue = decoded[idField.name];
255
+ }
256
+ return {
257
+ docId: String(idValue),
258
+ stored,
259
+ decoded
260
+ };
261
+ }
262
+ function buildUpdatedRow(model, current, data) {
263
+ const stored = {
264
+ ...current.stored
265
+ };
266
+ const decoded = {
267
+ ...current.data
268
+ };
269
+ for (const [fieldName, value] of Object.entries(data)) {
270
+ if (value === void 0) continue;
271
+ const field = model.fields[fieldName];
272
+ if (!field) {
273
+ throw new Error(`Unknown field "${fieldName}" on model "${model.name}".`);
274
+ }
275
+ const encoded = encodeValue(model.name, field, value);
276
+ stored[field.column] = encoded;
277
+ decoded[field.name] = decodeValue(model.name, field, encoded);
278
+ }
279
+ const idField = model.fields.id;
280
+ const docId = idField && decoded[idField.name] !== void 0 && decoded[idField.name] !== null ? String(decoded[idField.name]) : current.docId;
281
+ return {
282
+ docId,
283
+ stored,
284
+ decoded
285
+ };
286
+ }
287
+ async function getQuerySnapshot(query) {
288
+ if (state.transaction) {
289
+ const result = await state.transaction.get(query);
290
+ if ("docs" in result) {
291
+ return result;
292
+ }
293
+ return {
294
+ docs: result.exists ? [result] : []
295
+ };
296
+ }
297
+ return query.get();
298
+ }
299
+ async function getDocumentSnapshot(reference) {
300
+ if (state.transaction) {
301
+ const result = await state.transaction.get(reference);
302
+ if ("exists" in result) {
303
+ return result;
304
+ }
305
+ return result.docs[0] ?? {
306
+ id: reference.id,
307
+ exists: false,
308
+ data: () => void 0,
309
+ ref: reference
310
+ };
311
+ }
312
+ return reference.get();
313
+ }
314
+ async function loadRows(schema, modelName) {
315
+ const model = getSupportedManifest(schema).models[modelName];
316
+ const collection = getCollection(schema, modelName);
317
+ const snapshot = await getQuerySnapshot(collection);
318
+ return snapshot.docs.filter((doc) => doc.exists).map((doc) => {
319
+ const stored = doc.data() ?? {};
320
+ const decoded = {};
321
+ for (const field of Object.values(model.fields)) {
322
+ decoded[field.name] = decodeValue(model.name, field, stored[field.column], doc.id);
323
+ }
324
+ return {
325
+ docId: doc.id,
326
+ ref: doc.ref ?? collection.doc(doc.id),
327
+ data: decoded,
328
+ stored
329
+ };
330
+ });
331
+ }
332
+ async function loadUniqueRow(schema, modelName, where) {
333
+ const manifest = getSupportedManifest(schema);
334
+ const model = manifest.models[modelName];
335
+ const lookup = requireUniqueLookup(model, where, "FindUnique");
336
+ if (lookup.kind === "id") {
337
+ const reference = getCollection(schema, modelName).doc(
338
+ String(lookup.values[lookup.fields[0].name])
339
+ );
340
+ const doc = await getDocumentSnapshot(reference);
341
+ if (!doc.exists) return null;
342
+ const stored = doc.data() ?? {};
343
+ const decoded = {};
344
+ for (const field of Object.values(model.fields)) {
345
+ decoded[field.name] = decodeValue(model.name, field, stored[field.column], doc.id);
346
+ }
347
+ return {
348
+ docId: doc.id,
349
+ ref: doc.ref ?? reference,
350
+ data: decoded,
351
+ stored
352
+ };
353
+ }
354
+ const rows = await loadRows(schema, modelName);
355
+ return rows.find((row) => matchesModelWhere(model, row.data, where)) ?? null;
356
+ }
357
+ function findUniqueConflict(model, candidate, existingRows, ignoreDocIds = /* @__PURE__ */ new Set()) {
358
+ const idField = model.fields.id;
359
+ for (const row of existingRows) {
360
+ if (ignoreDocIds.has(row.docId)) continue;
361
+ if (idField && candidate[idField.name] !== void 0 && candidate[idField.name] !== null && row.data[idField.name] !== void 0 && row.data[idField.name] !== null && equalValues(candidate[idField.name], row.data[idField.name])) {
362
+ return [idField.name];
363
+ }
364
+ for (const field of Object.values(model.fields)) {
365
+ if (!field.unique) continue;
366
+ if (candidate[field.name] === void 0 || candidate[field.name] === null) continue;
367
+ if (row.data[field.name] === void 0 || row.data[field.name] === null) continue;
368
+ if (equalValues(candidate[field.name], row.data[field.name])) {
369
+ return [field.name];
370
+ }
371
+ }
372
+ for (const constraint of model.constraints.unique) {
373
+ if (!constraint.fields.every(
374
+ (fieldName) => candidate[fieldName] !== void 0 && candidate[fieldName] !== null && row.data[fieldName] !== void 0 && row.data[fieldName] !== null
375
+ )) {
376
+ continue;
377
+ }
378
+ if (constraint.fields.every(
379
+ (fieldName) => equalValues(candidate[fieldName], row.data[fieldName])
380
+ )) {
381
+ return [...constraint.fields];
382
+ }
383
+ }
384
+ }
385
+ return null;
386
+ }
387
+ async function writeDocument(schema, modelName, next, previousDocId) {
388
+ const collection = getCollection(schema, modelName);
389
+ const reference = collection.doc(next.docId);
390
+ if (state.transaction) {
391
+ state.transaction.set(reference, next.stored);
392
+ if (previousDocId && previousDocId !== next.docId) {
393
+ state.transaction.delete(collection.doc(previousDocId));
394
+ }
395
+ return;
396
+ }
397
+ await reference.set(next.stored);
398
+ if (previousDocId && previousDocId !== next.docId) {
399
+ await collection.doc(previousDocId).delete();
400
+ }
401
+ }
402
+ async function deleteDocument(schema, modelName, docId) {
403
+ const reference = getCollection(schema, modelName).doc(docId);
404
+ if (state.transaction) {
405
+ state.transaction.delete(reference);
406
+ return;
407
+ }
408
+ await reference.delete();
409
+ }
410
+ async function projectRow(schema, modelName, row, select) {
411
+ const modelDefinition = schema.models[modelName];
412
+ const output = {};
413
+ if (!select) {
414
+ for (const fieldName of Object.keys(modelDefinition.fields)) {
415
+ output[fieldName] = row[fieldName];
416
+ }
417
+ return output;
418
+ }
419
+ for (const [key, value] of Object.entries(select)) {
420
+ if (value !== true && value === void 0) continue;
421
+ if (key in modelDefinition.fields && value === true) {
422
+ output[key] = row[key];
423
+ continue;
424
+ }
425
+ if (key in modelDefinition.relations) {
426
+ output[key] = await resolveRelation(
427
+ schema,
428
+ modelName,
429
+ key,
430
+ row,
431
+ value
432
+ );
433
+ }
434
+ }
435
+ return output;
436
+ }
437
+ async function resolveRelation(schema, modelName, relationName, row, value) {
438
+ const relation = schema.models[modelName].relations[relationName];
439
+ const relationArgs = value === true ? {} : value;
440
+ if (relation.kind === "belongsTo") {
441
+ const foreignValue = row[relation.foreignKey];
442
+ const targetRows2 = (await loadRows(schema, relation.target)).filter(
443
+ (item) => equalValues(item.data.id, foreignValue)
444
+ );
445
+ const target = applyQuery(targetRows2, relationArgs)[0];
446
+ return target ? projectRow(
447
+ schema,
448
+ relation.target,
449
+ target.data,
450
+ relationArgs.select
451
+ ) : null;
452
+ }
453
+ if (relation.kind === "hasOne") {
454
+ const targetRows2 = (await loadRows(schema, relation.target)).filter(
455
+ (item) => equalValues(item.data[relation.foreignKey], row.id)
456
+ );
457
+ const target = applyQuery(targetRows2, relationArgs)[0];
458
+ return target ? projectRow(
459
+ schema,
460
+ relation.target,
461
+ target.data,
462
+ relationArgs.select
463
+ ) : null;
464
+ }
465
+ if (relation.kind === "hasMany") {
466
+ const targetRows2 = (await loadRows(schema, relation.target)).filter(
467
+ (item) => equalValues(item.data[relation.foreignKey], row.id)
468
+ );
469
+ const matchedRows2 = applyQuery(targetRows2, relationArgs);
470
+ return Promise.all(
471
+ matchedRows2.map(
472
+ (item) => projectRow(schema, relation.target, item.data, relationArgs.select)
473
+ )
474
+ );
475
+ }
476
+ const throughRows = (await loadRows(schema, relation.through)).filter(
477
+ (item) => equalValues(item.data[relation.from], row.id)
478
+ );
479
+ const targetIds = throughRows.map((item) => item.data[relation.to]);
480
+ const targetRows = (await loadRows(schema, relation.target)).filter(
481
+ (item) => targetIds.some((targetId) => equalValues(targetId, item.data.id))
482
+ );
483
+ const matchedRows = applyQuery(targetRows, relationArgs);
484
+ return Promise.all(
485
+ matchedRows.map(
486
+ (item) => projectRow(schema, relation.target, item.data, relationArgs.select)
487
+ )
488
+ );
489
+ }
490
+ function normalizeFilterValue(model, fieldName, value) {
491
+ const field = model.fields[fieldName];
492
+ if (!field || value === void 0 || value === null) {
493
+ return value;
494
+ }
495
+ return decodeValue(model.name, field, encodeValue(model.name, field, value));
496
+ }
497
+ function evaluateModelFilter(model, fieldName, value, filter) {
498
+ if (!isOperatorFilterObject(filter)) {
499
+ return equalValues(value, normalizeFilterValue(model, fieldName, filter));
500
+ }
501
+ const normalized = {
502
+ ...filter.eq !== void 0 ? { eq: normalizeFilterValue(model, fieldName, filter.eq) } : {},
503
+ ...filter.not !== void 0 ? { not: normalizeFilterValue(model, fieldName, filter.not) } : {},
504
+ ...filter.in !== void 0 ? {
505
+ in: (Array.isArray(filter.in) ? filter.in : []).map(
506
+ (entry) => normalizeFilterValue(model, fieldName, entry)
507
+ )
508
+ } : {},
509
+ ...filter.contains !== void 0 ? { contains: String(filter.contains) } : {},
510
+ ...filter.gt !== void 0 ? { gt: normalizeFilterValue(model, fieldName, filter.gt) } : {},
511
+ ...filter.gte !== void 0 ? { gte: normalizeFilterValue(model, fieldName, filter.gte) } : {},
512
+ ...filter.lt !== void 0 ? { lt: normalizeFilterValue(model, fieldName, filter.lt) } : {},
513
+ ...filter.lte !== void 0 ? { lte: normalizeFilterValue(model, fieldName, filter.lte) } : {}
514
+ };
515
+ return evaluateFilter(value, normalized);
516
+ }
517
+ function matchesModelWhere(model, record, where) {
518
+ if (!where) return true;
519
+ if (where.AND && !where.AND.every((clause) => matchesModelWhere(model, record, clause))) {
520
+ return false;
521
+ }
522
+ if (where.OR && !where.OR.some((clause) => matchesModelWhere(model, record, clause))) {
523
+ return false;
524
+ }
525
+ if (where.NOT && matchesModelWhere(model, record, where.NOT)) {
526
+ return false;
527
+ }
528
+ for (const [key, filter] of Object.entries(where)) {
529
+ if (key === "AND" || key === "OR" || key === "NOT") continue;
530
+ if (!evaluateModelFilter(model, key, record[key], filter)) return false;
531
+ }
532
+ return true;
533
+ }
534
+ function applyModelQuery(model, rows, args = {}) {
535
+ const filtered = rows.filter((row) => matchesModelWhere(model, row.data, args.where));
536
+ const sorted = sortRows(filtered, args.orderBy);
537
+ return pageRows(sorted, args.skip, args.take);
538
+ }
539
+ async function runWrite(run) {
540
+ if (state.transaction || !hasTransactionSupport(config.db)) {
541
+ return run();
542
+ }
543
+ return config.db.runTransaction(async (transaction) => {
544
+ const txDriver = createFirestoreDriverInternal(config, {
545
+ transaction
546
+ });
547
+ return run.call({
548
+ driver: txDriver
549
+ });
550
+ });
551
+ }
552
+ let driver;
553
+ driver = {
554
+ handle: createDriverHandle({
555
+ kind: "firestore",
556
+ client: {
557
+ db: config.db,
558
+ collections: config.collections
559
+ },
560
+ capabilities: {
561
+ supportsNumericIds: true,
562
+ numericIds: "manual",
563
+ supportsJSON: true,
564
+ supportsDates: true,
565
+ supportsBooleans: true,
566
+ supportsTransactions: hasTransactionSupport(config.db),
567
+ supportsSchemaNamespaces: false,
568
+ supportsTransactionalDDL: false,
569
+ supportsJoin: false,
570
+ nativeRelationLoading: "none",
571
+ textComparison: "case-sensitive",
572
+ textMatching: {
573
+ equality: "case-sensitive",
574
+ contains: "case-sensitive",
575
+ ordering: "case-sensitive"
576
+ },
577
+ upsert: "native",
578
+ returning: {
579
+ create: true,
580
+ update: true,
581
+ delete: false
582
+ },
583
+ returningMode: {
584
+ create: "record",
585
+ update: "record",
586
+ delete: "none"
587
+ },
588
+ nativeRelations: {
589
+ singularChains: false,
590
+ hasMany: false,
591
+ manyToMany: false,
592
+ filtered: false,
593
+ ordered: false,
594
+ paginated: false
595
+ }
596
+ }
597
+ }),
598
+ async findMany(schema, modelName, args) {
599
+ const model = getSupportedManifest(schema).models[modelName];
600
+ const rows = applyModelQuery(model, await loadRows(schema, modelName), args);
601
+ return Promise.all(rows.map((row) => projectRow(schema, modelName, row.data, args.select)));
602
+ },
603
+ async findFirst(schema, modelName, args) {
604
+ const model = getSupportedManifest(schema).models[modelName];
605
+ const row = applyModelQuery(model, await loadRows(schema, modelName), args)[0];
606
+ if (!row) return null;
607
+ return projectRow(schema, modelName, row.data, args.select);
608
+ },
609
+ async findUnique(schema, modelName, args) {
610
+ const model = getSupportedManifest(schema).models[modelName];
611
+ const row = await loadUniqueRow(schema, modelName, args.where);
612
+ if (!row || !matchesModelWhere(model, row.data, args.where)) {
613
+ return null;
614
+ }
615
+ return projectRow(schema, modelName, row.data, args.select);
616
+ },
617
+ async count(schema, modelName, args) {
618
+ const model = getSupportedManifest(schema).models[modelName];
619
+ return applyModelQuery(model, await loadRows(schema, modelName), args).length;
620
+ },
621
+ async create(schema, modelName, args) {
622
+ if (!state.transaction && hasTransactionSupport(config.db)) {
623
+ return config.db.runTransaction(async (transaction) => {
624
+ const txDriver = createFirestoreDriverInternal(config, {
625
+ transaction
626
+ });
627
+ return txDriver.create(schema, modelName, args);
628
+ });
629
+ }
630
+ const model = getSupportedManifest(schema).models[modelName];
631
+ const existingRows = await loadRows(schema, modelName);
632
+ const built = buildStoredRow(model, args.data);
633
+ const conflict = findUniqueConflict(model, built.decoded, existingRows);
634
+ if (conflict) {
635
+ throw firestoreConstraintError(conflict);
636
+ }
637
+ await writeDocument(schema, modelName, built);
638
+ return projectRow(schema, modelName, built.decoded, args.select);
639
+ },
640
+ async createMany(schema, modelName, args) {
641
+ if (!state.transaction && hasTransactionSupport(config.db)) {
642
+ return config.db.runTransaction(async (transaction) => {
643
+ const txDriver = createFirestoreDriverInternal(config, {
644
+ transaction
645
+ });
646
+ return txDriver.createMany(schema, modelName, args);
647
+ });
648
+ }
649
+ const model = getSupportedManifest(schema).models[modelName];
650
+ const existingRows = await loadRows(schema, modelName);
651
+ const created = [];
652
+ for (const entry of args.data) {
653
+ const built = buildStoredRow(model, entry);
654
+ const conflict = findUniqueConflict(model, built.decoded, [
655
+ ...existingRows,
656
+ ...created.map((row) => ({
657
+ docId: row.docId,
658
+ ref: getCollection(schema, modelName).doc(row.docId),
659
+ data: row.decoded,
660
+ stored: row.stored
661
+ }))
662
+ ]);
663
+ if (conflict) {
664
+ throw firestoreConstraintError(conflict);
665
+ }
666
+ created.push(built);
667
+ }
668
+ for (const row of created) {
669
+ await writeDocument(schema, modelName, row);
670
+ }
671
+ return Promise.all(
672
+ created.map((row) => projectRow(schema, modelName, row.decoded, args.select))
673
+ );
674
+ },
675
+ async update(schema, modelName, args) {
676
+ if (!state.transaction && hasTransactionSupport(config.db)) {
677
+ return config.db.runTransaction(async (transaction) => {
678
+ const txDriver = createFirestoreDriverInternal(config, {
679
+ transaction
680
+ });
681
+ return txDriver.update(schema, modelName, args);
682
+ });
683
+ }
684
+ const model = getSupportedManifest(schema).models[modelName];
685
+ const rows = await loadRows(schema, modelName);
686
+ const current = applyModelQuery(model, rows, {
687
+ where: args.where,
688
+ take: 1
689
+ })[0];
690
+ if (!current) return null;
691
+ const next = buildUpdatedRow(model, current, args.data);
692
+ const conflict = findUniqueConflict(model, next.decoded, rows, /* @__PURE__ */ new Set([current.docId]));
693
+ if (conflict) {
694
+ throw firestoreConstraintError(conflict);
695
+ }
696
+ await writeDocument(schema, modelName, next, current.docId);
697
+ return projectRow(schema, modelName, next.decoded, args.select);
698
+ },
699
+ async updateMany(schema, modelName, args) {
700
+ if (!state.transaction && hasTransactionSupport(config.db)) {
701
+ return config.db.runTransaction(async (transaction) => {
702
+ const txDriver = createFirestoreDriverInternal(config, {
703
+ transaction
704
+ });
705
+ return txDriver.updateMany(schema, modelName, args);
706
+ });
707
+ }
708
+ const model = getSupportedManifest(schema).models[modelName];
709
+ const rows = await loadRows(schema, modelName);
710
+ const matched = applyModelQuery(model, rows, {
711
+ where: args.where
712
+ });
713
+ if (!matched.length) return 0;
714
+ const nextRows = matched.map(
715
+ (row) => buildUpdatedRow(model, row, args.data)
716
+ );
717
+ const keepIds = new Set(matched.map((row) => row.docId));
718
+ const remaining = rows.filter((row) => !keepIds.has(row.docId));
719
+ const pending = [];
720
+ for (const next of nextRows) {
721
+ const conflict = findUniqueConflict(model, next.decoded, [...remaining, ...pending]);
722
+ if (conflict) {
723
+ throw firestoreConstraintError(conflict);
724
+ }
725
+ pending.push({
726
+ docId: next.docId,
727
+ ref: getCollection(schema, modelName).doc(next.docId),
728
+ data: next.decoded,
729
+ stored: next.stored
730
+ });
731
+ }
732
+ for (let index = 0; index < matched.length; index += 1) {
733
+ await writeDocument(schema, modelName, nextRows[index], matched[index].docId);
734
+ }
735
+ return nextRows.length;
736
+ },
737
+ async upsert(schema, modelName, args) {
738
+ if (!state.transaction && hasTransactionSupport(config.db)) {
739
+ return config.db.runTransaction(async (transaction) => {
740
+ const txDriver = createFirestoreDriverInternal(config, {
741
+ transaction
742
+ });
743
+ return txDriver.upsert(schema, modelName, args);
744
+ });
745
+ }
746
+ const model = getSupportedManifest(schema).models[modelName];
747
+ const lookup = requireUniqueLookup(model, args.where, "Upsert");
748
+ validateUniqueLookupUpdateData(
749
+ model,
750
+ args.update,
751
+ lookup,
752
+ "Upsert"
753
+ );
754
+ const current = await loadUniqueRow(schema, modelName, args.where);
755
+ if (current && matchesModelWhere(model, current.data, args.where)) {
756
+ const rows2 = await loadRows(schema, modelName);
757
+ const next = buildUpdatedRow(
758
+ model,
759
+ current,
760
+ args.update
761
+ );
762
+ const conflict2 = findUniqueConflict(model, next.decoded, rows2, /* @__PURE__ */ new Set([current.docId]));
763
+ if (conflict2) {
764
+ throw firestoreConstraintError(conflict2);
765
+ }
766
+ await writeDocument(schema, modelName, next, current.docId);
767
+ return projectRow(schema, modelName, next.decoded, args.select);
768
+ }
769
+ const created = buildStoredRow(
770
+ model,
771
+ mergeUniqueLookupCreateData(
772
+ model,
773
+ args.create,
774
+ lookup,
775
+ "Upsert"
776
+ )
777
+ );
778
+ const rows = await loadRows(schema, modelName);
779
+ const conflict = findUniqueConflict(model, created.decoded, rows);
780
+ if (conflict) {
781
+ throw firestoreConstraintError(conflict);
782
+ }
783
+ await writeDocument(schema, modelName, created);
784
+ return projectRow(schema, modelName, created.decoded, args.select);
785
+ },
786
+ async delete(schema, modelName, args) {
787
+ if (!state.transaction && hasTransactionSupport(config.db)) {
788
+ return config.db.runTransaction(async (transaction) => {
789
+ const txDriver = createFirestoreDriverInternal(config, {
790
+ transaction
791
+ });
792
+ return txDriver.delete(schema, modelName, args);
793
+ });
794
+ }
795
+ const model = getSupportedManifest(schema).models[modelName];
796
+ const row = applyModelQuery(model, await loadRows(schema, modelName), {
797
+ where: args.where,
798
+ take: 1
799
+ })[0];
800
+ if (!row) return 0;
801
+ await deleteDocument(schema, modelName, row.docId);
802
+ return 1;
803
+ },
804
+ async deleteMany(schema, modelName, args) {
805
+ if (!state.transaction && hasTransactionSupport(config.db)) {
806
+ return config.db.runTransaction(async (transaction) => {
807
+ const txDriver = createFirestoreDriverInternal(config, {
808
+ transaction
809
+ });
810
+ return txDriver.deleteMany(schema, modelName, args);
811
+ });
812
+ }
813
+ const model = getSupportedManifest(schema).models[modelName];
814
+ const rows = applyModelQuery(model, await loadRows(schema, modelName), {
815
+ where: args.where
816
+ });
817
+ for (const row of rows) {
818
+ await deleteDocument(schema, modelName, row.docId);
819
+ }
820
+ return rows.length;
821
+ },
822
+ async transaction(schema, run) {
823
+ getSupportedManifest(schema);
824
+ if (state.transaction || !hasTransactionSupport(config.db)) {
825
+ return run(driver);
826
+ }
827
+ return config.db.runTransaction(async (transaction) => {
828
+ const txDriver = createFirestoreDriverInternal(config, {
829
+ transaction
830
+ });
831
+ return run(txDriver);
832
+ });
833
+ }
834
+ };
835
+ return driver;
836
+ }
837
+ function createFirestoreDriver(config) {
838
+ return createFirestoreDriverInternal(config);
839
+ }
840
+ export {
841
+ createFirestoreDriver
842
+ };
843
+ //# sourceMappingURL=index.js.map