@base44-preview/vite-plugin 0.2.26-pr.43.e9195be → 0.2.27-pr.43.93e5e43

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 (42) hide show
  1. package/dist/capabilities/inline-edit/controller.d.ts +3 -0
  2. package/dist/capabilities/inline-edit/controller.d.ts.map +1 -0
  3. package/dist/capabilities/inline-edit/controller.js +194 -0
  4. package/dist/capabilities/inline-edit/controller.js.map +1 -0
  5. package/dist/capabilities/inline-edit/dom-utils.d.ts +6 -0
  6. package/dist/capabilities/inline-edit/dom-utils.d.ts.map +1 -0
  7. package/dist/capabilities/inline-edit/dom-utils.js +49 -0
  8. package/dist/capabilities/inline-edit/dom-utils.js.map +1 -0
  9. package/dist/capabilities/inline-edit/index.d.ts +3 -0
  10. package/dist/capabilities/inline-edit/index.d.ts.map +1 -0
  11. package/dist/capabilities/inline-edit/index.js +2 -0
  12. package/dist/capabilities/inline-edit/index.js.map +1 -0
  13. package/dist/capabilities/inline-edit/types.d.ts +25 -0
  14. package/dist/capabilities/inline-edit/types.d.ts.map +1 -0
  15. package/dist/capabilities/inline-edit/types.js +2 -0
  16. package/dist/capabilities/inline-edit/types.js.map +1 -0
  17. package/dist/injections/visual-edit-agent.d.ts.map +1 -1
  18. package/dist/injections/visual-edit-agent.js +72 -12
  19. package/dist/injections/visual-edit-agent.js.map +1 -1
  20. package/dist/processors/collection-item-field-processor.d.ts +8 -2
  21. package/dist/processors/collection-item-field-processor.d.ts.map +1 -1
  22. package/dist/processors/collection-item-field-processor.js +19 -13
  23. package/dist/processors/collection-item-field-processor.js.map +1 -1
  24. package/dist/processors/collection-tracing-utils.d.ts +5 -0
  25. package/dist/processors/collection-tracing-utils.d.ts.map +1 -1
  26. package/dist/processors/collection-tracing-utils.js +66 -2
  27. package/dist/processors/collection-tracing-utils.js.map +1 -1
  28. package/dist/processors/shared-utils.d.ts +13 -0
  29. package/dist/processors/shared-utils.d.ts.map +1 -1
  30. package/dist/processors/shared-utils.js +57 -0
  31. package/dist/processors/shared-utils.js.map +1 -1
  32. package/dist/statics/index.mjs +5 -1
  33. package/dist/statics/index.mjs.map +1 -1
  34. package/package.json +1 -1
  35. package/src/capabilities/inline-edit/controller.ts +245 -0
  36. package/src/capabilities/inline-edit/dom-utils.ts +48 -0
  37. package/src/capabilities/inline-edit/index.ts +2 -0
  38. package/src/capabilities/inline-edit/types.ts +30 -0
  39. package/src/injections/visual-edit-agent.ts +85 -12
  40. package/src/processors/collection-item-field-processor.ts +49 -26
  41. package/src/processors/collection-tracing-utils.ts +73 -2
  42. package/src/processors/shared-utils.ts +75 -0
