@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.
- package/LICENSE.md +21 -0
- package/README.md +269 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +71 -0
- package/dist/lib/base64.d.ts +24 -0
- package/dist/lib/base64.js +44 -0
- package/dist/lib/errors.d.ts +35 -0
- package/dist/lib/errors.js +41 -0
- package/dist/lib/parse.d.ts +19 -0
- package/dist/lib/parse.js +475 -0
- package/dist/lib/stringify.d.ts +20 -0
- package/dist/lib/stringify.js +352 -0
- package/dist/lib/types.d.ts +84 -0
- package/dist/lib/types.js +0 -0
- package/dist/lib/values.d.ts +53 -0
- package/dist/lib/values.js +65 -0
- package/dist/schema.d.ts +56 -0
- package/dist/schema.js +181 -0
- package/package.json +25 -0
package/dist/schema.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { createSchema, fail } from "remix/data-schema";
|
|
2
|
+
import { Decimal, DisplayString, Token } from "./index.js";
|
|
3
|
+
/** The largest magnitude an Integer may carry: fifteen nines. */
|
|
4
|
+
const MAX_INTEGER = 999_999_999_999_999;
|
|
5
|
+
/**
|
|
6
|
+
* Runs a nested schema at a path inside the field. A `remix/data-schema` schema runs with
|
|
7
|
+
* the path and options directly; any other Standard Schema has its issue paths prefixed.
|
|
8
|
+
*
|
|
9
|
+
* @param schema - The nested schema
|
|
10
|
+
* @param value - The nested value
|
|
11
|
+
* @param context - Where it sits
|
|
12
|
+
* @returns The nested result
|
|
13
|
+
*/
|
|
14
|
+
function run(schema, value, context) {
|
|
15
|
+
if ("~run" in schema && typeof schema["~run"] === "function") {
|
|
16
|
+
return schema["~run"](value, context);
|
|
17
|
+
}
|
|
18
|
+
let result = schema["~standard"].validate(value, context.options);
|
|
19
|
+
if (result instanceof Promise)
|
|
20
|
+
return fail("Schema must validate synchronously", context.path);
|
|
21
|
+
if (!result.issues)
|
|
22
|
+
return result;
|
|
23
|
+
return {
|
|
24
|
+
issues: result.issues.map((issue) => ({
|
|
25
|
+
...issue,
|
|
26
|
+
path: [...context.path, ...(issue.path ?? [])],
|
|
27
|
+
})),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Builds the failure a helper reports for an input of the wrong type.
|
|
32
|
+
*
|
|
33
|
+
* @param message - What the helper expected
|
|
34
|
+
* @param code - The error-map code, so callers can localize the message
|
|
35
|
+
* @param value - The input
|
|
36
|
+
* @param context - Where it sits
|
|
37
|
+
* @returns The failure
|
|
38
|
+
*/
|
|
39
|
+
function reject(message, code, value, context) {
|
|
40
|
+
return fail(message, context.path, { code, input: value, parseOptions: context.options });
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* @param value - Any model value
|
|
44
|
+
* @returns Whether it is an Item, the member form without `items`
|
|
45
|
+
*/
|
|
46
|
+
function isItem(value) {
|
|
47
|
+
return typeof value === "object" && value !== null && "value" in value && !("items" in value);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* @param value - Any model value
|
|
51
|
+
* @returns Whether it is an Inner List
|
|
52
|
+
*/
|
|
53
|
+
function isInnerList(value) {
|
|
54
|
+
return (typeof value === "object" && value !== null && "items" in value && Array.isArray(value.items));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Validates a member's parameters, passing them through when no schema is given.
|
|
58
|
+
*
|
|
59
|
+
* @param schema - The parameter schema, if any
|
|
60
|
+
* @param params - The member's parameters
|
|
61
|
+
* @param context - Where the member sits
|
|
62
|
+
* @returns The parameters' result
|
|
63
|
+
*/
|
|
64
|
+
function runParams(schema, params, context) {
|
|
65
|
+
let input = params ?? Object.create(null);
|
|
66
|
+
if (schema === undefined)
|
|
67
|
+
return { value: input };
|
|
68
|
+
return run(schema, input, { path: [...context.path, "params"], options: context.options });
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The Structured Field schema builders. Each call returns a fresh schema, and none holds
|
|
72
|
+
* state, so a field's schema is built once at module scope and reused on every request.
|
|
73
|
+
*
|
|
74
|
+
* @example s.object({ u: s.optional(sf.value(sf.integer())) })
|
|
75
|
+
* @example sf.item(sf.token(), s.object({ ttl: s.optional(sf.integer()) }))
|
|
76
|
+
*/
|
|
77
|
+
export const sf = {
|
|
78
|
+
integer() {
|
|
79
|
+
return createSchema((value, context) => {
|
|
80
|
+
if (typeof value === "number" && Number.isInteger(value) && Math.abs(value) <= MAX_INTEGER) {
|
|
81
|
+
return { value };
|
|
82
|
+
}
|
|
83
|
+
return reject("Expected an Integer", "sf.integer", value, context);
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
decimal() {
|
|
87
|
+
return createSchema((value, context) => {
|
|
88
|
+
if (value instanceof Decimal)
|
|
89
|
+
return { value: value.value };
|
|
90
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
91
|
+
return { value };
|
|
92
|
+
return reject("Expected a Decimal", "sf.decimal", value, context);
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
token(allowed) {
|
|
96
|
+
return createSchema((value, context) => {
|
|
97
|
+
if (!(value instanceof Token))
|
|
98
|
+
return reject("Expected a Token", "sf.token", value, context);
|
|
99
|
+
if (allowed && !allowed.includes(value.value)) {
|
|
100
|
+
return fail(`Expected one of ${allowed.join(", ")}`, context.path, {
|
|
101
|
+
code: "sf.token.allowed",
|
|
102
|
+
input: value,
|
|
103
|
+
values: { allowed: allowed.join(", ") },
|
|
104
|
+
parseOptions: context.options,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return { value: value.value };
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
displayString() {
|
|
111
|
+
return createSchema((value, context) => {
|
|
112
|
+
if (value instanceof DisplayString)
|
|
113
|
+
return { value: value.value };
|
|
114
|
+
return reject("Expected a Display String", "sf.displayString", value, context);
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
bytes() {
|
|
118
|
+
return createSchema((value, context) => {
|
|
119
|
+
if (value instanceof Uint8Array)
|
|
120
|
+
return { value };
|
|
121
|
+
return reject("Expected a Byte Sequence", "sf.bytes", value, context);
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
date() {
|
|
125
|
+
return createSchema((value, context) => {
|
|
126
|
+
if (value instanceof Date)
|
|
127
|
+
return { value };
|
|
128
|
+
return reject("Expected a Date", "sf.date", value, context);
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
item(value, params) {
|
|
132
|
+
return createSchema((input, context) => {
|
|
133
|
+
if (!isItem(input))
|
|
134
|
+
return reject("Expected an Item", "sf.item", input, context);
|
|
135
|
+
let bare = run(value, input.value, {
|
|
136
|
+
path: [...context.path, "value"],
|
|
137
|
+
options: context.options,
|
|
138
|
+
});
|
|
139
|
+
let parameters = runParams(params, input.params, context);
|
|
140
|
+
if (bare.issues || parameters.issues) {
|
|
141
|
+
return { issues: [...(bare.issues ?? []), ...(parameters.issues ?? [])] };
|
|
142
|
+
}
|
|
143
|
+
return { value: { value: bare.value, params: parameters.value } };
|
|
144
|
+
});
|
|
145
|
+
},
|
|
146
|
+
value(value) {
|
|
147
|
+
return createSchema((input, context) => {
|
|
148
|
+
if (!isItem(input))
|
|
149
|
+
return reject("Expected an Item", "sf.item", input, context);
|
|
150
|
+
return run(value, input.value, {
|
|
151
|
+
path: [...context.path, "value"],
|
|
152
|
+
options: context.options,
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
innerList(item, params) {
|
|
157
|
+
return createSchema((input, context) => {
|
|
158
|
+
if (!isInnerList(input)) {
|
|
159
|
+
return reject("Expected an Inner List", "sf.innerList", input, context);
|
|
160
|
+
}
|
|
161
|
+
let issues = [];
|
|
162
|
+
let items = [];
|
|
163
|
+
input.items.forEach((member, index) => {
|
|
164
|
+
let result = run(item, member, {
|
|
165
|
+
path: [...context.path, "items", index],
|
|
166
|
+
options: context.options,
|
|
167
|
+
});
|
|
168
|
+
if (result.issues)
|
|
169
|
+
issues.push(...result.issues);
|
|
170
|
+
else
|
|
171
|
+
items.push(result.value);
|
|
172
|
+
});
|
|
173
|
+
let parameters = runParams(params, input.params, context);
|
|
174
|
+
if (parameters.issues)
|
|
175
|
+
issues.push(...parameters.issues);
|
|
176
|
+
if (issues.length > 0 || parameters.issues)
|
|
177
|
+
return { issues };
|
|
178
|
+
return { value: { items, params: parameters.value } };
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sdxc/structured-fields",
|
|
3
|
+
"version": "0.0.0-pre.1",
|
|
4
|
+
"description": "Parse and serialize RFC 9651 structured HTTP field values",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./schema": "./dist/schema.js"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@sdxc/result": "2026.9.15",
|
|
13
|
+
"@standard-schema/spec": "^1.1.0",
|
|
14
|
+
"remix": "3.0.0-rc.2"
|
|
15
|
+
},
|
|
16
|
+
"gitHead": "f0a77f407e4c30389df65cd8138b150e367da94f",
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/sergiodxa/monorepo.git",
|
|
23
|
+
"directory": "packages/structured-fields"
|
|
24
|
+
}
|
|
25
|
+
}
|