@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,119 @@
1
+ import {
2
+ DateTime,
3
+ Effect,
4
+ Equal,
5
+ Hash,
6
+ Inspectable,
7
+ Option,
8
+ Order as order,
9
+ Predicate,
10
+ } from "effect";
11
+ import type * as Timestamp from "../Timestamp.ts";
12
+
13
+ /** @internal */
14
+ export const TypeId = "~@visiolab/effect-firebase/firestore/Timestamp";
15
+
16
+ /** `0001-01-01T00:00:00Z`, the earliest instant Firestore addresses. */
17
+ const minSeconds = -62_135_596_800;
18
+ /** `9999-12-31T23:59:59Z`, the latest. */
19
+ const maxSeconds = 253_402_300_799;
20
+ const maxNanoseconds = 999_999_999;
21
+ const nanosecondsPerMillisecond = 1_000_000;
22
+
23
+ /** Kept apart from the guards below, which narrow a `number` to `never`. */
24
+ const inSecondsRange = (n: number): boolean =>
25
+ Number.isInteger(n) && n >= minSeconds && n <= maxSeconds;
26
+
27
+ const inNanosecondsRange = (n: number): boolean =>
28
+ Number.isInteger(n) && n >= 0 && n <= maxNanoseconds;
29
+
30
+ /** @internal */
31
+ export const isTimestamp = (u: unknown): u is Timestamp.Timestamp =>
32
+ Predicate.hasProperty(u, TypeId) &&
33
+ u[TypeId] === TypeId &&
34
+ Predicate.hasProperty(u, "seconds") &&
35
+ Predicate.hasProperty(u, "nanoseconds") &&
36
+ Predicate.isNumber(u.seconds) &&
37
+ Predicate.isNumber(u.nanoseconds) &&
38
+ inSecondsRange(u.seconds) &&
39
+ inNanosecondsRange(u.nanoseconds);
40
+
41
+ const Proto: Omit<Timestamp.Timestamp, "seconds" | "nanoseconds"> = {
42
+ ...Inspectable.BaseProto,
43
+ [TypeId]: TypeId,
44
+ toJSON(this: Timestamp.Timestamp) {
45
+ return { _id: "Timestamp", seconds: this.seconds, nanoseconds: this.nanoseconds };
46
+ },
47
+ [Equal.symbol](this: Timestamp.Timestamp, that: unknown): boolean {
48
+ return (
49
+ isTimestamp(that) && this.seconds === that.seconds && this.nanoseconds === that.nanoseconds
50
+ );
51
+ },
52
+ [Hash.symbol](this: Timestamp.Timestamp): number {
53
+ return Hash.combine(Hash.number(this.seconds), Hash.number(this.nanoseconds));
54
+ },
55
+ };
56
+
57
+ const makeUnchecked = (seconds: number, nanoseconds: number): Timestamp.Timestamp =>
58
+ Object.assign(Object.create(Proto) as typeof Proto, { seconds, nanoseconds });
59
+
60
+ /** @internal */
61
+ export const makeUnsafe = (seconds: number, nanoseconds: number): Timestamp.Timestamp => {
62
+ if (!inSecondsRange(seconds)) {
63
+ throw new Error(
64
+ `Invalid Firestore timestamp: seconds must be an integer between ${minSeconds} and ${maxSeconds}, got ${seconds}`,
65
+ );
66
+ }
67
+ if (!inNanosecondsRange(nanoseconds)) {
68
+ throw new Error(
69
+ `Invalid Firestore timestamp: nanoseconds must be an integer between 0 and ${maxNanoseconds}, got ${nanoseconds}`,
70
+ );
71
+ }
72
+ return makeUnchecked(seconds, nanoseconds);
73
+ };
74
+
75
+ /** @internal */
76
+ export const make = (seconds: number, nanoseconds: number): Option.Option<Timestamp.Timestamp> =>
77
+ inSecondsRange(seconds) && inNanosecondsRange(nanoseconds)
78
+ ? Option.some(makeUnchecked(seconds, nanoseconds))
79
+ : Option.none();
80
+
81
+ /** Splits milliseconds into the second and the fraction of it Firestore stores. */
82
+ const partsOfMillis = (millis: number): readonly [seconds: number, nanoseconds: number] => {
83
+ const seconds = Math.floor(millis / 1000);
84
+ return [seconds, Math.floor((millis - seconds * 1000) * nanosecondsPerMillisecond)];
85
+ };
86
+
87
+ /** @internal */
88
+ export const fromMillisUnsafe = (millis: number): Timestamp.Timestamp =>
89
+ makeUnsafe(...partsOfMillis(millis));
90
+
91
+ /** @internal */
92
+ export const fromMillis = (millis: number): Option.Option<Timestamp.Timestamp> =>
93
+ make(...partsOfMillis(millis));
94
+
95
+ /** @internal */
96
+ export const fromDateTimeUnsafe = (dateTime: DateTime.Utc): Timestamp.Timestamp =>
97
+ fromMillisUnsafe(DateTime.toEpochMillis(dateTime));
98
+
99
+ /** @internal */
100
+ export const fromDateTime = (dateTime: DateTime.Utc): Option.Option<Timestamp.Timestamp> =>
101
+ fromMillis(DateTime.toEpochMillis(dateTime));
102
+
103
+ /** @internal */
104
+ export const now: Effect.Effect<Timestamp.Timestamp> = Effect.map(DateTime.now, fromDateTimeUnsafe);
105
+
106
+ /** @internal */
107
+ export const toMillis = (self: Timestamp.Timestamp): number =>
108
+ self.seconds * 1000 + Math.floor(self.nanoseconds / nanosecondsPerMillisecond);
109
+
110
+ /** @internal */
111
+ export const toDateTime = (self: Timestamp.Timestamp): DateTime.Utc =>
112
+ DateTime.makeUnsafe(toMillis(self));
113
+
114
+ /** @internal */
115
+ export const Order: order.Order<Timestamp.Timestamp> = order.make((self, that) =>
116
+ self.seconds === that.seconds
117
+ ? order.Number(self.nanoseconds, that.nanoseconds)
118
+ : order.Number(self.seconds, that.seconds),
119
+ );
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Apart from the other Firestore schemas, so that deriving `toCodecFirestore`,
3
+ * which writes instants as timestamps, need not import the schemas that
4
+ * derive it in turn.
5
+ */
6
+ import { Effect, Equal, Schema, SchemaGetter, SchemaIssue } from "effect";
7
+ import type * as FirestoreSchema from "../FirestoreSchema.ts";
8
+ import type * as Timestamp_ from "../Timestamp.ts";
9
+ import * as timestamp from "./timestamp.ts";
10
+
11
+ /** Writes a timestamp as JSON without losing the digits finer than a millisecond. */
12
+ const TimestampJson = Schema.Struct({ seconds: Schema.Int, nanoseconds: Schema.Int });
13
+
14
+ /** @internal */
15
+ export const Timestamp: FirestoreSchema.Timestamp = Schema.declare<Timestamp_.Timestamp>(
16
+ timestamp.isTimestamp,
17
+ {
18
+ representation: { id: "@visiolab/effect-firebase/firestore/Timestamp", payload: null },
19
+ expected: "a Firestore Timestamp",
20
+ toEquivalence: () => Equal.equals,
21
+ toCodecFirestore: () => undefined,
22
+ toCodecJson: () =>
23
+ Schema.link<Timestamp_.Timestamp>()(TimestampJson, {
24
+ decode: SchemaGetter.transformEffect((json: typeof TimestampJson.Type, options) =>
25
+ Effect.fromOption(timestamp.make(json.seconds, json.nanoseconds)).pipe(
26
+ Effect.mapError(
27
+ () =>
28
+ new SchemaIssue.InvalidValue(
29
+ { message: "Expected an instant Firestore can store" },
30
+ json,
31
+ options,
32
+ ),
33
+ ),
34
+ ),
35
+ ),
36
+ encode: SchemaGetter.transform((value: Timestamp_.Timestamp) => ({
37
+ seconds: value.seconds,
38
+ nanoseconds: value.nanoseconds,
39
+ })),
40
+ }),
41
+ },
42
+ );
@@ -0,0 +1,106 @@
1
+ import { Cause, Effect, Exit, Option } from "effect";
2
+ import { constVoid } from "effect/Function";
3
+ import type * as Firestore from "../Firestore.ts";
4
+ import { FirestoreError } from "./firestoreError.ts";
5
+
6
+ /**
7
+ * What the callback throws when the body fails with anything but a
8
+ * `FirestoreError` that carries its backend's cause.
9
+ *
10
+ * It carries no `code` on purpose: an SDK decides whether to retry by the code
11
+ * of what it caught, and a body's own error that happens to carry one would
12
+ * otherwise be retried as if Firestore had raised it.
13
+ */
14
+ class BodyFailed {
15
+ readonly cause: Cause.Cause<unknown>;
16
+ constructor(cause: Cause.Cause<unknown>) {
17
+ this.cause = cause;
18
+ }
19
+ }
20
+
21
+ /**
22
+ * What the callback throws for a body that failed with `cause`.
23
+ *
24
+ * A `FirestoreError` goes back as the SDK's own error behind it, so the SDK
25
+ * decides by its own rules whether to retry it - a read answered with ABORTED
26
+ * under contention, or an outage that passes - rather than by a copy of them
27
+ * kept here. Anything else goes back as {@link BodyFailed}, which it never
28
+ * retries.
29
+ */
30
+ const thrownFor = (cause: Cause.Cause<unknown>): unknown => {
31
+ const error = Option.getOrUndefined(Cause.findErrorOption(cause));
32
+ return error instanceof FirestoreError && error.cause !== undefined
33
+ ? error.cause
34
+ : new BodyFailed(cause);
35
+ };
36
+
37
+ /** @internal */
38
+ export const transactionFromCallback = <
39
+ T,
40
+ Operations extends Firestore.TransactionOperations,
41
+ TransactionServices = never,
42
+ >(
43
+ options: Firestore.TransactionFromCallbackOptions<T, Operations, TransactionServices>,
44
+ ): Firestore.RunTransaction<Operations, TransactionServices> => ({
45
+ run: (body, hookOptions) => runOver(options, body, hookOptions),
46
+ context: options.context,
47
+ });
48
+
49
+ /** Runs `body` in a transaction through `options.run`, once per attempt. */
50
+ const runOver = <T, Operations extends Firestore.TransactionOperations, A, E>(
51
+ options: Pick<
52
+ Firestore.TransactionFromCallbackOptions<T, Operations>,
53
+ "run" | "operations" | "onError"
54
+ >,
55
+ body: (operations: Operations) => Effect.Effect<A, E>,
56
+ hookOptions: Firestore.TransactionHookOptions,
57
+ ): Effect.Effect<A, E | FirestoreError> =>
58
+ Effect.uninterruptibleMask((restore) => {
59
+ const controller = new AbortController();
60
+ // The last thing the body failed with, and what was thrown for it, so
61
+ // that a failure the SDK hands back without retrying it comes back as
62
+ // the body's own rather than wrapped.
63
+ let failure: { readonly thrown: unknown; readonly cause: Cause.Cause<unknown> } | undefined;
64
+ const callback = async (transaction: unknown): Promise<A> => {
65
+ // The body has everything it needs provided, so it runs on a runtime
66
+ // of its own here, inside the SDK's callback. `run` hands this the
67
+ // SDK's transaction, which `operations` is typed by: `run`'s callback
68
+ // is not, so that `T` is inferred from `operations` alone.
69
+ const operations = options.operations(transaction as T);
70
+ const exit = await Effect.runPromiseExit(body(operations), {
71
+ signal: controller.signal,
72
+ });
73
+ if (Exit.isSuccess(exit)) return exit.value;
74
+ const thrown = thrownFor(exit.cause);
75
+ failure = { thrown, cause: exit.cause };
76
+ throw thrown;
77
+ };
78
+ let running: Promise<A>;
79
+ try {
80
+ running = options.run(callback, hookOptions);
81
+ } catch (cause) {
82
+ running = Promise.reject(cause);
83
+ }
84
+
85
+ return restore(
86
+ Effect.tryPromise({
87
+ try: () => running,
88
+ catch: (cause) =>
89
+ failure !== undefined && cause === failure.thrown
90
+ ? new BodyFailed(failure.cause)
91
+ : options.onError(cause),
92
+ }),
93
+ ).pipe(
94
+ // Interrupting the body is what makes the callback reject, and so what
95
+ // makes the SDK roll back - which is waited for, so that nothing of
96
+ // the transaction is left running once this is done.
97
+ Effect.onInterrupt(() => {
98
+ controller.abort();
99
+ return Effect.promise(() => running.then(constVoid, constVoid));
100
+ }),
101
+ Effect.catchIf(
102
+ (error): error is BodyFailed => error instanceof BodyFailed,
103
+ (failed) => Effect.failCause(failed.cause as Cause.Cause<E>),
104
+ ),
105
+ );
106
+ });