@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,207 @@
1
+ /**
2
+ * The call a Callable is answering: who made it, from which app, the raw
3
+ * Payload as it arrived and the HTTP request it came in.
4
+ *
5
+ * Every call runs with a {@link CallableRequest} in scope. Callable Middleware
6
+ * reads it to decide whether the call may go on, and a handler may read it
7
+ * too. It is portable: `Functions` from
8
+ * `@visiolab/effect-firebase-functions` provides it from Firebase's request,
9
+ * and `Callable.handler` provides it from a {@link Input} of your own.
10
+ *
11
+ * @example Rate limiting by the caller's address
12
+ * ```ts
13
+ * import { Effect, Layer, Option } from "effect";
14
+ * import { CallableError, CallableMiddleware, CallableRequest } from "@visiolab/effect-firebase/functions";
15
+ *
16
+ * class RateLimit extends CallableMiddleware.Service<RateLimit>()("RateLimit", {
17
+ * error: CallableError.ResourceExhausted,
18
+ * }) {}
19
+ *
20
+ * const RateLimitLive = Layer.sync(RateLimit, () => {
21
+ * const calls = new Map<string, number>();
22
+ * return (effect) =>
23
+ * Effect.gen(function* () {
24
+ * const { rawRequest } = yield* CallableRequest.CallableRequest;
25
+ * const address = Option.getOrElse(rawRequest.remoteAddress, () => "unknown");
26
+ * const count = (calls.get(address) ?? 0) + 1;
27
+ * calls.set(address, count);
28
+ * if (count > 100) return yield* new CallableError.ResourceExhausted();
29
+ * return yield* effect;
30
+ * });
31
+ * });
32
+ * ```
33
+ *
34
+ * @stability unstable
35
+ * @since 0.3.0
36
+ */
37
+ import type { Context } from "effect";
38
+ import type * as HttpServerRequest from "effect/http/HttpServerRequest";
39
+ import * as internal from "./internal/callableRequest.ts";
40
+
41
+ /**
42
+ * Who made a call: the signed-in user's uid, the claims of their ID token and
43
+ * the raw ID token.
44
+ *
45
+ * @stability unstable
46
+ * @category models
47
+ * @since 0.3.0
48
+ */
49
+ export interface Auth {
50
+ /** The signed-in user's uid. */
51
+ readonly uid: string;
52
+ /** The decoded claims of the user's ID token. */
53
+ readonly token: Readonly<Record<string, unknown>>;
54
+ /** The ID token as the client sent it. */
55
+ readonly rawToken: string;
56
+ }
57
+
58
+ /**
59
+ * The app a call came from, as App Check attested it.
60
+ *
61
+ * @stability unstable
62
+ * @category models
63
+ * @since 0.3.0
64
+ */
65
+ export interface App {
66
+ /** The app ID of the attested Firebase app. */
67
+ readonly appId: string;
68
+ /** The decoded claims of the App Check token. */
69
+ readonly token: Readonly<Record<string, unknown>>;
70
+ /** Whether the App Check token was already used, when replay protection is on. */
71
+ readonly alreadyConsumed?: boolean | undefined;
72
+ }
73
+
74
+ /**
75
+ * The call a Callable is answering.
76
+ *
77
+ * **Details**
78
+ *
79
+ * `data` is the raw Payload, the `Schema.toCodecJson` output the client sent.
80
+ * Callable Middleware runs before the Payload is decoded, so it sees only
81
+ * `data`.
82
+ *
83
+ * `rawRequest` is the HTTP request the call came in, as Effect's
84
+ * `HttpServerRequest`. `Functions` builds it from Firebase's request,
85
+ * streaming its body without copying the bytes, and `Callable.handler` takes
86
+ * it from {@link Input.rawRequest}.
87
+ *
88
+ * @stability unstable
89
+ * @category models
90
+ * @since 0.3.0
91
+ */
92
+ export interface CallableRequest {
93
+ /** The signed-in caller, absent for a signed-out call. */
94
+ readonly auth?: Auth | undefined;
95
+ /** The app the call came from, absent without App Check. */
96
+ readonly app?: App | undefined;
97
+ /** An unverified Firebase Instance ID token, if the client sent one. */
98
+ readonly instanceIdToken?: string | undefined;
99
+ /** The raw Payload, before it is decoded. */
100
+ readonly data: unknown;
101
+ /**
102
+ * The HTTP request the call came in: its headers, method, URL, body and the
103
+ * caller's `remoteAddress`.
104
+ *
105
+ * **Details**
106
+ *
107
+ * The Firebase web SDK's `httpsCallable` can't send custom headers, so a
108
+ * header of your own comes only from callers that don't use it, such as
109
+ * another server. Headers the browser or the SDK sets, such as `user-agent`
110
+ * and `accept-language`, are there for every caller.
111
+ *
112
+ * `remoteAddress` is the caller's IP address as the platform reports it,
113
+ * `Option.none()` when it is unknown.
114
+ */
115
+ readonly rawRequest: HttpServerRequest.HttpServerRequest;
116
+ }
117
+
118
+ /**
119
+ * The service key of the {@link CallableRequest}.
120
+ *
121
+ * **Details**
122
+ *
123
+ * Every call provides it, so Callable Middleware may read it without listing
124
+ * it in `requires`.
125
+ *
126
+ * @stability unstable
127
+ * @category services
128
+ * @since 0.3.0
129
+ */
130
+ export const CallableRequest: Context.Service<CallableRequest, CallableRequest> =
131
+ internal.CallableRequest;
132
+
133
+ /**
134
+ * What `Callable.handler` takes about a call: who makes it, from which app and
135
+ * over which HTTP request. `Callable.handler` fills `data` by encoding the
136
+ * Payload.
137
+ *
138
+ * **Details**
139
+ *
140
+ * Pass `{}` for a signed-out call and `{ auth: { uid, token } }` for a
141
+ * signed-in one. `rawToken` defaults to an empty string, since a call made in
142
+ * process has no real ID token.
143
+ *
144
+ * @stability unstable
145
+ * @category models
146
+ * @since 0.3.0
147
+ */
148
+ export interface Input {
149
+ /** The signed-in caller, absent for a signed-out call. */
150
+ readonly auth?:
151
+ | {
152
+ readonly uid: string;
153
+ readonly token: Readonly<Record<string, unknown>>;
154
+ readonly rawToken?: string | undefined;
155
+ }
156
+ | undefined;
157
+ /** The app the call came from. */
158
+ readonly app?: App | undefined;
159
+ /** A Firebase Instance ID token. */
160
+ readonly instanceIdToken?: string | undefined;
161
+ /**
162
+ * The HTTP request the call comes in, for middleware that reads headers or
163
+ * the caller's address.
164
+ *
165
+ * **Details**
166
+ *
167
+ * Left out, it is a `POST` of the encoded Payload as JSON to
168
+ * `http://localhost/<deployed name>`, with `content-type:
169
+ * application/json`, no other headers and no `remoteAddress`. Build one with
170
+ * `HttpServerRequest.fromWeb` from `effect/http/HttpServerRequest`, and set
171
+ * `remoteAddress` with its `modify`.
172
+ *
173
+ * @example Calling with a request of your own
174
+ * ```ts
175
+ * import { Effect, Layer, Option } from "effect";
176
+ * import * as HttpServerRequest from "effect/http/HttpServerRequest";
177
+ * import { Callable, CallableMiddleware, CallableRequest } from "@visiolab/effect-firebase/functions";
178
+ *
179
+ * class LogLocale extends CallableMiddleware.Service<LogLocale>()("LogLocale") {}
180
+ *
181
+ * const Ping = Callable.make("ping").middleware(LogLocale);
182
+ *
183
+ * const program = Effect.gen(function* () {
184
+ * const ping = yield* Callable.handler(Ping);
185
+ * const rawRequest = HttpServerRequest.fromWeb(
186
+ * new Request("https://example.com/ping", {
187
+ * method: "POST",
188
+ * headers: { "accept-language": "de-CH" },
189
+ * }),
190
+ * ).modify({ remoteAddress: Option.some("203.0.113.7") });
191
+ * yield* ping(undefined, { rawRequest });
192
+ * }).pipe(
193
+ * Effect.provide(
194
+ * Layer.mergeAll(
195
+ * Callable.toLayer(Ping, () => Effect.void),
196
+ * Layer.succeed(LogLocale, (effect) =>
197
+ * Effect.flatMap(Effect.service(CallableRequest.CallableRequest), ({ rawRequest }) =>
198
+ * Effect.andThen(Effect.logInfo("Locale", rawRequest.headers["accept-language"]), effect),
199
+ * ),
200
+ * ),
201
+ * ),
202
+ * ),
203
+ * );
204
+ * ```
205
+ */
206
+ readonly rawRequest?: HttpServerRequest.HttpServerRequest | undefined;
207
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The Caller: the signed-in user making a call, and the built-in
3
+ * `Authenticated` middleware that provides it.
4
+ *
5
+ * A Callable that requires a signed-in caller attaches {@link Authenticated}.
6
+ * Its handler then reads the {@link Caller} instead of the raw request, and a
7
+ * signed-out call fails with `CallableError.Unauthenticated` before its
8
+ * Payload is decoded.
9
+ *
10
+ * @example Reading the Caller in a handler
11
+ * ```ts
12
+ * import { Effect, Schema } from "effect";
13
+ * import { Callable, Caller } from "@visiolab/effect-firebase/functions";
14
+ *
15
+ * const WhoAmI = Callable.make("whoAmI", { success: Schema.String }).middleware(
16
+ * Caller.Authenticated,
17
+ * );
18
+ *
19
+ * const WhoAmILive = Callable.toLayer(WhoAmI, () =>
20
+ * Effect.map(Effect.service(Caller.Caller), ({ uid }) => uid),
21
+ * );
22
+ * ```
23
+ *
24
+ * @stability unstable
25
+ * @since 0.3.0
26
+ */
27
+ import { Context, Effect, Layer } from "effect";
28
+ import * as CallableError from "./CallableError.ts";
29
+ import * as CallableMiddleware from "./CallableMiddleware.ts";
30
+ import * as CallableRequest from "./CallableRequest.ts";
31
+
32
+ /**
33
+ * The signed-in user making a call.
34
+ *
35
+ * **Details**
36
+ *
37
+ * A portable shape, not `firebase-admin`'s `DecodedIdToken`. There is no
38
+ * constructor: {@link Authenticated} provides it from the request, and a test
39
+ * provides a fake one from a stub middleware Layer.
40
+ *
41
+ * @stability unstable
42
+ * @category models
43
+ * @since 0.3.0
44
+ */
45
+ export interface Caller {
46
+ /** The user's uid. */
47
+ readonly uid: string;
48
+ /** The decoded claims of the user's ID token. */
49
+ readonly token: Readonly<Record<string, unknown>>;
50
+ /** The ID token as the client sent it. */
51
+ readonly rawToken: string;
52
+ }
53
+
54
+ /**
55
+ * The service key of the {@link Caller}.
56
+ *
57
+ * @stability unstable
58
+ * @category services
59
+ * @since 0.3.0
60
+ */
61
+ export const Caller: Context.Service<Caller, Caller> = Context.Service<Caller>(
62
+ "@visiolab/effect-firebase/functions/Caller",
63
+ );
64
+
65
+ /**
66
+ * The built-in middleware that requires a signed-in caller and provides the
67
+ * {@link Caller}.
68
+ *
69
+ * **When to use**
70
+ *
71
+ * Use on every Callable that only a signed-in user may call. Write a
72
+ * middleware of your own with {@link CallableMiddleware.Service} for other
73
+ * requirements, such as a custom claim.
74
+ *
75
+ * **Details**
76
+ *
77
+ * A call without auth fails with `CallableError.Unauthenticated`, before its
78
+ * Payload is decoded. Provide {@link Authenticated.layer} where the chain
79
+ * runs, or a stub Layer that provides a fake Caller in tests.
80
+ *
81
+ * @example Stubbing the Caller in process
82
+ * ```ts
83
+ * import { Effect, Layer, Schema } from "effect";
84
+ * import { Callable, Caller } from "@visiolab/effect-firebase/functions";
85
+ *
86
+ * const WhoAmI = Callable.make("whoAmI", { success: Schema.String }).middleware(
87
+ * Caller.Authenticated,
88
+ * );
89
+ *
90
+ * const WhoAmILive = Callable.toLayer(WhoAmI, () =>
91
+ * Effect.map(Effect.service(Caller.Caller), ({ uid }) => uid),
92
+ * );
93
+ *
94
+ * const AsAda = Layer.succeed(Caller.Authenticated, (effect) =>
95
+ * Effect.provideService(effect, Caller.Caller, { uid: "ada", token: {}, rawToken: "" }),
96
+ * );
97
+ *
98
+ * const program = Effect.gen(function* () {
99
+ * const whoAmI = yield* Callable.handler(WhoAmI);
100
+ * return yield* whoAmI(undefined, {});
101
+ * }).pipe(Effect.provide(Layer.merge(WhoAmILive, AsAda)));
102
+ * ```
103
+ *
104
+ * @see {@link Caller} for what it provides
105
+ * @see {@link CallableMiddleware.Service} for declaring a middleware of your own
106
+ *
107
+ * @stability unstable
108
+ * @category middleware
109
+ * @since 0.3.0
110
+ */
111
+ export class Authenticated extends CallableMiddleware.Service<
112
+ Authenticated,
113
+ { provides: Caller }
114
+ >()("@visiolab/effect-firebase/functions/Caller/Authenticated", {
115
+ error: CallableError.Unauthenticated,
116
+ }) {
117
+ /**
118
+ * The implementation of {@link Authenticated}: provides the {@link Caller}
119
+ * from the request's auth, and fails with `CallableError.Unauthenticated`
120
+ * when there is none.
121
+ *
122
+ * **When to use**
123
+ *
124
+ * Use where the chain runs, in production and in process alike.
125
+ *
126
+ * @stability unstable
127
+ * @category layers
128
+ * @since 0.3.0
129
+ */
130
+ static readonly layer: Layer.Layer<Authenticated> = Layer.succeed(this, (effect) =>
131
+ Effect.gen(function* () {
132
+ const { auth } = yield* Effect.service(CallableRequest.CallableRequest);
133
+ if (auth === undefined) return yield* new CallableError.Unauthenticated();
134
+ return yield* Effect.provideService(effect, Caller, {
135
+ uid: auth.uid,
136
+ token: auth.token,
137
+ rawToken: auth.rawToken,
138
+ });
139
+ }),
140
+ );
141
+ }