@growthub/cli 0.9.18 → 0.10.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.
Files changed (50) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/reference-options/route.js +62 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +13 -2
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/resolver-templates/route.js +23 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +35 -5
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-source/route.js +15 -1
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +2048 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataTable.jsx +1 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/FieldEditor.jsx +1 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/FieldManager.jsx +9 -0
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ObjectSidebar.jsx +41 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/RecordDrawer.jsx +1 -0
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ReferencePicker.jsx +244 -0
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxRunPanel.jsx +21 -0
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SourceTestPanel.jsx +15 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/StatusPill.jsx +13 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ToggleField.jsx +41 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/dm-shared.jsx +99 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/page.jsx +2 -1528
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +66 -5
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/connector-template-authoring.md +8 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/data-model-reference-fields.md +15 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/mcp-chrome-tool-connectors.md +12 -0
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/resolver-template-library.md +17 -0
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/source-resolver-registry.js +13 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/README.md +12 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/chrome-bridge.js +22 -0
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/custom-http.js +23 -0
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-commerce.js +22 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-crm.js +23 -0
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-project-management.js +22 -0
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-spreadsheet.js +22 -0
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/mcp-tool.js +22 -0
  33. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/template-registry.js +50 -0
  34. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/webhook.js +22 -0
  35. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/collect-reference-options.js +133 -0
  36. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/reference-resolver-registry.js +17 -0
  37. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolver-loader.js +6 -0
  38. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolvers/README.md +8 -0
  39. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolvers/local-data-model.js +11 -0
  40. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolvers/source-records.js +34 -0
  41. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/adapters/README.md +5 -3
  42. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-intelligence.js +203 -0
  43. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/index.js +1 -0
  44. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/field-contracts.js +81 -0
  45. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/reference-option-schema.js +59 -0
  46. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/reference-options.js +29 -0
  47. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +527 -23
  48. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +131 -1
  49. package/dist/index.js +3036 -1340
  50. package/package.json +1 -1
@@ -163,6 +163,108 @@ function normalizeManualObjects(workspaceConfig) {
163
163
  return Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
164
164
  }
165
165
 
