@jayalfredprufrock/mobx-toolbox 0.5.4 → 0.6.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/form.d.mts +60 -8
- package/dist/form.d.mts.map +1 -1
- package/dist/form.mjs +50 -9
- package/dist/form.mjs.map +1 -1
- package/dist/model.d.mts +40 -12
- package/dist/model.d.mts.map +1 -1
- package/dist/model.mjs +43 -6
- package/dist/model.mjs.map +1 -1
- package/package.json +1 -1
package/dist/form.d.mts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as _$react from "react";
|
|
2
|
-
import * as _$
|
|
3
|
-
import { Static, TObject, TSchema } from "typebox";
|
|
2
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
3
|
import { Validator } from "typebox/schema";
|
|
4
|
+
import * as _$typebox from "typebox";
|
|
5
|
+
import { Static, TLiteral, TObject, TSchema, TUnion } from "typebox";
|
|
5
6
|
|
|
6
7
|
//#region src/form/form-field.model.d.ts
|
|
7
8
|
declare class FormFieldModel<T extends T.TSchema = T.TSchema> {
|
|
@@ -22,22 +23,51 @@ declare class FormFieldModel<T extends T.TSchema = T.TSchema> {
|
|
|
22
23
|
}
|
|
23
24
|
//#endregion
|
|
24
25
|
//#region src/form/form.types.d.ts
|
|
25
|
-
|
|
26
|
+
type FormSchema = TObject | TUnion;
|
|
27
|
+
interface FormConfig<T extends FormSchema = TObject> {
|
|
26
28
|
handleSubmit: (data: Static<T>) => Promise<unknown>;
|
|
27
|
-
initialValues?:
|
|
29
|
+
initialValues?: FormInitialValues<T>;
|
|
28
30
|
}
|
|
29
31
|
interface FormFieldConfig<T extends TSchema = TSchema> {
|
|
30
32
|
name: string;
|
|
31
33
|
schema: T;
|
|
32
34
|
initialValue?: Static<T>;
|
|
33
35
|
}
|
|
34
|
-
type
|
|
36
|
+
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
37
|
+
/** Keys present on the variant `O` (a single union member). */
|
|
38
|
+
type VariantKeys<O> = O extends TObject ? keyof O["properties"] : never;
|
|
39
|
+
/** Keys present on *every* variant — the shared base + discriminator. */
|
|
40
|
+
type SharedKeys<O> = UnionToIntersection<O extends TObject ? {
|
|
41
|
+
k: keyof O["properties"];
|
|
42
|
+
} : never> extends {
|
|
43
|
+
k: infer K extends PropertyKey;
|
|
44
|
+
} ? K : never;
|
|
45
|
+
/** The schema of key `K` across the variants that declare it (unioned when they differ). */
|
|
46
|
+
type VariantSchema<O, K extends PropertyKey> = O extends TObject ? K extends keyof O["properties"] ? O["properties"][K] : never : never;
|
|
47
|
+
/** The static value type of key `K` across the variants that declare it. */
|
|
48
|
+
type VariantValue<O, K extends PropertyKey> = O extends TObject ? K extends keyof O["properties"] ? Static<O["properties"][K]> : never : never;
|
|
49
|
+
type FormFields<T extends FormSchema = TObject> = T extends TUnion<infer V> ? { [K in SharedKeys<V[number]>]: FormFieldModel<VariantSchema<V[number], K>> } : T extends TObject ? { [K in keyof T["properties"]]: FormFieldModel<T["properties"][K]> } : never;
|
|
50
|
+
type RawFormFields<T extends FormSchema = TObject> = T extends TUnion<infer V> ? { [K in VariantKeys<V[number]>]: FormFieldModel<VariantSchema<V[number], K>> } : T extends TObject ? { [K in keyof T["properties"]]: FormFieldModel<T["properties"][K]> } : never;
|
|
51
|
+
type FormInitialValues<T extends FormSchema> = T extends TUnion<infer V> ? { [K in VariantKeys<V[number]>]?: VariantValue<V[number], K> } : T extends TObject ? Partial<Static<T>> : never;
|
|
52
|
+
/** Keys that are typed as a literal in at least one variant — valid discriminators. */
|
|
53
|
+
type DiscriminatorKeys<T extends TUnion> = T extends TUnion<infer V> ? LiteralKeys<V[number]> : never;
|
|
54
|
+
type LiteralKeys<O> = O extends TObject ? { [K in keyof O["properties"]]: O["properties"][K] extends TLiteral ? K : never }[keyof O["properties"]] : never;
|
|
55
|
+
/** The literal values a given discriminator key can take across all variants. */
|
|
56
|
+
type DiscriminatorValue<T extends TUnion, D extends PropertyKey> = T extends TUnion<infer V> ? LiteralValue<V[number], D> : never;
|
|
57
|
+
type LiteralValue<O, D extends PropertyKey> = O extends TObject ? D extends keyof O["properties"] ? O["properties"][D] extends TLiteral<infer L> ? L : never : never : never;
|
|
58
|
+
/** The variant object whose discriminator `D` equals the literal `V`. */
|
|
59
|
+
type MatchVariant<T extends TUnion, D extends PropertyKey, V> = T extends TUnion<infer Vars> ? Distribute<Vars[number], D, V> : never;
|
|
60
|
+
type Distribute<O, D extends PropertyKey, V> = O extends TObject ? D extends keyof O["properties"] ? O["properties"][D] extends TLiteral<infer L> ? V extends L ? O : never : never : never : never;
|
|
35
61
|
//#endregion
|
|
36
62
|
//#region src/form/form.model.d.ts
|
|
37
|
-
declare class FormModel<T extends
|
|
63
|
+
declare class FormModel<T extends FormSchema = TObject> {
|
|
64
|
+
/** Shared fields only (for unions); every field for a plain object schema. */
|
|
38
65
|
readonly fields: FormFields<T>;
|
|
66
|
+
/** Every field across all variants — escape hatch for reaching variant fields. */
|
|
67
|
+
readonly rawFields: RawFormFields<T>;
|
|
39
68
|
readonly config: FormConfig<T>;
|
|
40
69
|
readonly schema: T;
|
|
70
|
+
readonly validator: Validator<T>;
|
|
41
71
|
submitting: boolean;
|
|
42
72
|
submitted: boolean;
|
|
43
73
|
submitError: any;
|
|
@@ -58,6 +88,28 @@ interface MobxFormProps extends Omit<React.HTMLProps<HTMLFormElement>, "form" |
|
|
|
58
88
|
}
|
|
59
89
|
declare const MobxForm: _$react.ForwardRefExoticComponent<Omit<MobxFormProps, "ref"> & _$react.RefAttributes<unknown>>;
|
|
60
90
|
//#endregion
|
|
91
|
+
//#region src/form/components/form-when.d.ts
|
|
92
|
+
interface FormWhenProps<T extends TUnion, D extends DiscriminatorKeys<T>, V extends string> {
|
|
93
|
+
form: FormModel<T>;
|
|
94
|
+
/** The discriminator field name. */
|
|
95
|
+
field: D;
|
|
96
|
+
/** Render the children only while the discriminator field equals this value. */
|
|
97
|
+
value: V & DiscriminatorValue<T, D>;
|
|
98
|
+
/** Receives the matching variant's fields. The form itself is already in scope. */
|
|
99
|
+
children: (fields: FormFields<MatchVariant<T, D, V>>) => React.ReactNode;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Renders its children only when the form's discriminator field currently holds
|
|
103
|
+
* `value`, passing the fields narrowed to that variant. Stack one per variant to
|
|
104
|
+
* lay out a discriminated-union form without manual conditionals or casts.
|
|
105
|
+
*/
|
|
106
|
+
declare function FormWhen<T extends TUnion, D extends DiscriminatorKeys<T>, V extends string>({
|
|
107
|
+
form,
|
|
108
|
+
field,
|
|
109
|
+
value,
|
|
110
|
+
children
|
|
111
|
+
}: FormWhenProps<T, D, V>): _$react_jsx_runtime0.JSX.Element;
|
|
112
|
+
//#endregion
|
|
61
113
|
//#region src/form/form.context.d.ts
|
|
62
114
|
declare const formContext: _$react.Context<FormModel<_$typebox.TObject<_$typebox.TProperties>> | undefined>;
|
|
63
115
|
declare const useFormContext: () => FormModel<_$typebox.TObject<_$typebox.TProperties>>;
|
|
@@ -65,7 +117,7 @@ declare const FormProvider: _$react.Provider<FormModel<_$typebox.TObject<_$typeb
|
|
|
65
117
|
declare const useFormContextIfAvailable: () => FormModel<_$typebox.TObject<_$typebox.TProperties>> | undefined;
|
|
66
118
|
//#endregion
|
|
67
119
|
//#region src/form/use-form.d.ts
|
|
68
|
-
declare const useForm: <T extends
|
|
120
|
+
declare const useForm: <T extends FormSchema = TObject>(schema: T, config: FormConfig<T>) => FormModel<T>;
|
|
69
121
|
//#endregion
|
|
70
|
-
export { FormConfig, FormFieldConfig, FormFieldModel, FormFields, FormModel, FormProvider, MobxForm, MobxFormProps, formContext, useForm, useFormContext, useFormContextIfAvailable };
|
|
122
|
+
export { DiscriminatorKeys, DiscriminatorValue, FormConfig, FormFieldConfig, FormFieldModel, FormFields, FormModel, FormProvider, FormSchema, FormWhen, FormWhenProps, MatchVariant, MobxForm, MobxFormProps, RawFormFields, formContext, useForm, useFormContext, useFormContextIfAvailable };
|
|
71
123
|
//# sourceMappingURL=form.d.mts.map
|
package/dist/form.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.d.mts","names":[],"sources":["../src/form/form-field.model.ts","../src/form/form.types.ts","../src/form/form.model.ts","../src/form/components/form.tsx","../src/form/form.context.ts","../src/form/use-form.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"form.d.mts","names":[],"sources":["../src/form/form-field.model.ts","../src/form/form.types.ts","../src/form/form.model.ts","../src/form/components/form.tsx","../src/form/components/form-when.tsx","../src/form/form.context.ts","../src/form/use-form.ts"],"mappings":"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"}
|
package/dist/form.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { Observer } from "mobx-react-lite";
|
|
1
2
|
import { createContext, forwardRef, useContext, useRef } from "react";
|
|
2
3
|
import { makeAutoObservable, toJS } from "mobx";
|
|
3
|
-
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
4
5
|
import * as Format from "typebox/format";
|
|
5
|
-
import * as T from "typebox";
|
|
6
|
-
import Schema, { Validator } from "typebox/schema";
|
|
7
6
|
import * as Value from "typebox/value";
|
|
7
|
+
import Schema, { Validator } from "typebox/schema";
|
|
8
|
+
import * as T from "typebox";
|
|
9
|
+
import { IsUnion, Union } from "typebox";
|
|
8
10
|
|
|
9
11
|
//#region src/form/form.context.ts
|
|
10
12
|
const formContext = createContext(void 0);
|
|
@@ -31,6 +33,21 @@ const MobxForm = forwardRef(function MobxForm({ form, children, ...formProps },
|
|
|
31
33
|
});
|
|
32
34
|
});
|
|
33
35
|
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/form/components/form-when.tsx
|
|
38
|
+
/**
|
|
39
|
+
* Renders its children only when the form's discriminator field currently holds
|
|
40
|
+
* `value`, passing the fields narrowed to that variant. Stack one per variant to
|
|
41
|
+
* lay out a discriminated-union form without manual conditionals or casts.
|
|
42
|
+
*/
|
|
43
|
+
function FormWhen({ form, field, value, children }) {
|
|
44
|
+
return /* @__PURE__ */ jsx(Observer, { children: () => {
|
|
45
|
+
const fields = form.rawFields;
|
|
46
|
+
if (fields[field]?.value !== value) return null;
|
|
47
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(fields) });
|
|
48
|
+
} });
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
//#endregion
|
|
35
52
|
//#region src/form/form-field.model.ts
|
|
36
53
|
var FormFieldModel = class {
|
|
@@ -99,31 +116,53 @@ var FormFieldModel = class {
|
|
|
99
116
|
//#region src/form/form.model.ts
|
|
100
117
|
Format.Set("password", () => true);
|
|
101
118
|
Format.Set("phone", () => true);
|
|
119
|
+
function resolveProperties(schema) {
|
|
120
|
+
if (!IsUnion(schema)) return schema.properties;
|
|
121
|
+
const groups = {};
|
|
122
|
+
for (const variant of schema.anyOf) for (const [key, propSchema] of Object.entries(variant.properties)) (groups[key] ??= []).push(propSchema);
|
|
123
|
+
const merged = {};
|
|
124
|
+
for (const [key, schemas] of Object.entries(groups)) {
|
|
125
|
+
const distinct = [...new Map(schemas.map((s) => [JSON.stringify(s), s])).values()];
|
|
126
|
+
merged[key] = distinct.length === 1 ? distinct[0] : Union(distinct);
|
|
127
|
+
}
|
|
128
|
+
return merged;
|
|
129
|
+
}
|
|
102
130
|
var FormModel = class {
|
|
131
|
+
/** Shared fields only (for unions); every field for a plain object schema. */
|
|
103
132
|
fields;
|
|
133
|
+
/** Every field across all variants — escape hatch for reaching variant fields. */
|
|
134
|
+
rawFields;
|
|
104
135
|
config;
|
|
105
136
|
schema;
|
|
137
|
+
validator;
|
|
106
138
|
submitting = false;
|
|
107
139
|
submitted = false;
|
|
108
140
|
submitError;
|
|
109
141
|
constructor(schema, config) {
|
|
110
142
|
this.schema = schema;
|
|
111
143
|
this.config = config;
|
|
144
|
+
this.validator = Schema.Compile(schema);
|
|
112
145
|
makeAutoObservable(this, {
|
|
113
146
|
fields: false,
|
|
147
|
+
rawFields: false,
|
|
114
148
|
schema: false,
|
|
115
|
-
config: false
|
|
149
|
+
config: false,
|
|
150
|
+
validator: false
|
|
116
151
|
});
|
|
117
|
-
|
|
152
|
+
const initialValues = config?.initialValues;
|
|
153
|
+
const fields = Object.entries(resolveProperties(schema)).reduce((fields, [fieldName, fieldSchema]) => {
|
|
118
154
|
fields[fieldName] = new FormFieldModel({
|
|
119
155
|
name: fieldName,
|
|
120
156
|
schema: fieldSchema,
|
|
121
|
-
initialValue:
|
|
157
|
+
initialValue: initialValues?.[fieldName]
|
|
122
158
|
});
|
|
123
159
|
return fields;
|
|
124
160
|
}, {});
|
|
161
|
+
this.rawFields = fields;
|
|
162
|
+
this.fields = fields;
|
|
125
163
|
}
|
|
126
164
|
get valid() {
|
|
165
|
+
if (IsUnion(this.schema)) return this.validator.Check(this.toJSON());
|
|
127
166
|
return Object.values(this.fields).every((field) => field.valid);
|
|
128
167
|
}
|
|
129
168
|
props() {
|
|
@@ -160,10 +199,12 @@ var FormModel = class {
|
|
|
160
199
|
return this.valid;
|
|
161
200
|
}
|
|
162
201
|
toJSON() {
|
|
163
|
-
|
|
164
|
-
fields[field.name] = field.
|
|
202
|
+
const data = Object.values(this.fields).reduce((fields, field) => {
|
|
203
|
+
fields[field.name] = field.toJSON();
|
|
165
204
|
return fields;
|
|
166
205
|
}, {});
|
|
206
|
+
if (IsUnion(this.schema)) return Value.Clean(this.schema, data);
|
|
207
|
+
return data;
|
|
167
208
|
}
|
|
168
209
|
};
|
|
169
210
|
|
|
@@ -177,5 +218,5 @@ const useForm = (schema, config) => {
|
|
|
177
218
|
};
|
|
178
219
|
|
|
179
220
|
//#endregion
|
|
180
|
-
export { FormFieldModel, FormModel, FormProvider, MobxForm, formContext, useForm, useFormContext, useFormContextIfAvailable };
|
|
221
|
+
export { FormFieldModel, FormModel, FormProvider, FormWhen, MobxForm, formContext, useForm, useFormContext, useFormContextIfAvailable };
|
|
181
222
|
//# sourceMappingURL=form.mjs.map
|
package/dist/form.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.mjs","names":[],"sources":["../src/form/form.context.ts","../src/form/components/form.tsx","../src/form/form-field.model.ts","../src/form/form.model.ts","../src/form/use-form.ts"],"sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { FormModel } from \"./form.model\";\n\nexport const formContext = createContext<FormModel | undefined>(undefined);\nexport const useFormContext = () => {\n const context = useContext(formContext);\n if (!context) {\n throw new Error(\n \"Form context not available. Make sure you are within the <Form> component or providing the form context manually.\",\n );\n }\n return context;\n};\n\nexport const FormProvider = formContext.Provider;\n\nexport const useFormContextIfAvailable = () => useContext(formContext);\n","import { forwardRef } from \"react\";\nimport { FormProvider } from \"../form.context\";\nimport type { FormModel } from \"../form.model\";\n\nexport interface MobxFormProps extends Omit<\n React.HTMLProps<HTMLFormElement>,\n \"form\" | \"action\" | \"method\"\n> {\n form: FormModel<any>;\n}\n\nexport const MobxForm = forwardRef(function MobxForm(\n { form, children, ...formProps }: MobxFormProps,\n ref,\n) {\n return (\n <FormProvider value={form}>\n <form noValidate {...formProps} ref={ref} {...form.props()}>\n {children}\n </form>\n </FormProvider>\n );\n});\n","import Schema, { Validator } from \"typebox/schema\";\nimport * as Value from \"typebox/value\";\nimport * as T from \"typebox\";\nimport { makeAutoObservable, toJS } from \"mobx\";\nimport type { FormFieldConfig } from \"./form.types\";\n\n// TODO: should infer required prop\n// TODO: should also allow for \"id\", defaulting to name\n// TODO: probably shouldn't assume value is of correct type, maybe unknown?\n\nexport class FormFieldModel<T extends T.TSchema = T.TSchema> {\n readonly name: string;\n readonly schema: T;\n readonly config: FormFieldConfig<T>;\n readonly validator: Validator<T>;\n\n value: T.Static<T> | undefined;\n touched = false;\n\n get valid(): boolean {\n if (this.value === undefined && T.IsOptional(this.schema)) return true;\n return this.validator.Check(this.value);\n }\n\n get errorMessage(): string {\n if (this.valid || !this.touched) return \"\";\n // TODO: revisit this, now that .Errors returns success/fail\n const [_, errors] = this.validator.Errors(this.value);\n const error = errors.at(0);\n\n if (!error) return \"\";\n\n if (!T.IsOptional(this.schema) && (this.value === undefined || this.value === \"\")) {\n return \"This field is required.\";\n }\n\n if (\"errorMessage\" in this.schema) {\n if (typeof this.schema.errorMessage === \"string\") {\n return this.schema.errorMessage;\n } else if (typeof this.schema.errorMessage === \"function\") {\n return this.schema.errorMessage(this.value, this.schema);\n }\n }\n\n if (T.IsString(this.schema) && \"format\" in this.schema) {\n return `Please enter a valid ${String(this.schema.format)}`;\n }\n\n return error.message;\n }\n\n constructor(config: FormFieldConfig<T>) {\n this.config = config;\n this.name = config.name;\n this.schema = config.schema;\n this.validator = Schema.Compile(this.schema);\n this.value = Value.Convert(this.schema, config.initialValue) as T.Static<T>;\n\n makeAutoObservable(this, {\n name: false,\n schema: false,\n config: false,\n validator: false,\n });\n }\n\n setValue(value?: T.Static<T>) {\n // TODO: does this still make sense? If value is invalid,\n // then type will be wrong...revisit this\n this.value = Value.Convert(this.schema, value) as T.Static<T>;\n }\n\n setTouched(touched: boolean) {\n this.touched = touched;\n }\n\n reset() {\n this.setTouched(false);\n this.setValue(this.config.initialValue);\n }\n\n // TODO: this any type is dangerous, need to figure out a good way\n // to make this work OTTB for most form controls, while allowing\n // some kind of escape hatch for special cases\n props(): any {\n return {\n name: this.name,\n onChange: (v?: T.Static<T>) => this.setValue(v),\n value: this.value,\n onBlur: () => {\n this.setTouched(true);\n },\n };\n }\n\n toJSON(): T.Static<T> | undefined {\n return toJS(this.value);\n }\n}\n","import * as Format from \"typebox/format\";\nimport { type Static, type TObject, type TSchema } from \"typebox\";\nimport { makeAutoObservable } from \"mobx\";\nimport type { FormConfig, FormFields } from \"./form.types\";\nimport { FormFieldModel } from \"./form-field.model\";\n\n// should these be here?\nFormat.Set(\"password\", () => true);\nFormat.Set(\"phone\", () => true);\n\n// consider using enumerable to allow spreading of\n// field model instead of calling props() unless we need\n// to pass data to props...\n\n// TODO:\n// handleSubmit should catch a special MobxFormError\n// that can set field-level errors from an API response\n\nexport class FormModel<T extends TObject = TObject> {\n readonly fields: FormFields<T>;\n readonly config: FormConfig<T>;\n readonly schema: T;\n\n // TODO: maybe refactor into a \"state\" property?\n submitting = false;\n submitted = false;\n\n // TODO: what should this type be? should it be generic?\n submitError: any;\n\n constructor(schema: T, config: FormConfig<T>) {\n this.schema = schema;\n this.config = config;\n\n makeAutoObservable(this, { fields: false, schema: false, config: false });\n\n this.fields = Object.entries(schema.properties).reduce(\n (fields, [fieldName, fieldSchema]) => {\n fields[fieldName] = new FormFieldModel({\n name: fieldName,\n schema: fieldSchema,\n initialValue: config?.initialValues?.[fieldName],\n });\n return fields;\n },\n {} as Record<string, FormFieldModel<TSchema>>,\n ) as FormFields<T>;\n }\n\n get valid(): boolean {\n return Object.values(this.fields).every((field) => field.valid);\n }\n\n props(): any {\n return {\n onSubmit: (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n\n this.setSubmitError(undefined);\n\n if (!this.validate()) {\n return;\n }\n\n this.setSubmitting(true);\n this.config\n .handleSubmit(this.toJSON() as Static<T>)\n .then((resp) => {\n this.setSubmitted(true);\n return resp;\n })\n .catch((e) => {\n this.setSubmitError(e);\n })\n .finally(() => {\n this.setSubmitting(false);\n });\n },\n };\n }\n\n setSubmitError(error: any): void {\n this.submitError = error;\n }\n\n protected setSubmitting(submitting: boolean): void {\n this.submitting = submitting;\n }\n\n protected setSubmitted(submitted: boolean): void {\n this.submitted = submitted;\n }\n\n reset(): void {\n this.setSubmitError(undefined);\n for (const field of Object.values(this.fields)) {\n field.reset();\n }\n }\n\n validate(): boolean {\n for (const field of Object.values(this.fields)) {\n field.setTouched(true);\n }\n return this.valid;\n }\n\n toJSON(): Partial<Static<T>> {\n return Object.values(this.fields).reduce(\n (fields, field) => {\n fields[field.name] = field.value;\n return fields;\n },\n {} as Partial<Static<T>>,\n );\n }\n}\n","import type { TObject } from \"typebox\";\nimport { useRef } from \"react\";\nimport { FormModel } from \"./form.model\";\nimport type { FormConfig } from \"./form.types\";\n\nexport const useForm = <T extends TObject = TObject>(\n schema: T,\n config: FormConfig<T>,\n): FormModel<T> => {\n const formRef = useRef<FormModel<T>>(undefined);\n if (formRef.current) {\n Object.assign(formRef.current.config, config);\n } else {\n formRef.current = new FormModel<T>(schema, config);\n }\n\n return formRef.current;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"form.mjs","names":[],"sources":["../src/form/form.context.ts","../src/form/components/form.tsx","../src/form/components/form-when.tsx","../src/form/form-field.model.ts","../src/form/form.model.ts","../src/form/use-form.ts"],"sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { FormModel } from \"./form.model\";\n\nexport const formContext = createContext<FormModel | undefined>(undefined);\nexport const useFormContext = () => {\n const context = useContext(formContext);\n if (!context) {\n throw new Error(\n \"Form context not available. Make sure you are within the <Form> component or providing the form context manually.\",\n );\n }\n return context;\n};\n\nexport const FormProvider = formContext.Provider;\n\nexport const useFormContextIfAvailable = () => useContext(formContext);\n","import { forwardRef } from \"react\";\nimport { FormProvider } from \"../form.context\";\nimport type { FormModel } from \"../form.model\";\n\nexport interface MobxFormProps extends Omit<\n React.HTMLProps<HTMLFormElement>,\n \"form\" | \"action\" | \"method\"\n> {\n form: FormModel<any>;\n}\n\nexport const MobxForm = forwardRef(function MobxForm(\n { form, children, ...formProps }: MobxFormProps,\n ref,\n) {\n return (\n <FormProvider value={form}>\n <form noValidate {...formProps} ref={ref} {...form.props()}>\n {children}\n </form>\n </FormProvider>\n );\n});\n","import type { TUnion } from \"typebox\";\nimport { Observer } from \"mobx-react-lite\";\nimport type { FormModel } from \"../form.model\";\nimport type { FormFieldModel } from \"../form-field.model\";\nimport type {\n DiscriminatorKeys,\n DiscriminatorValue,\n FormFields,\n MatchVariant,\n} from \"../form.types\";\n\nexport interface FormWhenProps<T extends TUnion, D extends DiscriminatorKeys<T>, V extends string> {\n form: FormModel<T>;\n /** The discriminator field name. */\n field: D;\n /** Render the children only while the discriminator field equals this value. */\n value: V & DiscriminatorValue<T, D>;\n /** Receives the matching variant's fields. The form itself is already in scope. */\n children: (fields: FormFields<MatchVariant<T, D, V>>) => React.ReactNode;\n}\n\n/**\n * Renders its children only when the form's discriminator field currently holds\n * `value`, passing the fields narrowed to that variant. Stack one per variant to\n * lay out a discriminated-union form without manual conditionals or casts.\n */\nexport function FormWhen<T extends TUnion, D extends DiscriminatorKeys<T>, V extends string>({\n form,\n field,\n value,\n children,\n}: FormWhenProps<T, D, V>) {\n return (\n <Observer>\n {() => {\n const fields = form.rawFields as Record<string, FormFieldModel>;\n if (fields[field as string]?.value !== value) return null;\n return <>{children(fields as unknown as FormFields<MatchVariant<T, D, V>>)}</>;\n }}\n </Observer>\n );\n}\n","import Schema, { Validator } from \"typebox/schema\";\nimport * as Value from \"typebox/value\";\nimport * as T from \"typebox\";\nimport { makeAutoObservable, toJS } from \"mobx\";\nimport type { FormFieldConfig } from \"./form.types\";\n\n// TODO: should infer required prop\n// TODO: should also allow for \"id\", defaulting to name\n// TODO: probably shouldn't assume value is of correct type, maybe unknown?\n\nexport class FormFieldModel<T extends T.TSchema = T.TSchema> {\n readonly name: string;\n readonly schema: T;\n readonly config: FormFieldConfig<T>;\n readonly validator: Validator<T>;\n\n value: T.Static<T> | undefined;\n touched = false;\n\n get valid(): boolean {\n if (this.value === undefined && T.IsOptional(this.schema)) return true;\n return this.validator.Check(this.value);\n }\n\n get errorMessage(): string {\n if (this.valid || !this.touched) return \"\";\n // TODO: revisit this, now that .Errors returns success/fail\n const [_, errors] = this.validator.Errors(this.value);\n const error = errors.at(0);\n\n if (!error) return \"\";\n\n if (!T.IsOptional(this.schema) && (this.value === undefined || this.value === \"\")) {\n return \"This field is required.\";\n }\n\n if (\"errorMessage\" in this.schema) {\n if (typeof this.schema.errorMessage === \"string\") {\n return this.schema.errorMessage;\n } else if (typeof this.schema.errorMessage === \"function\") {\n return this.schema.errorMessage(this.value, this.schema);\n }\n }\n\n if (T.IsString(this.schema) && \"format\" in this.schema) {\n return `Please enter a valid ${String(this.schema.format)}`;\n }\n\n return error.message;\n }\n\n constructor(config: FormFieldConfig<T>) {\n this.config = config;\n this.name = config.name;\n this.schema = config.schema;\n this.validator = Schema.Compile(this.schema);\n this.value = Value.Convert(this.schema, config.initialValue) as T.Static<T>;\n\n makeAutoObservable(this, {\n name: false,\n schema: false,\n config: false,\n validator: false,\n });\n }\n\n setValue(value?: T.Static<T>) {\n // TODO: does this still make sense? If value is invalid,\n // then type will be wrong...revisit this\n this.value = Value.Convert(this.schema, value) as T.Static<T>;\n }\n\n setTouched(touched: boolean) {\n this.touched = touched;\n }\n\n reset() {\n this.setTouched(false);\n this.setValue(this.config.initialValue);\n }\n\n // TODO: this any type is dangerous, need to figure out a good way\n // to make this work OTTB for most form controls, while allowing\n // some kind of escape hatch for special cases\n props(): any {\n return {\n name: this.name,\n onChange: (v?: T.Static<T>) => this.setValue(v),\n value: this.value,\n onBlur: () => {\n this.setTouched(true);\n },\n };\n }\n\n toJSON(): T.Static<T> | undefined {\n return toJS(this.value);\n }\n}\n","import * as Format from \"typebox/format\";\nimport * as Value from \"typebox/value\";\nimport Schema, { type Validator } from \"typebox/schema\";\nimport { IsUnion, Union, type Static, type TObject, type TSchema } from \"typebox\";\nimport { makeAutoObservable } from \"mobx\";\nimport type { FormConfig, FormFields, FormSchema, RawFormFields } from \"./form.types\";\nimport { FormFieldModel } from \"./form-field.model\";\n\n// should these be here?\nFormat.Set(\"password\", () => true);\nFormat.Set(\"phone\", () => true);\n\n// Flatten a schema's fields into a single property map. For a discriminated\n// union this merges the properties of every variant, unioning the schemas of\n// any field that appears in more than one variant (which naturally turns the\n// discriminator into a union of its literals).\nfunction resolveProperties(schema: FormSchema): Record<string, TSchema> {\n if (!IsUnion(schema)) return schema.properties;\n\n const groups: Record<string, TSchema[]> = {};\n for (const variant of schema.anyOf) {\n for (const [key, propSchema] of Object.entries((variant as TObject).properties)) {\n (groups[key] ??= []).push(propSchema as TSchema);\n }\n }\n\n const merged: Record<string, TSchema> = {};\n for (const [key, schemas] of Object.entries(groups)) {\n const distinct = [...new Map(schemas.map((s) => [JSON.stringify(s), s])).values()];\n merged[key] = distinct.length === 1 ? distinct[0]! : Union(distinct);\n }\n return merged;\n}\n\n// consider using enumerable to allow spreading of\n// field model instead of calling props() unless we need\n// to pass data to props...\n\n// TODO:\n// handleSubmit should catch a special MobxFormError\n// that can set field-level errors from an API response\n\nexport class FormModel<T extends FormSchema = TObject> {\n /** Shared fields only (for unions); every field for a plain object schema. */\n readonly fields: FormFields<T>;\n /** Every field across all variants — escape hatch for reaching variant fields. */\n readonly rawFields: RawFormFields<T>;\n readonly config: FormConfig<T>;\n readonly schema: T;\n readonly validator: Validator<T>;\n\n // TODO: maybe refactor into a \"state\" property?\n submitting = false;\n submitted = false;\n\n // TODO: what should this type be? should it be generic?\n submitError: any;\n\n constructor(schema: T, config: FormConfig<T>) {\n this.schema = schema;\n this.config = config;\n this.validator = Schema.Compile(schema);\n\n makeAutoObservable(this, {\n fields: false,\n rawFields: false,\n schema: false,\n config: false,\n validator: false,\n });\n\n const initialValues = config?.initialValues as Record<string, unknown> | undefined;\n const fields = Object.entries(resolveProperties(schema)).reduce(\n (fields, [fieldName, fieldSchema]) => {\n fields[fieldName] = new FormFieldModel({\n name: fieldName,\n schema: fieldSchema,\n initialValue: initialValues?.[fieldName],\n });\n return fields;\n },\n {} as Record<string, FormFieldModel<TSchema>>,\n );\n\n // `fields` and `rawFields` are the same object; the types differ so that a\n // union form only surfaces shared fields by default.\n this.rawFields = fields as unknown as RawFormFields<T>;\n this.fields = fields as unknown as FormFields<T>;\n }\n\n get valid(): boolean {\n // A union form can't be validated field-by-field — fields belonging to the\n // inactive variant would fail — so validate the assembled object instead.\n if (IsUnion(this.schema)) {\n return this.validator.Check(this.toJSON());\n }\n return Object.values(this.fields).every((field) => field.valid);\n }\n\n props(): any {\n return {\n onSubmit: (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n\n this.setSubmitError(undefined);\n\n if (!this.validate()) {\n return;\n }\n\n this.setSubmitting(true);\n this.config\n .handleSubmit(this.toJSON() as Static<T>)\n .then((resp) => {\n this.setSubmitted(true);\n return resp;\n })\n .catch((e) => {\n this.setSubmitError(e);\n })\n .finally(() => {\n this.setSubmitting(false);\n });\n },\n };\n }\n\n setSubmitError(error: any): void {\n this.submitError = error;\n }\n\n protected setSubmitting(submitting: boolean): void {\n this.submitting = submitting;\n }\n\n protected setSubmitted(submitted: boolean): void {\n this.submitted = submitted;\n }\n\n reset(): void {\n this.setSubmitError(undefined);\n for (const field of Object.values(this.fields)) {\n field.reset();\n }\n }\n\n validate(): boolean {\n for (const field of Object.values(this.fields)) {\n field.setTouched(true);\n }\n return this.valid;\n }\n\n toJSON(): Partial<Static<T>> {\n const data = Object.values(this.fields).reduce(\n (fields, field) => {\n fields[field.name] = field.toJSON();\n return fields;\n },\n {} as Record<string, unknown>,\n );\n\n // Drop fields belonging to the inactive variant so submitted data matches\n // the selected member of the union exactly.\n if (IsUnion(this.schema)) {\n return Value.Clean(this.schema, data) as Partial<Static<T>>;\n }\n return data as Partial<Static<T>>;\n }\n}\n","import type { TObject } from \"typebox\";\nimport { useRef } from \"react\";\nimport { FormModel } from \"./form.model\";\nimport type { FormConfig, FormSchema } from \"./form.types\";\n\nexport const useForm = <T extends FormSchema = TObject>(\n schema: T,\n config: FormConfig<T>,\n): FormModel<T> => {\n const formRef = useRef<FormModel<T>>(undefined);\n if (formRef.current) {\n Object.assign(formRef.current.config, config);\n } else {\n formRef.current = new FormModel<T>(schema, config);\n }\n\n return formRef.current;\n};\n"],"mappings":"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"}
|
package/dist/model.d.mts
CHANGED
|
@@ -7,17 +7,23 @@ import * as T from "typebox";
|
|
|
7
7
|
interface ModelStore<M> {
|
|
8
8
|
remove?(model: M): void;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Root schema for a model: a single object (`makeModel`) or a discriminated
|
|
12
|
+
* union of objects (`makeUnionModel`). Shared by both factories and by
|
|
13
|
+
* `makeStore`, which accepts either.
|
|
14
|
+
*/
|
|
15
|
+
type ModelSchema = T.TObject | T.TUnion<T.TObject[]>;
|
|
16
|
+
type Resource<S extends ModelSchema> = T.Static<S>;
|
|
17
|
+
type KeyShape<S extends ModelSchema, K extends readonly (keyof Resource<S>)[]> = K extends readonly [] ? undefined : Pick<Resource<S>, K[number]>;
|
|
18
|
+
type KeyedFn<S extends ModelSchema, K extends readonly (keyof Resource<S>)[], R> = K extends readonly [] ? (...args: any[]) => Promise<R> : (params: KeyShape<S, K>, ...rest: any[]) => Promise<R>;
|
|
19
|
+
type KeyedBodyFn<S extends ModelSchema, K extends readonly (keyof Resource<S>)[], R> = K extends readonly [] ? (body: any, ...rest: any[]) => Promise<R> : (params: KeyShape<S, K>, body: any, ...rest: any[]) => Promise<R>;
|
|
14
20
|
type StripParams<K extends readonly any[], F> = K extends readonly [] ? F : F extends ((params: any, ...rest: infer R) => infer Ret) ? (...args: R) => Ret : never;
|
|
15
21
|
type ReplaceReturn$1<F, R> = F extends ((...args: infer A) => Promise<any>) ? (...args: A) => Promise<R> : never;
|
|
16
22
|
type ReservedActionKey = "reload" | "update" | "delete" | "setData" | "toJSON" | "store";
|
|
17
|
-
type ActionsConfig<S extends
|
|
23
|
+
type ActionsConfig<S extends ModelSchema, K extends readonly (keyof Resource<S>)[]> = {
|
|
18
24
|
[name: string]: KeyedFn<S, K, Resource<S>>;
|
|
19
25
|
} & { [Key in ReservedActionKey]?: never };
|
|
20
|
-
interface ModelConfig<S extends
|
|
26
|
+
interface ModelConfig<S extends ModelSchema, K extends readonly (keyof Resource<S>)[]> {
|
|
21
27
|
keys: K;
|
|
22
28
|
reload?: KeyedFn<S, K, Resource<S>>;
|
|
23
29
|
update?: KeyedBodyFn<S, K, Resource<S>>;
|
|
@@ -39,19 +45,41 @@ type ModelMethods<K extends readonly any[], Cfg> = (Cfg extends {
|
|
|
39
45
|
} : {}) & (Cfg extends {
|
|
40
46
|
actions: infer A;
|
|
41
47
|
} ? { [N in keyof A]: A[N] extends ((...args: any[]) => any) ? ReplaceReturn$1<StripParams<K, A[N]>, any> : never } : {});
|
|
42
|
-
type ModelInstance<S extends
|
|
48
|
+
type ModelInstance<S extends ModelSchema, K extends readonly any[], Cfg> = Resource<S> & {
|
|
43
49
|
readonly store?: ModelStore<any>;
|
|
44
|
-
setData(data:
|
|
50
|
+
setData(data: Resource<S>): void;
|
|
45
51
|
toJSON(): Resource<S>;
|
|
46
52
|
buildParams(): KeyShape<S, K extends readonly (keyof Resource<S>)[] ? K : readonly []>;
|
|
47
53
|
getMobxAnnotations?(): AnnotationsMap<any, never>;
|
|
48
54
|
} & ModelMethods<K, Cfg>;
|
|
49
|
-
type ModelConstructor<S extends
|
|
55
|
+
type ModelConstructor<S extends ModelSchema, K extends readonly any[], Cfg> = {
|
|
50
56
|
new (data: Resource<S>, store?: ModelStore<any>): ModelInstance<S, K, Cfg>;
|
|
51
57
|
readonly schema: S;
|
|
52
58
|
};
|
|
53
59
|
declare function makeModel<S extends T.TObject>(schema: S): ModelConstructor<S, readonly [], {}>;
|
|
54
60
|
declare function makeModel<S extends T.TObject, K extends readonly (keyof Resource<S>)[], Cfg extends ModelConfig<S, K>>(schema: S, config: Cfg): ModelConstructor<S, K, Cfg>;
|
|
61
|
+
type UnionSchema = T.TUnion<T.TObject[]>;
|
|
62
|
+
type SharedFields<S extends UnionSchema> = { [K in keyof Resource<S>]: Resource<S>[K] };
|
|
63
|
+
type VariantFields<S extends UnionSchema, D extends keyof Resource<S>, V> = Extract<Resource<S>, Record<D, V>>;
|
|
64
|
+
interface UnionModelMembers<S extends UnionSchema, D extends keyof Resource<S>, K extends readonly any[]> {
|
|
65
|
+
readonly store?: ModelStore<any>;
|
|
66
|
+
setData(data: Resource<S>): void;
|
|
67
|
+
toJSON(): Resource<S>;
|
|
68
|
+
buildParams(): KeyShape<S, K extends readonly (keyof Resource<S>)[] ? K : readonly []>;
|
|
69
|
+
getMobxAnnotations?(): AnnotationsMap<any, never>;
|
|
70
|
+
/** Type guard: true when the discriminator equals `value`, revealing that variant's fields on this same instance. */
|
|
71
|
+
is<V extends Resource<S>[D]>(value: V): this is this & VariantFields<S, D, V>;
|
|
72
|
+
/** This instance narrowed to the `value` variant (fields exposed directly), or `undefined` if it doesn't match. */
|
|
73
|
+
as<V extends Resource<S>[D]>(value: V): (this & VariantFields<S, D, V>) | undefined;
|
|
74
|
+
}
|
|
75
|
+
type UnionModelInstance<S extends UnionSchema, D extends keyof Resource<S>, K extends readonly any[], Cfg> = SharedFields<S> & UnionModelMembers<S, D, K> & ModelMethods<K, Cfg>;
|
|
76
|
+
type UnionModelConstructor<S extends UnionSchema, D extends keyof Resource<S>, K extends readonly any[], Cfg> = {
|
|
77
|
+
new (data: Resource<S>, store?: ModelStore<any>): UnionModelInstance<S, D, K, Cfg>;
|
|
78
|
+
readonly schema: S;
|
|
79
|
+
readonly discriminator: D;
|
|
80
|
+
};
|
|
81
|
+
declare function makeUnionModel<S extends UnionSchema, D extends keyof Resource<S> & string>(schema: S, discriminator: D): UnionModelConstructor<S, D, readonly [], {}>;
|
|
82
|
+
declare function makeUnionModel<S extends UnionSchema, D extends keyof Resource<S> & string, K extends readonly (keyof Resource<S>)[], Cfg extends ModelConfig<S, K>>(schema: S, discriminator: D, config: Cfg): UnionModelConstructor<S, D, K, Cfg>;
|
|
55
83
|
//#endregion
|
|
56
84
|
//#region src/model/make-store.d.ts
|
|
57
85
|
type ReplaceReturn<F, R> = F extends ((...args: infer A) => Promise<any>) ? (...args: A) => Promise<R> : never;
|
|
@@ -88,8 +116,8 @@ type StoreInstance<M, Cfg> = {
|
|
|
88
116
|
type StoreConstructor<M, Cfg> = {
|
|
89
117
|
new (): StoreInstance<M, Cfg>;
|
|
90
118
|
};
|
|
91
|
-
declare function makeStore<S extends
|
|
92
|
-
declare function makeStore<S extends
|
|
119
|
+
declare function makeStore<S extends ModelSchema>(schema: S): StoreConstructor<T.Static<S>, {}>;
|
|
120
|
+
declare function makeStore<S extends ModelSchema, Cfg extends StoreConfig<T.Static<S>>>(schema: S, config: Cfg & ThisType<StoreThis<InferModel<T.Static<S>, Cfg>>>): StoreConstructor<InferModel<T.Static<S>, Cfg>, Cfg>;
|
|
93
121
|
//#endregion
|
|
94
|
-
export { type AnnotationsMap, type LazyObservableArray, ModelConfig, ModelConstructor, ModelStore, StoreConfig, StoreConstructor, makeModel, makeStore };
|
|
122
|
+
export { type AnnotationsMap, type LazyObservableArray, ModelConfig, ModelConstructor, ModelSchema, ModelStore, StoreConfig, StoreConstructor, UnionModelConstructor, makeModel, makeStore, makeUnionModel };
|
|
95
123
|
//# sourceMappingURL=model.d.mts.map
|
package/dist/model.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.mts","names":[],"sources":["../src/model/make-model.ts","../src/model/make-store.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"model.d.mts","names":[],"sources":["../src/model/make-model.ts","../src/model/make-store.ts"],"mappings":"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"}
|
package/dist/model.mjs
CHANGED
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
import { n as lazyObservableArray } from "./lazy-observable-CdHXpUkk.mjs";
|
|
2
2
|
import { action, makeObservable, observable, runInAction, toJS } from "mobx";
|
|
3
|
-
import "typebox";
|
|
3
|
+
import * as Value from "typebox/value";
|
|
4
|
+
import * as T from "typebox";
|
|
4
5
|
|
|
5
6
|
//#region src/model/make-model.ts
|
|
6
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Every property name across the schema. For a union this is the merged set of
|
|
9
|
+
* all variants' keys, so all of them are made observable up front — that keeps
|
|
10
|
+
* `setData` reactive even when it switches the active variant. `toJSON` runs
|
|
11
|
+
* `Value.Clean` to emit only the keys of the variant the data currently matches.
|
|
12
|
+
*/
|
|
13
|
+
function getPropertyNames(schema) {
|
|
14
|
+
if (!T.IsUnion(schema)) return Object.keys(schema.properties);
|
|
15
|
+
const names = /* @__PURE__ */ new Set();
|
|
16
|
+
for (const variant of schema.anyOf) for (const key of Object.keys(variant.properties)) names.add(key);
|
|
17
|
+
return [...names];
|
|
18
|
+
}
|
|
19
|
+
function createModelClass(schema, config) {
|
|
7
20
|
const keys = config?.keys ?? [];
|
|
21
|
+
const isUnion = T.IsUnion(schema);
|
|
22
|
+
const propertyNames = getPropertyNames(schema);
|
|
8
23
|
class BaseModel {
|
|
9
24
|
static schema = schema;
|
|
10
25
|
store;
|
|
11
26
|
constructor(data, store) {
|
|
12
27
|
this.store = store;
|
|
13
28
|
const annotations = {};
|
|
14
|
-
for (const key of
|
|
29
|
+
for (const key of propertyNames) {
|
|
15
30
|
Object.defineProperty(this, key, {
|
|
16
31
|
value: data[key],
|
|
17
32
|
enumerable: true,
|
|
@@ -26,8 +41,14 @@ function makeModel(schema, config) {
|
|
|
26
41
|
...this.getMobxAnnotations?.()
|
|
27
42
|
});
|
|
28
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Replace the model's data with a complete resource. Every property is
|
|
46
|
+
* reassigned (fields absent from `data` — e.g. another variant's — become
|
|
47
|
+
* `undefined`), so the model always holds a coherent, whole variant rather
|
|
48
|
+
* than a partial merge that could mix fields across the union.
|
|
49
|
+
*/
|
|
29
50
|
setData(data) {
|
|
30
|
-
|
|
51
|
+
for (const key of propertyNames) this[key] = data[key];
|
|
31
52
|
}
|
|
32
53
|
/**
|
|
33
54
|
* Build the params object passed as the first arg to keyed API methods.
|
|
@@ -42,10 +63,11 @@ function makeModel(schema, config) {
|
|
|
42
63
|
}
|
|
43
64
|
toJSON() {
|
|
44
65
|
const data = this;
|
|
45
|
-
|
|
66
|
+
const snapshot = propertyNames.reduce((obj, key) => {
|
|
46
67
|
if (data[key] !== void 0) obj[key] = toJS(data[key]);
|
|
47
68
|
return obj;
|
|
48
69
|
}, {});
|
|
70
|
+
return isUnion ? Value.Clean(schema, snapshot) : snapshot;
|
|
49
71
|
}
|
|
50
72
|
}
|
|
51
73
|
const proto = BaseModel.prototype;
|
|
@@ -89,6 +111,21 @@ function makeModel(schema, config) {
|
|
|
89
111
|
}
|
|
90
112
|
return BaseModel;
|
|
91
113
|
}
|
|
114
|
+
function makeModel(schema, config) {
|
|
115
|
+
return createModelClass(schema, config);
|
|
116
|
+
}
|
|
117
|
+
function makeUnionModel(schema, discriminator, config) {
|
|
118
|
+
const ModelClass = createModelClass(schema, config);
|
|
119
|
+
ModelClass.discriminator = discriminator;
|
|
120
|
+
const proto = ModelClass.prototype;
|
|
121
|
+
proto.is = function(value) {
|
|
122
|
+
return this[discriminator] === value;
|
|
123
|
+
};
|
|
124
|
+
proto.as = function(value) {
|
|
125
|
+
return this[discriminator] === value ? this : void 0;
|
|
126
|
+
};
|
|
127
|
+
return ModelClass;
|
|
128
|
+
}
|
|
92
129
|
|
|
93
130
|
//#endregion
|
|
94
131
|
//#region src/model/make-store.ts
|
|
@@ -136,5 +173,5 @@ function makeStore(schema, config) {
|
|
|
136
173
|
}
|
|
137
174
|
|
|
138
175
|
//#endregion
|
|
139
|
-
export { makeModel, makeStore };
|
|
176
|
+
export { makeModel, makeStore, makeUnionModel };
|
|
140
177
|
//# sourceMappingURL=model.mjs.map
|
package/dist/model.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.mjs","names":[],"sources":["../src/model/make-model.ts","../src/model/make-store.ts"],"sourcesContent":["import { action, makeObservable, observable, runInAction, toJS, type AnnotationsMap } from \"mobx\";\nimport * as T from \"typebox\";\n\n// -----------------------------------------------------------------------------\n// Public structural contract\n// -----------------------------------------------------------------------------\n\n/** Minimal interface a model's host store must satisfy. */\nexport interface ModelStore<M> {\n remove?(model: M): void;\n}\n\n// -----------------------------------------------------------------------------\n// Type plumbing\n// -----------------------------------------------------------------------------\n\ntype Resource<S extends T.TObject> = T.Static<S>;\n\ntype KeyShape<S extends T.TObject, K extends readonly (keyof Resource<S>)[]> = K extends readonly []\n ? undefined\n : Pick<Resource<S>, K[number]>;\n\ntype KeyedFn<\n S extends T.TObject,\n K extends readonly (keyof Resource<S>)[],\n R,\n> = K extends readonly []\n ? (...args: any[]) => Promise<R>\n : (params: KeyShape<S, K>, ...rest: any[]) => Promise<R>;\n\ntype KeyedBodyFn<\n S extends T.TObject,\n K extends readonly (keyof Resource<S>)[],\n R,\n> = K extends readonly []\n ? (body: any, ...rest: any[]) => Promise<R>\n : (params: KeyShape<S, K>, body: any, ...rest: any[]) => Promise<R>;\n\n// Strip the first arg when keys is non-empty — model methods don't take the params.\ntype StripParams<K extends readonly any[], F> = K extends readonly []\n ? F\n : F extends (params: any, ...rest: infer R) => infer Ret\n ? (...args: R) => Ret\n : never;\n\n// Replace a function's Promise return with Promise<R>.\ntype ReplaceReturn<F, R> = F extends (...args: infer A) => Promise<any>\n ? (...args: A) => Promise<R>\n : never;\n\ntype ReservedActionKey = \"reload\" | \"update\" | \"delete\" | \"setData\" | \"toJSON\" | \"store\";\n\ntype ActionsConfig<S extends T.TObject, K extends readonly (keyof Resource<S>)[]> = {\n [name: string]: KeyedFn<S, K, Resource<S>>;\n} & { [Key in ReservedActionKey]?: never };\n\nexport interface ModelConfig<S extends T.TObject, K extends readonly (keyof Resource<S>)[]> {\n keys: K;\n reload?: KeyedFn<S, K, Resource<S>>;\n update?: KeyedBodyFn<S, K, Resource<S>>;\n delete?: KeyedFn<S, K, any>;\n actions?: ActionsConfig<S, K>;\n}\n\n// Instance method shape from config. Self-mutating methods return Promise<any>;\n// the instance is mutated in place via setData, so callers typically read fields\n// off the same reference instead of chaining the return.\ntype ModelMethods<K extends readonly any[], Cfg> = (Cfg extends { reload: infer F }\n ? { reload: ReplaceReturn<StripParams<K, F>, any> }\n : {}) &\n (Cfg extends { update: infer F } ? { update: ReplaceReturn<StripParams<K, F>, any> } : {}) &\n (Cfg extends { delete: infer F } ? { delete: StripParams<K, F> } : {}) &\n (Cfg extends { actions: infer A }\n ? {\n [N in keyof A]: A[N] extends (...args: any[]) => any\n ? ReplaceReturn<StripParams<K, A[N]>, any>\n : never;\n }\n : {});\n\n// -----------------------------------------------------------------------------\n// Constructor type\n// -----------------------------------------------------------------------------\n\ntype ModelInstance<S extends T.TObject, K extends readonly any[], Cfg> = Resource<S> & {\n readonly store?: ModelStore<any>;\n setData(data: Partial<Resource<S>>): void;\n toJSON(): Resource<S>;\n buildParams(): KeyShape<S, K extends readonly (keyof Resource<S>)[] ? K : readonly []>;\n getMobxAnnotations?(): AnnotationsMap<any, never>;\n} & ModelMethods<K, Cfg>;\n\nexport type ModelConstructor<S extends T.TObject, K extends readonly any[], Cfg> = {\n new (data: Resource<S>, store?: ModelStore<any>): ModelInstance<S, K, Cfg>;\n readonly schema: S;\n};\n\n// -----------------------------------------------------------------------------\n// makeModel\n// -----------------------------------------------------------------------------\n\nexport function makeModel<S extends T.TObject>(schema: S): ModelConstructor<S, readonly [], {}>;\nexport function makeModel<\n S extends T.TObject,\n K extends readonly (keyof Resource<S>)[],\n Cfg extends ModelConfig<S, K>,\n>(schema: S, config: Cfg): ModelConstructor<S, K, Cfg>;\nexport function makeModel<S extends T.TObject>(\n schema: S,\n config?: ModelConfig<S, readonly (keyof Resource<S>)[]>,\n): any {\n const keys = (config?.keys ?? []) as readonly PropertyKey[];\n\n abstract class BaseModel {\n static readonly schema = schema;\n\n readonly store?: ModelStore<any>;\n\n constructor(data: Resource<S>, store?: ModelStore<any>) {\n this.store = store;\n\n const annotations: Record<string, any> = {};\n for (const key of Object.keys(schema.properties)) {\n Object.defineProperty(this, key, {\n value: (data as any)[key],\n enumerable: true,\n configurable: true,\n writable: true,\n });\n annotations[key] = observable.ref;\n }\n\n makeObservable(this, {\n ...annotations,\n setData: action,\n ...(this as any).getMobxAnnotations?.(),\n });\n }\n\n setData(data: Partial<Resource<S>>): void {\n Object.assign(this, data);\n }\n\n /**\n * Build the params object passed as the first arg to keyed API methods.\n * Default extracts each property in `keys` from the model. Override on a\n * subclass when the model field name differs from the API param name, or\n * to construct composite params from derived values.\n */\n buildParams(): any {\n if (keys.length === 0) return undefined;\n const data = this as any;\n return Object.fromEntries(keys.map((k) => [k, data[k]]));\n }\n\n toJSON(): Resource<S> {\n const data = this as any;\n return Object.keys(schema.properties).reduce(\n (obj, key) => {\n if (data[key] !== undefined) obj[key] = toJS(data[key]);\n return obj;\n },\n {} as Record<string, any>,\n ) as Resource<S>;\n }\n }\n\n const proto = BaseModel.prototype as any;\n\n if (config?.reload) {\n const reload = config.reload as (...args: any[]) => Promise<any>;\n proto.reload = async function (...rest: any[]) {\n const params = this.buildParams();\n const data = params === undefined ? await reload(...rest) : await reload(params, ...rest);\n runInAction(() => this.setData(data));\n return this;\n };\n }\n\n if (config?.update) {\n const update = config.update as (...args: any[]) => Promise<any>;\n proto.update = async function (body: any, ...rest: any[]) {\n const params = this.buildParams();\n const data =\n params === undefined ? await update(body, ...rest) : await update(params, body, ...rest);\n runInAction(() => this.setData(data));\n return this;\n };\n }\n\n if (config?.delete) {\n const del = config.delete as (...args: any[]) => Promise<any>;\n proto.delete = async function (...rest: any[]) {\n const params = this.buildParams();\n const result = params === undefined ? await del(...rest) : await del(params, ...rest);\n this.store?.remove?.(this);\n return result;\n };\n }\n\n if (config?.actions) {\n for (const [name, fn] of Object.entries(config.actions)) {\n const call = fn as (...args: any[]) => Promise<any>;\n proto[name] = async function (body?: any, ...rest: any[]) {\n const params = this.buildParams();\n let data: any;\n if (params === undefined) {\n data = body === undefined ? await call() : await call(body, ...rest);\n } else {\n data = body === undefined ? await call(params) : await call(params, body, ...rest);\n }\n runInAction(() => this.setData(data));\n return this;\n };\n }\n }\n\n return BaseModel;\n}\n\nexport type { AnnotationsMap };\n","import * as T from \"typebox\";\nimport { lazyObservableArray, type LazyObservableArray } from \"../lazy-observable/lazy-observable\";\nimport { action, makeObservable, runInAction } from \"mobx\";\n\n// -----------------------------------------------------------------------------\n// Type plumbing\n// -----------------------------------------------------------------------------\n\n// Replace a function's Promise return with Promise<R> — used so get/create\n// return models rather than the raw API response.\ntype ReplaceReturn<F, R> = F extends (...args: infer A) => Promise<any>\n ? (...args: A) => Promise<R>\n : never;\n\n// The structural shape passed as `this` inside `transform` — the only member\n// transform ever needs is `remove`, since it's about to construct a model and\n// hand the store reference along to its constructor.\ntype StoreThis<M> = {\n remove(model: M): void;\n};\n\n// Infer M from config: when transform is present use its return type, else use R.\ntype InferModel<R, Cfg> = Cfg extends { transform: (...args: any[]) => infer M } ? M : R;\n\nexport interface StoreConfig<R> {\n transform?: (data: R, store: { remove(model: any): void }) => any;\n get?: (...args: any[]) => Promise<R>;\n getAll?: (...args: any[]) => Promise<R[]>;\n create?: (...args: any[]) => Promise<R>;\n}\n\n// Final store-instance shape, derived from config slots.\ntype StoreInstance<M, Cfg> = {\n remove(model: M): void;\n} & (Cfg extends { get: infer G } ? { get: ReplaceReturn<G, M> } : {}) &\n (Cfg extends { getAll: any } ? { getAll: () => Promise<M[]>; all: LazyObservableArray<M> } : {}) &\n (Cfg extends { create: infer C } ? { create: ReplaceReturn<C, M> } : {});\n\nexport type StoreConstructor<M, Cfg> = {\n new (): StoreInstance<M, Cfg>;\n};\n\n// -----------------------------------------------------------------------------\n// makeStore\n// -----------------------------------------------------------------------------\n\nexport function makeStore<S extends T.TObject>(schema: S): StoreConstructor<T.Static<S>, {}>;\nexport function makeStore<S extends T.TObject, Cfg extends StoreConfig<T.Static<S>>>(\n schema: S,\n config: Cfg & ThisType<StoreThis<InferModel<T.Static<S>, Cfg>>>,\n): StoreConstructor<InferModel<T.Static<S>, Cfg>, Cfg>;\nexport function makeStore<S extends T.TObject>(\n schema: S,\n config?: StoreConfig<T.Static<S>>,\n): StoreConstructor<any, any> {\n type R = T.Static<S>;\n\n class Store {\n private readonly _transform: (data: R) => any;\n\n all?: LazyObservableArray<any>;\n\n constructor() {\n const rawTransform = config?.transform;\n this._transform = rawTransform\n ? (data: R) => rawTransform.call(this, data, this)\n : (data: R) => data as any;\n\n makeObservable<this, \"_transform\">(this, {\n _transform: false,\n remove: action,\n });\n\n if (config?.getAll) {\n this.all = lazyObservableArray(async () => {\n const items = await (config.getAll as (...args: any[]) => Promise<R[]>)();\n return items.map((item) => this._transform(item));\n });\n }\n }\n\n remove(model: any): void {\n this.all?.value?.remove?.(model);\n }\n }\n\n const proto = Store.prototype as any;\n\n if (config?.get) {\n const get = config.get;\n proto.get = function (...args: any[]) {\n return get(...args).then((data: R) => this._transform(data));\n };\n }\n\n if (config?.getAll) {\n proto.getAll = function () {\n return this.all.getOrLoad();\n };\n }\n\n if (config?.create) {\n const create = config.create;\n proto.create = async function (...args: any[]) {\n const data = await create(...args);\n return runInAction(() => {\n const model = this._transform(data);\n if (this.all) this.all.value.unshift(model);\n return model;\n });\n };\n }\n\n return Store as unknown as StoreConstructor<any, any>;\n}\n\nexport type { LazyObservableArray };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"model.mjs","names":[],"sources":["../src/model/make-model.ts","../src/model/make-store.ts"],"sourcesContent":["import { action, makeObservable, observable, runInAction, toJS, type AnnotationsMap } from \"mobx\";\nimport * as T from \"typebox\";\nimport * as Value from \"typebox/value\";\n\n// -----------------------------------------------------------------------------\n// Public structural contract\n// -----------------------------------------------------------------------------\n\n/** Minimal interface a model's host store must satisfy. */\nexport interface ModelStore<M> {\n remove?(model: M): void;\n}\n\n/**\n * Root schema for a model: a single object (`makeModel`) or a discriminated\n * union of objects (`makeUnionModel`). Shared by both factories and by\n * `makeStore`, which accepts either.\n */\nexport type ModelSchema = T.TObject | T.TUnion<T.TObject[]>;\n\n/**\n * Every property name across the schema. For a union this is the merged set of\n * all variants' keys, so all of them are made observable up front — that keeps\n * `setData` reactive even when it switches the active variant. `toJSON` runs\n * `Value.Clean` to emit only the keys of the variant the data currently matches.\n */\nfunction getPropertyNames(schema: ModelSchema): string[] {\n if (!T.IsUnion(schema)) return Object.keys(schema.properties);\n const names = new Set<string>();\n for (const variant of schema.anyOf) {\n for (const key of Object.keys((variant as T.TObject).properties)) names.add(key);\n }\n return [...names];\n}\n\n// -----------------------------------------------------------------------------\n// Type plumbing\n// -----------------------------------------------------------------------------\n\ntype Resource<S extends ModelSchema> = T.Static<S>;\n\ntype KeyShape<\n S extends ModelSchema,\n K extends readonly (keyof Resource<S>)[],\n> = K extends readonly [] ? undefined : Pick<Resource<S>, K[number]>;\n\ntype KeyedFn<\n S extends ModelSchema,\n K extends readonly (keyof Resource<S>)[],\n R,\n> = K extends readonly []\n ? (...args: any[]) => Promise<R>\n : (params: KeyShape<S, K>, ...rest: any[]) => Promise<R>;\n\ntype KeyedBodyFn<\n S extends ModelSchema,\n K extends readonly (keyof Resource<S>)[],\n R,\n> = K extends readonly []\n ? (body: any, ...rest: any[]) => Promise<R>\n : (params: KeyShape<S, K>, body: any, ...rest: any[]) => Promise<R>;\n\n// Strip the first arg when keys is non-empty — model methods don't take the params.\ntype StripParams<K extends readonly any[], F> = K extends readonly []\n ? F\n : F extends (params: any, ...rest: infer R) => infer Ret\n ? (...args: R) => Ret\n : never;\n\n// Replace a function's Promise return with Promise<R>.\ntype ReplaceReturn<F, R> = F extends (...args: infer A) => Promise<any>\n ? (...args: A) => Promise<R>\n : never;\n\ntype ReservedActionKey = \"reload\" | \"update\" | \"delete\" | \"setData\" | \"toJSON\" | \"store\";\n\ntype ActionsConfig<S extends ModelSchema, K extends readonly (keyof Resource<S>)[]> = {\n [name: string]: KeyedFn<S, K, Resource<S>>;\n} & { [Key in ReservedActionKey]?: never };\n\nexport interface ModelConfig<S extends ModelSchema, K extends readonly (keyof Resource<S>)[]> {\n keys: K;\n reload?: KeyedFn<S, K, Resource<S>>;\n update?: KeyedBodyFn<S, K, Resource<S>>;\n delete?: KeyedFn<S, K, any>;\n actions?: ActionsConfig<S, K>;\n}\n\n// Instance method shape from config. Self-mutating methods return Promise<any>;\n// the instance is mutated in place via setData, so callers typically read fields\n// off the same reference instead of chaining the return.\ntype ModelMethods<K extends readonly any[], Cfg> = (Cfg extends { reload: infer F }\n ? { reload: ReplaceReturn<StripParams<K, F>, any> }\n : {}) &\n (Cfg extends { update: infer F } ? { update: ReplaceReturn<StripParams<K, F>, any> } : {}) &\n (Cfg extends { delete: infer F } ? { delete: StripParams<K, F> } : {}) &\n (Cfg extends { actions: infer A }\n ? {\n [N in keyof A]: A[N] extends (...args: any[]) => any\n ? ReplaceReturn<StripParams<K, A[N]>, any>\n : never;\n }\n : {});\n\n// -----------------------------------------------------------------------------\n// Constructor type\n// -----------------------------------------------------------------------------\n\ntype ModelInstance<S extends ModelSchema, K extends readonly any[], Cfg> = Resource<S> & {\n readonly store?: ModelStore<any>;\n setData(data: Resource<S>): void;\n toJSON(): Resource<S>;\n buildParams(): KeyShape<S, K extends readonly (keyof Resource<S>)[] ? K : readonly []>;\n getMobxAnnotations?(): AnnotationsMap<any, never>;\n} & ModelMethods<K, Cfg>;\n\nexport type ModelConstructor<S extends ModelSchema, K extends readonly any[], Cfg> = {\n new (data: Resource<S>, store?: ModelStore<any>): ModelInstance<S, K, Cfg>;\n readonly schema: S;\n};\n\n// -----------------------------------------------------------------------------\n// Shared class builder\n// -----------------------------------------------------------------------------\n\n// Builds the observable model class used by both makeModel and makeUnionModel.\n// Handles object and union schemas at runtime; the public factories layer the\n// appropriate types (and, for unions, the `is`/`as` guards) on top.\nfunction createModelClass(schema: ModelSchema, config?: ModelConfig<any, any>): any {\n const keys = (config?.keys ?? []) as readonly PropertyKey[];\n const isUnion = T.IsUnion(schema);\n const propertyNames = getPropertyNames(schema);\n\n abstract class BaseModel {\n static readonly schema = schema;\n\n readonly store?: ModelStore<any>;\n\n constructor(data: any, store?: ModelStore<any>) {\n this.store = store;\n\n // Make every property of every variant observable up front, so `setData`\n // stays reactive even when it switches the active variant. Foreign-variant\n // fields sit as `undefined`; TypeScript hides them, and `toJSON` cleans them out.\n const annotations: Record<string, any> = {};\n for (const key of propertyNames) {\n Object.defineProperty(this, key, {\n value: (data as any)[key],\n enumerable: true,\n configurable: true,\n writable: true,\n });\n annotations[key] = observable.ref;\n }\n\n makeObservable(this, {\n ...annotations,\n setData: action,\n ...(this as any).getMobxAnnotations?.(),\n });\n }\n\n /**\n * Replace the model's data with a complete resource. Every property is\n * reassigned (fields absent from `data` — e.g. another variant's — become\n * `undefined`), so the model always holds a coherent, whole variant rather\n * than a partial merge that could mix fields across the union.\n */\n setData(data: any): void {\n for (const key of propertyNames) {\n (this as any)[key] = (data as any)[key];\n }\n }\n\n /**\n * Build the params object passed as the first arg to keyed API methods.\n * Default extracts each property in `keys` from the model. Override on a\n * subclass when the model field name differs from the API param name, or\n * to construct composite params from derived values.\n */\n buildParams(): any {\n if (keys.length === 0) return undefined;\n const data = this as any;\n return Object.fromEntries(keys.map((k) => [k, data[k]]));\n }\n\n toJSON(): any {\n const data = this as any;\n const snapshot = propertyNames.reduce(\n (obj, key) => {\n if (data[key] !== undefined) obj[key] = toJS(data[key]);\n return obj;\n },\n {} as Record<string, any>,\n );\n // For a union, strip any fields not belonging to the variant the current\n // data matches (e.g. a stale field left over from a previous variant).\n return isUnion ? Value.Clean(schema, snapshot) : snapshot;\n }\n }\n\n const proto = BaseModel.prototype as any;\n\n if (config?.reload) {\n const reload = config.reload as (...args: any[]) => Promise<any>;\n proto.reload = async function (...rest: any[]) {\n const params = this.buildParams();\n const data = params === undefined ? await reload(...rest) : await reload(params, ...rest);\n runInAction(() => this.setData(data));\n return this;\n };\n }\n\n if (config?.update) {\n const update = config.update as (...args: any[]) => Promise<any>;\n proto.update = async function (body: any, ...rest: any[]) {\n const params = this.buildParams();\n const data =\n params === undefined ? await update(body, ...rest) : await update(params, body, ...rest);\n runInAction(() => this.setData(data));\n return this;\n };\n }\n\n if (config?.delete) {\n const del = config.delete as (...args: any[]) => Promise<any>;\n proto.delete = async function (...rest: any[]) {\n const params = this.buildParams();\n const result = params === undefined ? await del(...rest) : await del(params, ...rest);\n this.store?.remove?.(this);\n return result;\n };\n }\n\n if (config?.actions) {\n for (const [name, fn] of Object.entries(config.actions)) {\n const call = fn as (...args: any[]) => Promise<any>;\n proto[name] = async function (body?: any, ...rest: any[]) {\n const params = this.buildParams();\n let data: any;\n if (params === undefined) {\n data = body === undefined ? await call() : await call(body, ...rest);\n } else {\n data = body === undefined ? await call(params) : await call(params, body, ...rest);\n }\n runInAction(() => this.setData(data));\n return this;\n };\n }\n }\n\n return BaseModel;\n}\n\n// -----------------------------------------------------------------------------\n// makeModel (single object schemas)\n// -----------------------------------------------------------------------------\n\nexport function makeModel<S extends T.TObject>(schema: S): ModelConstructor<S, readonly [], {}>;\nexport function makeModel<\n S extends T.TObject,\n K extends readonly (keyof Resource<S>)[],\n Cfg extends ModelConfig<S, K>,\n>(schema: S, config: Cfg): ModelConstructor<S, K, Cfg>;\nexport function makeModel<S extends T.TObject>(\n schema: S,\n config?: ModelConfig<S, readonly (keyof Resource<S>)[]>,\n): any {\n return createModelClass(schema, config);\n}\n\n// -----------------------------------------------------------------------------\n// makeUnionModel (discriminated union schemas)\n// -----------------------------------------------------------------------------\n\ntype UnionSchema = T.TUnion<T.TObject[]>;\n\n// Properties common to every variant (`keyof` a union resolves to shared keys).\ntype SharedFields<S extends UnionSchema> = { [K in keyof Resource<S>]: Resource<S>[K] };\n\n// The full static shape of the variant whose discriminator `D` equals `V`.\ntype VariantFields<S extends UnionSchema, D extends keyof Resource<S>, V> = Extract<\n Resource<S>,\n Record<D, V>\n>;\n\n// The members makeUnionModel adds. An interface (not a type-alias literal) so the\n// polymorphic `this` in `is`/`as` is allowed; at a call site `this` resolves to\n// the full instance, so the guard reveals the variant's fields on it.\ninterface UnionModelMembers<\n S extends UnionSchema,\n D extends keyof Resource<S>,\n K extends readonly any[],\n> {\n readonly store?: ModelStore<any>;\n setData(data: Resource<S>): void;\n toJSON(): Resource<S>;\n buildParams(): KeyShape<S, K extends readonly (keyof Resource<S>)[] ? K : readonly []>;\n getMobxAnnotations?(): AnnotationsMap<any, never>;\n /** Type guard: true when the discriminator equals `value`, revealing that variant's fields on this same instance. */\n is<V extends Resource<S>[D]>(value: V): this is this & VariantFields<S, D, V>;\n /** This instance narrowed to the `value` variant (fields exposed directly), or `undefined` if it doesn't match. */\n as<V extends Resource<S>[D]>(value: V): (this & VariantFields<S, D, V>) | undefined;\n}\n\n// Base instance exposes only the shared fields (a single object type, so it can\n// be subclassed). Variant-specific fields exist at runtime but are revealed on\n// the type only through `is`/`as`.\ntype UnionModelInstance<\n S extends UnionSchema,\n D extends keyof Resource<S>,\n K extends readonly any[],\n Cfg,\n> = SharedFields<S> & UnionModelMembers<S, D, K> & ModelMethods<K, Cfg>;\n\nexport type UnionModelConstructor<\n S extends UnionSchema,\n D extends keyof Resource<S>,\n K extends readonly any[],\n Cfg,\n> = {\n new (data: Resource<S>, store?: ModelStore<any>): UnionModelInstance<S, D, K, Cfg>;\n readonly schema: S;\n readonly discriminator: D;\n};\n\nexport function makeUnionModel<S extends UnionSchema, D extends keyof Resource<S> & string>(\n schema: S,\n discriminator: D,\n): UnionModelConstructor<S, D, readonly [], {}>;\nexport function makeUnionModel<\n S extends UnionSchema,\n D extends keyof Resource<S> & string,\n K extends readonly (keyof Resource<S>)[],\n Cfg extends ModelConfig<S, K>,\n>(schema: S, discriminator: D, config: Cfg): UnionModelConstructor<S, D, K, Cfg>;\nexport function makeUnionModel(\n schema: UnionSchema,\n discriminator: string,\n config?: ModelConfig<any, any>,\n): any {\n const ModelClass = createModelClass(schema, config);\n ModelClass.discriminator = discriminator;\n\n const proto = ModelClass.prototype as any;\n proto.is = function (value: unknown): boolean {\n return (this as any)[discriminator] === value;\n };\n proto.as = function (value: unknown): unknown {\n return (this as any)[discriminator] === value ? this : undefined;\n };\n\n return ModelClass;\n}\n\nexport type { AnnotationsMap };\n","import * as T from \"typebox\";\nimport { lazyObservableArray, type LazyObservableArray } from \"../lazy-observable/lazy-observable\";\nimport { action, makeObservable, runInAction } from \"mobx\";\nimport type { ModelSchema } from \"./make-model\";\n\n// -----------------------------------------------------------------------------\n// Type plumbing\n// -----------------------------------------------------------------------------\n\n// Replace a function's Promise return with Promise<R> — used so get/create\n// return models rather than the raw API response.\ntype ReplaceReturn<F, R> = F extends (...args: infer A) => Promise<any>\n ? (...args: A) => Promise<R>\n : never;\n\n// The structural shape passed as `this` inside `transform` — the only member\n// transform ever needs is `remove`, since it's about to construct a model and\n// hand the store reference along to its constructor.\ntype StoreThis<M> = {\n remove(model: M): void;\n};\n\n// Infer M from config: when transform is present use its return type, else use R.\ntype InferModel<R, Cfg> = Cfg extends { transform: (...args: any[]) => infer M } ? M : R;\n\nexport interface StoreConfig<R> {\n transform?: (data: R, store: { remove(model: any): void }) => any;\n get?: (...args: any[]) => Promise<R>;\n getAll?: (...args: any[]) => Promise<R[]>;\n create?: (...args: any[]) => Promise<R>;\n}\n\n// Final store-instance shape, derived from config slots.\ntype StoreInstance<M, Cfg> = {\n remove(model: M): void;\n} & (Cfg extends { get: infer G } ? { get: ReplaceReturn<G, M> } : {}) &\n (Cfg extends { getAll: any } ? { getAll: () => Promise<M[]>; all: LazyObservableArray<M> } : {}) &\n (Cfg extends { create: infer C } ? { create: ReplaceReturn<C, M> } : {});\n\nexport type StoreConstructor<M, Cfg> = {\n new (): StoreInstance<M, Cfg>;\n};\n\n// -----------------------------------------------------------------------------\n// makeStore\n// -----------------------------------------------------------------------------\n\nexport function makeStore<S extends ModelSchema>(schema: S): StoreConstructor<T.Static<S>, {}>;\nexport function makeStore<S extends ModelSchema, Cfg extends StoreConfig<T.Static<S>>>(\n schema: S,\n config: Cfg & ThisType<StoreThis<InferModel<T.Static<S>, Cfg>>>,\n): StoreConstructor<InferModel<T.Static<S>, Cfg>, Cfg>;\nexport function makeStore<S extends ModelSchema>(\n schema: S,\n config?: StoreConfig<T.Static<S>>,\n): StoreConstructor<any, any> {\n type R = T.Static<S>;\n\n class Store {\n private readonly _transform: (data: R) => any;\n\n all?: LazyObservableArray<any>;\n\n constructor() {\n const rawTransform = config?.transform;\n this._transform = rawTransform\n ? (data: R) => rawTransform.call(this, data, this)\n : (data: R) => data as any;\n\n makeObservable<this, \"_transform\">(this, {\n _transform: false,\n remove: action,\n });\n\n if (config?.getAll) {\n this.all = lazyObservableArray(async () => {\n const items = await (config.getAll as (...args: any[]) => Promise<R[]>)();\n return items.map((item) => this._transform(item));\n });\n }\n }\n\n remove(model: any): void {\n this.all?.value?.remove?.(model);\n }\n }\n\n const proto = Store.prototype as any;\n\n if (config?.get) {\n const get = config.get;\n proto.get = function (...args: any[]) {\n return get(...args).then((data: R) => this._transform(data));\n };\n }\n\n if (config?.getAll) {\n proto.getAll = function () {\n return this.all.getOrLoad();\n };\n }\n\n if (config?.create) {\n const create = config.create;\n proto.create = async function (...args: any[]) {\n const data = await create(...args);\n return runInAction(() => {\n const model = this._transform(data);\n if (this.all) this.all.value.unshift(model);\n return model;\n });\n };\n }\n\n return Store as unknown as StoreConstructor<any, any>;\n}\n\nexport type { LazyObservableArray };\n"],"mappings":";;;;;;;;;;;;AA0BA,SAAS,iBAAiB,QAA+B;AACvD,KAAI,CAAC,EAAE,QAAQ,OAAO,CAAE,QAAO,OAAO,KAAK,OAAO,WAAW;CAC7D,MAAM,wBAAQ,IAAI,KAAa;AAC/B,MAAK,MAAM,WAAW,OAAO,MAC3B,MAAK,MAAM,OAAO,OAAO,KAAM,QAAsB,WAAW,CAAE,OAAM,IAAI,IAAI;AAElF,QAAO,CAAC,GAAG,MAAM;;AAgGnB,SAAS,iBAAiB,QAAqB,QAAqC;CAClF,MAAM,OAAQ,QAAQ,QAAQ,EAAE;CAChC,MAAM,UAAU,EAAE,QAAQ,OAAO;CACjC,MAAM,gBAAgB,iBAAiB,OAAO;CAE9C,MAAe,UAAU;EACvB,OAAgB,SAAS;EAEzB,AAAS;EAET,YAAY,MAAW,OAAyB;AAC9C,QAAK,QAAQ;GAKb,MAAM,cAAmC,EAAE;AAC3C,QAAK,MAAM,OAAO,eAAe;AAC/B,WAAO,eAAe,MAAM,KAAK;KAC/B,OAAQ,KAAa;KACrB,YAAY;KACZ,cAAc;KACd,UAAU;KACX,CAAC;AACF,gBAAY,OAAO,WAAW;;AAGhC,kBAAe,MAAM;IACnB,GAAG;IACH,SAAS;IACT,GAAI,KAAa,sBAAsB;IACxC,CAAC;;;;;;;;EASJ,QAAQ,MAAiB;AACvB,QAAK,MAAM,OAAO,cAChB,CAAC,KAAa,OAAQ,KAAa;;;;;;;;EAUvC,cAAmB;AACjB,OAAI,KAAK,WAAW,EAAG,QAAO;GAC9B,MAAM,OAAO;AACb,UAAO,OAAO,YAAY,KAAK,KAAK,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;;EAG1D,SAAc;GACZ,MAAM,OAAO;GACb,MAAM,WAAW,cAAc,QAC5B,KAAK,QAAQ;AACZ,QAAI,KAAK,SAAS,OAAW,KAAI,OAAO,KAAK,KAAK,KAAK;AACvD,WAAO;MAET,EAAE,CACH;AAGD,UAAO,UAAU,MAAM,MAAM,QAAQ,SAAS,GAAG;;;CAIrD,MAAM,QAAQ,UAAU;AAExB,KAAI,QAAQ,QAAQ;EAClB,MAAM,SAAS,OAAO;AACtB,QAAM,SAAS,eAAgB,GAAG,MAAa;GAC7C,MAAM,SAAS,KAAK,aAAa;GACjC,MAAM,OAAO,WAAW,SAAY,MAAM,OAAO,GAAG,KAAK,GAAG,MAAM,OAAO,QAAQ,GAAG,KAAK;AACzF,qBAAkB,KAAK,QAAQ,KAAK,CAAC;AACrC,UAAO;;;AAIX,KAAI,QAAQ,QAAQ;EAClB,MAAM,SAAS,OAAO;AACtB,QAAM,SAAS,eAAgB,MAAW,GAAG,MAAa;GACxD,MAAM,SAAS,KAAK,aAAa;GACjC,MAAM,OACJ,WAAW,SAAY,MAAM,OAAO,MAAM,GAAG,KAAK,GAAG,MAAM,OAAO,QAAQ,MAAM,GAAG,KAAK;AAC1F,qBAAkB,KAAK,QAAQ,KAAK,CAAC;AACrC,UAAO;;;AAIX,KAAI,QAAQ,QAAQ;EAClB,MAAM,MAAM,OAAO;AACnB,QAAM,SAAS,eAAgB,GAAG,MAAa;GAC7C,MAAM,SAAS,KAAK,aAAa;GACjC,MAAM,SAAS,WAAW,SAAY,MAAM,IAAI,GAAG,KAAK,GAAG,MAAM,IAAI,QAAQ,GAAG,KAAK;AACrF,QAAK,OAAO,SAAS,KAAK;AAC1B,UAAO;;;AAIX,KAAI,QAAQ,QACV,MAAK,MAAM,CAAC,MAAM,OAAO,OAAO,QAAQ,OAAO,QAAQ,EAAE;EACvD,MAAM,OAAO;AACb,QAAM,QAAQ,eAAgB,MAAY,GAAG,MAAa;GACxD,MAAM,SAAS,KAAK,aAAa;GACjC,IAAI;AACJ,OAAI,WAAW,OACb,QAAO,SAAS,SAAY,MAAM,MAAM,GAAG,MAAM,KAAK,MAAM,GAAG,KAAK;OAEpE,QAAO,SAAS,SAAY,MAAM,KAAK,OAAO,GAAG,MAAM,KAAK,QAAQ,MAAM,GAAG,KAAK;AAEpF,qBAAkB,KAAK,QAAQ,KAAK,CAAC;AACrC,UAAO;;;AAKb,QAAO;;AAaT,SAAgB,UACd,QACA,QACK;AACL,QAAO,iBAAiB,QAAQ,OAAO;;AAoEzC,SAAgB,eACd,QACA,eACA,QACK;CACL,MAAM,aAAa,iBAAiB,QAAQ,OAAO;AACnD,YAAW,gBAAgB;CAE3B,MAAM,QAAQ,WAAW;AACzB,OAAM,KAAK,SAAU,OAAyB;AAC5C,SAAQ,KAAa,mBAAmB;;AAE1C,OAAM,KAAK,SAAU,OAAyB;AAC5C,SAAQ,KAAa,mBAAmB,QAAQ,OAAO;;AAGzD,QAAO;;;;;AC5ST,SAAgB,UACd,QACA,QAC4B;CAG5B,MAAM,MAAM;EACV,AAAiB;EAEjB;EAEA,cAAc;GACZ,MAAM,eAAe,QAAQ;AAC7B,QAAK,aAAa,gBACb,SAAY,aAAa,KAAK,MAAM,MAAM,KAAK,IAC/C,SAAY;AAEjB,kBAAmC,MAAM;IACvC,YAAY;IACZ,QAAQ;IACT,CAAC;AAEF,OAAI,QAAQ,OACV,MAAK,MAAM,oBAAoB,YAAY;AAEzC,YADc,MAAO,OAAO,QAA6C,EAC5D,KAAK,SAAS,KAAK,WAAW,KAAK,CAAC;KACjD;;EAIN,OAAO,OAAkB;AACvB,QAAK,KAAK,OAAO,SAAS,MAAM;;;CAIpC,MAAM,QAAQ,MAAM;AAEpB,KAAI,QAAQ,KAAK;EACf,MAAM,MAAM,OAAO;AACnB,QAAM,MAAM,SAAU,GAAG,MAAa;AACpC,UAAO,IAAI,GAAG,KAAK,CAAC,MAAM,SAAY,KAAK,WAAW,KAAK,CAAC;;;AAIhE,KAAI,QAAQ,OACV,OAAM,SAAS,WAAY;AACzB,SAAO,KAAK,IAAI,WAAW;;AAI/B,KAAI,QAAQ,QAAQ;EAClB,MAAM,SAAS,OAAO;AACtB,QAAM,SAAS,eAAgB,GAAG,MAAa;GAC7C,MAAM,OAAO,MAAM,OAAO,GAAG,KAAK;AAClC,UAAO,kBAAkB;IACvB,MAAM,QAAQ,KAAK,WAAW,KAAK;AACnC,QAAI,KAAK,IAAK,MAAK,IAAI,MAAM,QAAQ,MAAM;AAC3C,WAAO;KACP;;;AAIN,QAAO"}
|