@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/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
+ }