@tryghost/metafield-types 0.1.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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013-2026 Ghost Foundation
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # @tryghost/metafield-types
2
+
3
+ The types of member custom field Ghost supports: which types exist, which parts a
4
+ type is made of, the type of value each part holds, and how a value of each type is
5
+ validated. Ghost, its Admin and Portal use it, and so can an integration that offers
6
+ inputs for a site's custom fields, such as the Ghost Zapier integration.
7
+
8
+ ```bash
9
+ npm install @tryghost/metafield-types
10
+ ```
11
+
12
+ ## Entry points
13
+
14
+ - `@tryghost/metafield-types/structure`: the type ids, each type's parts and the
15
+ type of value each part holds. It depends on nothing.
16
+ - `@tryghost/metafield-types/countries`: the two-letter country codes a country
17
+ part accepts. It depends on nothing.
18
+ - `@tryghost/metafield-types/identity`: how a field is named in filters and
19
+ payloads (`custom.<key>`). It depends on nothing.
20
+ - `@tryghost/metafield-types`: the structure above, plus the rules that validate
21
+ a value of each type. These are built on [zod](https://zod.dev), and only this
22
+ entry point loads it, so an integration that only draws inputs can use the three
23
+ entry points above without zod ever being loaded.
24
+
25
+ ## Compatibility
26
+
27
+ An integration using this package talks to sites on many Ghost versions, so a type
28
+ never changes once it has shipped: it keeps its parts and the type of value each
29
+ holds. New structure arrives as a new type. An integration should skip a type it
30
+ doesn't recognise, which is what happens when a site is on a newer Ghost than the
31
+ package the integration was built with.
32
+
33
+ ## Develop
34
+
35
+ This is a workspace package in the Ghost monorepo. From the repo root:
36
+
37
+ ```bash
38
+ pnpm --filter @tryghost/metafield-types build # compile to build/ with tsc (ESM)
39
+ pnpm --filter @tryghost/metafield-types test # type-check + unit tests
40
+ pnpm --filter @tryghost/metafield-types dev # rebuild on change
41
+ ```
42
+
43
+ In-monorepo consumers resolve this package via the `source` export condition
44
+ (raw `src/*.ts`, no build needed in dev/test). Production and any published
45
+ tarball use the compiled `build/` output.
46
+
47
+ This package is ESM-only and compiled with `tsc` (`module: nodenext`). Relative
48
+ imports in `src/` must carry an explicit extension; write the real `.ts` one —
49
+ `import {x} from './x.ts'` — and `tsc` rewrites it to `.js` on emit
50
+ (`rewriteRelativeImportExtensions`).
51
+
52
+ `./structure` is the half of the catalog a renderer needs — which parts a field type
53
+ has, and what each holds — and it stands apart from `./index` so that importing it
54
+ costs a bundle almost nothing. The rules in `./index` are built on zod, some
55
+ seventeen kilobytes gzipped that a renderer never runs. Portal imports `./structure`
56
+ and loads on every page view of every themed site, so the difference reaches every
57
+ visitor of every Ghost site.
58
+
59
+ That holds only while `./structure` depends on nothing, and no bundler enforces it: an
60
+ import added here would ship to every one of those visitors with every check still
61
+ green. So **`src/structure.ts` must not import anything** — an ESLint rule enforces
62
+ it, relative imports included, since anything reached through one is bundled too.
63
+ Whatever needs an import belongs in `./index`, which is free to use them.
64
+
65
+ `ghost/core` is CommonJS but consumes this package via `require()`, which works
66
+ on Ghost's Node version (22.13+/24) through Node's `require(esm)` support. That
67
+ support has one hard constraint: **no top-level `await`** anywhere in this
68
+ package's module graph — it makes the graph async and `require()` of it throws
69
+ `ERR_REQUIRE_ASYNC_MODULE`. Keep module-level initialization synchronous. (An
70
+ ESLint rule enforces this.)
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Every ISO 3166-1 country an address may name, as two-letter codes.
3
+ *
4
+ * Here because both renderers of an address need the same list and cannot import each
5
+ * other's: Admin's picker and Portal's select must offer the same countries, or a member
6
+ * could record one staff cannot read back. Codes only, no names: a browser names a
7
+ * region in the reader's language through `Intl.DisplayNames`, and a bundled name list
8
+ * would be English and stale. Kosovo (XK) is here as the one user-assigned code in
9
+ * common use, and Ascension Island (AC) and Tristan da Cunha (TA) as the exceptionally
10
+ * reserved codes real addresses use, which Stripe ships to and a checkout can store.
11
+ */
12
+ export declare const COUNTRY_CODES: readonly ['AC', 'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AO', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'SS', 'ST', 'SV', 'SX', 'SY', 'SZ', 'TA', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'XK', 'YE', 'YT', 'ZA', 'ZM', 'ZW'];
13
+ export type CountryCode = (typeof COUNTRY_CODES)[number];
14
+ //# sourceMappingURL=countries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"countries.d.ts","sourceRoot":"","sources":["../src/countries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,YACxB,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,CACI,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Every ISO 3166-1 country an address may name, as two-letter codes.
3
+ *
4
+ * Here because both renderers of an address need the same list and cannot import each
5
+ * other's: Admin's picker and Portal's select must offer the same countries, or a member
6
+ * could record one staff cannot read back. Codes only, no names: a browser names a
7
+ * region in the reader's language through `Intl.DisplayNames`, and a bundled name list
8
+ * would be English and stale. Kosovo (XK) is here as the one user-assigned code in
9
+ * common use, and Ascension Island (AC) and Tristan da Cunha (TA) as the exceptionally
10
+ * reserved codes real addresses use, which Stripe ships to and a checkout can store.
11
+ */
12
+ export const COUNTRY_CODES = [
13
+ 'AC',
14
+ 'AD',
15
+ 'AE',
16
+ 'AF',
17
+ 'AG',
18
+ 'AI',
19
+ 'AL',
20
+ 'AM',
21
+ 'AO',
22
+ 'AQ',
23
+ 'AR',
24
+ 'AS',
25
+ 'AT',
26
+ 'AU',
27
+ 'AW',
28
+ 'AX',
29
+ 'AZ',
30
+ 'BA',
31
+ 'BB',
32
+ 'BD',
33
+ 'BE',
34
+ 'BF',
35
+ 'BG',
36
+ 'BH',
37
+ 'BI',
38
+ 'BJ',
39
+ 'BL',
40
+ 'BM',
41
+ 'BN',
42
+ 'BO',
43
+ 'BQ',
44
+ 'BR',
45
+ 'BS',
46
+ 'BT',
47
+ 'BV',
48
+ 'BW',
49
+ 'BY',
50
+ 'BZ',
51
+ 'CA',
52
+ 'CC',
53
+ 'CD',
54
+ 'CF',
55
+ 'CG',
56
+ 'CH',
57
+ 'CI',
58
+ 'CK',
59
+ 'CL',
60
+ 'CM',
61
+ 'CN',
62
+ 'CO',
63
+ 'CR',
64
+ 'CU',
65
+ 'CV',
66
+ 'CW',
67
+ 'CX',
68
+ 'CY',
69
+ 'CZ',
70
+ 'DE',
71
+ 'DJ',
72
+ 'DK',
73
+ 'DM',
74
+ 'DO',
75
+ 'DZ',
76
+ 'EC',
77
+ 'EE',
78
+ 'EG',
79
+ 'EH',
80
+ 'ER',
81
+ 'ES',
82
+ 'ET',
83
+ 'FI',
84
+ 'FJ',
85
+ 'FK',
86
+ 'FM',
87
+ 'FO',
88
+ 'FR',
89
+ 'GA',
90
+ 'GB',
91
+ 'GD',
92
+ 'GE',
93
+ 'GF',
94
+ 'GG',
95
+ 'GH',
96
+ 'GI',
97
+ 'GL',
98
+ 'GM',
99
+ 'GN',
100
+ 'GP',
101
+ 'GQ',
102
+ 'GR',
103
+ 'GS',
104
+ 'GT',
105
+ 'GU',
106
+ 'GW',
107
+ 'GY',
108
+ 'HK',
109
+ 'HM',
110
+ 'HN',
111
+ 'HR',
112
+ 'HT',
113
+ 'HU',
114
+ 'ID',
115
+ 'IE',
116
+ 'IL',
117
+ 'IM',
118
+ 'IN',
119
+ 'IO',
120
+ 'IQ',
121
+ 'IR',
122
+ 'IS',
123
+ 'IT',
124
+ 'JE',
125
+ 'JM',
126
+ 'JO',
127
+ 'JP',
128
+ 'KE',
129
+ 'KG',
130
+ 'KH',
131
+ 'KI',
132
+ 'KM',
133
+ 'KN',
134
+ 'KP',
135
+ 'KR',
136
+ 'KW',
137
+ 'KY',
138
+ 'KZ',
139
+ 'LA',
140
+ 'LB',
141
+ 'LC',
142
+ 'LI',
143
+ 'LK',
144
+ 'LR',
145
+ 'LS',
146
+ 'LT',
147
+ 'LU',
148
+ 'LV',
149
+ 'LY',
150
+ 'MA',
151
+ 'MC',
152
+ 'MD',
153
+ 'ME',
154
+ 'MF',
155
+ 'MG',
156
+ 'MH',
157
+ 'MK',
158
+ 'ML',
159
+ 'MM',
160
+ 'MN',
161
+ 'MO',
162
+ 'MP',
163
+ 'MQ',
164
+ 'MR',
165
+ 'MS',
166
+ 'MT',
167
+ 'MU',
168
+ 'MV',
169
+ 'MW',
170
+ 'MX',
171
+ 'MY',
172
+ 'MZ',
173
+ 'NA',
174
+ 'NC',
175
+ 'NE',
176
+ 'NF',
177
+ 'NG',
178
+ 'NI',
179
+ 'NL',
180
+ 'NO',
181
+ 'NP',
182
+ 'NR',
183
+ 'NU',
184
+ 'NZ',
185
+ 'OM',
186
+ 'PA',
187
+ 'PE',
188
+ 'PF',
189
+ 'PG',
190
+ 'PH',
191
+ 'PK',
192
+ 'PL',
193
+ 'PM',
194
+ 'PN',
195
+ 'PR',
196
+ 'PS',
197
+ 'PT',
198
+ 'PW',
199
+ 'PY',
200
+ 'QA',
201
+ 'RE',
202
+ 'RO',
203
+ 'RS',
204
+ 'RU',
205
+ 'RW',
206
+ 'SA',
207
+ 'SB',
208
+ 'SC',
209
+ 'SD',
210
+ 'SE',
211
+ 'SG',
212
+ 'SH',
213
+ 'SI',
214
+ 'SJ',
215
+ 'SK',
216
+ 'SL',
217
+ 'SM',
218
+ 'SN',
219
+ 'SO',
220
+ 'SR',
221
+ 'SS',
222
+ 'ST',
223
+ 'SV',
224
+ 'SX',
225
+ 'SY',
226
+ 'SZ',
227
+ 'TA',
228
+ 'TC',
229
+ 'TD',
230
+ 'TF',
231
+ 'TG',
232
+ 'TH',
233
+ 'TJ',
234
+ 'TK',
235
+ 'TL',
236
+ 'TM',
237
+ 'TN',
238
+ 'TO',
239
+ 'TR',
240
+ 'TT',
241
+ 'TV',
242
+ 'TW',
243
+ 'TZ',
244
+ 'UA',
245
+ 'UG',
246
+ 'UM',
247
+ 'US',
248
+ 'UY',
249
+ 'UZ',
250
+ 'VA',
251
+ 'VC',
252
+ 'VE',
253
+ 'VG',
254
+ 'VI',
255
+ 'VN',
256
+ 'VU',
257
+ 'WF',
258
+ 'WS',
259
+ 'XK',
260
+ 'YE',
261
+ 'YT',
262
+ 'ZA',
263
+ 'ZM',
264
+ 'ZW',
265
+ ];
266
+ //# sourceMappingURL=countries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"countries.js","sourceRoot":"","sources":["../src/countries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;CACI,CAAC"}
package/build/csv.d.ts ADDED
@@ -0,0 +1,39 @@
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
@@ -0,0 +1 @@
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 ADDED
@@ -0,0 +1,119 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csv.js","sourceRoot":"","sources":["../src/csv.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * How a field is named on every surface that writes its name down as text: CSV column
3
+ * headers, filter values, error paths, URLs.
4
+ *
5
+ * The name is the namespace that declared the field and the field's key, joined by a
6
+ * dot — `custom.company`. A value made of several parts, such as an address, appends
7
+ * the part: `custom.shipping_address.country`. Splitting on dots by position is safe
8
+ * because a key can never contain one; that is enforced where keys are minted.
9
+ *
10
+ * `metafields` is the qualifier, not part of the identity: it names the container, and
11
+ * each context writes it exactly once — the payload key, the route prefix, the CSV
12
+ * column prefix, the filter relation alias — never twice.
13
+ */
14
+ export declare const QUALIFIER = "metafields";
15
+ export declare const SEPARATOR = ".";
16
+ /**
17
+ * The namespace holding the fields a site's own staff define.
18
+ *
19
+ * It is a constant only because the table storing these fields has no namespace
20
+ * column: it holds this namespace's fields and nothing else, so the query layer has
21
+ * to supply the name the rows implicitly carry. Nothing outside that layer should
22
+ * compare against this value — a namespace nobody has declared fields in is an empty
23
+ * namespace, not an error.
24
+ */
25
+ export declare const CUSTOM_NAMESPACE = "custom";
26
+ export declare const IDENTITY_SEGMENT: RegExp;
27
+ /** A formatted identity — `namespace.key`, or `namespace.key.partPath` for one part. */
28
+ export type FieldIdentityString = string;
29
+ export interface FieldIdentity {
30
+ namespace: string;
31
+ key: string;
32
+ partPath: string | null;
33
+ }
34
+ export declare function formatIdentity({ namespace, key, partPath }: FieldIdentity): FieldIdentityString;
35
+ export declare function parseIdentity(identity: string): FieldIdentity | null;
36
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,SAAS,eAAe,CAAC;AAEtC,eAAO,MAAM,SAAS,MAAM,CAAC;AAE7B;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,WAAW,CAAC;AAEzC,eAAO,MAAM,gBAAgB,QAAiB,CAAC;AAE/C,wFAAwF;AACxF,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEzC,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,aAAa,GAAG,mBAAmB,CAG/F;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAUpE"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * How a field is named on every surface that writes its name down as text: CSV column
3
+ * headers, filter values, error paths, URLs.
4
+ *
5
+ * The name is the namespace that declared the field and the field's key, joined by a
6
+ * dot — `custom.company`. A value made of several parts, such as an address, appends
7
+ * the part: `custom.shipping_address.country`. Splitting on dots by position is safe
8
+ * because a key can never contain one; that is enforced where keys are minted.
9
+ *
10
+ * `metafields` is the qualifier, not part of the identity: it names the container, and
11
+ * each context writes it exactly once — the payload key, the route prefix, the CSV
12
+ * column prefix, the filter relation alias — never twice.
13
+ */
14
+ export const QUALIFIER = 'metafields';
15
+ export const SEPARATOR = '.';
16
+ /**
17
+ * The namespace holding the fields a site's own staff define.
18
+ *
19
+ * It is a constant only because the table storing these fields has no namespace
20
+ * column: it holds this namespace's fields and nothing else, so the query layer has
21
+ * to supply the name the rows implicitly carry. Nothing outside that layer should
22
+ * compare against this value — a namespace nobody has declared fields in is an empty
23
+ * namespace, not an error.
24
+ */
25
+ export const CUSTOM_NAMESPACE = 'custom';
26
+ export const IDENTITY_SEGMENT = /^[a-z0-9_]+$/;
27
+ export function formatIdentity({ namespace, key, partPath }) {
28
+ const identity = `${namespace}${SEPARATOR}${key}`;
29
+ return partPath ? `${identity}${SEPARATOR}${partPath}` : identity;
30
+ }
31
+ export function parseIdentity(identity) {
32
+ const [namespace, key, ...parts] = identity.split(SEPARATOR);
33
+ if (namespace === undefined ||
34
+ key === undefined ||
35
+ ![namespace, key, ...parts].every((segment) => IDENTITY_SEGMENT.test(segment))) {
36
+ return null;
37
+ }
38
+ return { namespace, key, partPath: parts.length > 0 ? parts.join(SEPARATOR) : null };
39
+ }
40
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC;AAEtC,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,CAAC;AAE7B;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,QAAQ,CAAC;AAEzC,MAAM,CAAC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAW/C,MAAM,UAAU,cAAc,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAiB;IACxE,MAAM,QAAQ,GAAG,GAAG,SAAS,GAAG,SAAS,GAAG,GAAG,EAAE,CAAC;IAClD,OAAO,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC7D,IACE,SAAS,KAAK,SAAS;QACvB,GAAG,KAAK,SAAS;QACjB,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAC9E,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACvF,CAAC"}
@@ -0,0 +1,175 @@
1
+ import { z } from 'zod';
2
+ import { type FieldType } from './structure.js';
3
+ export { FIELD_KINDS, FIELD_PARTS, FIELD_TYPE_IDS, PART_TYPE_IDS, partTypesOf, subFieldsOf, type FieldKind, type FieldType, type PartsOf, type PartType, } from './structure.js';
4
+ /**
5
+ * The shared catalog of member metafield types.
6
+ *
7
+ * ## What a field type is
8
+ *
9
+ * A publisher defines fields; a field has a type; a type says what a valid value is. That
10
+ * last statement lives here and nowhere else, so that Ghost core, which enforces it, and
11
+ * admin, which gives instant feedback against it, cannot disagree about what is valid.
12
+ *
13
+ * A type is one of two things. Most are a value in their own right, declared as the
14
+ * schema that value must satisfy. An address is a *record*: a set of named parts, each a
15
+ * value in the same sense, from which the whole type's schema is derived. Nothing stops a
16
+ * part being a record itself.
17
+ *
18
+ * ## What a write may say
19
+ *
20
+ * A value is text, and so is every part of one. A write names what it means to change: a
21
+ * name it does not mention is left as it was, and a name given an empty string is being
22
+ * cleared. That is why every part accepts empty regardless of its own rule — emptying is
23
+ * a statement about the write, not about the part.
24
+ *
25
+ * A name nobody recognizes is an error rather than a silent drop, at both depths. A
26
+ * misspelled field key is refused by the values service, which alone knows which fields a
27
+ * site has defined; a misspelled part is refused here, because a type's parts are declared
28
+ * in this file and nowhere else. Each is enforced where the names are known.
29
+ *
30
+ * ## What a failure says
31
+ *
32
+ * A rule carries the sentence shown when it is broken, so the two cannot drift. Each
33
+ * states what is expected rather than what went wrong, so one sentence covers every way
34
+ * of breaking that rule.
35
+ *
36
+ * The sentences are plain literals with no interpolation, so each is usable as a
37
+ * translation key. Nothing here translates: Ghost's parser reads `t()` calls out of the
38
+ * app that renders a string, so that app owns the step. Admin, the only one today, is not
39
+ * translated at all.
40
+ *
41
+ * Two failures keep zod's own wording, both reachable only by a client sending the wrong
42
+ * JSON shape: a composite handed something that is not an object, and a part nobody
43
+ * declared. Naming the offending key serves whoever has to fix that client.
44
+ *
45
+ * ## What this package will not do
46
+ *
47
+ * No presentation beyond that sentence: labels, icons and input controls belong to the
48
+ * frontend, and so does any wording that depends on where it appears rather than on which
49
+ * rule was broken. No storage: columns and codecs belong to the backend. One exception
50
+ * lives in `./csv` — how a value maps onto CSV columns — because both tiers need the same
51
+ * answer and a disagreement between them is a file that silently stops round-tripping.
52
+ *
53
+ * The line is whether more than one renderer must agree on a string, not presentation
54
+ * against validity — the sentences above are presentation. Admin is the only renderer
55
+ * today, so a part's label lives there, held against this file by a type. A Portal
56
+ * collection form, which cannot reach admin's packages, moves the labels here.
57
+ */
58
+ export declare const FieldTypeSchema: z.ZodEnum<{
59
+ address: "address";
60
+ long_text: "long_text";
61
+ short_text: "short_text";
62
+ }>;
63
+ /**
64
+ * What the member whose record it is may do with a field.
65
+ *
66
+ * A property of a definition rather than of the publisher's namespace, so it has an
67
+ * answer for any regime that ever stores definitions, not only the publisher's.
68
+ *
69
+ * Ordered: `write` includes being able to read. Naming the levels for a publisher is
70
+ * presentation and stays with whoever renders them.
71
+ */
72
+ export declare const MEMBER_ACCESS_LEVELS: readonly ['none', 'read', 'write'];
73
+ export type MemberAccess = (typeof MEMBER_ACCESS_LEVELS)[number];
74
+ export declare const MemberAccessSchema: z.ZodEnum<{
75
+ none: "none";
76
+ read: "read";
77
+ write: "write";
78
+ }>;
79
+ export declare const MEMBER_ACCESS: {
80
+ readonly none: 'none';
81
+ readonly read: 'read';
82
+ readonly write: 'write';
83
+ };
84
+ /**
85
+ * Where a change to a member's values was made, as a member's activity feed names it.
86
+ * Shared so a place added on the server is one Admin has to be told how to describe.
87
+ */
88
+ export declare const METAFIELD_CHANGE_SOURCES: readonly ['admin', 'admin_api', 'portal', 'import', 'checkout'];
89
+ export type MetafieldChangeSource = (typeof METAFIELD_CHANGE_SOURCES)[number];
90
+ /** Whether a source is one this build knows how to name; a newer server can send others. */
91
+ export declare const isMetafieldChangeSource: (source: string) => source is MetafieldChangeSource;
92
+ /** A field as an activity feed entry names it: its name is copied so the entry outlives a rename. */
93
+ export declare const MetafieldChangeEventFieldSchema: z.ZodObject<{
94
+ namespace: z.ZodString;
95
+ key: z.ZodString;
96
+ name: z.ZodString;
97
+ }, z.core.$strip>;
98
+ export type MetafieldChangeEventField = z.infer<typeof MetafieldChangeEventFieldSchema>;
99
+ /**
100
+ * An entry on a member's activity feed saying which fields a write changed, as the members
101
+ * events endpoint returns it. The server builds it and Admin reads it, so both are held to
102
+ * this one shape. `created_at` is a date on the server and its serialised string in a
103
+ * client. `source` stays open to places a newer server adds.
104
+ */
105
+ export interface MetafieldChangeEntry<TDate = string> {
106
+ id: string;
107
+ member_id: string;
108
+ written_by_type: string;
109
+ written_by_id: string | null;
110
+ source: MetafieldChangeSource | (string & {});
111
+ metafields: MetafieldChangeEventField[];
112
+ created_at: TDate;
113
+ }
114
+ /**
115
+ * Bytes, not characters, because MySQL TEXT holds 65,535 of them: a character bound would
116
+ * accept a multibyte value the column cannot hold, and 65,535 emoji is four times over.
117
+ */
118
+ export declare const MAX_LONG_TEXT_BYTES = 65535;
119
+ /**
120
+ * The types a composite's parts can declare, each defined once with its rule. A country
121
+ * code and a postal code both store short text, but they are different things — the
122
+ * part's type is what a control or a filter dispatches on, the way a field's own type
123
+ * is for a scalar.
124
+ */
125
+ export declare const PART_TYPES: {
126
+ short_text: z.ZodString;
127
+ postal_code: z.ZodString;
128
+ country_code: z.ZodString;
129
+ };
130
+ export interface FieldTypeDefinition {
131
+ /** What kind of value this is, for anything that has to compare one. */
132
+ kind: import('./structure.js').FieldKind;
133
+ value: z.ZodType;
134
+ }
135
+ export declare const FIELD_TYPES: {
136
+ short_text: {
137
+ kind: "text";
138
+ value: z.ZodString;
139
+ };
140
+ long_text: {
141
+ kind: "text";
142
+ value: z.ZodString;
143
+ };
144
+ address: {
145
+ kind: 'record';
146
+ value: z.ZodObject<{
147
+ line1: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
148
+ line2: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
149
+ city: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
150
+ state: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
151
+ postal_code: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
152
+ country: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
153
+ }, z.core.$strict>;
154
+ };
155
+ };
156
+ /** Named for the admin types built on it, which speak of an address rather than a record. */
157
+ export declare const AddressValue: z.ZodObject<{
158
+ line1: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
159
+ line2: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
160
+ city: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
161
+ state: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
162
+ postal_code: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
163
+ country: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
164
+ }, z.core.$strict>;
165
+ export type Address = z.infer<typeof AddressValue>;
166
+ /**
167
+ * A value of any field type, as a caller holding a field of unknown type must accept it.
168
+ *
169
+ * Derived from the schemas rather than listed, so a type added here widens it without
170
+ * anyone remembering to.
171
+ */
172
+ export type FieldValue = {
173
+ [T in FieldType]: z.infer<(typeof FIELD_TYPES)[T]['value']>;
174
+ }[FieldType];
175
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAA+B,KAAK,SAAS,EAAiB,MAAM,gBAAgB,CAAC;AAI5F,OAAO,EACL,WAAW,EACX,WAAW,EACX,cAAc,EACd,aAAa,EACb,WAAW,EACX,WAAW,EACX,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,OAAO,EACZ,KAAK,QAAQ,GACd,MAAM,gBAAgB,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,eAAO,MAAM,eAAe;;;;EAAyB,CAAC;AAEtD;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,YAAI,MAAM,EAAE,MAAM,EAAE,OAAO,CAAU,CAAC;AACvE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AACjE,eAAO,MAAM,kBAAkB;;;;EAA+B,CAAC;AAC/D,eAAO,MAAM,aAAa;mBAClB,MAAM;mBACN,MAAM;oBACL,OAAO;CACuC,CAAC;AAExD;;;GAGG;AACH,eAAO,MAAM,wBAAwB,YACnC,OAAO,EACP,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,UAAU,CACF,CAAC;AACX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9E,4FAA4F;AAC5F,eAAO,MAAM,uBAAuB,WAAY,MAAM,KAAG,MAAM,IAAI,qBACD,CAAC;AAEnE,qGAAqG;AACrG,eAAO,MAAM,+BAA+B;;;;iBAI1C,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB,CAAC,KAAK,GAAG,MAAM;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,qBAAqB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAC9C,UAAU,EAAE,yBAAyB,EAAE,CAAC;IACxC,UAAU,EAAE,KAAK,CAAC;CACnB;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AA2BzC;;;;;GAKG;AACH,eAAO,MAAM,UAAU;;;;CAwBiB,CAAC;AA+BzC,MAAM,WAAW,mBAAmB;IAClC,wEAAwE;IACxE,IAAI,EAAE,OAAO,gBAAgB,EAAE,SAAS,CAAC;IACzC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;CAClB;AA2CD,eAAO,MAAM,WAAW;;;;;;;;;;cAHP,QAAQ;;;;;;;;;;CAevB,CAAC;AAEH,6FAA6F;AAC7F,eAAO,MAAM,YAAY;;;;;;;kBAA4B,CAAC;AACtD,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG;KAAG,CAAC,IAAI,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;CAAE,CAAC,SAAS,CAAC,CAAC"}
package/build/index.js ADDED
@@ -0,0 +1,203 @@
1
+ import { z } from 'zod';
2
+ import { FIELD_PARTS, FIELD_TYPE_IDS } from './structure.js';
3
+ // Re-exported so a caller that needs both halves still has one import. The split is
4
+ // about what a bundle pays for, not about where the catalog lives.
5
+ export { FIELD_KINDS, FIELD_PARTS, FIELD_TYPE_IDS, PART_TYPE_IDS, partTypesOf, subFieldsOf, } from './structure.js';
6
+ /**
7
+ * The shared catalog of member metafield types.
8
+ *
9
+ * ## What a field type is
10
+ *
11
+ * A publisher defines fields; a field has a type; a type says what a valid value is. That
12
+ * last statement lives here and nowhere else, so that Ghost core, which enforces it, and
13
+ * admin, which gives instant feedback against it, cannot disagree about what is valid.
14
+ *
15
+ * A type is one of two things. Most are a value in their own right, declared as the
16
+ * schema that value must satisfy. An address is a *record*: a set of named parts, each a
17
+ * value in the same sense, from which the whole type's schema is derived. Nothing stops a
18
+ * part being a record itself.
19
+ *
20
+ * ## What a write may say
21
+ *
22
+ * A value is text, and so is every part of one. A write names what it means to change: a
23
+ * name it does not mention is left as it was, and a name given an empty string is being
24
+ * cleared. That is why every part accepts empty regardless of its own rule — emptying is
25
+ * a statement about the write, not about the part.
26
+ *
27
+ * A name nobody recognizes is an error rather than a silent drop, at both depths. A
28
+ * misspelled field key is refused by the values service, which alone knows which fields a
29
+ * site has defined; a misspelled part is refused here, because a type's parts are declared
30
+ * in this file and nowhere else. Each is enforced where the names are known.
31
+ *
32
+ * ## What a failure says
33
+ *
34
+ * A rule carries the sentence shown when it is broken, so the two cannot drift. Each
35
+ * states what is expected rather than what went wrong, so one sentence covers every way
36
+ * of breaking that rule.
37
+ *
38
+ * The sentences are plain literals with no interpolation, so each is usable as a
39
+ * translation key. Nothing here translates: Ghost's parser reads `t()` calls out of the
40
+ * app that renders a string, so that app owns the step. Admin, the only one today, is not
41
+ * translated at all.
42
+ *
43
+ * Two failures keep zod's own wording, both reachable only by a client sending the wrong
44
+ * JSON shape: a composite handed something that is not an object, and a part nobody
45
+ * declared. Naming the offending key serves whoever has to fix that client.
46
+ *
47
+ * ## What this package will not do
48
+ *
49
+ * No presentation beyond that sentence: labels, icons and input controls belong to the
50
+ * frontend, and so does any wording that depends on where it appears rather than on which
51
+ * rule was broken. No storage: columns and codecs belong to the backend. One exception
52
+ * lives in `./csv` — how a value maps onto CSV columns — because both tiers need the same
53
+ * answer and a disagreement between them is a file that silently stops round-tripping.
54
+ *
55
+ * The line is whether more than one renderer must agree on a string, not presentation
56
+ * against validity — the sentences above are presentation. Admin is the only renderer
57
+ * today, so a part's label lives there, held against this file by a type. A Portal
58
+ * collection form, which cannot reach admin's packages, moves the labels here.
59
+ */
60
+ export const FieldTypeSchema = z.enum(FIELD_TYPE_IDS);
61
+ /**
62
+ * What the member whose record it is may do with a field.
63
+ *
64
+ * A property of a definition rather than of the publisher's namespace, so it has an
65
+ * answer for any regime that ever stores definitions, not only the publisher's.
66
+ *
67
+ * Ordered: `write` includes being able to read. Naming the levels for a publisher is
68
+ * presentation and stays with whoever renders them.
69
+ */
70
+ export const MEMBER_ACCESS_LEVELS = ['none', 'read', 'write'];
71
+ export const MemberAccessSchema = z.enum(MEMBER_ACCESS_LEVELS);
72
+ export const MEMBER_ACCESS = {
73
+ none: 'none',
74
+ read: 'read',
75
+ write: 'write',
76
+ };
77
+ /**
78
+ * Where a change to a member's values was made, as a member's activity feed names it.
79
+ * Shared so a place added on the server is one Admin has to be told how to describe.
80
+ */
81
+ export const METAFIELD_CHANGE_SOURCES = [
82
+ 'admin',
83
+ 'admin_api',
84
+ 'portal',
85
+ 'import',
86
+ 'checkout',
87
+ ];
88
+ /** Whether a source is one this build knows how to name; a newer server can send others. */
89
+ export const isMetafieldChangeSource = (source) => METAFIELD_CHANGE_SOURCES.includes(source);
90
+ /** A field as an activity feed entry names it: its name is copied so the entry outlives a rename. */
91
+ export const MetafieldChangeEventFieldSchema = z.object({
92
+ namespace: z.string(),
93
+ key: z.string(),
94
+ name: z.string(),
95
+ });
96
+ /**
97
+ * Bytes, not characters, because MySQL TEXT holds 65,535 of them: a character bound would
98
+ * accept a multibyte value the column cannot hold, and 65,535 emoji is four times over.
99
+ */
100
+ export const MAX_LONG_TEXT_BYTES = 65535;
101
+ // TextEncoder rather than Buffer: this package runs in the browser too.
102
+ const byteLength = (value) => new TextEncoder().encode(value).length;
103
+ /**
104
+ * Every value and every part is built from this, so no two types can disagree about
105
+ * whitespace: a value of nothing but spaces is nothing, whichever field it was typed
106
+ * into. Trimming first also makes each bound measure the value rather than its padding.
107
+ *
108
+ * Builders are named after what a value is rather than taking a size, so that a bound
109
+ * lives with everything else true of that thing instead of as a number at the call site.
110
+ */
111
+ const text = () => z.string({ error: 'Enter text.' }).trim();
112
+ const longText = () => text().refine((value) => byteLength(value) <= MAX_LONG_TEXT_BYTES, {
113
+ error: 'This text is too long to save. Shorten it a little.',
114
+ });
115
+ /**
116
+ * The types a composite's parts can declare, each defined once with its rule. A country
117
+ * code and a postal code both store short text, but they are different things — the
118
+ * part's type is what a control or a filter dispatches on, the way a field's own type
119
+ * is for a scalar.
120
+ */
121
+ export const PART_TYPES = {
122
+ short_text: text().max(255, { error: 'Use 255 characters or fewer.' }),
123
+ // A postal code, bounded well under a street address because no country's is long. The
124
+ // bound is a sanity limit rather than a format: postal codes vary too much between
125
+ // countries to check the shape of one without knowing which country it is for, and the
126
+ // country is a sibling part rather than something this can see.
127
+ postal_code: text().max(32, { error: 'Use 32 characters or fewer.' }),
128
+ // The shape of an ISO 3166-1 alpha-2 code, deliberately not checked against the list of
129
+ // them. Membership of that list is contested, and a closed list here would make Ghost
130
+ // the arbiter of it for every member of every site. The collection form can offer
131
+ // countries to pick from without this deciding which ones exist.
132
+ //
133
+ // Case is normalized so that `gb` and `GB` are not two values for one place, which a
134
+ // filter for either would silently half-miss.
135
+ //
136
+ // Checked as two ASCII letters on the way in rather than by length on the way out,
137
+ // because uppercasing does not preserve length: `ß` becomes `SS` and `aß` becomes `ASS`.
138
+ country_code: text()
139
+ .regex(/^[A-Za-z]{2}$/, { error: 'Enter a 2-letter country code, like US.' })
140
+ .toUpperCase(),
141
+ // Every part type declared next door needs a rule here, and nothing here may
142
+ // invent one that is not declared there.
143
+ };
144
+ const partSchemas = PART_TYPES;
145
+ /**
146
+ * A part as a write may name it: absent, empty, or a value of its own kind.
147
+ *
148
+ * A rule that is a bound would admit empty on its own; one that is a format would not,
149
+ * and would leave its part the only one that could be set but never removed.
150
+ */
151
+ const clearable = (schema) => text()
152
+ .pipe(z.union([z.literal(''), schema]))
153
+ .optional();
154
+ /**
155
+ * Every type states its kind alongside its schema, so no type can exist that nothing knows
156
+ * how to compare. The `Record<FieldType, …>` is what makes that exhaustive: an id added to
157
+ * `FIELD_TYPE_IDS` fails to compile until it is declared here.
158
+ */
159
+ function defineFieldTypes(declarations) {
160
+ return declarations;
161
+ }
162
+ /**
163
+ * Every part is optional, because none of an address's exists everywhere: there is no
164
+ * postal code in Ireland or Hong Kong, and no city in an Irish townland address. Which
165
+ * parts a value needs is a per-country question, and only the collection form knows the
166
+ * country. What holds instead is that a value must name at least one part.
167
+ *
168
+ * The type check below is load-bearing rather than defensive. An optional part that is
169
+ * explicitly undefined survives parsing as a key holding undefined, and a bare presence
170
+ * check would let `{line1: undefined}` through as if it named something.
171
+ */
172
+ function record(type, { error }) {
173
+ // The parts, and what each holds, are declared in `./structure`; this turns each
174
+ // into the rule a write must satisfy. Reading them from there is what stops the
175
+ // structure a renderer draws and the rules a server enforces from drifting apart.
176
+ const declared = FIELD_PARTS[type];
177
+ // Restated for the type system, which loses the key-to-part mapping through
178
+ // `Object.fromEntries`. Without it the parsed value widens to a string-keyed record,
179
+ // and a caller could name a part that compiles and is then refused at runtime.
180
+ const shape = Object.fromEntries(Object.entries(declared).map(([key, partType]) => [key, clearable(partSchemas[partType])]));
181
+ // Strict, so a part nobody declared is refused rather than dropped. That refusal keeps
182
+ // zod's wording, which names the offending key.
183
+ const value = z
184
+ .strictObject(shape)
185
+ .refine((parts) => Object.values(parts).some((entry) => typeof entry === 'string'), { error });
186
+ return { kind: 'record', value };
187
+ }
188
+ export const FIELD_TYPES = defineFieldTypes({
189
+ short_text: { kind: 'text', value: PART_TYPES.short_text },
190
+ long_text: { kind: 'text', value: longText() },
191
+ // An address is a delivery address, so its bounds are what a courier will accept
192
+ // rather than what the column could hold. Modeled on Stripe's Address object.
193
+ //
194
+ // Who the parcel is addressed to is not here. A parcel needs a name as well as an
195
+ // address, but that is a fact about posting parcels rather than about either type,
196
+ // and the name is often not the account name — gift subscriptions, workplace
197
+ // deliveries, c/o. A site that needs one keeps it in a field of its own, which is
198
+ // also how Stripe hands it back: beside the address rather than inside it.
199
+ address: record('address', { error: 'Enter at least one part of the address.' }),
200
+ });
201
+ /** Named for the admin types built on it, which speak of an address rather than a record. */
202
+ export const AddressValue = FIELD_TYPES.address.value;
203
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * What a field type is made of, with no opinion on what a valid value looks like.
3
+ *
4
+ * This is the half of the catalog a renderer needs. Drawing an address means knowing
5
+ * it has six parts, in that order, and that the last of them holds a country code —
6
+ * nothing about lengths, formats, or what happens when one is left blank.
7
+ *
8
+ * It is separate from `./index` because that half is built out of zod, and zod is
9
+ * sixty-odd kilobytes. Ghost's server enforces the rules and can afford them; Portal
10
+ * only draws the inputs, and it loads on every page view of every themed site. Split
11
+ * this way a renderer imports well under a kilobyte and still gets the types.
12
+ *
13
+ * The two halves cannot drift, because the schemas are built from these declarations
14
+ * rather than beside them: adding a part here without a rule in `./index` fails to
15
+ * compile.
16
+ */
17
+ /** The source for the union type, the zod enum and the `FIELD_TYPES` keys alike. */
18
+ export declare const FIELD_TYPE_IDS: readonly ['short_text', 'long_text', 'address'];
19
+ export type FieldType = (typeof FIELD_TYPE_IDS)[number];
20
+ /**
21
+ * What kind of thing a type's value is, as anything comparing values needs to know.
22
+ *
23
+ * Coarser than the type: `short_text` and `long_text` are both text, and differ only in
24
+ * how much of it. This is the level at which a value can be ordered, matched or grouped,
25
+ * so it is what a filter, a sort or an export reads to decide how to treat a value —
26
+ * without either of them enumerating the types themselves.
27
+ *
28
+ * Deliberately not presentation: it says a value is a date, not that its operator is
29
+ * called "is before". Naming the operators stays with whoever renders them.
30
+ */
31
+ export declare const FIELD_KINDS: readonly ['text', 'date', 'number', 'record'];
32
+ export type FieldKind = (typeof FIELD_KINDS)[number];
33
+ /**
34
+ * The types a composite's parts can declare. A country code and a postal code both
35
+ * store short text, but they are different things — the part's type is what a control
36
+ * or a filter dispatches on, the way a field's own type is for a scalar.
37
+ */
38
+ export declare const PART_TYPE_IDS: readonly ['short_text', 'postal_code', 'country_code'];
39
+ export type PartType = (typeof PART_TYPE_IDS)[number];
40
+ /**
41
+ * Which parts each type has, in the order they are shown, and what each part holds.
42
+ * `null` for a type whose value is a single thing.
43
+ *
44
+ * An address is a delivery address, modeled on Stripe's Address object. Who the parcel
45
+ * is addressed to is not here: a parcel needs a name as well as an address, but that is
46
+ * a fact about posting parcels rather than about either type, and the name is often not
47
+ * the account name — gift subscriptions, workplace deliveries, c/o. A site that needs
48
+ * one keeps it in a field of its own, which is also how Stripe hands it back.
49
+ */
50
+ export declare const FIELD_PARTS: {
51
+ readonly short_text: null;
52
+ readonly long_text: null;
53
+ readonly address: {
54
+ readonly line1: 'short_text';
55
+ readonly line2: 'short_text';
56
+ readonly city: 'short_text';
57
+ readonly state: 'short_text';
58
+ readonly postal_code: 'postal_code';
59
+ readonly country: 'country_code';
60
+ };
61
+ };
62
+ /**
63
+ * The parts a record type declares, or never for a type whose value is a single thing.
64
+ *
65
+ * Distributed over `T`, so a caller holding a type it only knows as `FieldType` gets
66
+ * every part any type declares rather than the empty intersection of all of them.
67
+ */
68
+ export type PartsOf<T extends FieldType> = T extends FieldType ? (typeof FIELD_PARTS)[T] extends null ? never : Extract<keyof (typeof FIELD_PARTS)[T], string> : never;
69
+ /**
70
+ * The parts of a record type in declaration order, or null for a type with none.
71
+ *
72
+ * Typed to the parts the caller's type declares, so a caller holding one of these can
73
+ * index a value of that type without restating which parts exist.
74
+ */
75
+ export declare function subFieldsOf<T extends FieldType>(type: T): PartsOf<T>[] | null;
76
+ /** Each part's declared type, keyed by part; null for a type with none, and for one this build has never heard of. */
77
+ export declare function partTypesOf<T extends FieldType>(type: T): Record<PartsOf<T>, PartType> | null;
78
+ //# sourceMappingURL=structure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../src/structure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,oFAAoF;AACpF,eAAO,MAAM,cAAc,YAAI,YAAY,EAAE,WAAW,EAAE,SAAS,CAAU,CAAC;AAC9E,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,YAAI,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AACzE,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,aAAa,YAAI,YAAY,EAAE,aAAa,EAAE,cAAc,CAAU,CAAC;AACpF,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtD;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW;;;;iBAIpB,KAAK,EAAE,YAAY;iBACnB,KAAK,EAAE,YAAY;iBACnB,IAAI,EAAE,YAAY;iBAClB,KAAK,EAAE,YAAY;iBACnB,WAAW,EAAE,aAAa;iBAC1B,OAAO,EAAE,cAAc;;CAEsD,CAAC;AAElF;;;;;GAKG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,SAAS,SAAS,IAAI,CAAC,SAAS,SAAS,GAC1D,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAClC,KAAK,GACL,OAAO,CAAC,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,GAChD,KAAK,CAAC;AAEV;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,SAAS,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAK7E;AAED,sHAAsH;AACtH,wBAAgB,WAAW,CAAC,CAAC,SAAS,SAAS,EAAE,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,IAAI,CAG7F"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * What a field type is made of, with no opinion on what a valid value looks like.
3
+ *
4
+ * This is the half of the catalog a renderer needs. Drawing an address means knowing
5
+ * it has six parts, in that order, and that the last of them holds a country code —
6
+ * nothing about lengths, formats, or what happens when one is left blank.
7
+ *
8
+ * It is separate from `./index` because that half is built out of zod, and zod is
9
+ * sixty-odd kilobytes. Ghost's server enforces the rules and can afford them; Portal
10
+ * only draws the inputs, and it loads on every page view of every themed site. Split
11
+ * this way a renderer imports well under a kilobyte and still gets the types.
12
+ *
13
+ * The two halves cannot drift, because the schemas are built from these declarations
14
+ * rather than beside them: adding a part here without a rule in `./index` fails to
15
+ * compile.
16
+ */
17
+ /** The source for the union type, the zod enum and the `FIELD_TYPES` keys alike. */
18
+ export const FIELD_TYPE_IDS = ['short_text', 'long_text', 'address'];
19
+ /**
20
+ * What kind of thing a type's value is, as anything comparing values needs to know.
21
+ *
22
+ * Coarser than the type: `short_text` and `long_text` are both text, and differ only in
23
+ * how much of it. This is the level at which a value can be ordered, matched or grouped,
24
+ * so it is what a filter, a sort or an export reads to decide how to treat a value —
25
+ * without either of them enumerating the types themselves.
26
+ *
27
+ * Deliberately not presentation: it says a value is a date, not that its operator is
28
+ * called "is before". Naming the operators stays with whoever renders them.
29
+ */
30
+ export const FIELD_KINDS = ['text', 'date', 'number', 'record'];
31
+ /**
32
+ * The types a composite's parts can declare. A country code and a postal code both
33
+ * store short text, but they are different things — the part's type is what a control
34
+ * or a filter dispatches on, the way a field's own type is for a scalar.
35
+ */
36
+ export const PART_TYPE_IDS = ['short_text', 'postal_code', 'country_code'];
37
+ /**
38
+ * Which parts each type has, in the order they are shown, and what each part holds.
39
+ * `null` for a type whose value is a single thing.
40
+ *
41
+ * An address is a delivery address, modeled on Stripe's Address object. Who the parcel
42
+ * is addressed to is not here: a parcel needs a name as well as an address, but that is
43
+ * a fact about posting parcels rather than about either type, and the name is often not
44
+ * the account name — gift subscriptions, workplace deliveries, c/o. A site that needs
45
+ * one keeps it in a field of its own, which is also how Stripe hands it back.
46
+ */
47
+ export const FIELD_PARTS = {
48
+ short_text: null,
49
+ long_text: null,
50
+ address: {
51
+ line1: 'short_text',
52
+ line2: 'short_text',
53
+ city: 'short_text',
54
+ state: 'short_text',
55
+ postal_code: 'postal_code',
56
+ country: 'country_code',
57
+ },
58
+ };
59
+ /**
60
+ * The parts of a record type in declaration order, or null for a type with none.
61
+ *
62
+ * Typed to the parts the caller's type declares, so a caller holding one of these can
63
+ * index a value of that type without restating which parts exist.
64
+ */
65
+ export function subFieldsOf(type) {
66
+ // Optional because a caller built against an older catalog than the server it talks
67
+ // to reaches here with a type this build has never heard of, which reads as no parts.
68
+ const parts = FIELD_PARTS[type];
69
+ return parts ? Object.keys(parts) : null;
70
+ }
71
+ /** Each part's declared type, keyed by part; null for a type with none, and for one this build has never heard of. */
72
+ export function partTypesOf(type) {
73
+ const parts = FIELD_PARTS[type];
74
+ return parts ? { ...parts } : null;
75
+ }
76
+ //# sourceMappingURL=structure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structure.js","sourceRoot":"","sources":["../src/structure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,oFAAoF;AACpF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,YAAY,EAAE,WAAW,EAAE,SAAS,CAAU,CAAC;AAG9E;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AAGzE;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,YAAY,EAAE,aAAa,EAAE,cAAc,CAAU,CAAC;AAGpF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,IAAI;IACf,OAAO,EAAE;QACP,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,YAAY;QACnB,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,aAAa;QAC1B,OAAO,EAAE,cAAc;KACxB;CAC8E,CAAC;AAclF;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAsB,IAAO;IACtD,oFAAoF;IACpF,sFAAsF;IACtF,MAAM,KAAK,GAA0D,WAAW,CAAC,IAAI,CAAC,CAAC;IACvF,OAAO,KAAK,CAAC,CAAC,CAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAkB,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7D,CAAC;AAED,sHAAsH;AACtH,MAAM,UAAU,WAAW,CAAsB,IAAO;IACtD,MAAM,KAAK,GAA0D,WAAW,CAAC,IAAI,CAAC,CAAC;IACvF,OAAO,KAAK,CAAC,CAAC,CAAE,EAAE,GAAG,KAAK,EAAmC,CAAC,CAAC,CAAC,IAAI,CAAC;AACvE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@tryghost/metafield-types",
3
+ "version": "0.1.0",
4
+ "description": "Ghost member custom field types: which types exist, what each is made of, and how a value of each is validated",
5
+ "license": "MIT",
6
+ "author": "Ghost Foundation",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/TryGhost/Ghost.git",
10
+ "directory": "packages/metafield-types"
11
+ },
12
+ "files": [
13
+ "build"
14
+ ],
15
+ "type": "module",
16
+ "main": "build/index.js",
17
+ "types": "build/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./build/index.d.ts",
21
+ "default": "./build/index.js"
22
+ },
23
+ "./structure": {
24
+ "types": "./build/structure.d.ts",
25
+ "default": "./build/structure.js"
26
+ },
27
+ "./identity": {
28
+ "types": "./build/identity.d.ts",
29
+ "default": "./build/identity.js"
30
+ },
31
+ "./countries": {
32
+ "types": "./build/countries.d.ts",
33
+ "default": "./build/countries.js"
34
+ }
35
+ },
36
+ "dependencies": {
37
+ "zod": "4.4.3"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "test:unit": "NODE_ENV=testing vitest run --coverage",
42
+ "test": "pnpm run '/^test:/'",
43
+ "test:types": "tsc --noEmit -p test/tsconfig.json",
44
+ "lint:code": "eslint src/ --cache",
45
+ "lint:test": "eslint test/ --cache",
46
+ "lint": "pnpm run '/^lint:/'"
47
+ }
48
+ }