@opensaas/stack-core 0.20.1 → 0.22.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 (136) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +334 -0
  3. package/CLAUDE.md +29 -11
  4. package/dist/access/access-filter.d.ts +29 -0
  5. package/dist/access/access-filter.d.ts.map +1 -0
  6. package/dist/access/access-filter.js +68 -0
  7. package/dist/access/access-filter.js.map +1 -0
  8. package/dist/access/engine.d.ts +15 -48
  9. package/dist/access/engine.d.ts.map +1 -1
  10. package/dist/access/engine.js +14 -280
  11. package/dist/access/engine.js.map +1 -1
  12. package/dist/access/field-access.d.ts +44 -0
  13. package/dist/access/field-access.d.ts.map +1 -0
  14. package/dist/access/field-access.js +123 -0
  15. package/dist/access/field-access.js.map +1 -0
  16. package/dist/access/field-access.test.d.ts +2 -0
  17. package/dist/access/field-access.test.d.ts.map +1 -0
  18. package/dist/access/{engine.test.js → field-access.test.js} +2 -2
  19. package/dist/access/field-access.test.js.map +1 -0
  20. package/dist/access/field-visibility.d.ts +13 -0
  21. package/dist/access/field-visibility.d.ts.map +1 -0
  22. package/dist/access/field-visibility.js +178 -0
  23. package/dist/access/field-visibility.js.map +1 -0
  24. package/dist/access/index.d.ts +4 -1
  25. package/dist/access/index.d.ts.map +1 -1
  26. package/dist/access/index.js +8 -1
  27. package/dist/access/index.js.map +1 -1
  28. package/dist/access/multi-column-read-write.test.d.ts +2 -0
  29. package/dist/access/multi-column-read-write.test.d.ts.map +1 -0
  30. package/dist/access/multi-column-read-write.test.js +149 -0
  31. package/dist/access/multi-column-read-write.test.js.map +1 -0
  32. package/dist/config/index.d.ts +1 -1
  33. package/dist/config/index.d.ts.map +1 -1
  34. package/dist/config/types.d.ts +334 -5
  35. package/dist/config/types.d.ts.map +1 -1
  36. package/dist/context/hook-pipeline.d.ts +49 -0
  37. package/dist/context/hook-pipeline.d.ts.map +1 -0
  38. package/dist/context/hook-pipeline.js +75 -0
  39. package/dist/context/hook-pipeline.js.map +1 -0
  40. package/dist/context/index.d.ts.map +1 -1
  41. package/dist/context/index.js +30 -462
  42. package/dist/context/index.js.map +1 -1
  43. package/dist/context/nested-operations.d.ts.map +1 -1
  44. package/dist/context/nested-operations.js +72 -68
  45. package/dist/context/nested-operations.js.map +1 -1
  46. package/dist/context/write-pipeline.d.ts +158 -0
  47. package/dist/context/write-pipeline.d.ts.map +1 -0
  48. package/dist/context/write-pipeline.js +306 -0
  49. package/dist/context/write-pipeline.js.map +1 -0
  50. package/dist/extend.d.ts +3 -0
  51. package/dist/extend.d.ts.map +1 -0
  52. package/dist/extend.js +10 -0
  53. package/dist/extend.js.map +1 -0
  54. package/dist/fields/format-prisma-default.d.ts +35 -0
  55. package/dist/fields/format-prisma-default.d.ts.map +1 -0
  56. package/dist/fields/format-prisma-default.js +52 -0
  57. package/dist/fields/format-prisma-default.js.map +1 -0
  58. package/dist/fields/format-prisma-default.test.d.ts +2 -0
  59. package/dist/fields/format-prisma-default.test.d.ts.map +1 -0
  60. package/dist/fields/format-prisma-default.test.js +54 -0
  61. package/dist/fields/format-prisma-default.test.js.map +1 -0
  62. package/dist/fields/index.d.ts +1 -0
  63. package/dist/fields/index.d.ts.map +1 -1
  64. package/dist/fields/index.js +267 -18
  65. package/dist/fields/index.js.map +1 -1
  66. package/dist/fields/select.test.js +85 -0
  67. package/dist/fields/select.test.js.map +1 -1
  68. package/dist/fields/text-keystone-compat.test.d.ts +2 -0
  69. package/dist/fields/text-keystone-compat.test.d.ts.map +1 -0
  70. package/dist/fields/text-keystone-compat.test.js +93 -0
  71. package/dist/fields/text-keystone-compat.test.js.map +1 -0
  72. package/dist/hooks/index.d.ts +20 -0
  73. package/dist/hooks/index.d.ts.map +1 -1
  74. package/dist/hooks/index.js +246 -0
  75. package/dist/hooks/index.js.map +1 -1
  76. package/dist/index.d.ts +6 -8
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +25 -9
  79. package/dist/index.js.map +1 -1
  80. package/dist/index.test.d.ts +2 -0
  81. package/dist/index.test.d.ts.map +1 -0
  82. package/dist/index.test.js +33 -0
  83. package/dist/index.test.js.map +1 -0
  84. package/dist/internal.d.ts +8 -0
  85. package/dist/internal.d.ts.map +1 -0
  86. package/dist/internal.js +16 -0
  87. package/dist/internal.js.map +1 -0
  88. package/dist/mcp/handler.js +0 -1
  89. package/dist/mcp/handler.js.map +1 -1
  90. package/dist/validation/field-config.d.ts +55 -0
  91. package/dist/validation/field-config.d.ts.map +1 -0
  92. package/dist/validation/field-config.js +100 -0
  93. package/dist/validation/field-config.js.map +1 -0
  94. package/dist/validation/field-config.test.d.ts +2 -0
  95. package/dist/validation/field-config.test.d.ts.map +1 -0
  96. package/dist/validation/field-config.test.js +159 -0
  97. package/dist/validation/field-config.test.js.map +1 -0
  98. package/package.json +11 -3
  99. package/src/access/access-filter.ts +97 -0
  100. package/src/access/engine.ts +13 -396
  101. package/src/access/{engine.test.ts → field-access.test.ts} +1 -1
  102. package/src/access/field-access.ts +159 -0
  103. package/src/access/field-visibility.ts +269 -0
  104. package/src/access/index.ts +7 -4
  105. package/src/access/multi-column-read-write.test.ts +255 -0
  106. package/src/config/index.ts +3 -0
  107. package/src/config/types.ts +342 -4
  108. package/src/context/hook-pipeline.ts +160 -0
  109. package/src/context/index.ts +29 -667
  110. package/src/context/nested-operations.ts +142 -111
  111. package/src/context/write-pipeline.ts +543 -0
  112. package/src/extend.ts +19 -0
  113. package/src/fields/format-prisma-default.test.ts +64 -0
  114. package/src/fields/format-prisma-default.ts +67 -0
  115. package/src/fields/index.ts +375 -20
  116. package/src/fields/select.test.ts +99 -0
  117. package/src/fields/text-keystone-compat.test.ts +126 -0
  118. package/src/hooks/index.ts +270 -0
  119. package/src/index.test.ts +50 -0
  120. package/src/index.ts +35 -82
  121. package/src/internal.ts +49 -0
  122. package/src/mcp/handler.ts +0 -2
  123. package/src/validation/field-config.test.ts +199 -0
  124. package/src/validation/field-config.ts +145 -0
  125. package/tests/access-relationships.test.ts +4 -4
  126. package/tests/access.test.ts +1 -1
  127. package/tests/field-hooks.test.ts +410 -0
  128. package/tests/field-types.test.ts +1 -1
  129. package/tests/hook-pipeline.test.ts +233 -0
  130. package/tests/nested-operation-registry.test.ts +206 -0
  131. package/tests/write-pipeline.test.ts +588 -0
  132. package/tsconfig.tsbuildinfo +1 -1
  133. package/vitest.config.ts +43 -1
  134. package/dist/access/engine.test.d.ts +0 -2
  135. package/dist/access/engine.test.d.ts.map +0 -1
  136. package/dist/access/engine.test.js.map +0 -1
