1import type * as firestore from "@distilled.cloud/gcp/firestore_v1";
2import type * as Effect from "effect/Effect";
3import * as Binding from "../../Binding.ts";
4import type { RuntimeContext } from "../../RuntimeContext.ts";
5import type { Database } from "./Database.ts";
6
7/**
8 * A Firestore document read through a {@link ReadDatabase} client. `fields`
9 * are decoded from Firestore `Value`s to plain JavaScript: integers →
10 * `number` (`bigint` beyond the safe range), doubles → `number`,
11 * timestamps → `Date`, bytes → `Uint8Array`, arrays and maps recursively,
12 * geo points → `{ latitude, longitude }`, references → resource name.
13 */
14export interface DocumentSnapshot {
15 /** Full resource name `projects/{p}/databases/{d}/documents/{path}`. */
16 name: string;
17 /** Decoded document fields. */
18 fields: Record<string, unknown>;
19 /** RFC 3339 creation time. */
20 createTime: string | undefined;
21 /** RFC 3339 last-update time. */
22 updateTime: string | undefined;
23}
24
25export interface ListDocumentsOptions {
26 /** Maximum documents per page. */
27 pageSize?: number;
28 /** Page token from a previous `list`. */
29 pageToken?: string;
30}
31
32export interface ListDocumentsResult {
33 documents: DocumentSnapshot[];
34 /** Pass to the next `list` call; `undefined` on the last page. */
35 nextPageToken: string | undefined;
36}
37
38/** Read-only client for one Firestore database. */
39export interface ReadDatabaseClient {
40 /**
41 * Get a document by path relative to the database (`"users/alice"`), or
42 * `undefined` when it does not exist.
43 */
44 get(
45 path: string,
46 ): Effect.Effect<
47 DocumentSnapshot | undefined,
48 firestore.GetProjectsDatabasesDocumentsError,
49 RuntimeContext
50 >;
51 /**
52 * One page of documents in a collection (`"users"`, or a subcollection
53 * such as `"users/alice/posts"`).
54 */
55 list(
56 collection: string,
57 options?: ListDocumentsOptions,
58 ): Effect.Effect<
59 ListDocumentsResult,
60 firestore.ListProjectsDatabasesDocumentsError,
61 RuntimeContext
62 >;
63 /**
64 * Run a Firestore `StructuredQuery` against the database root. Filter
65 * values use the Firestore REST `Value` shape
66 * (`{ integerValue: "2" }`, `{ stringValue: "a" }`, …).
67 */
68 query(
69 structuredQuery: firestore.StructuredQuery,
70 ): Effect.Effect<
71 DocumentSnapshot[],
72 firestore.RunQueryProjectsDatabasesDocumentsError,
73 RuntimeContext
74 >;
75}
76
77/**
78 * Read access to a Firestore {@link Database}: `get`, `list`, `query`.
79 * Grants `roles/datastore.viewer` on the project under an IAM Condition
80 * naming this database (Firestore databases have no resource-level IAM
81 * policy).
82 *
83 * ### Reading documents
84 * **Example:** Get a document and list a collection
85 * ```typescript
86 * const db = yield* GCP.Firestore.ReadDatabase(database);
87 * const alice = yield* db.get("users/alice");
88 * const { documents } = yield* db.list("users", { pageSize: 20 });
89 * // …provided with Effect.provide(GCP.Firestore.ReadDatabaseHttp)
90 * ```
91 *
92 * ### Querying
93 * **Example:** Filter a collection
94 * ```typescript
95 * const db = yield* GCP.Firestore.ReadDatabase(database);
96 * const admins = yield* db.query({
97 * from: [{ collectionId: "users" }],
98 * where: {
99 * fieldFilter: {
100 * field: { fieldPath: "role" },
101 * op: "EQUAL",
102 * value: { stringValue: "admin" },
103 * },
104 * },
105 * });
106 * ```
107 *
108 * @binding
109 * @category Firestore
110 */
111export interface ReadDatabase extends Binding.Service<
112 ReadDatabase,
113 "GCP.Firestore.ReadDatabase",
114 (database: Database) => Effect.Effect<ReadDatabaseClient>
115> {}
116
117export const ReadDatabase = Binding.Service<ReadDatabase>(
118 "GCP.Firestore.ReadDatabase",
119);