@compilr-dev/factory 0.1.1 → 0.1.3

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.
Files changed (2) hide show
  1. package/dist/model/tools.js +137 -69
  2. package/package.json +1 -1
@@ -112,7 +112,18 @@ function createAppModelGetTool(config) {
112
112
  function createAppModelUpdateTool(config) {
113
113
  return defineTool({
114
114
  name: 'app_model_update',
115
- description: `Apply a semantic operation to the Application Model. Operations: addEntity, updateEntity, removeEntity, renameEntity, reorderEntities, addField, updateField, removeField, renameField, addRelationship, removeRelationship, updateIdentity, updateLayout, updateFeatures, updateTheme, updateTechStack. If no model exists, one is created automatically with the first addEntity.`,
115
+ description: `Apply a semantic operation to the Application Model. If no model exists, one is created automatically.
116
+
117
+ EXAMPLES:
118
+ - updateIdentity: { "op": "updateIdentity", "updates": { "name": "My App", "description": "A task manager" } }
119
+ - updateTechStack: { "op": "updateTechStack", "updates": { "toolkit": "react-node" } }
120
+ - addEntity: { "op": "addEntity", "entity": { "name": "Task", "pluralName": "Tasks", "icon": "📋", "fields": [{ "name": "title", "label": "Title", "type": "string", "required": true }], "views": ["list", "detail", "card"], "relationships": [] } }
121
+ - addField: { "op": "addField", "entity": "Task", "field": { "name": "dueDate", "label": "Due Date", "type": "date", "required": false } }
122
+ - addRelationship: { "op": "addRelationship", "entity": "Task", "relationship": { "type": "belongsTo", "target": "Project" } }
123
+ - updateFeatures: { "op": "updateFeatures", "updates": { "dashboard": true, "darkMode": true } }
124
+ - updateTheme: { "op": "updateTheme", "updates": { "primaryColor": "#1976D2" } }
125
+ - updateLayout: { "op": "updateLayout", "updates": { "shell": "sidebar-header" } }
126
+ - removeEntity: { "op": "removeEntity", "entity": "Task", "force": true }`,
116
127
  inputSchema: {
117
128
  type: 'object',
118
129
  properties: {
@@ -143,23 +154,31 @@ function createAppModelUpdateTool(config) {
143
154
  description: 'Expected current revision for optimistic locking. Optional.',
144
155
  },
145
156
  entity: {
146
- description: 'Entity name (string) or full entity object (for addEntity).',
157
+ oneOf: [{ type: 'string' }, { type: 'object', additionalProperties: true }],
158
+ description: 'Entity name (string) for most ops, or full entity object (for addEntity). Entity object requires: name (PascalCase), pluralName, icon, fields array, views array, relationships array.',
147
159
  },
148
160
  field: {
149
- description: 'Field name (string) or full field object (for addField).',
161
+ oneOf: [{ type: 'string' }, { type: 'object', additionalProperties: true }],
162
+ description: 'Field name (string) for updateField/removeField/renameField, or full field object (for addField). Field object requires: name (camelCase), label, type (string|number|boolean|date|enum), required.',
150
163
  },
151
164
  relationship: {
152
- type: 'object',
153
- description: 'Relationship object for addRelationship.',
154
- properties: {
155
- type: { type: 'string', enum: ['belongsTo', 'hasMany'] },
156
- target: { type: 'string' },
157
- fieldName: { type: 'string' },
158
- },
165
+ oneOf: [
166
+ {
167
+ type: 'object',
168
+ additionalProperties: true,
169
+ properties: {
170
+ type: { type: 'string', enum: ['belongsTo', 'hasMany'] },
171
+ target: { type: 'string' },
172
+ fieldName: { type: 'string' },
173
+ },
174
+ },
175
+ { type: 'string' },
176
+ ],
177
+ description: 'Relationship object for addRelationship. Requires: type ("belongsTo" or "hasMany"), target (entity name). Optional: fieldName.',
159
178
  },
160
179
  updates: {
161
- type: 'object',
162
- description: 'Partial updates for update operations.',
180
+ oneOf: [{ type: 'object', additionalProperties: true }, { type: 'string' }],
181
+ description: 'Partial updates object for updateIdentity, updateLayout, updateFeatures, updateTheme, updateTechStack, updateEntity.',
163
182
  },
164
183
  newName: {
165
184
  type: 'string',
@@ -222,28 +241,53 @@ function createAppModelUpdateTool(config) {
222
241
  },
223
242
  });
224
243
  }
244
+ /**
245
+ * Coerce a value to an object. LLMs sometimes send stringified JSON for nested objects.
246
+ * Returns the parsed object, or null if coercion fails.
247
+ */
248
+ function coerceToObject(value) {
249
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
250
+ return value;
251
+ }
252
+ if (typeof value === 'string') {
253
+ try {
254
+ const parsed = JSON.parse(value);
255
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
256
+ return parsed;
257
+ }
258
+ }
259
+ catch {
260
+ // Not valid JSON
261
+ }
262
+ }
263
+ return null;
264
+ }
225
265
  function buildOperation(input) {
226
266
  switch (input.op) {
227
267
  case 'addEntity': {
228
- if (!input.entity || typeof input.entity !== 'object') {
229
- throw new Error('addEntity requires entity object');
268
+ const entity = coerceToObject(input.entity);
269
+ if (!entity) {
270
+ throw new Error(`addEntity requires "entity" as an object with name, pluralName, icon, fields, views, relationships. ` +
271
+ `Got: ${input.entity === undefined ? 'missing' : typeof input.entity}. ` +
272
+ `Example: { "op": "addEntity", "entity": { "name": "Employee", "pluralName": "Employees", "icon": "👤", "fields": [{ "name": "name", "label": "Name", "type": "string", "required": true }], "views": ["list", "detail", "card"], "relationships": [] } }`);
230
273
  }
231
- const entity = input.entity;
232
- return { op: 'addEntity', entity };
274
+ return { op: 'addEntity', entity: entity };
233
275
  }
234
- case 'updateEntity':
276
+ case 'updateEntity': {
235
277
  if (typeof input.entity !== 'string')
236
- throw new Error('updateEntity requires entity name (string)');
237
- if (!input.updates)
238
- throw new Error('updateEntity requires updates object');
239
- return { op: 'updateEntity', entity: input.entity, updates: input.updates };
278
+ throw new Error(`updateEntity requires "entity" as a string (entity name). Got: ${input.entity === undefined ? 'missing' : typeof input.entity}`);
279
+ const updates = coerceToObject(input.updates);
280
+ if (!updates)
281
+ throw new Error(`updateEntity requires "updates" object. Got: ${input.updates === undefined ? 'missing' : typeof input.updates}`);
282
+ return { op: 'updateEntity', entity: input.entity, updates };
283
+ }
240
284
  case 'removeEntity':
241
285
  if (typeof input.entity !== 'string')
242
- throw new Error('removeEntity requires entity name (string)');
286
+ throw new Error(`removeEntity requires "entity" as a string (entity name). Got: ${input.entity === undefined ? 'missing' : typeof input.entity}`);
243
287
  return { op: 'removeEntity', entity: input.entity, force: input.force };
244
288
  case 'renameEntity':
245
289
  if (typeof input.entity !== 'string')
246
- throw new Error('renameEntity requires entity name (string)');
290
+ throw new Error(`renameEntity requires "entity" as a string (entity name). Got: ${input.entity === undefined ? 'missing' : typeof input.entity}`);
247
291
  if (!input.newName)
248
292
  throw new Error('renameEntity requires newName');
249
293
  return { op: 'renameEntity', entity: input.entity, newName: input.newName };
@@ -253,36 +297,39 @@ function buildOperation(input) {
253
297
  return { op: 'reorderEntities', order: input.order };
254
298
  case 'addField': {
255
299
  if (typeof input.entity !== 'string')
256
- throw new Error('addField requires entity name (string)');
257
- if (!input.field || typeof input.field !== 'object')
258
- throw new Error('addField requires field object');
259
- const field = input.field;
260
- return { op: 'addField', entity: input.entity, field };
261
- }
262
- case 'updateField':
263
- if (typeof input.entity !== 'string')
264
- throw new Error('updateField requires entity name (string)');
265
- if (typeof input.field !== 'string')
266
- throw new Error('updateField requires field name (string)');
267
- if (!input.updates)
268
- throw new Error('updateField requires updates object');
300
+ throw new Error(`addField requires "entity" as a string (entity name). Got: ${input.entity === undefined ? 'missing' : typeof input.entity}`);
301
+ const field = coerceToObject(input.field);
302
+ if (!field)
303
+ throw new Error(`addField requires "field" as an object with name, label, type, required. ` +
304
+ `Got: ${input.field === undefined ? 'missing' : typeof input.field}. ` +
305
+ `Example: { "op": "addField", "entity": "Employee", "field": { "name": "email", "label": "Email", "type": "string", "required": true } }`);
269
306
  return {
270
- op: 'updateField',
307
+ op: 'addField',
271
308
  entity: input.entity,
272
- field: input.field,
273
- updates: input.updates,
309
+ field: field,
274
310
  };
311
+ }
312
+ case 'updateField': {
313
+ if (typeof input.entity !== 'string')
314
+ throw new Error(`updateField requires "entity" as a string (entity name). Got: ${input.entity === undefined ? 'missing' : typeof input.entity}`);
315
+ if (typeof input.field !== 'string')
316
+ throw new Error(`updateField requires "field" as a string (field name). Got: ${input.field === undefined ? 'missing' : typeof input.field}`);
317
+ const updates = coerceToObject(input.updates);
318
+ if (!updates)
319
+ throw new Error(`updateField requires "updates" object. Got: ${input.updates === undefined ? 'missing' : typeof input.updates}`);
320
+ return { op: 'updateField', entity: input.entity, field: input.field, updates };
321
+ }
275
322
  case 'removeField':
276
323
  if (typeof input.entity !== 'string')
277
- throw new Error('removeField requires entity name (string)');
324
+ throw new Error(`removeField requires "entity" as a string (entity name). Got: ${input.entity === undefined ? 'missing' : typeof input.entity}`);
278
325
  if (typeof input.field !== 'string')
279
- throw new Error('removeField requires field name (string)');
326
+ throw new Error(`removeField requires "field" as a string (field name). Got: ${input.field === undefined ? 'missing' : typeof input.field}`);
280
327
  return { op: 'removeField', entity: input.entity, field: input.field };
281
328
  case 'renameField':
282
329
  if (typeof input.entity !== 'string')
283
- throw new Error('renameField requires entity name (string)');
330
+ throw new Error(`renameField requires "entity" as a string (entity name). Got: ${input.entity === undefined ? 'missing' : typeof input.entity}`);
284
331
  if (typeof input.field !== 'string')
285
- throw new Error('renameField requires field name (string)');
332
+ throw new Error(`renameField requires "field" as a string (field name). Got: ${input.field === undefined ? 'missing' : typeof input.field}`);
286
333
  if (!input.newName)
287
334
  throw new Error('renameField requires newName');
288
335
  return {
@@ -293,38 +340,59 @@ function buildOperation(input) {
293
340
  };
294
341
  case 'addRelationship': {
295
342
  if (typeof input.entity !== 'string')
296
- throw new Error('addRelationship requires entity name (string)');
297
- if (!input.relationship)
298
- throw new Error('addRelationship requires relationship object');
299
- const relationship = input.relationship;
300
- return { op: 'addRelationship', entity: input.entity, relationship };
343
+ throw new Error(`addRelationship requires "entity" as a string (entity name). Got: ${input.entity === undefined ? 'missing' : typeof input.entity}`);
344
+ const relationship = coerceToObject(input.relationship);
345
+ if (!relationship)
346
+ throw new Error(`addRelationship requires "relationship" object with type and target. ` +
347
+ `Got: ${input.relationship === undefined ? 'missing' : typeof input.relationship}. ` +
348
+ `Example: { "op": "addRelationship", "entity": "Task", "relationship": { "type": "belongsTo", "target": "Project" } }`);
349
+ return {
350
+ op: 'addRelationship',
351
+ entity: input.entity,
352
+ relationship: relationship,
353
+ };
301
354
  }
302
355
  case 'removeRelationship':
303
356
  if (typeof input.entity !== 'string')
304
- throw new Error('removeRelationship requires entity name (string)');
357
+ throw new Error(`removeRelationship requires "entity" as a string (entity name). Got: ${input.entity === undefined ? 'missing' : typeof input.entity}`);
305
358
  if (!input.target)
306
359
  throw new Error('removeRelationship requires target');
307
360
  return { op: 'removeRelationship', entity: input.entity, target: input.target };
308
- case 'updateIdentity':
309
- if (!input.updates)
310
- throw new Error('updateIdentity requires updates object');
311
- return { op: 'updateIdentity', updates: input.updates };
312
- case 'updateLayout':
313
- if (!input.updates)
314
- throw new Error('updateLayout requires updates object');
315
- return { op: 'updateLayout', updates: input.updates };
316
- case 'updateFeatures':
317
- if (!input.updates)
318
- throw new Error('updateFeatures requires updates object');
319
- return { op: 'updateFeatures', updates: input.updates };
320
- case 'updateTheme':
321
- if (!input.updates)
322
- throw new Error('updateTheme requires updates object');
323
- return { op: 'updateTheme', updates: input.updates };
324
- case 'updateTechStack':
325
- if (!input.updates)
326
- throw new Error('updateTechStack requires updates object');
327
- return { op: 'updateTechStack', updates: input.updates };
361
+ case 'updateIdentity': {
362
+ const updates = coerceToObject(input.updates);
363
+ if (!updates)
364
+ throw new Error(`updateIdentity requires "updates" object. Got: ${input.updates === undefined ? 'missing' : typeof input.updates}. ` +
365
+ `Example: { "op": "updateIdentity", "updates": { "name": "My App", "description": "An app" } }`);
366
+ return { op: 'updateIdentity', updates };
367
+ }
368
+ case 'updateLayout': {
369
+ const updates = coerceToObject(input.updates);
370
+ if (!updates)
371
+ throw new Error(`updateLayout requires "updates" object. Got: ${input.updates === undefined ? 'missing' : typeof input.updates}. ` +
372
+ `Example: { "op": "updateLayout", "updates": { "shell": "sidebar-header" } }`);
373
+ return { op: 'updateLayout', updates };
374
+ }
375
+ case 'updateFeatures': {
376
+ const updates = coerceToObject(input.updates);
377
+ if (!updates)
378
+ throw new Error(`updateFeatures requires "updates" object. Got: ${input.updates === undefined ? 'missing' : typeof input.updates}. ` +
379
+ `Example: { "op": "updateFeatures", "updates": { "dashboard": true } }`);
380
+ return { op: 'updateFeatures', updates };
381
+ }
382
+ case 'updateTheme': {
383
+ const updates = coerceToObject(input.updates);
384
+ if (!updates)
385
+ throw new Error(`updateTheme requires "updates" object. Got: ${input.updates === undefined ? 'missing' : typeof input.updates}. ` +
386
+ `Example: { "op": "updateTheme", "updates": { "primaryColor": "#1976D2" } }`);
387
+ return { op: 'updateTheme', updates };
388
+ }
389
+ case 'updateTechStack': {
390
+ const updates = coerceToObject(input.updates);
391
+ if (!updates)
392
+ throw new Error(`updateTechStack requires "updates" object. Got: ${input.updates === undefined ? 'missing' : typeof input.updates}. ` +
393
+ `Example: { "op": "updateTechStack", "updates": { "toolkit": "react-node" } }`);
394
+ return { op: 'updateTechStack', updates };
395
+ }
328
396
  default:
329
397
  throw new Error(`Unknown operation: ${input.op}`);
330
398
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/factory",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "AI-driven application scaffolder for the compilr-dev ecosystem",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",