@eide/foir-cli 0.1.33 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -9,37 +9,18 @@ import { createRequire } from 'module';
9
9
  import { Command } from 'commander';
10
10
  const require = createRequire(import.meta.url);
11
11
  const { version } = require('../package.json');
12
- // Auth commands
12
+ // Auth commands (special — not GraphQL-driven)
13
13
  import { registerLoginCommand } from './commands/login.js';
14
14
  import { registerLogoutCommand } from './commands/logout.js';
15
15
  import { registerSelectProjectCommand } from './commands/select-project.js';
16
16
  import { registerWhoamiCommand } from './commands/whoami.js';
17
- // Resource commands
18
- import { registerRecordsCommands } from './commands/records.js';
19
- import { registerModelsCommands } from './commands/models.js';
20
- import { registerSearchCommands } from './commands/search.js';
21
- import { registerCustomersCommands } from './commands/customers.js';
22
- import { registerCustomerProfilesCommands } from './commands/customer-profiles.js';
23
- import { registerSegmentsCommands } from './commands/segments.js';
24
- import { registerExperimentsCommands } from './commands/experiments.js';
25
- import { registerSettingsCommands } from './commands/settings.js';
26
- import { registerApiKeysCommands } from './commands/api-keys.js';
27
- import { registerAuthProvidersCommands } from './commands/auth-providers.js';
28
- import { registerExtensionsCommands } from './commands/extensions.js';
29
- import { registerOperationsCommands } from './commands/operations.js';
30
- import { registerHooksCommands } from './commands/hooks.js';
31
- import { registerSchedulesCommands } from './commands/schedules.js';
17
+ // Special commands (REST, codegen, scaffolding, multi-type search)
32
18
  import { registerMediaCommands } from './commands/media.js';
33
- import { registerContextCommands } from './commands/context.js';
34
- import { registerNotificationsCommands } from './commands/notifications.js';
35
- import { registerLocalesCommands } from './commands/locales.js';
36
- import { registerFilesCommands } from './commands/files.js';
37
- import { registerNotesCommands } from './commands/notes.js';
38
- import { registerVariantCatalogCommands } from './commands/variant-catalog.js';
39
- import { registerAuthConfigCommands } from './commands/auth-config.js';
40
- import { registerEmbeddingsCommands } from './commands/embeddings.js';
41
19
  import { registerPullCommand } from './commands/pull.js';
42
20
  import { registerCreateExtensionCommand } from './commands/create-extension.js';
21
+ import { registerSearchCommands } from './commands/search.js';
22
+ // Dynamic commands from command registry (replaces ~25 imperative command files)
23
+ import { registerDynamicCommands } from './commands/register-commands.js';
43
24
  const program = new Command();
44
25
  program
45
26
  .name('foir')
@@ -58,36 +39,18 @@ function getGlobalOpts() {
58
39
  quiet: !!opts.quiet,
59
40
  };
60
41
  }
61
- // Register all command groups
42
+ // Auth commands
62
43
  registerLoginCommand(program, getGlobalOpts);
63
44
  registerLogoutCommand(program, getGlobalOpts);
64
45
  registerSelectProjectCommand(program, getGlobalOpts);
65
46
  registerWhoamiCommand(program, getGlobalOpts);
66
- registerRecordsCommands(program, getGlobalOpts);
67
- registerModelsCommands(program, getGlobalOpts);
68
- registerSearchCommands(program, getGlobalOpts);
69
- registerCustomersCommands(program, getGlobalOpts);
70
- registerCustomerProfilesCommands(program, getGlobalOpts);
71
- registerSegmentsCommands(program, getGlobalOpts);
72
- registerExperimentsCommands(program, getGlobalOpts);
73
- registerSettingsCommands(program, getGlobalOpts);
74
- registerApiKeysCommands(program, getGlobalOpts);
75
- registerAuthProvidersCommands(program, getGlobalOpts);
76
- registerExtensionsCommands(program, getGlobalOpts);
77
- registerOperationsCommands(program, getGlobalOpts);
78
- registerHooksCommands(program, getGlobalOpts);
79
- registerSchedulesCommands(program, getGlobalOpts);
47
+ // Special commands
80
48
  registerMediaCommands(program, getGlobalOpts);
