@fyrestack/database 0.1.0 → 0.1.1

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.cjs ADDED
@@ -0,0 +1,1293 @@
1
+
2
+ //#region src/context.ts
3
+ /**
4
+ * Detect if we're running in Node.js environment
5
+ */
6
+ const isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
7
+ /**
8
+ * Browser shim for AsyncLocalStorage.
9
+ * Note: This implementation has limited support for parallel async operations.
10
+ * In browsers, parallel context isolation may not work correctly.
11
+ * For full isolation, use Node.js with native AsyncLocalStorage.
12
+ */
13
+ var AsyncLocalStorageShim = class {
14
+ constructor() {
15
+ this._store = void 0;
16
+ this._stack = [];
17
+ }
18
+ run(store, callback) {
19
+ this._stack.push(this._store);
20
+ this._store = store;
21
+ try {
22
+ const result = callback();
23
+ if (result && typeof result.then === "function") return result.finally(() => {
24
+ this._store = this._stack.pop();
25
+ });
26
+ this._store = this._stack.pop();
27
+ return result;
28
+ } catch (error) {
29
+ this._store = this._stack.pop();
30
+ throw error;
31
+ }
32
+ }
33
+ getStore() {
34
+ return this._store;
35
+ }
36
+ };
37
+ /**
38
+ * Get the AsyncLocalStorage implementation.
39
+ * Uses native AsyncLocalStorage in Node.js, falls back to shim in browsers.
40
+ */
41
+ function getAsyncLocalStorageClass() {
42
+ if (isNode) try {
43
+ return require("async_hooks").AsyncLocalStorage;
44
+ } catch (_unused) {}
45
+ return AsyncLocalStorageShim;
46
+ }
47
+ const operationContext = new (getAsyncLocalStorageClass())();
48
+ /**
49
+ * Get the current operation context, if any.
50
+ * Returns undefined when not inside a transaction or batch.
51
+ */
52
+ function getCurrentContext() {
53
+ return operationContext.getStore();
54
+ }
55
+ /**
56
+ * Check if we're currently inside a transaction.
57
+ */
58
+ function isInTransaction() {
59
+ const ctx = operationContext.getStore();
60
+ return (ctx === null || ctx === void 0 ? void 0 : ctx.type) === "transaction";
61
+ }
62
+ /**
63
+ * Check if we're currently inside a batch.
64
+ */
65
+ function isInBatch() {
66
+ const ctx = operationContext.getStore();
67
+ return (ctx === null || ctx === void 0 ? void 0 : ctx.type) === "batch";
68
+ }
69
+ /**
70
+ * Get the current transaction, if inside one.
71
+ * Returns undefined when not in a transaction.
72
+ */
73
+ function getCurrentTransaction() {
74
+ const ctx = operationContext.getStore();
75
+ return (ctx === null || ctx === void 0 ? void 0 : ctx.type) === "transaction" ? ctx.tx : void 0;
76
+ }
77
+ /**
78
+ * Get the current batch, if inside one.
79
+ * Returns undefined when not in a batch.
80
+ */
81
+ function getCurrentBatch() {
82
+ const ctx = operationContext.getStore();
83
+ return (ctx === null || ctx === void 0 ? void 0 : ctx.type) === "batch" ? ctx.batch : void 0;
84
+ }
85
+ /**
86
+ * Run a function within a transaction context.
87
+ * @internal Used by runTransaction()
88
+ */
89
+ function runWithTransaction(tx, fn) {
90
+ const ctx = {
91
+ type: "transaction",
92
+ tx
93
+ };
94
+ return operationContext.run(ctx, fn);
95
+ }
96
+ /**
97
+ * Run a function within a batch context.
98
+ * @internal Used by writeBatch()
99
+ */
100
+ function runWithBatch(batch, fn) {
101
+ const ctx = {
102
+ type: "batch",
103
+ batch
104
+ };
105
+ return operationContext.run(ctx, fn);
106
+ }
107
+ /**
108
+ * Type guard for transaction context.
109
+ */
110
+ function isTransactionContext(ctx) {
111
+ return (ctx === null || ctx === void 0 ? void 0 : ctx.type) === "transaction";
112
+ }
113
+ /**
114
+ * Type guard for batch context.
115
+ */
116
+ function isBatchContext(ctx) {
117
+ return (ctx === null || ctx === void 0 ? void 0 : ctx.type) === "batch";
118
+ }
119
+
120
+ //#endregion
121
+ //#region src/errors.ts
122
+ /**
123
+ * FyreStack Custom Error Types
124
+ *
125
+ * Provides typed errors for better error handling and debugging.
126
+ */
127
+ /**
128
+ * Base error class for all FyreStack errors.
129
+ */
130
+ var FyreStackError = class extends Error {
131
+ constructor(message, code) {
132
+ super(message);
133
+ this.name = "FyreStackError";
134
+ this.code = code;
135
+ if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
136
+ }
137
+ };
138
+ /**
139
+ * Error thrown when validation fails.
140
+ */
141
+ var ValidationError = class ValidationError extends FyreStackError {
142
+ constructor(message, issues = []) {
143
+ super(message, "VALIDATION_ERROR");
144
+ this.name = "ValidationError";
145
+ this.issues = issues;
146
+ }
147
+ /**
148
+ * Create a ValidationError from Standard Schema validation issues.
149
+ */
150
+ static fromStandardSchema(issues) {
151
+ const mappedIssues = issues.map((issue) => {
152
+ var _issue$path$map, _issue$path;
153
+ return {
154
+ path: (_issue$path$map = (_issue$path = issue.path) === null || _issue$path === void 0 ? void 0 : _issue$path.map((p) => typeof p === "object" ? String(p.key) : String(p))) !== null && _issue$path$map !== void 0 ? _issue$path$map : [],
155
+ message: issue.message
156
+ };
157
+ });
158
+ return new ValidationError(mappedIssues.length > 0 && mappedIssues[0] !== void 0 ? mappedIssues.length === 1 ? mappedIssues[0].message : `Validation failed with ${String(mappedIssues.length)} issues` : "Validation failed", mappedIssues);
159
+ }
160
+ };
161
+ /**
162
+ * Error thrown when path parameters are invalid.
163
+ */
164
+ var PathParamError = class extends FyreStackError {
165
+ constructor(paramName, paramValue, reason) {
166
+ super(`Invalid path parameter "${paramName}": ${reason}`, "PATH_PARAM_ERROR");
167
+ this.name = "PathParamError";
168
+ this.paramName = paramName;
169
+ this.paramValue = paramValue;
170
+ }
171
+ };
172
+ /**
173
+ * Error thrown when Firestore adapter is not initialized.
174
+ */
175
+ var AdapterNotInitializedError = class extends FyreStackError {
176
+ constructor() {
177
+ super("Firestore adapter not initialized. Call setFirestoreAdapter() before using repositories.", "ADAPTER_NOT_INITIALIZED");
178
+ this.name = "AdapterNotInitializedError";
179
+ }
180
+ };
181
+ /**
182
+ * Error thrown when a model is missing required properties.
183
+ */
184
+ var ModelError = class extends FyreStackError {
185
+ constructor(message) {
186
+ super(message, "MODEL_ERROR");
187
+ this.name = "ModelError";
188
+ }
189
+ };
190
+ /**
191
+ * Error thrown when a document is not found.
192
+ */
193
+ var NotFoundError = class extends FyreStackError {
194
+ constructor(documentPath) {
195
+ super(`Document not found: ${documentPath}`, "NOT_FOUND");
196
+ this.name = "NotFoundError";
197
+ this.documentPath = documentPath;
198
+ }
199
+ };
200
+ /**
201
+ * Error thrown when a Firestore operation fails.
202
+ */
203
+ var FirestoreError = class FirestoreError extends FyreStackError {
204
+ constructor(message, originalError) {
205
+ super(message, "FIRESTORE_ERROR");
206
+ this.name = "FirestoreError";
207
+ this.originalError = originalError;
208
+ }
209
+ /**
210
+ * Wrap a Firestore error with context.
211
+ */
212
+ static wrap(operation, error) {
213
+ return new FirestoreError(`Firestore ${operation} failed: ${error instanceof Error ? error.message : "Unknown error"}`, error);
214
+ }
215
+ };
216
+ /**
217
+ * Helper to check if a ValidationResult is valid.
218
+ */
219
+ function isValidResult(result) {
220
+ return result.valid;
221
+ }
222
+
223
+ //#endregion
224
+ //#region src/firestore.types.ts
225
+ /**
226
+ * Abstract Firestore types for dependency injection.
227
+ * Allows switching between firebase-admin and client-side firebase.
228
+ */
229
+ /**
230
+ * Type guard to check if a value is a ServerTimestampSentinel.
231
+ * Note: This checks the runtime structure, not the brand.
232
+ */
233
+ function isServerTimestamp(value) {
234
+ return value !== null && typeof value === "object" && "__type__" in value && value.__type__ === "serverTimestamp";
235
+ }
236
+ /**
237
+ * Global Firestore adapter instance holder
238
+ */
239
+ let firestoreAdapter = null;
240
+ /**
241
+ * Set the Firestore adapter to use (call once at app initialization)
242
+ */
243
+ function setFirestoreAdapter(adapter) {
244
+ firestoreAdapter = adapter;
245
+ }
246
+ /**
247
+ * Get the current Firestore adapter
248
+ * @throws AdapterNotInitializedError if adapter not initialized
249
+ */
250
+ function getFirestoreAdapter() {
251
+ if (!firestoreAdapter) throw new AdapterNotInitializedError();
252
+ return firestoreAdapter;
253
+ }
254
+ /**
255
+ * Reset the Firestore adapter (for testing only)
256
+ * @internal
257
+ */
258
+ function _resetFirestoreAdapter() {
259
+ firestoreAdapter = null;
260
+ }
261
+
262
+ //#endregion
263
+ //#region src/batch.ts
264
+ /**
265
+ * Batch Write Support for FyreStack Database
266
+ *
267
+ * Provides atomic write-only operations across multiple repositories.
268
+ * Context is automatically propagated using AsyncContext.
269
+ */
270
+ /**
271
+ * Maximum operations per Firestore batch.
272
+ */
273
+ const MAX_BATCH_SIZE = 500;
274
+ /**
275
+ * Run operations within a write batch.
276
+ *
277
+ * Batches provide:
278
+ * - Atomic writes (all succeed or all fail)
279
+ * - Better performance than individual writes
280
+ * - Up to 500 operations per batch
281
+ * - Works across multiple repositories
282
+ *
283
+ * The batch context is **automatically propagated** to all repository
284
+ * methods called within the batch function - no need to pass context explicitly.
285
+ *
286
+ * Note: Batches are write-only. Use `runTransaction` if you need reads.
287
+ *
288
+ * @example
289
+ * ```typescript
290
+ * // Bulk create users
291
+ * await writeBatch(async () => {
292
+ * for (const user of users) {
293
+ * await UserRepo.save({}, user);
294
+ * }
295
+ * });
296
+ * ```
297
+ *
298
+ * @example
299
+ * ```typescript
300
+ * // Cross-repository batch write
301
+ * await writeBatch(async () => {
302
+ * await UserRepo.save({}, user);
303
+ * await ProfileRepo.save({ userId: user.id }, profile);
304
+ * await AuditRepo.save({}, auditLog);
305
+ * });
306
+ * ```
307
+ *
308
+ * @param fn - Async function to run within the batch
309
+ */
310
+ async function writeBatch(fn) {
311
+ const batch = getFirestoreAdapter().batch();
312
+ await runWithBatch(batch, fn);
313
+ await batch.commit();
314
+ }
315
+ /**
316
+ * Run operations with automatic batch chunking for large datasets.
317
+ *
318
+ * Automatically commits and creates new batches when exceeding 500 operations.
319
+ * Useful for migrations or bulk imports with thousands of records.
320
+ *
321
+ * The batch context is **automatically propagated** to all repository
322
+ * methods called within the function.
323
+ *
324
+ * @example
325
+ * ```typescript
326
+ * // Process 10,000 records with automatic batching
327
+ * await writeChunkedBatch(async () => {
328
+ * for (const user of thousandsOfUsers) {
329
+ * await UserRepo.save({}, user);
330
+ * // Automatically commits every 500 operations
331
+ * }
332
+ * });
333
+ * ```
334
+ *
335
+ * @param fn - Function to run with automatic batch chunking
336
+ */
337
+ async function writeChunkedBatch(fn) {
338
+ const adapter = getFirestoreAdapter();
339
+ const state = {
340
+ batch: adapter.batch(),
341
+ operationCount: 0
342
+ };
343
+ const flushIfNeeded = async () => {
344
+ if (state.operationCount >= MAX_BATCH_SIZE) {
345
+ await state.batch.commit();
346
+ state.batch = adapter.batch();
347
+ state.operationCount = 0;
348
+ }
349
+ };
350
+ const trackedBatch = {
351
+ set(docRef, data) {
352
+ state.batch.set(docRef, data);
353
+ state.operationCount++;
354
+ flushIfNeeded();
355
+ return trackedBatch;
356
+ },
357
+ update(docRef, data) {
358
+ state.batch.update(docRef, data);
359
+ state.operationCount++;
360
+ flushIfNeeded();
361
+ return trackedBatch;
362
+ },
363
+ delete(docRef) {
364
+ state.batch.delete(docRef);
365
+ state.operationCount++;
366
+ flushIfNeeded();
367
+ return trackedBatch;
368
+ },
369
+ async commit() {}
370
+ };
371
+ await runWithBatch(trackedBatch, fn);
372
+ if (state.operationCount > 0) await state.batch.commit();
373
+ }
374
+
375
+ //#endregion
376
+ //#region src/model.hooks.ts
377
+ /**
378
+ * Merge multiple repository hooks into a single hooks object.
379
+ * When multiple hooks define the same method, they are chained in order.
380
+ */
381
+ function mergeRepositoryHooks(...hooksList) {
382
+ var _filtered$;
383
+ const filtered = hooksList.filter((h) => h !== void 0);
384
+ if (filtered.length === 0) return {};
385
+ if (filtered.length === 1) return (_filtered$ = filtered[0]) !== null && _filtered$ !== void 0 ? _filtered$ : {};
386
+ const result = {};
387
+ const beforeSave = chainDataHooks(filtered.map((h) => h.beforeSave));
388
+ if (beforeSave) result.beforeSave = beforeSave;
389
+ const afterSave = chainVoidHooks(filtered.map((h) => h.afterSave));
390
+ if (afterSave) result.afterSave = afterSave;
391
+ const beforeUpdate = chainDataHooks(filtered.map((h) => h.beforeUpdate));
392
+ if (beforeUpdate) result.beforeUpdate = beforeUpdate;
393
+ const afterUpdate = chainVoidHooks(filtered.map((h) => h.afterUpdate));
394
+ if (afterUpdate) result.afterUpdate = afterUpdate;
395
+ const beforeDelete = chainVoidHooksNoData(filtered.map((h) => h.beforeDelete));
396
+ if (beforeDelete) result.beforeDelete = beforeDelete;
397
+ const afterDelete = chainVoidHooksNoData(filtered.map((h) => h.afterDelete));
398
+ if (afterDelete) result.afterDelete = afterDelete;
399
+ const afterGet = chainNullableDataHooks(filtered.map((h) => h.afterGet));
400
+ if (afterGet) result.afterGet = afterGet;
401
+ const afterFind = chainArrayDataHooks(filtered.map((h) => h.afterFind));
402
+ if (afterFind) result.afterFind = afterFind;
403
+ return result;
404
+ }
405
+ /**
406
+ * Merge multiple model hooks into a single hooks object.
407
+ */
408
+ function mergeModelHooks(...hooksList) {
409
+ var _filtered$2;
410
+ const filtered = hooksList.filter((h) => h !== void 0);
411
+ if (filtered.length === 0) return {};
412
+ if (filtered.length === 1) return (_filtered$2 = filtered[0]) !== null && _filtered$2 !== void 0 ? _filtered$2 : {};
413
+ const result = {};
414
+ const onConstruct = chainModelDataHooks(filtered.map((h) => h.onConstruct));
415
+ if (onConstruct) result.onConstruct = onConstruct;
416
+ const onValidate = chainModelVoidHooks(filtered.map((h) => h.onValidate));
417
+ if (onValidate) result.onValidate = onValidate;
418
+ return result;
419
+ }
420
+ /**
421
+ * Merge feature hooks (combines both model and repository hooks).
422
+ */
423
+ function mergeFeatureHooks(...hooksList) {
424
+ const filtered = hooksList.filter((h) => h !== void 0);
425
+ if (filtered.length === 0) return {};
426
+ return {
427
+ model: mergeModelHooks(...filtered.map((h) => h.model)),
428
+ repository: mergeRepositoryHooks(...filtered.map((h) => h.repository))
429
+ };
430
+ }
431
+ function chainDataHooks(hooks) {
432
+ const filtered = hooks.filter((h) => h !== void 0);
433
+ if (filtered.length === 0) return void 0;
434
+ if (filtered.length === 1) return filtered[0];
435
+ return (data, context) => {
436
+ let result = data;
437
+ for (const hook of filtered) result = hook(result, context);
438
+ return result;
439
+ };
440
+ }
441
+ function chainVoidHooks(hooks) {
442
+ const filtered = hooks.filter((h) => h !== void 0);
443
+ if (filtered.length === 0) return void 0;
444
+ if (filtered.length === 1) return filtered[0];
445
+ return (data, context) => {
446
+ for (const hook of filtered) hook(data, context);
447
+ };
448
+ }
449
+ function chainVoidHooksNoData(hooks) {
450
+ const filtered = hooks.filter((h) => h !== void 0);
451
+ if (filtered.length === 0) return void 0;
452
+ if (filtered.length === 1) return filtered[0];
453
+ return (context) => {
454
+ for (const hook of filtered) hook(context);
455
+ };
456
+ }
457
+ function chainNullableDataHooks(hooks) {
458
+ const filtered = hooks.filter((h) => h !== void 0);
459
+ if (filtered.length === 0) return void 0;
460
+ if (filtered.length === 1) return filtered[0];
461
+ return (data, context) => {
462
+ let result = data;
463
+ for (const hook of filtered) result = hook(result, context);
464
+ return result;
465
+ };
466
+ }
467
+ function chainArrayDataHooks(hooks) {
468
+ const filtered = hooks.filter((h) => h !== void 0);
469
+ if (filtered.length === 0) return void 0;
470
+ if (filtered.length === 1) return filtered[0];
471
+ return (data, context) => {
472
+ let result = data;
473
+ for (const hook of filtered) result = hook(result, context);
474
+ return result;
475
+ };
476
+ }
477
+ function chainModelDataHooks(hooks) {
478
+ const filtered = hooks.filter((h) => h !== void 0);
479
+ if (filtered.length === 0) return void 0;
480
+ if (filtered.length === 1) return filtered[0];
481
+ return (props, context) => {
482
+ let result = props;
483
+ for (const hook of filtered) result = hook(result, context);
484
+ return result;
485
+ };
486
+ }
487
+ function chainModelVoidHooks(hooks) {
488
+ const filtered = hooks.filter((h) => h !== void 0);
489
+ if (filtered.length === 0) return void 0;
490
+ if (filtered.length === 1) return filtered[0];
491
+ return (data, context) => {
492
+ for (const hook of filtered) hook(data, context);
493
+ };
494
+ }
495
+
496
+ //#endregion
497
+ //#region \0@oxc-project+runtime@0.103.0/helpers/typeof.js
498
+ function _typeof(o) {
499
+ "@babel/helpers - typeof";
500
+ return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
501
+ return typeof o$1;
502
+ } : function(o$1) {
503
+ return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
504
+ }, _typeof(o);
505
+ }
506
+
507
+ //#endregion
508
+ //#region \0@oxc-project+runtime@0.103.0/helpers/toPrimitive.js
509
+ function toPrimitive(t, r) {
510
+ if ("object" != _typeof(t) || !t) return t;
511
+ var e = t[Symbol.toPrimitive];
512
+ if (void 0 !== e) {
513
+ var i = e.call(t, r || "default");
514
+ if ("object" != _typeof(i)) return i;
515
+ throw new TypeError("@@toPrimitive must return a primitive value.");
516
+ }
517
+ return ("string" === r ? String : Number)(t);
518
+ }
519
+
520
+ //#endregion
521
+ //#region \0@oxc-project+runtime@0.103.0/helpers/toPropertyKey.js
522
+ function toPropertyKey(t) {
523
+ var i = toPrimitive(t, "string");
524
+ return "symbol" == _typeof(i) ? i : i + "";
525
+ }
526
+
527
+ //#endregion
528
+ //#region \0@oxc-project+runtime@0.103.0/helpers/defineProperty.js
529
+ function _defineProperty(e, r, t) {
530
+ return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
531
+ value: t,
532
+ enumerable: !0,
533
+ configurable: !0,
534
+ writable: !0
535
+ }) : e[r] = t, e;
536
+ }
537
+
538
+ //#endregion
539
+ //#region \0@oxc-project+runtime@0.103.0/helpers/objectSpread2.js
540
+ function ownKeys(e, r) {
541
+ var t = Object.keys(e);
542
+ if (Object.getOwnPropertySymbols) {
543
+ var o = Object.getOwnPropertySymbols(e);
544
+ r && (o = o.filter(function(r$1) {
545
+ return Object.getOwnPropertyDescriptor(e, r$1).enumerable;
546
+ })), t.push.apply(t, o);
547
+ }
548
+ return t;
549
+ }
550
+ function _objectSpread2(e) {
551
+ for (var r = 1; r < arguments.length; r++) {
552
+ var t = null != arguments[r] ? arguments[r] : {};
553
+ r % 2 ? ownKeys(Object(t), !0).forEach(function(r$1) {
554
+ _defineProperty(e, r$1, t[r$1]);
555
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r$1) {
556
+ Object.defineProperty(e, r$1, Object.getOwnPropertyDescriptor(t, r$1));
557
+ });
558
+ }
559
+ return e;
560
+ }
561
+
562
+ //#endregion
563
+ //#region src/model.metadata.ts
564
+ /**
565
+ * Symbol used to store metadata on model classes.
566
+ * Using a symbol prevents conflicts with user-defined properties.
567
+ */
568
+ const MODEL_METADATA = Symbol.for("fyrestack.model.metadata");
569
+ /**
570
+ * Get metadata from a model class.
571
+ */
572
+ function getModelMetadata(ModelClass) {
573
+ if (ModelClass !== null && typeof ModelClass === "function" && MODEL_METADATA in ModelClass) return ModelClass[MODEL_METADATA];
574
+ }
575
+ /**
576
+ * Get repository hooks from a model class.
577
+ */
578
+ function getRepositoryHooks(ModelClass) {
579
+ var _getModelMetadata;
580
+ return (_getModelMetadata = getModelMetadata(ModelClass)) === null || _getModelMetadata === void 0 || (_getModelMetadata = _getModelMetadata.hooks) === null || _getModelMetadata === void 0 ? void 0 : _getModelMetadata.repository;
581
+ }
582
+ /**
583
+ * Get model hooks from a model class.
584
+ */
585
+ function getModelHooks(ModelClass) {
586
+ var _getModelMetadata2;
587
+ return (_getModelMetadata2 = getModelMetadata(ModelClass)) === null || _getModelMetadata2 === void 0 || (_getModelMetadata2 = _getModelMetadata2.hooks) === null || _getModelMetadata2 === void 0 ? void 0 : _getModelMetadata2.model;
588
+ }
589
+ /**
590
+ * Check if a model has any repository hooks registered.
591
+ */
592
+ function hasRepositoryHooks(ModelClass) {
593
+ const hooks = getRepositoryHooks(ModelClass);
594
+ return hooks !== void 0 && Object.keys(hooks).length > 0;
595
+ }
596
+ /**
597
+ * Check if a model has a specific feature registered.
598
+ */
599
+ function hasFeature(ModelClass, featureName) {
600
+ var _metadata$features;
601
+ const metadata = getModelMetadata(ModelClass);
602
+ return (metadata === null || metadata === void 0 || (_metadata$features = metadata.features) === null || _metadata$features === void 0 ? void 0 : _metadata$features[featureName]) !== void 0;
603
+ }
604
+ /**
605
+ * Get configuration for a specific feature.
606
+ */
607
+ function getFeatureConfig(ModelClass, featureName) {
608
+ var _metadata$features2;
609
+ const metadata = getModelMetadata(ModelClass);
610
+ return metadata === null || metadata === void 0 || (_metadata$features2 = metadata.features) === null || _metadata$features2 === void 0 ? void 0 : _metadata$features2[featureName];
611
+ }
612
+ /**
613
+ * Create metadata for a feature.
614
+ * Used by feature functions like withTimestamps() to build metadata.
615
+ *
616
+ * @param featureName - Unique name for the feature
617
+ * @param config - Feature-specific configuration (for introspection)
618
+ * @param hooks - Hooks to register
619
+ * @param existingMetadata - Existing metadata to merge with
620
+ */
621
+ function createFeatureMetadata(featureName, config, hooks, existingMetadata) {
622
+ return {
623
+ hooks: mergeFeatureHooks(existingMetadata === null || existingMetadata === void 0 ? void 0 : existingMetadata.hooks, hooks),
624
+ features: _objectSpread2(_objectSpread2({}, existingMetadata === null || existingMetadata === void 0 ? void 0 : existingMetadata.features), {}, { [featureName]: config })
625
+ };
626
+ }
627
+ /**
628
+ * Default timestamp configuration.
629
+ */
630
+ const DEFAULT_TIMESTAMPS = {
631
+ createdAt: "createdAt",
632
+ updatedAt: "updatedAt",
633
+ useServerTimestamp: true
634
+ };
635
+ /**
636
+ * Default audit configuration.
637
+ */
638
+ const DEFAULT_AUDIT = {
639
+ createdBy: "createdBy",
640
+ updatedBy: "updatedBy"
641
+ };
642
+ /**
643
+ * Default status configuration.
644
+ */
645
+ const DEFAULT_STATUS = {
646
+ field: "status",
647
+ values: [
648
+ "active",
649
+ "inactive",
650
+ "deleted"
651
+ ],
652
+ defaultValue: "active",
653
+ deletedValue: "deleted",
654
+ deletedAt: "deletedAt"
655
+ };
656
+ /**
657
+ * Check if a model has timestamps feature.
658
+ */
659
+ function hasTimestamps(ModelClass) {
660
+ return hasFeature(ModelClass, "timestamps");
661
+ }
662
+ /**
663
+ * Get timestamps configuration from a model class.
664
+ */
665
+ function getTimestampsConfig(ModelClass) {
666
+ return getFeatureConfig(ModelClass, "timestamps");
667
+ }
668
+ /**
669
+ * Check if a model has audit feature.
670
+ */
671
+ function hasAudit(ModelClass) {
672
+ return hasFeature(ModelClass, "audit");
673
+ }
674
+ /**
675
+ * Get audit configuration from a model class.
676
+ */
677
+ function getAuditConfig(ModelClass) {
678
+ return getFeatureConfig(ModelClass, "audit");
679
+ }
680
+ /**
681
+ * Check if a model has status feature.
682
+ */
683
+ function hasStatus(ModelClass) {
684
+ return hasFeature(ModelClass, "status");
685
+ }
686
+ /**
687
+ * Get status configuration from a model class.
688
+ */
689
+ function getStatusConfig(ModelClass) {
690
+ return getFeatureConfig(ModelClass, "status");
691
+ }
692
+
693
+ //#endregion
694
+ //#region src/model.types.ts
695
+ /**
696
+ * Symbol for storing the schema on model instances (internal use).
697
+ */
698
+ const MODEL_SCHEMA = Symbol.for("fyrestack.model.schema");
699
+
700
+ //#endregion
701
+ //#region src/model.ts
702
+ function model(...features) {
703
+ const innerStore = features.reduce((store, feature) => feature(store), getInitialInnerStore());
704
+ const modelSchema = innerStore.schema;
705
+ const modelMethods = innerStore.methods;
706
+ class Model {
707
+ constructor(data) {
708
+ this[MODEL_SCHEMA] = modelSchema;
709
+ let validatedProps = {};
710
+ if (data !== void 0 && data !== null) if ("~standard" in modelSchema) {
711
+ const result = modelSchema["~standard"].validate(data);
712
+ if ("issues" in result && result.issues) throw toValidationError(result.issues);
713
+ if ("value" in result) validatedProps = result.value;
714
+ } else validatedProps = data;
715
+ for (const [key, value] of Object.entries(validatedProps)) this[key] = value;
716
+ for (const [key, method] of Object.entries(modelMethods)) this[key] = method;
717
+ }
718
+ /**
719
+ * Validate the current model data against the schema.
720
+ * @returns ValidationResult with validated value or error
721
+ */
722
+ validate() {
723
+ const schema = this[MODEL_SCHEMA];
724
+ const data = this.toObject();
725
+ if (!("~standard" in schema)) return {
726
+ valid: true,
727
+ value: data
728
+ };
729
+ const result = schema["~standard"].validate(data);
730
+ if ("issues" in result && result.issues) return {
731
+ valid: false,
732
+ error: toValidationError(result.issues)
733
+ };
734
+ if ("value" in result) return {
735
+ valid: true,
736
+ value: result.value
737
+ };
738
+ return {
739
+ valid: true,
740
+ value: data
741
+ };
742
+ }
743
+ /**
744
+ * Check if the current model data is valid.
745
+ * @returns true if valid, false otherwise
746
+ */
747
+ isValid() {
748
+ return this.validate().valid;
749
+ }
750
+ /**
751
+ * Convert model instance to a plain object (excludes methods).
752
+ * Used for Firestore writes and serialization.
753
+ */
754
+ toObject() {
755
+ const result = {};
756
+ for (const key of Object.keys(this)) {
757
+ const value = this[key];
758
+ if (typeof value !== "function") result[key] = value;
759
+ }
760
+ return result;
761
+ }
762
+ }
763
+ const metadata = innerStore._metadata;
764
+ if (metadata) Model[MODEL_METADATA] = metadata;
765
+ return Model;
766
+ }
767
+ function getInitialInnerStore() {
768
+ return {
769
+ schema: {},
770
+ props: {},
771
+ methods: {}
772
+ };
773
+ }
774
+ /**
775
+ * Convert Standard Schema issues to ValidationIssues.
776
+ * Handles various path formats from different schema libraries.
777
+ */
778
+ function toValidationError(issues) {
779
+ const mappedIssues = issues.map((issue) => {
780
+ let path = [];
781
+ if (Array.isArray(issue.path)) path = issue.path.map((p) => {
782
+ if (typeof p === "string" || typeof p === "number" || typeof p === "symbol") return String(p);
783
+ if (p !== null && typeof p === "object" && "key" in p) return String(p.key);
784
+ return String(p);
785
+ });
786
+ return {
787
+ path,
788
+ message: issue.message
789
+ };
790
+ });
791
+ return new ValidationError(mappedIssues.length > 0 && mappedIssues[0] !== void 0 ? mappedIssues.length === 1 ? mappedIssues[0].message : `Validation failed with ${String(mappedIssues.length)} issues` : "Validation failed", mappedIssues);
792
+ }
793
+
794
+ //#endregion
795
+ //#region src/model.with-schema.ts
796
+ function withSchema(schema) {
797
+ return (store) => ({
798
+ schema,
799
+ props: {},
800
+ methods: store.methods
801
+ });
802
+ }
803
+
804
+ //#endregion
805
+ //#region src/model.with-timestamps.ts
806
+ /**
807
+ * Create repository hooks for timestamp management.
808
+ * This is the core implementation extracted into hooks.
809
+ */
810
+ function createTimestampsHooks(config) {
811
+ return {
812
+ beforeSave(data, context) {
813
+ const timestamp = config.useServerTimestamp ? context.adapter.serverTimestamp() : /* @__PURE__ */ new Date();
814
+ if (config.createdAt !== false && (data[config.createdAt] === void 0 || data[config.createdAt] === null)) data[config.createdAt] = timestamp;
815
+ if (config.updatedAt !== false) data[config.updatedAt] = timestamp;
816
+ return data;
817
+ },
818
+ beforeUpdate(updates, context) {
819
+ if (config.updatedAt !== false) {
820
+ const timestamp = config.useServerTimestamp ? context.adapter.serverTimestamp() : /* @__PURE__ */ new Date();
821
+ updates[config.updatedAt] = timestamp;
822
+ }
823
+ return updates;
824
+ }
825
+ };
826
+ }
827
+ /**
828
+ * Add automatic timestamp management to a model.
829
+ *
830
+ * This feature registers hooks that:
831
+ * - Set `createdAt` on save (if not already set)
832
+ * - Set `updatedAt` on save and update
833
+ *
834
+ * @example
835
+ * ```typescript
836
+ * // Basic usage with defaults (createdAt, updatedAt)
837
+ * const UserModel = model(
838
+ * withSchema(UserSchema),
839
+ * withTimestamps(),
840
+ * );
841
+ *
842
+ * // Custom field names
843
+ * const PostModel = model(
844
+ * withSchema(PostSchema),
845
+ * withTimestamps({ createdAt: 'publishedAt', updatedAt: 'modifiedAt' }),
846
+ * );
847
+ *
848
+ * // Disable one field
849
+ * const LogModel = model(
850
+ * withSchema(LogSchema),
851
+ * withTimestamps({ updatedAt: false }), // Only track creation
852
+ * );
853
+ *
854
+ * // Use client-side timestamps (for offline support)
855
+ * const OfflineModel = model(
856
+ * withSchema(OfflineSchema),
857
+ * withTimestamps({ useServerTimestamp: false }),
858
+ * );
859
+ * ```
860
+ *
861
+ * @param options - Optional configuration for field names and behavior
862
+ */
863
+ function withTimestamps(options) {
864
+ var _options$createdAt, _options$updatedAt, _options$useServerTim;
865
+ const config = {
866
+ createdAt: (_options$createdAt = options === null || options === void 0 ? void 0 : options.createdAt) !== null && _options$createdAt !== void 0 ? _options$createdAt : DEFAULT_TIMESTAMPS.createdAt,
867
+ updatedAt: (_options$updatedAt = options === null || options === void 0 ? void 0 : options.updatedAt) !== null && _options$updatedAt !== void 0 ? _options$updatedAt : DEFAULT_TIMESTAMPS.updatedAt,
868
+ useServerTimestamp: (_options$useServerTim = options === null || options === void 0 ? void 0 : options.useServerTimestamp) !== null && _options$useServerTim !== void 0 ? _options$useServerTim : DEFAULT_TIMESTAMPS.useServerTimestamp
869
+ };
870
+ const hooks = createTimestampsHooks(config);
871
+ return (store) => {
872
+ const newMetadata = createFeatureMetadata("timestamps", config, { repository: hooks }, store._metadata);
873
+ return {
874
+ schema: store.schema,
875
+ props: store.props,
876
+ methods: store.methods,
877
+ _metadata: newMetadata
878
+ };
879
+ };
880
+ }
881
+
882
+ //#endregion
883
+ //#region src/repository.helpers.ts
884
+ /**
885
+ * Composable Repository Implementation for Firestore
886
+ */
887
+ function createTypedQueryBuilder(queryRef) {
888
+ return {
889
+ where(field, op, value) {
890
+ return createTypedQueryBuilder(queryRef.where(field, op, value));
891
+ },
892
+ orderBy(field, direction) {
893
+ return createTypedQueryBuilder(queryRef.orderBy(field, direction));
894
+ },
895
+ limit(count) {
896
+ return createTypedQueryBuilder(queryRef.limit(count));
897
+ },
898
+ startAt(...values) {
899
+ return createTypedQueryBuilder(queryRef.startAt(...values));
900
+ },
901
+ startAfter(...values) {
902
+ return createTypedQueryBuilder(queryRef.startAfter(...values));
903
+ },
904
+ endAt(...values) {
905
+ return createTypedQueryBuilder(queryRef.endAt(...values));
906
+ },
907
+ endBefore(...values) {
908
+ return createTypedQueryBuilder(queryRef.endBefore(...values));
909
+ },
910
+ _queryRef: queryRef
911
+ };
912
+ }
913
+ function getQueryRef(builder) {
914
+ return builder._queryRef;
915
+ }
916
+ /**
917
+ * Regex for valid path parameter values.
918
+ * Allows alphanumeric characters, underscores, and hyphens.
919
+ */
920
+ const VALID_PATH_PARAM_REGEX = /^[a-zA-Z0-9_-]+$/;
921
+ /**
922
+ * Validate a single path parameter value.
923
+ * @throws PathParamError if the value is invalid
924
+ */
925
+ function validatePathParam(paramName, value) {
926
+ if (!value || value.length === 0) throw new PathParamError(paramName, value, "cannot be empty");
927
+ if (value.includes("/")) throw new PathParamError(paramName, value, "cannot contain forward slashes");
928
+ if (value.includes("..")) throw new PathParamError(paramName, value, "cannot contain path traversal (..)");
929
+ if (value.includes("\0")) throw new PathParamError(paramName, value, "cannot contain null bytes");
930
+ if (!VALID_PATH_PARAM_REGEX.test(value)) throw new PathParamError(paramName, value, "must only contain alphanumeric characters, underscores, or hyphens");
931
+ }
932
+ /**
933
+ * Build a collection path from template and params.
934
+ * Validates all path parameters before building.
935
+ * @throws PathParamError if any parameter is invalid
936
+ */
937
+ function buildCollectionPath(pathTemplate, params) {
938
+ let result = pathTemplate;
939
+ for (const [key, value] of Object.entries(params)) {
940
+ validatePathParam(key, value);
941
+ result = result.replace(`{${key}}`, value);
942
+ }
943
+ return result;
944
+ }
945
+ /**
946
+ * Extract model ID from model instance or string.
947
+ * @throws ModelError if the model doesn't have a valid ID
948
+ */
949
+ function extractId(modelOrId) {
950
+ if (typeof modelOrId === "string") {
951
+ if (!modelOrId) throw new ModelError("Document ID cannot be empty");
952
+ return modelOrId;
953
+ }
954
+ if (modelOrId !== null && typeof modelOrId === "object" && "id" in modelOrId && typeof modelOrId.id === "string") {
955
+ if (!modelOrId.id) throw new ModelError("Model ID cannot be empty");
956
+ return modelOrId.id;
957
+ }
958
+ throw new ModelError("Model must have a string \"id\" property");
959
+ }
960
+ /**
961
+ * Type guard to check if a model has a validate() method.
962
+ */
963
+ function isValidatable(model$1) {
964
+ return "validate" in model$1 && typeof model$1.validate === "function";
965
+ }
966
+ /**
967
+ * Type guard to check if a model has a toObject() method.
968
+ */
969
+ function hasToObject(model$1) {
970
+ return "toObject" in model$1 && typeof model$1.toObject === "function";
971
+ }
972
+ /**
973
+ * Validate a model instance before write operations.
974
+ * @throws ValidationError if validation fails
975
+ */
976
+ function validateModel(model$1) {
977
+ if (model$1 === null || typeof model$1 !== "object") throw new ValidationError("Model must be an object");
978
+ if (isValidatable(model$1)) {
979
+ const result = model$1.validate();
980
+ if (!result.valid && result.error) throw result.error;
981
+ }
982
+ }
983
+ /**
984
+ * Convert model to plain object for Firestore.
985
+ * Uses toObject() if available, otherwise spreads the object.
986
+ */
987
+ function modelToObject(model$1) {
988
+ if (model$1 !== null && typeof model$1 === "object") {
989
+ if (hasToObject(model$1)) return model$1.toObject();
990
+ const result = {};
991
+ for (const [key, value] of Object.entries(model$1)) if (typeof value !== "function") result[key] = value;
992
+ return result;
993
+ }
994
+ throw new ModelError("Model must be an object");
995
+ }
996
+
997
+ //#endregion
998
+ //#region src/repository.ts
999
+ function repository(config, ...features) {
1000
+ const { path, model: ModelClass } = config;
1001
+ const hooks = getRepositoryHooks(ModelClass);
1002
+ class Repository {
1003
+ getFirestore() {
1004
+ return getFirestoreAdapter();
1005
+ }
1006
+ getCollectionRef(params) {
1007
+ const collectionPath = buildCollectionPath(path, params);
1008
+ return this.getFirestore().collection(collectionPath);
1009
+ }
1010
+ getDocumentRef(params, id) {
1011
+ return this.getCollectionRef(params).doc(id);
1012
+ }
1013
+ /**
1014
+ * Create hook context for repository hooks.
1015
+ */
1016
+ createHookContext(params, id) {
1017
+ const context = {
1018
+ adapter: this.getFirestore(),
1019
+ modelClass: ModelClass,
1020
+ path,
1021
+ params
1022
+ };
1023
+ if (id !== void 0) context.id = id;
1024
+ return context;
1025
+ }
1026
+ async get(params, id) {
1027
+ var _this = this;
1028
+ try {
1029
+ const docRef = _this.getDocumentRef(params, id);
1030
+ const ctx = getCurrentContext();
1031
+ const tx = isTransactionContext(ctx) ? ctx.tx : void 0;
1032
+ const snapshot = tx ? await tx.get(docRef) : await docRef.get();
1033
+ if (!snapshot.exists) return null;
1034
+ const data = snapshot.data();
1035
+ if (!data) return null;
1036
+ let result = new ModelClass(_objectSpread2(_objectSpread2({}, data), {}, { id: snapshot.id }));
1037
+ if (hooks === null || hooks === void 0 ? void 0 : hooks.afterGet) {
1038
+ const context = _this.createHookContext(params, id);
1039
+ result = hooks.afterGet(result, context);
1040
+ }
1041
+ return result;
1042
+ } catch (error) {
1043
+ if (error instanceof PathParamError || error instanceof ValidationError) throw error;
1044
+ throw FirestoreError.wrap("get", error);
1045
+ }
1046
+ }
1047
+ async find(params, queryFn) {
1048
+ var _this2 = this;
1049
+ try {
1050
+ const collectionRef = _this2.getCollectionRef(params);
1051
+ let queryRef = collectionRef;
1052
+ if (queryFn) queryRef = getQueryRef(queryFn(createTypedQueryBuilder(collectionRef)));
1053
+ let results = (await queryRef.get()).docs.map((doc) => {
1054
+ return new ModelClass(_objectSpread2(_objectSpread2({}, doc.data()), {}, { id: doc.id }));
1055
+ });
1056
+ if (hooks === null || hooks === void 0 ? void 0 : hooks.afterFind) {
1057
+ const context = _this2.createHookContext(params);
1058
+ results = hooks.afterFind(results, context);
1059
+ }
1060
+ return results;
1061
+ } catch (error) {
1062
+ if (error instanceof PathParamError || error instanceof ValidationError) throw error;
1063
+ throw FirestoreError.wrap("find", error);
1064
+ }
1065
+ }
1066
+ async save(params, model$1) {
1067
+ var _this3 = this;
1068
+ try {
1069
+ validateModel(model$1);
1070
+ const id = extractId(model$1);
1071
+ const docRef = _this3.getDocumentRef(params, id);
1072
+ let data = modelToObject(model$1);
1073
+ if (hooks === null || hooks === void 0 ? void 0 : hooks.beforeSave) {
1074
+ const context = _this3.createHookContext(params, id);
1075
+ data = hooks.beforeSave(data, context);
1076
+ }
1077
+ const ctx = getCurrentContext();
1078
+ if (isTransactionContext(ctx)) ctx.tx.set(docRef, data);
1079
+ else if (isBatchContext(ctx)) ctx.batch.set(docRef, data);
1080
+ else await docRef.set(data);
1081
+ if (hooks === null || hooks === void 0 ? void 0 : hooks.afterSave) {
1082
+ const context = _this3.createHookContext(params, id);
1083
+ hooks.afterSave(data, context);
1084
+ }
1085
+ } catch (error) {
1086
+ if (error instanceof PathParamError || error instanceof ValidationError || error instanceof ModelError) throw error;
1087
+ throw FirestoreError.wrap("save", error);
1088
+ }
1089
+ }
1090
+ async update(params, modelOrId, updates) {
1091
+ var _this4 = this;
1092
+ try {
1093
+ const id = extractId(modelOrId);
1094
+ const docRef = _this4.getDocumentRef(params, id);
1095
+ let updateData = _objectSpread2({}, updates);
1096
+ if (hooks === null || hooks === void 0 ? void 0 : hooks.beforeUpdate) {
1097
+ const context = _this4.createHookContext(params, id);
1098
+ updateData = hooks.beforeUpdate(updateData, context);
1099
+ }
1100
+ const ctx = getCurrentContext();
1101
+ if (isTransactionContext(ctx)) ctx.tx.update(docRef, updateData);
1102
+ else if (isBatchContext(ctx)) ctx.batch.update(docRef, updateData);
1103
+ else await docRef.update(updateData);
1104
+ if (hooks === null || hooks === void 0 ? void 0 : hooks.afterUpdate) {
1105
+ const context = _this4.createHookContext(params, id);
1106
+ hooks.afterUpdate(updateData, context);
1107
+ }
1108
+ } catch (error) {
1109
+ if (error instanceof PathParamError || error instanceof ModelError) throw error;
1110
+ throw FirestoreError.wrap("update", error);
1111
+ }
1112
+ }
1113
+ async delete(params, modelOrId) {
1114
+ var _this5 = this;
1115
+ try {
1116
+ const id = extractId(modelOrId);
1117
+ const docRef = _this5.getDocumentRef(params, id);
1118
+ if (hooks === null || hooks === void 0 ? void 0 : hooks.beforeDelete) {
1119
+ const context = _this5.createHookContext(params, id);
1120
+ hooks.beforeDelete(context);
1121
+ }
1122
+ const ctx = getCurrentContext();
1123
+ if (isTransactionContext(ctx)) ctx.tx.delete(docRef);
1124
+ else if (isBatchContext(ctx)) ctx.batch.delete(docRef);
1125
+ else await docRef.delete();
1126
+ if (hooks === null || hooks === void 0 ? void 0 : hooks.afterDelete) {
1127
+ const context = _this5.createHookContext(params, id);
1128
+ hooks.afterDelete(context);
1129
+ }
1130
+ } catch (error) {
1131
+ if (error instanceof PathParamError || error instanceof ModelError) throw error;
1132
+ throw FirestoreError.wrap("delete", error);
1133
+ }
1134
+ }
1135
+ }
1136
+ if (features.length > 0) {
1137
+ const repoInstance = new Repository();
1138
+ const innerRepo = {
1139
+ model: ModelClass,
1140
+ path,
1141
+ methods: {
1142
+ get: repoInstance.get.bind(repoInstance),
1143
+ find: repoInstance.find.bind(repoInstance),
1144
+ save: repoInstance.save.bind(repoInstance),
1145
+ update: repoInstance.update.bind(repoInstance),
1146
+ delete: repoInstance.delete.bind(repoInstance)
1147
+ },
1148
+ getCollection: repoInstance["getCollectionRef"].bind(repoInstance),
1149
+ getDocument: repoInstance["getDocumentRef"].bind(repoInstance)
1150
+ };
1151
+ let currentMethods = innerRepo.methods;
1152
+ for (const feature of features) currentMethods = feature(_objectSpread2(_objectSpread2({}, innerRepo), {}, { methods: currentMethods }));
1153
+ const FinalRepository = class extends Repository {
1154
+ constructor() {
1155
+ super();
1156
+ for (const [key, method] of Object.entries(currentMethods)) if (!(key in this)) this[key] = method;
1157
+ }
1158
+ };
1159
+ return FinalRepository;
1160
+ }
1161
+ return Repository;
1162
+ }
1163
+
1164
+ //#endregion
1165
+ //#region src/repository.with-methods.ts
1166
+ /**
1167
+ * Add custom methods to a repository
1168
+ */
1169
+ function withMethods(methodsFactory) {
1170
+ return (repo) => {
1171
+ const newMethods = methodsFactory(repo);
1172
+ return _objectSpread2(_objectSpread2({}, repo.methods), newMethods);
1173
+ };
1174
+ }
1175
+
1176
+ //#endregion
1177
+ //#region src/transaction.ts
1178
+ /**
1179
+ * Transaction Support for FyreStack Database
1180
+ *
1181
+ * Provides atomic read-write operations across multiple repositories.
1182
+ * Context is automatically propagated using AsyncContext.
1183
+ */
1184
+ /**
1185
+ * Run operations within a Firestore transaction.
1186
+ *
1187
+ * Transactions provide:
1188
+ * - Atomic reads and writes across multiple repositories
1189
+ * - Automatic retry on contention (up to maxAttempts)
1190
+ * - Consistent view of data (all reads see same snapshot)
1191
+ * - All-or-nothing semantics (all writes succeed or all fail)
1192
+ *
1193
+ * The transaction context is **automatically propagated** to all repository
1194
+ * methods called within the transaction function - no need to pass context explicitly.
1195
+ *
1196
+ * @example
1197
+ * ```typescript
1198
+ * // Transfer funds between accounts atomically
1199
+ * const result = await runTransaction(async () => {
1200
+ * const fromAccount = await AccountRepo.get({}, fromId);
1201
+ * const toAccount = await AccountRepo.get({}, toId);
1202
+ *
1203
+ * if (!fromAccount || !toAccount) throw new Error('Account not found');
1204
+ * if (fromAccount.balance < amount) throw new Error('Insufficient funds');
1205
+ *
1206
+ * await AccountRepo.update({}, fromId, {
1207
+ * balance: fromAccount.balance - amount
1208
+ * });
1209
+ *
1210
+ * await AccountRepo.update({}, toId, {
1211
+ * balance: toAccount.balance + amount
1212
+ * });
1213
+ *
1214
+ * return { newBalance: fromAccount.balance - amount };
1215
+ * });
1216
+ * ```
1217
+ *
1218
+ * @example
1219
+ * ```typescript
1220
+ * // Cross-repository transaction
1221
+ * await runTransaction(async () => {
1222
+ * const order = await OrderRepo.get({}, orderId);
1223
+ * const user = await UserRepo.get({}, order.userId);
1224
+ *
1225
+ * await OrderRepo.update({}, orderId, { status: 'paid' });
1226
+ * await UserRepo.update({}, user.id, {
1227
+ * balance: user.balance - order.total
1228
+ * });
1229
+ * });
1230
+ * ```
1231
+ *
1232
+ * @param fn - Async function to run within the transaction
1233
+ * @param options - Transaction options (maxAttempts, etc.)
1234
+ * @returns Result of the transaction function
1235
+ */
1236
+ async function runTransaction(fn, options) {
1237
+ return getFirestoreAdapter().runTransaction(async (tx) => {
1238
+ return runWithTransaction(tx, fn);
1239
+ }, options);
1240
+ }
1241
+
1242
+ //#endregion
1243
+ exports.AdapterNotInitializedError = AdapterNotInitializedError;
1244
+ exports.DEFAULT_AUDIT = DEFAULT_AUDIT;
1245
+ exports.DEFAULT_STATUS = DEFAULT_STATUS;
1246
+ exports.DEFAULT_TIMESTAMPS = DEFAULT_TIMESTAMPS;
1247
+ exports.FirestoreError = FirestoreError;
1248
+ exports.FyreStackError = FyreStackError;
1249
+ exports.MAX_BATCH_SIZE = MAX_BATCH_SIZE;
1250
+ exports.MODEL_METADATA = MODEL_METADATA;
1251
+ exports.MODEL_SCHEMA = MODEL_SCHEMA;
1252
+ exports.ModelError = ModelError;
1253
+ exports.NotFoundError = NotFoundError;
1254
+ exports.PathParamError = PathParamError;
1255
+ exports.ValidationError = ValidationError;
1256
+ exports._resetFirestoreAdapter = _resetFirestoreAdapter;
1257
+ exports.createFeatureMetadata = createFeatureMetadata;
1258
+ exports.getAuditConfig = getAuditConfig;
1259
+ exports.getCurrentBatch = getCurrentBatch;
1260
+ exports.getCurrentContext = getCurrentContext;
1261
+ exports.getCurrentTransaction = getCurrentTransaction;
1262
+ exports.getFeatureConfig = getFeatureConfig;
1263
+ exports.getFirestoreAdapter = getFirestoreAdapter;
1264
+ exports.getModelHooks = getModelHooks;
1265
+ exports.getModelMetadata = getModelMetadata;
1266
+ exports.getRepositoryHooks = getRepositoryHooks;
1267
+ exports.getStatusConfig = getStatusConfig;
1268
+ exports.getTimestampsConfig = getTimestampsConfig;
1269
+ exports.hasAudit = hasAudit;
1270
+ exports.hasFeature = hasFeature;
1271
+ exports.hasRepositoryHooks = hasRepositoryHooks;
1272
+ exports.hasStatus = hasStatus;
1273
+ exports.hasTimestamps = hasTimestamps;
1274
+ exports.isBatchContext = isBatchContext;
1275
+ exports.isInBatch = isInBatch;
1276
+ exports.isInTransaction = isInTransaction;
1277
+ exports.isServerTimestamp = isServerTimestamp;
1278
+ exports.isTransactionContext = isTransactionContext;
1279
+ exports.isValidResult = isValidResult;
1280
+ exports.mergeFeatureHooks = mergeFeatureHooks;
1281
+ exports.mergeModelHooks = mergeModelHooks;
1282
+ exports.mergeRepositoryHooks = mergeRepositoryHooks;
1283
+ exports.model = model;
1284
+ exports.repository = repository;
1285
+ exports.runTransaction = runTransaction;
1286
+ exports.runWithBatch = runWithBatch;
1287
+ exports.runWithTransaction = runWithTransaction;
1288
+ exports.setFirestoreAdapter = setFirestoreAdapter;
1289
+ exports.withMethods = withMethods;
1290
+ exports.withSchema = withSchema;
1291
+ exports.withTimestamps = withTimestamps;
1292
+ exports.writeBatch = writeBatch;
1293
+ exports.writeChunkedBatch = writeChunkedBatch;