166
+ function normalizeStringList(values) {
167
+ return Array.from(new Set((Array.isArray(values) ? values : [])
168
+ .map((value) => String(value || "").trim())
169
+ .filter(Boolean)));
170
+ }
171
+
172
+ function normalizeSortClauses(sort, columns) {
173
+ const allowed = new Set(columns);
174
+ return (Array.isArray(sort) ? sort : []).flatMap((clause) => {
175
+ if (!clause || typeof clause !== "object" || Array.isArray(clause)) return [];
176
+ const fieldId = String(clause.fieldId || "").trim();
177
+ const direction = String(clause.direction || "asc").trim().toLowerCase() === "desc" ? "desc" : "asc";
178
+ return allowed.has(fieldId) ? [{ fieldId, direction }] : [];
179
+ });
180
+ }
181
+
182
+ function normalizeFilterConfig(filter, columns) {
183
+ if (!filter || typeof filter !== "object" || Array.isArray(filter)) return undefined;
184
+ const allowed = new Set(columns);
185
+ const op = String(filter.op || "and").trim().toLowerCase() === "or" ? "or" : "and";
186
+ const clauses = (Array.isArray(filter.clauses) ? filter.clauses : []).flatMap((clause) => {
187
+ if (!clause || typeof clause !== "object" || Array.isArray(clause)) return [];
188
+ const fieldId = String(clause.fieldId || "").trim();
189
+ const operator = String(clause.operator || "eq").trim();
190
+ if (!allowed.has(fieldId)) return [];
191
+ if (["isEmpty", "isNotEmpty"].includes(operator)) return [{ fieldId, operator }];
192
+ const value = clause.value;
193
+ if (value === undefined || value === null || value === "") return [];
194
+ return [{ fieldId, operator, value }];
195
+ });
196
+ return clauses.length ? { op, clauses } : undefined;
197
+ }
198
+
199
+ function normalizeFieldTypes(types, columns) {
200
+ const result = {};
201
+ const source = types && typeof types === "object" && !Array.isArray(types) ? types : {};
202
+ columns.forEach((column) => {
203
+ const value = String(source[column] || "").trim();
204
+ if (value) result[column] = value;
205
+ });
206
+ return result;
207
+ }
208
+
209
+ function snapshotTableViewState(settings) {
210
+ return {
211
+ hidden: normalizeStringList(settings?.hidden),
212
+ order: normalizeStringList(settings?.order),
213
+ sort: Array.isArray(settings?.sort) ? settings.sort.map((clause) => ({ ...clause })) : [],
214
+ filter: settings?.filter ? {
215
+ op: settings.filter.op,
216
+ clauses: (settings.filter.clauses || []).map((clause) => ({ ...clause }))
217
+ } : undefined
218
+ };
219
+ }
220
+
221
+ function normalizeSavedViews(views, columns) {
222
+ return (Array.isArray(views) ? views : []).flatMap((view, index) => {
223
+ if (!view || typeof view !== "object" || Array.isArray(view)) return [];
224
+ const id = String(view.id || "").trim() || `view-${index + 1}`;
225
+ const name = String(view.name || "").trim();
226
+ if (!name) return [];
227
+ const state = normalizeFieldSettings({
228
+ hidden: view.hidden,
229
+ order: view.order,
230
+ sort: view.sort,
231
+ filter: view.filter,
232
+ }, columns);
233
+ return [{
234
+ id,
235
+ name,
236
+ favorite: Boolean(view.favorite),
237
+ locked: Boolean(view.locked),
238
+ hidden: state.hidden,
239
+ order: state.order,
240
+ sort: state.sort,
241
+ filter: state.filter
242
+ }];
243
+ });
244
+ }
245
+
246
+ function normalizeFieldSettings(fieldSettings, columns) {
247
+ const order = normalizeStringList([
248
+ ...(Array.isArray(fieldSettings?.order) ? fieldSettings.order : []),
249
+ ...columns
250
+ ]).filter((column) => columns.includes(column));
251
+ const hidden = normalizeStringList(fieldSettings?.hidden).filter((column) => columns.includes(column));
252
+ const sort = normalizeSortClauses(fieldSettings?.sort, columns);
253
+ const filter = normalizeFilterConfig(fieldSettings?.filter, columns);
254
+ const views = normalizeSavedViews(fieldSettings?.views, columns);
255
+ const activeViewId = String(fieldSettings?.activeViewId || "").trim();
256
+ return {
257
+ hidden,
258
+ order,
259
+ sort,
260
+ filter,
261
+ types: normalizeFieldTypes(fieldSettings?.types, columns),
262
+ views,
263
+ activeViewId: views.some((view) => view.id === activeViewId) ? activeViewId : "",
264
+ favorite: Boolean(fieldSettings?.favorite)
265
+ };
266
+ }
267
+
166
268
  function deriveManualObjectTable(object) {
167
269
  const columns = Array.isArray(object.columns) ? object.columns.filter(Boolean) : [];
168
270
  const rows = Array.isArray(object.rows) ? object.rows.filter((row) => row && typeof row === "object" && !Array.isArray(row)) : [];
@@ -181,10 +283,7 @@ function deriveManualObjectTable(object) {
181
283
  storage: "manual-object",
182
284
  objectId: object.id,
183
285
  widgetRefs: [],
184
- fieldSettings: {
185
- hidden: Array.isArray(object.fieldSettings?.hidden) ? object.fieldSettings.hidden : [],
186
- order: Array.isArray(object.fieldSettings?.order) ? object.fieldSettings.order : columns
187
- }
286
+ fieldSettings: normalizeFieldSettings(object.fieldSettings, columns)
188
287
  };
189
288
  }
190
289
 
@@ -224,10 +323,11 @@ function listWorkspaceDataModelTables(workspaceConfig) {
224
323
  widgetTitle: widget.title,
225
324
  widgetKind: widget.kind
226
325
  }],
227
- fieldSettings: {
228
- hidden: Array.isArray(widget.config?.fieldSettings?.hidden) ? widget.config.fieldSettings.hidden : [],
229
- order: Array.isArray(widget.config?.fieldSettings?.order) ? widget.config.fieldSettings.order : table.columns
230
- }
326
+ fieldSettings: normalizeFieldSettings({
327
+ ...(widget.config?.fieldSettings || {}),
328
+ sort: widget.config?.sort,
329
+ filter: widget.config?.filter
330
+ }, table.columns)
231
331
  };
232
332
  })
233
333
  .filter(Boolean);
@@ -235,25 +335,194 @@ function listWorkspaceDataModelTables(workspaceConfig) {
235
335
  }
236
336
 