@@ -0,0 +1,48 @@
1
+ const FOCUS_STYLE_ID = "visual-edit-focus-styles";
2
+
3
+ const EDITABLE_TAGS = [
4
+ "div", "p", "h1", "h2", "h3", "h4", "h5", "h6",
5
+ "span", "li", "td", "a", "button", "label",
6
+ ];
7
+
8
+ export const injectFocusOutlineCSS = () => {
9
+ if (document.getElementById(FOCUS_STYLE_ID)) return;
10
+
11
+ const style = document.createElement("style");
12
+ style.id = FOCUS_STYLE_ID;
13
+ style.textContent = `
14
+ [data-selected="true"][contenteditable="true"]:focus {
15
+ outline: none !important;
16
+ }
17
+ `;
18
+ document.head.appendChild(style);
19
+ };
20
+
21
+ export const removeFocusOutlineCSS = () => {
22
+ document.getElementById(FOCUS_STYLE_ID)?.remove();
23
+ };
24
+
25
+ export const selectText = (element: HTMLElement) => {
26
+ const range = document.createRange();
27
+ range.selectNodeContents(element);
28
+ const selection = window.getSelection();
29
+ selection?.removeAllRanges();
30
+ selection?.addRange(range);
31
+ };
32
+
33
+ export const isEditableTextElement = (element: Element): boolean => {
34
+ if (!(element instanceof HTMLElement)) return false;
35
+ if (!EDITABLE_TAGS.includes(element.tagName.toLowerCase())) return false;
36
+ if (!element.textContent?.trim()) return false;
37
+ if (element.querySelector("img, video, canvas, svg")) return false;
38
+ if (element.children?.length > 0) return false;
39
+ if (element.dataset.dynamicContent === "true") return false;
40
+ return true;
41
+ };
42
+
43
+ export const shouldEnterInlineEditingMode = (element: Element): boolean => {
44
+ if (!(element instanceof HTMLElement) || element.dataset.selected !== "true") {
45
+ return false;
46
+ }
47
+ return isEditableTextElement(element);
48
+ };
@@ -0,0 +1,2 @@
1
+ export { createInlineEditController } from "./controller.js";
2
+ export type { InlineEditController, InlineEditHost } from "./types.js";
@@ -0,0 +1,30 @@
1
+ export interface InlineEditHost {
2
+ findElementsById(id: string | null): Element[];
3
+ getSelectedElementId(): string | null;
4
+ getSelectedOverlays(): HTMLDivElement[];
5
+ positionOverlay(
6
+ overlay: HTMLDivElement,
7
+ element: Element,
8
+ isSelected?: boolean
9
+ ): void;
10
+ clearSelection(): void;
11
+ createSelectionOverlays(elements: Element[], elementId: string): void;
12
+ }
13
+
14
+ export interface ToggleInlineEditData {
15
+ dataSourceLocation: string;
16
+ inlineEditingMode: boolean;
17
+ }
18
+
19
+ export interface InlineEditController {
20
+ enabled: boolean;
21
+ isEditing(): boolean;
22
+ getCurrentElement(): HTMLElement | null;
23
+ canEdit(element: Element): boolean;
24
+ startEditing(element: HTMLElement): void;
25
+ stopEditing(): void;
26
+ markElementsSelected(elements: Element[]): void;
27
+ clearSelectedMarks(elementId: string | null): void;
28
+ handleToggleMessage(data: ToggleInlineEditData): void;
29
+ cleanup(): void;
30
+ }
@@ -1,6 +1,7 @@
1
1
  import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES, getElementSelectorId } from "./utils.js";
2
2
  import { createLayerController } from "./layer-dropdown/controller.js";
3
3
  import { LAYER_DROPDOWN_ATTR } from "./layer-dropdown/consts.js";
4
+ import { createInlineEditController } from "../capabilities/inline-edit/index.js";
4
5
 
