@oomfware/forms 0.1.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/LICENSE +14 -0
- package/README.md +248 -0
- package/dist/index.d.mts +181 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +488 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +52 -0
- package/src/index.ts +4 -0
- package/src/lib/errors.ts +47 -0
- package/src/lib/form-utils.ts +431 -0
- package/src/lib/form.ts +469 -0
- package/src/lib/middleware.ts +99 -0
- package/src/lib/types.ts +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
BSD Zero Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mary
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
9
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
10
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
11
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
12
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
13
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
14
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# @oomfware/forms
|
|
2
|
+
|
|
3
|
+
form validation middleware.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @oomfware/forms
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## usage
|
|
10
|
+
|
|
11
|
+
### basic form
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { asyncContext, createRouter, route } from '@oomfware/fetch-router';
|
|
15
|
+
import { form, forms } from '@oomfware/forms';
|
|
16
|
+
import { render } from '@oomfware/jsx';
|
|
17
|
+
import * as v from 'valibot';
|
|
18
|
+
|
|
19
|
+
const routes = route({
|
|
20
|
+
login: '/login',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const router = createRouter({
|
|
24
|
+
middleware: [asyncContext()],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const loginForm = form(
|
|
28
|
+
v.object({
|
|
29
|
+
email: v.pipe(v.string(), v.email()),
|
|
30
|
+
_password: v.pipe(v.string(), v.minLength(8)),
|
|
31
|
+
}),
|
|
32
|
+
async (data, issue) => {
|
|
33
|
+
const user = await findUserByEmail(data.email);
|
|
34
|
+
|
|
35
|
+
if (!user || !(await verifyPassword(user, data._password))) {
|
|
36
|
+
invalid(issue.email('Invalid email or password'));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { userId: user.id };
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
router.map(routes, {
|
|
44
|
+
middleware: [forms({ loginForm })],
|
|
45
|
+
actions: {
|
|
46
|
+
login() {
|
|
47
|
+
return render(
|
|
48
|
+
<form {...loginForm}>
|
|
49
|
+
{loginForm.fields.email.issues()?.[0] && (
|
|
50
|
+
<p class="error">{loginForm.fields.email.issues()![0].message}</p>
|
|
51
|
+
)}
|
|
52
|
+
|
|
53
|
+
<label>
|
|
54
|
+
Email
|
|
55
|
+
<input {...loginForm.fields.email.as('email')} />
|
|
56
|
+
</label>
|
|
57
|
+
|
|
58
|
+
<label>
|
|
59
|
+
Password
|
|
60
|
+
<input {...loginForm.fields._password.as('password')} />
|
|
61
|
+
</label>
|
|
62
|
+
|
|
63
|
+
<button>Sign in</button>
|
|
64
|
+
</form>,
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### imperative validation
|
|
72
|
+
|
|
73
|
+
use `invalid()` to add validation errors after schema validation passes:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { form, invalid } from '@oomfware/forms';
|
|
77
|
+
import * as v from 'valibot';
|
|
78
|
+
|
|
79
|
+
export const registerForm = form(
|
|
80
|
+
v.object({
|
|
81
|
+
username: v.pipe(v.string(), v.minLength(3)),
|
|
82
|
+
email: v.pipe(v.string(), v.email()),
|
|
83
|
+
_password: v.pipe(v.string(), v.minLength(8)),
|
|
84
|
+
}),
|
|
85
|
+
async (data, issue) => {
|
|
86
|
+
const issues = [];
|
|
87
|
+
|
|
88
|
+
if (await isUsernameTaken(data.username)) {
|
|
89
|
+
issues.push(issue.username('Username is already taken'));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (await isEmailRegistered(data.email)) {
|
|
93
|
+
issues.push(issue.email('Email is already registered'));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (issues.length > 0) {
|
|
97
|
+
invalid(...issues);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return await createUser(data);
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### nested and array fields
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
const profileForm = form(
|
|
109
|
+
v.object({
|
|
110
|
+
user: v.object({
|
|
111
|
+
name: v.string(),
|
|
112
|
+
bio: v.optional(v.string()),
|
|
113
|
+
}),
|
|
114
|
+
socialLinks: v.array(
|
|
115
|
+
v.object({
|
|
116
|
+
platform: v.string(),
|
|
117
|
+
url: v.pipe(v.string(), v.url()),
|
|
118
|
+
}),
|
|
119
|
+
),
|
|
120
|
+
}),
|
|
121
|
+
async (data) => {
|
|
122
|
+
return await updateProfile(data);
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// in template:
|
|
127
|
+
<input {...profileForm.fields.user.name.as('text')} />
|
|
128
|
+
<textarea {...profileForm.fields.user.bio.as('text')} />
|
|
129
|
+
|
|
130
|
+
{socialLinks.map((_, i) => (
|
|
131
|
+
<>
|
|
132
|
+
<input {...profileForm.fields.socialLinks[i].platform.as('text')} />
|
|
133
|
+
<input {...profileForm.fields.socialLinks[i].url.as('url')} />
|
|
134
|
+
</>
|
|
135
|
+
))}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### input types
|
|
139
|
+
|
|
140
|
+
the `.as()` method generates appropriate props for different input types:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
// text inputs
|
|
144
|
+
<input {...form.fields.name.as('text')} />
|
|
145
|
+
<input {...form.fields.email.as('email')} />
|
|
146
|
+
<input {...form.fields.password.as('password')} />
|
|
147
|
+
|
|
148
|
+
// numeric inputs (auto-parsed via n: prefix)
|
|
149
|
+
<input {...form.fields.age.as('number')} />
|
|
150
|
+
<input {...form.fields.rating.as('range')} />
|
|
151
|
+
|
|
152
|
+
// boolean checkbox (auto-parsed via b: prefix)
|
|
153
|
+
<input {...form.fields.subscribe.as('checkbox')} />
|
|
154
|
+
|
|
155
|
+
// checkbox group (multiple values)
|
|
156
|
+
<input {...form.fields.tags.as('checkbox', 'javascript')} />
|
|
157
|
+
<input {...form.fields.tags.as('checkbox', 'typescript')} />
|
|
158
|
+
|
|
159
|
+
// radio buttons
|
|
160
|
+
<input {...form.fields.color.as('radio', 'red')} />
|
|
161
|
+
<input {...form.fields.color.as('radio', 'blue')} />
|
|
162
|
+
|
|
163
|
+
// select
|
|
164
|
+
<select {...form.fields.country.as('select')}>
|
|
165
|
+
<option value="us">United States</option>
|
|
166
|
+
</select>
|
|
167
|
+
|
|
168
|
+
// hidden fields (for IDs, tokens, etc.)
|
|
169
|
+
<input {...form.fields.postId.as('hidden', postId)} />
|
|
170
|
+
|
|
171
|
+
// file uploads
|
|
172
|
+
<input {...form.fields.avatar.as('file')} />
|
|
173
|
+
<input {...form.fields.attachments.as('file multiple')} />
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### accessing form result
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
router.map(routes, {
|
|
180
|
+
middleware: [forms({ messageForm })],
|
|
181
|
+
messages() {
|
|
182
|
+
if (messageForm.result) {
|
|
183
|
+
return render(<p>Message sent! ID: {messageForm.result.messageId}</p>);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return render(<form {...messageForm}>{/* form fields */}</form>);
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### multiple submit actions
|
|
192
|
+
|
|
193
|
+
use `buttonProps` when a form has multiple submit buttons that trigger different actions:
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
import { form, forms } from '@oomfware/forms';
|
|
197
|
+
import { render } from '@oomfware/jsx';
|
|
198
|
+
import * as v from 'valibot';
|
|
199
|
+
|
|
200
|
+
const draftSchema = v.object({
|
|
201
|
+
title: v.string(),
|
|
202
|
+
content: v.string(),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const saveDraft = form(draftSchema, async (data) => {
|
|
206
|
+
return await saveDraftToDb(data);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const publishPost = form(draftSchema, async (data) => {
|
|
210
|
+
return await publishToDb(data);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
router.map(routes, {
|
|
214
|
+
middleware: [forms({ saveDraft, publishPost })],
|
|
215
|
+
actions: {
|
|
216
|
+
editor() {
|
|
217
|
+
return render(
|
|
218
|
+
<form {...saveDraft}>
|
|
219
|
+
<input {...saveDraft.fields.title.as('text')} placeholder="Title" />
|
|
220
|
+
<textarea {...saveDraft.fields.content.as('text')} />
|
|
221
|
+
|
|
222
|
+
<button>Save draft</button>
|
|
223
|
+
<button {...publishPost.buttonProps}>Publish</button>
|
|
224
|
+
</form>,
|
|
225
|
+
);
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### sensitive field redaction
|
|
232
|
+
|
|
233
|
+
fields prefixed with `_` are automatically redacted from form state on validation failure:
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
const loginForm = form(
|
|
237
|
+
v.object({
|
|
238
|
+
email: v.pipe(v.string(), v.email()),
|
|
239
|
+
_password: v.pipe(v.string(), v.minLength(8)), // redacted on error
|
|
240
|
+
}),
|
|
241
|
+
async (data) => {
|
|
242
|
+
// data._password is available here
|
|
243
|
+
},
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// if validation fails, loginForm.fields._password.value() returns undefined
|
|
247
|
+
// this prevents passwords from being echoed back in the form
|
|
248
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { RouterMiddleware } from "@oomfware/fetch-router";
|
|
2
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/types.d.ts
|
|
5
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/lib/form.d.ts
|
|
8
|
+
interface FormInput {
|
|
9
|
+
[key: string]: MaybeArray<string | number | boolean | File | FormInput>;
|
|
10
|
+
}
|
|
11
|
+
type MaybeArray<T> = T | T[];
|
|
12
|
+
interface FormIssue {
|
|
13
|
+
message: string;
|
|
14
|
+
path: (string | number)[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* the issue creator proxy passed to form callbacks.
|
|
18
|
+
* allows creating field-specific validation issues via property access.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* form(schema, async (data, issue) => {
|
|
23
|
+
* if (emailTaken(data.email)) {
|
|
24
|
+
* invalid(issue.email('Email already in use'));
|
|
25
|
+
* }
|
|
26
|
+
* // nested fields: issue.user.profile.name('Invalid name')
|
|
27
|
+
* // array fields: issue.items[0].name('Invalid item name')
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
type InvalidField<T> = ((message: string) => StandardSchemaV1.Issue) & { [K in keyof T]-?: T[K] extends (infer U)[] ? InvalidFieldArray<U> : T[K] extends object ? InvalidField<T[K]> : (message: string) => StandardSchemaV1.Issue };
|
|
32
|
+
type InvalidFieldArray<T> = {
|
|
33
|
+
[index: number]: T extends object ? InvalidField<T> : (message: string) => StandardSchemaV1.Issue;
|
|
34
|
+
} & ((message: string) => StandardSchemaV1.Issue);
|
|
35
|
+
/**
|
|
36
|
+
* internal info attached to a form instance.
|
|
37
|
+
* used by the forms() middleware to identify and process forms.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* the return value of a form() function.
|
|
42
|
+
* can be spread onto a <form> element.
|
|
43
|
+
*/
|
|
44
|
+
interface Form<Input extends FormInput | void, Output> {
|
|
45
|
+
/** HTTP method */
|
|
46
|
+
readonly method: 'POST';
|
|
47
|
+
/** the form action URL */
|
|
48
|
+
readonly action: string;
|
|
49
|
+
/** the handler result, if submission was successful */
|
|
50
|
+
readonly result: Output | undefined;
|
|
51
|
+
/** access form fields using object notation */
|
|
52
|
+
readonly fields: FormFields<Input>;
|
|
53
|
+
/** spread this onto a <button> or <input type="submit"> */
|
|
54
|
+
readonly buttonProps: FormButtonProps;
|
|
55
|
+
}
|
|
56
|
+
interface FormButtonProps {
|
|
57
|
+
type: 'submit';
|
|
58
|
+
readonly formaction: string;
|
|
59
|
+
}
|
|
60
|
+
/** valid leaf value types for form fields */
|
|
61
|
+
type FormFieldValue = string | string[] | number | boolean | File | File[];
|
|
62
|
+
/** guard to prevent infinite recursion when T is unknown or has an index signature */
|
|
63
|
+
type WillRecurseIndefinitely<T> = unknown extends T ? true : string extends keyof T ? true : false;
|
|
64
|
+
/** base methods available on all form fields */
|
|
65
|
+
interface FormFieldMethods<T> {
|
|
66
|
+
/** get the current value */
|
|
67
|
+
value(): T | undefined;
|
|
68
|
+
/** set the value */
|
|
69
|
+
set(value: T): T;
|
|
70
|
+
/** get validation issues for this field */
|
|
71
|
+
issues(): FormIssue[] | undefined;
|
|
72
|
+
}
|
|
73
|
+
/** leaf field (primitives, files) with .as() method */
|
|
74
|
+
type FormFieldLeaf<T extends FormFieldValue> = FormFieldMethods<T> & {
|
|
75
|
+
/** get props for an input element */
|
|
76
|
+
as(type: string, value?: string): Record<string, unknown>;
|
|
77
|
+
};
|
|
78
|
+
/** container field (objects, arrays) with allIssues() method */
|
|
79
|
+
type FormFieldContainer<T> = FormFieldMethods<T> & {
|
|
80
|
+
/** get all issues for this field and descendants */
|
|
81
|
+
allIssues(): FormIssue[] | undefined;
|
|
82
|
+
};
|
|
83
|
+
/** fallback field type when recursion would be infinite */
|
|
84
|
+
type FormFieldUnknown<T> = FormFieldMethods<T> & {
|
|
85
|
+
/** get all issues for this field and descendants */
|
|
86
|
+
allIssues(): FormIssue[] | undefined;
|
|
87
|
+
/** get props for an input element */
|
|
88
|
+
as(type: string, value?: string): Record<string, unknown>;
|
|
89
|
+
} & {
|
|
90
|
+
[key: string | number]: FormFieldUnknown<unknown>;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* recursive type to build form fields structure with proxy access.
|
|
94
|
+
* preserves type information through the object hierarchy.
|
|
95
|
+
*/
|
|
96
|
+
type FormFields<T> = T extends void ? Record<string, never> : WillRecurseIndefinitely<T> extends true ? FormFieldUnknown<T> : NonNullable<T> extends string | number | boolean | File ? FormFieldLeaf<NonNullable<T>> : T extends string[] | File[] ? FormFieldLeaf<T> & { [K in number]: FormFieldLeaf<T[number]> } : T extends Array<infer U> ? FormFieldContainer<T> & { [K in number]: FormFields<U> } : FormFieldContainer<T> & { [K in keyof T]-?: FormFields<T[K]> };
|
|
97
|
+
/**
|
|
98
|
+
* creates a form without validation.
|
|
99
|
+
*/
|
|
100
|
+
declare function form<Output>(fn: () => MaybePromise<Output>): Form<void, Output>;
|
|
101
|
+
/**
|
|
102
|
+
* creates a form with unchecked input (no validation).
|
|
103
|
+
*/
|
|
104
|
+
declare function form<Input extends FormInput, Output>(validate: 'unchecked', fn: (data: Input, issue: InvalidField<Input>) => MaybePromise<Output>): Form<Input, Output>;
|
|
105
|
+
/**
|
|
106
|
+
* creates a form with Standard Schema validation.
|
|
107
|
+
*/
|
|
108
|
+
declare function form<Schema extends StandardSchemaV1<FormInput, Record<string, unknown>>, Output>(validate: Schema, fn: (data: StandardSchemaV1.InferOutput<Schema>, issue: InvalidField<StandardSchemaV1.InferInput<Schema>>) => MaybePromise<Output>): Form<StandardSchemaV1.InferInput<Schema>, Output>;
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/lib/middleware.d.ts
|
|
111
|
+
/**
|
|
112
|
+
* a record of form instances to register with the middleware.
|
|
113
|
+
*/
|
|
114
|
+
type FormDefinitions = Record<string, Form<any, any>>;
|
|
115
|
+
/**
|
|
116
|
+
* creates a forms middleware that registers forms and handles form submissions.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* import { form, forms } from '@oomfware/forms';
|
|
121
|
+
* import * as v from 'valibot';
|
|
122
|
+
*
|
|
123
|
+
* const createUserForm = form(
|
|
124
|
+
* v.object({ name: v.string(), password: v.string() }),
|
|
125
|
+
* async (input, issue) => {
|
|
126
|
+
* // handle form submission
|
|
127
|
+
* },
|
|
128
|
+
* );
|
|
129
|
+
*
|
|
130
|
+
* router.map(routes.admin, {
|
|
131
|
+
* middleware: [forms({ createUserForm })],
|
|
132
|
+
* action() {
|
|
133
|
+
* return render(
|
|
134
|
+
* <form {...createUserForm}>
|
|
135
|
+
* <input {...createUserForm.fields.name.as('text')} required />
|
|
136
|
+
* </form>
|
|
137
|
+
* );
|
|
138
|
+
* },
|
|
139
|
+
* });
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
declare function forms(definitions: FormDefinitions): RouterMiddleware;
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/lib/errors.d.ts
|
|
145
|
+
/**
|
|
146
|
+
* error thrown when form validation fails imperatively
|
|
147
|
+
*/
|
|
148
|
+
declare class ValidationError extends Error {
|
|
149
|
+
issues: StandardSchemaV1.Issue[];
|
|
150
|
+
constructor(issues: StandardSchemaV1.Issue[]);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* use this to throw a validation error to imperatively fail form validation.
|
|
154
|
+
* can be used in combination with `issue` passed to form actions to create field-specific issues.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* import { invalid, form } from '@oomfware/forms';
|
|
159
|
+
* import * as v from 'valibot';
|
|
160
|
+
*
|
|
161
|
+
* export const login = form(
|
|
162
|
+
* v.object({ name: v.string(), _password: v.string() }),
|
|
163
|
+
* async ({ name, _password }, issue) => {
|
|
164
|
+
* const success = tryLogin(name, _password);
|
|
165
|
+
* if (!success) {
|
|
166
|
+
* invalid('Incorrect username or password');
|
|
167
|
+
* }
|
|
168
|
+
*
|
|
169
|
+
* // ...
|
|
170
|
+
* }
|
|
171
|
+
* );
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
declare function invalid(...issues: (StandardSchemaV1.Issue | string)[]): never;
|
|
175
|
+
/**
|
|
176
|
+
* checks whether this is a validation error thrown by {@link invalid}.
|
|
177
|
+
*/
|
|
178
|
+
declare function isValidationError(e: unknown): e is ValidationError;
|
|
179
|
+
//#endregion
|
|
180
|
+
export { type Form, type FormInput, type FormIssue, ValidationError, form, forms, invalid, isValidationError };
|
|
181
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/types.ts","../src/lib/form.ts","../src/lib/middleware.ts","../src/lib/errors.ts"],"sourcesContent":[],"mappings":"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"}
|