esoul-sdk 0.3.0 → 0.6.0

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.
Files changed (46) hide show
  1. package/README.md +135 -24
  2. package/dist/audience.d.ts +103 -0
  3. package/dist/audience.js +142 -0
  4. package/dist/bindings.d.ts +164 -0
  5. package/dist/bindings.js +163 -0
  6. package/dist/db/client-core.d.ts +154 -0
  7. package/dist/db/client-core.js +274 -0
  8. package/dist/db/compile-rules.d.ts +199 -0
  9. package/dist/db/compile-rules.js +390 -0
  10. package/dist/db/memory-client.d.ts +136 -0
  11. package/dist/db/memory-client.js +323 -0
  12. package/dist/db/schema-gen.d.ts +103 -0
  13. package/dist/db/schema-gen.js +329 -0
  14. package/dist/helpers.d.ts +67 -0
  15. package/dist/helpers.js +125 -8
  16. package/dist/index.d.ts +24 -0
  17. package/dist/index.js +22 -0
  18. package/dist/manifest.d.ts +445 -13
  19. package/dist/manifest.js +211 -5
  20. package/dist/react.d.ts +29 -0
  21. package/dist/react.js +10 -0
  22. package/dist/roles.d.ts +43 -0
  23. package/dist/roles.js +56 -0
  24. package/dist/server.d.ts +165 -0
  25. package/dist/server.js +80 -0
  26. package/dist/testing/db.d.ts +69 -0
  27. package/dist/testing/db.js +94 -0
  28. package/dist/testing/index.d.ts +14 -0
  29. package/dist/testing/index.js +9 -0
  30. package/dist/testing/ops.d.ts +84 -0
  31. package/dist/testing/ops.js +76 -0
  32. package/dist/types.d.ts +22 -1
  33. package/docs/04-tools.md +5 -2
  34. package/docs/05-ui.md +30 -0
  35. package/docs/06-server.md +49 -0
  36. package/docs/07-background-tasks.md +29 -3
  37. package/docs/10-testing.md +18 -0
  38. package/docs/12-rules.md +3 -2
  39. package/docs/13-people-and-access.md +148 -0
  40. package/docs/14-database.md +115 -0
  41. package/docs/15-realtime.md +88 -0
  42. package/docs/16-bindings.md +79 -0
  43. package/llms-full.txt +715 -31
  44. package/llms.txt +4 -0
  45. package/package.json +7 -3
  46. package/schemas/plugin.schema.json +323 -9
