| 1 | import * as GCP from "@/GCP"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import { serveProbes } from "../../bindingHost.ts"; |
| 4 | |
| 5 | const nativeDatabase = (id: string) => |
| 6 | GCP.Firestore.Database(id, { |
| 7 | location: "us-central1", |
| 8 | type: "FIRESTORE_NATIVE", |
| 9 | }); |
| 10 | |
| 11 | /** Database the per-document PatchDocument / GetDocument / DeleteDocument bind. */ |
| 12 | export const DocsDatabase = nativeDatabase("DocsDatabase"); |
| 13 | /** Database only {@link GCP.Firestore.ReadDatabase} binds (seeded by the test). */ |
| 14 | export const ReadOnlyDatabase = nativeDatabase("ReadOnlyDatabase"); |
| 15 | /** Database only {@link GCP.Firestore.WriteDatabase} binds. */ |
| 16 | export const WriteOnlyDatabase = nativeDatabase("WriteOnlyDatabase"); |
| 17 | /** Database only {@link GCP.Firestore.ReadWriteDatabase} binds. */ |
| 18 | export const ReadWriteOnlyDatabase = nativeDatabase("ReadWriteOnlyDatabase"); |
| 19 | |
| 20 | /** Documents the test seeds out of band. */ |
| 21 | export const SEEDED_GET = "users/seeded"; |
| 22 | export const SEEDED_DELETE = "users/doomed"; |
| 23 | export const PATCHED = "users/alice"; |
| 24 | |
| 25 | /** |
| 26 | * Effect-native Cloud Run service exercising every Firestore binding as its |
| 27 | * own runtime service account. Deployed from {@link ../Bindings.test.ts}. |
| 28 | */ |
| 29 | export default class FirestoreBindingsHost extends GCP.Function<FirestoreBindingsHost>()( |
| 30 | "FirestoreBindingsHost", |
| 31 | { main: import.meta.url, invokerIamDisabled: true }, |
| 32 | Effect.gen(function* () { |
| 33 | const patchDocument = yield* GCP.Firestore.PatchDocument(DocsDatabase); |
| 34 | const getDocument = yield* GCP.Firestore.GetDocument(DocsDatabase); |
| 35 | const deleteDocument = yield* GCP.Firestore.DeleteDocument(DocsDatabase); |
| 36 | const reader = yield* GCP.Firestore.ReadDatabase(ReadOnlyDatabase); |
| 37 | const writer = yield* GCP.Firestore.WriteDatabase(WriteOnlyDatabase); |
| 38 | const both = yield* GCP.Firestore.ReadWriteDatabase(ReadWriteOnlyDatabase); |
| 39 | |
| 40 | return { |
| 41 | fetch: serveProbes({ |
| 42 | patchDocument: patchDocument({ |
| 43 | documentPath: PATCHED, |
| 44 | body: { fields: { name: { stringValue: "Alice" } } }, |
| 45 | }), |
| 46 | getDocument: getDocument({ documentPath: SEEDED_GET }), |
| 47 | deleteDocument: deleteDocument({ documentPath: SEEDED_DELETE }), |
| 48 | read: Effect.gen(function* () { |
| 49 | const one = yield* reader.get("things/a"); |
| 50 | const missing = yield* reader.get("things/never"); |
| 51 | const page1 = yield* reader.list("things", { pageSize: 1 }); |
| 52 | const page2 = yield* reader.list("things", { |
| 53 | pageSize: 1, |
| 54 | pageToken: page1.nextPageToken, |
| 55 | }); |
| 56 | const big = yield* reader.query({ |
| 57 | from: [{ collectionId: "things" }], |
| 58 | where: { |
| 59 | fieldFilter: { |
| 60 | field: { fieldPath: "n" }, |
| 61 | op: "GREATER_THAN", |
| 62 | value: { integerValue: "1" }, |
| 63 | }, |
| 64 | }, |
| 65 | }); |
| 66 | return { |
| 67 | one: one?.fields, |
| 68 | missing: missing === undefined, |
| 69 | listed: [...page1.documents, ...page2.documents] |
| 70 | .map((doc) => doc.name.split("/").pop()) |
| 71 | .sort(), |
| 72 | hasNextPage: page1.nextPageToken !== undefined, |
| 73 | big: big.map((doc) => doc.fields.n), |
| 74 | }; |
| 75 | }), |
| 76 | write: Effect.gen(function* () { |
| 77 | yield* writer.delete("things/created"); |
| 78 | const created = yield* writer.create("things/created", { n: 1 }); |
| 79 | const conflict = yield* writer |
| 80 | .create("things/created", { n: 2 }) |
| 81 | .pipe( |
| 82 | Effect.map(() => "created"), |
| 83 | Effect.catchTag("GCP.Firestore.DocumentAlreadyExists", (error) => |
| 84 | Effect.succeed(error.path), |
| 85 | ), |
| 86 | ); |
| 87 | const updated = yield* writer.update("things/created", { |
| 88 | "odd key": "x", |
| 89 | }); |
| 90 | const updateMissing = yield* writer |
| 91 | .update("things/never", { a: 1 }) |
| 92 | .pipe( |
| 93 | Effect.map(() => "updated"), |
| 94 | Effect.catchTag("NotFound", () => Effect.succeed("NotFound")), |
| 95 | ); |
| 96 | yield* writer.set("things/set", { team: "red" }); |
| 97 | yield* writer.set("things/gone", { team: "blue" }); |
| 98 | yield* writer.delete("things/gone"); |
| 99 | yield* writer.delete("things/never-existed"); |
| 100 | return { |
| 101 | created: created.name.endsWith("/documents/things/created"), |
| 102 | conflict, |
| 103 | updated: updated.fields, |
| 104 | updateMissing, |
| 105 | }; |
| 106 | }), |
| 107 | readWrite: Effect.gen(function* () { |
| 108 | yield* both.delete("scores/a"); |
| 109 | const missing = yield* both.get("scores/a"); |
| 110 | yield* both.create("scores/a", { team: "red", n: 1 }); |
| 111 | yield* both.set("scores/b", { team: "blue", n: 2 }); |
| 112 | yield* both.update("scores/b", { n: 3 }); |
| 113 | const b = yield* both.get("scores/b"); |
| 114 | const listed = yield* both.list("scores"); |
| 115 | const red = yield* both.query({ |
| 116 | from: [{ collectionId: "scores" }], |
| 117 | where: { |
| 118 | fieldFilter: { |
| 119 | field: { fieldPath: "team" }, |
| 120 | op: "EQUAL", |
| 121 | value: { stringValue: "red" }, |
| 122 | }, |
| 123 | }, |
| 124 | }); |
| 125 | yield* both.delete("scores/a"); |
| 126 | const afterDelete = yield* both.get("scores/a"); |
| 127 | return { |
| 128 | missing: missing === undefined, |
| 129 | b: b?.fields, |
| 130 | listed: listed.documents |
| 131 | .map((doc) => doc.name.split("/").pop()) |
| 132 | .sort(), |
| 133 | red: red.map((doc) => doc.name.split("/").pop()), |
| 134 | afterDelete: afterDelete === undefined, |
| 135 | }; |
| 136 | }), |
| 137 | }), |
| 138 | }; |
| 139 | }).pipe( |
| 140 | Effect.provide(GCP.Firestore.PatchDocumentHttp), |
| 141 | Effect.provide(GCP.Firestore.GetDocumentHttp), |
| 142 | Effect.provide(GCP.Firestore.DeleteDocumentHttp), |
| 143 | Effect.provide(GCP.Firestore.ReadDatabaseHttp), |
| 144 | Effect.provide(GCP.Firestore.WriteDatabaseHttp), |
| 145 | Effect.provide(GCP.Firestore.ReadWriteDatabaseHttp), |
| 146 | ), |
| 147 | ) {} |