5
6
  export function setupVisualEditAgent() {
6
7
  // State variables (replacing React useState/useRef)
@@ -12,6 +13,8 @@ export function setupVisualEditAgent() {
12
13
  let currentHighlightedElements: Element[] = [];
13
14
  let selectedElementId: string | null = null;
14
15
 
16
+ const REPOSITION_DELAY_MS = 50;
17
+
15
18
  // Create overlay element
16
19
  const createOverlay = (isSelected = false): HTMLDivElement => {
17
20
  const overlay = document.createElement("div");
@@ -69,6 +72,34 @@ export function setupVisualEditAgent() {
69
72
  }
70
73
  };
71
74
 
75
+ // --- Inline edit controller ---
76
+ const inlineEdit = createInlineEditController({
77
+ findElementsById,
78
+ getSelectedElementId: () => selectedElementId,
79
+ getSelectedOverlays: () => selectedOverlays,
80
+ positionOverlay,
81
+ clearSelection: () => {
82
+ inlineEdit.clearSelectedMarks(selectedElementId);
83
+ clearSelectedOverlays();
84
+ selectedElementId = null;
85
+ },
86
+ createSelectionOverlays: (elements, elementId) => {
87
+ elements.forEach((el) => {
88
+ const overlay = createOverlay(true);
89
+ document.body.appendChild(overlay);
90
+ selectedOverlays.push(overlay);
91
+ positionOverlay(overlay, el, true);
92
+ });
93
+ selectedElementId = elementId;
94
+ },
95
+ });
96
+
97
+ const clearSelection = () => {
98
+ inlineEdit.clearSelectedMarks(selectedElementId);
99
+ clearSelectedOverlays();
100
+ selectedElementId = null;
101
+ };
102
+
72
103
  // Clear hover overlays
73
104
  const clearHoverOverlays = () => {
74
105
  hoverOverlays.forEach((overlay) => {
@@ -152,7 +183,8 @@ export function setupVisualEditAgent() {
152
183
 
153
184
  // Handle mouse over event
154
185
  const handleMouseOver = (e: MouseEvent) => {
155
- if (!isVisualEditMode || isPopoverDragging) return;
186
+ if (!isVisualEditMode || isPopoverDragging || inlineEdit.isEditing()) return;
187
+
156
188
 
157
189
  const target = e.target as Element;
158
190
 
@@ -221,6 +253,21 @@ export function setupVisualEditAgent() {
221
253
  // Let layer dropdown clicks pass through without interference
222
254
  if (target.closest(`[${LAYER_DROPDOWN_ATTR}]`)) return;
223
255
 
256
+ // Let clicks inside the editable element pass through to the browser
257
+ // so the user can reposition the cursor and select text naturally.
258
+ if (inlineEdit.enabled && target instanceof HTMLElement && target.contentEditable === "true") {
259
+ return;
260
+ }
261
+
262
+ // Clicking outside the editable element exits inline editing mode.
263
+ if (inlineEdit.isEditing()) {
264
+ e.preventDefault();
265
+ e.stopPropagation();
266
+ e.stopImmediatePropagation();
267
+ inlineEdit.stopEditing();
268
+ return;
269
+ }
270
+
224
271
  // Close dropdowns when clicking anywhere in iframe if a dropdown is open
225
272
  if (isDropdownOpen) {
226
273
  e.preventDefault();
@@ -249,14 +296,31 @@ export function setupVisualEditAgent() {
249
296
  return;
250
297
  }
251
298
 
299
+ const htmlElement = element as HTMLElement;
300
+ const visualSelectorId = getElementSelectorId(element);
301
+
302
+ const isAlreadySelected =
303
+ selectedElementId === visualSelectorId &&
304
+ htmlElement.dataset.selected === "true";
305
+
306
+ if (isAlreadySelected && inlineEdit.enabled && inlineEdit.canEdit(htmlElement)) {
307
+ inlineEdit.startEditing(htmlElement);
308
+ return;
309
+ }
310
+
311
+ inlineEdit.stopEditing();
312
+
313
+ if (inlineEdit.enabled) {
314
+ inlineEdit.markElementsSelected(findElementsById(visualSelectorId));
315
+ }
316
+
252
317
  const selectedOverlay = selectElement(element);
253
318
  layerController.attachToOverlay(selectedOverlay, element);
254
319
  };
255
320
 
256
- // Clear the current selection
257
- const clearSelection = () => {
258
- clearSelectedOverlays();
259
- selectedElementId = null;
321
+ const unselectElement = () => {
322
+ inlineEdit.stopEditing();
323
+ clearSelection();
260
324
  };
261
325
 
262
326
  const updateElementClassesAndReposition = (visualSelectorId: string, classes: string) => {
@@ -288,7 +352,7 @@ export function setupVisualEditAgent() {
288
352
  });
289
353
  }
290
354
  }
291
- }, 50);
355
+ }, REPOSITION_DELAY_MS);
292
356
  };
293
357
 
294
358
  // Update element attribute by visual selector ID
@@ -311,7 +375,7 @@ export function setupVisualEditAgent() {
311
375
  }
312
376
  });
