1import * as Effect from "effect/Effect";
2import * as Redacted from "effect/Redacted";
3import * as Provider from "../../Provider.ts";
4import type { ResourceClass, ResourceLike } from "../../Resource.ts";
5
6/**
7 * Shared helpers for the Prisma providers' local (`alchemy dev`) variants.
8 * Local variants fabricate deterministic `dev:`-prefixed identifiers and
9 * never talk to the Prisma Management API.
10 */
11
12export type DevRecord = Record<string, unknown>;
13
14export const DEV_TIMESTAMP = "1970-01-01T00:00:00.000Z";
15
16export const devId = (type: string, id: string) => `dev:${type}:${id}`;
17
18export const isRecord = (value: unknown): value is DevRecord =>
19 typeof value === "object" && value !== null;
20
21const attr = (value: unknown, key: string) =>
22 isRecord(value) ? value[key] : undefined;
23
24export const attrOrString = (value: unknown, attrName: string) =>
25 typeof value === "string"
26 ? value
27 : isRecord(value) && typeof value[attrName] === "string"
28 ? value[attrName]
29 : undefined;
30
31export const attrOrNullableString = (value: unknown, key: string) => {
32 const candidate = attr(value, key);
33 return candidate === null || typeof candidate === "string"
34 ? candidate
35 : undefined;
36};
37
38export const attrOrRedactedString = (value: unknown, key: string) => {
39 const candidate = attr(value, key);
40 return Redacted.isRedacted(candidate)
41 ? Redacted.make(String(Redacted.value(candidate)))
42 : typeof candidate === "string"
43 ? Redacted.make(candidate)
44 : undefined;
45};
46
47/**
48 * Build a stateless local provider stub: reconcile merges the previous
49 * outputs with freshly fabricated attributes, read echoes persisted state,
50 * and delete is a no-op.
51 */
52export const devProvider = <R extends ResourceLike>(
53 resource: ResourceClass<R>,
54 stables: Extract<keyof R["Attributes"], string>[],
55 attrs: (input: {
56 id: string;
57 news: DevRecord;
58 output?: DevRecord;
59 }) => DevRecord,
60) =>
61 Provider.succeed(resource, {
62 stables,
63 list: () => Effect.succeed([]),
64 diff: Effect.fn(function* () {
65 return { action: "update" } as const;
66 }),
67 read: Effect.fn(function* ({ output }) {
68 return output;
69 }),
70 reconcile: Effect.fn(function* ({ id, news, output }) {
71 const newsRecord = isRecord(news) ? news : {};
72 const outputRecord = isRecord(output) ? output : undefined;
73 return {
74 ...outputRecord,
75 ...attrs({ id, news: newsRecord, output: outputRecord }),
76 } as R["Attributes"];
77 }),
78 delete: Effect.fn(function* () {}),
79 });