@opensaas/stack-storage 0.21.0 → 0.23.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +35 -0
- package/dist/fields/index.d.ts +60 -0
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +101 -29
- package/dist/fields/index.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/multi-column.d.ts +112 -0
- package/dist/utils/multi-column.d.ts.map +1 -0
- package/dist/utils/multi-column.js +248 -0
- package/dist/utils/multi-column.js.map +1 -0
- package/package.json +2 -2
- package/src/fields/index.ts +198 -36
- package/src/utils/index.ts +1 -0
- package/src/utils/multi-column.ts +346 -0
- package/tests/multi-column-fields.test.ts +397 -0
- package/tests/multi-column.test.ts +308 -0
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# @opensaas/stack-storage
|
|
2
2
|
|
|
3
|
+
## 0.23.0
|
|
4
|
+
|
|
5
|
+
## 0.22.0
|
|
6
|
+
|
|
7
|
+
### Minor Changes
|
|
8
|
+
|
|
9
|
+
- [#511](https://github.com/OpenSaasAU/stack/pull/511) [`696f5c0`](https://github.com/OpenSaasAU/stack/commit/696f5c08c37d4a18107e48cb6b360c9492c7425c) Thanks [@borisno2](https://github.com/borisno2)! - Add non-destructive multi-column mode to `image()` / `file()` for adopting an existing Keystone database without dropping columns (ADR-0006).
|
|
10
|
+
|
|
11
|
+
Keystone stores an image across seven per-part columns (`_url`, `_width`, `_height`, `_filesize`, `_contentType`, `_contentDisposition`, `_pathname`) and a file across three (`_filename`, `_filesize`, `_url`). By default `image()`/`file()` still back a single `Json?` column (greenfield unchanged). Set `db.columns: 'keystone'` to map the field onto the existing per-part columns in place — assembled into an `ImageMetadata`/`FileMetadata` on read and split back on write — so a migrating project reaches a clean schema diff with no data migration and no re-upload of existing assets.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { image, file } from '@opensaas/stack-storage/fields'
|
|
15
|
+
|
|
16
|
+
fields: {
|
|
17
|
+
// Maps onto image_url, image_width, … image_pathname in place.
|
|
18
|
+
avatar: image({ storage: 'images', db: { columns: 'keystone' } }),
|
|
19
|
+
|
|
20
|
+
// Per-part @map names are configurable for non-default column names.
|
|
21
|
+
cover: image({
|
|
22
|
+
storage: 'images',
|
|
23
|
+
db: { columns: { mode: 'keystone', map: { url: 'cover_link' } } },
|
|
24
|
+
}),
|
|
25
|
+
|
|
26
|
+
resume: file({ storage: 'documents', db: { columns: 'keystone' } }),
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
No-re-upload guarantee (both modes): an already-shaped metadata value — or, in multi-column mode, populated columns — is authoritative and never triggers a storage upload; only a `File`-like input uploads.
|
|
31
|
+
|
|
32
|
+
Adds a multi-column field-emission contract (`getPrismaColumns`) plus `getColumnNames`/`assembleColumns`/`splitColumns` to the field-authoring surface so any field can map onto several physical columns. The generator emits one `@map`-ped Prisma line per column; reads assemble the logical value from the raw columns and strip them from the result; writes split the logical value back across the columns.
|
|
33
|
+
|
|
34
|
+
### Patch Changes
|
|
35
|
+
|
|
36
|
+
- [#520](https://github.com/OpenSaasAU/stack/pull/520) [`6610687`](https://github.com/OpenSaasAU/stack/commit/66106876643f0e9903eb6a677b7713890d0630e4) Thanks [@borisno2](https://github.com/borisno2)! - Add `file()` field-builder-level tests for multi-column (Keystone-parity) mode (issue [#478](https://github.com/OpenSaasAU/stack/issues/478)): assemble/split of `FileMetadata` across the three Keystone columns through the `file()` builder, including only-`file_url` partial rows, empty-row → null, custom `@map` round-trip, and nullable/`Int`-typed column emission. Test-only; no behaviour change.
|
|
37
|
+
|
|
3
38
|
## 0.21.0
|
|
4
39
|
|
|
5
40
|
### Minor Changes
|
package/dist/fields/index.d.ts
CHANGED
|
@@ -2,6 +2,54 @@ import type { BaseFieldConfig, TypeInfo } from '@opensaas/stack-core/extend';
|
|
|
2
2
|
import type { ComponentType } from 'react';
|
|
3
3
|
import type { ImageTransformationConfig } from '../config/types.js';
|
|
4
4
|
import type { FileValidationOptions } from '../utils/upload.js';
|
|
5
|
+
import { type FileColumnMap, type ImageColumnMap } from '../utils/multi-column.js';
|
|
6
|
+
/**
|
|
7
|
+
* Multi-column (Keystone-parity) database mode for image()/file() fields.
|
|
8
|
+
*
|
|
9
|
+
* The default backing for image()/file() is a single `Json?` column. A
|
|
10
|
+
* migrating project that already has a Keystone database can instead set
|
|
11
|
+
* `columns: 'keystone'` to map the field onto the existing per-part columns in
|
|
12
|
+
* place — no destructive migration, no re-upload of existing assets. The
|
|
13
|
+
* per-part column names (used in `@map`) follow Keystone's `<field>_<part>`
|
|
14
|
+
* convention and can be overridden individually. See ADR-0006.
|
|
15
|
+
*/
|
|
16
|
+
export interface ImageDbConfig {
|
|
17
|
+
/** Custom database column name for single-Json? mode (unused in multi-column mode). */
|
|
18
|
+
map?: string;
|
|
19
|
+
/** Override DB-level nullability for single-Json? mode. */
|
|
20
|
+
isNullable?: boolean;
|
|
21
|
+
/** Override the native database type for single-Json? mode. */
|
|
22
|
+
nativeType?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Enable multi-column mode by setting `'keystone'`. Per-part column-name
|
|
25
|
+
* overrides may be supplied; any omitted part falls back to the Keystone
|
|
26
|
+
* default `<field>_<part>`.
|
|
27
|
+
*/
|
|
28
|
+
columns?: 'keystone' | {
|
|
29
|
+
mode: 'keystone';
|
|
30
|
+
map?: Partial<ImageColumnMap>;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Multi-column (Keystone-parity) database mode for file() fields.
|
|
35
|
+
*/
|
|
36
|
+
export interface FileDbConfig {
|
|
37
|
+
/** Custom database column name for single-Json? mode (unused in multi-column mode). */
|
|
38
|
+
map?: string;
|
|
39
|
+
/** Override DB-level nullability for single-Json? mode. */
|
|
40
|
+
isNullable?: boolean;
|
|
41
|
+
/** Override the native database type for single-Json? mode. */
|
|
42
|
+
nativeType?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Enable multi-column mode by setting `'keystone'`. Per-part column-name
|
|
45
|
+
* overrides may be supplied; any omitted part falls back to the Keystone
|
|
46
|
+
* default `<field>_<part>`.
|
|
47
|
+
*/
|
|
48
|
+
columns?: 'keystone' | {
|
|
49
|
+
mode: 'keystone';
|
|
50
|
+
map?: Partial<FileColumnMap>;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
5
53
|
/**
|
|
6
54
|
* File field configuration
|
|
7
55
|
*/
|
|
@@ -15,6 +63,12 @@ export interface FileFieldConfig<TTypeInfo extends TypeInfo = TypeInfo> extends
|
|
|
15
63
|
cleanupOnDelete?: boolean;
|
|
16
64
|
/** Automatically delete old file from storage when replaced with new file */
|
|
17
65
|
cleanupOnReplace?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Database configuration. By default a file is backed by a single `Json?`
|
|
68
|
+
* column; set `db.columns: 'keystone'` to map onto existing Keystone per-part
|
|
69
|
+
* columns in place (non-destructive migration). See ADR-0006.
|
|
70
|
+
*/
|
|
71
|
+
db?: FileDbConfig;
|
|
18
72
|
/** UI options */
|
|
19
73
|
ui?: {
|
|
20
74
|
/** Custom component to use for rendering this field */
|
|
@@ -46,6 +100,12 @@ export interface ImageFieldConfig<TTypeInfo extends TypeInfo = TypeInfo> extends
|
|
|
46
100
|
cleanupOnDelete?: boolean;
|
|
47
101
|
/** Automatically delete old file from storage when replaced with new file */
|
|
48
102
|
cleanupOnReplace?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Database configuration. By default an image is backed by a single `Json?`
|
|
105
|
+
* column; set `db.columns: 'keystone'` to map onto existing Keystone per-part
|
|
106
|
+
* columns in place (non-destructive migration). See ADR-0006.
|
|
107
|
+
*/
|
|
108
|
+
db?: ImageDbConfig;
|
|
49
109
|
/** UI options */
|
|
50
110
|
ui?: {
|
|
51
111
|
/** Custom component to use for rendering this field */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fields/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fields/index.ts"],"names":[],"mappings":"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"}
|
package/dist/fields/index.js
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { assembleFileMetadata, assembleImageMetadata, fileColumnDescriptors, fileColumnNames, imageColumnDescriptors, imageColumnNames, resolveFileColumnMap, resolveImageColumnMap, splitFileMetadata, splitImageMetadata, } from '../utils/multi-column.js';
|
|
3
|
+
/** Whether a field-level `db.columns` option requests multi-column mode. */
|
|
4
|
+
function isMultiColumn(columns) {
|
|
5
|
+
return columns === 'keystone' || (typeof columns === 'object' && columns?.mode === 'keystone');
|
|
6
|
+
}
|
|
7
|
+
/** Extract any per-part `@map` overrides from a `db.columns` option. */
|
|
8
|
+
function imageColumnOverrides(columns) {
|
|
9
|
+
return typeof columns === 'object' ? columns.map : undefined;
|
|
10
|
+
}
|
|
11
|
+
function fileColumnOverrides(columns) {
|
|
12
|
+
return typeof columns === 'object' ? columns.map : undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Detect a File-like input (something we should upload). An already-shaped
|
|
16
|
+
* metadata value or populated multi-column row is authoritative and must never
|
|
17
|
+
* trigger an upload (the no-re-upload guarantee — see ADR-0006).
|
|
18
|
+
*/
|
|
19
|
+
function isFileLike(value) {
|
|
20
|
+
return (typeof value === 'object' &&
|
|
21
|
+
value !== null &&
|
|
22
|
+
'arrayBuffer' in value &&
|
|
23
|
+
typeof value.arrayBuffer === 'function');
|
|
24
|
+
}
|
|
2
25
|
/**
|
|
3
26
|
* Creates a file upload field
|
|
4
27
|
*
|
|
@@ -19,29 +42,38 @@ import { z } from 'zod';
|
|
|
19
42
|
*/
|
|
20
43
|
export function file(options) {
|
|
21
44
|
const { hooks: userHooks, ...restOptions } = options;
|
|
45
|
+
// Multi-column (Keystone-parity) mode maps onto existing per-part columns
|
|
46
|
+
// instead of a single Json? column. The column map is resolved lazily per
|
|
47
|
+
// field name so default `<field>_<part>` names line up with the live columns.
|
|
48
|
+
const multiColumn = isMultiColumn(options.db?.columns);
|
|
49
|
+
const columnMapFor = (fieldName) => resolveFileColumnMap(fieldName, fileColumnOverrides(options.db?.columns));
|
|
22
50
|
const fieldConfig = {
|
|
23
51
|
type: 'file',
|
|
24
52
|
...restOptions,
|
|
25
|
-
// Override Prisma's Json type with FileMetadata | null in context.db types
|
|
53
|
+
// Override Prisma's Json type with FileMetadata | null in context.db types.
|
|
54
|
+
// Multi-column mode adds the same logical field back via TransformedFields
|
|
55
|
+
// while the raw per-part columns are stripped from the payload.
|
|
26
56
|
resultExtension: {
|
|
27
57
|
outputType: "import('@opensaas/stack-storage').FileMetadata | null",
|
|
28
58
|
},
|
|
29
59
|
hooks: {
|
|
60
|
+
// Keystone-compliant field resolveInput args: the field value lives at
|
|
61
|
+
// `resolvedData[fieldKey]`. See FieldResolveInputHookArgs in core.
|
|
30
62
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Field builder hooks are generic and resolved at runtime
|
|
31
|
-
resolveInput: async ({
|
|
63
|
+
resolveInput: async ({ resolvedData, fieldKey, context, item }) => {
|
|
64
|
+
const inputValue = resolvedData?.[fieldKey];
|
|
32
65
|
// If null/undefined, return as-is (deletion or no change)
|
|
33
66
|
if (inputValue === null || inputValue === undefined) {
|
|
34
67
|
return inputValue;
|
|
35
68
|
}
|
|
36
|
-
// If already FileMetadata, keep existing (edit mode - no new file
|
|
69
|
+
// If already FileMetadata, keep existing (edit mode - no new file
|
|
70
|
+
// uploaded). An existing metadata value is AUTHORITATIVE and must never
|
|
71
|
+
// re-upload. See ADR-0006.
|
|
37
72
|
if (typeof inputValue === 'object' && 'filename' in inputValue && 'url' in inputValue) {
|
|
38
73
|
return inputValue;
|
|
39
74
|
}
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
if (typeof inputValue === 'object' &&
|
|
43
|
-
'arrayBuffer' in inputValue &&
|
|
44
|
-
typeof inputValue.arrayBuffer === 'function') {
|
|
75
|
+
// Only a File-like input triggers an upload.
|
|
76
|
+
if (isFileLike(inputValue)) {
|
|
45
77
|
// Convert File to buffer
|
|
46
78
|
const fileObj = inputValue;
|
|
47
79
|
const arrayBuffer = await fileObj.arrayBuffer();
|
|
@@ -51,8 +83,8 @@ export function file(options) {
|
|
|
51
83
|
validation: fieldConfig.validation,
|
|
52
84
|
}));
|
|
53
85
|
// If cleanupOnReplace is enabled and there was an old file, delete it
|
|
54
|
-
if (fieldConfig.cleanupOnReplace && item &&
|
|
55
|
-
const oldMetadata = item[
|
|
86
|
+
if (fieldConfig.cleanupOnReplace && item && fieldKey) {
|
|
87
|
+
const oldMetadata = item[fieldKey];
|
|
56
88
|
if (oldMetadata && oldMetadata.filename) {
|
|
57
89
|
try {
|
|
58
90
|
await context.storage.deleteFile(oldMetadata.storageProvider, oldMetadata.filename);
|
|
@@ -69,11 +101,11 @@ export function file(options) {
|
|
|
69
101
|
return inputValue;
|
|
70
102
|
},
|
|
71
103
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Field builder hooks are generic and resolved at runtime
|
|
72
|
-
afterOperation: async ({ operation,
|
|
73
|
-
// Only cleanup on delete if enabled
|
|
104
|
+
afterOperation: async ({ operation, originalItem, fieldKey, context }) => {
|
|
105
|
+
// Only cleanup on delete if enabled. The deleted row is `originalItem`.
|
|
74
106
|
if (operation === 'delete' && fieldConfig.cleanupOnDelete) {
|
|
75
|
-
const fileMetadata =
|
|
76
|
-
if (fileMetadata && fileMetadata.filename) {
|
|
107
|
+
const fileMetadata = originalItem?.[fieldKey];
|
|
108
|
+
if (fileMetadata && typeof fileMetadata === 'object' && fileMetadata.filename) {
|
|
77
109
|
try {
|
|
78
110
|
await context.storage.deleteFile(fileMetadata.storageProvider, fileMetadata.filename);
|
|
79
111
|
}
|
|
@@ -103,7 +135,7 @@ export function file(options) {
|
|
|
103
135
|
return z.union([fileMetadataSchema, z.null(), z.undefined()]);
|
|
104
136
|
},
|
|
105
137
|
getPrismaType: (_fieldName) => {
|
|
106
|
-
// Store as JSON in database
|
|
138
|
+
// Store as JSON in database (single-column default mode).
|
|
107
139
|
return { type: 'Json', modifiers: '?' };
|
|
108
140
|
},
|
|
109
141
|
getTypeScriptType: () => {
|
|
@@ -123,6 +155,22 @@ export function file(options) {
|
|
|
123
155
|
];
|
|
124
156
|
},
|
|
125
157
|
};
|
|
158
|
+
// Multi-column emission + assemble/split. Only attached in multi-column mode
|
|
159
|
+
// so the single-Json? default is completely unaffected.
|
|
160
|
+
if (multiColumn) {
|
|
161
|
+
fieldConfig.getPrismaColumns = (fieldName) => {
|
|
162
|
+
const map = columnMapFor(fieldName);
|
|
163
|
+
return fileColumnDescriptors(map).map((col) => ({
|
|
164
|
+
name: col.name,
|
|
165
|
+
type: col.type,
|
|
166
|
+
modifiers: '?',
|
|
167
|
+
map: col.map,
|
|
168
|
+
}));
|
|
169
|
+
};
|
|
170
|
+
fieldConfig.getColumnNames = (fieldName) => fileColumnNames(columnMapFor(fieldName));
|
|
171
|
+
fieldConfig.assembleColumns = (fieldName, row) => assembleFileMetadata(row, columnMapFor(fieldName), fieldConfig.storage);
|
|
172
|
+
fieldConfig.splitColumns = (fieldName, value) => splitFileMetadata((value ?? null), columnMapFor(fieldName));
|
|
173
|
+
}
|
|
126
174
|
return fieldConfig;
|
|
127
175
|
}
|
|
128
176
|
/**
|
|
@@ -149,21 +197,32 @@ export function file(options) {
|
|
|
149
197
|
*/
|
|
150
198
|
export function image(options) {
|
|
151
199
|
const { hooks: userHooks, ...restOptions } = options;
|
|
200
|
+
// Multi-column (Keystone-parity) mode maps onto existing per-part columns
|
|
201
|
+
// instead of a single Json? column. See ADR-0006.
|
|
202
|
+
const multiColumn = isMultiColumn(options.db?.columns);
|
|
203
|
+
const columnMapFor = (fieldName) => resolveImageColumnMap(fieldName, imageColumnOverrides(options.db?.columns));
|
|
152
204
|
const fieldConfig = {
|
|
153
205
|
type: 'image',
|
|
154
206
|
...restOptions,
|
|
155
|
-
// Override Prisma's Json type with ImageMetadata | null in context.db types
|
|
207
|
+
// Override Prisma's Json type with ImageMetadata | null in context.db types.
|
|
208
|
+
// Multi-column mode adds the same logical field back via TransformedFields
|
|
209
|
+
// while the raw per-part columns are stripped from the payload.
|
|
156
210
|
resultExtension: {
|
|
157
211
|
outputType: "import('@opensaas/stack-storage').ImageMetadata | null",
|
|
158
212
|
},
|
|
159
213
|
hooks: {
|
|
214
|
+
// Keystone-compliant field resolveInput args: the field value lives at
|
|
215
|
+
// `resolvedData[fieldKey]`. See FieldResolveInputHookArgs in core.
|
|
160
216
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Field builder hooks are generic and resolved at runtime
|
|
161
|
-
resolveInput: async ({
|
|
217
|
+
resolveInput: async ({ resolvedData, fieldKey, context, item }) => {
|
|
218
|
+
const inputValue = resolvedData?.[fieldKey];
|
|
162
219
|
// If null/undefined, return as-is (deletion or no change)
|
|
163
220
|
if (inputValue === null || inputValue === undefined) {
|
|
164
221
|
return inputValue;
|
|
165
222
|
}
|
|
166
|
-
// If already ImageMetadata, keep existing (edit mode - no new file
|
|
223
|
+
// If already ImageMetadata, keep existing (edit mode - no new file
|
|
224
|
+
// uploaded). An existing metadata value is AUTHORITATIVE and must never
|
|
225
|
+
// re-upload. See ADR-0006.
|
|
167
226
|
if (typeof inputValue === 'object' &&
|
|
168
227
|
'filename' in inputValue &&
|
|
169
228
|
'url' in inputValue &&
|
|
@@ -171,11 +230,8 @@ export function image(options) {
|
|
|
171
230
|
'height' in inputValue) {
|
|
172
231
|
return inputValue;
|
|
173
232
|
}
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
if (typeof inputValue === 'object' &&
|
|
177
|
-
'arrayBuffer' in inputValue &&
|
|
178
|
-
typeof inputValue.arrayBuffer === 'function') {
|
|
233
|
+
// Only a File-like input triggers an upload.
|
|
234
|
+
if (isFileLike(inputValue)) {
|
|
179
235
|
// Convert File to buffer
|
|
180
236
|
const fileObj = inputValue;
|
|
181
237
|
const arrayBuffer = await fileObj.arrayBuffer();
|
|
@@ -186,8 +242,8 @@ export function image(options) {
|
|
|
186
242
|
transformations: fieldConfig.transformations,
|
|
187
243
|
}));
|
|
188
244
|
// If cleanupOnReplace is enabled and there was an old file, delete it
|
|
189
|
-
if (fieldConfig.cleanupOnReplace && item &&
|
|
190
|
-
const oldMetadata = item[
|
|
245
|
+
if (fieldConfig.cleanupOnReplace && item && fieldKey) {
|
|
246
|
+
const oldMetadata = item[fieldKey];
|
|
191
247
|
if (oldMetadata && oldMetadata.filename) {
|
|
192
248
|
try {
|
|
193
249
|
await context.storage.deleteImage(oldMetadata);
|
|
@@ -204,11 +260,11 @@ export function image(options) {
|
|
|
204
260
|
return inputValue;
|
|
205
261
|
},
|
|
206
262
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Field builder hooks are generic and resolved at runtime
|
|
207
|
-
afterOperation: async ({ operation,
|
|
208
|
-
// Only cleanup on delete if enabled
|
|
263
|
+
afterOperation: async ({ operation, originalItem, fieldKey, context }) => {
|
|
264
|
+
// Only cleanup on delete if enabled. The deleted row is `originalItem`.
|
|
209
265
|
if (operation === 'delete' && fieldConfig.cleanupOnDelete) {
|
|
210
|
-
const imageMetadata =
|
|
211
|
-
if (imageMetadata && imageMetadata.filename) {
|
|
266
|
+
const imageMetadata = originalItem?.[fieldKey];
|
|
267
|
+
if (imageMetadata && typeof imageMetadata === 'object' && imageMetadata.filename) {
|
|
212
268
|
try {
|
|
213
269
|
await context.storage.deleteImage(imageMetadata);
|
|
214
270
|
}
|
|
@@ -268,6 +324,22 @@ export function image(options) {
|
|
|
268
324
|
];
|
|
269
325
|
},
|
|
270
326
|
};
|
|
327
|
+
// Multi-column emission + assemble/split. Only attached in multi-column mode
|
|
328
|
+
// so the single-Json? default is completely unaffected.
|
|
329
|
+
if (multiColumn) {
|
|
330
|
+
fieldConfig.getPrismaColumns = (fieldName) => {
|
|
331
|
+
const map = columnMapFor(fieldName);
|
|
332
|
+
return imageColumnDescriptors(map).map((col) => ({
|
|
333
|
+
name: col.name,
|
|
334
|
+
type: col.type,
|
|
335
|
+
modifiers: '?',
|
|
336
|
+
map: col.map,
|
|
337
|
+
}));
|
|
338
|
+
};
|
|
339
|
+
fieldConfig.getColumnNames = (fieldName) => imageColumnNames(columnMapFor(fieldName));
|
|
340
|
+
fieldConfig.assembleColumns = (fieldName, row) => assembleImageMetadata(row, columnMapFor(fieldName), fieldConfig.storage);
|
|
341
|
+
fieldConfig.splitColumns = (fieldName, value) => splitImageMetadata((value ?? null), columnMapFor(fieldName));
|
|
342
|
+
}
|
|
271
343
|
return fieldConfig;
|
|
272
344
|
}
|
|
273
345
|
//# sourceMappingURL=index.js.map
|
package/dist/fields/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fields/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fields/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,sBAAsB,EACtB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,GAGnB,MAAM,0BAA0B,CAAA;AA6CjC,4EAA4E;AAC5E,SAAS,aAAa,CAAC,OAA2D;IAChF,OAAO,OAAO,KAAK,UAAU,IAAI,CAAC,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,EAAE,IAAI,KAAK,UAAU,CAAC,CAAA;AAChG,CAAC;AAED,wEAAwE;AACxE,SAAS,oBAAoB,CAC3B,OAAiC;IAEjC,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;AAC9D,CAAC;AAED,SAAS,mBAAmB,CAAC,OAAgC;IAC3D,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;AAC9D,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,aAAa,IAAI,KAAK;QACtB,OAAQ,KAAmC,CAAC,WAAW,KAAK,UAAU,CACvE,CAAA;AACH,CAAC;AAoFD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,IAAI,CAClB,OAAiD;IAEjD,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,WAAW,EAAE,GAAG,OAAO,CAAA;IAEpD,0EAA0E;IAC1E,0EAA0E;IAC1E,8EAA8E;IAC9E,MAAM,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IACtD,MAAM,YAAY,GAAG,CAAC,SAAiB,EAAiB,EAAE,CACxD,oBAAoB,CAAC,SAAS,EAAE,mBAAmB,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAA;IAE3E,MAAM,WAAW,GAA+B;QAC9C,IAAI,EAAE,MAAM;QACZ,GAAG,WAAW;QAEd,4EAA4E;QAC5E,2EAA2E;QAC3E,gEAAgE;QAChE,eAAe,EAAE;YACf,UAAU,EAAE,uDAAuD;SACpE;QAED,KAAK,EAAE;YACL,uEAAuE;YACvE,mEAAmE;YACnE,yHAAyH;YACzH,YAAY,EAAE,KAAK,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAO,EAAE,EAAE;gBACrE,MAAM,UAAU,GAAG,YAAY,EAAE,CAAC,QAAQ,CAAC,CAAA;gBAE3C,0DAA0D;gBAC1D,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;oBACpD,OAAO,UAAU,CAAA;gBACnB,CAAC;gBAED,kEAAkE;gBAClE,wEAAwE;gBACxE,2BAA2B;gBAC3B,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,IAAI,UAAU,IAAI,KAAK,IAAI,UAAU,EAAE,CAAC;oBACtF,OAAO,UAA0B,CAAA;gBACnC,CAAC;gBAED,6CAA6C;gBAC7C,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;oBAC3B,yBAAyB;oBACzB,MAAM,OAAO,GAAG,UAAU,CAAA;oBAC1B,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAA;oBAC/C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;oBAEvC,8CAA8C;oBAC9C,MAAM,QAAQ,GAAG,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE;wBACvF,UAAU,EAAE,WAAW,CAAC,UAAU;qBACnC,CAAC,CAAiB,CAAA;oBAEnB,sEAAsE;oBACtE,IAAI,WAAW,CAAC,gBAAgB,IAAI,IAAI,IAAI,QAAQ,EAAE,CAAC;wBACrD,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAwB,CAAA;wBACzD,IAAI,WAAW,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC;4BACxC,IAAI,CAAC;gCACH,MAAM,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,eAAe,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;4BACrF,CAAC;4BAAC,OAAO,KAAK,EAAE,CAAC;gCACf,yCAAyC;gCACzC,OAAO,CAAC,KAAK,CAAC,+BAA+B,WAAW,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAA;4BAC7E,CAAC;wBACH,CAAC;oBACH,CAAC;oBAED,OAAO,QAAQ,CAAA;gBACjB,CAAC;gBAED,0DAA0D;gBAC1D,OAAO,UAAU,CAAA;YACnB,CAAC;YAED,yHAAyH;YACzH,cAAc,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAO,EAAE,EAAE;gBAC5E,wEAAwE;gBACxE,IAAI,SAAS,KAAK,QAAQ,IAAI,WAAW,CAAC,eAAe,EAAE,CAAC;oBAC1D,MAAM,YAAY,GAAG,YAAY,EAAE,CAAC,QAAQ,CAAwB,CAAA;oBAEpE,IAAI,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,QAAQ,EAAE,CAAC;wBAC9E,IAAI,CAAC;4BACH,MAAM,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,eAAe,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;wBACvF,CAAC;wBAAC,OAAO,KAAK,EAAE,CAAC;4BACf,yCAAyC;4BACzC,OAAO,CAAC,KAAK,CAAC,qCAAqC,YAAY,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAA;wBACpF,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YACD,wCAAwC;YACxC,GAAG,SAAS;SACb;QAED,YAAY,EAAE,CAAC,UAAkB,EAAE,UAA+B,EAAE,EAAE;YACpE,gDAAgD;YAChD,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;gBAClC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;gBACpB,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE;gBAC5B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,+CAA+C;gBAChE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;gBACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;gBAChB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;gBACtB,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE;gBAC3B,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;aACvD,CAAC,CAAA;YAEF,iCAAiC;YACjC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,kBAAkB,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAA;QAC/D,CAAC;QAED,aAAa,EAAE,CAAC,UAAkB,EAAE,EAAE;YACpC,0DAA0D;YAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,CAAA;QACzC,CAAC;QAED,iBAAiB,EAAE,GAAG,EAAE;YACtB,yCAAyC;YACzC,OAAO;gBACL,IAAI,EAAE,qBAAqB;gBAC3B,QAAQ,EAAE,IAAI;aACf,CAAA;QACH,CAAC;QAED,oBAAoB,EAAE,GAAG,EAAE;YACzB,OAAO;gBACL;oBACE,KAAK,EAAE,CAAC,cAAc,CAAC;oBACvB,IAAI,EAAE,yBAAyB;oBAC/B,QAAQ,EAAE,IAAI;iBACf;aACF,CAAA;QACH,CAAC;KACF,CAAA;IAED,6EAA6E;IAC7E,wDAAwD;IACxD,IAAI,WAAW,EAAE,CAAC;QAChB,WAAW,CAAC,gBAAgB,GAAG,CAAC,SAAiB,EAA6B,EAAE;YAC9E,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,CAAC,CAAA;YACnC,OAAO,qBAAqB,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBAC9C,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,SAAS,EAAE,GAAG;gBACd,GAAG,EAAE,GAAG,CAAC,GAAG;aACb,CAAC,CAAC,CAAA;QACL,CAAC,CAAA;QACD,WAAW,CAAC,cAAc,GAAG,CAAC,SAAiB,EAAY,EAAE,CAC3D,eAAe,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAA;QAC1C,WAAW,CAAC,eAAe,GAAG,CAAC,SAAiB,EAAE,GAA4B,EAAW,EAAE,CACzF,oBAAoB,CAAC,GAAG,EAAE,YAAY,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;QACzE,WAAW,CAAC,YAAY,GAAG,CAAC,SAAiB,EAAE,KAAc,EAA2B,EAAE,CACxF,iBAAiB,CAAC,CAAC,KAAK,IAAI,IAAI,CAAwB,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,CAAA;IACtF,CAAC;IAED,OAAO,WAAW,CAAA;AACpB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,KAAK,CACnB,OAAkD;IAElD,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,WAAW,EAAE,GAAG,OAAO,CAAA;IAEpD,0EAA0E;IAC1E,kDAAkD;IAClD,MAAM,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IACtD,MAAM,YAAY,GAAG,CAAC,SAAiB,EAAkB,EAAE,CACzD,qBAAqB,CAAC,SAAS,EAAE,oBAAoB,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAA;IAE7E,MAAM,WAAW,GAAgC;QAC/C,IAAI,EAAE,OAAO;QACb,GAAG,WAAW;QAEd,6EAA6E;QAC7E,2EAA2E;QAC3E,gEAAgE;QAChE,eAAe,EAAE;YACf,UAAU,EAAE,wDAAwD;SACrE;QAED,KAAK,EAAE;YACL,uEAAuE;YACvE,mEAAmE;YACnE,yHAAyH;YACzH,YAAY,EAAE,KAAK,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAO,EAAE,EAAE;gBACrE,MAAM,UAAU,GAAG,YAAY,EAAE,CAAC,QAAQ,CAAC,CAAA;gBAE3C,0DAA0D;gBAC1D,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;oBACpD,OAAO,UAAU,CAAA;gBACnB,CAAC;gBAED,mEAAmE;gBACnE,wEAAwE;gBACxE,2BAA2B;gBAC3B,IACE,OAAO,UAAU,KAAK,QAAQ;oBAC9B,UAAU,IAAI,UAAU;oBACxB,KAAK,IAAI,UAAU;oBACnB,OAAO,IAAI,UAAU;oBACrB,QAAQ,IAAI,UAAU,EACtB,CAAC;oBACD,OAAO,UAA2B,CAAA;gBACpC,CAAC;gBAED,6CAA6C;gBAC7C,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;oBAC3B,yBAAyB;oBACzB,MAAM,OAAO,GAAG,UAAU,CAAA;oBAC1B,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAA;oBAC/C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;oBAEvC,+CAA+C;oBAC/C,MAAM,QAAQ,GAAG,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,WAAW,CACjD,WAAW,CAAC,OAAO,EACnB,OAAO,EACP,MAAM,EACN;wBACE,UAAU,EAAE,WAAW,CAAC,UAAU;wBAClC,eAAe,EAAE,WAAW,CAAC,eAAe;qBAC7C,CACF,CAAkB,CAAA;oBAEnB,sEAAsE;oBACtE,IAAI,WAAW,CAAC,gBAAgB,IAAI,IAAI,IAAI,QAAQ,EAAE,CAAC;wBACrD,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAyB,CAAA;wBAC1D,IAAI,WAAW,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC;4BACxC,IAAI,CAAC;gCACH,MAAM,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC,CAAA;4BAChD,CAAC;4BAAC,OAAO,KAAK,EAAE,CAAC;gCACf,yCAAyC;gCACzC,OAAO,CAAC,KAAK,CAAC,gCAAgC,WAAW,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAA;4BAC9E,CAAC;wBACH,CAAC;oBACH,CAAC;oBAED,OAAO,QAAQ,CAAA;gBACjB,CAAC;gBAED,0DAA0D;gBAC1D,OAAO,UAAU,CAAA;YACnB,CAAC;YAED,yHAAyH;YACzH,cAAc,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAO,EAAE,EAAE;gBAC5E,wEAAwE;gBACxE,IAAI,SAAS,KAAK,QAAQ,IAAI,WAAW,CAAC,eAAe,EAAE,CAAC;oBAC1D,MAAM,aAAa,GAAG,YAAY,EAAE,CAAC,QAAQ,CAAyB,CAAA;oBAEtE,IAAI,aAAa,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,CAAC,QAAQ,EAAE,CAAC;wBACjF,IAAI,CAAC;4BACH,MAAM,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,aAAa,CAAC,CAAA;wBAClD,CAAC;wBAAC,OAAO,KAAK,EAAE,CAAC;4BACf,yCAAyC;4BACzC,OAAO,CAAC,KAAK,CAAC,sCAAsC,aAAa,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAA;wBACtF,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YACD,wCAAwC;YACxC,GAAG,SAAS;SACb;QAED,YAAY,EAAE,CAAC,UAAkB,EAAE,UAA+B,EAAE,EAAE;YACpE,yEAAyE;YACzE,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;gBACnC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;gBACpB,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE;gBAC5B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,+CAA+C;gBAChE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;gBACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;gBAChB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;gBACjB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;gBAClB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;gBACtB,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE;gBAC3B,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;gBACtD,eAAe,EAAE,CAAC;qBACf,MAAM,CACL,CAAC,CAAC,MAAM,EAAE,EACV,CAAC,CAAC,MAAM,CAAC;oBACP,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,+CAA+C;oBAChE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;oBACjB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;oBAClB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;iBACjB,CAAC,CACH;qBACA,QAAQ,EAAE;aACd,CAAC,CAAA;YAEF,iCAAiC;YACjC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,mBAAmB,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAA;QAChE,CAAC;QAED,aAAa,EAAE,CAAC,UAAkB,EAAE,EAAE;YACpC,4BAA4B;YAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,CAAA;QACzC,CAAC;QAED,iBAAiB,EAAE,GAAG,EAAE;YACtB,0CAA0C;YAC1C,OAAO;gBACL,IAAI,EAAE,sBAAsB;gBAC5B,QAAQ,EAAE,IAAI;aACf,CAAA;QACH,CAAC;QAED,oBAAoB,EAAE,GAAG,EAAE;YACzB,OAAO;gBACL;oBACE,KAAK,EAAE,CAAC,eAAe,CAAC;oBACxB,IAAI,EAAE,yBAAyB;oBAC/B,QAAQ,EAAE,IAAI;iBACf;aACF,CAAA;QACH,CAAC;KACF,CAAA;IAED,6EAA6E;IAC7E,wDAAwD;IACxD,IAAI,WAAW,EAAE,CAAC;QAChB,WAAW,CAAC,gBAAgB,GAAG,CAAC,SAAiB,EAA6B,EAAE;YAC9E,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,CAAC,CAAA;YACnC,OAAO,sBAAsB,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBAC/C,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,SAAS,EAAE,GAAG;gBACd,GAAG,EAAE,GAAG,CAAC,GAAG;aACb,CAAC,CAAC,CAAA;QACL,CAAC,CAAA;QACD,WAAW,CAAC,cAAc,GAAG,CAAC,SAAiB,EAAY,EAAE,CAC3D,gBAAgB,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAA;QAC3C,WAAW,CAAC,eAAe,GAAG,CAAC,SAAiB,EAAE,GAA4B,EAAW,EAAE,CACzF,qBAAqB,CAAC,GAAG,EAAE,YAAY,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;QAC1E,WAAW,CAAC,YAAY,GAAG,CAAC,SAAiB,EAAE,KAAc,EAA2B,EAAE,CACxF,kBAAkB,CAAC,CAAC,KAAK,IAAI,IAAI,CAAyB,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,CAAA;IACxF,CAAC;IAED,OAAO,WAAW,CAAA;AACpB,CAAC"}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA"}
|
package/dist/utils/index.js
CHANGED
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure `metadata ↔ columns` mapper for the multi-column (Keystone-parity) mode
|
|
3
|
+
* of the `image()` / `file()` fields.
|
|
4
|
+
*
|
|
5
|
+
* Keystone stores an image across seven per-part columns and a file across
|
|
6
|
+
* three. To adopt a live Keystone database without a destructive migration (see
|
|
7
|
+
* ADR-0006), the storage fields can map onto those existing columns in place,
|
|
8
|
+
* assembling them into an {@link ImageMetadata} / {@link FileMetadata} on read
|
|
9
|
+
* and splitting a metadata value back into columns on write.
|
|
10
|
+
*
|
|
11
|
+
* This module is intentionally pure: no I/O, no storage-provider calls, no
|
|
12
|
+
* dependency on the field builders. It is unit-tested in isolation
|
|
13
|
+
* (`multi-column.test.ts`) and the read/write hooks call it.
|
|
14
|
+
*/
|
|
15
|
+
import type { FileMetadata, ImageMetadata } from '../config/types.js';
|
|
16
|
+
/**
|
|
17
|
+
* The logical "parts" of a Keystone image field. Each maps to one physical
|
|
18
|
+
* column (whose physical name is configurable via `@map`).
|
|
19
|
+
*/
|
|
20
|
+
export type ImageColumnPart = 'url' | 'width' | 'height' | 'filesize' | 'contentType' | 'contentDisposition' | 'pathname';
|
|
21
|
+
/**
|
|
22
|
+
* The logical "parts" of a Keystone file field.
|
|
23
|
+
*/
|
|
24
|
+
export type FileColumnPart = 'filename' | 'filesize' | 'url';
|
|
25
|
+
/** Ordered list of image parts, matching Keystone's column layout. */
|
|
26
|
+
export declare const IMAGE_COLUMN_PARTS: readonly ImageColumnPart[];
|
|
27
|
+
/** Ordered list of file parts, matching Keystone's column layout. */
|
|
28
|
+
export declare const FILE_COLUMN_PARTS: readonly FileColumnPart[];
|
|
29
|
+
/**
|
|
30
|
+
* Map of image part → physical column name (the value used in `@map`).
|
|
31
|
+
* Defaults follow Keystone's `<field>_<part>` naming.
|
|
32
|
+
*/
|
|
33
|
+
export type ImageColumnMap = Record<ImageColumnPart, string>;
|
|
34
|
+
/** Map of file part → physical column name. */
|
|
35
|
+
export type FileColumnMap = Record<FileColumnPart, string>;
|
|
36
|
+
/** A single physical column to emit for a multi-column field. */
|
|
37
|
+
export interface MultiColumnDescriptor {
|
|
38
|
+
/** The Prisma model field name (the property carrying `@map`). */
|
|
39
|
+
name: string;
|
|
40
|
+
/** The Prisma scalar type. */
|
|
41
|
+
type: 'String' | 'Int';
|
|
42
|
+
/** The physical column name used in `@map`. */
|
|
43
|
+
map: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build the default per-part column-name map for an image field, following
|
|
47
|
+
* Keystone's `<field>_<part>` convention.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* keystoneImageColumnMap('image') // { url: 'image_url', width: 'image_width', ... }
|
|
51
|
+
*/
|
|
52
|
+
export declare function keystoneImageColumnMap(fieldName: string): ImageColumnMap;
|
|
53
|
+
/**
|
|
54
|
+
* Build the default per-part column-name map for a file field.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* keystoneFileColumnMap('file') // { filename: 'file_filename', filesize: 'file_filesize', url: 'file_url' }
|
|
58
|
+
*/
|
|
59
|
+
export declare function keystoneFileColumnMap(fieldName: string): FileColumnMap;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the effective image column map: defaults from {@link keystoneImageColumnMap},
|
|
62
|
+
* with any caller-supplied per-part overrides applied on top.
|
|
63
|
+
*/
|
|
64
|
+
export declare function resolveImageColumnMap(fieldName: string, overrides?: Partial<ImageColumnMap>): ImageColumnMap;
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the effective file column map.
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveFileColumnMap(fieldName: string, overrides?: Partial<FileColumnMap>): FileColumnMap;
|
|
69
|
+
/**
|
|
70
|
+
* Describe the physical columns an image field emits in multi-column mode.
|
|
71
|
+
* Each descriptor's `name` is the Prisma model field name and `map` is the
|
|
72
|
+
* physical column it maps to (here they are identical so the schema reads
|
|
73
|
+
* naturally over the live columns).
|
|
74
|
+
*/
|
|
75
|
+
export declare function imageColumnDescriptors(map: ImageColumnMap): MultiColumnDescriptor[];
|
|
76
|
+
/**
|
|
77
|
+
* Describe the physical columns a file field emits in multi-column mode.
|
|
78
|
+
*/
|
|
79
|
+
export declare function fileColumnDescriptors(map: FileColumnMap): MultiColumnDescriptor[];
|
|
80
|
+
/** The physical column field names an image field owns (for read stripping). */
|
|
81
|
+
export declare function imageColumnNames(map: ImageColumnMap): string[];
|
|
82
|
+
/** The physical column field names a file field owns (for read stripping). */
|
|
83
|
+
export declare function fileColumnNames(map: FileColumnMap): string[];
|
|
84
|
+
/**
|
|
85
|
+
* Assemble the per-part image columns from a database row into an
|
|
86
|
+
* {@link ImageMetadata}.
|
|
87
|
+
*
|
|
88
|
+
* Returns `null` when the row carries no image at all (the `url` column is the
|
|
89
|
+
* authoritative presence signal — Keystone leaves every part NULL for an empty
|
|
90
|
+
* image). Partially-populated rows (e.g. only `image_url`) still assemble into a
|
|
91
|
+
* valid object, with missing scalar parts defaulted (width/height/size → 0).
|
|
92
|
+
*
|
|
93
|
+
* `storageProvider` is supplied by the caller (the field knows which provider it
|
|
94
|
+
* is bound to); legacy columns do not carry it.
|
|
95
|
+
*/
|
|
96
|
+
export declare function assembleImageMetadata(row: Record<string, unknown>, map: ImageColumnMap, storageProvider: string): ImageMetadata | null;
|
|
97
|
+
/**
|
|
98
|
+
* Split an {@link ImageMetadata} (or `null`) back into the per-part image
|
|
99
|
+
* columns for writing. A `null`/`undefined` metadata clears every column.
|
|
100
|
+
*/
|
|
101
|
+
export declare function splitImageMetadata(metadata: ImageMetadata | null | undefined, map: ImageColumnMap): Record<string, string | number | null>;
|
|
102
|
+
/**
|
|
103
|
+
* Assemble the per-part file columns from a database row into a
|
|
104
|
+
* {@link FileMetadata}. Returns `null` when no file is present.
|
|
105
|
+
*/
|
|
106
|
+
export declare function assembleFileMetadata(row: Record<string, unknown>, map: FileColumnMap, storageProvider: string): FileMetadata | null;
|
|
107
|
+
/**
|
|
108
|
+
* Split a {@link FileMetadata} (or `null`) back into the per-part file columns
|
|
109
|
+
* for writing. A `null`/`undefined` metadata clears every column.
|
|
110
|
+
*/
|
|
111
|
+
export declare function splitFileMetadata(metadata: FileMetadata | null | undefined, map: FileColumnMap): Record<string, string | number | null>;
|
|
112
|
+
//# sourceMappingURL=multi-column.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-column.d.ts","sourceRoot":"","sources":["../../src/utils/multi-column.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAErE;;;GAGG;AACH,MAAM,MAAM,eAAe,GACvB,KAAK,GACL,OAAO,GACP,QAAQ,GACR,UAAU,GACV,aAAa,GACb,oBAAoB,GACpB,UAAU,CAAA;AAEd;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,UAAU,GAAG,KAAK,CAAA;AAE5D,sEAAsE;AACtE,eAAO,MAAM,kBAAkB,EAAE,SAAS,eAAe,EAQ/C,CAAA;AAEV,qEAAqE;AACrE,eAAO,MAAM,iBAAiB,EAAE,SAAS,cAAc,EAA6C,CAAA;AAoBpG;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAA;AAE5D,+CAA+C;AAC/C,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAA;AAE1D,iEAAiE;AACjE,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAA;IACZ,8BAA8B;IAC9B,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAA;IACtB,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAUxE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAMtE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAClC,cAAc,CAEhB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GACjC,aAAa,CAEf;AAeD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,GAAG,qBAAqB,EAAE,CAMnF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,aAAa,GAAG,qBAAqB,EAAE,CAMjF;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,EAAE,CAE9D;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,EAAE,CAE5D;AAqBD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,cAAc,EACnB,eAAe,EAAE,MAAM,GACtB,aAAa,GAAG,IAAI,CAiCtB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,EAC1C,GAAG,EAAE,cAAc,GAClB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,CA4BxC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,aAAa,EAClB,eAAe,EAAE,MAAM,GACtB,YAAY,GAAG,IAAI,CAoBrB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,EACzC,GAAG,EAAE,aAAa,GACjB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,CAcxC"}
|