237
337
  function writeTableConfig(config, storage, columns, rows) {
338
+ const fieldSettings = normalizeFieldSettings({
339
+ ...(config.fieldSettings || {}),
340
+ sort: config.sort,
341
+ filter: config.filter
342
+ }, columns);
238
343
  if (storage === "view") {
239
344
  const binding = config.binding?.mode === "manual" ? { ...config.binding, rows } : config.binding;
240
- return { ...config, columns, rows, binding, fieldSettings: { hidden: config.fieldSettings?.hidden || [], order: columns } };
345
+ return { ...config, columns, rows, binding, fieldSettings, sort: fieldSettings.sort, filter: fieldSettings.filter };
241
346
  }
242
347
  if (storage === "json") {
243
- return { ...config, binding: { ...config.binding, json: JSON.stringify(rows, null, 2) } };
348
+ return { ...config, binding: { ...config.binding, json: JSON.stringify(rows, null, 2) }, fieldSettings, sort: fieldSettings.sort, filter: fieldSettings.filter };
244
349
  }
245
350
  if (storage === "csv") {
246
- return { ...config, binding: { ...config.binding, csv: toCsv(columns, rows) } };
351
+ return { ...config, binding: { ...config.binding, csv: toCsv(columns, rows) }, fieldSettings, sort: fieldSettings.sort, filter: fieldSettings.filter };
247
352
  }
248
353
  if (storage === "manual-binding") {
249
- return { ...config, binding: { ...config.binding, rows } };
354
+ return { ...config, binding: { ...config.binding, rows }, fieldSettings, sort: fieldSettings.sort, filter: fieldSettings.filter };
250
355
  }
251
356
  if (storage === "chart-values") {
252
- return { ...config, values: rows.map((row) => Number(row.Value)).filter((value) => Number.isFinite(value)) };
357
+ return { ...config, values: rows.map((row) => Number(row.Value)).filter((value) => Number.isFinite(value)), fieldSettings, sort: fieldSettings.sort, filter: fieldSettings.filter };
253
358
  }
254
359
  return config;
255
360
  }
256
361
 
