@omnifyjp/omnify 3.14.1 → 3.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnifyjp/omnify",
3
- "version": "3.14.1",
3
+ "version": "3.15.1",
4
4
  "description": "Schema-driven code generation for Laravel, TypeScript, and SQL",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,10 +36,10 @@
36
36
  "zod": "^3.24.0"
37
37
  },
38
38
  "optionalDependencies": {
39
- "@omnifyjp/omnify-darwin-arm64": "3.14.1",
40
- "@omnifyjp/omnify-darwin-x64": "3.14.1",
41
- "@omnifyjp/omnify-linux-x64": "3.14.1",
42
- "@omnifyjp/omnify-linux-arm64": "3.14.1",
43
- "@omnifyjp/omnify-win32-x64": "3.14.1"
39
+ "@omnifyjp/omnify-darwin-arm64": "3.15.1",
40
+ "@omnifyjp/omnify-darwin-x64": "3.15.1",
41
+ "@omnifyjp/omnify-linux-x64": "3.15.1",
42
+ "@omnifyjp/omnify-linux-arm64": "3.15.1",
43
+ "@omnifyjp/omnify-win32-x64": "3.15.1"
44
44
  }
45
45
  }
package/ts-dist/cli.js CHANGED
@@ -102,6 +102,8 @@ function resolveFromConfig(configPath) {
102
102
  inputSpec,
103
103
  tsEnabled,
104
104
  tsOutput,
105
+ tsPlatform: tsConfig?.platform,
106
+ tsAuth: tsConfig?.auth,
105
107
  laravelEnabled,
106
108
  laravelOverrides,
107
109
  };
@@ -120,6 +122,8 @@ program
120
122
  let inputSpec;
121
123
  let tsEnabled = true;
122
124
  let tsOutput;
125
+ let tsPlatform;
126
+ let tsAuth;
123
127
  let laravelEnabled = false;
124
128
  let laravelOverrides;
125
129
  let configDir = process.cwd();
@@ -142,6 +146,8 @@ program
142
146
  inputSpec = opts.input ?? resolved.inputSpec;
143
147
  tsEnabled = resolved.tsEnabled;
144
148
  tsOutput = opts.output ? resolve(opts.output) : resolved.tsOutput;
149
+ tsPlatform = resolved.tsPlatform;
150
+ tsAuth = resolved.tsAuth;
145
151
  laravelEnabled = resolved.laravelEnabled;
146
152
  laravelOverrides = resolved.laravelOverrides;
147
153
  }
@@ -166,7 +172,10 @@ program
166
172
  // ---- TypeScript generation ----