313
377
  }
314
- }, 50);
378
+ }, REPOSITION_DELAY_MS);
315
379
  };
316
380
 
317
381
  // Update element content by visual selector ID
@@ -334,7 +398,7 @@ export function setupVisualEditAgent() {
334
398
  }
335
399
  });
336
400
  }
337
- }, 50);
401
+ }, REPOSITION_DELAY_MS);
338
402
  };
339
403
 
340
404
  // --- Layer dropdown controller ---
@@ -356,12 +420,12 @@ export function setupVisualEditAgent() {
356
420
  isVisualEditMode = isEnabled;
357
421
 
358
422
  if (!isEnabled) {
423
+ inlineEdit.stopEditing();
424
+ clearSelection();
359
425
  layerController.cleanup();
360
426
  clearHoverOverlays();
361
- clearSelectedOverlays();
362
427
 
363
428
  currentHighlightedElements = [];
364
- selectedElementId = null;
365
429
  document.body.style.cursor = "default";
366
430
 
367
431
  document.removeEventListener("mouseover", handleMouseOver);
@@ -422,6 +486,9 @@ export function setupVisualEditAgent() {
422
486
  switch (message.type) {
423
487
  case "toggle-visual-edit-mode":
424
488
  toggleVisualEditMode(message.data.enabled);
489
+ if (message.data.specs?.newInlineEditEnabled !== undefined) {
490
+ inlineEdit.enabled = message.data.specs.newInlineEditEnabled;
491
+ }
425
492
  break;
426
493
 
427
494
  case "update-classes":
@@ -459,7 +526,7 @@ export function setupVisualEditAgent() {
459
526
  break;
460
527
 
461
528
  case "unselect-element":
462
- clearSelection();
529
+ unselectElement();
463
530
  break;
464
531
 
465
532
  case "refresh-page":
@@ -537,6 +604,12 @@ export function setupVisualEditAgent() {
537
604
  }
538
605
  break;
539
606
 
607
+ case "toggle-inline-edit-mode":
608
+ if (message.data) {
609
+ inlineEdit.handleToggleMessage(message.data);
610
+ }
611
+ break;
612
+
540
613
  default:
541
614
  break;
542
615
  }
@@ -601,7 +674,7 @@ export function setupVisualEditAgent() {
601
674
  });
602
675
 
603
676
  if (needsUpdate) {
604
- setTimeout(handleResize, 50);
677
+ setTimeout(handleResize, REPOSITION_DELAY_MS);
605
678
  }
606
679
  });
607
680
 
@@ -229,21 +229,12 @@ export class DataItemFieldProcessor {
229
229
 
230
230
  const idField = this.resolveIdFieldName(path);
231
231
 
232
- // Simple identifier: {name} from ({ name, price }) => ...
233
- // rootIdentifier IS the field value (a string), not an object with ._id/.id.
234
- // We need to find the id field from sibling destructured params instead.
235
232
  if (rootIdentifier.name === fieldPath) {
236
233
  this.tryAddItemIdFromDestructuredParams(path, idField);
237
234
  return;
238
235
  }
239
236
 
240
- // Member expression: {product.name} → product?.id or product?._id
241
- const idExpr = this.types.optionalMemberExpression(
242
- this.types.identifier(rootIdentifier.name),
243
- this.types.identifier(idField),
244
- false,
245
- true
246
- );
237
+ const idExpr = this.buildIdExpression(rootIdentifier.name, idField);
247
238
 
248
239
  const binding = path.scope.getBinding(rootIdentifier.name);
249
240
  if (!binding) {
@@ -264,12 +255,47 @@ export class DataItemFieldProcessor {
264
255
  }
265
256
 
266
257
  /**
267
- * Walk up the JSX tree to find a key attribute that accesses .id or ._id,
268
- * returning the actual field name used. Falls back to "_id".
258
+ * Build the ID expression for a given root object name.
259
+ * If a specific idField was resolved from a key attribute, generates root?.id (or root?._id).
260
+ * If unknown (cross-file component), generates root?.id || root?._id to cover both conventions.
261
+ */
262
+ private buildIdExpression(
263
+ rootName: string,
264
+ idField: string | null
265
+ ): t.Expression {
266
+ if (idField) {
267
+ return this.types.optionalMemberExpression(
268
+ this.types.identifier(rootName),
269
+ this.types.identifier(idField),
270
+ false,
271
+ true
272
+ );
273
+ }
274
+
275
+ return this.types.logicalExpression(
276
+ "||",
277
+ this.types.optionalMemberExpression(
278
+ this.types.identifier(rootName),
279
+ this.types.identifier("id"),
280
+ false,
281
+ true
282
+ ),
283
+ this.types.optionalMemberExpression(
284
+ this.types.identifier(rootName),
285
+ this.types.identifier("_id"),
286
+ false,
287
+ true
288
+ )
289
+ );
290
+ }
291
+
292
+ /**
293
+ * Walk up the JSX tree to find a key attribute that accesses .id or ._id.
294
+ * Returns null when no key is found (e.g. cross-file component receiving props).
269
295
  */
270
296
  private resolveIdFieldName(
271
297
  path: NodePath<t.JSXOpeningElement>
272
- ): string {
298
+ ): string | null {
273
299
  let current: NodePath | null = path.parentPath;
274
300
  while (current) {
275
301
  if (current.isJSXElement()) {
@@ -279,7 +305,7 @@ export class DataItemFieldProcessor {
279
305
  }
280
306
  current = current.parentPath;
281
307
  }
282
- return "_id";
308
+ return null;
283
309
  }
284
310
 
285
311
  private extractIdFieldFromKey(
@@ -312,7 +338,7 @@ export class DataItemFieldProcessor {
312
338
  */
313
339
  private tryAddItemIdFromDestructuredParams(
314
340
  path: NodePath<t.JSXOpeningElement>,
315
- idField: string
341
+ idField: string | null
316
342
  ): void {
317
343
  const fn = path.getFunctionParent();
318
344
  if (!fn) return;
@@ -321,7 +347,6 @@ export class DataItemFieldProcessor {
321
347
  for (const param of Array.isArray(params) ? params : [params]) {
322
348
  if (!param.isObjectPattern()) continue;
323
349
 
324
- // Check if the id field is already destructured
325
350
  for (const prop of param.get("properties")) {
326
351
  if (
327
352
  prop.isObjectProperty() &&
@@ -338,11 +363,11 @@ export class DataItemFieldProcessor {
338
363
  }
339
364
  }
340
365
 
341
- // id field not destructured yet — inject it into the pattern
366
+ const fieldToInject = idField ?? "id";
342
367
  param.node.properties.push(
343
368
  this.types.objectProperty(
344
- this.types.identifier(idField),
345
- this.types.identifier(idField),
369
+ this.types.identifier(fieldToInject),
370
+ this.types.identifier(fieldToInject),
346
371
  false,
347
372
  true
348
373
  )
@@ -350,7 +375,7 @@ export class DataItemFieldProcessor {
350
375
  this.attributeUtils.addExpressionAttribute(
351
376
  path,
352
377
  DATA_COLLECTION_ITEM_ID,
353
- this.types.identifier(idField)
378
+ this.types.identifier(fieldToInject)
354
379
  );
355
380
  return;
356
381
  }
@@ -359,7 +384,7 @@ export class DataItemFieldProcessor {
359
384
  private tryAddItemIdFromParentComponent(
360
385
  path: NodePath<t.JSXOpeningElement>,
361
386
  rootIdentifier: t.Identifier,
362
- idField: string
387
+ idField: string | null
363
388
  ): void {
364
389
  let current: NodePath | null = path.parentPath;
365
390
  while (current) {
@@ -369,11 +394,9 @@ export class DataItemFieldProcessor {
369
394
  if (name && JSXUtils.isCustomComponent(name)) {
370
395
  const keyAttr = this.findKeyWithId(opening);
371
396
  if (keyAttr) {
372
- const idExpr = this.types.optionalMemberExpression(
373
- this.types.identifier(rootIdentifier.name),
374
- this.types.identifier(idField),
375
- false,
376
- true
397
+ const idExpr = this.buildIdExpression(
398
+ rootIdentifier.name,
399
+ idField
377
400
  );
378
401
  this.attributeUtils.addExpressionAttribute(
379
402
  path,
@@ -189,6 +189,9 @@ export class CollectionTracingUtils {
189
189
  const directResult = this.checkDirectServiceCall(path);
190
190
  if (directResult) return directResult;
191
191
 
192
+ const useQueryResult = this.traceUseQueryCall(path);
193
+ if (useQueryResult) return useQueryResult;
194
+
192
195
  if (this.callUtils.isChainedArrayMethod(path.node)) {
193
196
  const callee = path.get("callee");
194
197
  if (callee.isMemberExpression()) {
@@ -200,6 +203,51 @@ export class CollectionTracingUtils {
200
203
  return null;
201
204
  }
202
205
 
206
+ /**
207
+ * Trace useQuery({ queryFn: () => base44.entities.X.list() }) patterns.
208
+ * Extracts the collection from the queryFn return expression.
209
+ */
210
+ private traceUseQueryCall(
211
+ path: NodePath<t.CallExpression>
212
+ ): CollectionInfo | null {
213
+ const callee = path.get("callee");
214
+ if (!callee.isIdentifier() || callee.node.name !== "useQuery") return null;
215
+
216
+ const args = path.get("arguments");
217
+ const configArg = args[0];
218
+ if (!configArg?.isObjectExpression()) return null;
219
+
220
+ for (const prop of configArg.get("properties")) {
221
+ if (!prop.isObjectProperty()) continue;
222
+ const key = prop.get("key");
223
+ if (!key.isIdentifier() || key.node.name !== "queryFn") continue;
224
+
225
+ const value = prop.get("value");
226
+
227
+ if (value.isArrowFunctionExpression() || value.isFunctionExpression()) {
228
+ const fnBody = value.get("body") as NodePath<t.Node>;
229
+ if (fnBody.isCallExpression()) {
230
+ return this.checkDirectServiceCall(fnBody as NodePath<t.CallExpression>);
231
+ }
232
+ if (fnBody.isBlockStatement()) {
233
+ let result: CollectionInfo | null = null;
234
+ fnBody.traverse({
235
+ ReturnStatement: (retPath: NodePath<t.ReturnStatement>) => {
236
+ if (result) return;
237
+ const arg = retPath.get("argument");
238
+ if (arg.isCallExpression()) {
239
+ result = this.checkDirectServiceCall(arg);
240
+ }
241
+ },
242
+ });
243
+ if (result) return result;
244
+ }
245
+ }
246
+ }
247
+
248
+ return null;
249
+ }
250
+
203
251
  private checkDirectServiceCall(
204
252
  path: NodePath<t.CallExpression>
205
253
  ): CollectionInfo | null {
@@ -219,6 +267,16 @@ export class CollectionTracingUtils {
219
267
  };
220
268
  }
221
269
 
270
+ const base44List = this.callUtils.isBase44EntityListCall(path.node);
271
+ if (base44List) {
272
+ return { id: base44List.collectionName, references: [] };
273
+ }
274
+
275
+ const base44Get = this.callUtils.isBase44EntityGetCall(path.node);
276
+ if (base44Get) {
277
+ return { id: base44Get.collectionName, references: [] };
278
+ }
279
+
222
280
  return null;
223
281
  }
224
282
 
@@ -261,8 +319,12 @@ export class CollectionTracingUtils {
261
319
  const prop = pattern.node.properties.find(
262
320
  (p) =>
263
321
  this.types.isObjectProperty(p) &&
264
- this.types.isIdentifier(p.value) &&
265
- p.value.name === variableName
322
+ (
323
+ (this.types.isIdentifier(p.value) && p.value.name === variableName) ||
324
+ (this.types.isAssignmentPattern(p.value) &&
325
+ this.types.isIdentifier(p.value.left) &&
326
+ p.value.left.name === variableName)
327
+ )
266
328
  );
267
329
 
268
330
  if (!prop || !this.types.isObjectProperty(prop)) return null;
@@ -365,6 +427,10 @@ export class CollectionTracingUtils {
365
427
  },
366
428
  });
367
429
 
430
+ if (!result) {
431
+ return { id: paramName, references: [] };
432
+ }
433
+
368
434
  return result;
369
435
  }
370
436
 
@@ -427,6 +493,11 @@ export class CollectionTracingUtils {
427
493
  id: info.collectionName,
428
494
  references: info.multiRefFields,
429
495
  };
496
+ return;
497
+ }
498
+ const base44Get = this.callUtils.isBase44EntityGetCall(callPath.node);
499
+ if (base44Get) {
500
+ result = { id: base44Get.collectionName, references: [] };
430
501
  }
431
502
  },
432
503
  });
@@ -530,6 +530,81 @@ export class CallExpressionUtils {
530
530
  return chainMethods.some((m) => this.isArrayMethod(node, m));
531
531
  }
532
532
 
533
+ /**
534
+ * Detect base44.entities.EntityName.list() or .getAll() patterns.
535
+ * Returns the entity name as the collection name.
536
+ */
537
+ isBase44EntityListCall(
538
+ node: t.CallExpression
539
+ ): { collectionName: string } | null {
540
+ const callee = node.callee;
541
+ if (!this.types.isMemberExpression(callee)) return null;
542
+
543
+ const method = callee.property;
544
+ if (
545
+ !this.types.isIdentifier(method) ||
546
+ (method.name !== "list" && method.name !== "getAll")
547
+ ) {
548
+ return null;
549
+ }
550
+
551
+ const entityAccess = callee.object;
552
+ if (!this.types.isMemberExpression(entityAccess)) return null;
553
+
554
+ const entityName = entityAccess.property;
555
+ if (!this.types.isIdentifier(entityName)) return null;
556
+
557
+ const entitiesAccess = entityAccess.object;
558
+ if (!this.types.isMemberExpression(entitiesAccess)) return null;
559
+
560
+ const entitiesProp = entitiesAccess.property;
561
+ if (
562
+ !this.types.isIdentifier(entitiesProp) ||
563
+ entitiesProp.name !== "entities"
564
+ ) {
565
+ return null;
566
+ }
567
+
568
+ return { collectionName: entityName.name };
569
+ }
570
+
571
+ /**
572
+ * Detect base44.entities.EntityName.getById() or .get() patterns.
573
+ */
574
+ isBase44EntityGetCall(
575
+ node: t.CallExpression
576
+ ): { collectionName: string } | null {
577
+ const callee = node.callee;
578
+ if (!this.types.isMemberExpression(callee)) return null;
579
+
580
+ const method = callee.property;
581
+ if (
582
+ !this.types.isIdentifier(method) ||
583
+ (method.name !== "get" && method.name !== "getById")
584
+ ) {
585
+ return null;
586
+ }
587
+
588
+ const entityAccess = callee.object;
589
+ if (!this.types.isMemberExpression(entityAccess)) return null;
590
+
591
+ const entityName = entityAccess.property;
592
+ if (!this.types.isIdentifier(entityName)) return null;
593
+
594
+ const entitiesAccess = entityAccess.object;
595
+ if (!this.types.isMemberExpression(entitiesAccess)) return null;
596
+
597
+ const entitiesProp = entitiesAccess.property;
598
+ if (
599
+ !this.types.isIdentifier(entitiesProp) ||
600
+ entitiesProp.name !== "entities"
601
+ ) {
602
+ return null;
603
+ }
604
+
605
+ return { collectionName: entityName.name };
606
+ }
607
+
533
608
  getCallbackArgument(
534
609
  callExpr: t.CallExpression
535
610
  ): t.ArrowFunctionExpression | t.FunctionExpression | null {