1import * as Effect from "effect/Effect";
2import type { Database } from "../Firestore/Database.ts";
3import { bindGcpHost } from "../Host.ts";
4import { type BindingIam, type GcpHttpOp, grantFor } from "../HttpBinding.ts";
5
6/**
7 * A Datastore request with the project and database filled in from the
8 * bound database: callers only supply the request body (minus
9 * `databaseId`).
10 */
11export type DatastoreDatabaseRequest<
12 I extends {
13 projectId: string;
14 requestParams?: string;
15 body?: { databaseId?: string };
16 },
17> = Omit<I, "projectId" | "requestParams" | "body"> & {
18 body?: Omit<NonNullable<I["body"]>, "databaseId">;
19};
20
21/** The Datastore API names the default database `""`, not `(default)`. */
22const apiDatabaseId = (databaseId: string) =>
23 databaseId === "(default)" ? "" : databaseId;
24
25/**
26 * Shared HTTP scaffolding for Datastore bindings.
27 *
28 * Grants the role on the project under an IAM Condition matching only the
29 * bound database, and pins every request to that database's project and id.
30 * NOT exported from index.ts.
31 */
32export const makeDatastoreHttpBinding = <
33 I extends {
34 projectId: string;
35 requestParams?: string;
36 body?: { databaseId?: string };
37 },
38 A,
39 E,
40>(options: {
41 tag: string;
42 iam: BindingIam;
43 operation: GcpHttpOp<I, A, E>;
44}) =>
45 Effect.gen(function* () {
46 const run = yield* options.operation;
47 return Effect.fn(function* (database: Database) {
48 yield* bindGcpHost({
49 tag: options.tag,
50 resource: database,
51 iam: [grantFor(options.iam, database.name)],
52 });
53 const project = yield* database.project;
54 const databaseId = yield* database.databaseId;
55 return Effect.fn(`${options.tag}(${database.LogicalId})`)(function* (
56 request: DatastoreDatabaseRequest<I>,
57 ) {
58 const projectId = yield* project;
59 const id = apiDatabaseId(yield* databaseId);
60 return yield* run({
61 ...request,
62 projectId,
63 // Named databases require the routing header.
64 requestParams:
65 id.length > 0
66 ? `project_id=${projectId}&database_id=${id}`
67 : undefined,
68 body: { ...request.body, databaseId: id },
69 } as I);
70 });
71 });
72 });