You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add Firestore `document-reference` schema support, including optional target-model narrowing in generated TypeScript and Zod types, schema validation for referenced models, and integration coverage across TypeScript, Zod, Python, and Swift.
Copy file name to clipboardExpand all lines: docs/schema/types.mdx
+108Lines changed: 108 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -296,6 +296,114 @@ function isValidExample(data) {
296
296
297
297
</CodeGroup>
298
298
299
+
## `document-reference`
300
+
301
+
Represents a Firestore [document reference](https://firebase.google.com/docs/reference/js/firestore_.documentreference) — a pointer to another Firestore document. Use this in place of storing a document ID as a `string` when you want the field to round-trip as a native reference under each Firestore SDK.
302
+
303
+
<Info>
304
+
Firestore only supports storing **document** references in document fields, not collection references — the stored value's path must end on a document id. The type name mirrors the SDK class `DocumentReference` to make this constraint obvious at the schema-definition layer.
305
+
</Info>
306
+
307
+
<Info>
308
+
Without an explicit `model`, the `document-reference` type intentionally does not encode the target document's shape. If you want the generated TypeScript and Zod types to be narrowed to a specific target model, use the parameterized form below.
309
+
</Info>
310
+
311
+
<CodeGroup>
312
+
313
+
```yaml definition.yml
314
+
Example:
315
+
model: alias
316
+
type: document-reference
317
+
```
318
+
319
+
```ts models.ts
320
+
export type Example = firestore.DocumentReference<firestore.DocumentData>;
321
+
```
322
+
323
+
```swift models.swift
324
+
typealiasExample= DocumentReference
325
+
```
326
+
327
+
```python models.py
328
+
Example = firestore.DocumentReference
329
+
```
330
+
331
+
```javascript firestore.rules
332
+
functionisValidExample(data) {
333
+
return (data is path);
334
+
}
335
+
```
336
+
337
+
</CodeGroup>
338
+
339
+
### Parameterized form
340
+
341
+
`document-reference` also accepts an object form with an optional `model` field that names the target document (or alias) model. Typesync validates that the referenced model exists in the same schema and threads the target through to the generated code.
342
+
343
+
The narrowing only takes effect on platforms whose Firestore SDK class is generic:
344
+
345
+
-**TypeScript** — `firestore.DocumentReference<TargetModel>` is emitted instead of `firestore.DocumentReference<firestore.DocumentData>`.
346
+
-**Zod** — the inferred type from `z.infer<typeof Schema>` carries the same narrowed shape. The runtime `instanceof` check is identical for both forms — narrowing is purely at the type level.
347
+
-**Python**, **Swift**, **Security Rules** — these SDK classes are not generic, so the emitted type is identical to the bare form. The schema-level validation that `model` resolves still runs.
348
+
349
+
<Info>
350
+
Zod self-references (e.g. `NoteLink.next: DocumentReference<NoteLink>`) are emitted **without** the generic in Zod to avoid `TS2456 Type alias circularly references itself` on the `z.infer`-derived type. Pure `generate-ts` output is unaffected and emits the narrowed self-reference. Cross-model references narrow on both targets.
351
+
</Info>
352
+
353
+
<CodeGroup>
354
+
355
+
```yaml definition.yml
356
+
Author:
357
+
model: document
358
+
path: authors/{authorId}
359
+
type:
360
+
type: object
361
+
fields:
362
+
name: { type: string }
363
+
364
+
Book:
365
+
model: document
366
+
path: books/{bookId}
367
+
type:
368
+
type: object
369
+
fields:
370
+
title: { type: string }
371
+
author:
372
+
type:
373
+
type: document-reference
374
+
model: Author
375
+
```
376
+
377
+
```ts models.ts
378
+
export interface Author {
379
+
name: string;
380
+
}
381
+
export interface Book {
382
+
title: string;
383
+
author: firestore.DocumentReference<Author>;
384
+
}
385
+
```
386
+
387
+
```python models.py
388
+
classAuthor(TypesyncModel):
389
+
name: str
390
+
classBook(TypesyncModel):
391
+
title: str
392
+
author: firestore.DocumentReference # not narrowed; Python SDK class is not generic
393
+
```
394
+
395
+
```swift models.swift
396
+
structAuthor: Codable {
397
+
var name: String
398
+
}
399
+
structBook: Codable {
400
+
var title: String
401
+
var author: DocumentReference // not narrowed; iOS SDK class is not generic
Copy file name to clipboardExpand all lines: schema.local.json
+69-43Lines changed: 69 additions & 43 deletions
Original file line number
Diff line number
Diff line change
@@ -34,52 +34,78 @@
34
34
{
35
35
"anyOf": [
36
36
{
37
-
"type": "string",
38
-
"const": "any",
39
-
"description": "Any type."
40
-
},
41
-
{
42
-
"type": "string",
43
-
"const": "unknown",
44
-
"description": "An unknown type."
45
-
},
46
-
{
47
-
"type": "string",
48
-
"const": "nil",
49
-
"description": "A nil type."
50
-
},
51
-
{
52
-
"type": "string",
53
-
"const": "string",
54
-
"description": "A string type."
55
-
},
56
-
{
57
-
"type": "string",
58
-
"const": "boolean",
59
-
"description": "A boolean type."
60
-
},
61
-
{
62
-
"type": "string",
63
-
"const": "int",
64
-
"description": "An integer type."
65
-
},
66
-
{
67
-
"type": "string",
68
-
"const": "double",
69
-
"description": "A double type."
70
-
},
71
-
{
72
-
"type": "string",
73
-
"const": "timestamp",
74
-
"description": "A timestamp type."
37
+
"anyOf": [
38
+
{
39
+
"type": "string",
40
+
"const": "any",
41
+
"description": "Any type."
42
+
},
43
+
{
44
+
"type": "string",
45
+
"const": "unknown",
46
+
"description": "An unknown type."
47
+
},
48
+
{
49
+
"type": "string",
50
+
"const": "nil",
51
+
"description": "A nil type."
52
+
},
53
+
{
54
+
"type": "string",
55
+
"const": "string",
56
+
"description": "A string type."
57
+
},
58
+
{
59
+
"type": "string",
60
+
"const": "boolean",
61
+
"description": "A boolean type."
62
+
},
63
+
{
64
+
"type": "string",
65
+
"const": "int",
66
+
"description": "An integer type."
67
+
},
68
+
{
69
+
"type": "string",
70
+
"const": "double",
71
+
"description": "A double type."
72
+
},
73
+
{
74
+
"type": "string",
75
+
"const": "timestamp",
76
+
"description": "A timestamp type."
77
+
},
78
+
{
79
+
"type": "string",
80
+
"const": "bytes",
81
+
"description": "A bytes type."
82
+
},
83
+
{
84
+
"type": "string",
85
+
"const": "document-reference",
86
+
"description": "A Firestore document reference type. Use this for fields that store a pointer to another Firestore document (e.g. a `DocumentReference` to `users/alice`) rather than the document id as a `string`. Firestore only allows storing document references in fields (not collection references), hence the explicit name."
87
+
}
88
+
],
89
+
"description": "A primitive type"
75
90
},
76
91
{
77
-
"type": "string",
78
-
"const": "bytes",
79
-
"description": "A bytes type."
92
+
"type": "object",
93
+
"properties": {
94
+
"type": {
95
+
"type": "string",
96
+
"const": "document-reference"
97
+
},
98
+
"model": {
99
+
"description": "Name of the target model (alias or document) that the referenced document belongs to. When specified, generators that support generics (TypeScript and Zod) narrow the emitted type to `DocumentReference<TargetModel>` instead of `DocumentReference<DocumentData>`. Generators without generic `DocumentReference` classes (Python, Swift) ignore this field at the type level but the schema validator still checks that the named model exists.",
100
+
"type": "string",
101
+
"minLength": 1
102
+
}
103
+
},
104
+
"required": ["type"],
105
+
"additionalProperties": false,
106
+
"description": "A Firestore document reference type, parameterized by the target model. Equivalent to the bare `document-reference` string form when `model` is omitted."
0 commit comments