firestore-schema-kit 1.0.0 → 2.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/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 +21 -5
- 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
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { addDoc, collection, deleteDoc as _deleteDoc, doc, getDoc as _getDoc, getDocs as _getDocs, onSnapshot as _onSnapshot, query, runTransaction as _runTransaction, setDoc as _setDoc, updateDoc as _updateDoc, writeBatch, } from 'firebase/firestore';
|
|
2
|
+
function assertSchema(schema, opName) {
|
|
3
|
+
if (!schema || typeof schema !== 'object' || schema._type !== 'CollectionSchema') {
|
|
4
|
+
throw new TypeError(`${opName}: first argument must be a CollectionSchema from defineCollection(). ` +
|
|
5
|
+
`Got: ${typeof schema}`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function resolveDocRef(db, schema, idOrRef) {
|
|
9
|
+
if (typeof idOrRef === 'string') {
|
|
10
|
+
return doc(db, schema._name, idOrRef);
|
|
11
|
+
}
|
|
12
|
+
if (idOrRef && typeof idOrRef.path === 'string') {
|
|
13
|
+
return idOrRef;
|
|
14
|
+
}
|
|
15
|
+
throw new TypeError(`Expected a document ID (string) or DocumentReference, got: ${typeof idOrRef}`);
|
|
16
|
+
}
|
|
17
|
+
function collectionRef(db, schema) {
|
|
18
|
+
return collection(db, schema._name);
|
|
19
|
+
}
|
|
20
|
+
function docToData(snapshot) {
|
|
21
|
+
if (!snapshot.exists())
|
|
22
|
+
return null;
|
|
23
|
+
return { id: snapshot.id, ...snapshot.data() };
|
|
24
|
+
}
|
|
25
|
+
function snapshotToArray(querySnapshot) {
|
|
26
|
+
return querySnapshot.docs.map((docSnapshot) => ({
|
|
27
|
+
id: docSnapshot.id,
|
|
28
|
+
...docSnapshot.data(),
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
function shouldValidatePartial(options) {
|
|
32
|
+
const hasMergeFlag = 'merge' in options && Boolean(options.merge);
|
|
33
|
+
const hasMergeFields = 'mergeFields' in options && Array.isArray(options.mergeFields);
|
|
34
|
+
return hasMergeFlag || hasMergeFields;
|
|
35
|
+
}
|
|
36
|
+
export function initFirestoreSchema(db) {
|
|
37
|
+
if (!db)
|
|
38
|
+
throw new Error('initFirestoreSchema: db (Firestore instance) is required');
|
|
39
|
+
async function createDoc(schema, data) {
|
|
40
|
+
assertSchema(schema, 'createDoc');
|
|
41
|
+
const validated = schema.validate(data);
|
|
42
|
+
const ref = await addDoc(collectionRef(db, schema), validated);
|
|
43
|
+
return ref;
|
|
44
|
+
}
|
|
45
|
+
async function setDoc(schema, idOrRef, data, options = {}) {
|
|
46
|
+
assertSchema(schema, 'setDoc');
|
|
47
|
+
const ref = resolveDocRef(db, schema, idOrRef);
|
|
48
|
+
const validated = shouldValidatePartial(options)
|
|
49
|
+
? schema.validatePartial(data)
|
|
50
|
+
: schema.validate(data);
|
|
51
|
+
await _setDoc(ref, validated, options);
|
|
52
|
+
}
|
|
53
|
+
async function updateDoc(schema, idOrRef, data) {
|
|
54
|
+
assertSchema(schema, 'updateDoc');
|
|
55
|
+
const ref = resolveDocRef(db, schema, idOrRef);
|
|
56
|
+
const validated = schema.validatePartial(data);
|
|
57
|
+
await _updateDoc(ref, validated);
|
|
58
|
+
}
|
|
59
|
+
async function deleteDoc(schema, idOrRef) {
|
|
60
|
+
assertSchema(schema, 'deleteDoc');
|
|
61
|
+
const ref = resolveDocRef(db, schema, idOrRef);
|
|
62
|
+
await _deleteDoc(ref);
|
|
63
|
+
}
|
|
64
|
+
async function getDoc(schema, idOrRef) {
|
|
65
|
+
assertSchema(schema, 'getDoc');
|
|
66
|
+
const ref = resolveDocRef(db, schema, idOrRef);
|
|
67
|
+
const snapshot = await _getDoc(ref);
|
|
68
|
+
return docToData(snapshot);
|
|
69
|
+
}
|
|
70
|
+
async function getDocs(schema, ...constraints) {
|
|
71
|
+
assertSchema(schema, 'getDocs');
|
|
72
|
+
const ref = collectionRef(db, schema);
|
|
73
|
+
const q = constraints.length ? query(ref, ...constraints) : ref;
|
|
74
|
+
const snapshot = await _getDocs(q);
|
|
75
|
+
return snapshotToArray(snapshot);
|
|
76
|
+
}
|
|
77
|
+
function getDocRef(schema, id) {
|
|
78
|
+
assertSchema(schema, 'getDocRef');
|
|
79
|
+
return doc(db, schema._name, id);
|
|
80
|
+
}
|
|
81
|
+
function getCollectionRef(schema) {
|
|
82
|
+
assertSchema(schema, 'getCollectionRef');
|
|
83
|
+
return collectionRef(db, schema);
|
|
84
|
+
}
|
|
85
|
+
function onDocSnapshot(schema, idOrRef, callback, onError) {
|
|
86
|
+
assertSchema(schema, 'onDocSnapshot');
|
|
87
|
+
const ref = resolveDocRef(db, schema, idOrRef);
|
|
88
|
+
return _onSnapshot(ref, (snapshot) => callback(docToData(snapshot)), onError);
|
|
89
|
+
}
|
|
90
|
+
function onCollectionSnapshot(schema, callback, ...constraints) {
|
|
91
|
+
assertSchema(schema, 'onCollectionSnapshot');
|
|
92
|
+
const ref = collectionRef(db, schema);
|
|
93
|
+
const q = constraints.length ? query(ref, ...constraints) : ref;
|
|
94
|
+
return _onSnapshot(q, (snapshot) => callback(snapshotToArray(snapshot)));
|
|
95
|
+
}
|
|
96
|
+
function createBatch() {
|
|
97
|
+
const batch = writeBatch(db);
|
|
98
|
+
const schemaBatch = {
|
|
99
|
+
create(schema, data) {
|
|
100
|
+
assertSchema(schema, 'batch.create');
|
|
101
|
+
const validated = schema.validate(data);
|
|
102
|
+
const ref = doc(collectionRef(db, schema));
|
|
103
|
+
batch.set(ref, validated);
|
|
104
|
+
return ref;
|
|
105
|
+
},
|
|
106
|
+
set(schema, idOrRef, data, options = {}) {
|
|
107
|
+
assertSchema(schema, 'batch.set');
|
|
108
|
+
const ref = resolveDocRef(db, schema, idOrRef);
|
|
109
|
+
const validated = shouldValidatePartial(options)
|
|
110
|
+
? schema.validatePartial(data)
|
|
111
|
+
: schema.validate(data);
|
|
112
|
+
batch.set(ref, validated, options);
|
|
113
|
+
return schemaBatch;
|
|
114
|
+
},
|
|
115
|
+
update(schema, idOrRef, data) {
|
|
116
|
+
assertSchema(schema, 'batch.update');
|
|
117
|
+
const ref = resolveDocRef(db, schema, idOrRef);
|
|
118
|
+
const validated = schema.validatePartial(data);
|
|
119
|
+
batch.update(ref, validated);
|
|
120
|
+
return schemaBatch;
|
|
121
|
+
},
|
|
122
|
+
delete(schema, idOrRef) {
|
|
123
|
+
assertSchema(schema, 'batch.delete');
|
|
124
|
+
const ref = resolveDocRef(db, schema, idOrRef);
|
|
125
|
+
batch.delete(ref);
|
|
126
|
+
return schemaBatch;
|
|
127
|
+
},
|
|
128
|
+
commit() {
|
|
129
|
+
return batch.commit();
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
return schemaBatch;
|
|
133
|
+
}
|
|
134
|
+
async function runTransaction(updateFn) {
|
|
135
|
+
return _runTransaction(db, async (firestoreTx) => {
|
|
136
|
+
const transaction = {
|
|
137
|
+
async get(schema, idOrRef) {
|
|
138
|
+
assertSchema(schema, 'transaction.get');
|
|
139
|
+
const ref = resolveDocRef(db, schema, idOrRef);
|
|
140
|
+
const snapshot = await firestoreTx.get(ref);
|
|
141
|
+
return docToData(snapshot);
|
|
142
|
+
},
|
|
143
|
+
set(schema, idOrRef, data, options = {}) {
|
|
144
|
+
assertSchema(schema, 'transaction.set');
|
|
145
|
+
const ref = resolveDocRef(db, schema, idOrRef);
|
|
146
|
+
const validated = shouldValidatePartial(options)
|
|
147
|
+
? schema.validatePartial(data)
|
|
148
|
+
: schema.validate(data);
|
|
149
|
+
firestoreTx.set(ref, validated, options);
|
|
150
|
+
return transaction;
|
|
151
|
+
},
|
|
152
|
+
update(schema, idOrRef, data) {
|
|
153
|
+
assertSchema(schema, 'transaction.update');
|
|
154
|
+
const ref = resolveDocRef(db, schema, idOrRef);
|
|
155
|
+
const validated = schema.validatePartial(data);
|
|
156
|
+
firestoreTx.update(ref, validated);
|
|
157
|
+
return transaction;
|
|
158
|
+
},
|
|
159
|
+
delete(schema, idOrRef) {
|
|
160
|
+
assertSchema(schema, 'transaction.delete');
|
|
161
|
+
const ref = resolveDocRef(db, schema, idOrRef);
|
|
162
|
+
firestoreTx.delete(ref);
|
|
163
|
+
return transaction;
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
return updateFn(transaction);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
createDoc,
|
|
171
|
+
setDoc,
|
|
172
|
+
updateDoc,
|
|
173
|
+
deleteDoc,
|
|
174
|
+
getDoc,
|
|
175
|
+
getDocs,
|
|
176
|
+
getDocRef,
|
|
177
|
+
getCollectionRef,
|
|
178
|
+
onDocSnapshot,
|
|
179
|
+
onCollectionSnapshot,
|
|
180
|
+
createBatch,
|
|
181
|
+
runTransaction,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../operations.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firestore-schema-kit",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"main": "index.js",
|
|
5
|
-
"
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
6
12
|
"type": "module",
|
|
7
13
|
"scripts": {
|
|
14
|
+
"build": "tsc -p tsconfig.json",
|
|
15
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
8
16
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
17
|
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"readme.md"
|
|
21
|
+
],
|
|
10
22
|
"keywords": [],
|
|
11
23
|
"author": "Bonanza Narayan",
|
|
12
24
|
"license": "ISC",
|
|
@@ -20,8 +32,12 @@
|
|
|
20
32
|
},
|
|
21
33
|
"homepage": "https://github.com/BonanzaNarayan/firebase-schema#readme",
|
|
22
34
|
"dependencies": {
|
|
23
|
-
"firebase": "^12.11.0",
|
|
24
35
|
"zod": "^4.3.6"
|
|
25
36
|
},
|
|
26
|
-
"
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"firebase": "^12.11.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "^6.0.2"
|
|
42
|
+
}
|
|
27
43
|
}
|
package/readme.md
CHANGED
|
@@ -9,7 +9,9 @@ Zod handles validation internally. You never touch Zod directly.
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npm install
|
|
12
|
+
npm install firestore-schema-kit
|
|
13
|
+
pnpm add firestore-schema-kit
|
|
14
|
+
bun add firestore-schema-kit
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
Copy `src/` into your project (e.g. `lib/firebase-schema/`).
|
package/collection.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
import { s } from './fields.js'
|
|
3
|
-
|
|
4
|
-
// ─── Schema Validation Error ───────────────────────────────────────────────────
|
|
5
|
-
|
|
6
|
-
export class SchemaValidationError extends Error {
|
|
7
|
-
constructor(collectionName, zodError) {
|
|
8
|
-
const issues = zodError.issues
|
|
9
|
-
.map((i) => ` • ${i.path.join('.')}: ${i.message}`)
|
|
10
|
-
.join('\n')
|
|
11
|
-
|
|
12
|
-
super(`[${collectionName}] Schema validation failed:\n${issues}`)
|
|
13
|
-
this.name = 'SchemaValidationError'
|
|
14
|
-
this.collection = collectionName
|
|
15
|
-
this.issues = zodError.issues
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// ─── defineCollection ─────────────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Define a typed Firestore collection with schema validation.
|
|
23
|
-
*
|
|
24
|
-
* @param {string} collectionName - Firestore collection path (e.g. 'users', 'posts/comments')
|
|
25
|
-
* @param {(s: SchemaBuilder) => Record<string, BaseField>} builder - Field definition callback
|
|
26
|
-
* @returns {CollectionSchema}
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* export const usersSchema = defineCollection('users', (s) => ({
|
|
30
|
-
* name: s.string().min(1),
|
|
31
|
-
* email: s.string().email(),
|
|
32
|
-
* age: s.number().optional(),
|
|
33
|
-
* role: s.enum(['admin', 'user']).default('user'),
|
|
34
|
-
* createdAt: s.timestamp(),
|
|
35
|
-
* }))
|
|
36
|
-
*/
|
|
37
|
-
export function defineCollection(collectionName, builder) {
|
|
38
|
-
if (typeof collectionName !== 'string' || !collectionName.trim()) {
|
|
39
|
-
throw new Error('defineCollection: collectionName must be a non-empty string')
|
|
40
|
-
}
|
|
41
|
-
if (typeof builder !== 'function') {
|
|
42
|
-
throw new Error('defineCollection: builder must be a function')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Call builder with the schema builder (s)
|
|
46
|
-
const fieldDefs = builder(s)
|
|
47
|
-
|
|
48
|
-
if (!fieldDefs || typeof fieldDefs !== 'object') {
|
|
49
|
-
throw new Error('defineCollection: builder must return an object of field definitions')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Convert field definitions → Zod schema shape
|
|
53
|
-
const zodShape = {}
|
|
54
|
-
for (const [key, field] of Object.entries(fieldDefs)) {
|
|
55
|
-
if (typeof field?.toZod !== 'function') {
|
|
56
|
-
throw new Error(
|
|
57
|
-
`defineCollection [${collectionName}]: field "${key}" is not a valid schema field. ` +
|
|
58
|
-
`Use s.string(), s.number(), etc.`
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
zodShape[key] = field.toZod()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const zodSchema = z.object(zodShape)
|
|
65
|
-
const zodPartialSchema = zodSchema.partial()
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
// ── Metadata ────────────────────────────────────────────────────────────
|
|
69
|
-
_name: collectionName,
|
|
70
|
-
_fieldDefs: fieldDefs,
|
|
71
|
-
_type: 'CollectionSchema',
|
|
72
|
-
|
|
73
|
-
// ── Validation ──────────────────────────────────────────────────────────
|
|
74
|
-
|
|
75
|
-
/** Full validation — use for createDoc / setDoc */
|
|
76
|
-
validate(data) {
|
|
77
|
-
const result = zodSchema.safeParse(data)
|
|
78
|
-
if (!result.success) throw new SchemaValidationError(collectionName, result.error)
|
|
79
|
-
return result.data
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
/** Partial validation — use for updateDoc (only validates fields present in data) */
|
|
83
|
-
validatePartial(data) {
|
|
84
|
-
const result = zodPartialSchema.safeParse(data)
|
|
85
|
-
if (!result.success) throw new SchemaValidationError(collectionName, result.error)
|
|
86
|
-
return result.data
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
/** Safe (non-throwing) full validation */
|
|
90
|
-
safeParse(data) {
|
|
91
|
-
const result = zodSchema.safeParse(data)
|
|
92
|
-
if (!result.success) {
|
|
93
|
-
return { success: false, error: new SchemaValidationError(collectionName, result.error) }
|
|
94
|
-
}
|
|
95
|
-
return { success: true, data: result.data }
|
|
96
|
-
},
|
|
97
|
-
}
|
|
98
|
-
}
|
package/fields.js
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
|
|
3
|
-
// ─── Base Field ───────────────────────────────────────────────────────────────
|
|
4
|
-
|
|
5
|
-
class BaseField {
|
|
6
|
-
constructor() {
|
|
7
|
-
this._optional = false
|
|
8
|
-
this._nullable = false
|
|
9
|
-
this._hasDefault = false
|
|
10
|
-
this._defaultVal = undefined
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
optional() {
|
|
14
|
-
this._optional = true
|
|
15
|
-
return this
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
nullable() {
|
|
19
|
-
this._nullable = true
|
|
20
|
-
return this
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
default(val) {
|
|
24
|
-
this._hasDefault = true
|
|
25
|
-
this._defaultVal = val
|
|
26
|
-
return this
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Subclasses implement this — returns a raw Zod schema with no optional/default wrapping
|
|
30
|
-
_baseZod() {
|
|
31
|
-
throw new Error(`_baseZod() not implemented on ${this.constructor.name}`)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Builds the final Zod schema, applying nullable → default → optional in the correct order
|
|
35
|
-
toZod() {
|
|
36
|
-
let schema = this._baseZod()
|
|
37
|
-
if (this._nullable) schema = schema.nullable()
|
|
38
|
-
if (this._hasDefault) schema = schema.default(this._defaultVal)
|
|
39
|
-
if (this._optional) schema = schema.optional()
|
|
40
|
-
return schema
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ─── String ───────────────────────────────────────────────────────────────────
|
|
45
|
-
|
|
46
|
-
export class StringField extends BaseField {
|
|
47
|
-
constructor() {
|
|
48
|
-
super()
|
|
49
|
-
this._min = null
|
|
50
|
-
this._max = null
|
|
51
|
-
this._email = false
|
|
52
|
-
this._url = false
|
|
53
|
-
this._uuid = false
|
|
54
|
-
this._regex = null
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
min(n) { this._min = n; return this }
|
|
58
|
-
max(n) { this._max = n; return this }
|
|
59
|
-
email() { this._email = true; return this }
|
|
60
|
-
url() { this._url = true; return this }
|
|
61
|
-
uuid() { this._uuid = true; return this }
|
|
62
|
-
regex(r) { this._regex = r; return this }
|
|
63
|
-
|
|
64
|
-
_baseZod() {
|
|
65
|
-
let schema = z.string()
|
|
66
|
-
if (this._min !== null) schema = schema.min(this._min)
|
|
67
|
-
if (this._max !== null) schema = schema.max(this._max)
|
|
68
|
-
if (this._email) schema = schema.email()
|
|
69
|
-
if (this._url) schema = schema.url()
|
|
70
|
-
if (this._uuid) schema = schema.uuid()
|
|
71
|
-
if (this._regex !== null) schema = schema.regex(this._regex)
|
|
72
|
-
return schema
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// ─── Number ───────────────────────────────────────────────────────────────────
|
|
77
|
-
|
|
78
|
-
export class NumberField extends BaseField {
|
|
79
|
-
constructor() {
|
|
80
|
-
super()
|
|
81
|
-
this._min = null
|
|
82
|
-
this._max = null
|
|
83
|
-
this._int = false
|
|
84
|
-
this._positive = false
|
|
85
|
-
this._negative = false
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
min(n) { this._min = n; return this }
|
|
89
|
-
max(n) { this._max = n; return this }
|
|
90
|
-
int() { this._int = true; return this }
|
|
91
|
-
positive() { this._positive = true; return this }
|
|
92
|
-
negative() { this._negative = true; return this }
|
|
93
|
-
|
|
94
|
-
_baseZod() {
|
|
95
|
-
let schema = this._int ? z.number().int() : z.number()
|
|
96
|
-
if (this._min !== null) schema = schema.min(this._min)
|
|
97
|
-
if (this._max !== null) schema = schema.max(this._max)
|
|
98
|
-
if (this._positive) schema = schema.positive()
|
|
99
|
-
if (this._negative) schema = schema.negative()
|
|
100
|
-
return schema
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ─── Boolean ──────────────────────────────────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
export class BooleanField extends BaseField {
|
|
107
|
-
_baseZod() {
|
|
108
|
-
return z.boolean()
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// ─── Timestamp ────────────────────────────────────────────────────────────────
|
|
113
|
-
// Accepts: JS Date, Firestore Timestamp, serverTimestamp() sentinel
|
|
114
|
-
|
|
115
|
-
export class TimestampField extends BaseField {
|
|
116
|
-
_baseZod() {
|
|
117
|
-
return z.custom(
|
|
118
|
-
(val) => {
|
|
119
|
-
if (!val) return false
|
|
120
|
-
// JS Date
|
|
121
|
-
if (val instanceof Date) return true
|
|
122
|
-
// Firestore Timestamp (has toDate method)
|
|
123
|
-
if (typeof val.toDate === 'function') return true
|
|
124
|
-
// serverTimestamp() sentinel — has _methodName internally
|
|
125
|
-
if (typeof val === 'object' && '_methodName' in val) return true
|
|
126
|
-
return false
|
|
127
|
-
},
|
|
128
|
-
{ message: 'Expected a Date, Firestore Timestamp, or serverTimestamp()' }
|
|
129
|
-
)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ─── Array ────────────────────────────────────────────────────────────────────
|
|
134
|
-
|
|
135
|
-
export class ArrayField extends BaseField {
|
|
136
|
-
constructor(itemField) {
|
|
137
|
-
super()
|
|
138
|
-
this._itemField = itemField
|
|
139
|
-
this._min = null
|
|
140
|
-
this._max = null
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
min(n) { this._min = n; return this }
|
|
144
|
-
max(n) { this._max = n; return this }
|
|
145
|
-
|
|
146
|
-
_baseZod() {
|
|
147
|
-
let schema = z.array(this._itemField.toZod())
|
|
148
|
-
if (this._min !== null) schema = schema.min(this._min)
|
|
149
|
-
if (this._max !== null) schema = schema.max(this._max)
|
|
150
|
-
return schema
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ─── Map (nested object) ──────────────────────────────────────────────────────
|
|
155
|
-
|
|
156
|
-
export class MapField extends BaseField {
|
|
157
|
-
constructor(shape) {
|
|
158
|
-
super()
|
|
159
|
-
this._shape = shape
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
_baseZod() {
|
|
163
|
-
const zodShape = {}
|
|
164
|
-
for (const [key, field] of Object.entries(this._shape)) {
|
|
165
|
-
zodShape[key] = field.toZod()
|
|
166
|
-
}
|
|
167
|
-
return z.object(zodShape)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ─── Enum ─────────────────────────────────────────────────────────────────────
|
|
172
|
-
|
|
173
|
-
export class EnumField extends BaseField {
|
|
174
|
-
constructor(values) {
|
|
175
|
-
super()
|
|
176
|
-
this._values = values
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
_baseZod() {
|
|
180
|
-
return z.enum(this._values)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ─── Literal ──────────────────────────────────────────────────────────────────
|
|
185
|
-
|
|
186
|
-
export class LiteralField extends BaseField {
|
|
187
|
-
constructor(value) {
|
|
188
|
-
super()
|
|
189
|
-
this._value = value
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
_baseZod() {
|
|
193
|
-
return z.literal(this._value)
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ─── Reference (Firestore DocumentReference) ──────────────────────────────────
|
|
198
|
-
|
|
199
|
-
export class ReferenceField extends BaseField {
|
|
200
|
-
_baseZod() {
|
|
201
|
-
return z.custom(
|
|
202
|
-
(val) => val && typeof val.path === 'string' && typeof val.id === 'string',
|
|
203
|
-
{ message: 'Expected a Firestore DocumentReference' }
|
|
204
|
-
)
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// ─── Union ────────────────────────────────────────────────────────────────────
|
|
209
|
-
|
|
210
|
-
export class UnionField extends BaseField {
|
|
211
|
-
constructor(fields) {
|
|
212
|
-
super()
|
|
213
|
-
this._fields = fields
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
_baseZod() {
|
|
217
|
-
const schemas = this._fields.map((f) => f.toZod())
|
|
218
|
-
return z.union(schemas)
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// ─── Any (escape hatch) ───────────────────────────────────────────────────────
|
|
223
|
-
|
|
224
|
-
export class AnyField extends BaseField {
|
|
225
|
-
_baseZod() {
|
|
226
|
-
return z.any()
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// ─── Schema Builder (s) ───────────────────────────────────────────────────────
|
|
231
|
-
// This is what gets passed into the defineCollection callback
|
|
232
|
-
|
|
233
|
-
export const s = {
|
|
234
|
-
string: () => new StringField(),
|
|
235
|
-
number: () => new NumberField(),
|
|
236
|
-
boolean: () => new BooleanField(),
|
|
237
|
-
timestamp: () => new TimestampField(),
|
|
238
|
-
array: (itemField) => new ArrayField(itemField),
|
|
239
|
-
map: (shape) => new MapField(shape),
|
|
240
|
-
enum: (values) => new EnumField(values),
|
|
241
|
-
literal: (value) => new LiteralField(value),
|
|
242
|
-
reference: () => new ReferenceField(),
|
|
243
|
-
union: (fields) => new UnionField(fields),
|
|
244
|
-
any: () => new AnyField(),
|
|
245
|
-
}
|
package/index.js
DELETED