@visiolab/effect-firebase-client 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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -2
  3. package/dist/_virtual/_rolldown/runtime.mjs +12 -0
  4. package/dist/app/App.d.mts +117 -0
  5. package/dist/app/App.mjs +149 -0
  6. package/dist/app/App.mjs.map +1 -0
  7. package/dist/app/index.d.mts +2 -0
  8. package/dist/app/index.mjs +1 -0
  9. package/dist/app/internal/app.mjs +20 -0
  10. package/dist/app/internal/app.mjs.map +1 -0
  11. package/dist/firestore/ClientDocumentSnapshot.d.mts +106 -0
  12. package/dist/firestore/ClientDocumentSnapshot.mjs +86 -0
  13. package/dist/firestore/ClientDocumentSnapshot.mjs.map +1 -0
  14. package/dist/firestore/ClientFirestore.d.mts +331 -0
  15. package/dist/firestore/ClientFirestore.mjs +146 -0
  16. package/dist/firestore/ClientFirestore.mjs.map +1 -0
  17. package/dist/firestore/ClientFirestoreValue.d.mts +61 -0
  18. package/dist/firestore/ClientFirestoreValue.mjs +62 -0
  19. package/dist/firestore/ClientFirestoreValue.mjs.map +1 -0
  20. package/dist/firestore/ClientQuerySnapshot.d.mts +52 -0
  21. package/dist/firestore/ClientQuerySnapshot.mjs +58 -0
  22. package/dist/firestore/ClientQuerySnapshot.mjs.map +1 -0
  23. package/dist/firestore/index.d.mts +5 -0
  24. package/dist/firestore/index.mjs +5 -0
  25. package/dist/firestore/internal/firestore.mjs +158 -0
  26. package/dist/firestore/internal/firestore.mjs.map +1 -0
  27. package/dist/firestore/internal/firestoreValue.mjs +43 -0
  28. package/dist/firestore/internal/firestoreValue.mjs.map +1 -0
  29. package/dist/firestore/internal/snapshotMetadata.mjs +15 -0
  30. package/dist/firestore/internal/snapshotMetadata.mjs.map +1 -0
  31. package/dist/functions/CallableClient.d.mts +139 -0
  32. package/dist/functions/CallableClient.mjs +215 -0
  33. package/dist/functions/CallableClient.mjs.map +1 -0
  34. package/dist/functions/ClientFunctions.d.mts +112 -0
  35. package/dist/functions/ClientFunctions.mjs +105 -0
  36. package/dist/functions/ClientFunctions.mjs.map +1 -0
  37. package/dist/functions/index.d.mts +3 -0
  38. package/dist/functions/index.mjs +3 -0
  39. package/package.json +63 -3
  40. package/src/app/App.ts +163 -0
  41. package/src/app/index.ts +7 -0
  42. package/src/app/internal/app.ts +35 -0
  43. package/src/firestore/ClientDocumentSnapshot.ts +156 -0
  44. package/src/firestore/ClientFirestore.ts +410 -0
  45. package/src/firestore/ClientFirestoreValue.ts +94 -0
  46. package/src/firestore/ClientQuerySnapshot.ts +93 -0
  47. package/src/firestore/index.ts +30 -0
  48. package/src/firestore/internal/firestore.ts +264 -0
  49. package/src/firestore/internal/firestoreValue.ts +58 -0
  50. package/src/firestore/internal/snapshotMetadata.ts +14 -0
  51. package/src/functions/CallableClient.ts +296 -0
  52. package/src/functions/ClientFunctions.ts +149 -0
  53. package/src/functions/index.ts +15 -0
