@dereekb/firebase 13.6.5 → 13.6.7

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.
@@ -0,0 +1,187 @@
1
+ import { type Observable } from 'rxjs';
2
+ import { type Maybe, type Milliseconds } from '@dereekb/util';
3
+ import { type FirestoreCollectionType } from '../collection/collection';
4
+ import { type FirestoreModelKey } from '../collection/collection';
5
+ import { type FirestoreContextCacheFactory, type FirestoreCacheEntry, type FirestoreCacheEvent, type FirestoreContextCache } from './cache';
6
+ /**
7
+ * Default TTL used by the in-memory cache when no TTL is specified.
8
+ *
9
+ * Set to 5 minutes.
10
+ */
11
+ export declare const IN_MEMORY_CACHE_DEFAULT_TTL: Milliseconds;
12
+ /**
13
+ * Delegate that handles actual cache storage for a single collection.
14
+ *
15
+ * Implementations provide the storage mechanism (e.g. in-memory Map, no storage for logging).
16
+ * The shared {@link makeFirestoreCollectionCache} handles TTL checking, event emission,
17
+ * and enabled/disabled state around the delegate.
18
+ *
19
+ * @template T - The document data type
20
+ */
21
+ export interface FirestoreCollectionCacheDelegate<T> {
22
+ /**
23
+ * Gets a raw cached entry by key, without TTL or enabled checks.
24
+ */
25
+ get(key: FirestoreModelKey): Maybe<FirestoreCacheEntry<T>>;
26
+ /**
27
+ * Stores a cache entry by key.
28
+ */
29
+ set(key: FirestoreModelKey, entry: FirestoreCacheEntry<T>): void;
30
+ /**
31
+ * Deletes a cache entry by key.
32
+ */
33
+ delete(key: FirestoreModelKey): void;
34
+ /**
35
+ * Clears all entries.
36
+ */
37
+ clear(): void;
38
+ }
39
+ /**
40
+ * Creates an in-memory {@link FirestoreCollectionCacheDelegate} backed by a Map.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const delegate = inMemoryFirestoreCollectionCacheDelegate<UserData>();
45
+ * ```
46
+ */
47
+ export declare function inMemoryFirestoreCollectionCacheDelegate<T>(): FirestoreCollectionCacheDelegate<T>;
48
+ /**
49
+ * Creates a no-storage {@link FirestoreCollectionCacheDelegate} that discards all data.
50
+ *
51
+ * Used by {@link readLoggingFirestoreContextCache} where only event emission matters.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * const delegate = noopFirestoreCollectionCacheDelegate<UserData>();
56
+ * ```
57
+ */
58
+ export declare function noopFirestoreCollectionCacheDelegate<T>(): FirestoreCollectionCacheDelegate<T>;
59
+ /**
60
+ * Configuration for {@link makeFirestoreContextCache}.
61
+ */
62
+ export interface MakeFirestoreContextCacheConfig {
63
+ /**
64
+ * Default TTL applied to all collections unless overridden per-collection.
65
+ */
66
+ readonly defaultTtl: Milliseconds;
67
+ /**
68
+ * Creates a delegate for a new collection cache.
69
+ *
70
+ * Called once per collection type when the cache is first requested.
71
+ */
72
+ readonly createDelegate: <T>(collectionType: FirestoreCollectionType) => FirestoreCollectionCacheDelegate<T>;
73
+ /**
74
+ * Optional function that transforms the raw events observable before it is exposed as `events$`.
75
+ *
76
+ * Can be used to filter, map, or augment events. For example, the read-logging cache
77
+ * uses this to filter out 'miss' events since they are expected and not meaningful.
78
+ *
79
+ * When not provided, the raw events observable is used directly.
80
+ */
81
+ readonly mapEvents$?: (events$: Observable<FirestoreCacheEvent>) => Observable<FirestoreCacheEvent>;
82
+ }
83
+ /**
84
+ * Creates a {@link FirestoreContextCache} with shared event handling, enable/disable controls,
85
+ * and per-type management. The actual cache storage for each collection is handled by
86
+ * delegates created via {@link MakeFirestoreContextCacheConfig.createDelegate}.
87
+ *
88
+ * This is the shared foundation used by both {@link inMemoryFirestoreContextCache} and
89
+ * {@link readLoggingFirestoreContextCache}.
90
+ *
91
+ * @param config - Configuration including default TTL and delegate factory
92
+ * @returns A new context cache
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * const contextCache = makeFirestoreContextCache({
97
+ * defaultTtl: 60000,
98
+ * createDelegate: () => inMemoryFirestoreCollectionCacheDelegate()
99
+ * });
100
+ * ```
101
+ */
102
+ export declare function makeFirestoreContextCache(config: MakeFirestoreContextCacheConfig): FirestoreContextCache;
103
+ /**
104
+ * Creates a {@link FirestoreContextCacheFactory} that stores cached documents in memory.
105
+ *
106
+ * Uses simple Map-based storage with TTL-based expiration. Suitable for
107
+ * client-side caching where the application lifecycle matches the cache lifecycle.
108
+ *
109
+ * @returns A factory function that creates in-memory context caches
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * const factory = inMemoryFirestoreContextCacheFactory();
114
+ * const contextCache = factory({ defaultTtl: 60000 });
115
+ * ```
116
+ */
117
+ export declare function inMemoryFirestoreContextCacheFactory(): FirestoreContextCacheFactory;
118
+ /**
119
+ * Configuration for creating an in-memory {@link FirestoreContextCache}.
120
+ */
121
+ export interface InMemoryFirestoreContextCacheConfig {
122
+ /**
123
+ * Default TTL applied to all collections unless overridden per-collection.
124
+ */
125
+ readonly defaultTtl?: Milliseconds;
126
+ }
127
+ /**
128
+ * Creates an in-memory {@link FirestoreContextCache} that manages per-collection
129
+ * caches with TTL-based expiration, per-type enable/disable, and event streaming.
130
+ *
131
+ * @param config - Optional configuration
132
+ * @returns A new context cache
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * const contextCache = inMemoryFirestoreContextCache({ defaultTtl: 60000 });
137
+ * const userCache = contextCache.cacheForCollection<UserData>('user', { defaultTtl: 30000 });
138
+ * userCache.set('users/abc', { data: userData });
139
+ * ```
140
+ */
141
+ export declare function inMemoryFirestoreContextCache(config?: InMemoryFirestoreContextCacheConfig): FirestoreContextCache;
142
+ /**
143
+ * Creates a {@link FirestoreContextCacheFactory} that emits events for all cache interactions
144
+ * but does not actually store any data.
145
+ *
146
+ * Useful for analytics, debugging, and monitoring read patterns without the
147
+ * memory overhead of actual caching.
148
+ *
149
+ * @returns A factory function that creates read-logging context caches
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * const factory = readLoggingFirestoreContextCacheFactory();
154
+ * const contextCache = factory();
155
+ * contextCache.events$.subscribe((event) => console.log(event));
156
+ * ```
157
+ */
158
+ export declare function readLoggingFirestoreContextCacheFactory(): FirestoreContextCacheFactory;
159
+ /**
160
+ * Configuration for creating a read-logging {@link FirestoreContextCache}.
161
+ */
162
+ export interface ReadLoggingFirestoreContextCacheConfig {
163
+ /**
164
+ * Default TTL applied to all collections. Since no data is stored,
165
+ * this only affects what gets reported as a "miss" vs "hit" (always miss).
166
+ */
167
+ readonly defaultTtl?: Milliseconds;
168
+ }
169
+ /**
170
+ * Creates a {@link FirestoreContextCache} that emits events for all cache interactions
171
+ * but does not actually store any data. Every `get()` call results in a 'miss' event.
172
+ *
173
+ * Useful for tracking read patterns, debugging, and monitoring which documents
174
+ * are accessed and how often, without the memory overhead of actual caching.
175
+ *
176
+ * @param config - Optional configuration
177
+ * @returns A new read-logging context cache
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const contextCache = readLoggingFirestoreContextCache();
182
+ * contextCache.events$.subscribe((event) => {
183
+ * console.log(`${event.type}: ${event.collectionType} ${event.key}`);
184
+ * });
185
+ * ```
186
+ */
187
+ export declare function readLoggingFirestoreContextCache(config?: ReadLoggingFirestoreContextCacheConfig): FirestoreContextCache;
@@ -0,0 +1,2 @@
1
+ export * from './cache';
2
+ export * from './cache.memory';
@@ -3,6 +3,7 @@ import { type FirestoreDocument, type FirestoreDocumentAccessorFactory, type Fir
3
3
  import { type FirestoreItemPageIterationBaseConfig, type FirestoreItemPageIterationFactory } from '../query/iterator';
4
4
  import { type FirestoreQueryFactory } from '../query/query';
5
5
  import { type FirestoreDrivers } from '../driver/driver';
6
+ import { type FirestoreCollectionCacheRef, type FirestoreCollectionCacheConfig } from '../cache/cache';
6
7
  import { type FirestoreCollectionQueryFactory } from './collection.query';
7
8
  import { type ArrayOrValue, type Maybe, type ModelKey, type ModelTypeString } from '@dereekb/util';
8
9
  /**
@@ -570,12 +571,12 @@ export interface FirestoreModelKeyRef {
570
571
  * @template D - The concrete FirestoreDocument subclass
571
572
  * @template A - The accessor type (limited or full)
572
573
  */
573
- export interface FirestoreCollectionLike<T, D extends FirestoreDocument<T> = FirestoreDocument<T>, A extends LimitedFirestoreDocumentAccessor<T, D> = LimitedFirestoreDocumentAccessor<T, D>> extends FirestoreContextReference, FirestoreModelIdentityRef, QueryLikeReferenceRef<T>, FirestoreItemPageIterationFactory<T>, FirestoreQueryFactory<T>, LimitedFirestoreDocumentAccessorFactory<T, D, A>, LimitedFirestoreDocumentAccessorForTransactionFactory<T, D, A>, LimitedFirestoreDocumentAccessorForWriteBatchFactory<T, D, A>, FirestoreCollectionQueryFactory<T, D> {
574
+ export interface FirestoreCollectionLike<T, D extends FirestoreDocument<T> = FirestoreDocument<T>, A extends LimitedFirestoreDocumentAccessor<T, D> = LimitedFirestoreDocumentAccessor<T, D>> extends FirestoreContextReference, FirestoreModelIdentityRef, QueryLikeReferenceRef<T>, FirestoreItemPageIterationFactory<T>, FirestoreQueryFactory<T>, LimitedFirestoreDocumentAccessorFactory<T, D, A>, LimitedFirestoreDocumentAccessorForTransactionFactory<T, D, A>, LimitedFirestoreDocumentAccessorForWriteBatchFactory<T, D, A>, FirestoreCollectionQueryFactory<T, D>, FirestoreCollectionCacheRef<T> {
574
575
  }
575
576
  /**
576
577
  * FirestoreCollection configuration
577
578
  */
578
- export interface FirestoreCollectionConfig<T, D extends FirestoreDocument<T> = FirestoreDocument<T>> extends FirestoreContextReference, FirestoreDrivers, Omit<FirestoreItemPageIterationBaseConfig<T>, 'queryLike'>, Partial<QueryLikeReferenceRef<T>>, FirestoreDocumentAccessorFactoryConfig<T, D> {
579
+ export interface FirestoreCollectionConfig<T, D extends FirestoreDocument<T> = FirestoreDocument<T>> extends FirestoreContextReference, FirestoreDrivers, Omit<FirestoreItemPageIterationBaseConfig<T>, 'queryLike'>, Partial<QueryLikeReferenceRef<T>>, FirestoreDocumentAccessorFactoryConfig<T, D>, Partial<FirestoreCollectionCacheConfig> {
579
580
  }
580
581
  /**
581
582
  * Full Firestore collection interface with document CRUD, querying, iteration, and context support.
@@ -1,4 +1,5 @@
1
1
  import { type FirestoreDocument, type LimitedFirestoreDocumentAccessorFactoryConfig } from '../accessor/document';
2
+ import { type FirestoreCollectionCacheConfig } from '../cache/cache';
2
3
  import { type FirestoreItemPageIterationBaseConfig } from '../query/iterator';
3
4
  import { type FirestoreContextReference } from '../reference';
4
5
  import { type FirestoreDrivers } from '../driver/driver';
@@ -14,7 +15,7 @@ import { type FirestoreCollectionLike } from './collection';
14
15
  * @template T - The data type of the documents in the collection group
15
16
  * @template D - The FirestoreDocument type that wraps the data, defaults to FirestoreDocument<T>
16
17
  */
17
- export interface FirestoreCollectionGroupConfig<T, D extends FirestoreDocument<T> = FirestoreDocument<T>> extends FirestoreContextReference, FirestoreDrivers, FirestoreItemPageIterationBaseConfig<T>, LimitedFirestoreDocumentAccessorFactoryConfig<T, D> {
18
+ export interface FirestoreCollectionGroupConfig<T, D extends FirestoreDocument<T> = FirestoreDocument<T>> extends FirestoreContextReference, FirestoreDrivers, FirestoreItemPageIterationBaseConfig<T>, LimitedFirestoreDocumentAccessorFactoryConfig<T, D>, Partial<FirestoreCollectionCacheConfig> {
18
19
  }
19
20
  /**
20
21
  * Interface for working with documents across a Firestore collection group.
@@ -1,5 +1,6 @@
1
1
  import { type FirestoreDocument, type SingleItemFirestoreCollectionDocumentIdentifierRef } from './accessor/document';
2
2
  import { type FirestoreCollection, type FirestoreCollectionConfig, type FirestoreCollectionWithParent, type SingleItemFirestoreCollection, type FirestoreCollectionGroup, type RootSingleItemFirestoreCollection } from './collection';
3
+ import { type FirestoreContextCacheFactoryRef, type FirestoreContextCacheRef } from './cache/cache';
3
4
  import { type FirestoreDrivers } from './driver/driver';
4
5
  import { type WriteBatchFactoryReference, type RunTransactionFactoryReference } from './driver';
5
6
  import { type DocumentReference, type CollectionReference, type DocumentData, type Firestore, type CollectionGroup } from './types';
@@ -17,7 +18,7 @@ import { type QueryLikeReferenceRef } from './reference';
17
18
  *
18
19
  * @template F - The Firestore implementation type (defaults to standard Firestore)
19
20
  */
20
- export interface FirestoreContext<F extends Firestore = Firestore> extends RunTransactionFactoryReference, WriteBatchFactoryReference {
21
+ export interface FirestoreContext<F extends Firestore = Firestore> extends RunTransactionFactoryReference, WriteBatchFactoryReference, FirestoreContextCacheRef {
21
22
  /**
22
23
  * The underlying Firestore instance.
23
24
  */
@@ -113,7 +114,7 @@ export interface FirestoreContext<F extends Firestore = Firestore> extends RunTr
113
114
  * @template T - The document data type in the collection
114
115
  * @template D - The FirestoreDocument implementation type
115
116
  */
116
- export type FirestoreContextFirestoreCollectionConfig<T, D extends FirestoreDocument<T>> = Omit<FirestoreCollectionConfig<T, D>, 'firestoreDriverIdentifier' | 'firestoreDriverType' | 'firestoreQueryDriver' | 'firestoreAccessorDriver'>;
117
+ export type FirestoreContextFirestoreCollectionConfig<T, D extends FirestoreDocument<T>> = Omit<FirestoreCollectionConfig<T, D>, 'firestoreDriverIdentifier' | 'firestoreDriverType' | 'firestoreQueryDriver' | 'firestoreAccessorDriver' | 'cache'>;
117
118
  /**
118
119
  * Configuration for creating a RootSingleItemFirestoreCollection through a FirestoreContext.
119
120
  *
@@ -165,17 +166,34 @@ export interface FirestoreContextFirestoreCollectionWithParentConfig<T, PT, D ex
165
166
  */
166
167
  export interface FirestoreContextSingleItemFirestoreCollectionConfig<T, PT, D extends FirestoreDocument<T> = FirestoreDocument<T>, PD extends FirestoreDocument<PT> = FirestoreDocument<PT>> extends FirestoreContextFirestoreCollectionWithParentConfig<T, PT, D, PD>, Partial<SingleItemFirestoreCollectionDocumentIdentifierRef> {
167
168
  }
169
+ /**
170
+ * Optional parameters for {@link FirestoreContextFactory} that configure context-level
171
+ * features like caching. These are separate from the platform drivers because they
172
+ * are app-level concerns, not platform-level concerns.
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * const params: FirestoreContextFactoryParams = {
177
+ * firestoreContextCacheFactory: inMemoryFirestoreContextCacheFactory()
178
+ * };
179
+ * const context = contextFactory(firestore, params);
180
+ * ```
181
+ */
182
+ export interface FirestoreContextFactoryParams extends FirestoreContextCacheFactoryRef {
183
+ }
168
184
  /**
169
185
  * Factory function type for creating FirestoreContext instances.
170
186
  *
171
- * Takes a Firestore instance and returns a fully configured FirestoreContext that wraps
172
- * the instance and provides additional functionality through the context interface.
187
+ * Takes a Firestore instance and optional params, and returns a fully configured
188
+ * FirestoreContext. The params allow app-level configuration (e.g. caching) to be
189
+ * provided at the DI injection site rather than at driver creation time.
173
190
  *
174
191
  * @template F - The Firestore implementation type
175
192
  * @param firestore - The Firestore instance to wrap
193
+ * @param params - Optional context-level configuration (e.g. cache factory)
176
194
  * @returns A FirestoreContext instance for the specified Firestore
177
195
  */
178
- export type FirestoreContextFactory<F extends Firestore = Firestore> = (firestore: F) => FirestoreContext;
196
+ export type FirestoreContextFactory<F extends Firestore = Firestore> = (firestore: F, params?: FirestoreContextFactoryParams) => FirestoreContext;
179
197
  /**
180
198
  * Creates a factory function for generating FirestoreContext instances.
181
199
  *
@@ -1,4 +1,5 @@
1
1
  export * from './accessor';
2
+ export * from './cache';
2
3
  export * from './snapshot';
3
4
  export * from './error';
4
5
  export * from './query';
package/test/index.cjs.js CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  var util = require('@dereekb/util');
4
- var test = require('@dereekb/util/test');
5
4
  var rulesUnitTesting = require('@firebase/rules-unit-testing');
6
5
  var firebase = require('@dereekb/firebase');
7
6
  var firestore = require('firebase/firestore');
7
+ var test = require('@dereekb/util/test');
8
8
  var rxjs$1 = require('rxjs');
9
9
  var rxjs = require('@dereekb/rxjs');
10
10
  var dateFns = require('date-fns');
@@ -938,71 +938,113 @@ function _ts_generator$5(thisArg, body) {
938
938
  * A TestContextBuilderFunction for building firebase test context factories using @firebase/firebase and @firebase/rules-unit-testing. This means CLIENT TESTING ONLY. For server testing, look at @dereekb/firestore-server.
939
939
  *
940
940
  * This can be used to easily build a testing context that sets up RulesTestEnvironment for tests that sets itself up and tears itself down.
941
- */ var firebaseRulesUnitTestBuilder = test.testContextBuilder({
942
- buildConfig: function buildConfig(input) {
943
- var _ref;
944
- var config = {
945
- testEnvironment: (_ref = input === null || input === void 0 ? void 0 : input.testEnvironment) !== null && _ref !== void 0 ? _ref : {},
946
- rulesContext: input === null || input === void 0 ? void 0 : input.rulesContext
947
- };
948
- return config;
949
- },
950
- buildFixture: function buildFixture() {
951
- return new RulesUnitTestFirebaseTestingContextFixture();
952
- },
953
- setupInstance: function setupInstance(config) {
954
- return _async_to_generator$5(function() {
955
- var drivers, testEnvironment, rulesTestEnv, rulesTestContext;
956
- return _ts_generator$5(this, function(_state) {
957
- switch(_state.label){
958
- case 0:
941
+ *
942
+ * The {@link RulesTestEnvironment} is initialized once per test suite (`beforeAll`) and cleaned up
943
+ * once (`afterAll`), while fresh drivers and a {@link RulesTestContext} are created per test (`beforeEach`). This avoids
944
+ * repeated calls to `initializeTestEnvironment` (which hits the emulator's `PUT /internal/setRules` endpoint),
945
+ * preventing interference between parallel workers sharing the same Firebase Storage emulator. The Storage
946
+ * emulator maintains rules globally (not per-project), so concurrent `setRules` calls from multiple workers
947
+ * can momentarily leave the emulator in a transitional state that causes `storage/unauthorized` errors.
948
+ */ var firebaseRulesUnitTestBuilder = function firebaseRulesUnitTestBuilder(inputConfig) {
949
+ var _ref;
950
+ var config = {
951
+ testEnvironment: (_ref = inputConfig === null || inputConfig === void 0 ? void 0 : inputConfig.testEnvironment) !== null && _ref !== void 0 ? _ref : {},
952
+ rulesContext: inputConfig === null || inputConfig === void 0 ? void 0 : inputConfig.rulesContext
953
+ };
954
+ return function(buildTests) {
955
+ var fixture = new RulesUnitTestFirebaseTestingContextFixture();
956
+ var rulesTestEnvironment;
957
+ // Initialize the emulator environment once per test suite.
958
+ // This is the expensive operation that hits the emulator's REST API (e.g. PUT /internal/setRules).
959
+ beforeAll(function() {
960
+ return _async_to_generator$5(function() {
961
+ return _ts_generator$5(this, function(_state) {
962
+ switch(_state.label){
963
+ case 0:
964
+ return [
965
+ 4,
966
+ rulesUnitTesting.initializeTestEnvironment(config.testEnvironment)
967
+ ];
968
+ case 1:
969
+ rulesTestEnvironment = _state.sent();
970
+ return [
971
+ 2
972
+ ];
973
+ }
974
+ });
975
+ })();
976
+ });
977
+ // Clean up the emulator environment once after all tests complete.
978
+ afterAll(function() {
979
+ return _async_to_generator$5(function() {
980
+ return _ts_generator$5(this, function(_state) {
981
+ switch(_state.label){
982
+ case 0:
983
+ if (!rulesTestEnvironment) return [
984
+ 3,
985
+ 2
986
+ ];
987
+ return [
988
+ 4,
989
+ rulesTestEnvironment.cleanup().catch(function(e) {
990
+ console.warn('firebaseRulesUnitTestBuilder(): Failed to cleanup rules test environment', e);
991
+ throw e;
992
+ })
993
+ ];
994
+ case 1:
995
+ _state.sent();
996
+ _state.label = 2;
997
+ case 2:
998
+ return [
999
+ 2
1000
+ ];
1001
+ }
1002
+ });
1003
+ })();
1004
+ });
1005
+ // Create fresh drivers and RulesTestContext per test.
1006
+ // Drivers are recreated per test because the Firestore testing driver fuzzes collection names
1007
+ // (via makeTestingFirestoreAccesorDriver) to provide data isolation between tests.
1008
+ var clearInstance = null;
1009
+ beforeEach(function() {
1010
+ return _async_to_generator$5(function() {
1011
+ var drivers, rulesTestContext, instance;
1012
+ return _ts_generator$5(this, function(_state) {
1013
+ try {
959
1014
  drivers = _object_spread$1({}, makeTestingFirestoreDrivers(firebase.firebaseFirestoreClientDrivers()), makeTestingFirebaseStorageDrivers(firebase.firebaseStorageClientDrivers(), {
960
1015
  useTestDefaultBucket: true
961
1016
  }));
962
- testEnvironment = config.testEnvironment;
963
1017
  if (config.testEnvironment.collectionNames) {
964
1018
  drivers.firestoreAccessorDriver.initWithCollectionNames(config.testEnvironment.collectionNames);
965
- testEnvironment = _object_spread_props(_object_spread$1({}, testEnvironment), {
966
- firestore: rewriteEmulatorConfigRulesForFuzzedCollectionNames(testEnvironment.firestore)
967
- });
968
1019
  }
969
- return [
970
- 4,
971
- rulesUnitTesting.initializeTestEnvironment(config.testEnvironment)
972
- ];
973
- case 1:
974
- rulesTestEnv = _state.sent();
975
- rulesTestContext = rulesTestContextForConfig(rulesTestEnv, config.rulesContext);
976
- return [
977
- 2,
978
- new RulesUnitTestTestFirebaseInstance(drivers, rulesTestEnv, rulesTestContext)
979
- ];
980
- }
981
- });
982
- })();
983
- },
984
- teardownInstance: function teardownInstance(instance, config) {
985
- return _async_to_generator$5(function() {
986
- return _ts_generator$5(this, function(_state) {
987
- switch(_state.label){
988
- case 0:
989
- return [
990
- 4,
991
- instance.rulesTestEnvironment.cleanup().catch(function(e) {
992
- console.warn('firebaseRulesUnitTestBuilder(): Failed to cleanup rules test environment', e);
993
- throw e;
994
- })
995
- ];
996
- case 1:
997
- _state.sent();
998
- return [
999
- 2
1000
- ];
1001
- }
1002
- });
1003
- })();
1004
- }
1005
- });
1020
+ rulesTestContext = rulesTestContextForConfig(rulesTestEnvironment, config.rulesContext);
1021
+ instance = new RulesUnitTestTestFirebaseInstance(drivers, rulesTestEnvironment, rulesTestContext);
1022
+ clearInstance = fixture.setInstance(instance);
1023
+ } catch (e) {
1024
+ console.error('firebaseRulesUnitTestBuilder(): Failed building a test instance. Error: ', e);
1025
+ if (clearInstance) {
1026
+ clearInstance();
1027
+ clearInstance = null;
1028
+ }
1029
+ throw e;
1030
+ }
1031
+ return [
1032
+ 2
1033
+ ];
1034
+ });
1035
+ })();
1036
+ });
1037
+ // Declare tests
1038
+ buildTests(fixture);
1039
+ // Clear the instance reference after each test.
1040
+ afterEach(function() {
1041
+ if (clearInstance) {
1042
+ clearInstance();
1043
+ clearInstance = null;
1044
+ }
1045
+ });
1046
+ };
1047
+ };
1006
1048
  // MARK: Internal
1007
1049
  function rulesTestContextForConfig(rulesTestEnv, testingRulesConfig) {
1008
1050
  var rulesTestContext;
@@ -1014,18 +1056,6 @@ function rulesTestContextForConfig(rulesTestEnv, testingRulesConfig) {
1014
1056
  }
1015
1057
  return rulesTestContext;
1016
1058
  }
1017
- function rewriteEmulatorConfigRulesForFuzzedCollectionNames(config, fuzzedCollectionNamesMap) {
1018
- if (config && config.rules) {
1019
- config = _object_spread_props(_object_spread$1({}, config), {
1020
- rules: rewriteRulesForFuzzedCollectionNames(config.rules)
1021
- });
1022
- }
1023
- return config;
1024
- }
1025
- function rewriteRulesForFuzzedCollectionNames(rules, fuzzedCollectionNamesMap) {
1026
- // TODO: rewrite the rules using regex matching/replacement.
1027
- return rules;
1028
- }
1029
1059
  // MARK: Utility
1030
1060
  /**
1031
1061
  * Registers `beforeAll`/`afterAll` hooks to suppress verbose Firestore log output during tests.
@@ -1044,24 +1074,56 @@ function rewriteRulesForFuzzedCollectionNames(rules, fuzzedCollectionNamesMap) {
1044
1074
  /**
1045
1075
  * Default user ID used for the authenticated context in client-side Firebase tests.
1046
1076
  */ var TESTING_AUTHORIZED_FIREBASE_USER_ID = '0';
1077
+ /**
1078
+ * Permissive Firestore security rules that allow all reads and writes.
1079
+ */ var AUTHORIZED_FIRESTORE_RULES = "\nrules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;\n }\n }\n}\n";
1080
+ /**
1081
+ * Permissive Firebase Storage security rules that allow all reads and writes.
1082
+ */ var AUTHORIZED_STORAGE_RULES = "\nrules_version = '2';\nservice firebase.storage {\n match /b/{bucket}/o {\n match /{allPaths=**} {\n allow read, write: if true;\n }\n }\n}\n";
1047
1083
  /**
1048
1084
  * Pre-configured {@link FirebaseTestContextFactory} that provides a fully authorized (all reads/writes allowed)
1049
1085
  * Firebase emulator context for both Firestore and Storage.
1050
1086
  *
1051
1087
  * Uses permissive security rules and authenticates as {@link TESTING_AUTHORIZED_FIREBASE_USER_ID}.
1052
- * This is the most common base factory for client-side mock item tests.
1088
+ *
1089
+ * **Important:** Only use this factory for tests that actually need Firebase Storage access.
1090
+ * Firestore-only tests should use {@link authorizedFirestoreOnlyFactory} instead to avoid
1091
+ * interfering with the storage emulator's global rules endpoint during parallel test execution.
1053
1092
  *
1054
1093
  * @example
1055
1094
  * ```ts
1056
- * const f = testWithMockItemCollectionFixture()(authorizedFirebaseFactory);
1095
+ * const f = testWithMockItemStorageFixture()(authorizedFirebaseFactory);
1057
1096
  * ```
1058
1097
  */ var authorizedFirebaseFactory = firebaseRulesUnitTestBuilder({
1059
1098
  testEnvironment: {
1060
1099
  firestore: {
1061
- rules: "\n rules_version = '2';\n service cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;\n }\n }\n }\n "
1100
+ rules: AUTHORIZED_FIRESTORE_RULES
1062
1101
  },
1063
1102
  storage: {
1064
- rules: "\n rules_version = '2';\n service firebase.storage {\n match /b/{bucket}/o {\n match /{allPaths=**} {\n allow read, write: if true;\n }\n }\n }\n "
1103
+ rules: AUTHORIZED_STORAGE_RULES
1104
+ }
1105
+ },
1106
+ rulesContext: {
1107
+ userId: TESTING_AUTHORIZED_FIREBASE_USER_ID
1108
+ }
1109
+ });
1110
+ /**
1111
+ * Pre-configured {@link FirebaseTestContextFactory} that provides a fully authorized Firestore-only
1112
+ * emulator context without configuring Storage rules.
1113
+ *
1114
+ * Use this for Firestore-only tests when running with parallel workers. The Firebase Storage
1115
+ * emulator maintains rules globally (not per-project like Firestore), so `initializeTestEnvironment`
1116
+ * calls that include storage rules from non-storage workers will hit `PUT /internal/setRules`
1117
+ * concurrently, which can cause transient `storage/unauthorized` errors in the storage test worker.
1118
+ *
1119
+ * @example
1120
+ * ```ts
1121
+ * const f = testWithMockItemCollectionFixture()(authorizedFirestoreOnlyFactory);
1122
+ * ```
1123
+ */ var authorizedFirestoreOnlyFactory = firebaseRulesUnitTestBuilder({
1124
+ testEnvironment: {
1125
+ firestore: {
1126
+ rules: AUTHORIZED_FIRESTORE_RULES
1065
1127
  }
1066
1128
  },
1067
1129
  rulesContext: {
@@ -2135,8 +2197,11 @@ function _is_native_reflect_construct$2() {
2135
2197
  /**
2136
2198
  * Convenience mock instance for collection tests within an authorized firebase context.
2137
2199
  *
2200
+ * Uses {@link authorizedFirestoreOnlyFactory} to avoid touching the Storage emulator's
2201
+ * global rules endpoint, preventing interference with parallel storage test workers.
2202
+ *
2138
2203
  * Uses @firebase/firestore. This is ONLY for the client.
2139
- */ var authorizedTestWithMockItemCollection = testWithMockItemCollectionFixture()(authorizedFirebaseFactory);
2204
+ */ var authorizedTestWithMockItemCollection = testWithMockItemCollectionFixture()(authorizedFirestoreOnlyFactory);
2140
2205
  /**
2141
2206
  * Convenience mock instance for storage tests within an authorized firebase context.
2142
2207
  *
@@ -12483,6 +12548,8 @@ function _ts_generator(thisArg, body) {
12483
12548
  });
12484
12549
  }
12485
12550
 
12551
+ exports.AUTHORIZED_FIRESTORE_RULES = AUTHORIZED_FIRESTORE_RULES;
12552
+ exports.AUTHORIZED_STORAGE_RULES = AUTHORIZED_STORAGE_RULES;
12486
12553
  exports.MOCK_FIREBASE_MODEL_SERVICE_FACTORIES = MOCK_FIREBASE_MODEL_SERVICE_FACTORIES;
12487
12554
  exports.MOCK_SYSTEM_STATE_TYPE = MOCK_SYSTEM_STATE_TYPE;
12488
12555
  exports.MockItemCollectionFixture = MockItemCollectionFixture;
@@ -12506,6 +12573,7 @@ exports.TestFirestoreContextFixture = TestFirestoreContextFixture;
12506
12573
  exports.TestFirestoreInstance = TestFirestoreInstance;
12507
12574
  exports.allChildMockItemSubItemDeepsWithinMockItem = allChildMockItemSubItemDeepsWithinMockItem;
12508
12575
  exports.authorizedFirebaseFactory = authorizedFirebaseFactory;
12576
+ exports.authorizedFirestoreOnlyFactory = authorizedFirestoreOnlyFactory;
12509
12577
  exports.authorizedTestWithMockItemCollection = authorizedTestWithMockItemCollection;
12510
12578
  exports.authorizedTestWithMockItemStorage = authorizedTestWithMockItemStorage;
12511
12579
  exports.changeFirestoreLogLevelBeforeAndAfterTests = changeFirestoreLogLevelBeforeAndAfterTests;