@tryghost/metafield-types 0.1.0 → 0.1.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @tryghost/metafield-types
2
+
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 0.1.1
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated zod dependency
package/README.md CHANGED
@@ -62,7 +62,7 @@ green. So **`src/structure.ts` must not import anything** — an ESLint rule enf
62
62
  it, relative imports included, since anything reached through one is bundled too.
63
63
  Whatever needs an import belongs in `./index`, which is free to use them.
64
64
 
65
- `ghost/core` is CommonJS but consumes this package via `require()`, which works
65
+ `ghost` is CommonJS but consumes this package via `require()`, which works
66
66
  on Ghost's Node version (22.13+/24) through Node's `require(esm)` support. That
67
67
  support has one hard constraint: **no top-level `await`** anywhere in this
68
68
  package's module graph — it makes the graph async and `require()` of it throws
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryghost/metafield-types",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Ghost member custom field types: which types exist, what each is made of, and how a value of each is validated",
5
5
  "license": "MIT",
6
6
  "author": "Ghost Foundation",
@@ -34,7 +34,7 @@
34
34
  }
35
35
  },
36
36
  "dependencies": {
37
- "zod": "4.4.3"
37
+ "zod": "4.6.5"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsc",
package/build/csv.d.ts DELETED
@@ -1,39 +0,0 @@
1
- import { type FieldType } from './index.ts';
2
- import type { FieldIdentityString } from './identity.ts';
3
- /** A field definition reduced to what CSV needs to know about it. */
4
- export interface CsvField {
5
- namespace: string;
6
- key: string;
7
- type: FieldType;
8
- }
9
- /** A column a field occupies, and which part of the field's value it holds. */
10
- export interface CsvFieldColumn {
11
- column: string;
12
- /** The part this column holds, or null where the field's whole value is one column. */
13
- subField: string | null;
14
- }
15
- export declare function csvColumnsForField(field: CsvField): CsvFieldColumn[];
16
- export declare function isMetafieldColumn(column: string): boolean;
17
- export declare function csvCellsForFields(fields: readonly CsvField[], values: Record<string, Record<string, unknown> | undefined>): Record<string, string>;
18
- /**
19
- * The inverse of `csvCellsForFields`: read a row's metafield cells into the values a
20
- * member write takes. Only the passed fields are read, so a column naming no active
21
- * field is dropped rather than erroring.
22
- *
23
- * `decodeCell` turns a raw cell into its value; it defaults to identity. The caller owns
24
- * any de-serialization the specific file needs — the members importer passes one that
25
- * strips the export's formula guard — so this stays pure vocabulary and holds no knowledge
26
- * of how any particular CSV escapes its cells.
27
- *
28
- * A field is written only from a non-blank cell; a blank or absent cell leaves the
29
- * existing value untouched, not cleared — matching how the importer keeps a blank name or
30
- * note, so re-importing a partly-filled export can't wipe values a publisher didn't
31
- * touch. A composite whose every cell is blank is omitted too, because the export writes
32
- * "no address" and an all-blank address identically; one with any filled cell is read
33
- * from its non-blank cells and validated whole (a malformed sub-field fails the row).
34
- * No sub-field is required, so a row carrying only some of an address reads as only
35
- * those sub-fields. A blank sub-field cell reads as no data for that sub-field, the
36
- * same way a blank cell reads as no data for a whole field.
37
- */
38
- export declare function fieldValuesFromCsvRow(fields: readonly CsvField[], row: Record<string, unknown>, decodeCell?: (cell: string) => string): Record<FieldIdentityString, unknown>;
39
- //# sourceMappingURL=csv.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"csv.d.ts","sourceRoot":"","sources":["../src/csv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAqBzD,qEAAqE;AACrE,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,SAAS,CAAC;CACjB;AAcD,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAID,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,QAAQ,GAAG,cAAc,EAAE,CAMpE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzD;AAeD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,QAAQ,EAAE,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,GAC1D,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBxB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,QAAQ,EAAE,EAC3B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,UAAU,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAuB,GACpD,MAAM,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAqCtC"}
package/build/csv.js DELETED
@@ -1,119 +0,0 @@
1
- import { subFieldsOf } from './index.js';
2
- import { QUALIFIER, SEPARATOR, formatIdentity } from './identity.js';
3
- /**
4
- * How a field's value maps onto CSV columns.
5
- *
6
- * CSV is flat and a value need not be, so a composite occupies one column per
7
- * sub-field. That shape is derived from the type's own value schema rather than
8
- * declared alongside it: the two cannot drift, and it stays correct as sub-fields
9
- * are added, removed, or made optional — an optional sub-field is still a column,
10
- * it just holds an empty cell more often.
11
- *
12
- * The same column names are the vocabulary the importer maps onto, so an exported
13
- * file re-imports without the publisher remapping anything by hand.
14
- */
15
- const PREFIX = `${QUALIFIER}${SEPARATOR}`;
16
- function columnIdentity(field) {
17
- return formatIdentity({ namespace: field.namespace, key: field.key, partPath: null });
18
- }
19
- function toCell(value) {
20
- return value === undefined || value === null ? '' : String(value);
21
- }
22
- // A cell holding nothing but whitespace carries no more data than an empty one, so both
23
- // read as blank. Without this a stray space is a value: it would be stored as one on a
24
- // scalar, and on a composite it would make an otherwise untouched address fail its "at
25
- // least one part filled in" rule and take the whole member row down with it.
26
- function isBlank(cell) {
27
- return cell.trim() === '';
28
- }
29
- // Shares csvCellsForFields' column derivation, so a field is written, read, and offered
30
- // as a mapping target under one set of column names.
31
- export function csvColumnsForField(field) {
32
- const column = `${PREFIX}${columnIdentity(field)}`;
33
- const subFields = subFieldsOf(field.type);
34
- return subFields
35
- ? subFields.map((sub) => ({ column: `${column}${SEPARATOR}${sub}`, subField: sub }))
36
- : [{ column, subField: null }];
37
- }
38
- export function isMetafieldColumn(column) {
39
- return column === QUALIFIER || column.startsWith(PREFIX);
40
- }
41
- // A missing column and a column present but blank mean different things here, so
42
- // presence has to be detectable. The CSV parser only ever puts strings in these
43
- // cells, which makes "not a string" a reliable stand-in for "no such column".
44
- function cellFor(row, field, tail) {
45
- const suffix = tail === null ? '' : `${SEPARATOR}${tail}`;
46
- const cell = row[`${PREFIX}${columnIdentity(field)}${suffix}`];
47
- return typeof cell === 'string' ? cell : undefined;
48
- }
49
- export function csvCellsForFields(fields, values) {
50
- const cells = {};
51
- for (const field of fields) {
52
- const value = values[field.namespace]?.[field.key];
53
- const subFields = subFieldsOf(field.type);
54
- const column = `${PREFIX}${columnIdentity(field)}`;
55
- if (!subFields) {
56
- cells[column] = toCell(value);
57
- continue;
58
- }
59
- const composite = (value ?? {});
60
- for (const sub of subFields) {
61
- cells[`${column}${SEPARATOR}${sub}`] = toCell(composite[sub]);
62
- }
63
- }
64
- return cells;
65
- }
66
- /**
67
- * The inverse of `csvCellsForFields`: read a row's metafield cells into the values a
68
- * member write takes. Only the passed fields are read, so a column naming no active
69
- * field is dropped rather than erroring.
70
- *
71
- * `decodeCell` turns a raw cell into its value; it defaults to identity. The caller owns
72
- * any de-serialization the specific file needs — the members importer passes one that
73
- * strips the export's formula guard — so this stays pure vocabulary and holds no knowledge
74
- * of how any particular CSV escapes its cells.
75
- *
76
- * A field is written only from a non-blank cell; a blank or absent cell leaves the
77
- * existing value untouched, not cleared — matching how the importer keeps a blank name or
78
- * note, so re-importing a partly-filled export can't wipe values a publisher didn't
79
- * touch. A composite whose every cell is blank is omitted too, because the export writes
80
- * "no address" and an all-blank address identically; one with any filled cell is read
81
- * from its non-blank cells and validated whole (a malformed sub-field fails the row).
82
- * No sub-field is required, so a row carrying only some of an address reads as only
83
- * those sub-fields. A blank sub-field cell reads as no data for that sub-field, the
84
- * same way a blank cell reads as no data for a whole field.
85
- */
86
- export function fieldValuesFromCsvRow(fields, row, decodeCell = (cell) => cell) {
87
- const values = {};
88
- for (const field of fields) {
89
- const subFields = subFieldsOf(field.type);
90
- if (!subFields) {
91
- const cell = cellFor(row, field, null);
92
- if (cell !== undefined) {
93
- const decoded = decodeCell(cell);
94
- if (!isBlank(decoded)) {
95
- values[columnIdentity(field)] = decoded;
96
- }
97
- }
98
- continue;
99
- }
100
- let anyColumnPresent = false;
101
- const composite = {};
102
- for (const sub of subFields) {
103
- const cell = cellFor(row, field, sub);
104
- if (cell === undefined) {
105
- continue;
106
- }
107
- anyColumnPresent = true;
108
- const decoded = decodeCell(cell);
109
- if (!isBlank(decoded)) {
110
- composite[sub] = decoded;
111
- }
112
- }
113
- if (anyColumnPresent && Object.keys(composite).length > 0) {
114
- values[columnIdentity(field)] = composite;
115
- }
116
- }
117
- return values;
118
- }
119
- //# sourceMappingURL=csv.js.map
package/build/csv.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"csv.js","sourceRoot":"","sources":["../src/csv.ts"],"names":[],"mappings":"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"}