@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.
- package/LICENSE +21 -0
- package/README.md +70 -2
- package/dist/_virtual/_rolldown/runtime.mjs +12 -0
- package/dist/app/App.d.mts +117 -0
- package/dist/app/App.mjs +149 -0
- package/dist/app/App.mjs.map +1 -0
- package/dist/app/index.d.mts +2 -0
- package/dist/app/index.mjs +1 -0
- package/dist/app/internal/app.mjs +20 -0
- package/dist/app/internal/app.mjs.map +1 -0
- package/dist/firestore/ClientDocumentSnapshot.d.mts +106 -0
- package/dist/firestore/ClientDocumentSnapshot.mjs +86 -0
- package/dist/firestore/ClientDocumentSnapshot.mjs.map +1 -0
- package/dist/firestore/ClientFirestore.d.mts +331 -0
- package/dist/firestore/ClientFirestore.mjs +146 -0
- package/dist/firestore/ClientFirestore.mjs.map +1 -0
- package/dist/firestore/ClientFirestoreValue.d.mts +61 -0
- package/dist/firestore/ClientFirestoreValue.mjs +62 -0
- package/dist/firestore/ClientFirestoreValue.mjs.map +1 -0
- package/dist/firestore/ClientQuerySnapshot.d.mts +52 -0
- package/dist/firestore/ClientQuerySnapshot.mjs +58 -0
- package/dist/firestore/ClientQuerySnapshot.mjs.map +1 -0
- package/dist/firestore/index.d.mts +5 -0
- package/dist/firestore/index.mjs +5 -0
- package/dist/firestore/internal/firestore.mjs +158 -0
- package/dist/firestore/internal/firestore.mjs.map +1 -0
- package/dist/firestore/internal/firestoreValue.mjs +43 -0
- package/dist/firestore/internal/firestoreValue.mjs.map +1 -0
- package/dist/firestore/internal/snapshotMetadata.mjs +15 -0
- package/dist/firestore/internal/snapshotMetadata.mjs.map +1 -0
- package/dist/functions/CallableClient.d.mts +139 -0
- package/dist/functions/CallableClient.mjs +215 -0
- package/dist/functions/CallableClient.mjs.map +1 -0
- package/dist/functions/ClientFunctions.d.mts +112 -0
- package/dist/functions/ClientFunctions.mjs +105 -0
- package/dist/functions/ClientFunctions.mjs.map +1 -0
- package/dist/functions/index.d.mts +3 -0
- package/dist/functions/index.mjs +3 -0
- package/package.json +63 -3
- package/src/app/App.ts +163 -0
- package/src/app/index.ts +7 -0
- package/src/app/internal/app.ts +35 -0
- package/src/firestore/ClientDocumentSnapshot.ts +156 -0
- package/src/firestore/ClientFirestore.ts +410 -0
- package/src/firestore/ClientFirestoreValue.ts +94 -0
- package/src/firestore/ClientQuerySnapshot.ts +93 -0
- package/src/firestore/index.ts +30 -0
- package/src/firestore/internal/firestore.ts +264 -0
- package/src/firestore/internal/firestoreValue.ts +58 -0
- package/src/firestore/internal/snapshotMetadata.ts +14 -0
- package/src/functions/CallableClient.ts +296 -0
- package/src/functions/ClientFunctions.ts +149 -0
- 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));
|
package/src/app/index.ts
ADDED
|
@@ -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));
|