@sdxc/structured-fields 0.0.0-pre.1

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.
@@ -0,0 +1,352 @@
1
+ import { failure, success } from "@sdxc/result";
2
+ import { encodeBase64 } from "./base64.js";
3
+ import { StructuredFieldStringifyError } from "./errors.js";
4
+ import { Decimal, DisplayString, Token } from "./values.js";
5
+ /** The largest magnitude an Integer may carry: fifteen nines. */
6
+ const MAX_INTEGER = 999_999_999_999_999;
7
+ /** A Decimal's integer part may carry at most twelve digits. */
8
+ const MAX_DECIMAL_INTEGER_DIGITS = 12;
9
+ /** The key grammar (section 3.1.2). */
10
+ const KEY = /^[a-z*][a-z0-9_\-.*]*$/;
11
+ /** The token grammar (section 3.3.4). */
12
+ const TOKEN = /^[A-Za-z*][!#$%&'*+\-.^_`|~0-9A-Za-z:/]*$/;
13
+ /** The characters an sf-string may carry: visible ASCII and space. */
14
+ const STRING_CHARS = /^[\x20-\x7e]*$/;
15
+ /** A UTF-16 surrogate without its pair, which has no UTF-8 encoding. */
16
+ const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
17
+ /** Encodes a Display String's text into the octets it percent-encodes. */
18
+ const UTF8 = new TextEncoder();
19
+ /**
20
+ * Serializes a value as its definition's top-level type (RFC 9651 section 4.1). An empty
21
+ * List or Dictionary serializes as `""`, which RFC 9651 reads as "do not send the field".
22
+ *
23
+ * @param value - The model, or members written in their shorthand forms
24
+ * @param type - The top-level type the field's definition fixes
25
+ * @returns The canonical text, or the path to the value with no representation
26
+ */
27
+ export function stringifyField(value, type) {
28
+ let state = { error: null };
29
+ let text;
30
+ if (type === "list")
31
+ text = serializeList(value, state);
32
+ else if (type === "dictionary")
33
+ text = serializeDictionary(value, state);
34
+ else
35
+ text = serializeItem(value, [], state);
36
+ return state.error ? failure(state.error) : success(text);
37
+ }
38
+ /**
39
+ * Records a failure unless an earlier one is already recorded.
40
+ *
41
+ * @param state - The shared failure slot
42
+ * @param message - Why the value has no representation
43
+ * @param path - Where the value sits
44
+ * @returns `""`, so a serializer writes `return reject(…)`
45
+ */
46
+ function reject(state, message, path) {
47
+ state.error ??= new StructuredFieldStringifyError(message, path);
48
+ return "";
49
+ }
50
+ /**
51
+ * @param value - Anything
52
+ * @returns Whether it is a plain object usable as Parameters or a Dictionary
53
+ */
54
+ function isRecord(value) {
55
+ return typeof value === "object" && value !== null && !Array.isArray(value);
56
+ }
57
+ /**
58
+ * @param value - Anything
59
+ * @returns Whether it is one of the bare item types
60
+ */
61
+ function isBareItem(value) {
62
+ return (typeof value === "number" ||
63
+ typeof value === "string" ||
64
+ typeof value === "boolean" ||
65
+ value instanceof Token ||
66
+ value instanceof Decimal ||
67
+ value instanceof DisplayString ||
68
+ value instanceof Uint8Array ||
69
+ value instanceof Date);
70
+ }
71
+ /**
72
+ * @param value - A member in any accepted form
73
+ * @returns Whether it is an Inner List, the one form carrying `items`
74
+ */
75
+ function isInnerList(value) {
76
+ return isRecord(value) && !isBareItem(value) && "items" in value;
77
+ }
78
+ /**
79
+ * Serializes a List (section 4.1.1), members joined by `, `.
80
+ *
81
+ * @param list - The members
82
+ * @param state - The shared failure slot
83
+ * @returns The text
84
+ */
85
+ function serializeList(list, state) {
86
+ if (!Array.isArray(list))
87
+ return reject(state, "List must be an array", []);
88
+ return list.map((member, index) => serializeMember(member, [index], state)).join(", ");
89
+ }
90
+ /**
91
+ * Serializes a Dictionary (section 4.1.2). A member whose value is `true` writes the
92
+ * bare key followed by its parameters.
93
+ *
94
+ * @param dictionary - The members, keyed
95
+ * @param state - The shared failure slot
96
+ * @returns The text
97
+ */
98
+ function serializeDictionary(dictionary, state) {
99
+ if (!isRecord(dictionary))
100
+ return reject(state, "Dictionary must be an object", []);
101
+ let parts = [];
102
+ for (let [key, member] of Object.entries(dictionary)) {
103
+ let text = serializeKey(key, [key], state);
104
+ if (member === true) {
105
+ parts.push(text);
106
+ }
107
+ else if (!isBareItem(member) && !isInnerList(member) && member.value === true) {
108
+ parts.push(text + serializeParameters(member.params, [key, "params"], state));
109
+ }
110
+ else {
111
+ parts.push(`${text}=${serializeMember(member, [key], state)}`);
112
+ }
113
+ }
114
+ return parts.join(", ");
115
+ }
116
+ /**
117
+ * Serializes a List or Dictionary member in any of its accepted forms.
118
+ *
119
+ * @param member - A bare item, an Item or an Inner List
120
+ * @param path - Where the member sits
121
+ * @param state - The shared failure slot
122
+ * @returns The text
123
+ */
124
+ function serializeMember(member, path, state) {
125
+ if (isInnerList(member))
126
+ return serializeInnerList(member, path, state);
127
+ return serializeItem(member, path, state);
128
+ }
129
+ /**
130
+ * Serializes an Inner List (section 4.1.1.1): items separated by spaces inside
131
+ * parentheses, then the list's parameters.
132
+ *
133
+ * @param list - The items and parameters
134
+ * @param path - Where the Inner List sits
135
+ * @param state - The shared failure slot
136
+ * @returns The text
137
+ */
138
+ function serializeInnerList(list, path, state) {
139
+ if (!Array.isArray(list.items))
140
+ return reject(state, "Inner List items must be an array", path);
141
+ let items = list.items.map((item, index) => serializeItem(item, [...path, "items", index], state));
142
+ return `(${items.join(" ")})${serializeParameters(list.params, [...path, "params"], state)}`;
143
+ }
144
+ /**
145
+ * Serializes an Item (section 4.1.3), written bare or with parameters.
146
+ *
147
+ * @param item - A bare item, or its value with parameters
148
+ * @param path - Where the Item sits
149
+ * @param state - The shared failure slot
150
+ * @returns The text
151
+ */
152
+ function serializeItem(item, path, state) {
153
+ if (isBareItem(item))
154
+ return serializeBareItem(item, path, state);
155
+ if (!isRecord(item) || !("value" in item)) {
156
+ return reject(state, "Expected a bare item, an Item or an Inner List", path);
157
+ }
158
+ let value = serializeBareItem(item.value, [...path, "value"], state);
159
+ return value + serializeParameters(item.params, [...path, "params"], state);
160
+ }
161
+ /**
162
+ * Serializes Parameters (section 4.1.1.2); a `true` value writes the bare key.
163
+ *
164
+ * @param params - The parameters, absent for none
165
+ * @param path - Where the parameters sit
166
+ * @param state - The shared failure slot
167
+ * @returns The text, each parameter prefixed with `;`
168
+ */
169
+ function serializeParameters(params, path, state) {
170
+ if (params === undefined)
171
+ return "";
172
+ if (!isRecord(params))
173
+ return reject(state, "Parameters must be an object", path);
174
+ let output = "";
175
+ for (let key of Object.keys(params)) {
176
+ let value = params[key];
177
+ output += `;${serializeKey(key, [...path, key], state)}`;
178
+ if (value !== true)
179
+ output += `=${serializeBareItem(value, [...path, key], state)}`;
180
+ }
181
+ return output;
182
+ }
183
+ /**
184
+ * Serializes a key (section 4.1.1.3).
185
+ *
186
+ * @param key - The key
187
+ * @param path - Where the keyed value sits
188
+ * @param state - The shared failure slot
189
+ * @returns The key
190
+ */
191
+ function serializeKey(key, path, state) {
192
+ if (!KEY.test(key)) {
193
+ return reject(state, "Key must be lowercase letters, digits, `_-.*`, first a letter or `*`", path);
194
+ }
195
+ return key;
196
+ }
197
+ /**
198
+ * Serializes a bare item (section 4.1.3.1), choosing the type from the JavaScript value.
199
+ * A `number` that is an integer is an Integer, and any other `number` a Decimal.
200
+ *
201
+ * @param value - The bare item
202
+ * @param path - Where it sits
203
+ * @param state - The shared failure slot
204
+ * @returns The text
205
+ */
206
+ function serializeBareItem(value, path, state) {
207
+ if (typeof value === "number") {
208
+ return Number.isInteger(value)
209
+ ? serializeInteger(value, path, state)
210
+ : serializeDecimal(value, path, state);
211
+ }
212
+ if (value instanceof Decimal)
213
+ return serializeDecimal(value.value, path, state);
214
+ if (typeof value === "string")
215
+ return serializeString(value, path, state);
216
+ if (value instanceof Token)
217
+ return serializeToken(value.value, path, state);
218
+ if (value instanceof Uint8Array)
219
+ return `:${encodeBase64(value)}:`;
220
+ if (typeof value === "boolean")
221
+ return value ? "?1" : "?0";
222
+ if (value instanceof Date)
223
+ return serializeDate(value, path, state);
224
+ if (value instanceof DisplayString)
225
+ return serializeDisplayString(value.value, path, state);
226
+ return reject(state, "Value is not a Structured Field bare item", path);
227
+ }
228
+ /**
229
+ * Serializes an Integer (section 4.1.4), at most fifteen digits.
230
+ *
231
+ * @param value - An integral number
232
+ * @param path - Where it sits
233
+ * @param state - The shared failure slot
234
+ * @returns The digits, `-` first when negative
235
+ */
236
+ function serializeInteger(value, path, state) {
237
+ if (Math.abs(value) > MAX_INTEGER)
238
+ return reject(state, "Integer is outside ±999,999,999,999,999", path);
239
+ return String(value + 0);
240
+ }
241
+ /**
242
+ * Serializes a Decimal (section 4.1.5): rounded half-to-even to three fractional digits,
243
+ * at most twelve integer digits, trailing fractional zeros dropped but one digit kept.
244
+ * Rounding works on the number's shortest decimal representation, so `0.0025` rounds as
245
+ * the decimal text it was written as, to `0.002`.
246
+ *
247
+ * @param value - The number
248
+ * @param path - Where it sits
249
+ * @param state - The shared failure slot
250
+ * @returns The text
251
+ */
252
+ function serializeDecimal(value, path, state) {
253
+ if (!Number.isFinite(value))
254
+ return reject(state, "Decimal must be a finite number", path);
255
+ let magnitude = Math.abs(value);
256
+ if (magnitude >= 1e12)
257
+ return reject(state, "Decimal has more than 12 integer digits", path);
258
+ let thousandths = roundToThousandths(magnitude);
259
+ let integer = thousandths / 1000n;
260
+ if (integer.toString().length > MAX_DECIMAL_INTEGER_DIGITS) {
261
+ return reject(state, "Decimal has more than 12 integer digits", path);
262
+ }
263
+ let fraction = (thousandths % 1000n).toString().padStart(3, "0").replace(/0+$/, "") || "0";
264
+ return `${value < 0 ? "-" : ""}${integer}.${fraction}`;
265
+ }
266
+ /**
267
+ * Rounds a non-negative number below 1e12 to a whole count of thousandths, half-to-even,
268
+ * reading its digits from `toString` so the rounding sees the decimal the caller wrote.
269
+ *
270
+ * @param magnitude - The absolute value
271
+ * @returns Thousandths, as a bigint to keep every digit exact
272
+ */
273
+ function roundToThousandths(magnitude) {
274
+ if (magnitude < 1e-6)
275
+ return 0n;
276
+ let [whole = "0", fraction = ""] = magnitude.toString().split(".");
277
+ let kept = BigInt(whole + fraction.slice(0, 3).padEnd(3, "0"));
278
+ let rest = fraction.slice(3);
279
+ if (rest === "")
280
+ return kept;
281
+ let first = rest.charCodeAt(0) - 48;
282
+ let beyondHalf = /[1-9]/.test(rest.slice(1));
283
+ if (first > 5 || (first === 5 && (beyondHalf || kept % 2n === 1n)))
284
+ return kept + 1n;
285
+ return kept;
286
+ }
287
+ /**
288
+ * Serializes a String (section 4.1.6): visible ASCII and space, escaping `"` and `\`.
289
+ *
290
+ * @param value - The string
291
+ * @param path - Where it sits
292
+ * @param state - The shared failure slot
293
+ * @returns The quoted text
294
+ */
295
+ function serializeString(value, path, state) {
296
+ if (!STRING_CHARS.test(value)) {
297
+ return reject(state, "String may only carry visible ASCII and space; use DisplayString", path);
298
+ }
299
+ return `"${value.replace(/["\\]/g, "\\$&")}"`;
300
+ }
301
+ /**
302
+ * Serializes a Token (section 4.1.7).
303
+ *
304
+ * @param value - The token text
305
+ * @param path - Where it sits
306
+ * @param state - The shared failure slot
307
+ * @returns The token
308
+ */
309
+ function serializeToken(value, path, state) {
310
+ if (!TOKEN.test(value))
311
+ return reject(state, "Token breaks the token grammar", path);
312
+ return value;
313
+ }
314
+ /**
315
+ * Serializes a Date (section 4.1.10): `@` and whole seconds since the epoch.
316
+ *
317
+ * @param value - The Date
318
+ * @param path - Where it sits
319
+ * @param state - The shared failure slot
320
+ * @returns The text
321
+ */
322
+ function serializeDate(value, path, state) {
323
+ let time = value.getTime();
324
+ if (Number.isNaN(time))
325
+ return reject(state, "Date is invalid", path);
326
+ if (time % 1000 !== 0)
327
+ return reject(state, "Date must be whole seconds", path);
328
+ return `@${serializeInteger(time / 1000, path, state)}`;
329
+ }
330
+ /**
331
+ * Serializes a Display String (section 4.1.11): `%"`, then each UTF-8 octet as itself
332
+ * when it is visible ASCII other than `%` and `"`, and as lowercase `%xx` otherwise.
333
+ *
334
+ * @param value - The Unicode text
335
+ * @param path - Where it sits
336
+ * @param state - The shared failure slot
337
+ * @returns The text
338
+ */
339
+ function serializeDisplayString(value, path, state) {
340
+ if (LONE_SURROGATE.test(value))
341
+ return reject(state, "Display String has an unpaired surrogate", path);
342
+ let output = '%"';
343
+ for (let octet of UTF8.encode(value)) {
344
+ if (octet === 0x25 || octet === 0x22 || octet < 0x20 || octet > 0x7e) {
345
+ output += `%${octet.toString(16).padStart(2, "0")}`;
346
+ }
347
+ else {
348
+ output += String.fromCharCode(octet);
349
+ }
350
+ }
351
+ return `${output}"`;
352
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The Structured Field value model as types: what `parse` produces and what `stringify`
3
+ * accepts. Kept in one namespace so a caller writes `SF.Dictionary` next to the functions
4
+ * that read and write one.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
10
+ import type { Decimal, DisplayString, Token } from "./values.js";
11
+ /**
12
+ * Groups the Structured Field value model under a single import surface.
13
+ */
14
+ export declare namespace SF {
15
+ /**
16
+ * Integer and Decimal as `number` and `Decimal`, String as `string`, Token as `Token`,
17
+ * Byte Sequence as `Uint8Array`, Boolean as `boolean`, Date as `Date`,
18
+ * Display String as `DisplayString`.
19
+ */
20
+ type BareItem = number | Decimal | string | Token | Uint8Array | boolean | Date | DisplayString;
21
+ /** Keys in insertion order, on a null-prototype object. */
22
+ type Parameters = Record<string, BareItem>;
23
+ /**
24
+ * A bare item with its parameters.
25
+ *
26
+ * @template Value - The bare item type the field allows
27
+ */
28
+ interface Item<Value extends BareItem = BareItem> {
29
+ value: Value;
30
+ params: Parameters;
31
+ }
32
+ /** A parenthesized list of items, itself carrying parameters. */
33
+ interface InnerList {
34
+ items: Item[];
35
+ params: Parameters;
36
+ }
37
+ /** One List or Dictionary member. */
38
+ type Member = Item | InnerList;
39
+ /** Members in field order. */
40
+ type List = Member[];
41
+ /** Members keyed in field order, on a null-prototype object. */
42
+ type Dictionary = Record<string, Member>;
43
+ /** The top-level type a field's definition fixes. */
44
+ type FieldType = "list" | "dictionary" | "item";
45
+ /** The value `parse` produces for each field type. */
46
+ interface ValueOf {
47
+ list: List;
48
+ dictionary: Dictionary;
49
+ item: Item;
50
+ }
51
+ /** An Item as `stringify` accepts it, with `params` optional. */
52
+ interface ItemInput {
53
+ value: BareItem;
54
+ params?: Parameters;
55
+ }
56
+ /** An Inner List as `stringify` accepts it, each item bare or with parameters. */
57
+ interface InnerListInput {
58
+ items: Array<BareItem | ItemInput>;
59
+ params?: Parameters;
60
+ }
61
+ /**
62
+ * What `stringify` accepts: the parsed model, or any member written as its bare value
63
+ * (a member without parameters), and `params` optional everywhere.
64
+ */
65
+ type MemberInput = BareItem | ItemInput | InnerListInput;
66
+ /** The value `stringify` accepts for each field type. */
67
+ interface InputOf {
68
+ list: MemberInput[];
69
+ dictionary: Record<string, MemberInput>;
70
+ item: BareItem | ItemInput;
71
+ }
72
+ /**
73
+ * A Standard Schema whose `validate` answers synchronously, which is every
74
+ * `remix/data-schema` schema. Header parsing sits on hot paths that stay synchronous,
75
+ * so a schema with an async refinement is a type error here.
76
+ *
77
+ * @template Output - What the schema produces
78
+ */
79
+ type SyncSchema<Output = unknown> = StandardSchemaV1<unknown, Output> & {
80
+ readonly "~standard": {
81
+ validate(value: unknown, options?: StandardSchemaV1.Options): StandardSchemaV1.Result<Output>;
82
+ };
83
+ };
84
+ }
File without changes
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The bare item wrappers for the three RFC 9651 types a JavaScript primitive cannot tell
3
+ * apart from another type: Token from String, Decimal from Integer, Display String from
4
+ * String. Each keeps its type through a parse and stringify round trip.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ /**
10
+ * An sf-token: a bare word such as `HIT` or `text/html`, distinct from the sf-string `"HIT"`.
11
+ * Construction always succeeds; `stringify` checks the token grammar when it writes one.
12
+ */
13
+ export declare class Token {
14
+ readonly value: string;
15
+ /**
16
+ * @param value - The token text, written without quotes
17
+ */
18
+ constructor(value: string);
19
+ /**
20
+ * @returns The token text, so a Token interpolates as the word it carries
21
+ */
22
+ toString(): string;
23
+ }
24
+ /**
25
+ * An sf-decimal, so `1.0` stays a Decimal through a round trip where a plain `1` would be
26
+ * written as an Integer. `stringify` rounds it half-to-even to three fractional digits.
27
+ */
28
+ export declare class Decimal {
29
+ readonly value: number;
30
+ /**
31
+ * @param value - The number to write as a Decimal
32
+ */
33
+ constructor(value: number);
34
+ /**
35
+ * @returns The number, so a Decimal takes part in arithmetic and comparisons directly
36
+ */
37
+ valueOf(): number;
38
+ }
39
+ /**
40
+ * An sf-displaystring: Unicode text, written as `%"…"` with its non-ASCII UTF-8 bytes
41
+ * percent-encoded. A plain JavaScript string is an sf-string, which only carries ASCII.
42
+ */
43
+ export declare class DisplayString {
44
+ readonly value: string;
45
+ /**
46
+ * @param value - The Unicode text to carry
47
+ */
48
+ constructor(value: string);
49
+ /**
50
+ * @returns The Unicode text
51
+ */
52
+ toString(): string;
53
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The bare item wrappers for the three RFC 9651 types a JavaScript primitive cannot tell
3
+ * apart from another type: Token from String, Decimal from Integer, Display String from
4
+ * String. Each keeps its type through a parse and stringify round trip.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ /**
10
+ * An sf-token: a bare word such as `HIT` or `text/html`, distinct from the sf-string `"HIT"`.
11
+ * Construction always succeeds; `stringify` checks the token grammar when it writes one.
12
+ */
13
+ export class Token {
14
+ value;
15
+ /**
16
+ * @param value - The token text, written without quotes
17
+ */
18
+ constructor(value) {
19
+ this.value = value;
20
+ }
21
+ /**
22
+ * @returns The token text, so a Token interpolates as the word it carries
23
+ */
24
+ toString() {
25
+ return this.value;
26
+ }
27
+ }
28
+ /**
29
+ * An sf-decimal, so `1.0` stays a Decimal through a round trip where a plain `1` would be
30
+ * written as an Integer. `stringify` rounds it half-to-even to three fractional digits.
31
+ */
32
+ export class Decimal {
33
+ value;
34
+ /**
35
+ * @param value - The number to write as a Decimal
36
+ */
37
+ constructor(value) {
38
+ this.value = value;
39
+ }
40
+ /**
41
+ * @returns The number, so a Decimal takes part in arithmetic and comparisons directly
42
+ */
43
+ valueOf() {
44
+ return this.value;
45
+ }
46
+ }
47
+ /**
48
+ * An sf-displaystring: Unicode text, written as `%"…"` with its non-ASCII UTF-8 bytes
49
+ * percent-encoded. A plain JavaScript string is an sf-string, which only carries ASCII.
50
+ */
51
+ export class DisplayString {
52
+ value;
53
+ /**
54
+ * @param value - The Unicode text to carry
55
+ */
56
+ constructor(value) {
57
+ this.value = value;
58
+ }
59
+ /**
60
+ * @returns The Unicode text
61
+ */
62
+ toString() {
63
+ return this.value;
64
+ }
65
+ }
@@ -0,0 +1,56 @@
1
+ import type { Schema } from "remix/data-schema";
2
+ import type { SF } from "./index.js";
3
+ /** The schema builders, as one catalog of functions. */
4
+ export interface StructuredFieldSchemas {
5
+ /** An Integer, checked against the RFC range of fifteen digits. */
6
+ integer(): Schema<SF.BareItem, number>;
7
+ /** A Decimal or Integer, output as `number`. */
8
+ decimal(): Schema<SF.BareItem, number>;
9
+ /**
10
+ * A Token, output as its text; with a list, narrowed to those literals.
11
+ *
12
+ * @template T - The literals the field allows
13
+ */
14
+ token<const T extends string = string>(allowed?: readonly T[]): Schema<SF.BareItem, T>;
15
+ /** A Display String, output as its text. A plain sf-string is `s.string()`. */
16
+ displayString(): Schema<SF.BareItem, string>;
17
+ /** A Byte Sequence. */
18
+ bytes(): Schema<SF.BareItem, Uint8Array>;
19
+ /** A Date. */
20
+ date(): Schema<SF.BareItem, Date>;
21
+ /**
22
+ * An Item: its bare value through `value`, its parameters through `params`, which pass
23
+ * through untouched when no parameter schema is given.
24
+ *
25
+ * @template V - The bare value's output
26
+ * @template P - The parameters' output
27
+ */
28
+ item<V, P = SF.Parameters>(value: SF.SyncSchema<V>, params?: SF.SyncSchema<P>): Schema<SF.Member, {
29
+ value: V;
30
+ params: P;
31
+ }>;
32
+ /**
33
+ * An Item whose parameters the caller ignores, output as its bare value alone.
34
+ *
35
+ * @template V - The bare value's output
36
+ */
37
+ value<V>(value: SF.SyncSchema<V>): Schema<SF.Member, V>;
38
+ /**
39
+ * An Inner List of items matching `item`.
40
+ *
41
+ * @template I - Each item's output
42
+ * @template P - The list parameters' output
43
+ */
44
+ innerList<I, P = SF.Parameters>(item: SF.SyncSchema<I>, params?: SF.SyncSchema<P>): Schema<SF.Member, {
45
+ items: I[];
46
+ params: P;
47
+ }>;
48
+ }
49
+ /**
50
+ * The Structured Field schema builders. Each call returns a fresh schema, and none holds
51
+ * state, so a field's schema is built once at module scope and reused on every request.
52
+ *
53
+ * @example s.object({ u: s.optional(sf.value(sf.integer())) })
54
+ * @example sf.item(sf.token(), s.object({ ttl: s.optional(sf.integer()) }))
55
+ */
56
+ export declare const sf: StructuredFieldSchemas;