@@ -0,0 +1,136 @@
1
+ /**
2
+ * THE IN-MEMORY CLIENT — an author's unit tests, and the workbench box.
3
+ *
4
+ * Storage is an array per model. Every DECISION — which keys may be filtered,
5
+ * what scope means, who may write which fields, what a new row is stamped
6
+ * with, when a ref crosses scope — comes from `./client-core`, which the
7
+ * Prisma client shares. What is left here is honest storage: evaluate a
8
+ * filter over JS values, sort, page, mutate arrays, snapshot for a rollback.
9
+ *
10
+ * The one behaviour that is this client's alone: a sealed field is stored in
11
+ * clear (there is nothing to protect in a test's memory) and BLANKED on read
12
+ * for anyone who may not unseal it. Production seals at rest and unseals for
13
+ * the same set of readers; the differential test holds the two to the same
14
+ * visible result.
15
+ */
16
+ import { type CompiledRules, type RuleViewer } from "./compile-rules.js";
17
+ import { type Refuse, type Row } from "./client-core.js";
18
+ export type { Refuse, RefuseCode, Row } from "./client-core.js";
19
+ export interface FindManyArgs {
20
+ where?: Record<string, unknown>;
21
+ orderBy?: Record<string, "asc" | "desc"> | Record<string, "asc" | "desc">[];
22
+ take?: number;
23
+ skip?: number;
24
+ cursor?: {
25
+ id: string;
26
+ };
27
+ includeDeleted?: boolean;
28
+ }
29
+ export interface AggregateArgs {
30
+ where?: Record<string, unknown>;
31
+ _count?: true;
32
+ _sum?: Record<string, true>;
33
+ _avg?: Record<string, true>;
34
+ _min?: Record<string, true>;
35
+ _max?: Record<string, true>;
36
+ }
37
+ export interface GroupByArgs extends AggregateArgs {
38
+ by: string[];
39
+ }
40
+ export interface Collection {
41
+ findMany(args?: FindManyArgs): Promise<Row[]>;
42
+ findFirst(args?: FindManyArgs): Promise<Row | null>;
43
+ findUnique(args: {
44
+ where: {
45
+ id: string;
46
+ };
47
+ includeDeleted?: boolean;
48
+ }): Promise<Row | null>;
49
+ count(args?: {
50
+ where?: Record<string, unknown>;
51
+ includeDeleted?: boolean;
52
+ }): Promise<number>;
53
+ aggregate(args?: AggregateArgs): Promise<Record<string, unknown>>;
54
+ groupBy(args: GroupByArgs): Promise<Record<string, unknown>[]>;
55
+ create(args: {
56
+ data: Row;
57
+ }): Promise<Row>;
58
+ createMany(args: {
59
+ data: Row[];
60
+ }): Promise<{
61
+ count: number;
62
+ }>;
63
+ update(args: {
64
+ where: {
65
+ id: string;
66
+ };
67
+ data: Row;
68
+ }): Promise<Row>;
69
+ updateMany(args: {
70
+ where?: Record<string, unknown>;
71
+ data: Row;
72
+ }): Promise<{
73
+ count: number;
74
+ }>;
75
+ upsert(args: {
76
+ where: {
77
+ id: string;
78
+ };
79
+ create: Row;
80
+ update: Row;
81
+ }): Promise<Row>;
82
+ delete(args: {
83
+ where: {
84
+ id: string;
85
+ };
86
+ }): Promise<Row>;
87
+ deleteMany(args?: {
88
+ where?: Record<string, unknown>;
89
+ }): Promise<{
90
+ count: number;
91
+ }>;
92
+ }
93
+ export interface PluginDbClient {
94
+ [model: string]: unknown;
95
+ }
96
+ /** Rows per model name. Shared by every client built over one store. */
97
+ export interface MemoryStore {
98
+ rows: Record<string, Row[]>;
99
+ seq: number;
100
+ }
101
+ export declare function createStore(rules: CompiledRules): MemoryStore;
102
+ export interface MemoryDbOptions {
103
+ rules: CompiledRules;
104
+ viewer: RuleViewer;
105
+ scope: {
106
+ workspaceId: string;
107
+ nodeId: string;
108
+ };
109
+ store?: MemoryStore;
110
+ /** Read across instances — owner-only / own-rows-only, never for writes. */
111
+ across?: "owned-instances" | "my-rows";
112
+ /** Workspaces this viewer owns, resolved by the PLATFORM, never by the app. */
113
+ ownedWorkspaceIds?: string[];
114
+ /** True when another app is reading this one through a binding. */
115
+ viaBinding?: boolean;
116
+ refuse?: Refuse;
117
+ now?: () => Date;
118
+ newId?: () => string;
119
+ }
120
+ export interface MemoryDb {
121
+ /**
122
+ * Model collections, keyed camelCase: `db.order`, `db.product`.
123
+ *
124
+ * `any` until the generator emits a per-app `.esoul/db.d.ts` (S5), which is
125
+ * what makes `where: { bogus: 1 }` a compile error. Typing it `unknown` here
126
+ * would only mean every author writes a cast in every test.
127
+ */
128
+ [model: string]: any;
129
+ /** The same store seen as somebody else — how a test checks isolation. */
130
+ as(viewer: RuleViewer, opts?: Partial<MemoryDbOptions>): MemoryDb;
131
+ $transaction<T>(fn: (tx: MemoryDb) => Promise<T>): Promise<T>;
132
+ $store: MemoryStore;
133
+ }
134
+ export declare function createMemoryDb(options: MemoryDbOptions): MemoryDb;
135
+ /** Convenience: compile a manifest and hand back a client over a fresh store. */
136
+ export declare function collectionKeys(rules: CompiledRules): string[];
@@ -0,0 +1,323 @@
1
+ /**
2
+ * THE IN-MEMORY CLIENT — an author's unit tests, and the workbench box.
3
+ *
4
+ * Storage is an array per model. Every DECISION — which keys may be filtered,
5
+ * what scope means, who may write which fields, what a new row is stamped
6
+ * with, when a ref crosses scope — comes from `./client-core`, which the
7
+ * Prisma client shares. What is left here is honest storage: evaluate a
8
+ * filter over JS values, sort, page, mutate arrays, snapshot for a rollback.
9
+ *
10
+ * The one behaviour that is this client's alone: a sealed field is stored in
11
+ * clear (there is nothing to protect in a test's memory) and BLANKED on read
12
+ * for anyone who may not unseal it. Production seals at rest and unseals for
13
+ * the same set of readers; the differential test holds the two to the same
14
+ * visible result.
15
+ */
16
+ import { camelKey } from "./compile-rules.js";
17
+ import { assertFilterShape, assertFilterable, assertIncludeDeleted, assertNoInjectedKeys, assertRefTargets, assertUnique, checkWritableData, declaredValues, defaultRefuse, mayUnseal, orderTerms, ownerFilter, pageWindow, readPlan, scopeMatches, stampNewRow, writeGate, } from "./client-core.js";
18
+ export function createStore(rules) {
19
+ const rows = {};
20
+ for (const name of Object.keys(rules.models))
21
+ rows[name] = [];
22
+ return { rows, seq: 0 };
23
+ }
24
+ /* ──────────────────────────── the client ──────────────────────────────── */
25
+ export function createMemoryDb(options) {
26
+ const { rules, viewer, scope, across, ownedWorkspaceIds = [], viaBinding = false, refuse = defaultRefuse, } = options;
27
+ const store = options.store ?? createStore(rules);
28
+ const now = options.now ?? (() => new Date());
29
+ let idSeq = 0;
30
+ const newId = options.newId ?? (() => `row_${++idSeq}_${Math.random().toString(36).slice(2, 8)}`);
31
+ const db = {};
32
+ for (const model of Object.values(rules.models)) {
33
+ db[model.key] = makeCollection(model);
34
+ }
35
+ db.as = (v, opts) => createMemoryDb({ ...options, ...opts, viewer: v, store });
36
+ db.$store = store;
37
+ db.$transaction = async (fn) => {
38
+ // A snapshot restored on throw. Enough to prove all-or-nothing to an
39
+ // author; the real atomicity is the database's.
40
+ const snapshot = JSON.parse(JSON.stringify(store.rows));
41
+ try {
42
+ return await fn(db);
43
+ }
44
+ catch (err) {
45
+ store.rows = snapshot;
46
+ throw err;
47
+ }
48
+ };
49
+ return db;
50
+ /* ─────────────────────────── helpers ────────────────────────────── */
51
+ function coreFor(model) {
52
+ return { rules, model, viewer, scope, refuse, across, ownedWorkspaceIds, viaBinding };
53
+ }
54
+ /** Evaluate one `where` condition over a JS value — this client's half of filtering. */
55
+ function matchOne(value, cond) {
56
+ if (cond !== null && typeof cond === "object" && !(cond instanceof Date) && !Array.isArray(cond)) {
57
+ const c = cond;
58
+ for (const [op, operand] of Object.entries(c)) {
59
+ switch (op) {
60
+ case "in":
61
+ if (!Array.isArray(operand) || !operand.includes(value))
62
+ return false;
63
+ break;
64
+ case "notIn":
65
+ if (Array.isArray(operand) && operand.includes(value))
66
+ return false;
67
+ break;
68
+ case "not":
69
+ if (value === operand)
70
+ return false;
71
+ break;
72
+ case "gt":
73
+ if (!(cmp(value, operand) > 0))
74
+ return false;
75
+ break;
76
+ case "gte":
77
+ if (!(cmp(value, operand) >= 0))
78
+ return false;
79
+ break;
80
+ case "lt":
81
+ if (!(cmp(value, operand) < 0))
82
+ return false;
83
+ break;
84
+ case "lte":
85
+ if (!(cmp(value, operand) <= 0))
86
+ return false;
87
+ break;
88
+ case "contains":
89
+ if (typeof value !== "string" || !value.includes(String(operand)))
90
+ return false;
91
+ break;
92
+ default:
93
+ // Unreachable: `assertFilterShape` refused unknown operators first.
94
+ return false;
95
+ }
96
+ }
97
+ return true;
98
+ }
99
+ return value === cond;
100
+ }
101
+ function cmp(a, b) {
102
+ const av = a instanceof Date ? a.getTime() : a;
103
+ const bv = b instanceof Date ? b.getTime() : b;
104
+ if (typeof av === "number" && typeof bv === "number")
105
+ return av - bv;
106
+ return String(av).localeCompare(String(bv));
107
+ }
108
+ /**
109
+ * Every read funnels through here: the caller's keys are validated, the read
110
+ * plan decides visibility, then scope → owner filter → soft-delete → the
111
+ * caller's own `where`. A row the caller may not see never reaches them, and
112
+ * never produces a refusal either — that is the whole point.
113
+ */
114
+ function visibleRows(c, args) {
115
+ const where = args?.where;
116
+ assertNoInjectedKeys(c, where, "where");
117
+ if (where)
118
+ assertFilterable(c, Object.keys(where), "where");
119
+ assertFilterShape(c, where);
120
+ assertIncludeDeleted(c, args?.includeDeleted);
121
+ const plan = readPlan(c);
122
+ if (!plan.visible)
123
+ return [];
124
+ let rows = (store.rows[c.model.name] ?? []).filter((r) => scopeMatches(c, r));
125
+ const own = ownerFilter(c, plan);
126
+ if (own)
127
+ rows = rows.filter((r) => r.ownerId && own.includes(String(r.ownerId)));
128
+ if (!args?.includeDeleted)
129
+ rows = rows.filter((r) => !r.deletedAt);
130
+ if (where)
131
+ rows = rows.filter((r) => Object.entries(where).every(([k, v]) => matchOne(r[k], v)));
132
+ return rows;
133
+ }
134
+ function sortRows(c, rows, orderBy) {
135
+ const terms = orderTerms(c, orderBy);
136
+ if (!terms.length)
137
+ return rows;
138
+ return [...rows].sort((a, b) => {
139
+ for (const t of terms) {
140
+ for (const [field, dir] of Object.entries(t)) {
141
+ const d = cmp(a[field], b[field]);
142
+ if (d !== 0)
143
+ return dir === "desc" ? -d : d;
144
+ }
145
+ }
146
+ return 0;
147
+ });
148
+ }
149
+ /** Sealed fields are stored in clear here and BLANKED for anyone who may not unseal. */
150
+ function project(c, row) {
151
+ const out = { ...row };
152
+ if (!mayUnseal(c, row))
153
+ for (const f of c.model.sealed)
154
+ out[f] = null;
155
+ return out;
156
+ }
157
+ function page(c, rows, args) {
158
+ const { take, skip } = pageWindow(c, args);
159
+ let out = sortRows(c, rows, args?.orderBy);
160
+ if (args?.cursor) {
161
+ const at = out.findIndex((r) => r.id === args.cursor.id);
162
+ out = at >= 0 ? out.slice(at + 1) : [];
163
+ }
164
+ if (skip)
165
+ out = out.slice(skip);
166
+ return out.slice(0, take).map((r) => project(c, r));
167
+ }
168
+ /** The row a ref names, looked up RAW (rules do not apply to a scope check). */
169
+ async function lookupRaw(refModel, id) {
170
+ return (store.rows[refModel.name] ?? []).find((r) => r.id === id) ?? null;
171
+ }
172
+ function makeCollection(model) {
173
+ const c = coreFor(model);
174
+ const rowsOf = () => store.rows[model.name] ?? (store.rows[model.name] = []);
175
+ const bad = (detail) => refuse("invalid", "invalid", detail);
176
+ /** Update/delete find their target as a FILTER: not yours ⇒ not found, never "forbidden". */
177
+ const liveTarget = (id, ownerIn) => rowsOf().find((r) => r.id === id &&
178
+ scopeMatches(c, r) &&
179
+ !r.deletedAt &&
180
+ (!ownerIn || (r.ownerId && ownerIn.includes(String(r.ownerId)))));
181
+ return {
182
+ async findMany(args) {
183
+ return page(c, visibleRows(c, args), args);
184
+ },
185
+ async findFirst(args) {
186
+ return page(c, visibleRows(c, args), { ...args, take: 1 })[0] ?? null;
187
+ },
188
+ async findUnique(args) {
189
+ const rows = visibleRows(c, { where: { id: args.where.id }, includeDeleted: args.includeDeleted });
190
+ return rows[0] ? project(c, rows[0]) : null;
191
+ },
192
+ async count(args) {
193
+ return visibleRows(c, args).length;
194
+ },
195
+ async aggregate(args) {
196
+ const rows = visibleRows(c, args);
197
+ const out = {};
198
+ if (args?._count)
199
+ out._count = rows.length;
200
+ const reduce = (spec, fn) => {
201
+ if (!spec)
202
+ return undefined;
203
+ const res = {};
204
+ for (const f of Object.keys(spec)) {
205
+ if (!model.fields[f])
206
+ bad(`${model.name}.aggregate: "${f}" is not a declared field`);
207
+ const nums = rows.map((r) => Number(r[f])).filter((n) => !Number.isNaN(n));
208
+ res[f] = nums.length ? fn(nums) : null;
209
+ }
210
+ return res;
211
+ };
212
+ const sum = reduce(args?._sum, (n) => n.reduce((a, b) => a + b, 0));
213
+ const avg = reduce(args?._avg, (n) => n.reduce((a, b) => a + b, 0) / n.length);
214
+ const min = reduce(args?._min, (n) => Math.min(...n));
215
+ const max = reduce(args?._max, (n) => Math.max(...n));
216
+ if (sum)
217
+ out._sum = sum;
218
+ if (avg)
219
+ out._avg = avg;
220
+ if (min)
221
+ out._min = min;
222
+ if (max)
223
+ out._max = max;
224
+ return out;
225
+ },
226
+ async groupBy(args) {
227
+ assertFilterable(c, args.by, "groupBy.by");
228
+ const rows = visibleRows(c, args);
229
+ const groups = new Map();
230
+ for (const r of rows) {
231
+ const key = JSON.stringify(args.by.map((f) => r[f]));
232
+ (groups.get(key) ?? groups.set(key, []).get(key)).push(r);
233
+ }
234
+ return [...groups.entries()].map(([key, rs]) => {
235
+ const values = JSON.parse(key);
236
+ const out = {};
237
+ args.by.forEach((f, i) => (out[f] = values[i]));
238
+ if (args._count)
239
+ out._count = rs.length;
240
+ for (const f of Object.keys(args._sum ?? {})) {
241
+ out._sum = { ...out._sum, [f]: rs.reduce((a, r) => a + Number(r[f] ?? 0), 0) };
242
+ }
243
+ return out;
244
+ });
245
+ },
246
+ async create(args) {
247
+ const plan = writeGate(c, "create");
248
+ checkWritableData(c, args.data, plan.fields, "create");
249
+ const values = declaredValues(c, args.data);
250
+ await assertRefTargets(c, values, lookupRaw);
251
+ const row = { ...stampNewRow(c, now(), newId()), ...values };
252
+ await assertUnique(c, row, async (group, vals) => rowsOf().some((r) => scopeMatches(c, r) && !r.deletedAt && group.every((col, i) => r[col] === vals[i])));
253
+ rowsOf().push(row);
254
+ store.seq += 1;
255
+ return project(c, row);
256
+ },
257
+ async createMany(args) {
258
+ let count = 0;
259
+ for (const data of args.data) {
260
+ await this.create({ data });
261
+ count += 1;
262
+ }
263
+ return { count };
264
+ },
265
+ async update(args) {
266
+ const plan = writeGate(c, "update");
267
+ checkWritableData(c, args.data, plan.fields, "update");
268
+ await assertRefTargets(c, args.data, lookupRaw);
269
+ const target = liveTarget(args.where.id, plan.ownerIn);
270
+ if (!target)
271
+ throw bad(`${model.name}.update: no row ${args.where.id}`);
272
+ Object.assign(target, args.data, { updatedAt: now() });
273
+ store.seq += 1;
274
+ return project(c, target);
275
+ },
276
+ async updateMany(args) {
277
+ const plan = writeGate(c, "update");
278
+ checkWritableData(c, args.data, plan.fields, "updateMany");
279
+ await assertRefTargets(c, args.data, lookupRaw);
280
+ const targets = visibleRows(c, { where: args.where }).filter((r) => !plan.ownerIn || (r.ownerId && plan.ownerIn.includes(String(r.ownerId))));
281
+ for (const t of targets)
282
+ Object.assign(t, args.data, { updatedAt: now() });
283
+ store.seq += targets.length;
284
+ return { count: targets.length };
285
+ },
286
+ async upsert(args) {
287
+ const existing = await this.findUnique({ where: args.where });
288
+ if (existing)
289
+ return this.update({ where: args.where, data: args.update });
290
+ return this.create({ data: args.create });
291
+ },
292
+ async delete(args) {
293
+ const plan = writeGate(c, "delete");
294
+ const target = liveTarget(args.where.id, plan.ownerIn);
295
+ if (!target)
296
+ throw bad(`${model.name}.delete: no row ${args.where.id}`);
297
+ // A soft delete IS a modification: `updatedAt` moves. (The database
298
+ // would move it anyway through `@updatedAt`; the differential test
299
+ // caught the two clients disagreeing here, so both now say so.)
300
+ const at = now();
301
+ target.deletedAt = at;
302
+ target.updatedAt = at;
303
+ store.seq += 1;
304
+ return project(c, target);
305
+ },
306
+ async deleteMany(args) {
307
+ const plan = writeGate(c, "delete");
308
+ const targets = visibleRows(c, { where: args?.where }).filter((r) => !plan.ownerIn || (r.ownerId && plan.ownerIn.includes(String(r.ownerId))));
309
+ const at = now();
310
+ for (const t of targets) {
311
+ t.deletedAt = at;
312
+ t.updatedAt = at;
313
+ }
314
+ store.seq += targets.length;
315
+ return { count: targets.length };
316
+ },
317
+ };
318
+ }
319
+ }
320
+ /** Convenience: compile a manifest and hand back a client over a fresh store. */
321
+ export function collectionKeys(rules) {
322
+ return Object.keys(rules.models).map(camelKey);
323
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * THE GENERATORS — a compiled `db` block turned into the two texts the
3
+ * platform needs from it: a Prisma schema fragment (the real tables) and a
4
+ * `.d.ts` (what the author's editor knows).
5
+ *
6
+ * Both are PURE and byte-deterministic: same `CompiledRules` in, same bytes
7
+ * out, every time. That is not tidiness. The fragment is committed for a
8
+ * vendored app and diffed on every install of a fetched one; the typings are
9
+ * what an author's `tsc` runs against. A generator that reordered a field or
10
+ * renamed an index between runs would produce a migration for nothing and a
11
+ * red build for nothing. Nothing here reads a clock, a random source, or the
12
+ * filesystem — it runs unchanged inside a workbench box.
13
+ *
14
+ * Decisions that are easy to undo by accident, so they are written down:
15
+ *
16
+ * - **A sealed field is always `String`.** Ciphertext at rest, whatever the
17
+ * declared type. The declared type governs validation on write and the
18
+ * parse on read; the column never sees plaintext.
19
+ * - **A `ref` is a `String`, not a relation.** It holds the target row's id.
20
+ * No Prisma `@relation`: the target's SCOPE (same instance / same owner) is
21
+ * what must be checked, and that is a client-side rule (spec Part C, S3),
22
+ * not a foreign key — a foreign key would happily point at another shop's
23
+ * row.
24
+ * - **A declared `unique` group is a plain `@@index`, not `@@unique`.**
25
+ * Deletes are soft. A database-level unique would refuse re-creating a
26
+ * SKU after its row was soft-deleted, and Prisma cannot express a partial
27
+ * unique (`WHERE deletedAt IS NULL`). The client enforces uniqueness among
28
+ * live rows. That leaves a race window between two concurrent creates — a
29
+ * known v1 limitation, to be closed by a partial unique index emitted in the
30
+ * DDL step, where raw SQL can say what Prisma's DSL cannot.
31
+ * - **Indexes lead with the scope column.** Every query the client issues is
32
+ * narrowed to an instance (or workspace, or owner) FIRST, so every declared
33
+ * index is emitted as `[scopeColumn, ...group]` — an index that does not
34
+ * start with the scope column is one the planner cannot use for the query
35
+ * the platform actually runs.
36
+ */
37
+ import type { CompiledRules } from "./compile-rules.js";
38
+ /** `ShipAddress` → `ship_address`; `Order` → `order`. */
39
+ export declare function snakeCase(name: string): string;
40
+ /** The Prisma model name: `plugin_shop_min__Order`. */
41
+ export declare function prismaModelName(applicationType: string, model: string): string;
42
+ /** The table it maps to: `plugin_shop_min__order`. */
43
+ export declare function prismaTableName(applicationType: string, model: string): string;
44
+ /**
45
+ * The Prisma schema fragment for one app — every model, platform columns
46
+ * first, scope-led indexes, mapped to a prefixed table. No datasource and no
47
+ * generator: this is a FRAGMENT that joins the platform's schema folder.
48
+ */
49
+ export declare function prismaFragment(rules: CompiledRules, opts: {
50
+ applicationType: string;
51
+ }): string;
52
+ /**
53
+ * The `.esoul/db.d.ts` for one app: a `<Model>Row` and `<Model>Create` per
54
+ * model, a `<Model>Filterable` union that is exactly the compiled `filterable`
55
+ * list (so `where` and `orderBy` on an unindexed field are compile errors —
56
+ * the runtime rule, landed in the editor), and one `Collection` shape mirroring
57
+ * the memory client's method surface. Everything an author calls is typed with
58
+ * their own names; nothing here knows the table prefix.
59
+ */
60
+ export declare function dbTypings(rules: CompiledRules, opts: {
61
+ typeName: string;
62
+ }): string;
63
+ /** The compiled artefact as the bytes `.esoul/rules.json` holds. */
64
+ export declare function rulesJson(rules: CompiledRules): string;
65
+ /**
66
+ * The SQL an app's tables need, emitted by us and not by Prisma's CLI: the
67
+ * install job runs where there is no CLI (an Inngest step on Vercel), and an
68
+ * UPDATE needs the difference between what is declared and what is LIVE, which
69
+ * only an introspection of the database can answer (the ledger records what
70
+ * was applied, not what the previous fragment was). So the declaration
71
+ * becomes table specs here; `lib/plugin-db/introspect.ts` reads the live
72
+ * tables and plans the additive difference; and a golden test pins these
73
+ * statements to Prisma's own `migrate diff` for the reference shop, so what
74
+ * we emit is what Prisma would.
75
+ */
76
+ export interface TableColumn {
77
+ name: string;
78
+ /** Canonical Postgres type as Prisma spells it: TEXT, INTEGER, DOUBLE PRECISION, BOOLEAN, TIMESTAMP(3), JSONB, or one of those + `[]`. */
79
+ type: string;
80
+ nullable: boolean;
81
+ /** A SQL literal (`'new'`, `350`, `true`, `CURRENT_TIMESTAMP`) or null. */
82
+ default: string | null;
83
+ }
84
+ export interface TableIndex {
85
+ name: string;
86
+ columns: string[];
87
+ }
88
+ export interface TableSpec {
89
+ name: string;
90
+ columns: TableColumn[];
91
+ indexes: TableIndex[];
92
+ }
93
+ /** Prisma's index name: `<table>_<col>_<col>_idx`. */
94
+ export declare function indexName(table: string, columns: string[]): string;
95
+ /** Every table an app declares, as columns and indexes — the input to the planner and the emitter. */
96
+ export declare function tableSpecs(rules: CompiledRules, opts: {
97
+ applicationType: string;
98
+ }): TableSpec[];
99
+ export declare function createTableSql(t: TableSpec): string;
100
+ export declare function createIndexSql(table: string, idx: TableIndex): string;
101
+ export declare function addColumnSql(table: string, c: TableColumn): string;
102
+ /** The whole script for a fresh install: tables first, then every index — Prisma's order. */
103
+ export declare function ddlStatements(specs: TableSpec[]): string[];