@enterstellar-ai/adapter-firebase 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +196 -0
- package/NOTICE +17 -0
- package/README.md +167 -0
- package/dist/index.cjs +340 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +235 -0
- package/dist/index.d.ts +235 -0
- package/dist/index.js +336 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var auth = require('firebase/auth');
|
|
4
|
+
var types = require('@enterstellar-ai/types');
|
|
5
|
+
var firestore = require('firebase/firestore');
|
|
6
|
+
|
|
7
|
+
// src/create-firebase-auth-adapter.ts
|
|
8
|
+
function adapterValidationError(adapterType, reason) {
|
|
9
|
+
return new types.EnterstellarError(
|
|
10
|
+
"ENS-7001",
|
|
11
|
+
"adapters",
|
|
12
|
+
`Adapter validation failed for "${adapterType}": ${reason}`,
|
|
13
|
+
false
|
|
14
|
+
// non-recoverable — developer misconfiguration
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
function adapterMethodError(adapterName, methodName, cause) {
|
|
18
|
+
return new types.EnterstellarError(
|
|
19
|
+
"ENS-7002",
|
|
20
|
+
"adapters",
|
|
21
|
+
`Adapter "${adapterName}" method "${methodName}" threw.`,
|
|
22
|
+
true,
|
|
23
|
+
// recoverable — transient infrastructure failure
|
|
24
|
+
cause
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
function adapterQueryError(adapterName, resource, cause) {
|
|
28
|
+
return new types.EnterstellarError(
|
|
29
|
+
"ENS-7003",
|
|
30
|
+
"adapters",
|
|
31
|
+
`Adapter "${adapterName}" query failed for resource "${resource}".`,
|
|
32
|
+
true,
|
|
33
|
+
// recoverable — transient data source failure
|
|
34
|
+
cause
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function adapterMutationError(adapterName, resource, action, cause) {
|
|
38
|
+
return new types.EnterstellarError(
|
|
39
|
+
"ENS-7004",
|
|
40
|
+
"adapters",
|
|
41
|
+
`Adapter "${adapterName}" mutation "${action}" failed for resource "${resource}".`,
|
|
42
|
+
true,
|
|
43
|
+
// recoverable — transient data source failure
|
|
44
|
+
cause
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
function adapterAuthError(adapterName, operation, cause) {
|
|
48
|
+
return new types.EnterstellarError(
|
|
49
|
+
"ENS-7005",
|
|
50
|
+
"adapters",
|
|
51
|
+
`Adapter "${adapterName}" auth operation "${operation}" failed.`,
|
|
52
|
+
true,
|
|
53
|
+
// recoverable — auth provider may be temporarily unavailable
|
|
54
|
+
cause
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
var REQUIRED_METHODS = {
|
|
58
|
+
auth: ["getSession", "hasRole", "onAuthChange"],
|
|
59
|
+
data: ["query", "mutate", "subscribe"],
|
|
60
|
+
error: ["report", "shouldRetry", "sanitize"],
|
|
61
|
+
analytics: ["track", "identify"]
|
|
62
|
+
};
|
|
63
|
+
function validateAdapterConfig(adapterType, config) {
|
|
64
|
+
const name = config["name"];
|
|
65
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
66
|
+
throw adapterValidationError(
|
|
67
|
+
adapterType,
|
|
68
|
+
'"name" must be a non-empty string.'
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const requiredMethods = REQUIRED_METHODS[adapterType];
|
|
72
|
+
for (const methodName of requiredMethods) {
|
|
73
|
+
const method = config[methodName];
|
|
74
|
+
if (typeof method !== "function") {
|
|
75
|
+
const receivedType = method === null ? "null" : typeof method;
|
|
76
|
+
throw adapterValidationError(
|
|
77
|
+
adapterType,
|
|
78
|
+
`Missing or invalid method "${methodName}". Expected function, received ${receivedType}.`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function createAuthAdapter(config) {
|
|
84
|
+
validateAdapterConfig("auth", config);
|
|
85
|
+
const adapterName = config.name;
|
|
86
|
+
const adapter = {
|
|
87
|
+
/**
|
|
88
|
+
* Wrapped `getSession()` — catches vendor errors → `ENS-7005`.
|
|
89
|
+
*/
|
|
90
|
+
async getSession() {
|
|
91
|
+
try {
|
|
92
|
+
return await config.getSession();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
throw adapterAuthError(adapterName, "getSession", error);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
/**
|
|
98
|
+
* Wrapped `hasRole()` — catches vendor errors → `ENS-7005`.
|
|
99
|
+
*/
|
|
100
|
+
async hasRole(role) {
|
|
101
|
+
try {
|
|
102
|
+
return await config.hasRole(role);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
throw adapterAuthError(adapterName, "hasRole", error);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
/**
|
|
108
|
+
* Wrapped `onAuthChange()` — catches subscription setup errors → `ENS-7002`.
|
|
109
|
+
* Uses generic method error (not auth-specific) because `onAuthChange` is
|
|
110
|
+
* subscription management, not auth state retrieval.
|
|
111
|
+
*/
|
|
112
|
+
onAuthChange(callback) {
|
|
113
|
+
try {
|
|
114
|
+
return config.onAuthChange(callback);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
throw adapterMethodError(adapterName, "onAuthChange", error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
return Object.freeze(adapter);
|
|
121
|
+
}
|
|
122
|
+
function createDataAdapter(config) {
|
|
123
|
+
validateAdapterConfig("data", config);
|
|
124
|
+
const adapterName = config.name;
|
|
125
|
+
const adapter = {
|
|
126
|
+
/**
|
|
127
|
+
* Wrapped `query()` — catches vendor errors → `ENS-7003`.
|
|
128
|
+
* Includes the queried resource name in the error for debugging.
|
|
129
|
+
*/
|
|
130
|
+
async query(resource, params) {
|
|
131
|
+
try {
|
|
132
|
+
return await config.query(resource, params);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
throw adapterQueryError(adapterName, resource, error);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
/**
|
|
138
|
+
* Wrapped `mutate()` — catches vendor errors → `ENS-7004`.
|
|
139
|
+
* Includes the resource name and mutation action in the error for debugging.
|
|
140
|
+
*/
|
|
141
|
+
async mutate(resource, action, data) {
|
|
142
|
+
try {
|
|
143
|
+
return await config.mutate(resource, action, data);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
throw adapterMutationError(adapterName, resource, action, error);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
/**
|
|
149
|
+
* Wrapped `subscribe()` — catches vendor errors → `ENS-7002`.
|
|
150
|
+
*
|
|
151
|
+
* Only the `subscribe()` invocation itself is wrapped. The consumer's
|
|
152
|
+
* callback is NOT wrapped — callback errors are the consumer's responsibility.
|
|
153
|
+
* The returned unsubscribe function is also NOT wrapped — unsubscribe
|
|
154
|
+
* failures are fire-and-forget cleanup operations.
|
|
155
|
+
*/
|
|
156
|
+
subscribe(resource, callback) {
|
|
157
|
+
try {
|
|
158
|
+
return config.subscribe(resource, callback);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
throw adapterMethodError(adapterName, "subscribe", error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
return Object.freeze(adapter);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/create-firebase-auth-adapter.ts
|
|
168
|
+
var DEFAULT_NAME = "firebase-auth";
|
|
169
|
+
async function extractRolesFromClaims(user) {
|
|
170
|
+
const tokenResult = await user.getIdTokenResult();
|
|
171
|
+
const roles = tokenResult.claims["roles"];
|
|
172
|
+
if (!Array.isArray(roles)) return [];
|
|
173
|
+
return roles.filter((role) => typeof role === "string");
|
|
174
|
+
}
|
|
175
|
+
function toEnterstellarSessionSync(user, roleExtractor) {
|
|
176
|
+
if (!user) return null;
|
|
177
|
+
return { userId: user.uid, roles: roleExtractor(user) };
|
|
178
|
+
}
|
|
179
|
+
function createFirebaseAuthAdapter(config) {
|
|
180
|
+
const { auth: auth$1, name = DEFAULT_NAME, roleExtractor } = config;
|
|
181
|
+
const hasCustomExtractor = typeof roleExtractor === "function";
|
|
182
|
+
return createAuthAdapter({
|
|
183
|
+
name,
|
|
184
|
+
/**
|
|
185
|
+
* Maps to `auth.currentUser` + role extraction.
|
|
186
|
+
*
|
|
187
|
+
* If a custom `roleExtractor` is provided, uses it synchronously.
|
|
188
|
+
* Otherwise, fetches roles from `getIdTokenResult().claims['roles']`.
|
|
189
|
+
*
|
|
190
|
+
* Returns `null` if no user is signed in (`auth.currentUser === null`).
|
|
191
|
+
*/
|
|
192
|
+
async getSession() {
|
|
193
|
+
const user = auth$1.currentUser;
|
|
194
|
+
if (!user) return null;
|
|
195
|
+
if (hasCustomExtractor) {
|
|
196
|
+
return { userId: user.uid, roles: roleExtractor(user) };
|
|
197
|
+
}
|
|
198
|
+
const roles = await extractRolesFromClaims(user);
|
|
199
|
+
return { userId: user.uid, roles };
|
|
200
|
+
},
|
|
201
|
+
/**
|
|
202
|
+
* Maps to `getSession()` → checks `roles.includes(role)`.
|
|
203
|
+
*
|
|
204
|
+
* DRY pattern: re-uses session logic to avoid duplicating the
|
|
205
|
+
* user fetch and role extraction. Returns `false` if no user
|
|
206
|
+
* is signed in.
|
|
207
|
+
*
|
|
208
|
+
* @param role - The role to check (e.g., `'clinician'`, `'admin'`).
|
|
209
|
+
*/
|
|
210
|
+
async hasRole(role) {
|
|
211
|
+
const user = auth$1.currentUser;
|
|
212
|
+
if (!user) return false;
|
|
213
|
+
if (hasCustomExtractor) {
|
|
214
|
+
return roleExtractor(user).includes(role);
|
|
215
|
+
}
|
|
216
|
+
const roles = await extractRolesFromClaims(user);
|
|
217
|
+
return roles.includes(role);
|
|
218
|
+
},
|
|
219
|
+
/**
|
|
220
|
+
* Maps to `onAuthStateChanged(auth, callback)`.
|
|
221
|
+
*
|
|
222
|
+
* Firebase's `onAuthStateChanged()` returns an `Unsubscribe` function
|
|
223
|
+
* directly — a 1:1 mapping to Enterstellar's `onAuthChange()` signature.
|
|
224
|
+
*
|
|
225
|
+
* **Role resolution in callbacks:**
|
|
226
|
+
* - With custom `roleExtractor`: roles are extracted synchronously
|
|
227
|
+
* from the Firebase `User` object.
|
|
228
|
+
* - Without custom `roleExtractor`: roles default to `[]` in the
|
|
229
|
+
* callback. Full role resolution happens via `getSession()`/`hasRole()`.
|
|
230
|
+
* This is a pragmatic tradeoff — `getIdTokenResult()` is async and
|
|
231
|
+
* cannot be awaited inside `onAuthStateChanged`.
|
|
232
|
+
*
|
|
233
|
+
* @param callback - Called with the new Enterstellar session or `null`.
|
|
234
|
+
*/
|
|
235
|
+
onAuthChange(callback) {
|
|
236
|
+
return auth.onAuthStateChanged(auth$1, (user) => {
|
|
237
|
+
if (hasCustomExtractor) {
|
|
238
|
+
callback(toEnterstellarSessionSync(user, roleExtractor));
|
|
239
|
+
} else {
|
|
240
|
+
callback(user ? { userId: user.uid, roles: [] } : null);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
var DEFAULT_NAME2 = "firebase-data";
|
|
247
|
+
function createFirebaseDataAdapter(config) {
|
|
248
|
+
const { firestore: firestore$1, name = DEFAULT_NAME2 } = config;
|
|
249
|
+
return createDataAdapter({
|
|
250
|
+
name,
|
|
251
|
+
/**
|
|
252
|
+
* Maps to `getDocs(collection(firestore, resource))` with optional
|
|
253
|
+
* `where()` equality constraints.
|
|
254
|
+
*
|
|
255
|
+
* Each key-value pair in `params` is applied as a `where(key, '==', value)`
|
|
256
|
+
* constraint. If `params` is omitted, fetches all documents in the collection.
|
|
257
|
+
*
|
|
258
|
+
* Each document is returned as `{ id, ...data() }` — the Firestore document
|
|
259
|
+
* ID is injected as the `id` field for consistency with Supabase and Enterstellar
|
|
260
|
+
* conventions.
|
|
261
|
+
*
|
|
262
|
+
* @param resource - Collection name (AD3 dot-notation supported at v1 as literal).
|
|
263
|
+
* @param params - Optional equality filters.
|
|
264
|
+
*/
|
|
265
|
+
async query(resource, params) {
|
|
266
|
+
const collectionRef = firestore.collection(firestore$1, resource);
|
|
267
|
+
const constraints = params ? Object.entries(params).map(([key, value]) => firestore.where(key, "==", value)) : [];
|
|
268
|
+
const queryRef = constraints.length > 0 ? firestore.query(collectionRef, ...constraints) : collectionRef;
|
|
269
|
+
const snapshot = await firestore.getDocs(queryRef);
|
|
270
|
+
return snapshot.docs.map((docSnap) => ({
|
|
271
|
+
id: docSnap.id,
|
|
272
|
+
...docSnap.data()
|
|
273
|
+
}));
|
|
274
|
+
},
|
|
275
|
+
/**
|
|
276
|
+
* Maps to `addDoc` / `updateDoc` / `deleteDoc` based on action.
|
|
277
|
+
*
|
|
278
|
+
* - `'create'` → `addDoc(collection(...), data)` → returns `{ id, ...data }`
|
|
279
|
+
* - `'update'` → `updateDoc(doc(..., data.id), data)` → returns updated data with `id`
|
|
280
|
+
* - `'delete'` → `deleteDoc(doc(..., data.id))` → returns `null`
|
|
281
|
+
*
|
|
282
|
+
* For `'update'` and `'delete'`, the `data` payload MUST include an `id`
|
|
283
|
+
* field identifying the Firestore document.
|
|
284
|
+
*
|
|
285
|
+
* @param resource - Collection name.
|
|
286
|
+
* @param action - Mutation type: `'create'`, `'update'`, or `'delete'`.
|
|
287
|
+
* @param data - Mutation payload. For update/delete, must include an `id` field.
|
|
288
|
+
*/
|
|
289
|
+
async mutate(resource, action, data) {
|
|
290
|
+
if (action === "create") {
|
|
291
|
+
const { id: _id, ...payload } = data;
|
|
292
|
+
const docRef2 = await firestore.addDoc(firestore.collection(firestore$1, resource), payload);
|
|
293
|
+
return { id: docRef2.id, ...payload };
|
|
294
|
+
}
|
|
295
|
+
if (action === "update") {
|
|
296
|
+
const documentId2 = data["id"];
|
|
297
|
+
const docRef2 = firestore.doc(firestore$1, resource, documentId2);
|
|
298
|
+
const { id: _id, ...payload } = data;
|
|
299
|
+
await firestore.updateDoc(docRef2, payload);
|
|
300
|
+
return { id: documentId2, ...payload };
|
|
301
|
+
}
|
|
302
|
+
const documentId = data["id"];
|
|
303
|
+
const docRef = firestore.doc(firestore$1, resource, documentId);
|
|
304
|
+
await firestore.deleteDoc(docRef);
|
|
305
|
+
return null;
|
|
306
|
+
},
|
|
307
|
+
/**
|
|
308
|
+
* Maps to `onSnapshot(collection(firestore, resource), snapshot => ...)`.
|
|
309
|
+
*
|
|
310
|
+
* Subscribes to Firestore realtime updates for the specified collection.
|
|
311
|
+
* When documents change, the callback receives all documents in the
|
|
312
|
+
* collection with `{ id, ...data() }` shape.
|
|
313
|
+
*
|
|
314
|
+
* Returns the Firestore `Unsubscribe` function directly — a 1:1 mapping
|
|
315
|
+
* to Enterstellar's `subscribe()` return type.
|
|
316
|
+
*
|
|
317
|
+
* @param resource - Collection name to subscribe to.
|
|
318
|
+
* @param callback - Called with the full set of documents after each change.
|
|
319
|
+
*/
|
|
320
|
+
subscribe(resource, callback) {
|
|
321
|
+
const collectionRef = firestore.collection(firestore$1, resource);
|
|
322
|
+
return firestore.onSnapshot(collectionRef, (snapshot) => {
|
|
323
|
+
const records = snapshot.docs.map((docSnap) => ({
|
|
324
|
+
id: docSnap.id,
|
|
325
|
+
...docSnap.data()
|
|
326
|
+
}));
|
|
327
|
+
callback(records);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/version.ts
|
|
334
|
+
var FIREBASE_ADAPTER_VERSION = "0.0.0";
|
|
335
|
+
|
|
336
|
+
exports.FIREBASE_ADAPTER_VERSION = FIREBASE_ADAPTER_VERSION;
|
|
337
|
+
exports.createFirebaseAuthAdapter = createFirebaseAuthAdapter;
|
|
338
|
+
exports.createFirebaseDataAdapter = createFirebaseDataAdapter;
|
|
339
|
+
//# sourceMappingURL=index.cjs.map
|
|
340
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../adapters/src/errors.ts","../../adapters/src/validate-adapter.ts","../../adapters/src/create-auth-adapter.ts","../../adapters/src/create-data-adapter.ts","../src/create-firebase-auth-adapter.ts","../src/create-firebase-data-adapter.ts","../src/version.ts"],"names":["EnterstellarError","auth","onAuthStateChanged","DEFAULT_NAME","firestore","collection","where","firestoreQuery","getDocs","docRef","addDoc","documentId","doc","updateDoc","deleteDoc","onSnapshot"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @module @enterstellar-ai/adapters/errors\n * @description Error factory functions for the adapters module.\n *\n * Every error is an `EnterstellarError` with:\n * - Machine-readable `code` (`ENS-7xxx`)\n * - Module identifier `'adapters'`\n * - `recoverable` flag per Enterstellar error taxonomy\n *\n * Error taxonomy:\n * - `ENS-7001` — ADAPTER_VALIDATION_FAILED: config missing required methods or invalid name\n * (non-recoverable, developer misconfiguration)\n * - `ENS-7002` — ADAPTER_METHOD_ERROR: adapter method threw during execution (recoverable)\n * - `ENS-7003` — ADAPTER_QUERY_ERROR: DataAdapter query() failed (recoverable)\n * - `ENS-7004` — ADAPTER_MUTATION_ERROR: DataAdapter mutate() failed (recoverable)\n * - `ENS-7005` — ADAPTER_AUTH_ERROR: AuthAdapter session/role check failed (recoverable)\n *\n * @see Coding Rules — Error Taxonomy\n * @see Design Choice AD5 — wrap into EnterstellarError\n * @see Design Choice C14 — error code ranges\n */\n\nimport { EnterstellarError } from '@enterstellar-ai/types';\n\nimport type { AdapterType } from './types.js';\n\n// ---------------------------------------------------------------------------\n// ENS-7001: Adapter Validation Failed\n// ---------------------------------------------------------------------------\n\n/**\n * Creates an error for when an adapter config fails validation.\n *\n * This is a **non-recoverable** error — it indicates developer misconfiguration\n * (missing required methods, empty name, non-function fields). The consumer\n * must fix their adapter config before proceeding.\n *\n * @param adapterType - The adapter category that failed validation (e.g., `'auth'`, `'data'`).\n * @param reason - Human-readable explanation of what is invalid.\n * @returns An `EnterstellarError` with code `ENS-7001`.\n *\n * @example\n * ```ts\n * throw adapterValidationError('auth', 'Missing required method: getSession');\n * // EnterstellarError: Adapter validation failed for \"auth\": Missing required method: getSession\n * // code: 'ENS-7001', module: 'adapters', recoverable: false\n * ```\n */\nexport function adapterValidationError(\n adapterType: AdapterType,\n reason: string,\n): EnterstellarError {\n return new EnterstellarError(\n 'ENS-7001',\n 'adapters',\n `Adapter validation failed for \"${adapterType}\": ${reason}`,\n false, // non-recoverable — developer misconfiguration\n );\n}\n\n// ---------------------------------------------------------------------------\n// ENS-7002: Adapter Method Error\n// ---------------------------------------------------------------------------\n\n/**\n * Creates an error for when an adapter method throws during execution.\n *\n * This is a **recoverable** error — the underlying infrastructure may have\n * experienced a transient failure. The operation can be retried. The original\n * error is preserved in `cause` for debugging (AD5).\n *\n * @param adapterName - The adapter instance name (e.g., `'supabase-auth'`).\n * @param methodName - The method that threw (e.g., `'getSession'`, `'track'`).\n * @param cause - The original error thrown by the adapter implementation.\n * @returns An `EnterstellarError` with code `ENS-7002`.\n *\n * @example\n * ```ts\n * throw adapterMethodError('supabase-auth', 'getSession', originalError);\n * // EnterstellarError: Adapter \"supabase-auth\" method \"getSession\" threw.\n * // code: 'ENS-7002', module: 'adapters', recoverable: true, cause: originalError\n * ```\n */\nexport function adapterMethodError(\n adapterName: string,\n methodName: string,\n cause?: unknown,\n): EnterstellarError {\n return new EnterstellarError(\n 'ENS-7002',\n 'adapters',\n `Adapter \"${adapterName}\" method \"${methodName}\" threw.`,\n true, // recoverable — transient infrastructure failure\n cause,\n );\n}\n\n// ---------------------------------------------------------------------------\n// ENS-7003: Adapter Query Error\n// ---------------------------------------------------------------------------\n\n/**\n * Creates an error for when a {@link DataAdapter}'s `query()` method fails.\n *\n * This is a **recoverable** error — the data source may be temporarily\n * unavailable. Specialized variant of `ENS-7002` that includes the\n * queried resource name for debugging.\n *\n * @param adapterName - The adapter instance name (e.g., `'supabase-data'`).\n * @param resource - The resource that was being queried (e.g., `'patients.vitals'`).\n * @param cause - The original error thrown by the query implementation.\n * @returns An `EnterstellarError` with code `ENS-7003`.\n *\n * @example\n * ```ts\n * throw adapterQueryError('supabase-data', 'patients.vitals', pgError);\n * // EnterstellarError: Adapter \"supabase-data\" query failed for resource \"patients.vitals\".\n * // code: 'ENS-7003', module: 'adapters', recoverable: true, cause: pgError\n * ```\n */\nexport function adapterQueryError(\n adapterName: string,\n resource: string,\n cause?: unknown,\n): EnterstellarError {\n return new EnterstellarError(\n 'ENS-7003',\n 'adapters',\n `Adapter \"${adapterName}\" query failed for resource \"${resource}\".`,\n true, // recoverable — transient data source failure\n cause,\n );\n}\n\n// ---------------------------------------------------------------------------\n// ENS-7004: Adapter Mutation Error\n// ---------------------------------------------------------------------------\n\n/**\n * Creates an error for when a {@link DataAdapter}'s `mutate()` method fails.\n *\n * This is a **recoverable** error — the data source may be temporarily\n * unavailable. Specialized variant of `ENS-7002` that includes the\n * resource name and mutation action for debugging.\n *\n * @param adapterName - The adapter instance name (e.g., `'supabase-data'`).\n * @param resource - The resource being mutated (e.g., `'patients'`).\n * @param action - The mutation action that failed (`'create'`, `'update'`, or `'delete'`).\n * @param cause - The original error thrown by the mutation implementation.\n * @returns An `EnterstellarError` with code `ENS-7004`.\n *\n * @example\n * ```ts\n * throw adapterMutationError('supabase-data', 'patients', 'update', pgError);\n * // EnterstellarError: Adapter \"supabase-data\" mutation \"update\" failed for resource \"patients\".\n * // code: 'ENS-7004', module: 'adapters', recoverable: true, cause: pgError\n * ```\n */\nexport function adapterMutationError(\n adapterName: string,\n resource: string,\n action: 'create' | 'update' | 'delete',\n cause?: unknown,\n): EnterstellarError {\n return new EnterstellarError(\n 'ENS-7004',\n 'adapters',\n `Adapter \"${adapterName}\" mutation \"${action}\" failed for resource \"${resource}\".`,\n true, // recoverable — transient data source failure\n cause,\n );\n}\n\n// ---------------------------------------------------------------------------\n// ENS-7005: Adapter Auth Error\n// ---------------------------------------------------------------------------\n\n/**\n * Creates an error for when an {@link AuthAdapter}'s session or role check fails.\n *\n * This is a **recoverable** error — the auth provider may be temporarily\n * unavailable, or the session may have expired. Specialized variant of\n * `ENS-7002` for authentication operations.\n *\n * @param adapterName - The adapter instance name (e.g., `'clerk-auth'`).\n * @param operation - The auth operation that failed (e.g., `'getSession'`, `'hasRole'`).\n * @param cause - The original error thrown by the auth implementation.\n * @returns An `EnterstellarError` with code `ENS-7005`.\n *\n * @example\n * ```ts\n * throw adapterAuthError('clerk-auth', 'getSession', sessionExpiredError);\n * // EnterstellarError: Adapter \"clerk-auth\" auth operation \"getSession\" failed.\n * // code: 'ENS-7005', module: 'adapters', recoverable: true, cause: sessionExpiredError\n * ```\n */\nexport function adapterAuthError(\n adapterName: string,\n operation: string,\n cause?: unknown,\n): EnterstellarError {\n return new EnterstellarError(\n 'ENS-7005',\n 'adapters',\n `Adapter \"${adapterName}\" auth operation \"${operation}\" failed.`,\n true, // recoverable — auth provider may be temporarily unavailable\n cause,\n );\n}\n","/**\n * @module @enterstellar-ai/adapters/validate-adapter\n * @description Shared runtime validation for adapter configuration objects.\n *\n * Called by each `createXxxAdapter()` factory before wrapping the consumer's\n * implementation. Validates that:\n * - The config has a non-empty `name` string\n * - All required methods for the adapter type are present and are functions\n *\n * Throws `ENS-7001` (`adapterValidationError`) on any violation — this is a\n * non-recoverable developer error per Enterstellar error taxonomy.\n *\n * @see Coding Rules — Error Taxonomy (developer errors → fatal throw)\n * @see Design Choice AD1 — minimal but complete interfaces\n */\n\nimport { adapterValidationError } from './errors.js';\nimport type { AdapterType } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Required Method Maps\n// ---------------------------------------------------------------------------\n\n/**\n * Maps each adapter type to the set of method names that the config\n * object MUST provide as function values.\n *\n * @remarks\n * These lists must stay in sync with the corresponding config types\n * in `types.ts` and the adapter interfaces in `@enterstellar-ai/types/adapters`.\n */\nconst REQUIRED_METHODS: Readonly<Record<AdapterType, readonly string[]>> = {\n auth: ['getSession', 'hasRole', 'onAuthChange'],\n data: ['query', 'mutate', 'subscribe'],\n error: ['report', 'shouldRetry', 'sanitize'],\n analytics: ['track', 'identify'],\n};\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\n/**\n * Validates that an adapter config object has a valid name and all required methods.\n *\n * This function performs two checks:\n * 1. **Name check:** `config.name` must be a non-empty string.\n * 2. **Method check:** Every method listed in {@link REQUIRED_METHODS} for the\n * given `adapterType` must be present on the config and be `typeof === 'function'`.\n *\n * Throws `ENS-7001` on the first validation failure encountered.\n *\n * @param adapterType - The adapter category being validated (e.g., `'auth'`, `'data'`).\n * @param config - The raw config object to validate.\n * @throws `EnterstellarError` with code `ENS-7001` if validation fails.\n *\n * @example\n * ```ts\n * // Valid — passes silently\n * validateAdapterConfig('auth', {\n * name: 'supabase-auth',\n * getSession: async () => null,\n * hasRole: async () => false,\n * onAuthChange: (cb) => () => {},\n * });\n *\n * // Invalid — throws ENS-7001\n * validateAdapterConfig('auth', { name: '', getSession: null });\n * // EnterstellarError: Adapter validation failed for \"auth\": \"name\" must be a non-empty string.\n * ```\n */\nexport function validateAdapterConfig(\n adapterType: AdapterType,\n config: Readonly<Record<string, unknown>>,\n): void {\n // -----------------------------------------------------------------------\n // 1. Name validation\n // -----------------------------------------------------------------------\n const name = config['name'];\n\n if (typeof name !== 'string' || name.length === 0) {\n throw adapterValidationError(\n adapterType,\n '\"name\" must be a non-empty string.',\n );\n }\n\n // -----------------------------------------------------------------------\n // 2. Required method validation\n // -----------------------------------------------------------------------\n const requiredMethods = REQUIRED_METHODS[adapterType];\n\n for (const methodName of requiredMethods) {\n const method = config[methodName];\n\n if (typeof method !== 'function') {\n const receivedType = method === null ? 'null' : typeof method;\n throw adapterValidationError(\n adapterType,\n `Missing or invalid method \"${methodName}\". Expected function, received ${receivedType}.`,\n );\n }\n }\n}\n","/**\n * @module @enterstellar-ai/adapters/create-auth-adapter\n * @description Factory functions for creating validated `AuthAdapter` instances.\n *\n * - `createAuthAdapter(config)` — wraps a consumer-provided implementation,\n * validates config via {@link validateAdapterConfig}, and wraps every method\n * in error handling per Design Choice AD5 (raw vendor errors never leak).\n *\n * - `createNoopAuthAdapter()` — returns a no-op adapter for testing and\n * development when no real auth provider is connected.\n *\n * Both factories return a plain object with closures (R1 pattern — no classes).\n *\n * @see Bible §4.15\n * @see Design Choice AD1 — minimal but complete: getSession, hasRole, onAuthChange\n * @see Design Choice AD2 — always async (I/O methods)\n * @see Design Choice AD5 — wrap into EnterstellarError\n */\n\nimport type { AuthAdapter } from '@enterstellar-ai/types';\n\nimport { adapterAuthError, adapterMethodError } from './errors.js';\nimport type { AuthAdapterConfig } from './types.js';\nimport { validateAdapterConfig } from './validate-adapter.js';\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a validated `AuthAdapter` from consumer-provided config.\n *\n * The factory:\n * 1. Validates the config (name + required methods) — throws `ENS-7001` on failure.\n * 2. Wraps `getSession()` and `hasRole()` in error handling → `ENS-7005` on throw.\n * 3. Wraps `onAuthChange()` in error handling → `ENS-7002` on throw.\n *\n * Consumers never see raw vendor errors — all failures are `EnterstellarError` (AD5).\n *\n * @param config - The adapter implementation with a `name` and all required methods.\n * @returns A frozen `AuthAdapter` instance.\n * @throws `EnterstellarError` with code `ENS-7001` if config validation fails.\n *\n * @example\n * ```ts\n * import { createAuthAdapter } from '@enterstellar-ai/adapters';\n *\n * const auth = createAuthAdapter({\n * name: 'supabase-auth',\n * getSession: async () => {\n * const { data } = await supabase.auth.getSession();\n * if (!data.session) return null;\n * return { userId: data.session.user.id, roles: ['clinician'] };\n * },\n * hasRole: async (role) => {\n * const session = await supabase.auth.getSession();\n * return session.data.session?.user.role === role;\n * },\n * onAuthChange: (cb) => {\n * const { data } = supabase.auth.onAuthStateChange((_event, session) => {\n * cb(session ? { userId: session.user.id, roles: ['clinician'] } : null);\n * });\n * return () => data.subscription.unsubscribe();\n * },\n * });\n * ```\n */\nexport function createAuthAdapter(config: AuthAdapterConfig): AuthAdapter {\n // -----------------------------------------------------------------------\n // Step 1: Validate config — throws ENS-7001 on failure\n // -----------------------------------------------------------------------\n validateAdapterConfig('auth', config);\n\n const adapterName = config.name;\n\n // -----------------------------------------------------------------------\n // Step 2: Build wrapped adapter (plain object with closures — R1 pattern)\n // -----------------------------------------------------------------------\n const adapter: AuthAdapter = {\n /**\n * Wrapped `getSession()` — catches vendor errors → `ENS-7005`.\n */\n async getSession(): Promise<{ userId: string; roles: string[] } | null> {\n try {\n return await config.getSession();\n } catch (error: unknown) {\n throw adapterAuthError(adapterName, 'getSession', error);\n }\n },\n\n /**\n * Wrapped `hasRole()` — catches vendor errors → `ENS-7005`.\n */\n async hasRole(role: string): Promise<boolean> {\n try {\n return await config.hasRole(role);\n } catch (error: unknown) {\n throw adapterAuthError(adapterName, 'hasRole', error);\n }\n },\n\n /**\n * Wrapped `onAuthChange()` — catches subscription setup errors → `ENS-7002`.\n * Uses generic method error (not auth-specific) because `onAuthChange` is\n * subscription management, not auth state retrieval.\n */\n onAuthChange(\n callback: (session: { userId: string; roles: string[] } | null) => void,\n ): () => void {\n try {\n return config.onAuthChange(callback);\n } catch (error: unknown) {\n throw adapterMethodError(adapterName, 'onAuthChange', error);\n }\n },\n };\n\n // -----------------------------------------------------------------------\n // Step 3: Freeze and return — prevents accidental mutation (R4 pattern)\n // -----------------------------------------------------------------------\n return Object.freeze(adapter);\n}\n\n// ---------------------------------------------------------------------------\n// No-Op Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a no-op `AuthAdapter` for testing and development.\n *\n * All methods resolve to safe defaults:\n * - `getSession()` → `null` (unauthenticated)\n * - `hasRole()` → `false` (no permissions)\n * - `onAuthChange()` → no-op unsubscribe function (never fires)\n *\n * @returns A frozen, no-op `AuthAdapter` instance.\n *\n * @example\n * ```ts\n * import { createNoopAuthAdapter } from '@enterstellar-ai/adapters';\n *\n * const auth = createNoopAuthAdapter();\n * await auth.getSession(); // null\n * await auth.hasRole('admin'); // false\n * const unsub = auth.onAuthChange(() => {}); // never called\n * unsub(); // no-op\n * ```\n */\nexport function createNoopAuthAdapter(): AuthAdapter {\n const adapter: AuthAdapter = {\n /** Returns `null` — no active session in noop mode. */\n getSession(): Promise<{ userId: string; roles: string[] } | null> {\n return Promise.resolve(null);\n },\n\n /** Returns `false` — no permissions in noop mode. */\n hasRole(_role: string): Promise<boolean> {\n return Promise.resolve(false);\n },\n\n /** Returns a no-op unsubscribe function — never fires a callback. */\n onAuthChange(\n _callback: (session: { userId: string; roles: string[] } | null) => void,\n ): () => void {\n // No-op — no auth state changes to subscribe to.\n return () => { /* noop unsubscribe */ };\n },\n };\n\n return Object.freeze(adapter);\n}\n","/**\n * @module @enterstellar-ai/adapters/create-data-adapter\n * @description Factory functions for creating validated `DataAdapter` instances.\n *\n * - `createDataAdapter(config)` — wraps a consumer-provided implementation,\n * validates config via {@link validateAdapterConfig}, and wraps every method\n * in error handling per Design Choice AD5 (raw vendor errors never leak).\n *\n * - `createNoopDataAdapter()` — returns a no-op adapter for testing and\n * development when no real data source is connected.\n *\n * Both factories return a plain object with closures (R1 pattern — no classes).\n *\n * @see Bible §4.15\n * @see Design Choice AD1 — minimal but complete: query, mutate, subscribe\n * @see Design Choice AD2 — always async (except subscribe's sync unsubscribe return)\n * @see Design Choice AD3 — convention-based dot-notation resolver\n * @see Design Choice AD5 — wrap into EnterstellarError\n */\n\nimport type { DataAdapter } from '@enterstellar-ai/types';\n\nimport { adapterMethodError, adapterMutationError, adapterQueryError } from './errors.js';\nimport type { DataAdapterConfig } from './types.js';\nimport { validateAdapterConfig } from './validate-adapter.js';\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a validated `DataAdapter` from consumer-provided config.\n *\n * The factory:\n * 1. Validates the config (name + required methods) — throws `ENS-7001` on failure.\n * 2. Wraps `query()` in error handling → `ENS-7003` on throw (includes resource name).\n * 3. Wraps `mutate()` in error handling → `ENS-7004` on throw (includes resource + action).\n * 4. Wraps `subscribe()` in error handling → `ENS-7002` on throw (generic method error).\n *\n * Consumers never see raw vendor errors — all failures are `EnterstellarError` (AD5).\n *\n * @param config - The adapter implementation with a `name` and all required methods.\n * @returns A frozen `DataAdapter` instance.\n * @throws `EnterstellarError` with code `ENS-7001` if config validation fails.\n *\n * @example\n * ```ts\n * import { createDataAdapter } from '@enterstellar-ai/adapters';\n *\n * const data = createDataAdapter({\n * name: 'supabase-data',\n * query: async (resource, params) => {\n * const { data } = await supabase.from(resource).select('*').match(params ?? {});\n * return data ?? [];\n * },\n * mutate: async (resource, action, payload) => {\n * if (action === 'create') {\n * const { data } = await supabase.from(resource).insert(payload).select().single();\n * return data;\n * }\n * return null;\n * },\n * subscribe: (resource, callback) => {\n * const channel = supabase.channel(resource)\n * .on('postgres_changes', { event: '*', schema: 'public', table: resource },\n * () => { data.query(resource).then(callback); })\n * .subscribe();\n * return () => { void supabase.removeChannel(channel); };\n * },\n * });\n * ```\n */\nexport function createDataAdapter(config: DataAdapterConfig): DataAdapter {\n // -----------------------------------------------------------------------\n // Step 1: Validate config — throws ENS-7001 on failure\n // -----------------------------------------------------------------------\n validateAdapterConfig('data', config);\n\n const adapterName = config.name;\n\n // -----------------------------------------------------------------------\n // Step 2: Build wrapped adapter (plain object with closures — R1 pattern)\n // -----------------------------------------------------------------------\n const adapter: DataAdapter = {\n /**\n * Wrapped `query()` — catches vendor errors → `ENS-7003`.\n * Includes the queried resource name in the error for debugging.\n */\n async query(\n resource: string,\n params?: Readonly<Record<string, unknown>>,\n ): Promise<readonly Record<string, unknown>[]> {\n try {\n return await config.query(resource, params);\n } catch (error: unknown) {\n throw adapterQueryError(adapterName, resource, error);\n }\n },\n\n /**\n * Wrapped `mutate()` — catches vendor errors → `ENS-7004`.\n * Includes the resource name and mutation action in the error for debugging.\n */\n async mutate(\n resource: string,\n action: 'create' | 'update' | 'delete',\n data: Readonly<Record<string, unknown>>,\n ): Promise<Record<string, unknown> | null> {\n try {\n return await config.mutate(resource, action, data);\n } catch (error: unknown) {\n throw adapterMutationError(adapterName, resource, action, error);\n }\n },\n\n /**\n * Wrapped `subscribe()` — catches vendor errors → `ENS-7002`.\n *\n * Only the `subscribe()` invocation itself is wrapped. The consumer's\n * callback is NOT wrapped — callback errors are the consumer's responsibility.\n * The returned unsubscribe function is also NOT wrapped — unsubscribe\n * failures are fire-and-forget cleanup operations.\n */\n subscribe(\n resource: string,\n callback: (data: readonly Record<string, unknown>[]) => void,\n ): () => void {\n try {\n return config.subscribe(resource, callback);\n } catch (error: unknown) {\n throw adapterMethodError(adapterName, 'subscribe', error);\n }\n },\n };\n\n // -----------------------------------------------------------------------\n // Step 3: Freeze and return — prevents accidental mutation (R4 pattern)\n // -----------------------------------------------------------------------\n return Object.freeze(adapter);\n}\n\n// ---------------------------------------------------------------------------\n// No-Op Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a no-op `DataAdapter` for testing and development.\n *\n * All methods resolve to safe defaults:\n * - `query()` → `[]` (empty result set)\n * - `mutate()` → `null` (no record returned)\n * - `subscribe()` → no-op unsubscribe function\n *\n * @returns A frozen, no-op `DataAdapter` instance.\n *\n * @example\n * ```ts\n * import { createNoopDataAdapter } from '@enterstellar-ai/adapters';\n *\n * const data = createNoopDataAdapter();\n * await data.query('patients.vitals'); // []\n * await data.mutate('patients', 'create', { name: 'Test' }); // null\n * const unsub = data.subscribe('patients', () => {}); // noop unsub\n * unsub(); // no-op\n * ```\n */\nexport function createNoopDataAdapter(): DataAdapter {\n const adapter: DataAdapter = {\n query(\n _resource: string,\n _params?: Readonly<Record<string, unknown>>,\n ): Promise<readonly Record<string, unknown>[]> {\n return Promise.resolve([]);\n },\n\n mutate(\n _resource: string,\n _action: 'create' | 'update' | 'delete',\n _data: Readonly<Record<string, unknown>>,\n ): Promise<Record<string, unknown> | null> {\n return Promise.resolve(null);\n },\n\n subscribe(\n _resource: string,\n _callback: (data: readonly Record<string, unknown>[]) => void,\n ): () => void {\n // Return a no-op unsubscribe function.\n return () => {\n // No-op — no subscription to clean up.\n };\n },\n };\n\n return Object.freeze(adapter);\n}\n","/**\n * @module @enterstellar-ai/adapter-firebase/create-firebase-auth-adapter\n * @description Factory function for creating a Firebase-backed `AuthAdapter`.\n *\n * This factory maps Firebase Auth SDK calls to the Enterstellar `AuthAdapter` interface:\n * - `getSession()` → `auth.currentUser` + `getIdTokenResult()` → `{ userId, roles } | null`\n * - `hasRole(role)` → `getSession()` → checks `roles.includes(role)`\n * - `onAuthChange(cb)` → `onAuthStateChanged(auth, cb)` → returns `unsubscribe`\n *\n * It builds an `AuthAdapterConfig` and delegates to `createAuthAdapter()` from\n * `@enterstellar-ai/adapters`, which handles all validation (ENS-7001) and AD5 error\n * wrapping (ENS-7005 / ENS-7002). This factory is purely an SDK-to-Enterstellar translator.\n *\n * ## Role Extraction Strategy\n *\n * Firebase stores RBAC roles in custom claims via `getIdTokenResult().claims`.\n * The default behavior extracts roles from `claims['roles']` (an async operation).\n * If a custom `roleExtractor` is provided, it is called synchronously with the\n * raw Firebase `User` object — this enables `onAuthChange()` to include roles\n * in the callback (since `onAuthChange` is synchronous).\n *\n * When no custom `roleExtractor` is provided and `onAuthChange()` fires,\n * roles default to `[]` in the callback. Full role resolution happens\n * through `getSession()` or `hasRole()` (which can await `getIdTokenResult()`).\n *\n * @see Bible §4.15\n * @see Design Choice AD1 — minimal but complete: getSession, hasRole, onAuthChange\n * @see Design Choice AD5 — error wrapping delegated to createAuthAdapter()\n */\n\nimport type { AuthAdapter } from '@enterstellar-ai/types';\nimport type { User } from 'firebase/auth';\nimport { onAuthStateChanged } from 'firebase/auth';\n\nimport { createAuthAdapter } from '@enterstellar-ai/adapters';\n\nimport type { FirebaseAuthConfig } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Default adapter name when none is provided via config. */\nconst DEFAULT_NAME = 'firebase-auth';\n\n// ---------------------------------------------------------------------------\n// Internal Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts roles from a Firebase user via `getIdTokenResult()` custom claims.\n *\n * This is an **async** operation — it fetches the ID token result from Firebase\n * to read custom claims. Falls back to `[]` if the `roles` claim is missing\n * or not an array.\n *\n * @param user - The Firebase `User` object.\n * @returns Array of role strings.\n *\n * @internal\n */\nasync function extractRolesFromClaims(user: User): Promise<string[]> {\n const tokenResult = await user.getIdTokenResult();\n const roles = tokenResult.claims['roles'];\n\n if (!Array.isArray(roles)) return [];\n\n // Ensure all elements are strings — reject non-string values silently\n return roles.filter((role): role is string => typeof role === 'string');\n}\n\n/**\n * Converts a Firebase `User` to the Enterstellar session shape using the\n * synchronous `roleExtractor`. Returns `null` if the user is null.\n *\n * Only used when a custom `roleExtractor` is provided (synchronous path).\n *\n * @param user - The Firebase `User` object (or null).\n * @param roleExtractor - The synchronous role extraction function.\n * @returns Enterstellar session `{ userId, roles }` or `null`.\n *\n * @internal\n */\nfunction toEnterstellarSessionSync(\n user: User | null,\n roleExtractor: (user: unknown) => string[],\n): { userId: string; roles: string[] } | null {\n if (!user) return null;\n return { userId: user.uid, roles: roleExtractor(user) };\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a Firebase-backed `AuthAdapter`.\n *\n * Maps Firebase Auth SDK methods to the Enterstellar `AuthAdapter` interface,\n * then delegates to `createAuthAdapter()` from `@enterstellar-ai/adapters` for\n * config validation and AD5 error wrapping.\n *\n * @param config - Firebase auth configuration with `Auth` instance and optional overrides.\n * @returns A frozen, validated `AuthAdapter` instance.\n * @throws `EnterstellarError` with code `ENS-7001` if config validation fails.\n *\n * @example\n * ```ts\n * import { initializeApp } from 'firebase/app';\n * import { getAuth } from 'firebase/auth';\n * import { createFirebaseAuthAdapter } from '@enterstellar-ai/adapter-firebase';\n *\n * const app = initializeApp({ projectId: 'my-project', ... });\n * const firebaseAuth = getAuth(app);\n *\n * const auth = createFirebaseAuthAdapter({ auth: firebaseAuth });\n *\n * // With custom role extraction (synchronous — enables roles in onAuthChange)\n * const authWithRoles = createFirebaseAuthAdapter({\n * auth: firebaseAuth,\n * roleExtractor: (user) => {\n * const u = user as { customClaims?: { roles?: string[] } };\n * return u.customClaims?.roles ?? [];\n * },\n * });\n *\n * // Usage\n * const session = await auth.getSession(); // { userId, roles } | null\n * const isAdmin = await auth.hasRole('admin'); // boolean\n * const unsub = auth.onAuthChange((session) => {\n * console.log('Auth state changed:', session);\n * });\n * ```\n */\nexport function createFirebaseAuthAdapter(config: FirebaseAuthConfig): AuthAdapter {\n const { auth, name = DEFAULT_NAME, roleExtractor } = config;\n\n // -----------------------------------------------------------------------\n // Determine role extraction strategy\n // -----------------------------------------------------------------------\n // If a custom roleExtractor is provided, use it synchronously everywhere.\n // If not, use async getIdTokenResult() for getSession/hasRole,\n // and fall back to [] for onAuthChange (sync context).\n const hasCustomExtractor = typeof roleExtractor === 'function';\n\n // -----------------------------------------------------------------------\n // Build AuthAdapterConfig and delegate to createAuthAdapter()\n // -----------------------------------------------------------------------\n\n return createAuthAdapter({\n name,\n\n /**\n * Maps to `auth.currentUser` + role extraction.\n *\n * If a custom `roleExtractor` is provided, uses it synchronously.\n * Otherwise, fetches roles from `getIdTokenResult().claims['roles']`.\n *\n * Returns `null` if no user is signed in (`auth.currentUser === null`).\n */\n async getSession(): Promise<{ userId: string; roles: string[] } | null> {\n const user = auth.currentUser;\n if (!user) return null;\n\n if (hasCustomExtractor) {\n return { userId: user.uid, roles: roleExtractor(user) };\n }\n\n // Default: async role extraction from custom claims\n const roles = await extractRolesFromClaims(user);\n return { userId: user.uid, roles };\n },\n\n /**\n * Maps to `getSession()` → checks `roles.includes(role)`.\n *\n * DRY pattern: re-uses session logic to avoid duplicating the\n * user fetch and role extraction. Returns `false` if no user\n * is signed in.\n *\n * @param role - The role to check (e.g., `'clinician'`, `'admin'`).\n */\n async hasRole(role: string): Promise<boolean> {\n const user = auth.currentUser;\n if (!user) return false;\n\n if (hasCustomExtractor) {\n return roleExtractor(user).includes(role);\n }\n\n // Default: async role extraction from custom claims\n const roles = await extractRolesFromClaims(user);\n return roles.includes(role);\n },\n\n /**\n * Maps to `onAuthStateChanged(auth, callback)`.\n *\n * Firebase's `onAuthStateChanged()` returns an `Unsubscribe` function\n * directly — a 1:1 mapping to Enterstellar's `onAuthChange()` signature.\n *\n * **Role resolution in callbacks:**\n * - With custom `roleExtractor`: roles are extracted synchronously\n * from the Firebase `User` object.\n * - Without custom `roleExtractor`: roles default to `[]` in the\n * callback. Full role resolution happens via `getSession()`/`hasRole()`.\n * This is a pragmatic tradeoff — `getIdTokenResult()` is async and\n * cannot be awaited inside `onAuthStateChanged`.\n *\n * @param callback - Called with the new Enterstellar session or `null`.\n */\n onAuthChange(\n callback: (session: { userId: string; roles: string[] } | null) => void,\n ): () => void {\n return onAuthStateChanged(auth, (user) => {\n if (hasCustomExtractor) {\n callback(toEnterstellarSessionSync(user, roleExtractor));\n } else {\n // No custom extractor — roles default to [] in sync context.\n // Full role resolution available via getSession()/hasRole().\n callback(user ? { userId: user.uid, roles: [] } : null);\n }\n });\n },\n });\n}\n","/**\n * @module @enterstellar-ai/adapter-firebase/create-firebase-data-adapter\n * @description Factory function for creating a Firestore-backed `DataAdapter`.\n *\n * This factory maps Firestore SDK calls to the Enterstellar `DataAdapter` interface:\n * - `query(resource, params?)` → `getDocs(collection(...))` + `where()` constraints\n * - `mutate(resource, action, data)` → `addDoc` / `updateDoc` / `deleteDoc`\n * - `subscribe(resource, cb)` → `onSnapshot(collection(...), cb)` → returns `unsubscribe`\n *\n * It builds a `DataAdapterConfig` and delegates to `createDataAdapter()` from\n * `@enterstellar-ai/adapters`, which handles all validation (ENS-7001) and AD5 error\n * wrapping (ENS-7003 / ENS-7004 / ENS-7002). This factory is purely an\n * SDK-to-Enterstellar translator.\n *\n * @see Bible §4.15\n * @see Design Choice AD3 — convention-based dot-notation for resource names\n * @see Design Choice AD5 — error wrapping delegated to createDataAdapter()\n */\n\nimport type { DataAdapter } from '@enterstellar-ai/types';\nimport {\n collection,\n getDocs,\n addDoc,\n updateDoc,\n deleteDoc,\n doc,\n onSnapshot,\n query as firestoreQuery,\n where,\n} from 'firebase/firestore';\n\nimport { createDataAdapter } from '@enterstellar-ai/adapters';\n\nimport type { FirebaseDataConfig } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Default adapter name when none is provided via config. */\nconst DEFAULT_NAME = 'firebase-data';\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a Firestore-backed `DataAdapter`.\n *\n * Maps Firestore SDK methods to the Enterstellar `DataAdapter` interface,\n * then delegates to `createDataAdapter()` from `@enterstellar-ai/adapters`\n * for config validation and AD5 error wrapping.\n *\n * @param config - Firestore data configuration with Firestore instance and optional overrides.\n * @returns A frozen, validated `DataAdapter` instance.\n * @throws `EnterstellarError` with code `ENS-7001` if config validation fails.\n *\n * @example\n * ```ts\n * import { initializeApp } from 'firebase/app';\n * import { getFirestore } from 'firebase/firestore';\n * import { createFirebaseDataAdapter } from '@enterstellar-ai/adapter-firebase';\n *\n * const app = initializeApp({ projectId: 'my-project', ... });\n * const firestore = getFirestore(app);\n * const data = createFirebaseDataAdapter({ firestore });\n *\n * // Query with filters\n * const patients = await data.query('patients', { status: 'active' });\n *\n * // Mutate — create a document\n * const newPatient = await data.mutate('patients', 'create', {\n * name: 'Jane Doe',\n * status: 'active',\n * });\n *\n * // Subscribe to realtime changes\n * const unsub = data.subscribe('patients', (records) => {\n * console.log('Patients updated:', records);\n * });\n * ```\n */\nexport function createFirebaseDataAdapter(config: FirebaseDataConfig): DataAdapter {\n const { firestore, name = DEFAULT_NAME } = config;\n\n // -----------------------------------------------------------------------\n // Build DataAdapterConfig and delegate to createDataAdapter()\n // -----------------------------------------------------------------------\n\n return createDataAdapter({\n name,\n\n /**\n * Maps to `getDocs(collection(firestore, resource))` with optional\n * `where()` equality constraints.\n *\n * Each key-value pair in `params` is applied as a `where(key, '==', value)`\n * constraint. If `params` is omitted, fetches all documents in the collection.\n *\n * Each document is returned as `{ id, ...data() }` — the Firestore document\n * ID is injected as the `id` field for consistency with Supabase and Enterstellar\n * conventions.\n *\n * @param resource - Collection name (AD3 dot-notation supported at v1 as literal).\n * @param params - Optional equality filters.\n */\n async query(\n resource: string,\n params?: Readonly<Record<string, unknown>>,\n ): Promise<readonly Record<string, unknown>[]> {\n const collectionRef = collection(firestore, resource);\n\n // Build query constraints from params\n const constraints = params\n ? Object.entries(params).map(([key, value]) => where(key, '==', value))\n : [];\n\n const queryRef = constraints.length > 0\n ? firestoreQuery(collectionRef, ...constraints)\n : collectionRef;\n\n const snapshot = await getDocs(queryRef);\n\n // Extract document data with injected `id` field\n return snapshot.docs.map((docSnap) => ({\n id: docSnap.id,\n ...docSnap.data(),\n }));\n },\n\n /**\n * Maps to `addDoc` / `updateDoc` / `deleteDoc` based on action.\n *\n * - `'create'` → `addDoc(collection(...), data)` → returns `{ id, ...data }`\n * - `'update'` → `updateDoc(doc(..., data.id), data)` → returns updated data with `id`\n * - `'delete'` → `deleteDoc(doc(..., data.id))` → returns `null`\n *\n * For `'update'` and `'delete'`, the `data` payload MUST include an `id`\n * field identifying the Firestore document.\n *\n * @param resource - Collection name.\n * @param action - Mutation type: `'create'`, `'update'`, or `'delete'`.\n * @param data - Mutation payload. For update/delete, must include an `id` field.\n */\n async mutate(\n resource: string,\n action: 'create' | 'update' | 'delete',\n data: Readonly<Record<string, unknown>>,\n ): Promise<Record<string, unknown> | null> {\n if (action === 'create') {\n // Strip `id` from the payload — Firestore auto-generates document IDs\n const { id: _id, ...payload } = data;\n const docRef = await addDoc(collection(firestore, resource), payload);\n return { id: docRef.id, ...payload };\n }\n\n if (action === 'update') {\n const documentId = data['id'] as string;\n const docRef = doc(firestore, resource, documentId);\n\n // Strip `id` from the update payload — don't write it as a field\n const { id: _id, ...payload } = data;\n await updateDoc(docRef, payload);\n\n return { id: documentId, ...payload };\n }\n\n // action === 'delete'\n const documentId = data['id'] as string;\n const docRef = doc(firestore, resource, documentId);\n await deleteDoc(docRef);\n return null;\n },\n\n /**\n * Maps to `onSnapshot(collection(firestore, resource), snapshot => ...)`.\n *\n * Subscribes to Firestore realtime updates for the specified collection.\n * When documents change, the callback receives all documents in the\n * collection with `{ id, ...data() }` shape.\n *\n * Returns the Firestore `Unsubscribe` function directly — a 1:1 mapping\n * to Enterstellar's `subscribe()` return type.\n *\n * @param resource - Collection name to subscribe to.\n * @param callback - Called with the full set of documents after each change.\n */\n subscribe(\n resource: string,\n callback: (data: readonly Record<string, unknown>[]) => void,\n ): () => void {\n const collectionRef = collection(firestore, resource);\n\n // onSnapshot returns an Unsubscribe function — direct mapping\n return onSnapshot(collectionRef, (snapshot) => {\n const records = snapshot.docs.map((docSnap) => ({\n id: docSnap.id,\n ...docSnap.data(),\n })) as readonly Record<string, unknown>[];\n\n callback(records);\n });\n },\n });\n}\n","/**\n * @module @enterstellar-ai/adapter-firebase/version\n * @description Package version constant for `@enterstellar-ai/adapter-firebase`.\n *\n * Used by DevTools for version display and runtime compatibility checks.\n * Must be kept in sync with the `version` field in `package.json`.\n *\n * @see Design Choice T14 — version exports\n */\n\n/**\n * Current version of the `@enterstellar-ai/adapter-firebase` package.\n *\n * @remarks\n * This value MUST match the `version` field in `package.json`.\n * Update this constant whenever a new version is released via Changesets.\n */\nexport const FIREBASE_ADAPTER_VERSION = '0.0.0' as const;\n"]}
|