@growthub/cli 0.9.13 → 0.9.14

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 (24) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/README.md +27 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integration-entities/route.js +41 -9
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/list-entities/route.js +67 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-source/route.js +124 -0
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +127 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/register-resolver/route.js +119 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/resolvers/route.js +41 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-api-record/route.js +126 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-source/route.js +130 -0
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/page.jsx +692 -223
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +996 -4
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +1539 -433
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/data-sources-api-registry.md +139 -0
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolver-loader.js +57 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolvers/README.md +133 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolvers/google-analytics.js +160 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/source-resolver-registry.js +85 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-config.js +79 -1
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +104 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +23 -6
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +7 -0
  23. package/dist/index.js +1764 -40677
  24. package/package.json +1 -1
@@ -171,9 +171,12 @@ function deriveManualObjectTable(object) {
171
171
  id: `manual-object:${object.id || source}`,
172
172
  label: object.label || object.name || source,
173
173
  source,
174
+ objectType: object.objectType || "custom",
175
+ icon: object.icon || null,
174
176
  columns,
175
177
  rows,
176
178
  binding: object.binding || { mode: "manual", source: "Data Model" },
179
+ relations: Array.isArray(object.relations) ? object.relations : [],
177
180
  mutable: true,
178
181
  storage: "manual-object",
179
182
  objectId: object.id,
@@ -313,6 +316,105 @@ function uniqueObjectId(workspaceConfig, name) {
313
316
  return `${base}-${index}`;
314
317
  }
315
318
 
319
+ /**
320
+ * Top-level object type presets.
321
+ * Each entry defines: label, icon (Lucide name), description, default columns, and
322
+ * any built-in relations. These are the five first-class types the UI offers when
323
+ * a user clicks "New object" — they act like schema templates, not hard constraints.
324
+ *
325
+ * Relation shape:
326
+ * {
327
+ * id: string, // stable slug within this object
328
+ * name: string, // display label
329
+ * field: string, // FK column on THIS object
330
+ * targetObjectType:string, // objectType of the referenced object
331
+ * type: "belongs-to" | "has-many",
332
+ * description: string
333
+ * }
334
+ */
335
+ const OBJECT_TYPE_PRESETS = {
336
+ "data-source": {
337
+ label: "Data Source",
338
+ icon: "Globe",
339
+ 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"],
341
+ relations: [
342
+ {
343
+ id: "resolver-binding",
344
+ name: "Resolver",
345
+ field: "registryId",
346
+ targetObjectType: "api-registry",
347
+ type: "belongs-to",
348
+ description: "The API Registry entry whose fetchRecords function resolves this source. Set registryId to match the resolver integrationId."
349
+ }
350
+ ]
351
+ },
352
+ "api-registry": {
353
+ label: "API Registry",
354
+ icon: "Code2",
355
+ 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"],
357
+ relations: []
358
+ },
359
+ "people": {
360
+ label: "People",
361
+ icon: "Users",
362
+ description: "Contacts, leads, or team members with standard CRM fields.",
363
+ columns: ["Name", "Email", "Phone", "Company", "Status", "Role"],
364
+ relations: []
365
+ },
366
+ "tasks": {
367
+ label: "Tasks",
368
+ icon: "CheckSquare",
369
+ description: "Action items, to-dos, or work items.",
370
+ columns: ["Name", "Status", "DueDate", "Assignee", "Priority"],
371
+ relations: []
372
+ },
373
+ "custom": {
374
+ label: "Custom",
375
+ icon: "Plus",
376
+ description: "Start with a blank table — define your own fields and records.",
377
+ columns: ["Name"],
378
+ relations: []
379
+ }
380
+ };
381
+
382
+ /**
383
+ * Create a typed business object from a preset template.
384
+ * Accepts objectType (one of the OBJECT_TYPE_PRESETS keys) and an optional icon override.
385
+ * The object is stored in dataModel.objects[] alongside manual objects.
386
+ */
387
+ function createTypedBusinessObject(workspaceConfig, { name, objectType = "custom", icon } = {}) {
388
+ const label = String(name || "").trim();
389
+ if (!label) return workspaceConfig;
390
+ const preset = OBJECT_TYPE_PRESETS[objectType] || OBJECT_TYPE_PRESETS.custom;
391
+ const columns = [...preset.columns];
392
+ const dataModel =
393
+ workspaceConfig.dataModel && typeof workspaceConfig.dataModel === "object" && !Array.isArray(workspaceConfig.dataModel)
394
+ ? workspaceConfig.dataModel
395
+ : {};
396
+ const id = uniqueObjectId(workspaceConfig, label);
397
+ const object = {
398
+ id,
399
+ label,
400
+ source: label,
401
+ objectType,
402
+ icon: icon || preset.icon,
403
+ columns,
404
+ rows: [],
405
+ binding: { mode: "manual", source: "Data Model" },
406
+ relations: preset.relations ? preset.relations.map((r) => ({ ...r })) : [],
407
+ fieldSettings: { hidden: [], order: columns }
408
+ };
409
+ return {
410
+ ...workspaceConfig,
411
+ dataModel: {
412
+ ...dataModel,
413
+ objects: [...normalizeManualObjects(workspaceConfig), object]
414
+ }
415
+ };
416
+ }
417
+
316
418
  function createManualBusinessObject(workspaceConfig, { name, fields } = {}) {
317
419
  const label = String(name || "").trim();
318
420
  const columns = Array.from(new Set((Array.isArray(fields) ? fields : String(fields || "").split(","))
@@ -417,10 +519,12 @@ function describeBindingMode(binding) {
417
519
  }
418
520
 
419
521
  export {
522
+ OBJECT_TYPE_PRESETS,
420
523
  addTableField,
421
524
  addTableRow,
422
525
  appendRowsToTable,
423
526
  createManualBusinessObject,
527
+ createTypedBusinessObject,
424
528
  deleteTableRow,
425
529
  describeBindingLane,
426
530
  describeBindingMode,
@@ -152,7 +152,9 @@ const WIDGET_SCHEMA_CONTRACTS = {
152
152
  lane: "string optional (when mode === 'integration')",
153
153
  entityId: "string optional — stable source object ID (never a token or credential)",
154
154
  entityType: "string optional — adapter-provided object type",
155
- entityLabel: "string optional — display-only resolved label, not authoritative"
155
+ entityLabel: "string optional — display-only resolved label, not authoritative",
156
+ sourceStorage: "'workspace-source-records' optional — marks this binding as live-backed; records are written by POST /api/workspace/refresh-sources and keyed by dataModel.objects[].sourceId",
157
+ sourceId: "string optional — stable key in growthub.source-records.json; required when sourceStorage === 'workspace-source-records'"
156
158
  },
157
159
  NormalizedIntegrationEntity: {
158
160
  id: "non-empty string — stable source object ID",
@@ -200,19 +202,19 @@ const SAMPLE_DATA_BINDINGS = {
200
202
  function defaultConfigFor(kind) {
201
203
  switch (kind) {
202
204
  case "chart":
203
- return { values: [58, 36, 72, 48, 64], binding: SAMPLE_DATA_BINDINGS.reportingJson };
205
+ return { values: [], binding: { mode: "manual", source: "", rows: [] } };
204
206
  case "view":
205
207
  return {
206
208
  source: "",
207
209
  layout: "Table",
208
210
  columns: [],
209
211
  rows: [],
210
- binding: { mode: "manual", source: "Static rows", rows: [] }
212
+ binding: { mode: "manual", source: "", rows: [] }
211
213
  };
212
214
  case "iframe":
213
215
  return { url: "" };
214
216
  case "rich-text":
215
- return { text: "", binding: { mode: "manual", source: "Manual text", rows: [] } };
217
+ return { text: "", binding: { mode: "manual", source: "", rows: [] } };
216
218
  default:
217
219
  return {};
218
220
  }
@@ -396,8 +398,11 @@ function validateStaticDataBinding(binding, path, errors) {
396
398
  if (typeof binding.integrationId !== "string" || !binding.integrationId.trim()) {
397
399
  errors.push(`${path}.integrationId is required when mode is integration`);
398
400
  }
399
- if (typeof binding.lane !== "string" || !binding.lane.trim()) {
400
- errors.push(`${path}.lane is required when mode is integration`);
401
+ // lane is not required when sourceStorage delegates routing to a registry resolver
402
+ if (binding.sourceStorage !== "workspace-source-records") {
403
+ if (typeof binding.lane !== "string" || !binding.lane.trim()) {
404
+ errors.push(`${path}.lane is required when mode is integration`);
405
+ }
401
406
  }
402
407
  }
403
408
  if (binding.source !== undefined && typeof binding.source !== "string") {
@@ -439,6 +444,14 @@ function validateStaticDataBinding(binding, path, errors) {
439
444
  if (binding.entityLabel !== undefined && typeof binding.entityLabel !== "string") {
440
445
  errors.push(`${path}.entityLabel must be a string`);
441
446
  }
447
+ if (binding.sourceStorage !== undefined) {
448
+ if (binding.sourceStorage !== "workspace-source-records") {
449
+ errors.push(`${path}.sourceStorage must be "workspace-source-records" when present`);
450
+ }
451
+ }
452
+ if (binding.sourceId !== undefined && typeof binding.sourceId !== "string") {
453
+ errors.push(`${path}.sourceId must be a string`);
454
+ }
442
455
  }
443
456
 
444
457
  function validateFieldSettings(fieldSettings, path, errors) {
@@ -805,6 +818,7 @@ function validateDataModelConfig(dataModel, errors) {
805
818
  }
806
819
  if (typeof object.label !== "string" || !object.label.trim()) errors.push(`${prefix}.label must be a non-empty string`);
807
820
  if (object.source !== undefined && typeof object.source !== "string") errors.push(`${prefix}.source must be a string`);
821
+ if (object.sourceId !== undefined && typeof object.sourceId !== "string") errors.push(`${prefix}.sourceId must be a string`);
808
822
  validateStringArray(object.columns, `${prefix}.columns`, errors);
809
823
  if (!Array.isArray(object.rows)) {
810
824
  errors.push(`${prefix}.rows must be an array`);
@@ -814,6 +828,9 @@ function validateDataModelConfig(dataModel, errors) {
814
828
  });
815
829
  }
816
830
  validateStaticDataBinding(object.binding, `${prefix}.binding`, errors);
831
+ if (object.binding?.sourceStorage === "workspace-source-records" && typeof object.sourceId !== "string") {
832
+ errors.push(`${prefix}.sourceId is required when binding.sourceStorage is "workspace-source-records"`);
833
+ }
817
834
  validateFieldSettings(object.fieldSettings, `${prefix}.fieldSettings`, errors);
818
835
  });
819
836
  }
@@ -10,6 +10,7 @@
10
10
  "lint": "next lint"
11
11
  },
12
12
  "dependencies": {
13
+ "@tanstack/react-table": "^8.21.3",
13
14
  "lucide-react": "^0.468.0",
14
15
  "next": "16.2.4",
15
16
  "react": "19.2.4",
@@ -76,6 +76,10 @@
76
76
  "apps/workspace/app/workspace-builder.jsx",
77
77
  "apps/workspace/app/settings/integrations/page.jsx",
78
78
  "apps/workspace/app/api/workspace/route.js",
79
+ "apps/workspace/app/api/workspace/refresh-sources/route.js",
80
+ "apps/workspace/app/api/workspace/test-source/route.js",
81
+ "apps/workspace/app/api/workspace/register-resolver/route.js",
82
+ "apps/workspace/app/api/workspace/resolvers/route.js",
79
83
  "apps/workspace/app/api/settings/integrations/route.js",
80
84
  "apps/workspace/lib/workspace-schema.js",
81
85
  "apps/workspace/lib/workspace-config.js",
@@ -85,6 +89,9 @@
85
89
  "apps/workspace/lib/adapters/auth/index.js",
86
90
  "apps/workspace/lib/adapters/integrations/index.js",
87
91
  "apps/workspace/lib/adapters/integrations/growthub-connection-normalizer.js",
92
+ "apps/workspace/lib/adapters/integrations/source-resolver-registry.js",
93
+ "apps/workspace/lib/adapters/integrations/resolver-loader.js",
94
+ "apps/workspace/lib/adapters/integrations/resolvers/README.md",
88
95
  "apps/workspace/lib/adapters/payments/index.js",
89
96
  "apps/workspace/lib/adapters/persistence/index.js",
90
97
  "apps/workspace/lib/adapters/persistence/postgres.js",