@omnifyjp/ts 3.22.0 → 3.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,29 @@
1
1
  /**
2
- * Generates base + editable service classes for schemas with `options.api`
3
- * or `options.service`.
2
+ * Generates base + editable service classes.
4
3
  *
5
4
  * Issue #57: Full BaseService codegen with translatable, softDelete, audit,
6
5
  * DB::transaction, eagerLoad/eagerCount, lookupFields, defaultSort.
6
+ *
7
+ * Issue #81 (v3.23.0): convention-over-configuration flip. Services are now
8
+ * generated by default for every project `kind: object` schema. Opt out with
9
+ * `options.service: false` (pivots, translation sidecars, audit logs).
10
+ * Property-level flags (`searchable`, `filterable`, `sortable`, `lookupable`,
11
+ * `defaultSort: asc|desc`) are the single source of truth — the legacy
12
+ * `options.service.{searchable,filterable,defaultSort,lookupFields,eagerLoad,
13
+ * eagerCount}` keys are deprecated and emit warnings at generate time.
7
14
  */
8
15
  import { SchemaReader } from './schema-reader.js';
9
16
  import type { GeneratedFile, PhpConfig } from './types.js';
10
- /** Generate service classes for all schemas with api or service config. */
17
+ /**
18
+ * Generate service classes for every project object schema unless explicitly
19
+ * opted out via `options.service: false`. Also generates for schemas that
20
+ * still use the legacy `options.api` block (unchanged from v3.22.x).
21
+ *
22
+ * Skipped automatically:
23
+ * - kind != object (pivot, partial, enum, extend)
24
+ * - options.service === false (explicit opt-out)
25
+ * - schemas whose name matches a translation sidecar pattern
26
+ * (`*Translation` auto-generated by the translation-model-generator)
27
+ * - package-owned schemas (those get services in their owning package)
28
+ */
11
29
  export declare function generateServices(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
@@ -1,28 +1,108 @@
1
1
  /**
2
- * Generates base + editable service classes for schemas with `options.api`
3
- * or `options.service`.
2
+ * Generates base + editable service classes.
4
3
  *
5
4
  * Issue #57: Full BaseService codegen with translatable, softDelete, audit,
6
5
  * DB::transaction, eagerLoad/eagerCount, lookupFields, defaultSort.
6
+ *
7
+ * Issue #81 (v3.23.0): convention-over-configuration flip. Services are now
8
+ * generated by default for every project `kind: object` schema. Opt out with
9
+ * `options.service: false` (pivots, translation sidecars, audit logs).
10
+ * Property-level flags (`searchable`, `filterable`, `sortable`, `lookupable`,
11
+ * `defaultSort: asc|desc`) are the single source of truth — the legacy
12
+ * `options.service.{searchable,filterable,defaultSort,lookupFields,eagerLoad,
13
+ * eagerCount}` keys are deprecated and emit warnings at generate time.
7
14
  */
8
15
  import { toPascalCase, toSnakeCase } from './naming-helper.js';
9
16
  import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace } from './types.js';
10
17
  // ============================================================================
11
18
  // Public entry point
12
19
  // ============================================================================
13
- /** Generate service classes for all schemas with api or service config. */
20
+ /**
21
+ * Generate service classes for every project object schema unless explicitly
22
+ * opted out via `options.service: false`. Also generates for schemas that
23
+ * still use the legacy `options.api` block (unchanged from v3.22.x).
24
+ *
25
+ * Skipped automatically:
26
+ * - kind != object (pivot, partial, enum, extend)
27
+ * - options.service === false (explicit opt-out)
28
+ * - schemas whose name matches a translation sidecar pattern
29
+ * (`*Translation` auto-generated by the translation-model-generator)
30
+ * - package-owned schemas (those get services in their owning package)
31
+ */
14
32
  export function generateServices(reader, config) {
15
33
  const files = [];
16
- // Merge schemas with api OR service options (union)
17
- const candidates = {
18
- ...reader.getSchemasWithApi(),
19
- ...reader.getSchemasWithService(),
20
- };
34
+ const candidates = {};
35
+ // Project-owned object schemas — the new default-enabled set (#81).
36
+ for (const [name, schema] of Object.entries(reader.getProjectObjectSchemas())) {
37
+ // #81 explicit opt-out: `service: false` on the schema
38
+ if (schema.options?.service === false) {
39
+ continue;
40
+ }
41
+ // Skip Astrotomic translation sidecars — they're auto-emitted as PHP
42
+ // translation models, not as full services. Heuristic by name suffix.
43
+ if (name.endsWith('Translation')) {
44
+ continue;
45
+ }
46
+ // Skip hidden schemas (options.hidden = true) — intentional internal types
47
+ if (schema.options?.hidden) {
48
+ continue;
49
+ }
50
+ candidates[name] = schema;
51
+ }
52
+ // Legacy `options.api` schemas — these are already in project set above
53
+ // (getSchemasWithApi is a subset of project schemas). We don't need a
54
+ // second pass for them unless someone sets `service: false` + `api: {}`
55
+ // on the same schema, in which case the explicit api opt-in wins.
56
+ for (const [name, schema] of Object.entries(reader.getSchemasWithApi())) {
57
+ if (!candidates[name]) {
58
+ candidates[name] = schema;
59
+ }
60
+ }
61
+ // Emit deprecation warnings once per schema for legacy service-block keys
62
+ // that now duplicate property-level metadata.
63
+ for (const [name, schema] of Object.entries(candidates)) {
64
+ warnLegacyServiceKeys(name, schema);
65
+ }
21
66
  for (const [name, schema] of Object.entries(candidates)) {
22
67
  files.push(...generateForSchema(name, schema, reader, config));
23
68
  }
24
69
  return files;
25
70
  }