167
173
  if (tsEnabled && tsOutput) {
168
174
  console.log(`\n[TypeScript] Output: ${tsOutput}`);
169
- const files = generateTypeScript(input);
175
+ const files = generateTypeScript(input, {
176
+ platform: tsPlatform,
177
+ auth: tsAuth,
178
+ });
170
179
  mkdirSync(join(tsOutput, 'base'), { recursive: true });
171
180
  mkdirSync(join(tsOutput, 'enum'), { recursive: true });
172
181
  let tsCreated = 0;
@@ -13,7 +13,15 @@
13
13
  * index.ts — Re-exports (always overwritten)
14
14
  */
15
15
  import type { SchemasJson, TypeScriptFile } from './types.js';
16
+ /** Build GeneratorOptions from schemas.json. */
17
+ /** Extra config that the CLI resolves from omnify.yaml's codegen.typescript section. */
18
+ export interface GenerateExtraConfig {
19
+ platform?: 'web' | 'expo';
20
+ auth?: 'cookie' | 'secureStore';
21
+ }
16
22
  /**
17
23
  * Generate all TypeScript files from schemas.json input.
24
+ *
25
+ * @param extra - Platform/auth config resolved from omnify.yaml's codegen.typescript section (issue #63).
18
26
  */
19
- export declare function generateTypeScript(input: SchemasJson): TypeScriptFile[];
27
+ export declare function generateTypeScript(input: SchemasJson, extra?: GenerateExtraConfig): TypeScriptFile[];
@@ -21,6 +21,7 @@ import { generateTsServices } from './ts-service-generator.js';
21
21
  import { generateTsQueryKeys } from './ts-query-keys-generator.js';
22
22
  import { generateTsHooks } from './ts-hooks-generator.js';
23
23
  import { buildFormShape, formatPayloadBuilderSection, } from './payload-builder-generator.js';
24
+ import { generateRnQuerySetup } from './rn-query-setup-generator.js';
24
25
  /** Auto-generated file header. */
25
26
  function generateBaseHeader() {
26
27
  return `/**
@@ -33,13 +34,14 @@ function generateBaseHeader() {
33
34
 
34
35
  `;
35
36
  }
36
- /** Build GeneratorOptions from schemas.json. */
37
- function buildOptions(input) {
37
+ function buildOptions(input, extra) {
38
38
  return {
39
39
  locales: input.locale.locales,
40
40
  defaultLocale: input.locale.defaultLocale,
41
41
  fallbackLocale: input.locale.fallbackLocale,
42
42
  customTypes: input.customTypes,
43
+ platform: extra?.platform,
44
+ auth: extra?.auth,
43
45
  };
44
46
  }
45
47
  /** Generate a base interface file for a schema. */
@@ -437,9 +439,11 @@ function validateSchemaNames(schemas) {
437
439
  }
438
440
  /**
439
441
  * Generate all TypeScript files from schemas.json input.
442
+ *
443
+ * @param extra - Platform/auth config resolved from omnify.yaml's codegen.typescript section (issue #63).
440
444
  */
441
- export function generateTypeScript(input) {
442
- const options = buildOptions(input);
445
+ export function generateTypeScript(input, extra) {
446
+ const options = buildOptions(input, extra);
443
447
  const schemas = input.schemas;
444
448
  // Validate no reserved name collisions
445
449
  validateSchemaNames(schemas);
@@ -501,6 +505,10 @@ export function generateTypeScript(input) {
501
505
  files.push(...generateTsServices(schemas, options));
502
506
  files.push(...generateTsQueryKeys(schemas));
503
507
  files.push(...generateTsHooks(schemas));
508
+ // React Native / Expo query setup (issue #63)
509
+ if (options.platform === 'expo') {
510
+ files.push(generateRnQuerySetup(options.auth ?? 'secureStore'));
511
+ }
504
512
  // Index re-exports
505
513
  files.push(generateIndexFile(schemas, schemaEnums, pluginEnums, inlineTypeAliases, hasFiles));
506
514
  return files;
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * TypeScript model type generator from Omnify schemas.json.
5
5
  */
6
- export { generateTypeScript } from './generator.js';
6
+ export { generateTypeScript, type GenerateExtraConfig } from './generator.js';
7
7
  export type { SchemasJson, SchemaDefinition, PropertyDefinition, TypeScriptFile, TSInterface, TSProperty, TSEnum, TSEnumValue, TSTypeAlias, GeneratorOptions, LocalizedString, LocaleMap, } from './types.js';
8
8
  export { schemaToInterface, formatInterface, formatProperty, generateInterfaces, getPropertyType, toSnakeCase, } from './interface-generator.js';
9
9
  export { generateEnums, generatePluginEnums, formatEnum, formatTypeAlias, extractInlineEnums, toPascalCase, toEnumMemberName, } from './enum-generator.js';
@@ -51,7 +51,9 @@ export declare class SchemaReader {
51
51
  getSchemasWithApi(): Record<string, SchemaDefinition>;
52
52
  /** Check if any schema has api options. */
53
53
  hasApiSchemas(): boolean;
54
- /** Check if any schema has File-type properties. */
54
+ /** Check if any schema has File-type properties OR the project has
55
+ * `file:` config (issue #64: generate File infrastructure even when
56
+ * no schema declares `type: File` yet). */
55
57
  hasFileProperties(): boolean;
56
58
  /** Get the file config from schemas.json. */
57
59
  getFileConfig(): import("../types.js").FileConfigExport | null;
@@ -172,8 +172,12 @@ export class SchemaReader {
172
172
  hasApiSchemas() {
173
173
  return Object.keys(this.getSchemasWithApi()).length > 0;
174
174
  }
175
- /** Check if any schema has File-type properties. */
175
+ /** Check if any schema has File-type properties OR the project has
176
+ * `file:` config (issue #64: generate File infrastructure even when
177
+ * no schema declares `type: File` yet). */
176
178
  hasFileProperties() {
179
+ if (this.data.fileConfig)
180
+ return true;
177
181
  for (const schema of Object.values(this.getObjectSchemas())) {
178
182
  const props = schema.properties ?? {};
179
183
  for (const prop of Object.values(props)) {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @omnify/ts — React Native Query Setup Generator (issue #63)
3
+ *
4
+ * When the codegen target has `platform: 'expo'`, this module generates a
5
+ * `rn-query-setup.ts` utility file at the output root. The file wires up
6
+ * TanStack Query's `focusManager` and `onlineManager` for React Native so
7
+ * queries auto-refetch when the app comes back from the background and
8
+ * pause when the device is offline.
9
+ *
10
+ * The file also exports an `apiFetch` wrapper that reads the bearer token
11
+ * from Expo SecureStore (async) and injects it into every request. This
12
+ * replaces the browser-cookie auth path used by web targets.
13
+ */
14
+ import type { TypeScriptFile } from './types.js';
15
+ /**
16
+ * Generate `rn-query-setup.ts` for Expo / React Native targets.
17
+ *
18
+ * @param auth - Auth strategy: 'secureStore' (default for expo) or 'cookie'
19
+ */
20
+ export declare function generateRnQuerySetup(auth?: string): TypeScriptFile;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * @omnify/ts — React Native Query Setup Generator (issue #63)
3
+ *
4
+ * When the codegen target has `platform: 'expo'`, this module generates a
5
+ * `rn-query-setup.ts` utility file at the output root. The file wires up
6
+ * TanStack Query's `focusManager` and `onlineManager` for React Native so
7
+ * queries auto-refetch when the app comes back from the background and
8
+ * pause when the device is offline.
9
+ *
10
+ * The file also exports an `apiFetch` wrapper that reads the bearer token
11
+ * from Expo SecureStore (async) and injects it into every request. This
12
+ * replaces the browser-cookie auth path used by web targets.
13
+ */
14
+ /**
15
+ * Generate `rn-query-setup.ts` for Expo / React Native targets.
16
+ *
17
+ * @param auth - Auth strategy: 'secureStore' (default for expo) or 'cookie'
18
+ */
19
+ export function generateRnQuerySetup(auth = 'secureStore') {
20
+ const authImport = auth === 'secureStore'
21
+ ? `import * as SecureStore from 'expo-secure-store';`
22
+ : '';
23
+ const authHeaders = auth === 'secureStore'
24
+ ? `
25
+ /**
26
+ * Read the bearer token from Expo SecureStore (async) and return the
27
+ * Authorization header. Returns an empty object when no token is stored.
28
+ */
29
+ async function getAuthHeaders(): Promise<Record<string, string>> {
30
+ const token = await SecureStore.getItemAsync('auth_token');
31
+ return token ? { Authorization: \`Bearer \${token}\` } : {};
32
+ }
33
+
34
+ /**
35
+ * Authenticated fetch wrapper for React Native. Injects the bearer token
36
+ * from SecureStore into every request. Use this as the \`apiFetch\`
37
+ * parameter when creating service factories.
38
+ *
39
+ * @example
40
+ * const zoneService = createZoneBaseService((path) => \`\${BASE_URL}/zones\${path}\`);
41
+ * // In the app, wrap with apiFetch from this module.
42
+ */
43
+ export async function apiFetch<T>(url: string, init?: RequestInit): Promise<T> {
44
+ const headers = {
45
+ 'Content-Type': 'application/json',
46
+ 'Accept': 'application/json',
47
+ ...await getAuthHeaders(),
48
+ ...init?.headers,
49
+ };
50
+ const res = await fetch(url, { ...init, headers });
51
+ if (!res.ok) {
52
+ const body = await res.text().catch(() => '');
53
+ throw new Error(body || \`HTTP \${res.status}\`);
54
+ }
55
+ return res.json();
56
+ }
57
+ `
58
+ : `
59
+ /**
60
+ * Basic fetch wrapper (cookie auth). Same as the web target's user-land
61
+ * apiFetch — provided here so the generated service files can import from
62
+ * a consistent location.
63
+ */
64
+ export async function apiFetch<T>(url: string, init?: RequestInit): Promise<T> {
65
+ const headers = {
66
+ 'Content-Type': 'application/json',
67
+ 'Accept': 'application/json',
68
+ ...init?.headers,
69
+ };
70
+ const res = await fetch(url, { ...init, headers, credentials: 'include' });
71
+ if (!res.ok) {
72
+ const body = await res.text().catch(() => '');
73
+ throw new Error(body || \`HTTP \${res.status}\`);
74
+ }
75
+ return res.json();
76
+ }
77
+ `;
78
+ const content = `/**
79
+ * DO NOT EDIT - Auto-generated by Omnify.
80
+ *
81
+ * React Native / Expo query setup. Wire this up in your app's entry point
82
+ * (e.g. \`app/_layout.tsx\`) to enable:
83
+ * - Auto-refetch when the app returns from background (AppState)
84
+ * - Pause queries when offline (NetInfo)
85
+ * - Authenticated fetch with bearer token from SecureStore
86
+ *
87
+ * @generated by omnify (issue #63)
88
+ */
89
+
90
+ import { AppState, type AppStateStatus } from 'react-native';
91
+ import NetInfo from '@react-native-community/netinfo';
92
+ import {
93
+ focusManager,
94
+ onlineManager,
95
+ QueryClient,
96
+ } from '@tanstack/react-query';
97
+ ${authImport}
98
+
99
+ // ---- Focus Manager (refetch on app foreground) ----
100
+
101
+ function onAppStateChange(status: AppStateStatus) {
102
+ focusManager.setFocused(status === 'active');
103
+ }
104
+
105
+ /**
106
+ * Call once at app startup to wire AppState → TanStack Query focusManager.
107
+ * When the user switches back to the app, all stale queries refetch.
108
+ */
109
+ export function setupFocusManager(): () => void {
110
+ const sub = AppState.addEventListener('change', onAppStateChange);
111
+ return () => sub.remove();
112
+ }
113
+
114
+ // ---- Online Manager (pause queries when offline) ----
115
+
116
+ /**
117
+ * Call once at app startup to wire NetInfo → TanStack Query onlineManager.
118
+ * Queries pause when the device loses connectivity and resume when it's back.
119
+ */
120
+ export function setupOnlineManager(): () => void {
121
+ return onlineManager.setEventListener((setOnline) => {
122
+ return NetInfo.addEventListener((state) => {
123
+ setOnline(!!state.isConnected);
124
+ });
125
+ });
126
+ }
127
+
128
+ // ---- Query Client factory ----
129
+
130
+ /**
131
+ * Pre-configured QueryClient for React Native with sensible defaults:
132
+ * - \`refetchOnWindowFocus\` wired via focusManager above
133
+ * - \`retry: 2\` for flaky mobile networks
134
+ * - \`staleTime: 30s\` to reduce unnecessary refetches on tab switch
135
+ */
136
+ export function createRnQueryClient(): QueryClient {
137
+ return new QueryClient({
138
+ defaultOptions: {
139
+ queries: {
140
+ retry: 2,
141
+ staleTime: 30_000,
142
+ },
143
+ },
144
+ });
145
+ }
146
+ ${authHeaders}
147
+ `;
148
+ return {
149
+ filePath: 'rn-query-setup.ts',
150
+ content,
151
+ types: ['setupFocusManager', 'setupOnlineManager', 'createRnQueryClient', 'apiFetch'],
152
+ overwrite: true,
153
+ };
154
+ }
@@ -51,6 +51,10 @@ export interface SchemasJson {
51
51
  export interface PackageExportInfo {
52
52
  readonly migrationsPath?: string;
53
53
  readonly codegen?: PackageCodegenExport;
54
+ /** Schema name → consumer FQCN mapping for relation resolution. Issue #61. */
55
+ readonly modelMap?: Record<string, string>;
56
+ /** Whether this package's models are MappedSuperclass (abstract). Issue #61. */
57
+ readonly abstract?: boolean;
54
58
  }
55
59
  /** Codegen namespace info from a package. */
56
60
  export interface PackageCodegenExport {
@@ -254,6 +258,9 @@ export interface PropertyDefinition {
254
258
  readonly mappedBy?: string;
255
259
  readonly orderBy?: readonly OrderByItem[];
256
260
  readonly useCurrent?: boolean;
261
+ readonly deprecated?: boolean;
262
+ readonly deprecatedSince?: string;
263
+ readonly removalTarget?: string;
257
264
  readonly searchable?: boolean;
258
265
  readonly filterable?: boolean;
259
266
  readonly sortable?: boolean;
@@ -347,4 +354,8 @@ export interface GeneratorOptions {
347
354
  readonly defaultLocale: string;
348
355
  readonly fallbackLocale: string;
349
356
  readonly customTypes: SchemasJson['customTypes'];
357
+ /** Target platform: 'web' (default) or 'expo' (React Native). Issue #63. */
358
+ readonly platform?: 'web' | 'expo';
359
+ /** Auth strategy: 'cookie' (default) or 'secureStore' (Expo). Issue #63. */
360
+ readonly auth?: 'cookie' | 'secureStore';
350
361
  }
package/types/config.d.ts CHANGED
@@ -19,6 +19,8 @@ export interface MigrationConfig {
19
19
  schemasPath?: string;
20
20
  /** SQL dialect for generated migrations (sql only). */
21
21
  dialect?: SQLDialect;
22
+ /** Use deterministic timestamps (year-2000 base + sort-order offset) for packages. Issue #60. */
23
+ stableTimestamps?: boolean;
22
24
  }
23
25
 
24
26
  /** Per-connection database and migration configuration. */
@@ -135,6 +137,8 @@ export interface OmnifyConfig {
135
137
  compoundTypes?: string[];
136
138
  /** Code generation configuration (connection-independent). */
137
139
  codegen?: CodegenConfig;
140
+ /** Package mode: stable deterministic timestamps for all Laravel migrations. Issue #60. */
141
+ package?: boolean;
138
142
  /** Enable verbose output during generation. */
139
143
  verbose?: boolean;
140
144
  }