@sdxc/structured-fields 2026.10.6 → 2026.10.16
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/README.md +156 -104
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -2,39 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
Parse and serialize RFC 9651 structured HTTP field values.
|
|
4
4
|
|
|
5
|
+
[RFC 9651](https://www.rfc-editor.org/rfc/rfc9651) is the grammar behind fields such as
|
|
6
|
+
`Priority`, `Cache-Status`, `RateLimit`, `Idempotency-Key` and `Repr-Digest`: each is a List, a
|
|
7
|
+
Dictionary or an Item of typed bare values. Every call names that top-level type (`"list"`,
|
|
8
|
+
`"dictionary"` or `"item"`), since the field's definition fixes it. Parsing passes the HTTP
|
|
9
|
+
working group's [structured-field-tests](https://github.com/httpwg/structured-field-tests) suite.
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
|
-
```
|
|
13
|
+
```sh
|
|
8
14
|
npm add @sdxc/structured-fields
|
|
9
15
|
```
|
|
10
16
|
|
|
11
17
|
Results are [`@sdxc/result`](https://www.npmjs.com/package/@sdxc/result) values, and the
|
|
12
|
-
`./schema` builders
|
|
13
|
-
`remix/data-schema`. Both install alongside this package.
|
|
14
|
-
|
|
15
|
-
[RFC 9651](https://www.rfc-editor.org/rfc/rfc9651) defines one grammar for new HTTP fields:
|
|
16
|
-
`Priority`, `Cache-Status`, `RateLimit`, `Idempotency-Key`, `Repr-Digest` and the client hints
|
|
17
|
-
are all a List, a Dictionary or an Item of typed bare values. This package implements the RFC's
|
|
18
|
-
parsing and serialization algorithms, checked against the HTTP working group's
|
|
19
|
-
[structured-field-tests](https://github.com/httpwg/structured-field-tests) suite. Every call
|
|
20
|
-
names the field's top-level type (`"list"`, `"dictionary"` or `"item"`), because the RFC fixes
|
|
21
|
-
it in the field's definition rather than in the text. Parsing fails the whole field on any
|
|
22
|
-
error, as the RFC requires, and never returns a partial value.
|
|
18
|
+
`./schema` builders are `remix/data-schema` schemas from [`remix`](https://www.npmjs.com/package/remix).
|
|
23
19
|
|
|
24
20
|
## Usage
|
|
25
21
|
|
|
26
|
-
### Write
|
|
22
|
+
### Write a field
|
|
27
23
|
|
|
28
24
|
```typescript
|
|
29
25
|
import { setField, stringify } from "@sdxc/structured-fields";
|
|
30
26
|
|
|
27
|
+
let headers = new Headers();
|
|
31
28
|
setField(headers, "RateLimit", { limit: 10, remaining: 0, reset: 7 }, "dictionary");
|
|
32
29
|
// RateLimit: limit=10, remaining=0, reset=7
|
|
33
30
|
|
|
34
31
|
stringify({ value: 10, params: { w: 60 } }, "item"); // success: "10;w=60"
|
|
35
32
|
```
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
Members take their shorthand: a bare value for a member without parameters, `params` omitted
|
|
35
|
+
anywhere, and `true` written as the bare key.
|
|
36
|
+
|
|
37
|
+
### Read the untyped model
|
|
38
38
|
|
|
39
39
|
```typescript
|
|
40
40
|
import { parse } from "@sdxc/structured-fields";
|
|
@@ -46,7 +46,21 @@ let parsed = parse('ExampleCache; hit; ttl=376, "Origin"; fwd=uri-miss', "list")
|
|
|
46
46
|
// ]
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Any error fails the whole field with a `StructuredFieldParseError`; a failure never carries a
|
|
50
|
+
partial value.
|
|
51
|
+
|
|
52
|
+
### Keep a type a primitive cannot carry
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { Decimal, DisplayString, Token, stringify } from "@sdxc/structured-fields";
|
|
56
|
+
|
|
57
|
+
stringify([new Token("HIT"), "HIT", new Decimal(1), 1, new DisplayString("fü")], "list");
|
|
58
|
+
// success: 'HIT, "HIT", 1.0, 1, %"f%c3%bc"'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`parse` returns the same wrappers, so a round trip keeps every type.
|
|
62
|
+
|
|
63
|
+
### Read a field through a schema
|
|
50
64
|
|
|
51
65
|
```typescript
|
|
52
66
|
import { isSuccess } from "@sdxc/result";
|
|
@@ -55,18 +69,18 @@ import { sf } from "@sdxc/structured-fields/schema";
|
|
|
55
69
|
import * as s from "remix/data-schema";
|
|
56
70
|
import * as checks from "remix/data-schema/checks";
|
|
57
71
|
|
|
58
|
-
|
|
72
|
+
let priority = s.object({
|
|
59
73
|
u: s.optional(sf.value(sf.integer().pipe(checks.min(0), checks.max(7)))),
|
|
60
74
|
i: s.optional(sf.value(s.boolean())),
|
|
61
75
|
});
|
|
62
76
|
|
|
63
|
-
let
|
|
64
|
-
if (isSuccess(
|
|
65
|
-
|
|
77
|
+
let result = getField(request.headers, "Priority", "dictionary", priority);
|
|
78
|
+
if (isSuccess(result) && result.data) {
|
|
79
|
+
result.data.u; // number | undefined
|
|
66
80
|
}
|
|
67
81
|
```
|
|
68
82
|
|
|
69
|
-
`sf.*` schemas hold no state, so
|
|
83
|
+
`sf.*` schemas hold no state, so build a field's schema once at module scope.
|
|
70
84
|
|
|
71
85
|
## API
|
|
72
86
|
|
|
@@ -74,90 +88,56 @@ if (isSuccess(priority) && priority.data) {
|
|
|
74
88
|
|
|
75
89
|
#### `parse(text, type, schema?)`
|
|
76
90
|
|
|
77
|
-
Parses a field value as `type
|
|
78
|
-
`StructuredFieldParseError`. With a synchronous Standard Schema it
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
parse("u=1, i", "dictionary"); // success: { u: { value: 1, params: {} }, i: { value: true, params: {} } }
|
|
84
|
-
parse("1, 2,", "list"); // failure: StructuredFieldParseError, position 5
|
|
85
|
-
```
|
|
91
|
+
Parses a field value as `type` into `SF.ValueOf[type]`, or fails with a
|
|
92
|
+
`StructuredFieldParseError`. With a synchronous Standard Schema, it answers the schema's output
|
|
93
|
+
or a `ValidationError`. Field lines joined with `,` (what `Headers#get` returns) parse as one
|
|
94
|
+
value.
|
|
86
95
|
|
|
87
96
|
#### `stringify(value, type)`
|
|
88
97
|
|
|
89
|
-
Serializes to
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
- A `number` that is an integer writes as an Integer (at most 15 digits), any other `number`
|
|
95
|
-
as a Decimal rounded half-to-even to three fractional digits
|
|
96
|
-
- A `string` writes as a quoted sf-string, which carries printable ASCII only
|
|
97
|
-
- A member or parameter whose value is `true` writes as the bare key
|
|
98
|
-
- A `Date` writes as whole seconds; one with milliseconds fails
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
stringify([1, 1.5, new Decimal(1)], "list"); // success: "1, 1.5, 1.0"
|
|
102
|
-
stringify({ i: true, u: 3 }, "dictionary"); // success: "i, u=3"
|
|
103
|
-
stringify("fü", "item"); // failure: use new DisplayString("fü")
|
|
104
|
-
```
|
|
98
|
+
Serializes `SF.InputOf[type]` to canonical text, or fails with a
|
|
99
|
+
`StructuredFieldStringifyError`. An empty List or Dictionary succeeds with `""`. An integral
|
|
100
|
+
`number` writes as an Integer (at most 15 digits), any other as a Decimal rounded half-to-even to
|
|
101
|
+
three digits; a `string` writes as a quoted ASCII sf-string; a `Date` writes as whole seconds.
|
|
105
102
|
|
|
106
103
|
#### `getField(headers, name, type, schema?)`
|
|
107
104
|
|
|
108
|
-
|
|
105
|
+
`parse` over `headers.get(name)`. An absent field succeeds with `null`.
|
|
109
106
|
|
|
110
107
|
#### `setField(headers, name, value, type)`
|
|
111
108
|
|
|
112
|
-
|
|
113
|
-
Dictionary deletes the field
|
|
109
|
+
`stringify` onto `headers.set(name, …)`, replacing any earlier value. An empty List or
|
|
110
|
+
Dictionary deletes the field, and a failure leaves `headers` untouched.
|
|
114
111
|
|
|
115
112
|
#### `Token`, `Decimal`, `DisplayString`
|
|
116
113
|
|
|
117
|
-
Wrappers
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
round trip is lossless. Byte Sequences are `Uint8Array`, Dates are `Date`, Booleans are
|
|
121
|
-
`boolean`.
|
|
114
|
+
Wrappers holding a readonly `value` for the types a primitive cannot tell apart: an sf-token
|
|
115
|
+
(`HIT` against the string `"HIT"`), a Decimal (`1.0` against the Integer `1`), and a Unicode
|
|
116
|
+
Display String (`%"f%c3%bc"`). Byte Sequences are `Uint8Array`, Dates `Date`, Booleans `boolean`.
|
|
122
117
|
|
|
123
118
|
#### `StructuredFieldParseError`
|
|
124
119
|
|
|
125
|
-
`position` is the offset
|
|
126
|
-
|
|
120
|
+
`position` is the offset where parsing stopped. RFC 9651 has a recipient ignore an invalid field,
|
|
121
|
+
so treat this failure as an absent field.
|
|
127
122
|
|
|
128
123
|
#### `StructuredFieldStringifyError`
|
|
129
124
|
|
|
130
|
-
`path` lists the keys and indices
|
|
125
|
+
`path` lists the keys and indices to the value with no representation, such as
|
|
131
126
|
`[0, "params", "q"]`.
|
|
132
127
|
|
|
133
128
|
#### `ValidationError`
|
|
134
129
|
|
|
135
|
-
`remix/data-schema`'s error, re-exported so
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
#### `SF` (types)
|
|
139
|
-
|
|
140
|
-
| Type | Meaning |
|
|
141
|
-
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
142
|
-
| `SF.BareItem` | `number \| Decimal \| string \| Token \| Uint8Array \| boolean \| Date \| DisplayString` |
|
|
143
|
-
| `SF.Parameters` | `Record<string, BareItem>` on a null-prototype object, in field order |
|
|
144
|
-
| `SF.Item` | `{ value, params }` |
|
|
145
|
-
| `SF.InnerList` | `{ items: Item[], params }` |
|
|
146
|
-
| `SF.Member`, `SF.List`, `SF.Dictionary` | A List or Dictionary member, the List, and the Dictionary (null prototype) |
|
|
147
|
-
| `SF.FieldType` | `"list" \| "dictionary" \| "item"` |
|
|
148
|
-
| `SF.ValueOf`, `SF.InputOf` | What `parse` returns and what `stringify` accepts, per field type |
|
|
149
|
-
| `SF.ItemInput`, `SF.InnerListInput`, `SF.MemberInput` | The shorthand member forms `stringify` accepts |
|
|
150
|
-
| `SF.SyncSchema<Output>` | A Standard Schema whose `validate` answers synchronously |
|
|
130
|
+
`remix/data-schema`'s error, re-exported so both failures branch from one import; `issues` holds
|
|
131
|
+
the Standard Schema issues.
|
|
151
132
|
|
|
152
133
|
### `@sdxc/structured-fields/schema`
|
|
153
134
|
|
|
154
135
|
#### `sf`
|
|
155
136
|
|
|
156
|
-
Schema builders
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
`StructuredFieldSchemas` is its type.
|
|
137
|
+
Schema builders over the model that compose with `s.object`, `s.array`, `s.union`, `s.optional`,
|
|
138
|
+
`.pipe()` and `.refine()`. Issue paths point into the field (`["u", "value"]`,
|
|
139
|
+
`[0, "items", 1, "value"]`) and carry an error-map code (`sf.integer`, `sf.token.allowed`, …)
|
|
140
|
+
for localized messages.
|
|
161
141
|
|
|
162
142
|
| Builder | Accepts | Outputs |
|
|
163
143
|
| ----------------------------- | --------------------------------- | ---------------------------------- |
|
|
@@ -171,11 +151,25 @@ error-map code (`sf.integer`, `sf.token.allowed`, …) for localized messages.
|
|
|
171
151
|
| `sf.value(value)` | An Item, parameters ignored | the bare value's output |
|
|
172
152
|
| `sf.innerList(item, params?)` | An Inner List | `{ items, params }` |
|
|
173
153
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
require; the object schema's `unknownKeys` option decides what happens to the rest.
|
|
154
|
+
An sf-string is `s.string()` and a Boolean `s.boolean()`. Parameters and Dictionaries are
|
|
155
|
+
`s.object(...)`, whose `unknownKeys` option decides what happens to keys the schema omits.
|
|
177
156
|
|
|
178
|
-
|
|
157
|
+
### Types
|
|
158
|
+
|
|
159
|
+
| Type | Meaning |
|
|
160
|
+
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
161
|
+
| `SF.BareItem` | `number \| Decimal \| string \| Token \| Uint8Array \| boolean \| Date \| DisplayString` |
|
|
162
|
+
| `SF.Parameters` | `Record<string, BareItem>` on a null-prototype object, in field order |
|
|
163
|
+
| `SF.Item` | `{ value, params }` |
|
|
164
|
+
| `SF.InnerList` | `{ items: Item[], params }` |
|
|
165
|
+
| `SF.Member`, `SF.List`, `SF.Dictionary` | A List or Dictionary member, the List, and the Dictionary (null prototype) |
|
|
166
|
+
| `SF.FieldType` | `"list" \| "dictionary" \| "item"` |
|
|
167
|
+
| `SF.ValueOf`, `SF.InputOf` | What `parse` returns and what `stringify` accepts, per field type |
|
|
168
|
+
| `SF.ItemInput`, `SF.InnerListInput`, `SF.MemberInput` | The shorthand member forms `stringify` accepts |
|
|
169
|
+
| `SF.SyncSchema<Output>` | A Standard Schema whose `validate` answers synchronously |
|
|
170
|
+
| `StructuredFieldSchemas` | The type of `sf`, from `./schema` |
|
|
171
|
+
|
|
172
|
+
## Pattern: Read Cache-Status Hops
|
|
179
173
|
|
|
180
174
|
A List of cache hops, each a Token or String naming the cache, with typed parameters.
|
|
181
175
|
|
|
@@ -184,49 +178,83 @@ import { getField } from "@sdxc/structured-fields";
|
|
|
184
178
|
import { sf } from "@sdxc/structured-fields/schema";
|
|
185
179
|
import * as s from "remix/data-schema";
|
|
186
180
|
|
|
187
|
-
|
|
181
|
+
let hop = sf.item(
|
|
188
182
|
s.union([sf.token(), s.string()]),
|
|
189
183
|
s.object({
|
|
190
184
|
hit: s.optional(s.boolean()),
|
|
191
|
-
fwd: s.optional(
|
|
192
|
-
sf.token([
|
|
193
|
-
"bypass",
|
|
194
|
-
"method",
|
|
195
|
-
"uri-miss",
|
|
196
|
-
"vary-miss",
|
|
197
|
-
"miss",
|
|
198
|
-
"request",
|
|
199
|
-
"stale",
|
|
200
|
-
"partial",
|
|
201
|
-
]),
|
|
202
|
-
),
|
|
185
|
+
fwd: s.optional(sf.token(["bypass", "method", "uri-miss", "vary-miss", "miss", "stale"])),
|
|
203
186
|
ttl: s.optional(sf.integer()),
|
|
204
|
-
stored: s.optional(s.boolean()),
|
|
205
187
|
}),
|
|
206
188
|
);
|
|
207
189
|
|
|
208
|
-
let hops = getField(response.headers, "Cache-Status", "list", s.array(
|
|
190
|
+
let hops = getField(response.headers, "Cache-Status", "list", s.array(hop));
|
|
209
191
|
```
|
|
210
192
|
|
|
211
193
|
## Pattern: An Idempotency-Key Item
|
|
212
194
|
|
|
213
|
-
The field is an Item
|
|
214
|
-
it.
|
|
195
|
+
The field is an Item holding an sf-string: reading unwraps it, writing quotes and escapes it.
|
|
215
196
|
|
|
216
197
|
```typescript
|
|
217
|
-
import { getField,
|
|
198
|
+
import { getField, setField } from "@sdxc/structured-fields";
|
|
218
199
|
import { sf } from "@sdxc/structured-fields/schema";
|
|
219
200
|
import * as s from "remix/data-schema";
|
|
220
201
|
|
|
221
202
|
let key = getField(request.headers, "Idempotency-Key", "item", sf.value(s.string()));
|
|
222
|
-
|
|
223
|
-
|
|
203
|
+
|
|
204
|
+
let headers = new Headers();
|
|
205
|
+
setField(headers, "Idempotency-Key", "8e03978e-40d5-43e8-bc93-6894a57f9324", "item");
|
|
206
|
+
// Idempotency-Key: "8e03978e-40d5-43e8-bc93-6894a57f9324"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Pattern: Digests As Byte Sequences
|
|
210
|
+
|
|
211
|
+
`Repr-Digest` is a Dictionary of Byte Sequences; a `Uint8Array` writes as `:base64:` and
|
|
212
|
+
`sf.bytes()` reads it back.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { getField, setField } from "@sdxc/structured-fields";
|
|
216
|
+
import { sf } from "@sdxc/structured-fields/schema";
|
|
217
|
+
import * as s from "remix/data-schema";
|
|
218
|
+
|
|
219
|
+
let body = new TextEncoder().encode("hello");
|
|
220
|
+
let digest = new Uint8Array(await crypto.subtle.digest("SHA-256", body));
|
|
221
|
+
setField(response.headers, "Repr-Digest", { "sha-256": digest }, "dictionary");
|
|
222
|
+
|
|
223
|
+
let digests = getField(
|
|
224
|
+
response.headers,
|
|
225
|
+
"Repr-Digest",
|
|
226
|
+
"dictionary",
|
|
227
|
+
s.object({ "sha-256": s.optional(sf.value(sf.bytes())) }),
|
|
228
|
+
);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Pattern: Inner Lists With Parameters
|
|
232
|
+
|
|
233
|
+
A Dictionary member can be a parenthesized list carrying its own parameters, as in a signature's
|
|
234
|
+
covered components.
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import { parse, stringify } from "@sdxc/structured-fields";
|
|
238
|
+
import { sf } from "@sdxc/structured-fields/schema";
|
|
239
|
+
import * as s from "remix/data-schema";
|
|
240
|
+
|
|
241
|
+
let text = stringify(
|
|
242
|
+
{ sig1: { items: ["@method", "@path"], params: { created: 1_700_000_000 } } },
|
|
243
|
+
"dictionary",
|
|
244
|
+
);
|
|
245
|
+
// success: 'sig1=("@method" "@path");created=1700000000'
|
|
246
|
+
|
|
247
|
+
let input = s.object({
|
|
248
|
+
sig1: sf.innerList(sf.value(s.string()), s.object({ created: sf.integer() })),
|
|
249
|
+
});
|
|
250
|
+
if (text.status === "success") parse(text.data, "dictionary", input);
|
|
251
|
+
// success: { sig1: { items: ["@method", "@path"], params: { created: 1700000000 } } }
|
|
224
252
|
```
|
|
225
253
|
|
|
226
254
|
## Pattern: Write Only The Members You Have
|
|
227
255
|
|
|
228
|
-
`setField` deletes the field when the Dictionary
|
|
229
|
-
|
|
256
|
+
`setField` deletes the field when the Dictionary is empty, so build it from the values that exist
|
|
257
|
+
and write it unconditionally.
|
|
230
258
|
|
|
231
259
|
```typescript
|
|
232
260
|
import { setField } from "@sdxc/structured-fields";
|
|
@@ -235,7 +263,31 @@ let quota: Record<string, number> = {};
|
|
|
235
263
|
if (limit !== null) quota.limit = limit;
|
|
236
264
|
if (remaining !== null) quota.remaining = remaining;
|
|
237
265
|
|
|
238
|
-
|
|
266
|
+
setField(headers, "RateLimit", quota, "dictionary");
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Pattern: Branch On Why A Field Failed
|
|
270
|
+
|
|
271
|
+
An unparseable field is ignored as if absent; a field that parses but breaks the schema is the
|
|
272
|
+
client's mistake to report.
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { isFailure } from "@sdxc/result";
|
|
276
|
+
import { StructuredFieldParseError, getField } from "@sdxc/structured-fields";
|
|
277
|
+
import { sf } from "@sdxc/structured-fields/schema";
|
|
278
|
+
import * as s from "remix/data-schema";
|
|
279
|
+
|
|
280
|
+
let schema = s.object({ u: s.optional(sf.value(sf.integer())) });
|
|
281
|
+
let result = getField(request.headers, "Priority", "dictionary", schema);
|
|
282
|
+
|
|
283
|
+
let priority = { u: 3 };
|
|
284
|
+
if (isFailure(result)) {
|
|
285
|
+
if (!(result.error instanceof StructuredFieldParseError)) {
|
|
286
|
+
return Response.json({ issues: result.error.issues }, { status: 400 });
|
|
287
|
+
}
|
|
288
|
+
} else if (result.data?.u !== undefined) {
|
|
289
|
+
priority.u = result.data.u;
|
|
290
|
+
}
|
|
239
291
|
```
|
|
240
292
|
|
|
241
293
|
## Versioning
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdxc/structured-fields",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.16",
|
|
4
4
|
"description": "Parse and serialize RFC 9651 structured HTTP field values",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
"./schema": "./dist/schema.js"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@sdxc/result": "2026.10.
|
|
13
|
-
"@standard-schema/spec": "^1.1.0"
|
|
12
|
+
"@sdxc/result": "2026.10.13",
|
|
13
|
+
"@standard-schema/spec": "^1.1.0"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
14
16
|
"remix": "3.0.0"
|
|
15
17
|
},
|
|
16
|
-
"gitHead": "
|
|
18
|
+
"gitHead": "1e547d8431568f5ed9bdb079ca3046c936240fde",
|
|
17
19
|
"publishConfig": {
|
|
18
20
|
"access": "public"
|
|
19
21
|
},
|
|
@@ -22,7 +24,7 @@
|
|
|
22
24
|
"url": "git+https://github.com/sergiodxa/monorepo.git",
|
|
23
25
|
"directory": "packages/structured-fields"
|
|
24
26
|
},
|
|
25
|
-
"homepage": "https://sdxc.
|
|
27
|
+
"homepage": "https://sdxc.dev/api/structured-fields",
|
|
26
28
|
"author": {
|
|
27
29
|
"name": "Sergio Xalambrí",
|
|
28
30
|
"url": "https://sergiodxa.com"
|