@pyreon/feature 0.0.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/lib/index.js ADDED
@@ -0,0 +1,464 @@
1
+ import { batch, signal } from "@pyreon/reactivity";
2
+ import { useForm } from "@pyreon/form";
3
+ import { zodSchema } from "@pyreon/validation";
4
+ import { useMutation, useQuery, useQueryClient } from "@pyreon/query";
5
+ import { getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useTable } from "@pyreon/table";
6
+ import { defineStore } from "@pyreon/store";
7
+
8
+ //#region src/schema.ts
9
+ /** Symbol used to tag reference schema objects. */
10
+ const REFERENCE_TAG = Symbol.for("pyreon:feature:reference");
11
+ /**
12
+ * Check if a value is a reference schema created by `reference()`.
13
+ */
14
+ function isReference(value) {
15
+ return value !== null && typeof value === "object" && value[REFERENCE_TAG] === true;
16
+ }
17
+ /**
18
+ * Create a reference field that links to another feature.
19
+ *
20
+ * Returns a Zod-compatible schema that validates as `string | number` and
21
+ * carries metadata about the referenced feature for form dropdowns and table links.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * import { defineFeature, reference } from '@pyreon/feature'
26
+ *
27
+ * const posts = defineFeature({
28
+ * name: 'posts',
29
+ * schema: z.object({
30
+ * title: z.string(),
31
+ * authorId: reference(users),
32
+ * }),
33
+ * api: '/api/posts',
34
+ * })
35
+ * ```
36
+ */
37
+ function reference(feature) {
38
+ const featureName = feature.name;
39
+ function validateRef(value) {
40
+ if (typeof value === "string" || typeof value === "number") return { success: true };
41
+ return {
42
+ success: false,
43
+ error: { issues: [{ message: `Expected string or number reference to ${featureName}, got ${typeof value}` }] }
44
+ };
45
+ }
46
+ return {
47
+ [REFERENCE_TAG]: true,
48
+ _featureName: featureName,
49
+ safeParse: validateRef,
50
+ safeParseAsync: async (value) => validateRef(value),
51
+ _def: { typeName: "ZodString" }
52
+ };
53
+ }
54
+ /**
55
+ * Convert a field name to a human-readable label.
56
+ * e.g., 'firstName' → 'First Name', 'created_at' → 'Created At'
57
+ */
58
+ function nameToLabel(name) {
59
+ return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
60
+ }
61
+ /**
62
+ * Detect the field type from a Zod schema shape entry.
63
+ * Duck-typed — works with Zod v3 and v4 without importing Zod.
64
+ */
65
+ function detectFieldType(zodField) {
66
+ if (isReference(zodField)) return {
67
+ type: "reference",
68
+ optional: false,
69
+ referenceTo: zodField._featureName
70
+ };
71
+ if (!zodField || typeof zodField !== "object") return {
72
+ type: "unknown",
73
+ optional: false
74
+ };
75
+ let inner = zodField;
76
+ let optional = false;
77
+ const getTypeName = (obj) => {
78
+ const def = obj._def;
79
+ if (def?.typeName && typeof def.typeName === "string") return def.typeName;
80
+ const zodDef = obj._zod?.def;
81
+ if (zodDef?.type && typeof zodDef.type === "string") return zodDef.type;
82
+ };
83
+ const typeName = getTypeName(inner);
84
+ if (typeName === "ZodOptional" || typeName === "ZodNullable" || typeName === "optional" || typeName === "nullable") {
85
+ optional = true;
86
+ const innerType = inner._def?.innerType ?? inner._zod?.def;
87
+ if (innerType && typeof innerType === "object") inner = innerType;
88
+ }
89
+ const innerTypeName = getTypeName(inner) ?? typeName;
90
+ if (!innerTypeName) return {
91
+ type: "unknown",
92
+ optional
93
+ };
94
+ const type = {
95
+ ZodString: "string",
96
+ ZodNumber: "number",
97
+ ZodBoolean: "boolean",
98
+ ZodDate: "date",
99
+ ZodEnum: "enum",
100
+ ZodNativeEnum: "enum",
101
+ ZodArray: "array",
102
+ ZodObject: "object",
103
+ string: "string",
104
+ number: "number",
105
+ boolean: "boolean",
106
+ date: "date",
107
+ enum: "enum",
108
+ array: "array",
109
+ object: "object"
110
+ }[innerTypeName] ?? "string";
111
+ let enumValues;
112
+ if (type === "enum") {
113
+ const def = inner._def;
114
+ if (def?.values && Array.isArray(def.values)) enumValues = def.values;
115
+ const zodDef = inner._zod?.def;
116
+ if (zodDef?.values && Array.isArray(zodDef.values)) enumValues = zodDef.values;
117
+ }
118
+ return {
119
+ type,
120
+ optional,
121
+ enumValues
122
+ };
123
+ }
124
+ /**
125
+ * Extract field information from a Zod object schema.
126
+ * Returns an array of FieldInfo objects describing each field.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * const schema = z.object({ name: z.string(), age: z.number().optional() })
131
+ * const fields = extractFields(schema)
132
+ * // [
133
+ * // { name: 'name', type: 'string', optional: false, label: 'Name' },
134
+ * // { name: 'age', type: 'number', optional: true, label: 'Age' },
135
+ * // ]
136
+ * ```
137
+ */
138
+ function extractFields(schema) {
139
+ if (!schema || typeof schema !== "object") return [];
140
+ const s = schema;
141
+ let shape;
142
+ if (s.shape && typeof s.shape === "object") shape = s.shape;
143
+ if (!shape) {
144
+ const def = s._def;
145
+ if (def?.shape) shape = typeof def.shape === "function" ? def.shape() : def.shape;
146
+ }
147
+ if (!shape) {
148
+ const zodDef = s._zod?.def;
149
+ if (zodDef?.shape && typeof zodDef.shape === "object") shape = zodDef.shape;
150
+ }
151
+ if (!shape) return [];
152
+ return Object.entries(shape).map(([name, fieldSchema]) => {
153
+ const { type, optional, enumValues, referenceTo } = detectFieldType(fieldSchema);
154
+ const info = {
155
+ name,
156
+ type,
157
+ optional,
158
+ label: nameToLabel(name)
159
+ };
160
+ if (enumValues) info.enumValues = enumValues;
161
+ if (referenceTo) info.referenceTo = referenceTo;
162
+ return info;
163
+ });
164
+ }
165
+ /**
166
+ * Generate default initial values from a schema's field types.
167
+ */
168
+ function defaultInitialValues(fields) {
169
+ const values = {};
170
+ for (const field of fields) switch (field.type) {
171
+ case "string":
172
+ values[field.name] = "";
173
+ break;
174
+ case "number":
175
+ values[field.name] = 0;
176
+ break;
177
+ case "boolean":
178
+ values[field.name] = false;
179
+ break;
180
+ case "enum":
181
+ values[field.name] = field.enumValues?.[0] ?? "";
182
+ break;
183
+ case "date":
184
+ values[field.name] = "";
185
+ break;
186
+ default: values[field.name] = "";
187
+ }
188
+ return values;
189
+ }
190
+
191
+ //#endregion
192
+ //#region src/define-feature.ts
193
+ function createFetcher(baseFetcher = fetch) {
194
+ async function request(url, init) {
195
+ const res = await baseFetcher(url, init);
196
+ if (!res.ok) {
197
+ let message = `${init?.method ?? "GET"} ${url} failed: ${res.status}`;
198
+ try {
199
+ const body = await res.json();
200
+ if (body?.message) message = body.message;
201
+ if (body?.errors) throw Object.assign(new Error(message), {
202
+ status: res.status,
203
+ errors: body.errors
204
+ });
205
+ } catch (e) {
206
+ if (e instanceof Error && "errors" in e) throw e;
207
+ }
208
+ throw Object.assign(new Error(message), { status: res.status });
209
+ }
210
+ if (res.status === 204) return void 0;
211
+ return res.json();
212
+ }
213
+ return {
214
+ list(url, params) {
215
+ return request(`${url}${params ? `?${new URLSearchParams(Object.entries(params).map(([k, v]) => [k, String(v)])).toString()}` : ""}`);
216
+ },
217
+ getById(url, id) {
218
+ return request(`${url}/${id}`);
219
+ },
220
+ create(url, data) {
221
+ return request(url, {
222
+ method: "POST",
223
+ headers: { "Content-Type": "application/json" },
224
+ body: JSON.stringify(data)
225
+ });
226
+ },
227
+ update(url, id, data) {
228
+ return request(`${url}/${id}`, {
229
+ method: "PUT",
230
+ headers: { "Content-Type": "application/json" },
231
+ body: JSON.stringify(data)
232
+ });
233
+ },
234
+ delete(url, id) {
235
+ return request(`${url}/${id}`, { method: "DELETE" });
236
+ }
237
+ };
238
+ }
239
+ function createValidator(schema, customValidate) {
240
+ if (customValidate) return customValidate;
241
+ if (schema && typeof schema === "object" && "safeParseAsync" in schema && typeof schema.safeParseAsync === "function") return zodSchema(schema);
242
+ }
243
+ function resolvePageValue(page) {
244
+ if (page === void 0) return void 0;
245
+ if (typeof page === "function") return page();
246
+ return page;
247
+ }
248
+ /**
249
+ * Define a schema-driven feature with auto-generated CRUD hooks.
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * import { defineFeature } from '@pyreon/feature'
254
+ * import { z } from 'zod'
255
+ *
256
+ * const users = defineFeature({
257
+ * name: 'users',
258
+ * schema: z.object({
259
+ * name: z.string().min(2),
260
+ * email: z.string().email(),
261
+ * role: z.enum(['admin', 'editor', 'viewer']),
262
+ * }),
263
+ * api: '/api/users',
264
+ * })
265
+ * ```
266
+ */
267
+ function defineFeature(config) {
268
+ const { name, schema, api, fetcher: customFetcher } = config;
269
+ const http = createFetcher(customFetcher);
270
+ const fields = extractFields(schema);
271
+ const autoInitialValues = defaultInitialValues(fields);
272
+ const initialValues = config.initialValues ? {
273
+ ...autoInitialValues,
274
+ ...config.initialValues
275
+ } : autoInitialValues;
276
+ const validate = createValidator(schema, config.validate);
277
+ const queryKeyBase = [name];
278
+ const queryKey = (suffix) => suffix !== void 0 ? [name, suffix] : [name];
279
+ return {
280
+ name,
281
+ api,
282
+ schema,
283
+ fields,
284
+ queryKey,
285
+ useStore: defineStore(name, () => {
286
+ const items = signal([]);
287
+ const selected = signal(null);
288
+ const loading = signal(false);
289
+ const select = (id) => {
290
+ const found = items.peek().find((item) => {
291
+ return item.id === id;
292
+ });
293
+ selected.set(found ?? null);
294
+ };
295
+ const clear = () => {
296
+ selected.set(null);
297
+ };
298
+ return {
299
+ items,
300
+ selected,
301
+ loading,
302
+ select,
303
+ clear
304
+ };
305
+ }),
306
+ useList(options) {
307
+ return useQuery(() => {
308
+ const pageValue = resolvePageValue(options?.page);
309
+ const pageSize = options?.pageSize ?? 20;
310
+ const params = { ...options?.params ?? {} };
311
+ if (pageValue !== void 0) {
312
+ params.page = pageValue;
313
+ params.pageSize = pageSize;
314
+ }
315
+ return {
316
+ queryKey: [
317
+ ...queryKeyBase,
318
+ "list",
319
+ params
320
+ ],
321
+ queryFn: () => http.list(api, Object.keys(params).length > 0 ? params : void 0),
322
+ staleTime: options?.staleTime,
323
+ enabled: options?.enabled
324
+ };
325
+ });
326
+ },
327
+ useById(id) {
328
+ return useQuery(() => ({
329
+ queryKey: [name, id],
330
+ queryFn: () => http.getById(api, id),
331
+ enabled: id !== void 0 && id !== null
332
+ }));
333
+ },
334
+ useSearch(searchTerm, options) {
335
+ return useQuery(() => ({
336
+ queryKey: [
337
+ ...queryKeyBase,
338
+ "search",
339
+ searchTerm()
340
+ ],
341
+ queryFn: () => http.list(api, {
342
+ ...options?.params,
343
+ q: searchTerm()
344
+ }),
345
+ enabled: searchTerm().length > 0,
346
+ staleTime: options?.staleTime
347
+ }));
348
+ },
349
+ useCreate() {
350
+ const client = useQueryClient();
351
+ return useMutation({
352
+ mutationFn: (data) => http.create(api, data),
353
+ onSuccess: () => {
354
+ client.invalidateQueries({ queryKey: queryKeyBase });
355
+ }
356
+ });
357
+ },
358
+ useUpdate() {
359
+ const client = useQueryClient();
360
+ return useMutation({
361
+ mutationFn: ({ id, data }) => http.update(api, id, data),
362
+ onMutate: async (variables) => {
363
+ await client.cancelQueries({ queryKey: [name, variables.id] });
364
+ const previous = client.getQueryData([name, variables.id]);
365
+ client.setQueryData([name, variables.id], (old) => {
366
+ if (old && typeof old === "object") return {
367
+ ...old,
368
+ ...variables.data
369
+ };
370
+ return variables.data;
371
+ });
372
+ return { previous };
373
+ },
374
+ onError: (_err, variables, context) => {
375
+ if (context?.previous) client.setQueryData([name, variables.id], context.previous);
376
+ },
377
+ onSuccess: (_data, variables) => {
378
+ client.invalidateQueries({ queryKey: queryKeyBase });
379
+ client.invalidateQueries({ queryKey: [name, variables.id] });
380
+ }
381
+ });
382
+ },
383
+ useDelete() {
384
+ const client = useQueryClient();
385
+ return useMutation({
386
+ mutationFn: (id) => http.delete(api, id),
387
+ onSuccess: () => {
388
+ client.invalidateQueries({ queryKey: queryKeyBase });
389
+ }
390
+ });
391
+ },
392
+ useForm(options) {
393
+ const mode = options?.mode ?? "create";
394
+ const form = useForm({
395
+ initialValues: {
396
+ ...initialValues,
397
+ ...options?.initialValues ?? {}
398
+ },
399
+ schema: validate,
400
+ validateOn: options?.validateOn ?? "blur",
401
+ onSubmit: async (values) => {
402
+ try {
403
+ let result;
404
+ if (mode === "edit" && options?.id !== void 0) result = await http.update(api, options.id, values);
405
+ else result = await http.create(api, values);
406
+ options?.onSuccess?.(result);
407
+ } catch (err) {
408
+ options?.onError?.(err);
409
+ throw err;
410
+ }
411
+ }
412
+ });
413
+ if (mode === "edit" && options?.id !== void 0) {
414
+ form.isSubmitting.set(true);
415
+ http.getById(api, options.id).then((data) => {
416
+ batch(() => {
417
+ for (const key of Object.keys(data)) form.setFieldValue(key, data[key]);
418
+ form.isSubmitting.set(false);
419
+ });
420
+ }, () => {
421
+ form.isSubmitting.set(false);
422
+ });
423
+ }
424
+ return form;
425
+ },
426
+ useTable(data, options) {
427
+ const visibleFields = options?.columns ? fields.filter((f) => options.columns.includes(f.name)) : fields;
428
+ const columns = visibleFields.map((field) => ({
429
+ accessorKey: field.name,
430
+ header: field.label,
431
+ ...options?.columnOverrides?.[field.name] ?? {}
432
+ }));
433
+ const sorting = signal([]);
434
+ const globalFilter = signal("");
435
+ return {
436
+ table: useTable(() => ({
437
+ data: typeof data === "function" ? data() : data,
438
+ columns,
439
+ state: {
440
+ sorting: sorting(),
441
+ globalFilter: globalFilter()
442
+ },
443
+ onSortingChange: (updater) => {
444
+ sorting.set(typeof updater === "function" ? updater(sorting()) : updater);
445
+ },
446
+ onGlobalFilterChange: (updater) => {
447
+ globalFilter.set(typeof updater === "function" ? updater(globalFilter()) : updater);
448
+ },
449
+ getCoreRowModel: getCoreRowModel(),
450
+ getSortedRowModel: getSortedRowModel(),
451
+ getFilteredRowModel: getFilteredRowModel(),
452
+ ...options?.pageSize ? { getPaginationRowModel: getPaginationRowModel() } : {}
453
+ })),
454
+ sorting,
455
+ globalFilter,
456
+ columns: visibleFields
457
+ };
458
+ }
459
+ };
460
+ }
461
+
462
+ //#endregion
463
+ export { defaultInitialValues, defineFeature, extractFields, isReference, reference };
464
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["_useQuery","_useMutation","_useForm","_useTable"],"sources":["../src/schema.ts","../src/define-feature.ts"],"sourcesContent":["/**\n * Schema introspection utilities.\n *\n * Extracts field names, types, and metadata from Zod schemas at runtime\n * without importing Zod types directly (duck-typed).\n */\n\nexport interface FieldInfo {\n /** Field name (key in the schema object). */\n name: string\n /** Inferred type: 'string' | 'number' | 'boolean' | 'date' | 'enum' | 'array' | 'object' | 'reference' | 'unknown'. */\n type: FieldType\n /** Whether the field is optional. */\n optional: boolean\n /** For enum fields, the list of allowed values. */\n enumValues?: (string | number)[]\n /** For reference fields, the name of the referenced feature. */\n referenceTo?: string\n /** Human-readable label derived from field name. */\n label: string\n}\n\nexport type FieldType =\n | 'string'\n | 'number'\n | 'boolean'\n | 'date'\n | 'enum'\n | 'array'\n | 'object'\n | 'reference'\n | 'unknown'\n\n/** Symbol used to tag reference schema objects. */\nconst REFERENCE_TAG = Symbol.for('pyreon:feature:reference')\n\n/**\n * Metadata carried by a reference schema.\n */\nexport interface ReferenceSchema {\n /** Marker symbol for detection. */\n [key: symbol]: true\n /** Name of the referenced feature. */\n _featureName: string\n /** Duck-typed Zod-like interface: validates as string | number. */\n safeParse: (value: unknown) => {\n success: boolean\n error?: { issues: { message: string }[] }\n }\n /** Async variant for compatibility. */\n safeParseAsync: (\n value: unknown,\n ) => Promise<{ success: boolean; error?: { issues: { message: string }[] } }>\n /** Shape-like marker for schema introspection. */\n _def: { typeName: string }\n}\n\n/**\n * Check if a value is a reference schema created by `reference()`.\n */\nexport function isReference(value: unknown): value is ReferenceSchema {\n return (\n value !== null &&\n typeof value === 'object' &&\n (value as Record<symbol, unknown>)[REFERENCE_TAG] === true\n )\n}\n\n/**\n * Create a reference field that links to another feature.\n *\n * Returns a Zod-compatible schema that validates as `string | number` and\n * carries metadata about the referenced feature for form dropdowns and table links.\n *\n * @example\n * ```ts\n * import { defineFeature, reference } from '@pyreon/feature'\n *\n * const posts = defineFeature({\n * name: 'posts',\n * schema: z.object({\n * title: z.string(),\n * authorId: reference(users),\n * }),\n * api: '/api/posts',\n * })\n * ```\n */\nexport function reference(feature: { name: string }): ReferenceSchema {\n const featureName = feature.name\n\n function validateRef(value: unknown): {\n success: boolean\n error?: { issues: { message: string }[] }\n } {\n if (typeof value === 'string' || typeof value === 'number') {\n return { success: true }\n }\n return {\n success: false,\n error: {\n issues: [\n {\n message: `Expected string or number reference to ${featureName}, got ${typeof value}`,\n },\n ],\n },\n }\n }\n\n return {\n [REFERENCE_TAG]: true,\n _featureName: featureName,\n safeParse: validateRef,\n safeParseAsync: async (value: unknown) => validateRef(value),\n _def: { typeName: 'ZodString' },\n }\n}\n\n/**\n * Convert a field name to a human-readable label.\n * e.g., 'firstName' → 'First Name', 'created_at' → 'Created At'\n */\nfunction nameToLabel(name: string): string {\n return name\n .replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase → camel Case\n .replace(/[_-]/g, ' ') // snake_case/kebab-case → spaces\n .replace(/\\b\\w/g, (c) => c.toUpperCase()) // capitalize words\n}\n\n/**\n * Detect the field type from a Zod schema shape entry.\n * Duck-typed — works with Zod v3 and v4 without importing Zod.\n */\nfunction detectFieldType(zodField: unknown): {\n type: FieldType\n optional: boolean\n enumValues?: (string | number)[]\n referenceTo?: string\n} {\n // Check for reference fields first\n if (isReference(zodField)) {\n return {\n type: 'reference',\n optional: false,\n referenceTo: zodField._featureName,\n }\n }\n\n if (!zodField || typeof zodField !== 'object') {\n return { type: 'unknown', optional: false }\n }\n\n const field = zodField as Record<string, unknown>\n\n // Check for optional wrapper (ZodOptional or ZodNullable)\n let inner = field\n let optional = false\n\n // Zod v3: _def.typeName, Zod v4: _zod.def.type\n const getTypeName = (obj: Record<string, unknown>): string | undefined => {\n // v3 path\n const def = obj._def as Record<string, unknown> | undefined\n if (def?.typeName && typeof def.typeName === 'string') {\n return def.typeName\n }\n // v4 path\n const zod = obj._zod as Record<string, unknown> | undefined\n const zodDef = zod?.def as Record<string, unknown> | undefined\n if (zodDef?.type && typeof zodDef.type === 'string') {\n return zodDef.type\n }\n return undefined\n }\n\n const typeName = getTypeName(inner)\n\n // Unwrap optional/nullable\n if (\n typeName === 'ZodOptional' ||\n typeName === 'ZodNullable' ||\n typeName === 'optional' ||\n typeName === 'nullable'\n ) {\n optional = true\n const def = inner._def as Record<string, unknown> | undefined\n const innerType =\n def?.innerType ?? (inner._zod as Record<string, unknown>)?.def\n if (innerType && typeof innerType === 'object') {\n inner = innerType as Record<string, unknown>\n }\n }\n\n const innerTypeName = getTypeName(inner) ?? typeName\n\n // Map Zod type names to our FieldType\n if (!innerTypeName) return { type: 'unknown', optional }\n\n const typeMap: Record<string, FieldType> = {\n ZodString: 'string',\n ZodNumber: 'number',\n ZodBoolean: 'boolean',\n ZodDate: 'date',\n ZodEnum: 'enum',\n ZodNativeEnum: 'enum',\n ZodArray: 'array',\n ZodObject: 'object',\n // v4 names\n string: 'string',\n number: 'number',\n boolean: 'boolean',\n date: 'date',\n enum: 'enum',\n array: 'array',\n object: 'object',\n }\n\n const type = typeMap[innerTypeName] ?? 'string'\n\n // Extract enum values\n let enumValues: (string | number)[] | undefined\n if (type === 'enum') {\n const def = inner._def as Record<string, unknown> | undefined\n if (def?.values && Array.isArray(def.values)) {\n enumValues = def.values as (string | number)[]\n }\n // v4 path\n const zodDef = (inner._zod as Record<string, unknown>)?.def as\n | Record<string, unknown>\n | undefined\n if (zodDef?.values && Array.isArray(zodDef.values)) {\n enumValues = zodDef.values as (string | number)[]\n }\n }\n\n return { type, optional, enumValues }\n}\n\n/**\n * Extract field information from a Zod object schema.\n * Returns an array of FieldInfo objects describing each field.\n *\n * @example\n * ```ts\n * const schema = z.object({ name: z.string(), age: z.number().optional() })\n * const fields = extractFields(schema)\n * // [\n * // { name: 'name', type: 'string', optional: false, label: 'Name' },\n * // { name: 'age', type: 'number', optional: true, label: 'Age' },\n * // ]\n * ```\n */\nexport function extractFields(schema: unknown): FieldInfo[] {\n if (!schema || typeof schema !== 'object') return []\n\n const s = schema as Record<string, unknown>\n\n // Get the shape object from the schema\n // Zod v3: schema._def.shape() or schema.shape\n // Zod v4: schema._zod.def.shape or schema.shape\n let shape: Record<string, unknown> | undefined\n\n // Try schema.shape (works for both v3 and v4)\n if (s.shape && typeof s.shape === 'object') {\n shape = s.shape as Record<string, unknown>\n }\n\n // Try _def.shape (v3 — can be a function)\n if (!shape) {\n const def = s._def as Record<string, unknown> | undefined\n if (def?.shape) {\n shape =\n typeof def.shape === 'function'\n ? (def.shape as () => Record<string, unknown>)()\n : (def.shape as Record<string, unknown>)\n }\n }\n\n // Try _zod.def.shape (v4)\n if (!shape) {\n const zod = s._zod as Record<string, unknown> | undefined\n const zodDef = zod?.def as Record<string, unknown> | undefined\n if (zodDef?.shape && typeof zodDef.shape === 'object') {\n shape = zodDef.shape as Record<string, unknown>\n }\n }\n\n if (!shape) return []\n\n return Object.entries(shape).map(([name, fieldSchema]) => {\n const { type, optional, enumValues, referenceTo } =\n detectFieldType(fieldSchema)\n const info: FieldInfo = {\n name,\n type,\n optional,\n label: nameToLabel(name),\n }\n if (enumValues) info.enumValues = enumValues\n if (referenceTo) info.referenceTo = referenceTo\n return info\n })\n}\n\n/**\n * Generate default initial values from a schema's field types.\n */\nexport function defaultInitialValues(\n fields: FieldInfo[],\n): Record<string, unknown> {\n const values: Record<string, unknown> = {}\n for (const field of fields) {\n switch (field.type) {\n case 'string':\n values[field.name] = ''\n break\n case 'number':\n values[field.name] = 0\n break\n case 'boolean':\n values[field.name] = false\n break\n case 'enum':\n values[field.name] = field.enumValues?.[0] ?? ''\n break\n case 'date':\n values[field.name] = ''\n break\n default:\n values[field.name] = ''\n }\n }\n return values\n}\n","import { signal, batch } from '@pyreon/reactivity'\nimport { useForm as _useForm } from '@pyreon/form'\nimport type { SchemaValidateFn } from '@pyreon/form'\nimport { zodSchema } from '@pyreon/validation'\nimport {\n useQuery as _useQuery,\n useMutation as _useMutation,\n useQueryClient,\n} from '@pyreon/query'\nimport type { QueryKey } from '@pyreon/query'\nimport {\n useTable as _useTable,\n getCoreRowModel,\n getSortedRowModel,\n getFilteredRowModel,\n getPaginationRowModel,\n} from '@pyreon/table'\nimport type { ColumnDef, SortingState } from '@pyreon/table'\nimport { defineStore } from '@pyreon/store'\nimport { extractFields, defaultInitialValues } from './schema'\nimport type {\n Feature,\n FeatureConfig,\n FeatureFormOptions,\n FeatureStore,\n FeatureTableOptions,\n ListOptions,\n} from './types'\n\n// ─── Fetch wrapper ────────────────────────────────────────────────────────────\n\nfunction createFetcher(baseFetcher: typeof fetch = fetch) {\n async function request<T>(url: string, init?: RequestInit): Promise<T> {\n const res = await baseFetcher(url, init)\n\n if (!res.ok) {\n let message = `${init?.method ?? 'GET'} ${url} failed: ${res.status}`\n try {\n const body = await res.json()\n if (body?.message) message = body.message\n if (body?.errors) {\n throw Object.assign(new Error(message), {\n status: res.status,\n errors: body.errors,\n })\n }\n } catch (e) {\n if (e instanceof Error && 'errors' in e) throw e\n }\n throw Object.assign(new Error(message), { status: res.status })\n }\n\n if (res.status === 204) return undefined as T\n return res.json()\n }\n\n return {\n list<T>(\n url: string,\n params?: Record<string, string | number | boolean>,\n ): Promise<T[]> {\n const query = params\n ? `?${new URLSearchParams(Object.entries(params).map(([k, v]) => [k, String(v)])).toString()}`\n : ''\n return request<T[]>(`${url}${query}`)\n },\n getById<T>(url: string, id: string | number): Promise<T> {\n return request<T>(`${url}/${id}`)\n },\n create<T>(url: string, data: unknown): Promise<T> {\n return request<T>(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(data),\n })\n },\n update<T>(url: string, id: string | number, data: unknown): Promise<T> {\n return request<T>(`${url}/${id}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(data),\n })\n },\n delete(url: string, id: string | number): Promise<void> {\n return request<void>(`${url}/${id}`, { method: 'DELETE' })\n },\n }\n}\n\n// ─── Schema validation ────────────────────────────────────────────────────────\n\nfunction createValidator<TValues extends Record<string, unknown>>(\n schema: unknown,\n customValidate?: SchemaValidateFn<TValues>,\n): SchemaValidateFn<TValues> | undefined {\n if (customValidate) return customValidate\n\n if (\n schema &&\n typeof schema === 'object' &&\n 'safeParseAsync' in schema &&\n typeof (schema as Record<string, unknown>).safeParseAsync === 'function'\n ) {\n return zodSchema(\n schema as Parameters<typeof zodSchema>[0],\n ) as SchemaValidateFn<TValues>\n }\n\n return undefined\n}\n\n// ─── Resolve page value ───────────────────────────────────────────────────────\n\nfunction resolvePageValue(\n page: number | (() => number) | undefined,\n): number | undefined {\n if (page === undefined) return undefined\n if (typeof page === 'function') return page()\n return page\n}\n\n// ─── defineFeature ────────────────────────────────────────────────────────────\n\n/**\n * Define a schema-driven feature with auto-generated CRUD hooks.\n *\n * @example\n * ```ts\n * import { defineFeature } from '@pyreon/feature'\n * import { z } from 'zod'\n *\n * const users = defineFeature({\n * name: 'users',\n * schema: z.object({\n * name: z.string().min(2),\n * email: z.string().email(),\n * role: z.enum(['admin', 'editor', 'viewer']),\n * }),\n * api: '/api/users',\n * })\n * ```\n */\nexport function defineFeature<TValues extends Record<string, unknown>>(\n config: FeatureConfig<TValues>,\n): Feature<TValues> {\n const { name, schema, api, fetcher: customFetcher } = config\n const http = createFetcher(customFetcher)\n\n // Introspect schema fields\n const fields = extractFields(schema)\n const autoInitialValues = defaultInitialValues(fields) as TValues\n const initialValues = config.initialValues\n ? { ...autoInitialValues, ...config.initialValues }\n : autoInitialValues\n\n const validate = createValidator<TValues>(schema, config.validate)\n\n const queryKeyBase = [name] as const\n const queryKey = (suffix?: string | number): QueryKey =>\n suffix !== undefined ? [name, suffix] : [name]\n\n // ─── Store definition ──────────────────────────────────────────────\n\n const useStoreHook = defineStore<FeatureStore<TValues>>(name, () => {\n const items = signal<TValues[]>([])\n const selected = signal<TValues | null>(null)\n const loading = signal(false)\n\n const select = (id: string | number) => {\n const found = items.peek().find((item) => {\n const record = item as Record<string, unknown>\n return record.id === id\n })\n selected.set(found ?? null)\n }\n\n const clear = () => {\n selected.set(null)\n }\n\n return { items, selected, loading, select, clear }\n })\n\n return {\n name,\n api,\n schema,\n fields,\n queryKey,\n\n // ─── Store ───────────────────────────────────────────────────────\n\n useStore: useStoreHook,\n\n // ─── Queries ────────────────────────────────────────────────────\n\n useList(options?: ListOptions) {\n return _useQuery(() => {\n const pageValue = resolvePageValue(options?.page)\n const pageSize = options?.pageSize ?? 20\n\n const params: Record<string, string | number | boolean> = {\n ...(options?.params ?? {}),\n }\n\n if (pageValue !== undefined) {\n params.page = pageValue\n params.pageSize = pageSize\n }\n\n const queryKeyParts: unknown[] = [...queryKeyBase, 'list', params]\n\n return {\n queryKey: queryKeyParts as QueryKey,\n queryFn: () =>\n http.list<TValues>(\n api,\n Object.keys(params).length > 0 ? params : undefined,\n ),\n staleTime: options?.staleTime,\n enabled: options?.enabled,\n }\n })\n },\n\n useById(id: string | number) {\n return _useQuery(() => ({\n queryKey: [name, id],\n queryFn: () => http.getById<TValues>(api, id),\n enabled: id !== undefined && id !== null,\n }))\n },\n\n useSearch(searchTerm, options?: ListOptions) {\n return _useQuery(() => ({\n queryKey: [...queryKeyBase, 'search', searchTerm()],\n queryFn: () =>\n http.list<TValues>(api, { ...options?.params, q: searchTerm() }),\n enabled: searchTerm().length > 0,\n staleTime: options?.staleTime,\n }))\n },\n\n // ─── Mutations ──────────────────────────────────────────────────\n\n useCreate() {\n const client = useQueryClient()\n return _useMutation({\n mutationFn: (data: Partial<TValues>) => http.create<TValues>(api, data),\n onSuccess: () => {\n client.invalidateQueries({\n queryKey: queryKeyBase as unknown as QueryKey,\n })\n },\n })\n },\n\n useUpdate() {\n type TVariables = { id: string | number; data: Partial<TValues> }\n const client = useQueryClient()\n return _useMutation<TValues, unknown, TVariables, { previous?: unknown }>(\n {\n mutationFn: ({ id, data }: TVariables) =>\n http.update<TValues>(api, id, data),\n onMutate: async (variables) => {\n await client.cancelQueries({ queryKey: [name, variables.id] })\n const previous = client.getQueryData([name, variables.id])\n client.setQueryData([name, variables.id], (old: unknown) => {\n if (old && typeof old === 'object') {\n return { ...old, ...variables.data }\n }\n return variables.data\n })\n return { previous }\n },\n onError: (_err, variables, context) => {\n if (context?.previous) {\n client.setQueryData([name, variables.id], context.previous)\n }\n },\n onSuccess: (_data, variables) => {\n client.invalidateQueries({\n queryKey: queryKeyBase as unknown as QueryKey,\n })\n client.invalidateQueries({ queryKey: [name, variables.id] })\n },\n },\n ) as ReturnType<Feature<TValues>['useUpdate']>\n },\n\n useDelete() {\n const client = useQueryClient()\n return _useMutation({\n mutationFn: (id: string | number) => http.delete(api, id),\n onSuccess: () => {\n client.invalidateQueries({\n queryKey: queryKeyBase as unknown as QueryKey,\n })\n },\n })\n },\n\n // ─── Form ───────────────────────────────────────────────────────\n\n useForm(options?: FeatureFormOptions<TValues>) {\n const mode = options?.mode ?? 'create'\n const mergedInitial = {\n ...initialValues,\n ...(options?.initialValues ?? {}),\n } as TValues\n\n const form = _useForm<TValues>({\n initialValues: mergedInitial,\n schema: validate,\n validateOn: options?.validateOn ?? 'blur',\n onSubmit: async (values) => {\n try {\n let result: unknown\n if (mode === 'edit' && options?.id !== undefined) {\n result = await http.update<TValues>(api, options.id, values)\n } else {\n result = await http.create<TValues>(api, values)\n }\n options?.onSuccess?.(result)\n } catch (err) {\n options?.onError?.(err)\n throw err\n }\n },\n })\n\n // Auto-fetch in edit mode\n if (mode === 'edit' && options?.id !== undefined) {\n form.isSubmitting.set(true)\n http.getById<TValues>(api, options.id).then(\n (data) => {\n batch(() => {\n for (const key of Object.keys(data)) {\n form.setFieldValue(\n key as keyof TValues & string,\n (data as Record<string, unknown>)[\n key\n ] as TValues[keyof TValues],\n )\n }\n form.isSubmitting.set(false)\n })\n },\n () => {\n form.isSubmitting.set(false)\n },\n )\n }\n\n return form\n },\n\n // ─── Table ──────────────────────────────────────────────────────\n\n useTable(\n data: TValues[] | (() => TValues[]),\n options?: FeatureTableOptions<TValues>,\n ) {\n const visibleFields = options?.columns\n ? fields.filter((f) =>\n options.columns!.includes(f.name as keyof TValues & string),\n )\n : fields\n\n const columns: ColumnDef<TValues, unknown>[] = visibleFields.map(\n (field) => ({\n accessorKey: field.name,\n header: field.label,\n ...(options?.columnOverrides?.[\n field.name as keyof TValues & string\n ] ?? {}),\n }),\n )\n\n const sorting = signal<SortingState>([])\n const globalFilter = signal('')\n\n const table = _useTable(() => ({\n data: typeof data === 'function' ? data() : data,\n columns,\n state: {\n sorting: sorting(),\n globalFilter: globalFilter(),\n },\n onSortingChange: (updater: unknown) => {\n sorting.set(\n typeof updater === 'function'\n ? (updater as (prev: SortingState) => SortingState)(sorting())\n : (updater as SortingState),\n )\n },\n onGlobalFilterChange: (updater: unknown) => {\n globalFilter.set(\n typeof updater === 'function'\n ? (updater as (prev: string) => string)(globalFilter())\n : (updater as string),\n )\n },\n getCoreRowModel: getCoreRowModel(),\n getSortedRowModel: getSortedRowModel(),\n getFilteredRowModel: getFilteredRowModel(),\n ...(options?.pageSize\n ? { getPaginationRowModel: getPaginationRowModel() }\n : {}),\n }))\n\n return {\n table,\n sorting,\n globalFilter,\n columns: visibleFields,\n }\n },\n }\n}\n"],"mappings":"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"}