362
+ function updateTableFieldSettings(workspaceConfig, table, updater) {
363
+ const nextSettings = normalizeFieldSettings(
364
+ updater(normalizeFieldSettings(table.fieldSettings, table.columns)),
365
+ table.columns
366
+ );
367
+ if (table.storage === "manual-object") {
368
+ const objects = normalizeManualObjects(workspaceConfig);
369
+ const dataModel = workspaceConfig.dataModel && typeof workspaceConfig.dataModel === "object" && !Array.isArray(workspaceConfig.dataModel)
370
+ ? workspaceConfig.dataModel
371
+ : {};
372
+ return {
373
+ ...workspaceConfig,
374
+ dataModel: {
375
+ ...dataModel,
376
+ objects: objects.map((object) => object.id === table.objectId
377
+ ? { ...object, fieldSettings: nextSettings }
378
+ : object)
379
+ }
380
+ };
381
+ }
382
+
383
+ const ids = new Set((table.widgetRefs || []).map((ref) => ref.widgetId));
384
+ const mutateWidgets = (widgets) => (widgets || []).map((widget) => {
385
+ if (!ids.has(widget.id)) return widget;
386
+ const current = deriveWidgetTable(widget, { widgetId: widget.id });
387
+ if (!current?.mutable) return widget;
388
+ return {
389
+ ...widget,
390
+ config: {
391
+ ...(widget.config || {}),
392
+ fieldSettings: nextSettings,
393
+ sort: nextSettings.sort,
394
+ filter: nextSettings.filter
395
+ }
396
+ };
397
+ });
398
+
399
+ const dashboards = (workspaceConfig.dashboards || []).map((dashboard) => ({
400
+ ...dashboard,
401
+ tabs: (dashboard.tabs || []).map((tab) => ({ ...tab, widgets: mutateWidgets(tab.widgets) }))
402
+ }));
403
+ let canvas = workspaceConfig.canvas ? { ...workspaceConfig.canvas } : {};
404
+ if (Array.isArray(canvas.widgets)) canvas = { ...canvas, widgets: mutateWidgets(canvas.widgets) };
405
+ if (Array.isArray(canvas.tabs)) canvas = { ...canvas, tabs: canvas.tabs.map((tab) => ({ ...tab, widgets: mutateWidgets(tab.widgets) })) };
406
+ return { ...workspaceConfig, dashboards, canvas };
407
+ }
408
+
409
+ function remapFieldName(name, renameMap = {}) {
410
+ return renameMap[name] || name;
411
+ }
412
+
413
+ function remapFieldSettings(fieldSettings, columns, renameMap = {}) {
414
+ const mapList = (values) => normalizeStringList((values || []).map((value) => remapFieldName(value, renameMap))).filter((value) => columns.includes(value));
415
+ const mapSort = (sort) => normalizeSortClauses((sort || []).map((clause) => ({
416
+ ...clause,
417
+ fieldId: remapFieldName(clause.fieldId, renameMap)
418
+ })), columns);
419
+ const mapFilter = (filter) => normalizeFilterConfig(filter ? {
420
+ ...filter,
421
+ clauses: (filter.clauses || []).map((clause) => ({
422
+ ...clause,
423
+ fieldId: remapFieldName(clause.fieldId, renameMap)
424
+ }))
425
+ } : null, columns);
426
+ const sourceTypes = fieldSettings?.types && typeof fieldSettings.types === "object" && !Array.isArray(fieldSettings.types)
427
+ ? fieldSettings.types
428
+ : {};
429
+ const types = columns.reduce((acc, column) => {
430
+ const previousKey = Object.keys(renameMap).find((key) => renameMap[key] === column) || column;
431
+ const typeValue = sourceTypes[column] || sourceTypes[previousKey];
432
+ if (typeValue) acc[column] = typeValue;
433
+ return acc;
434
+ }, {});
435
+ const views = normalizeSavedViews((fieldSettings?.views || []).map((view) => ({
436
+ ...view,
437
+ hidden: mapList(view.hidden || []),
438
+ order: mapList(view.order || []),
439
+ sort: mapSort(view.sort || []),
440
+ filter: mapFilter(view.filter)
441
+ })), columns);
442
+ const activeViewId = String(fieldSettings?.activeViewId || "").trim();
443
+ return {
444
+ hidden: mapList(fieldSettings?.hidden || []),
445
+ order: mapList(fieldSettings?.order || columns),
446
+ sort: mapSort(fieldSettings?.sort || []),
447
+ filter: mapFilter(fieldSettings?.filter),
448
+ types,
449
+ views,
450
+ activeViewId: views.some((view) => view.id === activeViewId) ? activeViewId : "",
451
+ favorite: Boolean(fieldSettings?.favorite)
452
+ };
453
+ }
454
+
455
+ function renameRowFields(row, nextColumns, renameMap = {}) {
456
+ const reverseMap = Object.entries(renameMap).reduce((acc, [previous, next]) => {
457
+ acc[next] = previous;
458
+ return acc;
459
+ }, {});
460
+ return nextColumns.reduce((acc, column) => {
461
+ const previousKey = reverseMap[column] || column;
462
+ acc[column] = row?.[previousKey] ?? row?.[column] ?? "";
463
+ return acc;
464
+ }, {});
465
+ }
466
+
467
+ function transformTableSchema(workspaceConfig, table, { columns, renameMap = {} }) {
468
+ const nextColumns = Array.isArray(columns) ? columns.filter(Boolean) : table.columns;
469
+ if (!nextColumns.length) return workspaceConfig;
470
+
471
+ if (table.storage === "manual-object") {
472
+ const objects = normalizeManualObjects(workspaceConfig);
473
+ const dataModel = workspaceConfig.dataModel && typeof workspaceConfig.dataModel === "object" && !Array.isArray(workspaceConfig.dataModel)
474
+ ? workspaceConfig.dataModel
475
+ : {};
476
+ return {
477
+ ...workspaceConfig,
478
+ dataModel: {
479
+ ...dataModel,
480
+ objects: objects.map((object) => {
481
+ if (object.id !== table.objectId) return object;
482
+ const rows = (Array.isArray(object.rows) ? object.rows : []).map((row) => renameRowFields(row, nextColumns, renameMap));
483
+ return {
484
+ ...object,
485
+ columns: nextColumns,
486
+ rows,
487
+ fieldSettings: remapFieldSettings(object.fieldSettings || {}, nextColumns, renameMap)
488
+ };
489
+ })
490
+ }
491
+ };
492
+ }
493
+
494
+ const ids = new Set((table.widgetRefs || []).map((ref) => ref.widgetId));
495
+ const mutateWidgets = (widgets) => (widgets || []).map((widget) => {
496
+ if (!ids.has(widget.id)) return widget;
497
+ const current = deriveWidgetTable(widget, { widgetId: widget.id });
498
+ if (!current?.mutable) return widget;
499
+ const rows = (current.rows || []).map((row) => renameRowFields(row, nextColumns, renameMap));
500
+ const nextFieldSettings = remapFieldSettings({
501
+ ...(widget.config?.fieldSettings || {}),
502
+ sort: widget.config?.sort,
503
+ filter: widget.config?.filter
504
+ }, nextColumns, renameMap);
505
+ return {
506
+ ...widget,
507
+ config: {
508
+ ...writeTableConfig(widget.config || {}, current.storage, nextColumns, rows),
509
+ fieldSettings: nextFieldSettings,
510
+ sort: nextFieldSettings.sort,
511
+ filter: nextFieldSettings.filter
512
+ }
513
+ };
514
+ });
515
+
516
+ const dashboards = (workspaceConfig.dashboards || []).map((dashboard) => ({
517
+ ...dashboard,
518
+ tabs: (dashboard.tabs || []).map((tab) => ({ ...tab, widgets: mutateWidgets(tab.widgets) }))
519
+ }));
520
+ let canvas = workspaceConfig.canvas ? { ...workspaceConfig.canvas } : {};
521
+ if (Array.isArray(canvas.widgets)) canvas = { ...canvas, widgets: mutateWidgets(canvas.widgets) };
522
+ if (Array.isArray(canvas.tabs)) canvas = { ...canvas, tabs: canvas.tabs.map((tab) => ({ ...tab, widgets: mutateWidgets(tab.widgets) })) };
523
+ return { ...workspaceConfig, dashboards, canvas };
524
+ }
525
+
257
526
  function applyTableMutation(workspaceConfig, table, mutate) {
258
527
  if (table.storage === "manual-object") {
259
528
  const objects = normalizeManualObjects(workspaceConfig);
@@ -330,6 +599,16 @@ function uniqueObjectId(workspaceConfig, name) {
330
599
  * targetObjectType:string, // objectType of the referenced object
331
600
  * type: "belongs-to" | "has-many",
332
601
  * description: string
602
+ * valueField?: string, // column on TARGET row used as stored FK value (default integrationId)
603
+ * labelField?: string, // primary label column on target (default Name)
604
+ * secondaryLabelField?: string,
605
+ * statusField?: string, // default status
606
+ * statusAllowlist?: string[] // when set, only rows whose status matches (case-insensitive) appear
607
+ * searchable?: boolean,
608
+ * pageSize?: number,
609
+ * resolver?: { integrationId: string } // optional listEntities-backed option source
610
+ * referenceSource?: "workspace-rows" | "source-records" // default workspace-rows
611
+ * sidecarSourceId?: string // when referenceSource is source-records
333
612
  * }
334
613
  */
335
614
  const OBJECT_TYPE_PRESETS = {
@@ -337,7 +616,20 @@ const OBJECT_TYPE_PRESETS = {
337
616
  label: "Data Source",
338
617
  icon: "Globe",
339
618
  description: "Custom API, webhook, or external feed. References an API Registry record while credentials stay in workspace settings.",
340
- columns: ["Name", "registryId", "endpoint", "authRef", "baseUrl", "status", "lastTested", "lastResponse"],
619
+ columns: [
620
+ "Name",
621
+ "registryId",
622
+ "endpoint",
623
+ "authRef",
624
+ "baseUrl",
625
+ "status",
626
+ "lastTested",
627
+ "lastResponse",
628
+ "entityType",
629
+ "sourceId",
630
+ "sourceStorage",
631
+ "resolverTemplateId"
632
+ ],
341
633
  relations: [
342
634
  {
343
635
  id: "resolver-binding",
@@ -345,7 +637,14 @@ const OBJECT_TYPE_PRESETS = {
345
637
  field: "registryId",
346
638
  targetObjectType: "api-registry",
347
639
  type: "belongs-to",
348
- description: "The API Registry entry whose fetchRecords function resolves this source. Set registryId to match the resolver integrationId."
640
+ description: "The API Registry entry whose fetchRecords function resolves this source. Set registryId to match the resolver integrationId.",
641
+ valueField: "integrationId",
642
+ labelField: "Name",
643
+ secondaryLabelField: "endpoint",
644
+ statusField: "status",
645
+ statusAllowlist: null,
646
+ searchable: true,
647
+ pageSize: 25
349
648
  }
350
649
  ]
351
650
  },
@@ -353,7 +652,23 @@ const OBJECT_TYPE_PRESETS = {
353
652
  label: "API Registry",
354
653
  icon: "Code2",
355
654
  description: "HTTP API records with endpoint config, auth references, connection status, and stored test output.",
356
- columns: ["integrationId", "authRef", "baseUrl", "endpoint", "method", "status", "lastTested", "lastResponse", "entityTypes", "description"],
655
+ columns: [
656
+ "integrationId",
657
+ "authRef",
658
+ "baseUrl",
659
+ "endpoint",
660
+ "method",
661
+ "status",
662
+ "lastTested",
663
+ "lastResponse",
664
+ "entityTypes",
665
+ "description",
666
+ "connectorKind",
667
+ "resolverTemplateId",
668
+ "schemaVersion",
669
+ "capabilities",
670
+ "executionLane"
671
+ ],
357
672
  relations: []
358
673
  },
359
674
  "people": {
@@ -383,6 +698,9 @@ const OBJECT_TYPE_PRESETS = {
383
698
  "runtime",
384
699
  "adapter",
385
700
  "agentHost",
701
+ "localModel",
702
+ "localEndpoint",
703
+ "intelligenceAdapterMode",
386
704
  "envRefs",
387
705
  "networkAllow",
388
706
  "allowList",
@@ -393,7 +711,10 @@ const OBJECT_TYPE_PRESETS = {
393
711
  "lastTested",
394
712
  "lastRunId",
395
713
  "lastSourceId",
396
- "lastResponse"
714
+ "lastResponse",
715
+ "resolverTemplateId",
716
+ "connectorKind",
717
+ "executionLane"
397
718
  ],
398
719
  relations: [
399
720
  {
@@ -402,7 +723,14 @@ const OBJECT_TYPE_PRESETS = {
402
723
  field: "schedulerRegistryId",
403
724
  targetObjectType: "api-registry",
404
725
  type: "belongs-to",
405
- description: "When runLocality is serverless, POST /api/workspace/sandbox-run sends growthub-sandbox-run-v1 to this API Registry record (METHOD, baseUrl, endpoint, authRef resolved server-side). Use for Supabase Edge URL, QStash forwarder, Vercel-exposed webhook, cron targets, etc."
726
+ description: "When runLocality is serverless, POST /api/workspace/sandbox-run sends growthub-sandbox-run-v1 to this API Registry record (METHOD, baseUrl, endpoint, authRef resolved server-side). Use for Supabase Edge URL, QStash forwarder, Vercel-exposed webhook, cron targets, etc.",
727
+ valueField: "integrationId",
728
+ labelField: "Name",
729
+ secondaryLabelField: "endpoint",
730
+ statusField: "status",
731
+ statusAllowlist: ["connected", "approved", "ok", "success"],
732
+ searchable: true,
733
+ pageSize: 25
406
734
  }
407
735
  ]
408
736
  },
@@ -440,7 +768,7 @@ function createTypedBusinessObject(workspaceConfig, { name, objectType = "custom
440
768
  rows: [],
441
769
  binding: { mode: "manual", source: "Data Model" },
442
770
  relations: preset.relations ? preset.relations.map((r) => ({ ...r })) : [],
443
- fieldSettings: { hidden: [], order: columns }
771
+ fieldSettings: normalizeFieldSettings({}, columns)
444
772
  };
445
773
  return {
446
774
  ...workspaceConfig,
@@ -468,7 +796,7 @@ function createManualBusinessObject(workspaceConfig, { name, fields } = {}) {
468
796
  columns,
469
797
  rows: [],
470
798
  binding: { mode: "manual", source: "Data Model" },
471
- fieldSettings: { hidden: [], order: columns }
799
+ fieldSettings: normalizeFieldSettings({}, columns)
472
800
  };
473
801
  return {
474
802
  ...workspaceConfig,
@@ -479,13 +807,22 @@ function createManualBusinessObject(workspaceConfig, { name, fields } = {}) {
479
807
  };
480
808
  }
481
809
 
482
- function addTableField(workspaceConfig, table, fieldName) {
483
- const name = String(fieldName || "").trim();
810
+ function addTableField(workspaceConfig, table, fieldSpec) {
811
+ const spec = fieldSpec && typeof fieldSpec === "object" && !Array.isArray(fieldSpec)
812
+ ? fieldSpec
813
+ : { name: fieldSpec };
814
+ const name = String(spec.name || "").trim();
815
+ const fieldType = String(spec.type || "").trim();
484
816
  if (!name || !table.mutable) return workspaceConfig;
485
- return applyTableMutation(workspaceConfig, table, ({ columns, rows }) => {
817
+ const nextConfig = applyTableMutation(workspaceConfig, table, ({ columns, rows }) => {
486
818
  if (columns.includes(name)) return { columns, rows };
487
819
  return { columns: [...columns, name], rows: rows.map((row) => ({ ...row, [name]: "" })) };
488
820
  });
821
+ return updateTableFieldSettings(nextConfig, { ...table, columns: table.columns.includes(name) ? table.columns : [...table.columns, name] }, (settings) => ({
822
+ ...settings,
823
+ order: settings.order.includes(name) ? settings.order : [...settings.order, name],
824
+ types: fieldType ? { ...(settings.types || {}), [name]: fieldType } : settings.types
825
+ }));
489
826
  }
490
827
 
491
828
  function addTableRow(workspaceConfig, table) {
@@ -621,6 +958,163 @@ function describeBindingMode(binding) {
621
958
  return { label: "Manual local table", description: "Rows and fields live in the existing widget config and travel with workspace export/import." };
622
959
  }
623
960
 
961
+ /**
962
+ * Normalize a reference option for API/UI interchange.
963
+ */
964
+ function normalizeReferenceOption(option) {
965
+ if (!option || typeof option !== "object") return null;
966
+ const value = String(option.value ?? "").trim();
967
+ if (!value) return null;
968
+ const source = ["workspace-config", "source-records", "resolver"].includes(option.source)
969
+ ? option.source
970
+ : "workspace-config";
971
+ const out = {
972
+ value,
973
+ label: String(option.label ?? value).trim() || value,
974
+ source,
975
+ objectType: typeof option.objectType === "string" && option.objectType.trim() ? option.objectType.trim() : undefined,
976
+ provider: typeof option.provider === "string" && option.provider.trim() ? option.provider.trim() : undefined,
977
+ status: typeof option.status === "string" && option.status.trim() ? option.status.trim() : undefined
978
+ };
979
+ if (option.secondaryLabel !== undefined && option.secondaryLabel !== null && String(option.secondaryLabel).trim()) {
980
+ out.secondaryLabel = String(option.secondaryLabel).trim();
981
+ }
982
+ if (option.metadata && typeof option.metadata === "object" && !Array.isArray(option.metadata)) {
983
+ out.metadata = option.metadata;
984
+ }
985
+ return out;
986
+ }
987
+
988
+ /**
989
+ * Merge preset relation defaults with stored `object.relations[]` so older rows
990
+ * pick up new optional metadata (valueField, statusAllowlist, …).
991
+ */
992
+ function effectiveRelations(object) {
993
+ const stored = Array.isArray(object?.relations) ? object.relations : [];
994
+ const presets =
995
+ object?.objectType && OBJECT_TYPE_PRESETS[object.objectType]?.relations
996
+ ? OBJECT_TYPE_PRESETS[object.objectType].relations
997
+ : [];
998
+ const presetFields = new Set(presets.map((p) => p.field).filter(Boolean));
999
+ const mergedByField = new Map();
1000
+ for (const preset of presets) {
1001
+ if (!preset?.field) continue;
1002
+ const storedMatch = stored.find((s) => s?.field === preset.field);
1003
+ mergedByField.set(preset.field, { ...preset, ...(storedMatch || {}) });
1004
+ }
1005
+ const extras = stored.filter((s) => s?.field && !presetFields.has(s.field));
1006
+ return [...Array.from(mergedByField.values()), ...extras];
1007
+ }
1008
+
1009
+ function findRelationForField(object, field) {
1010
+ if (!field) return null;
1011
+ return effectiveRelations(object).find((r) => r.field === field) || null;
1012
+ }
1013
+
1014
+ function listReferenceFields(object) {
1015
+ return effectiveRelations(object).map((r) => r.field).filter(Boolean);
1016
+ }
1017
+
1018
+ function decodeRefCursor(cursor) {
1019
+ if (typeof cursor !== "string" || !cursor.startsWith("o:")) return 0;
1020
+ const n = Number(cursor.slice(2));
1021
+ return Number.isFinite(n) && n >= 0 ? n : 0;
1022
+ }
1023
+
1024
+ function encodeRefCursor(offset) {
1025
+ return `o:${offset}`;
1026
+ }
1027
+
1028
+ /**
1029
+ * Resolve reference options from local `dataModel.objects[]` rows (workspace-config source).
1030
+ */
1031
+ function resolveLocalReferenceOptions(workspaceConfig, {
1032
+ objectId,
1033
+ field,
1034
+ query = "",
1035
+ cursor = null,
1036
+ limit = 25,
1037
+ relation: relationOverride = null
1038
+ } = {}) {
1039
+ const objects = normalizeManualObjects(workspaceConfig);
1040
+ const objectItem = objects.find((o) => o.id === objectId) || null;
1041
+ const relation = relationOverride || (objectItem && field ? findRelationForField(objectItem, field) : null);
1042
+ if (!relation || !relation.targetObjectType) {
1043
+ return { options: [], nextCursor: null, reason: objectItem ? "unknown-field" : "unknown-object" };
1044
+ }
1045
+
1046
+ const valueField = typeof relation.valueField === "string" && relation.valueField.trim()
1047
+ ? relation.valueField.trim()
1048
+ : "integrationId";
1049
+ const labelField = typeof relation.labelField === "string" && relation.labelField.trim()
1050
+ ? relation.labelField.trim()
1051
+ : "Name";
1052
+ const secondaryLabelField =
1053
+ typeof relation.secondaryLabelField === "string" && relation.secondaryLabelField.trim()
1054
+ ? relation.secondaryLabelField.trim()
1055
+ : "";
1056
+ const statusField =
1057
+ typeof relation.statusField === "string" && relation.statusField.trim()
1058
+ ? relation.statusField.trim()
1059
+ : "status";
1060
+ const allowlist = Array.isArray(relation.statusAllowlist)
1061
+ ? relation.statusAllowlist.map((s) => String(s).toLowerCase())
1062
+ : null;
1063
+
1064
+ const pageSize = Math.min(100, Math.max(1, Number(relation.pageSize) || Number(limit) || 25));
1065
+ const offset = decodeRefCursor(cursor);
1066
+
1067
+ const targets = objects.filter((o) => o.objectType === relation.targetObjectType);
1068
+ const needle = String(query || "").trim().toLowerCase();
1069
+
1070
+ const candidates = [];
1071
+ for (const target of targets) {
1072
+ const rows = Array.isArray(target.rows) ? target.rows : [];
1073
+ rows.forEach((row, index) => {
1074
+ if (!row || typeof row !== "object") return;
1075
+ const rawValue =
1076
+ row[valueField] ??
1077
+ row.integrationId ??
1078
+ row.id ??
1079
+ row.Name ??
1080
+ `${target.id}:${index}`;
1081
+ const value = String(rawValue ?? "").trim();
1082
+ if (!value) return;
1083
+ const label = String(row[labelField] ?? row.Name ?? row.integrationId ?? value).trim() || value;
1084
+ const secondaryLabel = secondaryLabelField
1085
+ ? String(row[secondaryLabelField] ?? "").trim()
1086
+ : "";
1087
+ const status = String(row[statusField] ?? "").trim();
1088
+ if (allowlist && allowlist.length) {
1089
+ const st = status.toLowerCase();
1090
+ if (!st || !allowlist.includes(st)) return;
1091
+ }
1092
+ if (needle) {
1093
+ const hay = `${value} ${label} ${secondaryLabel} ${status}`.toLowerCase();
1094
+ if (!hay.includes(needle)) return;
1095
+ }
1096
+ candidates.push(
1097
+ normalizeReferenceOption({
1098
+ value,
1099
+ label,
1100
+ secondaryLabel: secondaryLabel || undefined,
1101
+ source: "workspace-config",
1102
+ objectType: relation.targetObjectType,
1103
+ status: status || undefined,
1104
+ metadata: { objectLabel: target.label || target.source }
1105
+ })
1106
+ );
1107
+ });
1108
+ }
1109
+
1110
+ const filtered = candidates.filter(Boolean);
1111
+ const page = filtered.slice(offset, offset + pageSize);
1112
+ const nextOffset = offset + page.length;
1113
+ const nextCursor = nextOffset < filtered.length ? encodeRefCursor(nextOffset) : null;
1114
+
1115
+ return { options: page, nextCursor, reason: null, total: filtered.length };
1116
+ }
1117
+
624
1118
  export {
625
1119
  OBJECT_TYPE_PRESETS,
626
1120
  addTableField,
@@ -632,13 +1126,23 @@ export {
632
1126
  describeBindingLane,
633
1127
  describeBindingMode,
634
1128
  duplicateTableRow,
1129
+ effectiveRelations,
635
1130
  exportTableAsCsv,
1131
+ findRelationForField,
636
1132
  importTableFromCsv,
1133
+ listReferenceFields,
637
1134
  listSavedEnvRefs,
638
1135
  listWorkspaceDataModelTables,
1136
+ normalizeManualObjects,
1137
+ normalizeReferenceOption,
639
1138
  parseSandboxAllowList,
640
1139
  parseSandboxEnvRefs,
641
1140
  replaceTableContent,
1141
+ resolveLocalReferenceOptions,
642
1142
  sandboxRunSourceId,
1143
+ snapshotTableViewState,
1144
+ transformTableSchema,
1145
+ normalizeFieldSettings,
1146
+ updateTableFieldSettings,
643
1147
  updateTableCell
644
1148
  };