@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.
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/reference-options/route.js +62 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +13 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/resolver-templates/route.js +23 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +35 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-source/route.js +15 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +2048 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataTable.jsx +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/FieldEditor.jsx +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/FieldManager.jsx +9 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ObjectSidebar.jsx +41 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/RecordDrawer.jsx +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ReferencePicker.jsx +244 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxRunPanel.jsx +21 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SourceTestPanel.jsx +15 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/StatusPill.jsx +13 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ToggleField.jsx +41 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/dm-shared.jsx +99 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/page.jsx +2 -1528
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +66 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/connector-template-authoring.md +8 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/data-model-reference-fields.md +15 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/mcp-chrome-tool-connectors.md +12 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/resolver-template-library.md +17 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/source-resolver-registry.js +13 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/README.md +12 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/chrome-bridge.js +22 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/custom-http.js +23 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-commerce.js +22 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-crm.js +23 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-project-management.js +22 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-spreadsheet.js +22 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/mcp-tool.js +22 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/template-registry.js +50 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/webhook.js +22 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/collect-reference-options.js +133 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/reference-resolver-registry.js +17 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolver-loader.js +6 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolvers/README.md +8 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolvers/local-data-model.js +11 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolvers/source-records.js +34 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/adapters/README.md +5 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-intelligence.js +203 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/index.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/field-contracts.js +81 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/reference-option-schema.js +59 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/reference-options.js +29 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +527 -23
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +131 -1
- package/dist/index.js +3036 -1340
- 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
|
-
|
|
229
|
-
|
|
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
|
|
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: [
|
|
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: [
|
|
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: {
|
|
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: {
|
|
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,
|
|
483
|
-
const
|
|
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
|
-
|
|
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
|
};
|