@@ -0,0 +1,306 @@
1
+ import { checkAccess, mergeFilters, filterReadableFields, filterWritableFields, } from '../access/index.js';
2
+ import { executeValidate, executeBeforeOperation, executeAfterOperation, executeFieldValidateHooks, executeFieldBeforeOperationHooks, executeFieldAfterOperationHooks, ValidationError, } from '../hooks/index.js';
3
+ import { hookPipeline } from './hook-pipeline.js';
4
+ import { processNestedOperations } from './nested-operations.js';
5
+ import { getDbKey } from '../lib/case-utils.js';
6
+ /**
7
+ * Resolve the dynamic Prisma model for a list. Model names are generated at
8
+ * runtime from list keys, which is the one place a cast is unavoidable — it is
9
+ * kept localized here (mirroring the existing pattern in `context/index.ts`).
10
+ */
11
+ function getModel(prisma, listName) {
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- model names are generated at runtime
13
+ return prisma[getDbKey(listName)];
14
+ }
15
+ /**
16
+ * Check if a list is configured as a singleton.
17
+ */
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
19
+ function isSingletonList(listConfig) {
20
+ return !!listConfig.isSingleton;
21
+ }
22
+ /**
23
+ * Run the canonical secured write sequence once.
24
+ *
25
+ * Phase order (owned here, in one place):
26
+ * resolve target + operation-level access
27
+ * → list/field `resolveInput`
28
+ * → list/field `validate`
29
+ * → built-in field rules (`validateFieldRules`)
30
+ * → filter writable fields
31
+ * → nested operations
32
+ * → list/field `beforeOperation`
33
+ * → DB
34
+ * → list/field `afterOperation`
35
+ * → `filterReadableFields` (Field Visibility)
36
+ *
37
+ * Contract preserved exactly:
38
+ * - missing target / access denied / filter non-match → `null` (silent),
39
+ * BEFORE the DB call and BEFORE `beforeOperation`.
40
+ * - validation failure → THROW `ValidationError` (never silent).
41
+ * - sudo mode skips access checks and writable-field filtering (the strategy
42
+ * and `filterWritableFields` both honour `context._isSudo`).
43
+ * - `afterOperation` receives `originalItem` for update/delete (undefined for
44
+ * create).
45
+ * - delete returns the deleted row as-is (no Field Visibility pass), matching
46
+ * current behaviour.
47
+ */
48
+ export async function runWritePipeline(args) {
49
+ const { listName, listConfig, prisma, context, config, inputData, strategy } = args;
50
+ const { operation } = strategy;
51
+ const model = getModel(prisma, listName);
52
+ // ── Phase 1: resolve target + operation-level access ──────────────────────
53
+ // Short-circuits to `null` (silent failure) for missing target, denied
54
+ // access, or filter non-match — before any hook side effects or the DB call.
55
+ const resolution = await strategy.resolveTarget(model);
56
+ if (resolution.status === 'denied') {
57
+ return null;
58
+ }
59
+ const originalItem = resolution.originalItem;
60
+ // ── Delete path: skip input phases, run only validate/field-validate ────────
61
+ // (matches current delete behaviour exactly).
62
+ if (!strategy.runInputPhases) {
63
+ return runDeletePath({ listName, listConfig, context, originalItem, model, strategy });
64
+ }
65
+ // Only create/update reach here (delete short-circuited above). Narrow the
66
+ // operation so the field-hook helpers receive a 'create' | 'update' value.
67
+ const writeOp = operation === 'create' ? 'create' : 'update';
68
+ // `inputData` is always present for create/update (the operations that run
69
+ // input phases). Default to {} only as a defensive measure.
70
+ const input = inputData ?? {};
71
+ // ── Phases 2–4: transform + validate span (Hook Pipeline) ──────────────────
72
+ // The Hook Pipeline owns the list/field `resolveInput` → list/field `validate`
73
+ // → built-in field rules span and the `resolvedData` threading through it. It
74
+ // THROWS `ValidationError` on any validation failure (never silent).
75
+ const { resolvedData } = await hookPipeline.run({
76
+ operation: writeOp,
77
+ listName,
78
+ listConfig,
79
+ inputData: input,
80
+ item: originalItem,
81
+ context,
82
+ });
83
+ // ── Phase 5: filter writable fields (field-level access, skip if sudo) ──────
84
+ const filteredData = await filterWritableFields(resolvedData, listConfig.fields, writeOp, {
85
+ session: context.session,
86
+ item: originalItem,
87
+ context: { ...context, _isSudo: context._isSudo },
88
+ inputData: input,
89
+ });
90
+ // ── Phase 5.5: process nested relationship operations ───────────────────────
91
+ const data = await processNestedOperations(filteredData, listConfig.fields, config, { ...context, prisma }, writeOp);
92
+ // ── Phase 6: field-level beforeOperation (side effects only) ────────────────
93
+ await executeFieldBeforeOperationHooks(input, resolvedData, listConfig.fields, writeOp, context, listName, originalItem);
94
+ // ── Phase 7: list-level beforeOperation ─────────────────────────────────────
95
+ await executeBeforeOperation(listConfig.hooks, operation === 'create'
96
+ ? {
97
+ listKey: listName,
98
+ operation: 'create',
99
+ inputData: input,
100
+ resolvedData,
101
+ context,
102
+ }
103
+ : {
104
+ listKey: listName,
105
+ operation: 'update',
106
+ inputData: input,
107
+ item: originalItem,
108
+ resolvedData,
109
+ context,
110
+ });
111
+ // ── Phase 8: DB write ───────────────────────────────────────────────────────
112
+ const item = await strategy.persist(model, data);
113
+ // ── Phase 9: list-level afterOperation ──────────────────────────────────────
114
+ await executeAfterOperation(listConfig.hooks, operation === 'create'
115
+ ? {
116
+ listKey: listName,
117
+ operation: 'create',
118
+ inputData: input,
119
+ item,
120
+ resolvedData,
121
+ context,
122
+ }
123
+ : {
124
+ listKey: listName,
125
+ operation: 'update',
126
+ inputData: input,
127
+ // originalItem is the row before the update
128
+ originalItem: originalItem,
129
+ item,
130
+ resolvedData,
131
+ context,
132
+ });
133
+ // ── Phase 10: field-level afterOperation (side effects only) ────────────────
134
+ await executeFieldAfterOperationHooks(item, input, resolvedData, listConfig.fields, writeOp, context, listName, originalItem);
135
+ // ── Phase 11: Field Visibility (filter readable fields + resolveOutput) ─────
136
+ return filterReadableFields(item, listConfig.fields, {
137
+ session: context.session,
138
+ context: { ...context, _isSudo: context._isSudo },
139
+ }, config, 0, listName);
140
+ }
141
+ /**
142
+ * The delete tail of the pipeline: skips the input-shaping phases and runs only
143
+ * validate/field-validate before the DB delete, then the after-hooks. Returns
144
+ * the deleted row as-is (no Field Visibility pass) — matching current delete
145
+ * behaviour exactly.
146
+ */
147
+ async function runDeletePath(args) {
148
+ const { listName, listConfig, context, originalItem, model, strategy } = args;
149
+ const item = originalItem;
150
+ // ── Phase 3: list-level validate (delete) ──────────────────────────────────
151
+ await executeValidate(listConfig.hooks, {
152
+ listKey: listName,
153
+ operation: 'delete',
154
+ item,
155
+ context,
156
+ });
157
+ // ── Phase 3.5: field-level validate (delete) ────────────────────────────────
158
+ await executeFieldValidateHooks(undefined, undefined, listConfig.fields, 'delete', context, listName, item);
159
+ // ── Phase 6: field-level beforeOperation (delete) ───────────────────────────
160
+ await executeFieldBeforeOperationHooks({}, {}, listConfig.fields, 'delete', context, listName, item);
161
+ // ── Phase 7: list-level beforeOperation (delete) ────────────────────────────
162
+ await executeBeforeOperation(listConfig.hooks, {
163
+ listKey: listName,
164
+ operation: 'delete',
165
+ item,
166
+ context,
167
+ });
168
+ // ── Phase 8: DB delete ──────────────────────────────────────────────────────
169
+ const deleted = await strategy.persist(model, {});
170
+ // ── Phase 9: list-level afterOperation (delete) ─────────────────────────────
171
+ await executeAfterOperation(listConfig.hooks, {
172
+ listKey: listName,
173
+ operation: 'delete',
174
+ originalItem: item,
175
+ context,
176
+ });
177
+ // ── Phase 10: field-level afterOperation (delete) ───────────────────────────
178
+ await executeFieldAfterOperationHooks(deleted, undefined, undefined, listConfig.fields, 'delete', context, listName, item);
179
+ return deleted;
180
+ }
181
+ // ── Per-operation strategies ──────────────────────────────────────────────────
182
+ /**
183
+ * Create strategy.
184
+ *
185
+ * Axis 1: checks `create` access with NO existing row. Enforces the
186
+ * singleton-create constraint even under sudo. On create, an access result of
187
+ * `true` OR a filter object both proceed — there is no filter re-check.
188
+ * Axis 2: runs all input phases.
189
+ * Axis 3: `model.create({ data })`, prepending `id: 1` for singleton lists.
190
+ */
191
+ export function createWriteStrategy(listName,
192
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
193
+ listConfig, context) {
194
+ const singleton = isSingletonList(listConfig);
195
+ return {
196
+ operation: 'create',
197
+ runInputPhases: true,
198
+ async resolveTarget(model) {
199
+ // Singleton constraint is enforced even under sudo.
200
+ if (singleton) {
201
+ const existingCount = await model.count();
202
+ if (existingCount > 0) {
203
+ throw new ValidationError([`Cannot create: ${listName} is a singleton list with an existing record`], {});
204
+ }
205
+ }
206
+ if (!context._isSudo) {
207
+ const accessResult = await checkAccess(listConfig.access?.operation?.create, {
208
+ session: context.session,
209
+ context,
210
+ });
211
+ if (accessResult === false) {
212
+ return { status: 'denied' };
213
+ }
214
+ }
215
+ return { status: 'ok', originalItem: undefined };
216
+ },
217
+ async persist(model, data) {
218
+ // Singleton lists use Int @id with value always 1 (matching Keystone 6).
219
+ const createData = singleton ? { id: 1, ...data } : data;
220
+ return model.create({ data: createData });
221
+ },
222
+ };
223
+ }
224
+ /**
225
+ * Build the shared target resolution for update/delete: fetch the row (missing
226
+ * → denied), check operation-level access (false → denied), and if access
227
+ * returns a filter, re-check via `findFirst(mergeFilters(where, filter))`
228
+ * (no match → denied). An access result of `true` proceeds with no re-check.
229
+ */
230
+ function resolveExistingTarget(
231
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
232
+ listConfig, context, where, access) {
233
+ return async (model) => {
234
+ const item = await model.findUnique({ where });
235
+ if (!item) {
236
+ return { status: 'denied' };
237
+ }
238
+ if (!context._isSudo) {
239
+ const accessResult = await checkAccess(listConfig.access?.operation?.[access], {
240
+ session: context.session,
241
+ item,
242
+ context,
243
+ });
244
+ if (accessResult === false) {
245
+ return { status: 'denied' };
246
+ }
247
+ // A filter result must additionally match the target row.
248
+ if (typeof accessResult === 'object') {
249
+ const matchesFilter = await model.findFirst({
250
+ where: mergeFilters(where, accessResult) ?? {},
251
+ });
252
+ if (!matchesFilter) {
253
+ return { status: 'denied' };
254
+ }
255
+ }
256
+ }
257
+ return { status: 'ok', originalItem: item };
258
+ };
259
+ }
260
+ /**
261
+ * Update strategy.
262
+ *
263
+ * Axis 1: fetch row, check `update` access, re-check filter results.
264
+ * Axis 2: runs all input phases.
265
+ * Axis 3: `model.update({ where, data })`; afterOperation gets `originalItem`.
266
+ */
267
+ export function updateWriteStrategy(
268
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
269
+ listConfig, context, where) {
270
+ return {
271
+ operation: 'update',
272
+ runInputPhases: true,
273
+ resolveTarget: resolveExistingTarget(listConfig, context, where, 'update'),
274
+ async persist(model, data) {
275
+ return model.update({ where, data });
276
+ },
277
+ };
278
+ }
279
+ /**
280
+ * Delete strategy.
281
+ *
282
+ * Axis 1: enforce singleton constraint (even under sudo), fetch row, check
283
+ * `delete` access, re-check filter results.
284
+ * Axis 2: SKIPS input phases (runs only validate/field-validate).
285
+ * Axis 3: `model.delete({ where })`; afterOperation gets `originalItem`.
286
+ */
287
+ export function deleteWriteStrategy(listName,
288
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
289
+ listConfig, context, where) {
290
+ const resolveTarget = resolveExistingTarget(listConfig, context, where, 'delete');
291
+ return {
292
+ operation: 'delete',
293
+ runInputPhases: false,
294
+ async resolveTarget(model) {
295
+ // Singleton lists may not be deleted (enforced even under sudo).
296
+ if (isSingletonList(listConfig)) {
297
+ throw new ValidationError([`Cannot delete: ${listName} is a singleton list`], {});
298
+ }
299
+ return resolveTarget(model);
300
+ },
301
+ async persist(model) {
302
+ return model.delete({ where });
303
+ },
304
+ };
305
+ }
306
+ //# sourceMappingURL=write-pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-pipeline.js","sourceRoot":"","sources":["../../src/context/write-pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export type { Plugin, PluginContext, GeneratedFiles } from './config/index.js';
2
+ export type { BaseFieldConfig, TypeInfo, TypeDescriptor, MultiColumnPrismaResult, } from './config/index.js';
3
+ //# sourceMappingURL=extend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extend.d.ts","sourceRoot":"","sources":["../src/extend.ts"],"names":[],"mappings":"AAUA,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAG9E,YAAY,EACV,eAAe,EACf,QAAQ,EACR,cAAc,EACd,uBAAuB,GACxB,MAAM,mBAAmB,CAAA"}
package/dist/extend.js ADDED
@@ -0,0 +1,10 @@
1
+ // ───────────────────────────────────────────────────────────────
2
+ // @opensaas/stack-core/extend
3
+ //
4
+ // Authoring contracts for extending the stack: implement these when
5
+ // you build a plugin or a third-party field package. Stable, public
6
+ // API — distinct from the everyday consumer surface on the root entry
7
+ // point and from the unstable plumbing on `/internal`.
8
+ // ───────────────────────────────────────────────────────────────
9
+ export {};
10
+ //# sourceMappingURL=extend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extend.js","sourceRoot":"","sources":["../src/extend.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,8BAA8B;AAC9B,EAAE;AACF,oEAAoE;AACpE,oEAAoE;AACpE,sEAAsE;AACtE,uDAAuD;AACvD,kEAAkE"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Field types that {@link formatPrismaDefault} knows how to serialise.
3
+ *
4
+ * Kept narrow on purpose: only the scalar fields whose `defaultValue` maps to a
5
+ * Prisma `@default(...)` literal via this shared helper. Other fields (e.g.
6
+ * `checkbox`, `decimal`, `timestamp`) format their own defaults inline because
7
+ * their literal forms diverge (`@default(now())`, bare booleans, etc.).
8
+ */
9
+ export type PrismaDefaultFieldType = 'text' | 'integer' | 'json';
10
+ /**
11
+ * Serialise a field's `defaultValue` into the inner literal of a Prisma
12
+ * `@default(...)` attribute.
13
+ *
14
+ * Pure: no I/O, no field-builder coupling. Returns just the literal (the caller
15
+ * wraps it in `@default(...)`), so it composes with whatever modifier string a
16
+ * field builder assembles. Returns `undefined` when there is nothing to emit, so
17
+ * a field with no `defaultValue` produces no `@default(...)` at all.
18
+ *
19
+ * Serialisation rules (Keystone 6 compatible):
20
+ * - `integer` → bare numeric literal, e.g. `3550` → `@default(3550)`.
21
+ * - `text` → double-quoted string literal, e.g. `PLEASE_UPDATE` → `@default("PLEASE_UPDATE")`.
22
+ * - `json` → Keystone's JSON-literal form: `JSON.stringify` the value with no
23
+ * extra whitespace, then wrap the result in escaped double quotes, e.g.
24
+ * `[1,2,3,4,5]` → `@default("[1,2,3,4,5]")` and `[]` → `@default("[]")`.
25
+ *
26
+ * Nullability (the `?` modifier) is the caller's concern and is handled
27
+ * independently of the default — this function never touches it.
28
+ *
29
+ * @param value - The configured `defaultValue` (the field builder's `defaultValue`).
30
+ * @param fieldType - The field's discriminator, selecting the serialisation rule.
31
+ * @returns The literal to place inside `@default(...)`, or `undefined` when
32
+ * `value` is `undefined` (no default to emit).
33
+ */
34
+ export declare function formatPrismaDefault(value: unknown, fieldType: PrismaDefaultFieldType): string | undefined;
35
+ //# sourceMappingURL=format-prisma-default.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-prisma-default.d.ts","sourceRoot":"","sources":["../../src/fields/format-prisma-default.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAA;AAEhE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,sBAAsB,GAChC,MAAM,GAAG,SAAS,CA6BpB"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Serialise a field's `defaultValue` into the inner literal of a Prisma
3
+ * `@default(...)` attribute.
4
+ *
5
+ * Pure: no I/O, no field-builder coupling. Returns just the literal (the caller
6
+ * wraps it in `@default(...)`), so it composes with whatever modifier string a
7
+ * field builder assembles. Returns `undefined` when there is nothing to emit, so
8
+ * a field with no `defaultValue` produces no `@default(...)` at all.
9
+ *
10
+ * Serialisation rules (Keystone 6 compatible):
11
+ * - `integer` → bare numeric literal, e.g. `3550` → `@default(3550)`.
12
+ * - `text` → double-quoted string literal, e.g. `PLEASE_UPDATE` → `@default("PLEASE_UPDATE")`.
13
+ * - `json` → Keystone's JSON-literal form: `JSON.stringify` the value with no
14
+ * extra whitespace, then wrap the result in escaped double quotes, e.g.
15
+ * `[1,2,3,4,5]` → `@default("[1,2,3,4,5]")` and `[]` → `@default("[]")`.
16
+ *
17
+ * Nullability (the `?` modifier) is the caller's concern and is handled
18
+ * independently of the default — this function never touches it.
19
+ *
20
+ * @param value - The configured `defaultValue` (the field builder's `defaultValue`).
21
+ * @param fieldType - The field's discriminator, selecting the serialisation rule.
22
+ * @returns The literal to place inside `@default(...)`, or `undefined` when
23
+ * `value` is `undefined` (no default to emit).
24
+ */
25
+ export function formatPrismaDefault(value, fieldType) {
26
+ if (value === undefined) {
27
+ return undefined;
28
+ }
29
+ switch (fieldType) {
30
+ case 'integer':
31
+ // Bare numeric literal — Prisma expects no quotes for Int defaults.
32
+ return String(value);
33
+ case 'text':
34
+ // Double-quoted string literal. The value is escaped via JSON.stringify so
35
+ // embedded quotes/backslashes are handled correctly.
36
+ return JSON.stringify(String(value));
37
+ case 'json': {
38
+ // Keystone's JSON-literal form: canonical, space-free JSON.stringify of the
39
+ // value, then wrap the whole serialised string in escaped double quotes so
40
+ // Prisma stores the JSON text as the column default. The outer
41
+ // JSON.stringify produces the escaped, double-quoted wrapper.
42
+ const serialised = JSON.stringify(value);
43
+ // JSON.stringify can return undefined for unserialisable values (e.g. a
44
+ // function). Treat that as "no default" rather than emitting `@default()`.
45
+ if (serialised === undefined) {
46
+ return undefined;
47
+ }
48
+ return JSON.stringify(serialised);
49
+ }
50
+ }
51
+ }
52
+ //# sourceMappingURL=format-prisma-default.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-prisma-default.js","sourceRoot":"","sources":["../../src/fields/format-prisma-default.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAc,EACd,SAAiC;IAEjC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,SAAS;YACZ,oEAAoE;YACpE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAA;QAEtB,KAAK,MAAM;YACT,2EAA2E;YAC3E,qDAAqD;YACrD,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAEtC,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,4EAA4E;YAC5E,2EAA2E;YAC3E,+DAA+D;YAC/D,8DAA8D;YAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;YACxC,wEAAwE;YACxE,2EAA2E;YAC3E,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC7B,OAAO,SAAS,CAAA;YAClB,CAAC;YACD,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;QACnC,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=format-prisma-default.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-prisma-default.test.d.ts","sourceRoot":"","sources":["../../src/fields/format-prisma-default.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { formatPrismaDefault } from './format-prisma-default.js';
3
+ describe('formatPrismaDefault', () => {
4
+ describe('table-driven serialisation', () => {
5
+ const cases = [
6
+ // text
7
+ {
8
+ name: 'non-empty string',
9
+ value: 'PLEASE_UPDATE',
10
+ fieldType: 'text',
11
+ expected: '"PLEASE_UPDATE"',
12
+ },
13
+ { name: 'empty string', value: '', fieldType: 'text', expected: '""' },
14
+ {
15
+ name: 'string with embedded quotes',
16
+ value: 'say "hi"',
17
+ fieldType: 'text',
18
+ expected: '"say \\"hi\\""',
19
+ },
20
+ // integer
21
+ { name: 'integer', value: 3550, fieldType: 'integer', expected: '3550' },
22
+ { name: 'zero integer', value: 0, fieldType: 'integer', expected: '0' },
23
+ { name: 'negative integer', value: -7, fieldType: 'integer', expected: '-7' },
24
+ // json
25
+ { name: 'JSON array', value: [1, 2, 3, 4, 5], fieldType: 'json', expected: '"[1,2,3,4,5]"' },
26
+ {
27
+ name: 'JSON object',
28
+ value: { a: 1, b: 'two' },
29
+ fieldType: 'json',
30
+ expected: '"{\\"a\\":1,\\"b\\":\\"two\\"}"',
31
+ },
32
+ { name: 'empty array', value: [], fieldType: 'json', expected: '"[]"' },
33
+ { name: 'empty object', value: {}, fieldType: 'json', expected: '"{}"' },
34
+ // undefined → no default for every field type
35
+ { name: 'undefined text', value: undefined, fieldType: 'text', expected: undefined },
36
+ { name: 'undefined integer', value: undefined, fieldType: 'integer', expected: undefined },
37
+ { name: 'undefined json', value: undefined, fieldType: 'json', expected: undefined },
38
+ ];
39
+ it.each(cases)('serialises $name → $expected', ({ value, fieldType, expected }) => {
40
+ expect(formatPrismaDefault(value, fieldType)).toBe(expected);
41
+ });
42
+ });
43
+ it('produces canonical space-free JSON (no extra whitespace)', () => {
44
+ // Guards against pretty-printed JSON sneaking into the literal.
45
+ expect(formatPrismaDefault([1, 2, 3], 'json')).toBe('"[1,2,3]"');
46
+ expect(formatPrismaDefault({ nested: { x: [1] } }, 'json')).toBe('"{\\"nested\\":{\\"x\\":[1]}}"');
47
+ });
48
+ it('wraps a JSON string default in escaped quotes around the JSON text', () => {
49
+ // A string value under the json field type is itself valid JSON; it gets
50
+ // double-serialised: inner JSON.stringify("hi") = "\"hi\"", then wrapped.
51
+ expect(formatPrismaDefault('hi', 'json')).toBe('"\\"hi\\""');
52
+ });
53
+ });
54
+ //# sourceMappingURL=format-prisma-default.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-prisma-default.test.js","sourceRoot":"","sources":["../../src/fields/format-prisma-default.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,mBAAmB,EAA+B,MAAM,4BAA4B,CAAA;AAE7F,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;QAC1C,MAAM,KAAK,GAKN;YACH,OAAO;YACP;gBACE,IAAI,EAAE,kBAAkB;gBACxB,KAAK,EAAE,eAAe;gBACtB,SAAS,EAAE,MAAM;gBACjB,QAAQ,EAAE,iBAAiB;aAC5B;YACD,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;YACtE;gBACE,IAAI,EAAE,6BAA6B;gBACnC,KAAK,EAAE,UAAU;gBACjB,SAAS,EAAE,MAAM;gBACjB,QAAQ,EAAE,gBAAgB;aAC3B;YACD,UAAU;YACV,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE;YACxE,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE;YACvE,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC7E,OAAO;YACP,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE;YAC5F;gBACE,IAAI,EAAE,aAAa;gBACnB,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE;gBACzB,SAAS,EAAE,MAAM;gBACjB,QAAQ,EAAE,iCAAiC;aAC5C;YACD,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE;YACvE,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE;YACxE,8CAA8C;YAC9C,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE;YACpF,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE;YAC1F,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE;SACrF,CAAA;QAED,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,8BAA8B,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE;YAChF,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC9D,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,gEAAgE;QAChE,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QAChE,MAAM,CAAC,mBAAmB,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAC9D,gCAAgC,CACjC,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,yEAAyE;QACzE,0EAA0E;QAC1E,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IAC9D,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -1,4 +1,5 @@
1
1
  import type { TextField, IntegerField, DecimalField, CheckboxField, TimestampField, CalendarDayField, PasswordField, SelectField, RelationshipField, JsonField, VirtualField } from '../config/types.js';
2
+ export type { TextField, IntegerField, DecimalField, CheckboxField, TimestampField, CalendarDayField, PasswordField, SelectField, RelationshipField, JsonField, VirtualField, PrismaRelationResult, MultiColumnPrismaResult, } from '../config/types.js';
2
3
  /**
3
4
  * Text field
4
5
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fields/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fields/index.ts"],"names":[],"mappings":"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"}