@visiolab/effect-firebase 0.0.0-stage → 0.3.1

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.
Files changed (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -2
  3. package/dist/_virtual/_rolldown/runtime.mjs +12 -0
  4. package/dist/app/App.d.mts +27 -0
  5. package/dist/app/App.mjs +27 -0
  6. package/dist/app/index.d.mts +2 -0
  7. package/dist/app/index.mjs +1 -0
  8. package/dist/firestore/Collection.d.mts +423 -0
  9. package/dist/firestore/Collection.mjs +133 -0
  10. package/dist/firestore/CollectionGroup.d.mts +164 -0
  11. package/dist/firestore/CollectionGroup.mjs +84 -0
  12. package/dist/firestore/DocumentPath.d.mts +95 -0
  13. package/dist/firestore/DocumentPath.mjs +71 -0
  14. package/dist/firestore/DocumentSnapshot.d.mts +239 -0
  15. package/dist/firestore/DocumentSnapshot.mjs +134 -0
  16. package/dist/firestore/FieldValue.d.mts +332 -0
  17. package/dist/firestore/FieldValue.mjs +256 -0
  18. package/dist/firestore/Firestore.d.mts +1165 -0
  19. package/dist/firestore/Firestore.mjs +274 -0
  20. package/dist/firestore/FirestoreModel.d.mts +1232 -0
  21. package/dist/firestore/FirestoreModel.mjs +586 -0
  22. package/dist/firestore/FirestoreSchema.d.mts +567 -0
  23. package/dist/firestore/FirestoreSchema.mjs +419 -0
  24. package/dist/firestore/FirestoreValue.d.mts +145 -0
  25. package/dist/firestore/FirestoreValue.mjs +87 -0
  26. package/dist/firestore/GeoPoint.d.mts +90 -0
  27. package/dist/firestore/GeoPoint.mjs +75 -0
  28. package/dist/firestore/Patch.d.mts +73 -0
  29. package/dist/firestore/Patch.mjs +57 -0
  30. package/dist/firestore/QuerySnapshot.d.mts +107 -0
  31. package/dist/firestore/QuerySnapshot.mjs +43 -0
  32. package/dist/firestore/Timestamp.d.mts +273 -0
  33. package/dist/firestore/Timestamp.mjs +255 -0
  34. package/dist/firestore/index.d.mts +14 -0
  35. package/dist/firestore/index.mjs +14 -0
  36. package/dist/firestore/internal/collection.mjs +156 -0
  37. package/dist/firestore/internal/collectionGroup.mjs +64 -0
  38. package/dist/firestore/internal/documentPath.d.mts +4 -0
  39. package/dist/firestore/internal/documentPath.mjs +42 -0
  40. package/dist/firestore/internal/documentSnapshot.mjs +63 -0
  41. package/dist/firestore/internal/fieldValue.d.mts +11 -0
  42. package/dist/firestore/internal/fieldValue.mjs +88 -0
  43. package/dist/firestore/internal/firestore.mjs +145 -0
  44. package/dist/firestore/internal/firestoreError.d.mts +29 -0
  45. package/dist/firestore/internal/firestoreError.mjs +30 -0
  46. package/dist/firestore/internal/firestoreModel.mjs +719 -0
  47. package/dist/firestore/internal/firestoreSchema.mjs +123 -0
  48. package/dist/firestore/internal/firestoreValue.mjs +108 -0
  49. package/dist/firestore/internal/firestoreValueWalk.d.mts +58 -0
  50. package/dist/firestore/internal/firestoreValueWalk.mjs +223 -0
  51. package/dist/firestore/internal/geoPoint.d.mts +4 -0
  52. package/dist/firestore/internal/geoPoint.mjs +39 -0
  53. package/dist/firestore/internal/patch.mjs +19 -0
  54. package/dist/firestore/internal/querySnapshot.mjs +24 -0
  55. package/dist/firestore/internal/schemaAST.mjs +35 -0
  56. package/dist/firestore/internal/snapshotExtra.mjs +35 -0
  57. package/dist/firestore/internal/streamFromListener.mjs +12 -0
  58. package/dist/firestore/internal/timestamp.d.mts +4 -0
  59. package/dist/firestore/internal/timestamp.mjs +66 -0
  60. package/dist/firestore/internal/timestampSchema.mjs +31 -0
  61. package/dist/firestore/internal/transactionFromCallback.mjs +66 -0
  62. package/dist/functions/Callable.d.mts +654 -0
  63. package/dist/functions/Callable.mjs +450 -0
  64. package/dist/functions/CallableError.d.mts +297 -0
  65. package/dist/functions/CallableError.mjs +256 -0
  66. package/dist/functions/CallableMiddleware.d.mts +213 -0
  67. package/dist/functions/CallableMiddleware.mjs +52 -0
  68. package/dist/functions/CallableRequest.d.mts +166 -0
  69. package/dist/functions/CallableRequest.mjs +18 -0
  70. package/dist/functions/Caller.d.mts +103 -0
  71. package/dist/functions/Caller.mjs +115 -0
  72. package/dist/functions/DocumentTrigger.d.mts +795 -0
  73. package/dist/functions/DocumentTrigger.mjs +566 -0
  74. package/dist/functions/FirebaseFunction.d.mts +79 -0
  75. package/dist/functions/FirebaseFunction.mjs +4 -0
  76. package/dist/functions/FunctionGroup.d.mts +175 -0
  77. package/dist/functions/FunctionGroup.mjs +64 -0
  78. package/dist/functions/index.d.mts +9 -0
  79. package/dist/functions/index.mjs +9 -0
  80. package/dist/functions/internal/callable.mjs +167 -0
  81. package/dist/functions/internal/callableMiddleware.d.mts +19 -0
  82. package/dist/functions/internal/callableMiddleware.mjs +15 -0
  83. package/dist/functions/internal/callableRequest.mjs +6 -0
  84. package/dist/functions/internal/documentTrigger.d.mts +10 -0
  85. package/dist/functions/internal/documentTrigger.mjs +73 -0
  86. package/dist/functions/internal/firebaseFunction.d.mts +5 -0
  87. package/dist/functions/internal/firebaseFunction.mjs +24 -0
  88. package/dist/functions/internal/functionGroup.mjs +36 -0
  89. package/package.json +72 -3
  90. package/src/app/App.ts +24 -0
  91. package/src/app/index.ts +7 -0
  92. package/src/firestore/Collection.ts +520 -0
  93. package/src/firestore/CollectionGroup.ts +245 -0
  94. package/src/firestore/DocumentPath.ts +114 -0
  95. package/src/firestore/DocumentSnapshot.ts +308 -0
  96. package/src/firestore/FieldValue.ts +375 -0
  97. package/src/firestore/Firestore.ts +1432 -0
  98. package/src/firestore/FirestoreModel.ts +1884 -0
  99. package/src/firestore/FirestoreSchema.ts +643 -0
  100. package/src/firestore/FirestoreValue.ts +207 -0
  101. package/src/firestore/GeoPoint.ts +105 -0
  102. package/src/firestore/Patch.ts +117 -0
  103. package/src/firestore/QuerySnapshot.ts +150 -0
  104. package/src/firestore/Timestamp.ts +302 -0
  105. package/src/firestore/index.ts +67 -0
  106. package/src/firestore/internal/collection.ts +246 -0
  107. package/src/firestore/internal/collectionGroup.ts +81 -0
  108. package/src/firestore/internal/documentPath.ts +53 -0
  109. package/src/firestore/internal/documentSnapshot.ts +117 -0
  110. package/src/firestore/internal/fieldValue.ts +119 -0
  111. package/src/firestore/internal/firestore.ts +322 -0
  112. package/src/firestore/internal/firestoreError.ts +36 -0
  113. package/src/firestore/internal/firestoreModel.ts +1242 -0
  114. package/src/firestore/internal/firestoreSchema.ts +218 -0
  115. package/src/firestore/internal/firestoreValue.ts +202 -0
  116. package/src/firestore/internal/firestoreValueWalk.ts +349 -0
  117. package/src/firestore/internal/geoPoint.ts +59 -0
  118. package/src/firestore/internal/patch.ts +21 -0
  119. package/src/firestore/internal/querySnapshot.ts +39 -0
  120. package/src/firestore/internal/schemaAST.ts +83 -0
  121. package/src/firestore/internal/snapshotExtra.ts +47 -0
  122. package/src/firestore/internal/streamFromListener.ts +27 -0
  123. package/src/firestore/internal/timestamp.ts +119 -0
  124. package/src/firestore/internal/timestampSchema.ts +42 -0
  125. package/src/firestore/internal/transactionFromCallback.ts +106 -0
  126. package/src/functions/Callable.ts +855 -0
  127. package/src/functions/CallableError.ts +348 -0
  128. package/src/functions/CallableMiddleware.ts +279 -0
  129. package/src/functions/CallableRequest.ts +207 -0
  130. package/src/functions/Caller.ts +141 -0
  131. package/src/functions/DocumentTrigger.ts +927 -0
  132. package/src/functions/FirebaseFunction.ts +96 -0
  133. package/src/functions/FunctionGroup.ts +225 -0
  134. package/src/functions/index.ts +62 -0
  135. package/src/functions/internal/callable.ts +272 -0
  136. package/src/functions/internal/callableMiddleware.ts +54 -0
  137. package/src/functions/internal/callableRequest.ts +8 -0
  138. package/src/functions/internal/documentTrigger.ts +143 -0
  139. package/src/functions/internal/firebaseFunction.ts +58 -0
  140. package/src/functions/internal/functionGroup.ts +47 -0
@@ -0,0 +1,218 @@
1
+ import {
2
+ Array,
3
+ Effect,
4
+ Equal,
5
+ Schema,
6
+ SchemaAST,
7
+ SchemaGetter,
8
+ SchemaIssue,
9
+ SchemaParser,
10
+ SchemaTransformation,
11
+ } from "effect";
12
+ import type * as Collection from "../Collection.ts";
13
+ import type * as DocumentPath_ from "../DocumentPath.ts";
14
+ import type * as FieldValue from "../FieldValue.ts";
15
+ import type * as FirestoreSchema from "../FirestoreSchema.ts";
16
+ import type * as GeoPoint_ from "../GeoPoint.ts";
17
+ import * as collection_ from "./collection.ts";
18
+ import * as documentPath from "./documentPath.ts";
19
+ import * as fieldValue from "./fieldValue.ts";
20
+ import * as firestoreValue from "./firestoreValue.ts";
21
+ import * as geoPoint from "./geoPoint.ts";
22
+ import * as timestampSchema from "./timestampSchema.ts";
23
+
24
+ /** @internal */
25
+ export const Timestamp: FirestoreSchema.Timestamp = timestampSchema.Timestamp;
26
+
27
+ /** @internal */
28
+ export const GeoPoint: FirestoreSchema.GeoPoint = Schema.declare<GeoPoint_.GeoPoint>(
29
+ geoPoint.isGeoPoint,
30
+ {
31
+ representation: { id: "@visiolab/effect-firebase/firestore/GeoPoint", payload: null },
32
+ expected: "a Firestore GeoPoint",
33
+ toEquivalence: () => Equal.equals,
34
+ toCodecFirestore: () => undefined,
35
+ },
36
+ );
37
+
38
+ /** @internal */
39
+ export const DocumentPath: FirestoreSchema.DocumentPath =
40
+ Schema.declare<DocumentPath_.DocumentPath>(documentPath.isDocumentPath, {
41
+ representation: { id: "@visiolab/effect-firebase/firestore/DocumentPath", payload: null },
42
+ expected: "a Firestore document path",
43
+ toEquivalence: () => Equal.equals,
44
+ toCodecFirestore: () => undefined,
45
+ });
46
+
47
+ /** @internal */
48
+ export const DocumentReference = <C extends Collection.Any>(
49
+ collection: C,
50
+ ): FirestoreSchema.DocumentReference<C> => {
51
+ const pattern = collection_.pattern(collection);
52
+ return Schema.declare<Collection.DocumentReference<C>>(
53
+ (u): u is Collection.DocumentReference<C> =>
54
+ collection_.isDocumentReference(u) && u.collection === collection,
55
+ {
56
+ representation: {
57
+ id: "@visiolab/effect-firebase/firestore/DocumentReference",
58
+ payload: null,
59
+ },
60
+ expected: `a reference to a document in ${pattern}`,
61
+ toEquivalence: () => Equal.equals,
62
+ // Firestore stores the path alone, so reading one back checks it against
63
+ // `collection`'s segments and builds `collection`'s own reference.
64
+ toCodecFirestore: () =>
65
+ Schema.link<Collection.DocumentReference<C>>()(
66
+ DocumentPath.annotate({ expected: `a reference to a document in ${pattern}` }),
67
+ {
68
+ decode: SchemaGetter.transformEffect((value: DocumentPath_.DocumentPath, options) =>
69
+ Effect.fromOption(collection_.fromPath(collection, value.path)).pipe(
70
+ Effect.mapError(
71
+ () =>
72
+ new SchemaIssue.InvalidValue(
73
+ { message: `Expected a document in ${pattern}, got "${value.path}"` },
74
+ value,
75
+ options,
76
+ ),
77
+ ),
78
+ ),
79
+ ),
80
+ encode: SchemaGetter.transform((ref: Collection.DocumentReference<C>) =>
81
+ documentPath.makeUnchecked(ref.path),
82
+ ),
83
+ },
84
+ ),
85
+ },
86
+ );
87
+ };
88
+
89
+ /** @internal */
90
+ export const ServerTimestamp: FirestoreSchema.ServerTimestamp =
91
+ Schema.declare<FieldValue.ServerTimestamp>(fieldValue.isServerTimestamp, {
92
+ representation: {
93
+ id: "@visiolab/effect-firebase/firestore/FieldValue/ServerTimestamp",
94
+ payload: null,
95
+ },
96
+ expected: "a server timestamp",
97
+ toCodecFirestore: () => undefined,
98
+ }).pipe(Schema.withConstructorDefault(Effect.succeed(fieldValue.serverTimestamp)));
99
+
100
+ /** @internal */
101
+ export const ServerTimestampOr = <S extends FirestoreSchema.TimeSchema>(
102
+ schema: S,
103
+ ): FirestoreSchema.ServerTimestampOr<S> => Schema.Union([schema, ServerTimestamp]);
104
+
105
+ /** @internal */
106
+ export const Increment: FirestoreSchema.Increment = Schema.declare<FieldValue.Increment>(
107
+ fieldValue.isIncrement,
108
+ {
109
+ representation: {
110
+ id: "@visiolab/effect-firebase/firestore/FieldValue/Increment",
111
+ payload: null,
112
+ },
113
+ expected: "an increment",
114
+ toEquivalence: () => Equal.equals,
115
+ toCodecFirestore: () => undefined,
116
+ },
117
+ );
118
+
119
+ /** @internal */
120
+ export const IncrementOr = <S extends Schema.Codec<unknown, number, unknown, unknown>>(
121
+ schema: S,
122
+ ): FirestoreSchema.IncrementOr<S> => Schema.Union([schema, Increment]);
123
+
124
+ /** @internal */
125
+ export const DeleteField: FirestoreSchema.DeleteField = Schema.declare<FieldValue.DeleteField>(
126
+ fieldValue.isDeleteField,
127
+ {
128
+ representation: {
129
+ id: "@visiolab/effect-firebase/firestore/FieldValue/DeleteField",
130
+ payload: null,
131
+ },
132
+ expected: "a field deletion",
133
+ toCodecFirestore: () => undefined,
134
+ },
135
+ );
136
+
137
+ /** @internal */
138
+ export const DeleteFieldOr = <S extends Schema.Top>(schema: S): FirestoreSchema.DeleteFieldOr<S> =>
139
+ Schema.Union([DeleteField, schema]);
140
+
141
+ /**
142
+ * Declares a sentinel that carries elements, each of them run through `item` -
143
+ * decoded on the way in and encoded on the way out, as `Schema.Option` does
144
+ * with what it holds.
145
+ *
146
+ * Its Firestore form is the same sentinel, holding the elements in their own
147
+ * Firestore form: `toCodecFirestore` links it to a sentinel of `item`'s derived
148
+ * codec, so a `DateTimeUtc` among them is stored as a timestamp.
149
+ */
150
+ const elementsSentinel = <Sentinel extends { readonly elements: ReadonlyArray<unknown> }>(
151
+ name: string,
152
+ is: (u: unknown) => u is Sentinel,
153
+ make: (...elements: ReadonlyArray<unknown>) => Sentinel,
154
+ ) => {
155
+ const declareSentinel = <S extends Schema.Top>(
156
+ item: S,
157
+ toCodecFirestore: (typeParameters: readonly [Schema.Top]) => SchemaAST.Link | undefined,
158
+ ) =>
159
+ Schema.declareConstructor<Sentinel, Sentinel>()(
160
+ [item],
161
+ ([item]) => {
162
+ const elements = Schema.Array(item);
163
+ return (input, ast, options) =>
164
+ is(input)
165
+ ? SchemaParser.decodeEffect(elements)(input.elements, options).pipe(
166
+ Effect.mapBothEager({
167
+ onSuccess: (decoded) => make(...decoded),
168
+ onFailure: (issue) => new SchemaIssue.Pointer(["elements"], issue),
169
+ }),
170
+ )
171
+ : Effect.fail(new SchemaIssue.InvalidType(ast, input, options));
172
+ },
173
+ {
174
+ representation: {
175
+ id: `@visiolab/effect-firebase/firestore/FieldValue/${name}`,
176
+ payload: null,
177
+ },
178
+ expected: `an ${name}`,
179
+ toCodecFirestore,
180
+ toEquivalence: ([item]) => {
181
+ const elements = Array.makeEquivalence(item);
182
+ return (self, that) => elements(self.elements, that.elements);
183
+ },
184
+ },
185
+ );
186
+ return <S extends Schema.Top>(item: S) =>
187
+ declareSentinel(item, ([encoded]) =>
188
+ Schema.link<Sentinel>()(
189
+ // Already in Firestore form, elements and all, so derivation leaves it.
190
+ declareSentinel(firestoreValue.toCodecFirestore(encoded), () => undefined),
191
+ SchemaTransformation.passthrough(),
192
+ ),
193
+ );
194
+ };
195
+
196
+ const ArrayUnionSentinel = elementsSentinel(
197
+ "ArrayUnion",
198
+ fieldValue.isArrayUnion,
199
+ fieldValue.arrayUnion,
200
+ );
201
+
202
+ /** @internal */
203
+ export const ArrayUnion = <S extends Schema.Top>(item: S): FirestoreSchema.ArrayUnion<S> =>
204
+ ArrayUnionSentinel(item) as unknown as FirestoreSchema.ArrayUnion<S>;
205
+
206
+ const ArrayRemoveSentinel = elementsSentinel(
207
+ "ArrayRemove",
208
+ fieldValue.isArrayRemove,
209
+ fieldValue.arrayRemove,
210
+ );
211
+
212
+ /** @internal */
213
+ export const ArrayRemove = <S extends Schema.Top>(item: S): FirestoreSchema.ArrayRemove<S> =>
214
+ ArrayRemoveSentinel(item) as unknown as FirestoreSchema.ArrayRemove<S>;
215
+
216
+ /** @internal */
217
+ export const ArrayOr = <S extends Schema.Top>(item: S): FirestoreSchema.ArrayOr<S> =>
218
+ Schema.Union([Schema.Array(item), ArrayUnion(item), ArrayRemove(item)]);
@@ -0,0 +1,202 @@
1
+ import {
2
+ DateTime,
3
+ Effect,
4
+ Predicate,
5
+ Schema,
6
+ SchemaAST,
7
+ SchemaGetter,
8
+ SchemaIssue,
9
+ SchemaTransformation,
10
+ } from "effect";
11
+ import type * as FirestoreValue from "../FirestoreValue.ts";
12
+ import type * as Timestamp_ from "../Timestamp.ts";
13
+ import * as firestoreValueWalk from "./firestoreValueWalk.ts";
14
+ import * as schemaAST from "./schemaAST.ts";
15
+ import * as timestamp from "./timestamp.ts";
16
+ import * as timestampSchema from "./timestampSchema.ts";
17
+
18
+ /** @internal */
19
+ export const isFirestoreValue = firestoreValueWalk.isFirestoreValue;
20
+
21
+ /**
22
+ * Accepts only Firestore Values. A declaration with no link to any canonical
23
+ * format falls back to this, so a value Firestore cannot store fails at its own
24
+ * path instead of being stored as a map.
25
+ */
26
+ const FirestoreValueSchema = Schema.declare<FirestoreValue.FirestoreValue>(isFirestoreValue, {
27
+ expected: "a Firestore value",
28
+ toCodecFirestore: () => undefined,
29
+ });
30
+
31
+ /** @internal */
32
+ export const unknownToFirestore = new SchemaAST.Link(
33
+ FirestoreValueSchema.ast,
34
+ SchemaTransformation.passthrough(),
35
+ );
36
+
37
+ const timestampFromMillis = (millis: number, options: SchemaAST.ParseOptions) => {
38
+ const out = timestamp.fromMillis(millis);
39
+ return out._tag === "Some"
40
+ ? Effect.succeed(out.value)
41
+ : Effect.fail(
42
+ new SchemaIssue.InvalidValue(
43
+ { message: "Expected an instant Firestore can store, between the years 1 and 9999" },
44
+ millis,
45
+ options,
46
+ ),
47
+ );
48
+ };
49
+
50
+ const dateToTimestamp = Schema.link<globalThis.Date>()(timestampSchema.Timestamp, {
51
+ decode: SchemaGetter.transform((ts: Timestamp_.Timestamp) => new Date(timestamp.toMillis(ts))),
52
+ encode: SchemaGetter.transformEffect((date: globalThis.Date, options) =>
53
+ timestampFromMillis(date.getTime(), options),
54
+ ),
55
+ });
56
+
57
+ const dateTimeUtcToTimestamp = Schema.link<DateTime.Utc>()(timestampSchema.Timestamp, {
58
+ decode: SchemaGetter.transform(timestamp.toDateTime),
59
+ encode: SchemaGetter.transformEffect((dateTime: DateTime.Utc, options) =>
60
+ timestampFromMillis(DateTime.toEpochMillis(dateTime), options),
61
+ ),
62
+ });
63
+
64
+ const representationId = (ast: SchemaAST.Declaration): unknown => {
65
+ const representation: unknown = ast.annotations?.representation;
66
+ return Predicate.hasProperty(representation, "id") ? representation.id : undefined;
67
+ };
68
+
69
+ /**
70
+ * Firestore's own types come first, so these win over the declaration's
71
+ * `toCodecJson`, which would write a string.
72
+ */
73
+ const nativeLink = (ast: SchemaAST.Declaration): SchemaAST.Link | null | undefined => {
74
+ switch (representationId(ast)) {
75
+ case "effect/schema/Date":
76
+ return dateToTimestamp;
77
+ case "effect/schema/DateTimeUtc":
78
+ return dateTimeUtcToTimestamp;
79
+ case "effect/schema/Uint8Array":
80
+ return null;
81
+ }
82
+ return undefined;
83
+ };
84
+
85
+ type GetLink = (typeParameters: ReadonlyArray<Schema.Constraint>) => SchemaAST.Link | undefined;
86
+
87
+ const declarationStep = (
88
+ ast: SchemaAST.Declaration,
89
+ recur: (ast: SchemaAST.AST) => SchemaAST.AST,
90
+ ): SchemaAST.AST => {
91
+ const native = nativeLink(ast);
92
+ if (native === null) return ast;
93
+ if (native !== undefined) return schemaAST.replaceEncoding(ast, [native]);
94
+ const annotations = ast.annotations as
95
+ | {
96
+ readonly toCodecFirestore?: unknown;
97
+ readonly toCodecJson?: unknown;
98
+ readonly toCodec?: unknown;
99
+ }
100
+ | undefined;
101
+ const getLink = [
102
+ annotations?.toCodecFirestore,
103
+ annotations?.toCodecJson,
104
+ annotations?.toCodec,
105
+ ].find(Predicate.isFunction) as GetLink | undefined;
106
+ if (getLink === undefined) return schemaAST.replaceEncoding(ast, [unknownToFirestore]);
107
+ const typeParameters = ast.typeParameters.map((tp) => Schema.make(SchemaAST.toEncoded(tp)));
108
+ const link = getLink(typeParameters);
109
+ // `undefined` means the declaration is already canonical, and so is
110
+ // everything it holds: in Firestore form, or in JSON form, and every JSON
111
+ // value is a Firestore Value.
112
+ return link === undefined
113
+ ? ast
114
+ : schemaAST.replaceEncoding(ast, [schemaAST.mapLink(link, recur)]);
115
+ };
116
+
117
+ const reorderUnion = (types: ReadonlyArray<SchemaAST.AST>): ReadonlyArray<SchemaAST.AST> => {
118
+ // Members written as strings go first, so a string member doesn't swallow
119
+ // them when reading back, as `toCodecJson` does.
120
+ const priority = (ast: SchemaAST.AST): number => {
121
+ switch (SchemaAST.toEncoded(ast)._tag) {
122
+ case "BigInt":
123
+ case "Symbol":
124
+ case "UniqueSymbol":
125
+ return 0;
126
+ default:
127
+ return 1;
128
+ }
129
+ };
130
+ const sorted = [...types].sort((a, b) => priority(a) - priority(b));
131
+ return sorted.some((ast, i) => ast !== types[i]) ? sorted : types;
132
+ };
133
+
134
+ const step = (ast: SchemaAST.AST, recur: (ast: SchemaAST.AST) => SchemaAST.AST): SchemaAST.AST => {
135
+ switch (ast._tag) {
136
+ case "Declaration":
137
+ return declarationStep(ast, recur);
138
+ // Unlike `Schema.toCodecJson`, `Schema.Any` is no escape hatch: the SDK
139
+ // mappings take Firestore Values only, so nothing else may get past here.
140
+ case "Any":
141
+ case "Unknown":
142
+ case "ObjectKeyword":
143
+ return schemaAST.replaceEncoding(ast, [unknownToFirestore]);
144
+ case "Undefined":
145
+ case "Void":
146
+ case "Literal":
147
+ return schemaAST.keywordToCodecJson(ast);
148
+ case "UniqueSymbol":
149
+ case "Symbol":
150
+ case "BigInt":
151
+ return schemaAST.keywordToCodecStringTree(ast);
152
+ case "Objects":
153
+ // A map's keys are strings, so a struct keyed by a symbol has no
154
+ // Firestore form. Derivation never fails: the struct takes Firestore
155
+ // Values only, and encoding one fails with a schema issue at its path.
156
+ if (ast.propertySignatures.some((ps) => typeof ps.name !== "string")) {
157
+ return schemaAST.replaceEncoding(ast, [unknownToFirestore]);
158
+ }
159
+ return schemaAST.recur(ast, recur, schemaAST.parameterFromString);
160
+ case "Union": {
161
+ const types = reorderUnion(ast.types);
162
+ const union =
163
+ types === ast.types
164
+ ? ast
165
+ : new SchemaAST.Union(
166
+ types,
167
+ ast.options,
168
+ ast.annotations,
169
+ ast.checks,
170
+ ast.encoding,
171
+ ast.context,
172
+ ast.encodingChecks,
173
+ );
174
+ return schemaAST.recur(union, recur);
175
+ }
176
+ case "Arrays":
177
+ case "Suspend":
178
+ return schemaAST.recur(ast, recur);
179
+ }
180
+ // `null`, `boolean`, `number` and `string` are already Firestore Values.
181
+ return ast;
182
+ };
183
+
184
+ const withoutConstructorDefault = (context: SchemaAST.Context): SchemaAST.Context =>
185
+ context.constructorDefault === undefined
186
+ ? context
187
+ : new SchemaAST.Context(context.isOptional, context.isMutable, undefined, context.annotations);
188
+
189
+ /** @internal */
190
+ export const toCodecFirestoreAST: (ast: SchemaAST.AST) => SchemaAST.AST =
191
+ schemaAST.applyToSelfOrLastLinkEncodingIdempotent((ast) => {
192
+ const out = step(ast, toCodecFirestoreAST);
193
+ const context = ast.context;
194
+ if (out === ast || context === undefined) return out;
195
+ return schemaAST.replaceContextLastLink(out, withoutConstructorDefault(context));
196
+ });
197
+
198
+ /** @internal */
199
+ export const toCodecFirestore = <S extends Schema.Constraint>(
200
+ schema: S,
201
+ ): FirestoreValue.toCodecFirestore<S> =>
202
+ Schema.make<FirestoreValue.toCodecFirestore<S>>(toCodecFirestoreAST(schema.ast), { schema });