firestore-schema-kit 1.0.0 → 2.0.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/dist/collection.d.ts +24 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +62 -0
- package/dist/collection.js.map +1 -0
- package/dist/fields.d.ts +107 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +242 -0
- package/dist/fields.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/operations.d.ts +34 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +184 -0
- package/dist/operations.js.map +1 -0
- package/package.json +18 -4
- package/readme.md +3 -1
- package/collection.js +0 -98
- package/fields.js +0 -245
- package/index.js +0 -3
- package/operations.js +0 -419
package/operations.js
DELETED
|
@@ -1,419 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
collection,
|
|
3
|
-
doc,
|
|
4
|
-
addDoc,
|
|
5
|
-
setDoc as _setDoc,
|
|
6
|
-
updateDoc as _updateDoc,
|
|
7
|
-
deleteDoc as _deleteDoc,
|
|
8
|
-
getDoc as _getDoc,
|
|
9
|
-
getDocs as _getDocs,
|
|
10
|
-
onSnapshot as _onSnapshot,
|
|
11
|
-
query,
|
|
12
|
-
writeBatch,
|
|
13
|
-
runTransaction as _runTransaction,
|
|
14
|
-
serverTimestamp,
|
|
15
|
-
Timestamp,
|
|
16
|
-
} from 'firebase/firestore'
|
|
17
|
-
|
|
18
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Validate that `schema` is a CollectionSchema produced by defineCollection().
|
|
22
|
-
*/
|
|
23
|
-
function assertSchema(schema, opName) {
|
|
24
|
-
if (!schema || schema._type !== 'CollectionSchema') {
|
|
25
|
-
throw new TypeError(
|
|
26
|
-
`${opName}: first argument must be a CollectionSchema from defineCollection(). Got: ${typeof schema}`
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Resolve a string ID or an existing DocumentReference into a DocumentReference.
|
|
33
|
-
*/
|
|
34
|
-
function resolveDocRef(db, schema, idOrRef) {
|
|
35
|
-
if (typeof idOrRef === 'string') {
|
|
36
|
-
return doc(db, schema._name, idOrRef)
|
|
37
|
-
}
|
|
38
|
-
// Already a DocumentReference
|
|
39
|
-
if (idOrRef && typeof idOrRef.path === 'string') {
|
|
40
|
-
return idOrRef
|
|
41
|
-
}
|
|
42
|
-
throw new TypeError(
|
|
43
|
-
`Expected a document ID (string) or DocumentReference, got: ${typeof idOrRef}`
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Get a CollectionReference for the schema.
|
|
49
|
-
*/
|
|
50
|
-
function collectionRef(db, schema) {
|
|
51
|
-
return collection(db, schema._name)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Map a Firestore DocumentSnapshot → plain data object with `id` field attached.
|
|
56
|
-
* Returns null if the document doesn't exist.
|
|
57
|
-
*/
|
|
58
|
-
function docToData(snapshot) {
|
|
59
|
-
if (!snapshot.exists()) return null
|
|
60
|
-
return { id: snapshot.id, ...snapshot.data() }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Map a QuerySnapshot → array of plain data objects with `id` field attached.
|
|
65
|
-
*/
|
|
66
|
-
function snapshotToArray(querySnapshot) {
|
|
67
|
-
return querySnapshot.docs.map((d) => ({ id: d.id, ...d.data() }))
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ─── Factory ─────────────────────────────────────────────────────────────────
|
|
71
|
-
// All operations are created via initFirestoreSchema(db) so db doesn't need
|
|
72
|
-
// to be passed on every call.
|
|
73
|
-
|
|
74
|
-
export function initFirestoreSchema(db) {
|
|
75
|
-
if (!db) throw new Error('initFirestoreSchema: db (Firestore instance) is required')
|
|
76
|
-
|
|
77
|
-
// ── createDoc ─────────────────────────────────────────────────────────────
|
|
78
|
-
/**
|
|
79
|
-
* Add a new document with an auto-generated ID.
|
|
80
|
-
* Equivalent to Firestore's addDoc().
|
|
81
|
-
*
|
|
82
|
-
* @returns {Promise<DocumentReference>}
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* const ref = await createDoc(usersSchema, {
|
|
86
|
-
* name: 'Alice',
|
|
87
|
-
* email: 'alice@example.com',
|
|
88
|
-
* createdAt: serverTimestamp(),
|
|
89
|
-
* })
|
|
90
|
-
* console.log(ref.id) // auto-generated ID
|
|
91
|
-
*/
|
|
92
|
-
async function createDoc(schema, data) {
|
|
93
|
-
assertSchema(schema, 'createDoc')
|
|
94
|
-
const validated = schema.validate(data)
|
|
95
|
-
return addDoc(collectionRef(db, schema), validated)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// ── setDoc ────────────────────────────────────────────────────────────────
|
|
99
|
-
/**
|
|
100
|
-
* Write a document with a specific ID (overwrites by default).
|
|
101
|
-
* Pass { merge: true } to merge instead of overwrite.
|
|
102
|
-
* Equivalent to Firestore's setDoc().
|
|
103
|
-
*
|
|
104
|
-
* @param {CollectionSchema} schema
|
|
105
|
-
* @param {string | DocumentReference} idOrRef
|
|
106
|
-
* @param {object} data
|
|
107
|
-
* @param {{ merge?: boolean, mergeFields?: string[] }} [options]
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* await setDoc(usersSchema, 'user-123', { name: 'Alice', email: 'alice@example.com', createdAt: serverTimestamp() })
|
|
111
|
-
* await setDoc(usersSchema, 'user-123', { name: 'Alice Updated' }, { merge: true })
|
|
112
|
-
*/
|
|
113
|
-
async function setDoc(schema, idOrRef, data, options = {}) {
|
|
114
|
-
assertSchema(schema, 'setDoc')
|
|
115
|
-
const ref = resolveDocRef(db, schema, idOrRef)
|
|
116
|
-
// merge/mergeFields: only validate fields present in data (partial)
|
|
117
|
-
const validated = (options.merge || options.mergeFields)
|
|
118
|
-
? schema.validatePartial(data)
|
|
119
|
-
: schema.validate(data)
|
|
120
|
-
return _setDoc(ref, validated, options)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ── updateDoc ─────────────────────────────────────────────────────────────
|
|
124
|
-
/**
|
|
125
|
-
* Partially update a document — only fields provided will be written.
|
|
126
|
-
* The document must already exist.
|
|
127
|
-
* Equivalent to Firestore's updateDoc().
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* await updateDoc(usersSchema, 'user-123', { name: 'Alice Updated' })
|
|
131
|
-
*/
|
|
132
|
-
async function updateDoc(schema, idOrRef, data) {
|
|
133
|
-
assertSchema(schema, 'updateDoc')
|
|
134
|
-
const ref = resolveDocRef(db, schema, idOrRef)
|
|
135
|
-
const validated = schema.validatePartial(data)
|
|
136
|
-
return _updateDoc(ref, validated)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ── deleteDoc ─────────────────────────────────────────────────────────────
|
|
140
|
-
/**
|
|
141
|
-
* Delete a document by ID or ref.
|
|
142
|
-
* No schema validation needed — it's just a deletion.
|
|
143
|
-
*
|
|
144
|
-
* @example
|
|
145
|
-
* await deleteDoc(usersSchema, 'user-123')
|
|
146
|
-
*/
|
|
147
|
-
async function deleteDoc(schema, idOrRef) {
|
|
148
|
-
assertSchema(schema, 'deleteDoc')
|
|
149
|
-
const ref = resolveDocRef(db, schema, idOrRef)
|
|
150
|
-
return _deleteDoc(ref)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ── getDoc ────────────────────────────────────────────────────────────────
|
|
154
|
-
/**
|
|
155
|
-
* Fetch a single document by ID or ref.
|
|
156
|
-
* Returns { id, ...data } or null if not found.
|
|
157
|
-
*
|
|
158
|
-
* @returns {Promise<{ id: string } & T | null>}
|
|
159
|
-
*
|
|
160
|
-
* @example
|
|
161
|
-
* const user = await getDoc(usersSchema, 'user-123')
|
|
162
|
-
* if (!user) console.log('Not found')
|
|
163
|
-
*/
|
|
164
|
-
async function getDoc(schema, idOrRef) {
|
|
165
|
-
assertSchema(schema, 'getDoc')
|
|
166
|
-
const ref = resolveDocRef(db, schema, idOrRef)
|
|
167
|
-
const snapshot = await _getDoc(ref)
|
|
168
|
-
return docToData(snapshot)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ── getDocs ───────────────────────────────────────────────────────────────
|
|
172
|
-
/**
|
|
173
|
-
* Fetch multiple documents from a collection, with optional query constraints.
|
|
174
|
-
* Returns an array of { id, ...data } objects.
|
|
175
|
-
*
|
|
176
|
-
* @param {CollectionSchema} schema
|
|
177
|
-
* @param {...QueryConstraint} constraints - where(), orderBy(), limit(), startAfter(), etc.
|
|
178
|
-
* @returns {Promise<Array<{ id: string } & T>>}
|
|
179
|
-
*
|
|
180
|
-
* @example
|
|
181
|
-
* import { where, orderBy, limit } from 'firebase/firestore'
|
|
182
|
-
*
|
|
183
|
-
* const users = await getDocs(usersSchema)
|
|
184
|
-
* const admins = await getDocs(usersSchema, where('role', '==', 'admin'))
|
|
185
|
-
* const recent = await getDocs(usersSchema, orderBy('createdAt', 'desc'), limit(10))
|
|
186
|
-
*/
|
|
187
|
-
async function getDocs(schema, ...constraints) {
|
|
188
|
-
assertSchema(schema, 'getDocs')
|
|
189
|
-
const ref = collectionRef(db, schema)
|
|
190
|
-
const q = constraints.length ? query(ref, ...constraints) : ref
|
|
191
|
-
const snapshot = await _getDocs(q)
|
|
192
|
-
return snapshotToArray(snapshot)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// ── getDocRef ─────────────────────────────────────────────────────────────
|
|
196
|
-
/**
|
|
197
|
-
* Get a Firestore DocumentReference for a given schema and ID.
|
|
198
|
-
* Useful when you need the ref itself (e.g. for storing references).
|
|
199
|
-
*
|
|
200
|
-
* @example
|
|
201
|
-
* const ref = getDocRef(usersSchema, 'user-123')
|
|
202
|
-
* await setDoc(postsSchema, 'post-1', { author: ref, ... })
|
|
203
|
-
*/
|
|
204
|
-
function getDocRef(schema, id) {
|
|
205
|
-
assertSchema(schema, 'getDocRef')
|
|
206
|
-
return doc(db, schema._name, id)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Get a Firestore CollectionReference for a schema.
|
|
211
|
-
*
|
|
212
|
-
* @example
|
|
213
|
-
* const ref = getCollectionRef(usersSchema)
|
|
214
|
-
*/
|
|
215
|
-
function getCollectionRef(schema) {
|
|
216
|
-
assertSchema(schema, 'getCollectionRef')
|
|
217
|
-
return collectionRef(db, schema)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// ── onDocSnapshot ─────────────────────────────────────────────────────────
|
|
221
|
-
/**
|
|
222
|
-
* Listen to real-time updates on a single document.
|
|
223
|
-
* Returns an unsubscribe function.
|
|
224
|
-
*
|
|
225
|
-
* @param {CollectionSchema} schema
|
|
226
|
-
* @param {string | DocumentReference} idOrRef
|
|
227
|
-
* @param {(data: T | null) => void} callback - called with { id, ...data } or null
|
|
228
|
-
* @param {(error: Error) => void} [onError]
|
|
229
|
-
* @returns {Unsubscribe}
|
|
230
|
-
*
|
|
231
|
-
* @example
|
|
232
|
-
* const unsub = onDocSnapshot(usersSchema, 'user-123', (user) => {
|
|
233
|
-
* if (user) console.log(user.name)
|
|
234
|
-
* })
|
|
235
|
-
* // Later: unsub()
|
|
236
|
-
*/
|
|
237
|
-
function onDocSnapshot(schema, idOrRef, callback, onError) {
|
|
238
|
-
assertSchema(schema, 'onDocSnapshot')
|
|
239
|
-
const ref = resolveDocRef(db, schema, idOrRef)
|
|
240
|
-
return _onSnapshot(
|
|
241
|
-
ref,
|
|
242
|
-
(snapshot) => callback(docToData(snapshot)),
|
|
243
|
-
onError
|
|
244
|
-
)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// ── onCollectionSnapshot ──────────────────────────────────────────────────
|
|
248
|
-
/**
|
|
249
|
-
* Listen to real-time updates on a collection or query.
|
|
250
|
-
* Returns an unsubscribe function.
|
|
251
|
-
*
|
|
252
|
-
* @param {CollectionSchema} schema
|
|
253
|
-
* @param {(docs: Array<T>) => void} callback - called with array of { id, ...data }
|
|
254
|
-
* @param {...QueryConstraint} constraints - optional where(), orderBy(), limit(), etc.
|
|
255
|
-
* @returns {Unsubscribe}
|
|
256
|
-
*
|
|
257
|
-
* @example
|
|
258
|
-
* const unsub = onCollectionSnapshot(
|
|
259
|
-
* usersSchema,
|
|
260
|
-
* (users) => console.log(users),
|
|
261
|
-
* where('role', '==', 'admin'),
|
|
262
|
-
* orderBy('name')
|
|
263
|
-
* )
|
|
264
|
-
* // Later: unsub()
|
|
265
|
-
*/
|
|
266
|
-
function onCollectionSnapshot(schema, callback, ...constraints) {
|
|
267
|
-
assertSchema(schema, 'onCollectionSnapshot')
|
|
268
|
-
const ref = collectionRef(db, schema)
|
|
269
|
-
const q = constraints.length ? query(ref, ...constraints) : ref
|
|
270
|
-
return _onSnapshot(
|
|
271
|
-
q,
|
|
272
|
-
(snapshot) => callback(snapshotToArray(snapshot)),
|
|
273
|
-
)
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// ── Batch Writes ──────────────────────────────────────────────────────────
|
|
277
|
-
/**
|
|
278
|
-
* Create a schema-aware write batch.
|
|
279
|
-
* All operations are validated before being added to the batch.
|
|
280
|
-
*
|
|
281
|
-
* @example
|
|
282
|
-
* const batch = createBatch()
|
|
283
|
-
*
|
|
284
|
-
* batch.create(usersSchema, { name: 'Alice', email: 'alice@example.com', createdAt: serverTimestamp() })
|
|
285
|
-
* batch.set(usersSchema, 'user-456', { name: 'Bob', email: 'bob@example.com', createdAt: serverTimestamp() })
|
|
286
|
-
* batch.update(usersSchema, 'user-123', { name: 'Alice Updated' })
|
|
287
|
-
* batch.delete(usersSchema, 'user-789')
|
|
288
|
-
*
|
|
289
|
-
* await batch.commit()
|
|
290
|
-
*/
|
|
291
|
-
function createBatch() {
|
|
292
|
-
const batch = writeBatch(db)
|
|
293
|
-
|
|
294
|
-
return {
|
|
295
|
-
/**
|
|
296
|
-
* Add a new document with auto-generated ID to the batch.
|
|
297
|
-
* Note: Firestore's batch doesn't support addDoc — this uses doc() with a generated ref.
|
|
298
|
-
*/
|
|
299
|
-
create(schema, data) {
|
|
300
|
-
assertSchema(schema, 'batch.create')
|
|
301
|
-
const validated = schema.validate(data)
|
|
302
|
-
const ref = doc(collectionRef(db, schema))
|
|
303
|
-
batch.set(ref, validated)
|
|
304
|
-
return ref // return ref so caller can use the ID
|
|
305
|
-
},
|
|
306
|
-
|
|
307
|
-
/** Set (overwrite) a document in the batch */
|
|
308
|
-
set(schema, idOrRef, data, options = {}) {
|
|
309
|
-
assertSchema(schema, 'batch.set')
|
|
310
|
-
const ref = resolveDocRef(db, schema, idOrRef)
|
|
311
|
-
const validated = (options.merge || options.mergeFields)
|
|
312
|
-
? schema.validatePartial(data)
|
|
313
|
-
: schema.validate(data)
|
|
314
|
-
batch.set(ref, validated, options)
|
|
315
|
-
return this
|
|
316
|
-
},
|
|
317
|
-
|
|
318
|
-
/** Partially update a document in the batch */
|
|
319
|
-
update(schema, idOrRef, data) {
|
|
320
|
-
assertSchema(schema, 'batch.update')
|
|
321
|
-
const ref = resolveDocRef(db, schema, idOrRef)
|
|
322
|
-
const validated = schema.validatePartial(data)
|
|
323
|
-
batch.update(ref, validated)
|
|
324
|
-
return this
|
|
325
|
-
},
|
|
326
|
-
|
|
327
|
-
/** Delete a document in the batch */
|
|
328
|
-
delete(schema, idOrRef) {
|
|
329
|
-
assertSchema(schema, 'batch.delete')
|
|
330
|
-
const ref = resolveDocRef(db, schema, idOrRef)
|
|
331
|
-
batch.delete(ref)
|
|
332
|
-
return this
|
|
333
|
-
},
|
|
334
|
-
|
|
335
|
-
/** Commit all batched operations */
|
|
336
|
-
commit() {
|
|
337
|
-
return batch.commit()
|
|
338
|
-
},
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// ── Transactions ──────────────────────────────────────────────────────────
|
|
343
|
-
/**
|
|
344
|
-
* Run a schema-aware transaction.
|
|
345
|
-
* The transaction callback receives a schema-aware transaction object.
|
|
346
|
-
*
|
|
347
|
-
* @param {(t: SchemaTransaction) => Promise<T>} updateFn
|
|
348
|
-
* @returns {Promise<T>}
|
|
349
|
-
*
|
|
350
|
-
* @example
|
|
351
|
-
* await runTransaction(async (t) => {
|
|
352
|
-
* const user = await t.get(usersSchema, 'user-123')
|
|
353
|
-
* if (!user) throw new Error('User not found')
|
|
354
|
-
*
|
|
355
|
-
* await t.update(usersSchema, 'user-123', {
|
|
356
|
-
* points: user.points + 10
|
|
357
|
-
* })
|
|
358
|
-
* })
|
|
359
|
-
*/
|
|
360
|
-
async function runTransaction(updateFn) {
|
|
361
|
-
return _runTransaction(db, (firestoreTx) => {
|
|
362
|
-
const t = {
|
|
363
|
-
/** Get a document within the transaction. Returns { id, ...data } or null. */
|
|
364
|
-
async get(schema, idOrRef) {
|
|
365
|
-
assertSchema(schema, 'transaction.get')
|
|
366
|
-
const ref = resolveDocRef(db, schema, idOrRef)
|
|
367
|
-
const snapshot = await firestoreTx.get(ref)
|
|
368
|
-
return docToData(snapshot)
|
|
369
|
-
},
|
|
370
|
-
|
|
371
|
-
/** Set (overwrite) a document within the transaction */
|
|
372
|
-
set(schema, idOrRef, data, options = {}) {
|
|
373
|
-
assertSchema(schema, 'transaction.set')
|
|
374
|
-
const ref = resolveDocRef(db, schema, idOrRef)
|
|
375
|
-
const validated = (options.merge || options.mergeFields)
|
|
376
|
-
? schema.validatePartial(data)
|
|
377
|
-
: schema.validate(data)
|
|
378
|
-
firestoreTx.set(ref, validated, options)
|
|
379
|
-
return this
|
|
380
|
-
},
|
|
381
|
-
|
|
382
|
-
/** Partially update a document within the transaction */
|
|
383
|
-
update(schema, idOrRef, data) {
|
|
384
|
-
assertSchema(schema, 'transaction.update')
|
|
385
|
-
const ref = resolveDocRef(db, schema, idOrRef)
|
|
386
|
-
const validated = schema.validatePartial(data)
|
|
387
|
-
firestoreTx.update(ref, validated)
|
|
388
|
-
return this
|
|
389
|
-
},
|
|
390
|
-
|
|
391
|
-
/** Delete a document within the transaction */
|
|
392
|
-
delete(schema, idOrRef) {
|
|
393
|
-
assertSchema(schema, 'transaction.delete')
|
|
394
|
-
const ref = resolveDocRef(db, schema, idOrRef)
|
|
395
|
-
firestoreTx.delete(ref)
|
|
396
|
-
return this
|
|
397
|
-
},
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return updateFn(t)
|
|
401
|
-
})
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// ── Return all operations ─────────────────────────────────────────────────
|
|
405
|
-
return {
|
|
406
|
-
createDoc,
|
|
407
|
-
setDoc,
|
|
408
|
-
updateDoc,
|
|
409
|
-
deleteDoc,
|
|
410
|
-
getDoc,
|
|
411
|
-
getDocs,
|
|
412
|
-
getDocRef,
|
|
413
|
-
getCollectionRef,
|
|
414
|
-
onDocSnapshot,
|
|
415
|
-
onCollectionSnapshot,
|
|
416
|
-
createBatch,
|
|
417
|
-
runTransaction,
|
|
418
|
-
}
|
|
419
|
-
}
|