package/src/app/App.ts ADDED
@@ -0,0 +1,163 @@
1
+ /**
2
+ * The Firebase client SDK app as an Effect service.
3
+ *
4
+ * Every other service in this package, such as
5
+ * {@link ClientFirestore.ClientFirestore}, is built on the {@link App} in
6
+ * context. Provide it with {@link layer}, or hand it an app initialized
7
+ * elsewhere.
8
+ *
9
+ * @example Initializing the app for a program
10
+ * ```ts
11
+ * import { Effect } from "effect";
12
+ * import { App } from "@visiolab/effect-firebase-client/app";
13
+ *
14
+ * const program = Effect.gen(function* () {
15
+ * const app = yield* App.App;
16
+ * yield* Effect.log(`Running against ${app.name}`);
17
+ * });
18
+ *
19
+ * program.pipe(
20
+ * Effect.provide(App.layer({ apiKey: "<api key>", projectId: "my-project", appId: "<app id>" })),
21
+ * );
22
+ * ```
23
+ *
24
+ * @see [Add Firebase to your JavaScript project](https://firebase.google.com/docs/web/setup)
25
+ *
26
+ * @stability unstable
27
+ * @since 0.1.0
28
+ */
29
+ import { Context, Effect, Layer, Schema } from "effect";
30
+ import type * as Client from "firebase/app";
31
+ // Only the module's documentation links to it.
32
+ // oxlint-disable-next-line no-unused-vars
33
+ import type * as ClientFirestore from "../firestore/ClientFirestore.ts";
34
+ import * as internalApp from "./internal/app.ts";
35
+
36
+ /**
37
+ * A Firebase client SDK app: the SDK's own `FirebaseApp`.
38
+ *
39
+ * @stability unstable
40
+ * @category models
41
+ * @since 0.1.0
42
+ */
43
+ export interface App extends Client.FirebaseApp {}
44
+
45
+ /**
46
+ * Service key for the Firebase client SDK app.
47
+ *
48
+ * **When to use**
49
+ *
50
+ * Use to provide an app initialized elsewhere, such as one from `getApp()`,
51
+ * with `Layer.succeed`. Nothing deletes an app provided this way.
52
+ *
53
+ * @example Providing an app initialized elsewhere
54
+ * ```ts
55
+ * import { Layer } from "effect";
56
+ * import { getApp } from "firebase/app";
57
+ * import { App } from "@visiolab/effect-firebase-client/app";
58
+ *
59
+ * const AppLive = Layer.succeed(App.App, getApp());
60
+ * ```
61
+ *
62
+ * @see {@link layer} for initializing the app for the lifetime of a layer
63
+ *
64
+ * @stability unstable
65
+ * @category services
66
+ * @since 0.1.0
67
+ */
68
+ export const App = Context.Service<App>("@visiolab/effect-firebase-client/app/App");
69
+
70
+ /**
71
+ * Fails {@link make} and {@link layer} when the client SDK refuses to
72
+ * initialize the app, with what it threw as the `cause`.
73
+ *
74
+ * @stability unstable
75
+ * @category errors
76
+ * @since 0.1.0
77
+ */
78
+ export class AppError extends Schema.TaggedError<AppError, { readonly brand: unique symbol }>()(
79
+ "AppError",
80
+ {
81
+ message: Schema.String,
82
+ cause: Schema.optional(Schema.Defect()),
83
+ },
84
+ ) {}
85
+
86
+ /**
87
+ * Initializes a Firebase client SDK app, and deletes it again when the
88
+ * surrounding scope closes.
89
+ *
90
+ * **When to use**
91
+ *
92
+ * Use to build the app inside an effect of your own. Most programs provide it
93
+ * with {@link layer} instead.
94
+ *
95
+ * **Details**
96
+ *
97
+ * Takes the arguments of `initializeApp(options, name)`. Unlike the Admin SDK's,
98
+ * the options are required: they are the config the Firebase console gives a
99
+ * web app, which the client SDK only finds on its own when served from
100
+ * Firebase Hosting.
101
+ *
102
+ * An app already registered under the same name is not deleted when the scope
103
+ * closes. The client SDK only hands back that app when it was initialized with
104
+ * the same options, and throws otherwise.
105
+ *
106
+ * Fails with an {@link AppError} when the client SDK refuses to initialize the
107
+ * app. A failure to delete the app is logged as a warning, not raised.
108
+ *
109
+ * @see {@link layer} for the same as a layer
110
+ *
111
+ * @stability unstable
112
+ * @category constructors
113
+ * @since 0.1.0
114
+ */
115
+ export const make = Effect.fn("App.make")(function* (
116
+ options: Client.FirebaseOptions,
117
+ name?: string,
118
+ ) {
119
+ const { app } = yield* Effect.acquireRelease(
120
+ Effect.try({
121
+ try: () => internalApp.initialize(options, name),
122
+ catch: (cause) =>
123
+ new AppError({ message: "Failed to initialize the Firebase client SDK app", cause }),
124
+ }),
125
+ internalApp.release,
126
+ );
127
+ return app;
128
+ });
129
+
130
+ /**
131
+ * A layer that initializes a Firebase client SDK app for the lifetime of the
132
+ * layer.
133
+ *
134
+ * **When to use**
135
+ *
136
+ * Use when this program owns the app. Where something else already initialized
137
+ * it, provide that app as {@link App} instead.
138
+ *
139
+ * **Details**
140
+ *
141
+ * Takes the arguments of `initializeApp(options, name)`.
142
+ *
143
+ * @example Initializing the app from the console's config
144
+ * ```ts
145
+ * import { App } from "@visiolab/effect-firebase-client/app";
146
+ *
147
+ * const AppLive = App.layer({
148
+ * apiKey: "<api key>",
149
+ * authDomain: "my-project.firebaseapp.com",
150
+ * projectId: "my-project",
151
+ * appId: "<app id>",
152
+ * });
153
+ * ```
154
+ *
155
+ * @see {@link make} for how the app is initialized and torn down
156
+ * @see {@link App} for providing an app initialized elsewhere
157
+ *
158
+ * @stability unstable
159
+ * @category layers
160
+ * @since 0.1.0
161
+ */
162
+ export const layer = (options: Client.FirebaseOptions, name?: string): Layer.Layer<App, AppError> =>
163
+ Layer.effect(App, make(options, name));
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The Firebase client SDK app as an Effect service.
3
+ *
4
+ * @stability unstable
5
+ * @since 0.1.0
6
+ */
7
+ export * as App from "./App.ts";
@@ -0,0 +1,35 @@
1
+ import { Effect } from "effect";
2
+ import { defaultAppName } from "@visiolab/effect-firebase/app/App";
3
+ import * as Client from "firebase/app";
4
+ import type * as App from "../App.ts";
5
+
6
+ /**
7
+ * The app together with whether we are the ones who initialized it.
8
+ *
9
+ * @internal
10
+ */
11
+ export interface Initialized {
12
+ readonly app: App.App;
13
+ readonly owned: boolean;
14
+ }
15
+
16
+ /** @internal */
17
+ export const initialize = (options: Client.FirebaseOptions, name?: string): Initialized => {
18
+ // An app that is already registered under this name was initialized by
19
+ // somebody else, so ending its lifecycle is not ours to do. `initializeApp`
20
+ // still throws when that app was configured differently, so conflicting
21
+ // configuration is not swallowed here.
22
+ const owned = !Client.getApps().some((app) => app.name === (name ?? defaultAppName));
23
+ return { app: Client.initializeApp(options, name), owned };
24
+ };
25
+
26
+ /** @internal */
27
+ export const release = ({ app, owned }: Initialized): Effect.Effect<void> =>
28
+ owned
29
+ ? Effect.tryPromise(() => Client.deleteApp(app)).pipe(
30
+ Effect.ignore({
31
+ log: "Warn",
32
+ message: `Failed to delete the Firebase client SDK app "${app.name}"`,
33
+ }),
34
+ )
35
+ : Effect.void;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * What a read of one document through {@link ClientFirestore.ClientFirestore}
3
+ * returns: a portable {@link DocumentSnapshot.DocumentSnapshot} that also
4
+ * carries the {@link SnapshotMetadata} the client SDK reported for the read.
5
+ *
6
+ * Each variant extends its portable one, so a client snapshot can be handed to
7
+ * portable code as it is, and the portable guards, such as
8
+ * {@link DocumentSnapshot.isExisting}, tell its variants apart.
9
+ *
10
+ * @example Checking whether a read came from the cache
11
+ * ```ts
12
+ * import { Effect, Schema } from "effect";
13
+ * import { Collection, FirestoreModel } from "@visiolab/effect-firebase/firestore";
14
+ * import { ClientFirestore } from "@visiolab/effect-firebase-client/firestore";
15
+ *
16
+ * class User extends FirestoreModel.Class<User>("User")({
17
+ * name: Schema.String,
18
+ * }) {}
19
+ *
20
+ * const Users = Collection.make("users", User);
21
+ *
22
+ * const program = Effect.gen(function* () {
23
+ * const firestore = yield* ClientFirestore.ClientFirestore;
24
+ * const snapshot = yield* firestore.getDoc(Users.doc(Users.id.make("alice")));
25
+ * if (snapshot.metadata.fromCache) {
26
+ * // Served from the local cache, so possibly out of date.
27
+ * }
28
+ * });
29
+ * ```
30
+ *
31
+ * @see {@link ClientQuerySnapshot} for what a read of many documents returns
32
+ *
33
+ * @stability unstable
34
+ * @since 0.1.0
35
+ */
36
+ import * as DocumentSnapshot from "@visiolab/effect-firebase/firestore/DocumentSnapshot";
37
+ import type * as Collection from "@visiolab/effect-firebase/firestore/Collection";
38
+ import * as snapshotMetadata from "./internal/snapshotMetadata.ts";
39
+
40
+ /**
41
+ * What the client SDK reports about where a snapshot came from: the SDK's
42
+ * `SnapshotMetadata`.
43
+ *
44
+ * @see {@link ClientFirestore.ListenOptions} for hearing a change to it on a listen
45
+ *
46
+ * @stability unstable
47
+ * @category models
48
+ * @since 0.1.0
49
+ */
50
+ export interface SnapshotMetadata {
51
+ /**
52
+ * Whether it was served from the local cache instead of confirmed by the
53
+ * server, such as while offline, and so may be out of date.
54
+ */
55
+ readonly fromCache: boolean;
56
+ /**
57
+ * Whether it reflects writes made by this client that the server has not
58
+ * acknowledged yet.
59
+ */
60
+ readonly hasPendingWrites: boolean;
61
+ }
62
+
63
+ /**
64
+ * A document that was there when it was read, with the
65
+ * {@link SnapshotMetadata} of that read.
66
+ *
67
+ * @see {@link DocumentSnapshot.Existing} for the portable snapshot it extends
68
+ *
69
+ * @stability unstable
70
+ * @category models
71
+ * @since 0.1.0
72
+ */
73
+ export interface Existing<C extends Collection.Any> extends DocumentSnapshot.Existing<C> {
74
+ readonly metadata: SnapshotMetadata;
75
+ }
76
+
77
+ /**
78
+ * A document that was not there when it was read, with the
79
+ * {@link SnapshotMetadata} of that read. Its `fromCache` tells "the cache says
80
+ * it is gone" from "the server says it is gone".
81
+ *
82
+ * @see {@link DocumentSnapshot.Missing} for the portable snapshot it extends
83
+ *
84
+ * @stability unstable
85
+ * @category models
86
+ * @since 0.1.0
87
+ */
88
+ export interface Missing<C extends Collection.Any> extends DocumentSnapshot.Missing<C> {
89
+ readonly metadata: SnapshotMetadata;
90
+ }
91
+
92
+ /**
93
+ * A document in `C`, as a read through {@link ClientFirestore.ClientFirestore}
94
+ * found it: {@link Existing} or {@link Missing}.
95
+ *
96
+ * @see {@link DocumentSnapshot.DocumentSnapshot} for the portable snapshot
97
+ *
98
+ * @stability unstable
99
+ * @category models
100
+ * @since 0.1.0
101
+ */
102
+ export type ClientDocumentSnapshot<C extends Collection.Any> = Existing<C> | Missing<C>;
103
+
104
+ /**
105
+ * Builds a snapshot of the document at `ref` from what the client SDK's
106
+ * `snapshot.data()` returned and the `snapshot.metadata` it reported.
107
+ *
108
+ * **When to use**
109
+ *
110
+ * Use to turn a snapshot read through the client SDK itself into this
111
+ * library's. An `undefined` `data` builds {@link Missing}, anything else
112
+ * {@link Existing}.
113
+ *
114
+ * @see {@link existing} for a document known to be there
115
+ * @see {@link missing} for a document known not to be there
116
+ *
117
+ * @stability unstable
118
+ * @category constructors
119
+ * @since 0.1.0
120
+ */
121
+ export const make = <C extends Collection.Any>(
122
+ ref: Collection.DocumentReference<C>,
123
+ data: unknown,
124
+ metadata: SnapshotMetadata,
125
+ ): ClientDocumentSnapshot<C> =>
126
+ DocumentSnapshot.make(ref, data, snapshotMetadata.extraOf(metadata));
127
+
128
+ /**
129
+ * Builds a snapshot of a document known to be there, such as one a query
130
+ * returned.
131
+ *
132
+ * @see {@link make} for a document that may or may not be there
133
+ *
134
+ * @stability unstable
135
+ * @category constructors
136
+ * @since 0.1.0
137
+ */
138
+ export const existing = <C extends Collection.Any>(
139
+ ref: Collection.DocumentReference<C>,
140
+ data: unknown,
141
+ metadata: SnapshotMetadata,
142
+ ): Existing<C> => DocumentSnapshot.existing(ref, data, snapshotMetadata.extraOf(metadata));
143
+
144
+ /**
145
+ * Builds a snapshot of a document known not to be there.
146
+ *
147
+ * @see {@link make} for a document that may or may not be there
148
+ *
149
+ * @stability unstable
150
+ * @category constructors
151
+ * @since 0.1.0
152
+ */
153
+ export const missing = <C extends Collection.Any>(
154
+ ref: Collection.DocumentReference<C>,
155
+ metadata: SnapshotMetadata,
156
+ ): Missing<C> => DocumentSnapshot.missing(ref, snapshotMetadata.extraOf(metadata));