71
+ /**
72
+ * Print a deprecation warning (once per schema) for every legacy
73
+ * `options.service.*` key that duplicates property-level metadata.
74
+ * Each warning points at the property-level equivalent so the migration
75
+ * path is self-documenting.
76
+ */
77
+ function warnLegacyServiceKeys(name, schema) {
78
+ const svc = schema.options?.service;
79
+ if (!svc || typeof svc !== 'object')
80
+ return;
81
+ const deprecations = [];
82
+ if (svc.searchable && svc.searchable.length > 0) {
83
+ deprecations.push({ key: 'searchable', replacement: "property-level `searchable: true` on each field" });
84
+ }
85
+ if (svc.filterable && svc.filterable.length > 0) {
86
+ deprecations.push({ key: 'filterable', replacement: "property-level `filterable: true` on each field" });
87
+ }
88
+ if (svc.defaultSort !== undefined) {
89
+ deprecations.push({ key: 'defaultSort', replacement: "property-level `defaultSort: asc|desc` on exactly one field" });
90
+ }
91
+ if (svc.lookupFields && svc.lookupFields.length > 0) {
92
+ deprecations.push({ key: 'lookupFields', replacement: "property-level `lookupable: true` on each field" });
93
+ }
94
+ if (svc.eagerLoad && svc.eagerLoad.length > 0) {
95
+ deprecations.push({ key: 'eagerLoad', replacement: "override `applyListEagerLoads()` / `applyFindByIdEagerLoads()` / `applyLookupEagerLoads()` in the editable service" });
96
+ }
97
+ if (svc.eagerCount && svc.eagerCount.length > 0) {
98
+ deprecations.push({ key: 'eagerCount', replacement: "override `applyListEagerLoads()` in the editable service and call `$query->withCount(...)`" });
99
+ }
100
+ for (const d of deprecations) {
101
+ console.warn(`[omnify-ts] ${name}.options.service.${d.key} is deprecated (#81) — ` +
102
+ `use ${d.replacement} instead. The legacy key still works for this ` +
103
+ `release but will be removed in a future major version.`);
104
+ }
105
+ }
26
106
  function generateForSchema(name, schema, reader, config) {
27
107
  return [
28
108
  generateBaseService(name, schema, reader, config),
@@ -40,7 +120,11 @@ function resolveFieldLists(schema, reader, name) {
40
120
  const properties = schema.properties ?? {};
41
121
  const propertyOrder = reader.getPropertyOrder(name);
42
122
  const expandedProperties = reader.getExpandedProperties(name);
43
- const svc = schema.options?.service;
123
+ // #81: options.service can now be `false` (explicit opt-out). Narrow to
124
+ // an object here so every downstream read works the same on both shapes;
125
+ // the opt-out is filtered before this function is reached.
126
+ const svcRaw = schema.options?.service;
127
+ const svc = (svcRaw && typeof svcRaw === 'object') ? svcRaw : {};
44
128
  const searchableFields = [];
45
129
  const filterableFields = [];
46
130
  const sortableFields = [];
@@ -173,6 +257,72 @@ function reconstructRelationTokens(items) {
173
257
  result.push(current);
174
258
  return result;
175
259
  }
260
+ /**
261
+ * #81: resolve the lookup() projection from property-level `lookupable: true`
262
+ * flags. Returns snake_case column names. Always includes `id` at the head
263
+ * of the list because the caller always wants the primary key. Falls back
264
+ * to ['id', 'name', 'slug'] (filtered to actually-present columns) when no
265
+ * property is marked — matches the v3.22.x default for backward compat.
266
+ */
267
+ function resolveLookupFields(schema) {
268
+ const properties = schema.properties ?? {};
269
+ const order = schema.propertyOrder ?? Object.keys(properties);
270
+ const explicit = [];
271
+ for (const propName of order) {
272
+ const prop = properties[propName];
273
+ if (!prop)
274
+ continue;
275
+ if (prop.lookupable) {
276
+ explicit.push(toSnakeCase(propName));
277
+ }
278
+ }
279
+ if (explicit.length > 0) {
280
+ // Always prefix with `id` so the caller can key the returned rows.
281
+ return explicit.includes('id') ? explicit : ['id', ...explicit];
282
+ }
283
+ // Default: id + name + slug if those columns exist on the schema
284
+ const defaults = ['id'];
285
+ if (properties['name'])
286
+ defaults.push('name');
287
+ if (properties['slug'])
288
+ defaults.push('slug');
289
+ return defaults;
290
+ }
291
+ /**
292
+ * #81: resolve the default sort from a property-level `defaultSort: asc|desc`
293
+ * flag. At most one property per schema should carry this; if multiple are
294
+ * present, the first one wins and a warning is printed. Falls back to the
295
+ * legacy `svc.defaultSort` string (deprecation warning fired elsewhere)
296
+ * then to `-created_at`.
297
+ */
298
+ function resolveDefaultSort(schema, svc) {
299
+ const properties = schema.properties ?? {};
300
+ const order = schema.propertyOrder ?? Object.keys(properties);
301
+ let picked = null;
302
+ const collisions = [];
303
+ for (const propName of order) {
304
+ const prop = properties[propName];
305
+ if (!prop)
306
+ continue;
307
+ const dir = prop.defaultSort;
308
+ if (dir !== 'asc' && dir !== 'desc')
309
+ continue;
310
+ if (picked) {
311
+ collisions.push(propName);
312
+ continue;
313
+ }
314
+ picked = { colName: toSnakeCase(propName), direction: dir };
315
+ }
316
+ if (collisions.length > 0 && picked) {
317
+ console.warn(`[omnify-ts] ${schema.name}: multiple properties declare defaultSort ` +
318
+ `(${collisions.join(', ')}); using '${picked.colName}' and ignoring ` +
319
+ `the rest. Remove the extras to silence this warning.`);
320
+ }
321
+ if (picked) {
322
+ return picked.direction === 'desc' ? `-${picked.colName}` : picked.colName;
323
+ }
324
+ return svc.defaultSort ?? '-created_at';
325
+ }
176
326
  /** Collect properties that have non-null defaults (for create() method). */
177
327
  function resolveDefaults(schema, reader, name) {
178
328
  const properties = schema.properties ?? {};
@@ -206,11 +356,23 @@ function generateBaseService(name, schema, reader, config) {
206
356
  const modelName = toPascalCase(name);
207
357
  const options = schema.options ?? {};
208
358
  const api = options.api ?? {};
209
- const svc = options.service ?? {};
359
+ // #81: options.service can be `false` for explicit opt-out — narrow to
360
+ // a plain object so downstream reads of svc.* are always safe. The
361
+ // opt-out filter in generateServices() means we never reach this path
362
+ // for schemas that set `service: false`.
363
+ const svcRaw = options.service;
364
+ const svc = (svcRaw && typeof svcRaw === 'object') ? svcRaw : {};
210
365
  const hasSoftDelete = options.softDelete ?? false;
211
366
  const perPage = api.perPage ?? 25;
212
- const hasApiConfig = options.api != null;
213
- const lookup = api.lookup ?? (hasApiConfig ? true : (svc.lookupFields != null && svc.lookupFields.length > 0));
367
+ // #81: lookup fields come from property-level `lookupable: true`.
368
+ // Legacy `options.service.lookupFields` still honoured (deprecation warning
369
+ // fired earlier). Always emit lookup() — it's a free method on every
370
+ // generated service under the opt-out default.
371
+ const lookupFieldsFromProps = resolveLookupFields(schema);
372
+ const lookupFields = (svc.lookupFields && svc.lookupFields.length > 0)
373
+ ? [...svc.lookupFields]
374
+ : lookupFieldsFromProps;
375
+ const lookup = lookupFields.length > 0;
214
376
  // Audit detection — schema-level then global
215
377
  const schemaAudit = options.audit;
216
378
  const globalAudit = reader.getAuditConfig();
@@ -228,13 +390,14 @@ function generateBaseService(name, schema, reader, config) {
228
390
  fkColumn: `${modelSnake}_id`,
229
391
  translatableFields,
230
392
  };
231
- // Eager load / count (#75.1: rejoin YAML-split `rel:col,col` tokens)
393
+ // Eager load / count legacy YAML-declared (deprecated in #81, still
394
+ // honoured during the deprecation cycle). New code should override
395
+ // applyListEagerLoads() / applyFindByIdEagerLoads() / applyLookupEagerLoads()
396
+ // in the editable service.
232
397
  const eagerLoad = reconstructRelationTokens(svc.eagerLoad ?? []);
233
398
  const eagerCount = svc.eagerCount ?? [];
234
- // Default sort
235
- const defaultSort = svc.defaultSort ?? '-created_at';
236
- // Lookup fields
237
- const lookupFields = svc.lookupFields ?? ['id', 'name'];
399
+ // Default sort — #81: property-level `defaultSort: asc|desc` takes priority.
400
+ const defaultSort = resolveDefaultSort(schema, svc);
238
401
  // Field lists
239
402
  const { searchableFields, filterableFields, sortableFields } = resolveFieldLists(schema, reader, name);
240
403
  // Property defaults
@@ -293,6 +456,10 @@ function generateBaseService(name, schema, reader, config) {
293
456
  sections.push(buildSectionComment('Lookup'));
294
457
  sections.push(buildLookupMethod(modelName, lookupFields, filterableFields, defaultSort, translationCtx, reader, hasSoftDelete));
295
458
  }
459
+ // ── Eager Load Hooks (#81) ────────────────────────────────────────────────
460
+ sections.push('');
461
+ sections.push(buildSectionComment('Eager Load Hooks (#81)'));
462
+ sections.push(buildEagerLoadHooks());
296
463
  // ── Translatable helpers ──────────────────────────────────────────────────
297
464
  if (hasTranslatable) {
298
465
  sections.push('');
@@ -340,6 +507,63 @@ function buildSectionComment(title) {
340
507
  // ${title}
341
508
  // =========================================================================`;
342
509
  }
510
+ /**
511
+ * #81: empty protected hook methods that every read path calls after
512
+ * building its query. Consumers override these in the editable sibling
513
+ * service to apply per-method eager loads, keeping eager loading as a
514
+ * query-time concern (as every other ORM in the ecosystem does) instead
515
+ * of a schema-level one.
516
+ *
517
+ * The legacy `options.service.eagerLoad` / `eagerCount` YAML config is
518
+ * still honoured in v3.23.x — its emitted `->with(...)->withCount(...)`
519
+ * chain runs BEFORE the hook, so overrides compose additively. The YAML
520
+ * config will be removed in a future major release per the #81
521
+ * deprecation path.
522
+ */
523
+ function buildEagerLoadHooks() {
524
+ return `
525
+ /**
526
+ * Override in the editable sibling service to eager-load relations
527
+ * on the list() query. Default: no-op. Legacy schema-level eagerLoad
528
+ * configuration still applies before this hook runs.
529
+ *
530
+ * @param \\Illuminate\\Database\\Eloquent\\Builder $query
531
+ */
532
+ protected function applyListEagerLoads($query): void
533
+ {
534
+ // Override in your editable service:
535
+ // $query->with(['author', 'category'])->withCount(['comments']);
536
+ }
537
+
538
+ /**
539
+ * Override in the editable sibling service to eager-load relations on
540
+ * the findById() query. Default: no-op.
541
+ *
542
+ * @param \\Illuminate\\Database\\Eloquent\\Builder $query
543
+ */
544
+ protected function applyFindByIdEagerLoads($query): void
545
+ {
546
+ // Override in your editable service for detail-page eager loads:
547
+ // $query->with([
548
+ // 'author:id,name',
549
+ // 'category',
550
+ // 'comments.author',
551
+ // ]);
552
+ }
553
+
554
+ /**
555
+ * Override in the editable sibling service to eager-load relations on
556
+ * the lookup() query. Default: no-op. Most lookup endpoints should
557
+ * stay lean — eager loading defeats the point of a dropdown projection.
558
+ *
559
+ * @param \\Illuminate\\Database\\Eloquent\\Builder $query
560
+ */
561
+ protected function applyLookupEagerLoads($query): void
562
+ {
563
+ // Override only when the lookup result needs related data that
564
+ // can't come from the sidecar translation table.
565
+ }`;
566
+ }
343
567
  // ============================================================================
344
568
  // #77 follow-up: strict @param array-shape + @example block derivation
345
569
  // ============================================================================
@@ -733,7 +957,7 @@ ${listExample}
733
957
  public function list(array $filters = []): LengthAwarePaginator
734
958
  {
735
959
  $query = $this->model::query()`);
736
- // Eager load
960
+ // Eager load (legacy YAML-configured, deprecated #81)
737
961
  const eagerChain = buildEagerLoadChain(eagerLoad, eagerCount);
738
962
  if (eagerChain) {
739
963
  lines.push(`${eagerChain};`);
@@ -742,6 +966,11 @@ ${listExample}
742
966
  // Close the query chain
743
967
  lines[lines.length - 1] += ';';
744
968
  }
969
+ // #81: user-overridable eager load hook. Runs after the legacy chain so
970
+ // subclass overrides compose additively on top of whatever YAML declared.
971
+ lines.push('');
972
+ lines.push(' // -- Eager loads via #81 hook (override applyListEagerLoads in sibling service) --');
973
+ lines.push(' $this->applyListEagerLoads($query);');
745
974
  // Filterable
746
975
  if (filterableFields.length > 0) {
747
976
  lines.push('');
@@ -888,7 +1117,9 @@ function buildSortSection(_sortableFields, defaultSort, allowedSortColumns, tx)
888
1117
  // ── findById() ──────────────────────────────────────────────────────────────
889
1118
  function buildFindByIdMethod(modelName, eagerLoad, eagerCount, hasSoftDelete) {
890
1119
  const eagerChain = buildEagerLoadChain(eagerLoad, eagerCount);
891
- const chain = eagerChain ? `\n${eagerChain}\n ` : '';
1120
+ // Preserve a leading newline + the chain, but NO trailing whitespace so
1121
+ // the semicolon lands cleanly in `$query = ...${chain};`
1122
+ const chain = eagerChain ? `\n${eagerChain}` : '';
892
1123
  if (!hasSoftDelete) {
893
1124
  return `
894
1125
  /**
@@ -902,7 +1133,10 @@ function buildFindByIdMethod(modelName, eagerLoad, eagerCount, hasSoftDelete) {
902
1133
  */
903
1134
  public function findById(string $id): ${modelName}
904
1135
  {
905
- return $this->model::query()${chain}->findOrFail($id);
1136
+ $query = $this->model::query()${chain};
1137
+ $this->applyFindByIdEagerLoads($query);
1138
+
1139
+ return $query->findOrFail($id);
906
1140
  }`;
907
1141
  }
908
1142
  // #79: soft-deletable schemas accept an opt-in `$withTrashed` flag so admin
@@ -1248,6 +1482,10 @@ ${lookupExample}
1248
1482
  ->select([${selectFields}])
1249
1483
  ->orderBy('${sortCol}', '${sortDir}');
1250
1484
 
1485
+ // #81: override applyLookupEagerLoads in sibling service to add any
1486
+ // relations that the dropdown projection needs (keep it lean!).
1487
+ $this->applyLookupEagerLoads($query);
1488
+
1251
1489
  ${filterBlock}${softDeleteBlock}${limitBlock}
1252
1490
 
1253
1491
  // #76 followup (v3.21.2): use getAttributes() to bypass Astrotomic's
@@ -27,7 +27,12 @@ export function generateTsServices(schemas, options) {
27
27
  // Per-schema generator
28
28
  // ---------------------------------------------------------------------------
29
29
  function generateServiceFile(name, schema, options) {
30
- const svc = schema.options?.service ?? {};
30
+ // #81: options.service can be `false` for explicit opt-out. Treat `false`
31
+ // the same as an empty service block for TS codegen purposes — the Go
32
+ // side already filtered this path when emitting the PHP base, but the
33
+ // TS side runs independently and needs its own narrowing.
34
+ const svcRaw = schema.options?.service;
35
+ const svc = (svcRaw && typeof svcRaw === 'object') ? svcRaw : {};
31
36
  const api = schema.options?.api;
32
37
  const hasSoftDelete = schema.options?.softDelete ?? false;
33
38
  const hasRestore = api?.restore ?? hasSoftDelete;
package/dist/types.d.ts CHANGED
@@ -117,13 +117,32 @@ export interface ApiOptions {
117
117
  readonly middleware?: readonly string[];
118
118
  readonly perPage?: number;
119
119
  }
120
- /** Service layer codegen options — controls BaseService generation. Issue #57. */
120
+ /**
121
+ * Service layer codegen options — controls BaseService generation. Issue #57.
122
+ *
123
+ * #81 (v3.23.0): every field in this interface is DEPRECATED. Use the
124
+ * property-level flags instead (searchable / filterable / sortable /
125
+ * lookupable / defaultSort), which are the single source of truth. This
126
+ * interface is kept for backward compatibility and emits deprecation
127
+ * warnings from the generator when any legacy key is set.
128
+ *
129
+ * The only keys that will survive the deprecation cycle are legitimate
130
+ * service-level overrides that property-level can't express (pagination
131
+ * strategy, per_page default, etc.) — those will be added in a future
132
+ * release when the deprecations clear.
133
+ */
121
134
  export interface ServiceOptions {
135
+ /** @deprecated #81 — use property-level `searchable: true` instead */
122
136
  readonly searchable?: readonly string[];
137
+ /** @deprecated #81 — use property-level `filterable: true` instead */
123
138
  readonly filterable?: readonly string[];
139
+ /** @deprecated #81 — use property-level `defaultSort: 'asc'|'desc'` on exactly one field */
124
140
  readonly defaultSort?: string;
141
+ /** @deprecated #81 — override applyListEagerLoads/applyFindByIdEagerLoads/applyLookupEagerLoads in the editable service instead */
125
142
  readonly eagerLoad?: readonly string[];
143
+ /** @deprecated #81 — override applyListEagerLoads hook in the editable service instead */
126
144
  readonly eagerCount?: readonly string[];
145
+ /** @deprecated #81 — use property-level `lookupable: true` instead */
127
146
  readonly lookupFields?: readonly string[];
128
147
  }
129
148
  /** Schema options. */
@@ -142,8 +161,18 @@ export interface SchemaOptions {
142
161
  readonly updatedBy?: boolean;
143
162
  readonly deletedBy?: boolean;
144
163
  };
145
- /** Service layer codegen options. Issue #57. */
146
- readonly service?: ServiceOptions;
164
+ /**
165
+ * Service layer codegen options (issue #57).
166
+ *
167
+ * #81 (v3.23.0): accepts `false` as an explicit opt-out — every `kind:
168
+ * object` project schema gets a generated base service by default now.
169
+ * Set `service: false` on pivot tables / sidecars / audit logs that
170
+ * shouldn't have a service. Set to an object only for legitimate
171
+ * service-level overrides that property-level flags can't express
172
+ * (legacy property-duplication keys emit deprecation warnings at
173
+ * generate time).
174
+ */
175
+ readonly service?: ServiceOptions | false;
147
176
  /** Schema-level default ordering — generates a global Eloquent scope. Issue #40. */
148
177
  readonly defaultOrder?: readonly OrderByItem[];
149
178
  }
@@ -264,6 +293,21 @@ export interface PropertyDefinition {
264
293
  readonly searchable?: boolean;
265
294
  readonly filterable?: boolean;
266
295
  readonly sortable?: boolean;
296
+ /**
297
+ * #81: mark a property as part of the lookup() projection (id/label/slug
298
+ * shape for type-ahead / dropdown endpoints). When at least one property
299
+ * on a schema has `lookupable: true`, the generator uses that set instead
300
+ * of the legacy `options.service.lookupFields` array.
301
+ */
302
+ readonly lookupable?: boolean;
303
+ /**
304
+ * #81: declare this property as the schema's default sort column. At most
305
+ * one property per schema should set this. The value is the direction:
306
+ * `asc` emits `defaultSort: <col>`, `desc` emits `-<col>`. Legacy
307
+ * `options.service.defaultSort` is still honoured with a deprecation
308
+ * warning.
309
+ */
310
+ readonly defaultSort?: 'asc' | 'desc';
267
311
  readonly fields?: Record<string, FieldOverride>;
268
312
  }
269
313
  /** Single column ordering. Direction defaults to 'asc'. Issue #40. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnifyjp/ts",
3
- "version": "3.22.0",
3
+ "version": "3.23.0",
4
4
  "description": "TypeScript model type generator from Omnify schemas.json",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",