@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 +22 -0
- package/README.md +70 -0
- package/build/countries.d.ts +14 -0
- package/build/countries.d.ts.map +1 -0
- package/build/countries.js +266 -0
- package/build/countries.js.map +1 -0
- package/build/csv.d.ts +39 -0
- package/build/csv.d.ts.map +1 -0
- package/build/csv.js +119 -0
- package/build/csv.js.map +1 -0
- package/build/identity.d.ts +36 -0
- package/build/identity.d.ts.map +1 -0
- package/build/identity.js +40 -0
- package/build/identity.js.map +1 -0
- package/build/index.d.ts +175 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +203 -0
- package/build/index.js.map +1 -0
- package/build/structure.d.ts +78 -0
- package/build/structure.d.ts.map +1 -0
- package/build/structure.js +76 -0
- package/build/structure.js.map +1 -0
- package/package.json +48 -0
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
|
package/build/csv.js.map
ADDED
|
@@ -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"}
|
package/build/index.d.ts
ADDED
|
@@ -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
|
+
}
|