@omnifyjp/ts 8.0.7 → 8.1.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/README.md +3 -1
- package/dist/cli.js +22 -0
- package/dist/generator.d.ts +7 -0
- package/dist/generator.js +3 -1
- package/dist/interface-generator.js +1 -0
- package/dist/metadata-generator.js +1 -1
- package/dist/php/faker-mapper.js +2 -0
- package/dist/php/openapi-generator.js +1 -0
- package/dist/php/schema-reader.js +49 -1
- package/dist/php/type-mapper.js +3 -1
- package/dist/ts-hooks-generator.d.ts +9 -1
- package/dist/ts-hooks-generator.js +5 -7
- package/dist/zod-generator.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,7 +26,9 @@ Per model with `options.service` or `options.api` (in `<output>/base/`):
|
|
|
26
26
|
- **`<Model>QueryKeys.ts`** — TanStack Query key factory per model
|
|
27
27
|
- **`<Model>Hooks.ts`** — React Query hooks (`useList`, `useDetail`,
|
|
28
28
|
`useCreate`, `useUpdate`, `useDelete`, `useRestore`) with auto-invalidation
|
|
29
|
-
and toast notifications via `sonner`
|
|
29
|
+
and toast notifications via `sonner` (configurable with `hooks.toastImport`,
|
|
30
|
+
or skipped with `hooks.enable: false` — see
|
|
31
|
+
[`docs/reference/typescript-hooks.md`](../../docs/reference/typescript-hooks.md))
|
|
30
32
|
|
|
31
33
|
Plus shared infrastructure at the codegen output root:
|
|
32
34
|
|
package/dist/cli.js
CHANGED
|
@@ -113,6 +113,7 @@ function resolveFromConfig(configPath) {
|
|
|
113
113
|
tsPlatform: tsConfig?.platform,
|
|
114
114
|
tsAuth: tsConfig?.auth,
|
|
115
115
|
tsEnumStyle: tsConfig?.enumStyle,
|
|
116
|
+
tsHooks: tsConfig?.hooks,
|
|
116
117
|
laravelEnabled,
|
|
117
118
|
laravelOverrides,
|
|
118
119
|
};
|
|
@@ -125,6 +126,11 @@ program
|
|
|
125
126
|
.option('-i, --input <spec>', 'schemas.json source: local path, http(s) URL, or npm package spec (overrides config)')
|
|
126
127
|
.option('-o, --output <path>', 'Output directory for TypeScript (overrides config)')
|
|
127
128
|
.option('--force', 'Force regeneration of auto-generated files', false)
|
|
129
|
+
.option('--platform <platform>', 'TypeScript target platform with --input/--output: web | expo')
|
|
130
|
+
.option('--auth <auth>', 'Auth strategy with --input/--output: cookie | secureStore')
|
|
131
|
+
.option('--enum-style <style>', 'Enum emission style with --input/--output: enum | const')
|
|
132
|
+
.option('--toast-import <module>', 'Module the generated hooks import `toast` from (default "sonner")')
|
|
133
|
+
.option('--no-hooks', 'Skip generating {Entity}Hooks.ts')
|
|
128
134
|
.option('--frozen-lockfile', 'Fail if remote input differs from .omnify/input.lock.json (CI mode)', false)
|
|
129
135
|
.option('--update', 'Force re-fetch of remote input and refresh the lockfile', false)
|
|
130
136
|
.action(async (opts) => {
|
|
@@ -134,6 +140,7 @@ program
|
|
|
134
140
|
let tsPlatform;
|
|
135
141
|
let tsAuth;
|
|
136
142
|
let tsEnumStyle;
|
|
143
|
+
let tsHooks;
|
|
137
144
|
let laravelEnabled = false;
|
|
138
145
|
let laravelOverrides;
|
|
139
146
|
let configDir = process.cwd();
|
|
@@ -142,6 +149,19 @@ program
|
|
|
142
149
|
// passed through unchanged so URLs/npm specs work too.
|
|
143
150
|
inputSpec = opts.input;
|
|
144
151
|
tsOutput = resolve(opts.output);
|
|
152
|
+
// The workspace dispatcher calls this mode once per TypeScript target
|
|
153
|
+
// and has no config file to point at, so the target's options arrive
|
|
154
|
+
// as flags. Before these flags existed every workspace target silently
|
|
155
|
+
// generated with the web/cookie/enum/"sonner" defaults.
|
|
156
|
+
tsPlatform = opts.platform;
|
|
157
|
+
tsAuth = opts.auth;
|
|
158
|
+
tsEnumStyle = opts.enumStyle;
|
|
159
|
+
if (opts.toastImport !== undefined || opts.hooks === false) {
|
|
160
|
+
tsHooks = {
|
|
161
|
+
...(opts.hooks === false ? { enable: false } : {}),
|
|
162
|
+
...(opts.toastImport !== undefined ? { toastImport: opts.toastImport } : {}),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
145
165
|
}
|
|
146
166
|
else {
|
|
147
167
|
// Read from omnify.yaml
|
|
@@ -159,6 +179,7 @@ program
|
|
|
159
179
|
tsPlatform = resolved.tsPlatform;
|
|
160
180
|
tsAuth = resolved.tsAuth;
|
|
161
181
|
tsEnumStyle = resolved.tsEnumStyle;
|
|
182
|
+
tsHooks = resolved.tsHooks;
|
|
162
183
|
laravelEnabled = resolved.laravelEnabled;
|
|
163
184
|
laravelOverrides = resolved.laravelOverrides;
|
|
164
185
|
}
|
|
@@ -187,6 +208,7 @@ program
|
|
|
187
208
|
platform: tsPlatform,
|
|
188
209
|
auth: tsAuth,
|
|
189
210
|
enumStyle: tsEnumStyle,
|
|
211
|
+
hooks: tsHooks,
|
|
190
212
|
});
|
|
191
213
|
mkdirSync(join(tsOutput, 'base'), { recursive: true });
|
|
192
214
|
mkdirSync(join(tsOutput, 'enum'), { recursive: true });
|
package/dist/generator.d.ts
CHANGED
|
@@ -20,6 +20,13 @@ export interface GenerateExtraConfig {
|
|
|
20
20
|
auth?: 'cookie' | 'secureStore';
|
|
21
21
|
/** Enum emission style — see GeneratorOptions.enumStyle. Issue #103. */
|
|
22
22
|
enumStyle?: 'enum' | 'const';
|
|
23
|
+
/** Hooks layer (`{Entity}Hooks.ts`). Issue #327. */
|
|
24
|
+
hooks?: {
|
|
25
|
+
/** false skips `{Entity}Hooks.ts` entirely. Defaults to true. */
|
|
26
|
+
enable?: boolean;
|
|
27
|
+
/** Module the hooks import `toast` from. Defaults to "sonner". */
|
|
28
|
+
toastImport?: string;
|
|
29
|
+
};
|
|
23
30
|
}
|
|
24
31
|
/**
|
|
25
32
|
* Generate all TypeScript files from schemas.json input.
|
package/dist/generator.js
CHANGED
|
@@ -538,7 +538,9 @@ export function generateTypeScript(input, extra) {
|
|
|
538
538
|
// Only for schemas with options.api or options.service.
|
|
539
539
|
files.push(...generateTsServices(schemas, options));
|
|
540
540
|
files.push(...generateTsQueryKeys(schemas));
|
|
541
|
-
|
|
541
|
+
if (extra?.hooks?.enable !== false) {
|
|
542
|
+
files.push(...generateTsHooks(schemas, { toastImport: extra?.hooks?.toastImport }));
|
|
543
|
+
}
|
|
542
544
|
// React Native / Expo query setup (issue #63)
|
|
543
545
|
if (options.platform === 'expo') {
|
|
544
546
|
files.push(generateRnQuerySetup(options.auth ?? 'secureStore'));
|
|
@@ -21,7 +21,7 @@ import { formatTsObjectKey, tsObjectLiteral } from './ts-literal.js';
|
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
const TEXT_TYPES = new Set(['Text', 'MediumText', 'LongText']);
|
|
23
23
|
const STRING_TYPES = new Set([
|
|
24
|
-
'String', 'Email', 'Password', 'Phone', 'Slug', 'Url', 'Uuid',
|
|
24
|
+
'String', 'Char', 'Email', 'Password', 'Phone', 'Slug', 'Url', 'Uuid',
|
|
25
25
|
]);
|
|
26
26
|
const NUMBER_TYPES = new Set([
|
|
27
27
|
'TinyInt', 'Int', 'BigInt', 'Float', 'Decimal',
|
package/dist/php/faker-mapper.js
CHANGED
|
@@ -13,6 +13,8 @@ export function toFaker(propName, property, schemas = {}) {
|
|
|
13
13
|
return null;
|
|
14
14
|
switch (type) {
|
|
15
15
|
case 'String': return stringFaker(propName, snakeName, property);
|
|
16
|
+
// Fixed-length: exactly `length` characters, as a CHAR column holds (#319).
|
|
17
|
+
case 'Char': return `'${snakeName}' => fake()->regexify('[A-Z]{${property['length'] ?? 255}}'),`;
|
|
16
18
|
case 'Email': return `'${snakeName}' => fake()->unique()->safeEmail(),`;
|
|
17
19
|
case 'Password': return `'${snakeName}' => bcrypt('password'),`;
|
|
18
20
|
case 'Int':
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
export class SchemaReader {
|
|
5
5
|
data;
|
|
6
6
|
constructor(data) {
|
|
7
|
-
this.data = data;
|
|
7
|
+
this.data = normalizeEnumValues(data);
|
|
8
8
|
}
|
|
9
9
|
static fromData(data) {
|
|
10
10
|
return new SchemaReader(data);
|
|
@@ -371,3 +371,51 @@ export class SchemaReader {
|
|
|
371
371
|
return fields;
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Bring enum values into the one shape every PHP generator reads (#322).
|
|
376
|
+
*
|
|
377
|
+
* The schema language accepts both forms in both places — `values: [draft]`
|
|
378
|
+
* and `values: [{value: draft, label: …}]` on a `kind: enum` schema, and the
|
|
379
|
+
* same two on an inline `enum:` property — and schemas.json carries them as
|
|
380
|
+
* written. The PHP generators were each written against one form: enum
|
|
381
|
+
* classes read `value.value` (so the README's plain-string example crashed),
|
|
382
|
+
* inline-enum rules interpolate the entry itself (so an object became
|
|
383
|
+
* `[object Object]`). Normalising here, once, keeps them from disagreeing.
|
|
384
|
+
*/
|
|
385
|
+
function normalizeEnumValues(data) {
|
|
386
|
+
if (!data.schemas)
|
|
387
|
+
return data;
|
|
388
|
+
const schemas = {};
|
|
389
|
+
for (const [name, schema] of Object.entries(data.schemas)) {
|
|
390
|
+
let next = schema;
|
|
391
|
+
const rawValues = schema.values;
|
|
392
|
+
if (Array.isArray(rawValues) && rawValues.some(v => typeof v === 'string')) {
|
|
393
|
+
next = {
|
|
394
|
+
...next,
|
|
395
|
+
values: rawValues.map(v => (typeof v === 'string' ? { value: v } : v)),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
const props = schema.properties;
|
|
399
|
+
if (props) {
|
|
400
|
+
let changed = false;
|
|
401
|
+
const nextProps = {};
|
|
402
|
+
for (const [propName, prop] of Object.entries(props)) {
|
|
403
|
+
const rawEnum = prop.enum;
|
|
404
|
+
if (Array.isArray(rawEnum) && rawEnum.some(v => typeof v !== 'string')) {
|
|
405
|
+
changed = true;
|
|
406
|
+
nextProps[propName] = {
|
|
407
|
+
...prop,
|
|
408
|
+
enum: rawEnum.map(v => (typeof v === 'string' ? v : String(v?.value ?? ''))),
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
nextProps[propName] = prop;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (changed)
|
|
416
|
+
next = { ...next, properties: nextProps };
|
|
417
|
+
}
|
|
418
|
+
schemas[name] = next;
|
|
419
|
+
}
|
|
420
|
+
return { ...data, schemas };
|
|
421
|
+
}
|
package/dist/php/type-mapper.js
CHANGED
|
@@ -67,6 +67,7 @@ export function toPhpDocType(type, nullable = false) {
|
|
|
67
67
|
let phpType;
|
|
68
68
|
switch (type) {
|
|
69
69
|
case 'String':
|
|
70
|
+
case 'Char':
|
|
70
71
|
case 'Email':
|
|
71
72
|
case 'Password':
|
|
72
73
|
case 'Text':
|
|
@@ -108,7 +109,7 @@ export function toPhpDocType(type, nullable = false) {
|
|
|
108
109
|
}
|
|
109
110
|
/** Type categories for rule applicability. */
|
|
110
111
|
const STRING_TYPES = new Set([
|
|
111
|
-
'String', 'Email', 'Password', 'Text', 'MediumText', 'LongText',
|
|
112
|
+
'String', 'Char', 'Email', 'Password', 'Text', 'MediumText', 'LongText',
|
|
112
113
|
'Slug', 'Url', 'Phone', 'Uuid',
|
|
113
114
|
]);
|
|
114
115
|
const NUMERIC_TYPES = new Set(['Int', 'BigInt', 'TinyInt', 'Float', 'Decimal']);
|
|
@@ -148,6 +149,7 @@ export function toStoreRules(property, tableName, fkRuleType = 'integer') {
|
|
|
148
149
|
}
|
|
149
150
|
switch (type) {
|
|
150
151
|
case 'String':
|
|
152
|
+
case 'Char':
|
|
151
153
|
case 'Slug':
|
|
152
154
|
case 'Url':
|
|
153
155
|
case 'Phone': {
|
|
@@ -7,4 +7,12 @@
|
|
|
7
7
|
* notifications and cache invalidation.
|
|
8
8
|
*/
|
|
9
9
|
import type { SchemaDefinition, TypeScriptFile } from './types.js';
|
|
10
|
-
export
|
|
10
|
+
export interface TsHooksOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Module the generated hooks import `toast` from. Defaults to "sonner".
|
|
13
|
+
* Point it at a design-system wrapper (e.g. "@godxjp/ui") whose Toaster
|
|
14
|
+
* listens to its own copy of sonner. Issue #327.
|
|
15
|
+
*/
|
|
16
|
+
toastImport?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function generateTsHooks(schemas: Record<string, SchemaDefinition>, hooksOptions?: TsHooksOptions): TypeScriptFile[];
|
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
* that returns TanStack Query hooks (useQuery + useMutation) with toast
|
|
7
7
|
* notifications and cache invalidation.
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
|
-
// Public API
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
export function generateTsHooks(schemas) {
|
|
9
|
+
export function generateTsHooks(schemas, hooksOptions = {}) {
|
|
13
10
|
const files = [];
|
|
11
|
+
const toastImport = hooksOptions.toastImport || 'sonner';
|
|
14
12
|
for (const [name, schema] of Object.entries(schemas)) {
|
|
15
13
|
if (schema.kind === 'enum')
|
|
16
14
|
continue;
|
|
@@ -18,14 +16,14 @@ export function generateTsHooks(schemas) {
|
|
|
18
16
|
continue;
|
|
19
17
|
if (!schema.options?.api && !schema.options?.service)
|
|
20
18
|
continue;
|
|
21
|
-
files.push(generateHooksFile(name, schema));
|
|
19
|
+
files.push(generateHooksFile(name, schema, toastImport));
|
|
22
20
|
}
|
|
23
21
|
return files;
|
|
24
22
|
}
|
|
25
23
|
// ---------------------------------------------------------------------------
|
|
26
24
|
// Per-schema generator
|
|
27
25
|
// ---------------------------------------------------------------------------
|
|
28
|
-
function generateHooksFile(name, schema) {
|
|
26
|
+
function generateHooksFile(name, schema, toastImport) {
|
|
29
27
|
const hasSoftDelete = schema.options?.softDelete ?? false;
|
|
30
28
|
const hasRestore = schema.options?.api?.restore ?? hasSoftDelete;
|
|
31
29
|
const restoreHook = hasRestore ? `
|
|
@@ -46,7 +44,7 @@ function generateHooksFile(name, schema) {
|
|
|
46
44
|
* @generated by omnify
|
|
47
45
|
*/
|
|
48
46
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
49
|
-
import { toast } from
|
|
47
|
+
import { toast } from ${JSON.stringify(toastImport)};
|
|
50
48
|
import type { ${name}Filters, ${name}BaseServiceType } from "./${name}Service";
|
|
51
49
|
import type { create${name}QueryKeys } from "./${name}QueryKeys";
|
|
52
50
|
|
package/dist/zod-generator.js
CHANGED
|
@@ -50,7 +50,7 @@ function autoDerivePlaceholder(label, locales) {
|
|
|
50
50
|
}
|
|
51
51
|
/** Type categories for Zod rule applicability. */
|
|
52
52
|
const ZOD_STRING_TYPES = new Set([
|
|
53
|
-
'String', 'Email', 'Password', 'Text', 'MediumText', 'LongText',
|
|
53
|
+
'String', 'Char', 'Email', 'Password', 'Text', 'MediumText', 'LongText',
|
|
54
54
|
'Slug', 'Url', 'Phone', 'Uuid',
|
|
55
55
|
]);
|
|
56
56
|
const ZOD_NUMERIC_TYPES = new Set(['Int', 'BigInt', 'TinyInt', 'Float', 'Decimal']);
|
|
@@ -172,6 +172,7 @@ function getZodSchemaForType(propDef, _fieldName, options) {
|
|
|
172
172
|
}
|
|
173
173
|
switch (propDef.type) {
|
|
174
174
|
case 'String':
|
|
175
|
+
case 'Char':
|
|
175
176
|
case 'Text':
|
|
176
177
|
case 'MediumText':
|
|
177
178
|
case 'LongText':
|