81
- registerContextCommands(program, getGlobalOpts);
82
- registerNotificationsCommands(program, getGlobalOpts);
83
- registerLocalesCommands(program, getGlobalOpts);
84
- registerFilesCommands(program, getGlobalOpts);
85
- registerNotesCommands(program, getGlobalOpts);
86
- registerVariantCatalogCommands(program, getGlobalOpts);
87
- registerAuthConfigCommands(program, getGlobalOpts);
88
- registerEmbeddingsCommands(program, getGlobalOpts);
49
+ registerSearchCommands(program, getGlobalOpts);
89
50
  // Codegen
90
51
  registerPullCommand(program, getGlobalOpts);
91
52
  // Scaffolding
92
53
  registerCreateExtensionCommand(program, getGlobalOpts);
54
+ // All GraphQL-driven commands (models, records, locales, etc.)
55
+ registerDynamicCommands(program, getGlobalOpts);
93
56
  program.parse();
@@ -1 +1 @@
1
- {"version":3,"file":"field-mapping.d.ts","sourceRoot":"","sources":["../../src/codegen/field-mapping.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,qBAAqB,aAoBhC,CAAC;AAEH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAoD1D,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,iBAAiB,EACxB,IAAI,GAAE,QAAQ,GAAG,OAAkB,GAClC,MAAM,CAqCR;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAW3E;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,iBAAiB,EAAE,GAC1B,GAAG,CAAC,MAAM,CAAC,CAUb;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,iBAAiB,EAAE,GAC1B,GAAG,CAAC,MAAM,CAAC,CAcb;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMpD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CA2EjE"}
1
+ {"version":3,"file":"field-mapping.d.ts","sourceRoot":"","sources":["../../src/codegen/field-mapping.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,qBAAqB,aAqBhC,CAAC;AAEH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAqD1D,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,iBAAiB,EACxB,IAAI,GAAE,QAAQ,GAAG,OAAkB,GAClC,MAAM,CAqCR;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAW3E;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,iBAAiB,EAAE,GAC1B,GAAG,CAAC,MAAM,CAAC,CAUb;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,iBAAiB,EAAE,GAC1B,GAAG,CAAC,MAAM,CAAC,CAcb;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMpD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CA2EjE"}
@@ -22,6 +22,7 @@ export const PRIMITIVE_FIELD_TYPES = new Set([
22
22
  'reference',
23
23
  'link',
24
24
  'flexible',
25
+ 'model',
25
26
  ]);
26
27
  export function isPrimitiveFieldType(type) {
27
28
  return PRIMITIVE_FIELD_TYPES.has(type);
@@ -78,6 +79,7 @@ export const FIELD_TYPE_MAPPING = {
78
79
  inputType: 'LinkValue',
79
80
  needsImport: 'field-types',
80
81
  },
82
+ model: { outputType: 'string', inputType: 'string' },
81
83
  };
82
84
  export function getFieldType(field, mode = 'output') {
83
85
  if (!field?.type)
@@ -1 +1 @@
1
- {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../../src/codegen/generators/documents.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAiGlE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAoBhD"}
1
+ {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../../src/codegen/generators/documents.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAyGlE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAoBhD"}
@@ -25,10 +25,10 @@ fragment ${typeName}Fields on Record {
25
25
  updatedAt
26
26
  }
27
27
 
28
- query Get${typeName}($id: ID!, $locale: String, $preview: Boolean) {
28
+ query Get${typeName}($id: ID!, $locale: String, $preview: Boolean, $fields: FieldSelectionInput) {
29
29
  record(id: $id) {
30
30
  ...${typeName}Fields
31
- resolved(locale: $locale, preview: $preview) {
31
+ resolved(locale: $locale, preview: $preview, fields: $fields) {
32
32
  content
33
33
  record { id modelKey naturalKey }
34
34
  version { id versionNumber }
@@ -36,10 +36,10 @@ query Get${typeName}($id: ID!, $locale: String, $preview: Boolean) {
36
36
  }
37
37
  }
38
38
 
39
- query Get${typeName}ByKey($naturalKey: String!, $locale: String, $preview: Boolean) {
39
+ query Get${typeName}ByKey($naturalKey: String!, $locale: String, $preview: Boolean, $fields: FieldSelectionInput) {
40
40
  recordByKey(modelKey: "${model.key}", naturalKey: $naturalKey) {
41
41
  ...${typeName}Fields
42
- resolved(locale: $locale, preview: $preview) {
42
+ resolved(locale: $locale, preview: $preview, fields: $fields) {
43
43
  content
44
44
  record { id modelKey naturalKey }
45
45
  version { id versionNumber }
@@ -52,6 +52,9 @@ query List${pluralName}(
52
52
  $offset: Int
53
53
  $filters: [FilterInput!]
54
54
  $sort: SortInput
55
+ $locale: String
56
+ $preview: Boolean
57
+ $fields: FieldSelectionInput
55
58
  ) {
56
59
  records(
57
60
  modelKey: "${model.key}"
@@ -62,6 +65,11 @@ query List${pluralName}(
62
65
  ) {
63
66
  items {
64
67
  ...${typeName}Fields
68
+ resolved(locale: $locale, preview: $preview, fields: $fields) {
69
+ content
70
+ record { id modelKey naturalKey }
71
+ version { id versionNumber }
72
+ }
65
73
  }
66
74
  total
67
75
  }
@@ -147,7 +147,8 @@ function generateDataInterface(model, fields, interfaceName, allModels) {
147
147
  }
148
148
  }
149
149
  // Only parameterize if all targets resolved — otherwise keep default generic
150
- if (resolvedPreviewTypes.length === refTypes.length && resolvedPreviewTypes.length > 0) {
150
+ if (resolvedPreviewTypes.length === refTypes.length &&
151
+ resolvedPreviewTypes.length > 0) {
151
152
  fieldType = `ReferenceValue<${resolvedPreviewTypes.join(' | ')}>`;
152
153
  }
153
154
  }
@@ -1 +1 @@
1
- {"version":3,"file":"typed-operations-common.d.ts","sourceRoot":"","sources":["../../../src/codegen/generators/typed-operations-common.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAuE1E"}
1
+ {"version":3,"file":"typed-operations-common.d.ts","sourceRoot":"","sources":["../../../src/codegen/generators/typed-operations-common.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CA+E1E"}
@@ -35,7 +35,7 @@ export interface ResolvedContent<T> {
35
35
 
36
36
  /** Paginated list result. */
37
37
  export interface PaginatedResult<T> {
38
- items: BaseRecord<T>[];
38
+ items: (BaseRecord<T> & { resolved: ResolvedContent<T> | null })[];
39
39
  total: number;
40
40
  }
41
41
 
@@ -72,5 +72,13 @@ export interface ShareResult {
72
72
  export interface ShareWithRecord<T> extends ShareResult {
73
73
  record: BaseRecord<T>;
74
74
  }
75
+
76
+ /** Field selection for resolved content — pick or omit specific fields. */
77
+ export interface FieldSelection<T = Record<string, unknown>> {
78
+ /** Include only these field keys (mutually exclusive with omit) */
79
+ pick?: (keyof T & string)[];
80
+ /** Exclude these field keys (mutually exclusive with omit) */
81
+ omit?: (keyof T & string)[];
82
+ }
75
83
  `;
76
84
  }
@@ -1 +1 @@
1
- {"version":3,"file":"typed-operations.d.ts","sourceRoot":"","sources":["../../../src/codegen/generators/typed-operations.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,YAAY,EACnB,YAAY,EAAE,MAAM,GACnB,MAAM,CA6QR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG5E"}
1
+ {"version":3,"file":"typed-operations.d.ts","sourceRoot":"","sources":["../../../src/codegen/generators/typed-operations.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,YAAY,EACnB,YAAY,EAAE,MAAM,GACnB,MAAM,CAwRR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG5E"}
@@ -29,7 +29,8 @@ import type {
29
29
  PaginatedResult,
30
30
  CreateRecordResult,
31
31
  UpdateRecordResult,
32
- DeleteRecordResult,${model.config.sharing?.enabled ? '\n ShareResult,\n ShareWithRecord,' : ''}
32
+ DeleteRecordResult,
33
+ FieldSelection,${model.config.sharing?.enabled ? '\n ShareResult,\n ShareWithRecord,' : ''}
33
34
  } from './_common.js';
34
35
  `);
35
36
  // Type alias
@@ -37,12 +38,12 @@ import type {
37
38
  lines.push('');
38
39
  // GET query
39
40
  lines.push(`export const GET_${upperSnake} = \`
40
- query Get${typeName}($id: ID!, $locale: String, $preview: Boolean) {
41
+ query Get${typeName}($id: ID!, $locale: String, $preview: Boolean, $fields: FieldSelectionInput) {
41
42
  record(id: $id) {
42
43
  id modelKey naturalKey data metadata
43
44
  publishedVersionNumber publishedAt versionNumber changeDescription
44
45
  createdAt updatedAt
45
- resolved(locale: $locale, preview: $preview) {
46
+ resolved(locale: $locale, preview: $preview, fields: $fields) {
46
47
  content
47
48
  record { id modelKey naturalKey }
48
49
  version { id versionNumber }
@@ -55,6 +56,7 @@ import type {
55
56
  id: string;
56
57
  locale?: string;
57
58
  preview?: boolean;
59
+ fields?: FieldSelection<${dataType}>;
58
60
  }`);
59
61
  lines.push('');
60
62
  lines.push(`export interface Get${typeName}Result {
@@ -65,12 +67,12 @@ import type {
65
67
  lines.push('');
66
68
  // GET BY KEY query
67
69
  lines.push(`export const GET_${upperSnake}_BY_KEY = \`
68
- query Get${typeName}ByKey($naturalKey: String!, $locale: String, $preview: Boolean) {
70
+ query Get${typeName}ByKey($naturalKey: String!, $locale: String, $preview: Boolean, $fields: FieldSelectionInput) {
69
71
  recordByKey(modelKey: "${model.key}", naturalKey: $naturalKey) {
70
72
  id modelKey naturalKey data metadata
71
73
  publishedVersionNumber publishedAt versionNumber changeDescription
72
74
  createdAt updatedAt
73
- resolved(locale: $locale, preview: $preview) {
75
+ resolved(locale: $locale, preview: $preview, fields: $fields) {
74
76
  content
75
77
  record { id modelKey naturalKey }
76
78
  version { id versionNumber }
@@ -83,6 +85,7 @@ import type {
83
85
  naturalKey: string;
84
86
  locale?: string;
85
87
  preview?: boolean;
88
+ fields?: FieldSelection<${dataType}>;
86
89
  }`);
87
90
  lines.push('');
88
91
  lines.push(`export interface Get${typeName}ByKeyResult {
@@ -93,12 +96,17 @@ import type {
93
96
  lines.push('');
94
97
  // LIST query
95
98
  lines.push(`export const LIST_${pluralUpperSnake} = \`
96
- query List${pluralName}($limit: Int, $offset: Int, $filters: [FilterInput!], $sort: SortInput) {
99
+ query List${pluralName}($limit: Int, $offset: Int, $filters: [FilterInput!], $sort: SortInput, $locale: String, $preview: Boolean, $fields: FieldSelectionInput) {
97
100
  records(modelKey: "${model.key}", limit: $limit, offset: $offset, filters: $filters, sort: $sort) {
98
101
  items {
99
102
  id modelKey naturalKey data metadata
100
103
  publishedVersionNumber publishedAt versionNumber changeDescription
101
104
  createdAt updatedAt
105
+ resolved(locale: $locale, preview: $preview, fields: $fields) {
106
+ content
107
+ record { id modelKey naturalKey }
108
+ version { id versionNumber }
109
+ }
102
110
  }
103
111
  total
104
112
  }
@@ -110,6 +118,9 @@ import type {
110
118
  offset?: number;
111
119
  filters?: Array<{ field: string; operator: string; value: JsonValue }>;
112
120
  sort?: { field: string; direction: 'ASC' | 'DESC' };
121
+ locale?: string;
122
+ preview?: boolean;
123
+ fields?: FieldSelection<${dataType}>;
113
124
  }`);
114
125
  lines.push('');
115
126
  lines.push(`export interface List${pluralName}Result {
@@ -1 +1 @@
1
- {"version":3,"file":"swift-field-mapping.d.ts","sourceRoot":"","sources":["../../src/codegen/swift-field-mapping.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAC;IACxB,gFAAgF;IAChF,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAwHrE,CAAC;AAEF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACvC,CAeA"}
1
+ {"version":3,"file":"swift-field-mapping.d.ts","sourceRoot":"","sources":["../../src/codegen/swift-field-mapping.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAC;IACxB,gFAAgF;IAChF,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CA8HrE,CAAC;AAEF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACvC,CAeA"}
@@ -124,6 +124,12 @@ export const SWIFT_FIELD_TYPE_MAPPING = {
124
124
  castExpression: 'as? [String: Any]',
125
125
  needsSharedType: true,
126
126
  },
127
+ model: {
128
+ type: 'String',
129
+ alwaysOptional: true,
130
+ defaultValue: '""',
131
+ castExpression: 'as? String',
132
+ },
127
133
  };
128
134
  /**
129
135
  * Get the Swift type for a field.
@@ -0,0 +1,4 @@
1
+ import { type Command } from 'commander';
2
+ import type { GlobalOptions } from '../lib/config.js';
3
+ export declare function registerPlaygroundCommand(program: Command, getGlobalOpts: () => GlobalOptions): void;
4
+ //# sourceMappingURL=playground.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playground.d.ts","sourceRoot":"","sources":["../../src/commands/playground.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAMzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAgCtD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,MAAM,aAAa,QA8QnC"}
@@ -0,0 +1,270 @@
1
+ import chalk from 'chalk';
2
+ import * as readline from 'readline';
3
+ import { homedir } from 'os';
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { createClient } from '../lib/client.js';
7
+ import { getProjectContext } from '../auth/credentials.js';
8
+ import { ListModelsDocument } from '../graphql/generated.js';
9
+ const HISTORY_FILE = join(homedir(), '.foir', 'repl-history');
10
+ const MAX_HISTORY = 500;
11
+ function loadHistory() {
12
+ try {
13
+ if (existsSync(HISTORY_FILE)) {
14
+ return readFileSync(HISTORY_FILE, 'utf-8')
15
+ .split('\n')
16
+ .filter(Boolean)
17
+ .slice(-MAX_HISTORY);
18
+ }
19
+ }
20
+ catch {
21
+ // ignore
22
+ }
23
+ return [];
24
+ }
25
+ function saveHistory(lines) {
26
+ try {
27
+ const dir = join(homedir(), '.foir');
28
+ if (!existsSync(dir))
29
+ mkdirSync(dir, { recursive: true });
30
+ writeFileSync(HISTORY_FILE, lines.slice(-MAX_HISTORY).join('\n') + '\n');
31
+ }
32
+ catch {
33
+ // ignore
34
+ }
35
+ }
36
+ export function registerPlaygroundCommand(program, getGlobalOpts) {
37
+ program
38
+ .command('playground')
39
+ .alias('repl')
40
+ .description('Interactive REPL with tab completion')
41
+ .action(async () => {
42
+ const opts = getGlobalOpts();
43
+ // Validate auth and create client
44
+ let client;
45
+ try {
46
+ client = await createClient(opts);
47
+ }
48
+ catch (err) {
49
+ throw new Error('Authentication required. Run `foir login` first.');
50
+ }
51
+ // Pre-fetch model keys for tab completion (don't block startup)
52
+ let modelKeys = [];
53
+ client
54
+ .request(ListModelsDocument, { limit: 200 })
55
+ .then((data) => {
56
+ modelKeys = data.models.items.map((m) => m.key);
57
+ })
58
+ .catch(() => {
59
+ /* ignore — completion just won't have model keys */
60
+ });
61
+ // Get project context for prompt
62
+ let projectName = 'no project';
63
+ try {
64
+ const ctx = await getProjectContext();
65
+ if (ctx?.name)
66
+ projectName = ctx.name;
67
+ }
68
+ catch {
69
+ // no project context
70
+ }
71
+ // Collect command names for completion
72
+ const commandNames = program.commands.map((c) => c.name());
73
+ const commandMap = new Map();
74
+ for (const cmd of program.commands) {
75
+ commandMap.set(cmd.name(), cmd);
76
+ for (const alias of cmd.aliases()) {
77
+ commandMap.set(alias, cmd);
78
+ }
79
+ }
80
+ // Build completer
81
+ const metaCommands = [
82
+ '.help',
83
+ '.clear',
84
+ '.exit',
85
+ '.commands',
86
+ '.context',
87
+ '.models',
88
+ ];
89
+ function completer(line) {
90
+ const trimmed = line.trim();
91
+ const parts = trimmed.split(/\s+/);
92
+ // Meta commands
93
+ if (parts.length === 1 && trimmed.startsWith('.')) {
94
+ const hits = metaCommands.filter((c) => c.startsWith(trimmed));
95
+ return [hits.length ? hits : metaCommands, trimmed];
96
+ }
97
+ // First word: command names
98
+ if (parts.length <= 1) {
99
+ const hits = commandNames.filter((c) => c.startsWith(trimmed));
100
+ return [hits.length ? hits : commandNames, trimmed];
101
+ }
102
+ // Second word: subcommands
103
+ const firstPart = parts[0] ?? '';
104
+ const cmd = commandMap.get(firstPart);
105
+ if (cmd && parts.length === 2) {
106
+ const subNames = cmd.commands.map((c) => c.name());
107
+ const partial = parts[1] ?? '';
108
+ const hits = subNames.filter((s) => s.startsWith(partial));
109
+ return [hits.length ? hits : subNames, partial];
110
+ }
111
+ // Level 3: model keys for records/search commands
112
+ const cmdName = cmd?.name();
113
+ if ((cmdName === 'records' || cmdName === 'search') &&
114
+ parts.length === 3 &&
115
+ modelKeys.length > 0) {
116
+ const partial = parts[2] ?? '';
117
+ const hits = modelKeys.filter((k) => k.startsWith(partial));
118
+ return [hits.length ? hits : modelKeys, partial];
119
+ }
120
+ // Flags
121
+ if (cmd) {
122
+ const lastPart = parts[parts.length - 1] ?? '';
123
+ if (lastPart.startsWith('-')) {
124
+ const flags = cmd.options.map((o) => o.long).filter((f) => Boolean(f));
125
+ const hits = flags.filter((f) => f.startsWith(lastPart));
126
+ return [hits.length ? hits : [], lastPart];
127
+ }
128
+ }
129
+ return [[], trimmed];
130
+ }
131
+ // Load history
132
+ const history = loadHistory();
133
+ const rl = readline.createInterface({
134
+ input: process.stdin,
135
+ output: process.stdout,
136
+ prompt: chalk.cyan(`foir (${projectName})> `),
137
+ completer,
138
+ history,
139
+ historySize: MAX_HISTORY,
140
+ });
141
+ console.log(chalk.bold('\nFoir Interactive Playground'));
142
+ console.log(chalk.dim('Type commands without the "foir" prefix. Use Tab for completion.'));
143
+ console.log(chalk.dim('Type .help for available meta-commands.\n'));
144
+ rl.prompt();
145
+ // Prevent Commander from calling process.exit()
146
+ program.exitOverride();
147
+ rl.on('line', async (line) => {
148
+ const input = line.trim();
149
+ if (!input) {
150
+ rl.prompt();
151
+ return;
152
+ }
153
+ // Meta commands
154
+ if (input === '.help') {
155
+ console.log(chalk.bold('\nMeta-commands:'));
156
+ console.log(' .help Show this help');
157
+ console.log(' .clear Clear terminal');
158
+ console.log(' .exit Exit playground');
159
+ console.log(' .commands List available commands');
160
+ console.log(' .context Show current auth/project context');
161
+ console.log(' .models Refresh and list model keys');
162
+ console.log(chalk.dim('\nRun any foir command without the "foir" prefix.'));
163
+ console.log(chalk.dim('Example: records list page\n'));
164
+ rl.prompt();
165
+ return;
166
+ }
167
+ if (input === '.clear') {
168
+ console.clear();
169
+ rl.prompt();
170
+ return;
171
+ }
172
+ if (input === '.exit' || input === 'exit' || input === 'quit') {
173
+ saveHistory(rl.history || []);
174
+ console.log(chalk.dim('Goodbye!'));
175
+ rl.close();
176
+ return;
177
+ }
178
+ if (input === '.commands') {
179
+ console.log(chalk.bold('\nAvailable commands:'));
180
+ for (const cmd of program.commands) {
181
+ if (cmd.name() === 'playground')
182
+ continue;
183
+ const desc = cmd.description() || '';
184
+ console.log(` ${chalk.green(cmd.name().padEnd(24))} ${chalk.dim(desc)}`);
185
+ }
186
+ console.log('');
187
+ rl.prompt();
188
+ return;
189
+ }
190
+ if (input === '.context') {
191
+ try {
192
+ const ctx = await getProjectContext();
193
+ console.log(chalk.bold('\nCurrent context:'));
194
+ if (ctx) {
195
+ console.log(` Project: ${chalk.green(ctx.name || 'unnamed')}`);
196
+ console.log(` ID: ${chalk.dim(ctx.id || 'n/a')}`);
197
+ console.log(` Tenant: ${chalk.dim(ctx.tenantId || 'n/a')}`);
198
+ }
199
+ else {
200
+ console.log(chalk.yellow(' No project selected. Run: select-project'));
201
+ }
202
+ }
203
+ catch {
204
+ console.log(chalk.yellow(' No project context available.'));
205
+ }
206
+ console.log('');
207
+ rl.prompt();
208
+ return;
209
+ }
210
+ if (input === '.models') {
211
+ try {
212
+ const data = await client.request(ListModelsDocument, {
213
+ limit: 200,
214
+ });
215
+ modelKeys = data.models.items.map((m) => m.key);
216
+ console.log(chalk.bold('\nModel keys:'));
217
+ for (const key of modelKeys) {
218
+ console.log(` ${chalk.green(key)}`);
219
+ }
220
+ if (modelKeys.length === 0) {
221
+ console.log(chalk.yellow(' No models found.'));
222
+ }
223
+ }
224
+ catch {
225
+ console.log(chalk.yellow(' Failed to fetch models.'));
226
+ }
227
+ console.log('');
228
+ rl.prompt();
229
+ return;
230
+ }
231
+ // Regular command - parse and execute via Commander
232
+ const args = input.match(/(?:[^\s"']+|"[^"]*"|'[^']*')/g) || [];
233
+ // Strip quotes from arguments
234
+ const cleanArgs = args.map((a) => a.replace(/^["']|["']$/g, ''));
235
+ try {
236
+ await program.parseAsync(['node', 'foir', ...cleanArgs], {
237
+ from: 'user',
238
+ });
239
+ }
240
+ catch (err) {
241
+ // CommanderError is thrown by exitOverride instead of process.exit
242
+ if (err.code === 'commander.helpDisplayed' ||
243
+ err.code === 'commander.version') {
244
+ // Help/version output already printed
245
+ }
246
+ else if (err.code === 'commander.unknownCommand') {
247
+ console.error(chalk.red(`Unknown command: ${cleanArgs[0]}`));
248
+ console.log(chalk.dim('Type .commands to see available commands.'));
249
+ }
250
+ else if (err.exitCode !== undefined) {
251
+ // Commander error with exit code - already printed message
252
+ }
253
+ else {
254
+ // Application error
255
+ console.error(chalk.red(err.message || String(err)));
256
+ }
257
+ }
258
+ console.log(''); // spacing
259
+ rl.prompt();
260
+ });
261
+ rl.on('close', () => {
262
+ saveHistory(rl.history || []);
263
+ });
264
+ // Handle SIGINT gracefully
265
+ rl.on('SIGINT', () => {
266
+ console.log(chalk.dim('\n(Use .exit or Ctrl+D to quit)'));
267
+ rl.prompt();
268
+ });
269
+ });
270
+ }
@@ -0,0 +1,7 @@
1
+ import type { Command } from 'commander';
2
+ import type { GlobalOptions } from '../lib/config.js';
3
+ /**
4
+ * Register all declarative commands from the command registry.
5
+ */
6
+ export declare function registerDynamicCommands(program: Command, globalOpts: () => GlobalOptions): void;
7
+ //# sourceMappingURL=register-commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-commands.d.ts","sourceRoot":"","sources":["../../src/commands/register-commands.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AA0GtD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,aAAa,GAC9B,IAAI,CA8LN"}