@tryghost/metafield-types 0.2.0 → 0.4.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/CHANGELOG.md +66 -0
- package/README.md +26 -20
- package/build/index.d.ts +313 -63
- package/build/index.d.ts.map +1 -1
- package/build/index.js +325 -100
- package/build/index.js.map +1 -1
- package/build/shapes.d.ts +185 -0
- package/build/shapes.d.ts.map +1 -0
- package/build/shapes.js +262 -0
- package/build/shapes.js.map +1 -0
- package/build/structure.d.ts +283 -59
- package/build/structure.d.ts.map +1 -1
- package/build/structure.js +179 -59
- package/build/structure.js.map +1 -1
- package/package.json +6 -2
package/build/structure.d.ts
CHANGED
|
@@ -1,78 +1,302 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a field type is made of, with no opinion on what a valid value looks like.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Every type is composed from a few building blocks. A leaf is one value: text of a size, a
|
|
5
|
+
* choice from a list. A type is a leaf on its own, a record of named leaves, or a set of options its
|
|
6
|
+
* field offers. Everything that stores, exports, edits, filters or draws a value is written
|
|
7
|
+
* once per building block, so a type composed from existing blocks needs nothing beyond its entry
|
|
8
|
+
* here.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
* sixty-odd kilobytes. Ghost's server enforces the rules and can afford them; Portal
|
|
10
|
-
*
|
|
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.
|
|
10
|
+
* This half is separate from `./index` because that half is built out of zod, and zod is
|
|
11
|
+
* sixty-odd kilobytes. Ghost's server enforces the rules and can afford them; Portal only draws
|
|
12
|
+
* the inputs, and it loads on every page view of every themed site.
|
|
16
13
|
*/
|
|
17
|
-
/**
|
|
18
|
-
export declare const
|
|
19
|
-
export type
|
|
14
|
+
/** How much text a text leaf holds. Each size has its bound, and its message, in `./index`. */
|
|
15
|
+
export declare const TEXT_SIZES: readonly ['line', 'code', 'long'];
|
|
16
|
+
export type TextSize = (typeof TEXT_SIZES)[number];
|
|
17
|
+
/** The lists a choice can pick from. Each list says which keys it accepts in `./index`. */
|
|
18
|
+
export declare const LIST_SOURCES: readonly ['countries'];
|
|
19
|
+
export type ListSource = (typeof LIST_SOURCES)[number];
|
|
20
|
+
export interface TextLeaf {
|
|
21
|
+
readonly leaf: 'text';
|
|
22
|
+
readonly size: TextSize;
|
|
23
|
+
}
|
|
24
|
+
export interface ChoiceLeaf {
|
|
25
|
+
readonly leaf: 'choice';
|
|
26
|
+
readonly from: ListSource;
|
|
27
|
+
}
|
|
28
|
+
/** One value. */
|
|
29
|
+
export type Leaf = TextLeaf | ChoiceLeaf;
|
|
30
|
+
export type LeafKind = Leaf['leaf'];
|
|
31
|
+
/** What a value of each kind of leaf is, as JSON carries it. */
|
|
32
|
+
export interface LeafValues {
|
|
33
|
+
text: string;
|
|
34
|
+
choice: string;
|
|
35
|
+
}
|
|
36
|
+
export type LeafValue<L extends Leaf> = LeafValues[L['leaf']];
|
|
37
|
+
export declare const text: <const S extends TextSize>(size: S) => {
|
|
38
|
+
readonly leaf: 'text';
|
|
39
|
+
readonly size: S;
|
|
40
|
+
};
|
|
41
|
+
export declare const choice: <const F extends ListSource>(from: F) => {
|
|
42
|
+
readonly leaf: 'choice';
|
|
43
|
+
readonly from: F;
|
|
44
|
+
};
|
|
45
|
+
/** A country, as the two-letter code a list of countries offers. */
|
|
46
|
+
export declare const country: {
|
|
47
|
+
readonly leaf: 'choice';
|
|
48
|
+
readonly from: "countries";
|
|
49
|
+
};
|
|
50
|
+
/** A type whose value is one leaf. */
|
|
51
|
+
export interface ScalarStructure {
|
|
52
|
+
readonly shape: 'scalar';
|
|
53
|
+
readonly leaf: Leaf;
|
|
54
|
+
}
|
|
20
55
|
/**
|
|
21
|
-
*
|
|
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.
|
|
56
|
+
* A type whose value is a leaf per named part. Parts are listed in the order they are shown, and
|
|
57
|
+
* that order is relied on wherever parts are laid out.
|
|
30
58
|
*/
|
|
31
|
-
export
|
|
32
|
-
|
|
59
|
+
export interface RecordStructure {
|
|
60
|
+
readonly shape: 'record';
|
|
61
|
+
readonly parts: Readonly<Record<string, Leaf>>;
|
|
62
|
+
}
|
|
33
63
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
64
|
+
* A type whose value is some of the options its field offers, each named by its text. Which
|
|
65
|
+
* options there are, and whether a member picks one or several, are settings of the field rather
|
|
66
|
+
* than of its type, so each field asks a question of its own.
|
|
37
67
|
*/
|
|
38
|
-
export
|
|
39
|
-
|
|
68
|
+
export interface SetStructure {
|
|
69
|
+
readonly shape: 'set';
|
|
70
|
+
}
|
|
71
|
+
export type FieldStructure = ScalarStructure | RecordStructure | SetStructure;
|
|
72
|
+
export type FieldShape = FieldStructure['shape'];
|
|
73
|
+
/** The source for the union type, the zod enum and the `FIELD_STRUCTURES` keys alike. */
|
|
74
|
+
export declare const FIELD_TYPE_IDS: readonly ['short_text', 'long_text', 'list', 'address'];
|
|
75
|
+
export type FieldType = (typeof FIELD_TYPE_IDS)[number];
|
|
40
76
|
/**
|
|
41
|
-
*
|
|
42
|
-
* `null` for a type whose value is a single thing.
|
|
77
|
+
* What each type is composed of.
|
|
43
78
|
*
|
|
44
|
-
* An address is a delivery address, modeled on Stripe's Address object. Who the parcel
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*/
|
|
50
|
-
export declare const
|
|
51
|
-
readonly short_text:
|
|
52
|
-
|
|
79
|
+
* An address is a delivery address, modeled on Stripe's Address object. Who the parcel is
|
|
80
|
+
* addressed to is not here: a parcel needs a name as well as an address, but that is a fact about
|
|
81
|
+
* posting parcels rather than about either type, and the name is often not the account name, as
|
|
82
|
+
* with gift subscriptions, workplace deliveries or c/o. A site that needs one keeps it in a field of
|
|
83
|
+
* its own, which is also how Stripe hands it back.
|
|
84
|
+
*/
|
|
85
|
+
export declare const FIELD_STRUCTURES: {
|
|
86
|
+
readonly short_text: {
|
|
87
|
+
readonly shape: 'scalar';
|
|
88
|
+
readonly leaf: {
|
|
89
|
+
readonly leaf: 'text';
|
|
90
|
+
readonly size: "line";
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
readonly long_text: {
|
|
94
|
+
readonly shape: 'scalar';
|
|
95
|
+
readonly leaf: {
|
|
96
|
+
readonly leaf: 'text';
|
|
97
|
+
readonly size: "long";
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
readonly list: {
|
|
101
|
+
readonly shape: 'set';
|
|
102
|
+
};
|
|
53
103
|
readonly address: {
|
|
54
|
-
readonly
|
|
55
|
-
readonly
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
104
|
+
readonly shape: 'record';
|
|
105
|
+
readonly parts: {
|
|
106
|
+
readonly line1: {
|
|
107
|
+
readonly leaf: 'text';
|
|
108
|
+
readonly size: "line";
|
|
109
|
+
};
|
|
110
|
+
readonly line2: {
|
|
111
|
+
readonly leaf: 'text';
|
|
112
|
+
readonly size: "line";
|
|
113
|
+
};
|
|
114
|
+
readonly city: {
|
|
115
|
+
readonly leaf: 'text';
|
|
116
|
+
readonly size: "line";
|
|
117
|
+
};
|
|
118
|
+
readonly state: {
|
|
119
|
+
readonly leaf: 'text';
|
|
120
|
+
readonly size: "line";
|
|
121
|
+
};
|
|
122
|
+
readonly postal_code: {
|
|
123
|
+
readonly leaf: 'text';
|
|
124
|
+
readonly size: "code";
|
|
125
|
+
};
|
|
126
|
+
readonly country: {
|
|
127
|
+
readonly leaf: 'choice';
|
|
128
|
+
readonly from: "countries";
|
|
129
|
+
};
|
|
130
|
+
};
|
|
60
131
|
};
|
|
61
132
|
};
|
|
133
|
+
export type StructureOf<T extends FieldType> = (typeof FIELD_STRUCTURES)[T];
|
|
134
|
+
export type ShapeOf<T extends FieldType> = StructureOf<T>['shape'];
|
|
135
|
+
export type FieldTypeOfShape<S extends FieldShape> = {
|
|
136
|
+
[T in FieldType]: ShapeOf<T> extends S ? T : never;
|
|
137
|
+
}[FieldType];
|
|
62
138
|
/**
|
|
63
|
-
* The parts a record type declares, or never for
|
|
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.
|
|
139
|
+
* The parts a record type declares, or never for another shape. Distributes over `T`, so a union
|
|
140
|
+
* of types gives every part any of them declares.
|
|
67
141
|
*/
|
|
68
|
-
export type PartsOf<T extends FieldType> = T extends FieldType ?
|
|
142
|
+
export type PartsOf<T extends FieldType> = T extends FieldType ? StructureOf<T> extends {
|
|
143
|
+
parts: infer P;
|
|
144
|
+
} ? Extract<keyof P, string> : never : never;
|
|
145
|
+
type ValueOfStructure<S extends FieldStructure> = {
|
|
146
|
+
scalar: S extends {
|
|
147
|
+
leaf: infer L extends Leaf;
|
|
148
|
+
} ? LeafValue<L> : never;
|
|
149
|
+
record: S extends {
|
|
150
|
+
parts: infer P;
|
|
151
|
+
} ? {
|
|
152
|
+
[K in keyof P]?: P[K] extends Leaf ? LeafValue<P[K]> : never;
|
|
153
|
+
} : never;
|
|
154
|
+
set: string[];
|
|
155
|
+
}[S['shape']];
|
|
69
156
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
157
|
+
* A value of a type, by its shape: one string for a scalar, a string for any of its declared parts
|
|
158
|
+
* for a record, and the text of each option picked for a set, even when its field takes only one.
|
|
159
|
+
* Every value is laid out this way, so storage, which keeps one string per leaf or option, can
|
|
160
|
+
* always read back what was written.
|
|
161
|
+
*/
|
|
162
|
+
export type ValueOf<T extends FieldType> = T extends FieldType ? ValueOfStructure<StructureOf<T>> : never;
|
|
163
|
+
type WriteOfStructure<S extends FieldStructure> = {
|
|
164
|
+
scalar: ValueOfStructure<S>;
|
|
165
|
+
record: S extends {
|
|
166
|
+
parts: infer P;
|
|
167
|
+
} ? {
|
|
168
|
+
[K in keyof P]?: (P[K] extends Leaf ? LeafValue<P[K]> : never) | null;
|
|
169
|
+
} : never;
|
|
170
|
+
set: ValueOfStructure<S>;
|
|
171
|
+
}[S['shape']];
|
|
172
|
+
/**
|
|
173
|
+
* What a write gives a field of a type that still holds a value afterwards; `null` clears the
|
|
174
|
+
* whole field. As in JSON Merge Patch, a record's write names a part as `null` to clear it and
|
|
175
|
+
* leaves out a part to leave it alone, so a write is not a value: a value never holds `null`.
|
|
176
|
+
*/
|
|
177
|
+
export type WriteOf<T extends FieldType> = T extends FieldType ? WriteOfStructure<StructureOf<T>> : never;
|
|
178
|
+
/**
|
|
179
|
+
* The keys a value has, typed as its type's keys rather than as strings, which is what
|
|
180
|
+
* `Object.keys` gives: a `for...in` over a value of a generic type gives each key as typed.
|
|
181
|
+
*/
|
|
182
|
+
export declare function keysOf<R extends object>(value: R): Extract<keyof R, string>[];
|
|
183
|
+
/**
|
|
184
|
+
* What a field of a set type is set up with: the options it offers, in the order they are shown,
|
|
185
|
+
* and whether a member may pick more than one. An option is named by its text alone, so the same
|
|
186
|
+
* text is the same option whenever it is offered, and nothing else identifies one.
|
|
187
|
+
*/
|
|
188
|
+
export interface SetSettings {
|
|
189
|
+
readonly options: string[];
|
|
190
|
+
readonly multiple: boolean;
|
|
191
|
+
}
|
|
192
|
+
/** How many options a set field offers: at least two, or it isn't a choice, and at most a hundred. */
|
|
193
|
+
export declare const MIN_OPTIONS = 2;
|
|
194
|
+
export declare const MAX_OPTIONS = 100;
|
|
195
|
+
/**
|
|
196
|
+
* What joins a member's options in one cell when a set is exported, and splits them when one is
|
|
197
|
+
* imported. No option may contain it, so a cell always splits back into what was joined. Chosen
|
|
198
|
+
* because it is rare in names and no spreadsheet splits columns on it by default.
|
|
199
|
+
*/
|
|
200
|
+
export declare const OPTION_SEPARATOR = "|";
|
|
201
|
+
/**
|
|
202
|
+
* An option's text as it is matched: without surrounding spaces, and with accented letters
|
|
203
|
+
* encoded one way. Neither changes how the text reads, and without them an option pasted from a
|
|
204
|
+
* spreadsheet, or typed on a system that encodes accents differently, would not match one that
|
|
205
|
+
* reads exactly the same. Everything else is kept, case included, so an answer always reads as
|
|
206
|
+
* it was given.
|
|
207
|
+
*/
|
|
208
|
+
export declare function optionText(option: string): string;
|
|
209
|
+
/**
|
|
210
|
+
* What two options are compared by to tell whether they are the same option: their text as
|
|
211
|
+
* `optionText` tidies it, without regard to case. A list offers each option once, so it can't
|
|
212
|
+
* offer both "Podcasts" and "podcasts". Lower-casing, upper-casing and lower-casing again also
|
|
213
|
+
* matches letters that one change of case keeps apart, such as "ß", "ẞ" and "SS", or a final "ς"
|
|
214
|
+
* and "σ", and does so the same way whatever the locale.
|
|
215
|
+
*/
|
|
216
|
+
export declare function optionKey(option: string): string;
|
|
217
|
+
/** The answers among `held` that a set field offers, in the order it offers them. */
|
|
218
|
+
export declare function inOptionOrder(settings: SetSettings, held: readonly unknown[]): string[];
|
|
219
|
+
/**
|
|
220
|
+
* What a form editing a set field's answers starts from: only the options the field offers now,
|
|
221
|
+
* since one it no longer offers can't be picked again. A field that has gone from several answers
|
|
222
|
+
* to one starts unanswered while the member holds several, even if only one of them is still
|
|
223
|
+
* offered, so the next answer replaces them rather than one being shown as their only answer.
|
|
224
|
+
*/
|
|
225
|
+
export declare function editableAnswers(settings: SetSettings, held: readonly unknown[]): string[];
|
|
226
|
+
/**
|
|
227
|
+
* What a field of each shape is set up with beyond its name: a set's options, and nothing for any
|
|
228
|
+
* other shape.
|
|
229
|
+
*/
|
|
230
|
+
export interface SettingsOfShape {
|
|
231
|
+
scalar: undefined;
|
|
232
|
+
record: undefined;
|
|
233
|
+
set: SetSettings;
|
|
234
|
+
}
|
|
235
|
+
export type SettingsOf<T extends FieldType> = T extends FieldType ? SettingsOfShape[ShapeOf<T>] : never;
|
|
236
|
+
/** A field's settings, which a field whose type has nothing to set up doesn't have. */
|
|
237
|
+
type SettingsField<S> = [S] extends [undefined] ? {
|
|
238
|
+
readonly settings?: undefined;
|
|
239
|
+
} : {
|
|
240
|
+
readonly settings: S;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* A field's type together with what the field is set up with. They are one value because only the
|
|
244
|
+
* two together say what the field takes: a list field without its options has no answers to
|
|
245
|
+
* offer, and a short text field has no settings at all.
|
|
246
|
+
*/
|
|
247
|
+
export type TypedSettings = {
|
|
248
|
+
[T in FieldType]: {
|
|
249
|
+
readonly type: T;
|
|
250
|
+
} & SettingsField<SettingsOf<T>>;
|
|
251
|
+
}[FieldType];
|
|
252
|
+
/** The fields among `F` whose type has shape `S`, with their settings narrowed to match. */
|
|
253
|
+
export type FieldOfShape<S extends FieldShape, F extends TypedSettings = TypedSettings> = Extract<F, {
|
|
254
|
+
type: FieldTypeOfShape<S>;
|
|
255
|
+
}>;
|
|
256
|
+
/**
|
|
257
|
+
* Whether a value names a type this build knows. A site newer than an integration can send a type
|
|
258
|
+
* the integration doesn't know, so check a type from the wire with this first. `constructor` and
|
|
259
|
+
* other names every object has are not types.
|
|
260
|
+
*/
|
|
261
|
+
export declare function isFieldType(value: unknown): value is FieldType;
|
|
262
|
+
export type ShapeHandlers<R> = {
|
|
263
|
+
[S in FieldShape]: (structure: Extract<FieldStructure, {
|
|
264
|
+
shape: S;
|
|
265
|
+
}>, type: FieldTypeOfShape<S>) => R;
|
|
266
|
+
};
|
|
267
|
+
/** Every type of shape `shape`, in catalog order. */
|
|
268
|
+
export declare const typesOfShape: <S extends FieldShape>(shape: S) => FieldTypeOfShape<S>[];
|
|
269
|
+
/**
|
|
270
|
+
* Calls the handler for the shape `type` declares, with the type narrowed to that shape. Throws,
|
|
271
|
+
* naming the type, for one this build doesn't know; check with `isFieldType` first.
|
|
272
|
+
*/
|
|
273
|
+
export declare function matchShape<R>(type: FieldType, handlers: ShapeHandlers<R>): R;
|
|
274
|
+
export type FieldHandlers<F extends TypedSettings, R> = {
|
|
275
|
+
[S in FieldShape]: (field: FieldOfShape<S, F>) => R;
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* Calls the handler for the shape a field's type declares, with the field narrowed to that shape,
|
|
279
|
+
* settings included, for a caller that needs what the field is set up with rather than only its
|
|
280
|
+
* type. Throws, naming the type, for one this build doesn't know.
|
|
281
|
+
*/
|
|
282
|
+
export declare function matchField<F extends TypedSettings, R>(field: F, handlers: FieldHandlers<F, R>): R;
|
|
283
|
+
export type LeafHandlers<R> = {
|
|
284
|
+
[K in LeafKind]: (leaf: Extract<Leaf, {
|
|
285
|
+
leaf: K;
|
|
286
|
+
}>) => R;
|
|
287
|
+
};
|
|
288
|
+
/** Calls the handler for a leaf's kind. */
|
|
289
|
+
export declare function matchLeaf<R>(leaf: Leaf, handlers: LeafHandlers<R>): R;
|
|
290
|
+
/**
|
|
291
|
+
* Whether a value is laid out as a record: an object of parts, which a list is not. What a record
|
|
292
|
+
* must hold is its type's rule to decide.
|
|
293
|
+
*/
|
|
294
|
+
export declare function isRecordValue(value: unknown): value is Record<string, unknown>;
|
|
295
|
+
/**
|
|
296
|
+
* A record type's parts in declaration order, or null for any other type, including one this build
|
|
297
|
+
* doesn't know. Parts are plain strings here; `FIELD_STRUCTURES[type].parts` gives one type's parts
|
|
298
|
+
* with typed keys.
|
|
74
299
|
*/
|
|
75
|
-
export declare function partsOf
|
|
76
|
-
|
|
77
|
-
export declare function partTypesOf<T extends FieldType>(type: T): Record<PartsOf<T>, PartType> | null;
|
|
300
|
+
export declare function partsOf(type: FieldType): string[] | null;
|
|
301
|
+
export {};
|
|
78
302
|
//# sourceMappingURL=structure.d.ts.map
|
package/build/structure.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../src/structure.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../src/structure.ts"],"names":[],"mappings":"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"}
|
package/build/structure.js
CHANGED
|
@@ -1,76 +1,196 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a field type is made of, with no opinion on what a valid value looks like.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Every type is composed from a few building blocks. A leaf is one value: text of a size, a
|
|
5
|
+
* choice from a list. A type is a leaf on its own, a record of named leaves, or a set of options its
|
|
6
|
+
* field offers. Everything that stores, exports, edits, filters or draws a value is written
|
|
7
|
+
* once per building block, so a type composed from existing blocks needs nothing beyond its entry
|
|
8
|
+
* here.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
* sixty-odd kilobytes. Ghost's server enforces the rules and can afford them; Portal
|
|
10
|
-
*
|
|
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.
|
|
10
|
+
* This half is separate from `./index` because that half is built out of zod, and zod is
|
|
11
|
+
* sixty-odd kilobytes. Ghost's server enforces the rules and can afford them; Portal only draws
|
|
12
|
+
* the inputs, and it loads on every page view of every themed site.
|
|
16
13
|
*/
|
|
17
|
-
/**
|
|
18
|
-
export const
|
|
14
|
+
/** How much text a text leaf holds. Each size has its bound, and its message, in `./index`. */
|
|
15
|
+
export const TEXT_SIZES = ['line', 'code', 'long'];
|
|
16
|
+
/** The lists a choice can pick from. Each list says which keys it accepts in `./index`. */
|
|
17
|
+
export const LIST_SOURCES = ['countries'];
|
|
18
|
+
export const text = (size) => ({ leaf: 'text', size });
|
|
19
|
+
export const choice = (from) => ({ leaf: 'choice', from });
|
|
20
|
+
/** A country, as the two-letter code a list of countries offers. */
|
|
21
|
+
export const country = choice('countries');
|
|
22
|
+
/** The source for the union type, the zod enum and the `FIELD_STRUCTURES` keys alike. */
|
|
23
|
+
export const FIELD_TYPE_IDS = ['short_text', 'long_text', 'list', 'address'];
|
|
19
24
|
/**
|
|
20
|
-
* What
|
|
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.
|
|
25
|
+
* What each type is composed of.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* An address is a delivery address, modeled on Stripe's Address object. Who the parcel is
|
|
28
|
+
* addressed to is not here: a parcel needs a name as well as an address, but that is a fact about
|
|
29
|
+
* posting parcels rather than about either type, and the name is often not the account name, as
|
|
30
|
+
* with gift subscriptions, workplace deliveries or c/o. A site that needs one keeps it in a field of
|
|
31
|
+
* its own, which is also how Stripe hands it back.
|
|
29
32
|
*/
|
|
30
|
-
export const
|
|
33
|
+
export const FIELD_STRUCTURES = {
|
|
34
|
+
short_text: { shape: 'scalar', leaf: text('line') },
|
|
35
|
+
long_text: { shape: 'scalar', leaf: text('long') },
|
|
36
|
+
list: { shape: 'set' },
|
|
37
|
+
address: {
|
|
38
|
+
shape: 'record',
|
|
39
|
+
parts: {
|
|
40
|
+
line1: text('line'),
|
|
41
|
+
line2: text('line'),
|
|
42
|
+
city: text('line'),
|
|
43
|
+
state: text('line'),
|
|
44
|
+
postal_code: text('code'),
|
|
45
|
+
country,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
31
49
|
/**
|
|
32
|
-
* The
|
|
33
|
-
*
|
|
34
|
-
* or a filter dispatches on, the way a field's own type is for a scalar.
|
|
50
|
+
* The keys a value has, typed as its type's keys rather than as strings, which is what
|
|
51
|
+
* `Object.keys` gives: a `for...in` over a value of a generic type gives each key as typed.
|
|
35
52
|
*/
|
|
36
|
-
export
|
|
53
|
+
export function keysOf(value) {
|
|
54
|
+
const keys = [];
|
|
55
|
+
for (const key in value) {
|
|
56
|
+
keys.push(key);
|
|
57
|
+
}
|
|
58
|
+
return keys;
|
|
59
|
+
}
|
|
60
|
+
/** How many options a set field offers: at least two, or it isn't a choice, and at most a hundred. */
|
|
61
|
+
export const MIN_OPTIONS = 2;
|
|
62
|
+
export const MAX_OPTIONS = 100;
|
|
37
63
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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.
|
|
64
|
+
* What joins a member's options in one cell when a set is exported, and splits them when one is
|
|
65
|
+
* imported. No option may contain it, so a cell always splits back into what was joined. Chosen
|
|
66
|
+
* because it is rare in names and no spreadsheet splits columns on it by default.
|
|
46
67
|
*/
|
|
47
|
-
export const
|
|
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
|
-
};
|
|
68
|
+
export const OPTION_SEPARATOR = '|';
|
|
59
69
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
70
|
+
* An option's text as it is matched: without surrounding spaces, and with accented letters
|
|
71
|
+
* encoded one way. Neither changes how the text reads, and without them an option pasted from a
|
|
72
|
+
* spreadsheet, or typed on a system that encodes accents differently, would not match one that
|
|
73
|
+
* reads exactly the same. Everything else is kept, case included, so an answer always reads as
|
|
74
|
+
* it was given.
|
|
64
75
|
*/
|
|
65
|
-
export function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
export function optionText(option) {
|
|
77
|
+
return option.trim().normalize('NFC');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* What two options are compared by to tell whether they are the same option: their text as
|
|
81
|
+
* `optionText` tidies it, without regard to case. A list offers each option once, so it can't
|
|
82
|
+
* offer both "Podcasts" and "podcasts". Lower-casing, upper-casing and lower-casing again also
|
|
83
|
+
* matches letters that one change of case keeps apart, such as "ß", "ẞ" and "SS", or a final "ς"
|
|
84
|
+
* and "σ", and does so the same way whatever the locale.
|
|
85
|
+
*/
|
|
86
|
+
export function optionKey(option) {
|
|
87
|
+
return optionText(option).toLowerCase().toUpperCase().toLowerCase();
|
|
88
|
+
}
|
|
89
|
+
/** The answers among `held` that a set field offers, in the order it offers them. */
|
|
90
|
+
export function inOptionOrder(settings, held) {
|
|
91
|
+
return settings.options.filter((option) => held.includes(option));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* What a form editing a set field's answers starts from: only the options the field offers now,
|
|
95
|
+
* since one it no longer offers can't be picked again. A field that has gone from several answers
|
|
96
|
+
* to one starts unanswered while the member holds several, even if only one of them is still
|
|
97
|
+
* offered, so the next answer replaces them rather than one being shown as their only answer.
|
|
98
|
+
*/
|
|
99
|
+
export function editableAnswers(settings, held) {
|
|
100
|
+
return !settings.multiple && held.length > 1 ? [] : inOptionOrder(settings, held);
|
|
70
101
|
}
|
|
71
|
-
/**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Whether a value names a type this build knows. A site newer than an integration can send a type
|
|
104
|
+
* the integration doesn't know, so check a type from the wire with this first. `constructor` and
|
|
105
|
+
* other names every object has are not types.
|
|
106
|
+
*/
|
|
107
|
+
export function isFieldType(value) {
|
|
108
|
+
return FIELD_TYPE_IDS.some((id) => id === value);
|
|
109
|
+
}
|
|
110
|
+
const hasShape = (shape) => (type) => isFieldType(type) && FIELD_STRUCTURES[type].shape === shape;
|
|
111
|
+
const isScalarType = hasShape('scalar');
|
|
112
|
+
const isRecordType = hasShape('record');
|
|
113
|
+
const isSetType = hasShape('set');
|
|
114
|
+
/** Every type of shape `shape`, in catalog order. */
|
|
115
|
+
export const typesOfShape = (shape) => FIELD_TYPE_IDS.filter(hasShape(shape));
|
|
116
|
+
/**
|
|
117
|
+
* Calls the handler for the shape `type` declares, with the type narrowed to that shape. Throws,
|
|
118
|
+
* naming the type, for one this build doesn't know; check with `isFieldType` first.
|
|
119
|
+
*/
|
|
120
|
+
export function matchShape(type, handlers) {
|
|
121
|
+
if (isScalarType(type)) {
|
|
122
|
+
return handlers.scalar(FIELD_STRUCTURES[type], type);
|
|
123
|
+
}
|
|
124
|
+
if (isRecordType(type)) {
|
|
125
|
+
return handlers.record(FIELD_STRUCTURES[type], type);
|
|
126
|
+
}
|
|
127
|
+
if (isSetType(type)) {
|
|
128
|
+
return handlers.set(FIELD_STRUCTURES[type], type);
|
|
129
|
+
}
|
|
130
|
+
return unknownType(type);
|
|
131
|
+
}
|
|
132
|
+
const fieldHasShape = (shape) => (field) => hasShape(shape)(field.type);
|
|
133
|
+
const isScalarField = fieldHasShape('scalar');
|
|
134
|
+
const isRecordField = fieldHasShape('record');
|
|
135
|
+
const isSetField = fieldHasShape('set');
|
|
136
|
+
/**
|
|
137
|
+
* Calls the handler for the shape a field's type declares, with the field narrowed to that shape,
|
|
138
|
+
* settings included, for a caller that needs what the field is set up with rather than only its
|
|
139
|
+
* type. Throws, naming the type, for one this build doesn't know.
|
|
140
|
+
*/
|
|
141
|
+
export function matchField(field, handlers) {
|
|
142
|
+
if (isScalarField(field)) {
|
|
143
|
+
return handlers.scalar(field);
|
|
144
|
+
}
|
|
145
|
+
if (isRecordField(field)) {
|
|
146
|
+
return handlers.record(field);
|
|
147
|
+
}
|
|
148
|
+
if (isSetField(field)) {
|
|
149
|
+
return handlers.set(field);
|
|
150
|
+
}
|
|
151
|
+
// A type this build doesn't know, which a plain JavaScript caller can still pass. The guards
|
|
152
|
+
// above can't narrow a generic field to never, so this can't be `unknownType`.
|
|
153
|
+
// oxlint-disable-next-line ghost/ghost-custom/no-native-error
|
|
154
|
+
throw new Error(`Unknown field type: ${String(field.type)}`);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Every shape has a branch above, so only a type this build doesn't know, which a plain
|
|
158
|
+
* JavaScript caller can still pass, reaches here.
|
|
159
|
+
*/
|
|
160
|
+
function unknownType(type) {
|
|
161
|
+
// A native error, because this module imports nothing, @tryghost/errors included.
|
|
162
|
+
// oxlint-disable-next-line ghost/ghost-custom/no-native-error
|
|
163
|
+
throw new Error(`Unknown field type: ${String(type)}`);
|
|
164
|
+
}
|
|
165
|
+
/** Calls the handler for a leaf's kind. */
|
|
166
|
+
export function matchLeaf(leaf, handlers) {
|
|
167
|
+
switch (leaf.leaf) {
|
|
168
|
+
case 'text':
|
|
169
|
+
return handlers.text(leaf);
|
|
170
|
+
case 'choice':
|
|
171
|
+
return handlers.choice(leaf);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Whether a value is laid out as a record: an object of parts, which a list is not. What a record
|
|
176
|
+
* must hold is its type's rule to decide.
|
|
177
|
+
*/
|
|
178
|
+
export function isRecordValue(value) {
|
|
179
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* A record type's parts in declaration order, or null for any other type, including one this build
|
|
183
|
+
* doesn't know. Parts are plain strings here; `FIELD_STRUCTURES[type].parts` gives one type's parts
|
|
184
|
+
* with typed keys.
|
|
185
|
+
*/
|
|
186
|
+
export function partsOf(type) {
|
|
187
|
+
if (!isFieldType(type)) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return matchShape(type, {
|
|
191
|
+
scalar: () => null,
|
|
192
|
+
record: ({ parts }) => Object.keys(parts),
|
|
193
|
+
set: () => null,
|
|
194
|
+
});
|
|
75
195
|
}
|
|
76
196
|
//# sourceMappingURL=structure.js.map
|