@asteby/metacore-runtime-react 39.2.7 → 39.2.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 39.2.8
4
+
5
+ ### Patch Changes
6
+
7
+ - 871800c: useApi().get agrupa lista, metadata y options en un POST /api/q, guarda cada id de un in: durante 30 s y DynamicTable pide include= solo de las columnas visibles.
8
+
3
9
  ## 39.2.7
4
10
 
5
11
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"api-context.d.ts","sourceRoot":"","sources":["../src/api-context.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAoC,MAAM,OAAO,CAAA;AAExD,uEAAuE;AACvE,MAAM,WAAW,SAAS;IACtB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;IACzE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;IACtF,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;IACrF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;CAC/E;AAID,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,SAAS,CAAA;IACjB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,gBAAgB,qBAEjE;AAED,mFAAmF;AACnF,wBAAgB,MAAM,IAAI,SAAS,CAMlC;AAED;;wEAEwE;AACxE,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CACzC;AAID,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAED,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,mBAAmB,qBAEvE;AAED,wBAAgB,gBAAgB,IAAI,WAAW,CAE9C"}
1
+ {"version":3,"file":"api-context.d.ts","sourceRoot":"","sources":["../src/api-context.tsx"],"names":[],"mappings":"AAIA,OAAO,KAA6C,MAAM,OAAO,CAAA;AAGjE,uEAAuE;AACvE,MAAM,WAAW,SAAS;IACtB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;IACzE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;IACtF,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;IACrF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;CAC/E;AAID,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,SAAS,CAAA;IACjB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,gBAAgB,qBAEjE;AAED,mFAAmF;AACnF,wBAAgB,MAAM,IAAI,SAAS,CAalC;AAED;;wEAEwE;AACxE,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CACzC;AAID,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAED,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,mBAAmB,qBAEvE;AAED,wBAAgB,gBAAgB,IAAI,WAAW,CAE9C"}
@@ -3,7 +3,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  // runtime-react components (DynamicTable, dialogs, action dispatcher) can
4
4
  // talk to the backend without a bundler alias to `@/lib/api`. Hosts wrap
5
5
  // their app in <ApiProvider value={axiosInstance}> once at the root.
6
- import { createContext, useContext } from 'react';
6
+ import { createContext, useContext, useMemo } from 'react';
7
+ import { batchGet } from './query-batch';
7
8
  const ApiContext = createContext(null);
8
9
  export function ApiProvider({ client, children }) {
9
10
  return _jsx(ApiContext.Provider, { value: client, children: children });
@@ -14,7 +15,14 @@ export function useApi() {
14
15
  if (!ctx) {
15
16
  throw new Error('useApi() requires an <ApiProvider> ancestor. Hosts must inject an axios-like client via runtime-react ApiProvider.');
16
17
  }
17
- return ctx;
18
+ // List, metadata and options GETs share one POST /api/q. Mutations stay
19
+ // on the host client. The wrapper is stable while the host client is.
20
+ return useMemo(() => ({
21
+ get: (url, config) => batchGet(ctx, url, config),
22
+ post: (url, body, config) => ctx.post(url, body, config),
23
+ put: (url, body, config) => ctx.put(url, body, config),
24
+ delete: (url, config) => ctx.delete(url, config),
25
+ }), [ctx]);
18
26
  }
19
27
  const BranchContext = createContext({ id: undefined });
20
28
  export function BranchProvider({ branch, children }) {
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAuGnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IACjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CACpC;AAQD,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAoB,EACpB,UAAU,EACV,UAAU,EACV,aAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,kBAA0B,EAC1C,cAAc,GACjB,EAAE,iBAAiB,+BAgiDnB"}
1
+ {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAwGnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IACjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CACpC;AAQD,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAoB,EACpB,UAAU,EACV,UAAU,EACV,aAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,kBAA0B,EAC1C,cAAc,GACjB,EAAE,iBAAiB,+BAuiDnB"}
@@ -31,6 +31,7 @@ import { dedupeById, useInfiniteScrollSentinel } from './use-infinite-scroll';
31
31
  import { DYNAMIC_TABLE_CARD_ESTIMATE_PX, DYNAMIC_TABLE_ROW_ESTIMATE_PX, resolveVirtualizeThreshold, } from './table-virtualization';
32
32
  import { OptionsContext } from './options-context';
33
33
  import { getSearchableColumnKeys } from './column-visibility';
34
+ import { visibleRelationInclude } from './list-include';
34
35
  import { useDebouncedValue } from './use-debounced-value';
35
36
  import { useCan, usePermissionsActive, gateTableMetadata } from './permissions-context';
36
37
  import { useDynamicRowActions } from './dynamic-row-actions';
@@ -649,6 +650,7 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
649
650
  set.add(k);
650
651
  return Array.from(set);
651
652
  }, [hiddenColumns, scopeHiddenColumns]);
653
+ const relationInclude = useMemo(() => visibleRelationInclude(metadata?.columns, effectiveHiddenColumns), [metadata, effectiveHiddenColumns]);
652
654
  const buildFilterParams = useCallback(() => {
653
655
  const params = {};
654
656
  if (sorting.length > 0) {
@@ -719,6 +721,8 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
719
721
  per_page: pagination.pageSize,
720
722
  ...buildFilterParams(),
721
723
  };
724
+ if (relationInclude)
725
+ params.include = relationInclude;
722
726
  const res = await api.get(endpoint || `/data/${model}`, { params });
723
727
  setForbidden(false);
724
728
  if (res.data.success) {
@@ -746,7 +750,7 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
746
750
  finally {
747
751
  setLoadingData(false);
748
752
  }
749
- }, [model, metadata, pagination, buildFilterParams, refreshTrigger, endpoint, currentBranch?.id, api, enableUrlSync]);
753
+ }, [model, metadata, pagination, buildFilterParams, refreshTrigger, endpoint, currentBranch?.id, api, enableUrlSync, relationInclude]);
750
754
  // Columns whose metadata opts into a footer total (display_config.aggregate
751
755
  // → styleConfig.aggregate). When empty, no footer row is rendered and no
752
756
  // aggregate request is made.
@@ -784,6 +788,8 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
784
788
  per_page: infPageSize,
785
789
  ...buildFilterParams(),
786
790
  };
791
+ if (relationInclude)
792
+ params.include = relationInclude;
787
793
  const res = (await api.get(endpoint || `/data/${model}`, {
788
794
  params,
789
795
  }));
@@ -823,7 +829,7 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
823
829
  else
824
830
  setLoadingData(false);
825
831
  }
826
- }, [metadata, buildFilterParams, endpoint, model, api, currentBranch?.id, enableUrlSync]);
832
+ }, [metadata, buildFilterParams, endpoint, model, api, currentBranch?.id, enableUrlSync, relationInclude]);
827
833
  // Signature of everything that must reset the incremental list to page 1:
828
834
  // the filters/search AND the sort (both live in buildFilterParams).
829
835
  const filterSignature = useMemo(() => JSON.stringify(buildFilterParams()), [buildFilterParams]);
package/dist/index.d.ts CHANGED
@@ -74,7 +74,7 @@ export { DynamicRelation, type DynamicRelationProps, type DynamicRelationStrings
74
74
  export { DynamicRelations, resolveParentId, buildRelationFilters, isEmbedded, type DynamicRelationsProps, } from './dynamic-relations';
75
75
  export { registerModelExtension, getModelExtension, clearModelExtensions, type ModelExtension, type ModelExtensionProps, } from './model-extension-registry';
76
76
  export { isColumnVisibleInTable, isColumnVisibleInModal, isColumnVisibleInLineSubtable, AUDIT_SUBTABLE_COLUMN_KEYS, getSearchableColumnKeys, } from './column-visibility';
77
- export { loadQueryPart, nameBatchTokens, optionsBatchToken, optionsModelFromUrl, type QueryPart, } from './query-batch';
77
+ export { batchGet, loadQueryPart, nameBatchTokens, narrowInToken, optionsBatchToken, optionsModelFromUrl, rememberInRows, resetQueryBatchCache, splitInToken, tokenForGet, type InListToken, type NarrowedIn, type QueryPart, } from './query-batch';
78
78
  export { useOptionsResolver, projectOption, type ResolvedOption, type OptionsMeta, type UseOptionsResolverArgs, type UseOptionsResolverResult, } from './use-options-resolver';
79
79
  export { setOrgConfigBridge, getOrgConfigBridge, resolveValidatorToken, type OrgConfigBridge, } from './use-org-config-bridge';
80
80
  export { registerValidator, getDependsOn, resolveDependsValue, getOptionsConfig, resolveOptionsSource, getVisibleWhen, evaluateVisibleWhen, scopeValueFromFilterToken, buildListScopeValues, evaluateVisibleWhenForListScope, applyLineItemRowFormulas, computeLineItemTotals, toNumber, } from './dynamic-form-schema';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -74,7 +74,7 @@ export { DynamicRelation, buildRelationFilterParams, buildCreatePayload, deriveR
74
74
  export { DynamicRelations, resolveParentId, buildRelationFilters, isEmbedded, } from './dynamic-relations';
75
75
  export { registerModelExtension, getModelExtension, clearModelExtensions, } from './model-extension-registry';
76
76
  export { isColumnVisibleInTable, isColumnVisibleInModal, isColumnVisibleInLineSubtable, AUDIT_SUBTABLE_COLUMN_KEYS, getSearchableColumnKeys, } from './column-visibility';
77
- export { loadQueryPart, nameBatchTokens, optionsBatchToken, optionsModelFromUrl, } from './query-batch';
77
+ export { batchGet, loadQueryPart, nameBatchTokens, narrowInToken, optionsBatchToken, optionsModelFromUrl, rememberInRows, resetQueryBatchCache, splitInToken, tokenForGet, } from './query-batch';
78
78
  export { useOptionsResolver, projectOption, } from './use-options-resolver';
79
79
  export { setOrgConfigBridge, getOrgConfigBridge, resolveValidatorToken, } from './use-org-config-bridge';
80
80
  export { registerValidator, getDependsOn, resolveDependsValue, getOptionsConfig, resolveOptionsSource, getVisibleWhen, evaluateVisibleWhen, scopeValueFromFilterToken, buildListScopeValues, evaluateVisibleWhenForListScope, applyLineItemRowFormulas, computeLineItemTotals, toNumber, } from './dynamic-form-schema';
@@ -0,0 +1,9 @@
1
+ import type { ColumnDefinition } from './types';
2
+ /**
3
+ * Columns whose labels come from a relation lookup. The list asks the server
4
+ * to resolve only these, so hidden foreign keys do not each become a query.
5
+ * Returns undefined when nothing relational is visible: the server then keeps
6
+ * resolving everything, which is what hosts do before they send `include=`.
7
+ */
8
+ export declare function visibleRelationInclude(columns: ColumnDefinition[] | undefined, hidden?: string[]): string | undefined;
9
+ //# sourceMappingURL=list-include.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-include.d.ts","sourceRoot":"","sources":["../src/list-include.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAClC,OAAO,EAAE,gBAAgB,EAAE,GAAG,SAAS,EACvC,MAAM,GAAE,MAAM,EAAO,GACtB,MAAM,GAAG,SAAS,CAYpB"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Columns whose labels come from a relation lookup. The list asks the server
3
+ * to resolve only these, so hidden foreign keys do not each become a query.
4
+ * Returns undefined when nothing relational is visible: the server then keeps
5
+ * resolving everything, which is what hosts do before they send `include=`.
6
+ */
7
+ export function visibleRelationInclude(columns, hidden = []) {
8
+ if (!columns?.length)
9
+ return undefined;
10
+ const hide = new Set(hidden);
11
+ const keys = [];
12
+ for (const col of columns) {
13
+ if (!col?.key || col.hidden || hide.has(col.key))
14
+ continue;
15
+ if (col.visibility === 'modal' || col.visibility === 'list')
16
+ continue;
17
+ if (!columnNeedsRelation(col))
18
+ continue;
19
+ keys.push(col.key);
20
+ }
21
+ if (keys.length === 0)
22
+ return undefined;
23
+ return keys.join(',');
24
+ }
25
+ function columnNeedsRelation(col) {
26
+ if (col.type === 'relation' || col.type === 'relation-badge-list' || col.type === 'creator' || col.type === 'user') {
27
+ return true;
28
+ }
29
+ if (col.cellStyle === 'relation' || col.cellStyle === 'reference')
30
+ return true;
31
+ if (col.ref)
32
+ return true;
33
+ if (col.itemFields?.length || col.item_fields?.length)
34
+ return true;
35
+ return false;
36
+ }
@@ -14,6 +14,25 @@ export interface QueryPart {
14
14
  etag?: string;
15
15
  not_modified?: boolean;
16
16
  }
17
+ export interface InListToken {
18
+ field: string;
19
+ ids: string[];
20
+ shape: string;
21
+ }
22
+ /** A concrete `t:` `in:` list. `@` references stay whole: the ids are not known yet. */
23
+ export declare function splitInToken(token: string): InListToken | null;
24
+ export interface NarrowedIn {
25
+ wire: string;
26
+ rows: Record<string, unknown>[];
27
+ missingIds: string[];
28
+ field: string;
29
+ shape: string;
30
+ }
31
+ /** Drops ids already cached for this shape. An empty wire means nothing has to travel. */
32
+ export declare function narrowInToken(token: string, now?: number): NarrowedIn | null;
33
+ /** Stores one row per requested id. An id with no row is remembered as empty. */
34
+ export declare function rememberInRows(token: string, ids: string[], rows: unknown, now?: number): void;
35
+ export declare function resetQueryBatchCache(): void;
17
36
  export declare function optionsModelFromUrl(url: string): string | null;
18
37
  export declare function optionsBatchToken(model: string, field: string, query: string, limit: number | undefined, filter: string | undefined): string;
19
38
  /** Names each distinct token so the server can key the response. */
@@ -23,4 +42,19 @@ export declare function nameBatchTokens(tokens: string[]): {
23
42
  plan: string;
24
43
  }[];
25
44
  export declare function loadQueryPart(api: ApiClient, token: string): Promise<QueryPart>;
45
+ /**
46
+ * Token for a host GET that the batch can serve. Custom endpoints, aggregates
47
+ * and `/me` stay on the legacy GET.
48
+ */
49
+ export declare function tokenForGet(url: string, params?: Record<string, unknown>): string | null;
50
+ /**
51
+ * GET through the batch when the URL is a list, table metadata, modal metadata
52
+ * or options read. Anything the batch refuses falls back to the original GET.
53
+ */
54
+ export declare function batchGet(api: ApiClient, url: string, config?: {
55
+ params?: Record<string, unknown>;
56
+ }): Promise<{
57
+ data: any;
58
+ headers?: any;
59
+ }>;
26
60
  //# sourceMappingURL=query-batch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"query-batch.d.ts","sourceRoot":"","sources":["../src/query-batch.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE9C,MAAM,WAAW,SAAS;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,OAAO,CAAA;CACzB;AA6BD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG9D;AAED,wBAAgB,iBAAiB,CAC7B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,CAOR;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAYjG;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAU/E"}
1
+ {"version":3,"file":"query-batch.d.ts","sourceRoot":"","sources":["../src/query-batch.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE9C,MAAM,WAAW,SAAS;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,OAAO,CAAA;CACzB;AAsBD,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,EAAE,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CAChB;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAsB9D;AAaD,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;IAC/B,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CAChB;AAED,0FAA0F;AAC1F,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,UAAU,GAAG,IAAI,CAsBhF;AAED,iFAAiF;AACjF,wBAAgB,cAAc,CAC1B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EAAE,EACb,IAAI,EAAE,OAAO,EACb,GAAG,SAAa,GACjB,IAAI,CAqBN;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAM3C;AAsBD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG9D;AAED,wBAAgB,iBAAiB,CAC7B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,CAOR;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAYjG;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAsB/E;AA6DD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CA2BxF;AAMD;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE;;;GAwBxG"}
@@ -1,9 +1,117 @@
1
1
  const WINDOW_MS = 8;
2
2
  const PART_TTL_MS = 30_000;
3
+ const ENTITY_TTL_MS = 30_000;
3
4
  const partCache = new Map();
5
+ const entityCache = new Map();
6
+ /** A concrete `t:` `in:` list. `@` references stay whole: the ids are not known yet. */
7
+ export function splitInToken(token) {
8
+ const q = token.indexOf('?');
9
+ if (q < 0 || !token.startsWith('t:'))
10
+ return null;
11
+ const model = token.slice(2, q);
12
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(model))
13
+ return null;
14
+ const params = new URLSearchParams(token.slice(q + 1));
15
+ let field = '';
16
+ let ids = [];
17
+ for (const [key, value] of params.entries()) {
18
+ if (!value.startsWith('in:'))
19
+ continue;
20
+ if (field || value.includes('@'))
21
+ return null;
22
+ field = key;
23
+ ids = value
24
+ .slice(3)
25
+ .split(',')
26
+ .map((id) => id.trim())
27
+ .filter(Boolean);
28
+ }
29
+ if (!field || ids.length === 0)
30
+ return null;
31
+ const keys = [...new Set(params.keys())].sort();
32
+ const bits = keys.map((key) => `${key}=${key === field ? 'in:$1' : (params.get(key) ?? '')}`);
33
+ return { field, ids, shape: `t:${model}?${bits.join('&')}` };
34
+ }
35
+ function entityKey(scope, shape, id) {
36
+ return `${scope}\n${shape}\n${id}`;
37
+ }
38
+ function replaceInIds(token, field, ids) {
39
+ const q = token.indexOf('?');
40
+ const params = new URLSearchParams(token.slice(q + 1));
41
+ params.set(field, `in:${ids.join(',')}`);
42
+ return `${token.slice(0, q)}?${params.toString()}`;
43
+ }
44
+ /** Drops ids already cached for this shape. An empty wire means nothing has to travel. */
45
+ export function narrowInToken(token, now = Date.now()) {
46
+ const parsed = splitInToken(token);
47
+ if (!parsed)
48
+ return null;
49
+ const scope = cacheScope();
50
+ const rows = [];
51
+ const missingIds = [];
52
+ for (const id of parsed.ids) {
53
+ const hit = entityCache.get(entityKey(scope, parsed.shape, id));
54
+ if (!hit || now - hit.at >= ENTITY_TTL_MS) {
55
+ missingIds.push(id);
56
+ continue;
57
+ }
58
+ if (hit.row)
59
+ rows.push(hit.row);
60
+ }
61
+ if (missingIds.length === parsed.ids.length)
62
+ return null;
63
+ return {
64
+ wire: missingIds.length === 0 ? '' : replaceInIds(token, parsed.field, missingIds),
65
+ rows,
66
+ missingIds,
67
+ field: parsed.field,
68
+ shape: parsed.shape,
69
+ };
70
+ }
71
+ /** Stores one row per requested id. An id with no row is remembered as empty. */
72
+ export function rememberInRows(token, ids, rows, now = Date.now()) {
73
+ const parsed = splitInToken(token);
74
+ if (!parsed)
75
+ return;
76
+ const scope = cacheScope();
77
+ const seen = new Set();
78
+ if (Array.isArray(rows)) {
79
+ for (const row of rows) {
80
+ if (!row || typeof row !== 'object')
81
+ continue;
82
+ const id = String(row[parsed.field] ?? '').trim();
83
+ if (!id)
84
+ continue;
85
+ seen.add(id);
86
+ entityCache.set(entityKey(scope, parsed.shape, id), {
87
+ row: row,
88
+ at: now,
89
+ });
90
+ }
91
+ }
92
+ for (const id of ids) {
93
+ if (seen.has(id))
94
+ continue;
95
+ entityCache.set(entityKey(scope, parsed.shape, id), { row: null, at: now });
96
+ }
97
+ }
98
+ export function resetQueryBatchCache() {
99
+ partCache.clear();
100
+ entityCache.clear();
101
+ waiters = [];
102
+ if (timer != null)
103
+ clearTimeout(timer);
104
+ timer = null;
105
+ }
4
106
  let waiters = [];
5
107
  let timer = null;
6
108
  let transport = null;
109
+ function mergeCachedRows(cached, part) {
110
+ if (!cached?.length)
111
+ return part;
112
+ const fresh = Array.isArray(part.data) ? part.data : [];
113
+ return { ...part, data: [...cached, ...fresh] };
114
+ }
7
115
  function cacheScope() {
8
116
  if (typeof localStorage === 'undefined')
9
117
  return '';
@@ -52,9 +160,21 @@ export function loadQueryPart(api, token) {
52
160
  if (cached && Date.now() - cached.at < PART_TTL_MS && cached.part.success) {
53
161
  return Promise.resolve(cached.part);
54
162
  }
163
+ const narrowed = narrowInToken(token);
164
+ if (narrowed && narrowed.wire === '') {
165
+ return Promise.resolve({ success: true, data: narrowed.rows, meta: { total: narrowed.rows.length } });
166
+ }
55
167
  return new Promise((resolve, reject) => {
56
168
  transport = api;
57
- waiters.push({ token, resolve, reject });
169
+ waiters.push({
170
+ token,
171
+ wire: narrowed?.wire ?? token,
172
+ resolve,
173
+ reject,
174
+ cachedRows: narrowed?.rows,
175
+ missingIds: narrowed?.missingIds,
176
+ field: narrowed?.field,
177
+ });
58
178
  if (timer == null)
59
179
  timer = setTimeout(() => void flushQueryBatch(), WINDOW_MS);
60
180
  });
@@ -66,7 +186,7 @@ async function flushQueryBatch() {
66
186
  const api = transport;
67
187
  if (!api || batch.length === 0)
68
188
  return;
69
- const named = nameBatchTokens(batch.map((waiter) => waiter.token));
189
+ const named = nameBatchTokens(batch.map((waiter) => waiter.wire));
70
190
  const byToken = new Map(named.map((part) => [part.token, part.name]));
71
191
  const inm = {};
72
192
  const scope = cacheScope();
@@ -82,7 +202,7 @@ async function flushQueryBatch() {
82
202
  throw new Error('batch unavailable');
83
203
  }
84
204
  for (const waiter of batch) {
85
- const name = byToken.get(waiter.token);
205
+ const name = byToken.get(waiter.wire);
86
206
  const part = name ? body.parts[name] : undefined;
87
207
  if (!part) {
88
208
  waiter.reject(new Error('batch part missing'));
@@ -95,10 +215,14 @@ async function flushQueryBatch() {
95
215
  continue;
96
216
  }
97
217
  }
218
+ const merged = mergeCachedRows(waiter.cachedRows, part);
219
+ if (waiter.missingIds && waiter.field) {
220
+ rememberInRows(waiter.token, waiter.missingIds, part.data);
221
+ }
98
222
  if (part.success) {
99
- partCache.set(`${scope}\n${waiter.token}`, { part, at: Date.now() });
223
+ partCache.set(`${scope}\n${waiter.token}`, { part: merged, at: Date.now() });
100
224
  }
101
- waiter.resolve(part);
225
+ waiter.resolve(merged);
102
226
  }
103
227
  }
104
228
  catch (err) {
@@ -107,3 +231,76 @@ async function flushQueryBatch() {
107
231
  waiter.reject(error);
108
232
  }
109
233
  }
234
+ function canonicalParams(params) {
235
+ if (!params)
236
+ return '';
237
+ const keys = Object.keys(params)
238
+ .filter((key) => params[key] != null && params[key] !== '')
239
+ .sort();
240
+ const search = new URLSearchParams();
241
+ for (const key of keys)
242
+ search.set(key, String(params[key]));
243
+ return search.toString();
244
+ }
245
+ /**
246
+ * Token for a host GET that the batch can serve. Custom endpoints, aggregates
247
+ * and `/me` stay on the legacy GET.
248
+ */
249
+ export function tokenForGet(url, params) {
250
+ const [path, query = ''] = url.split('?');
251
+ const table = path.match(/^\/metadata\/table\/([A-Za-z][A-Za-z0-9_]*)$/);
252
+ if (table)
253
+ return `m:${table[1]}`;
254
+ const modal = path.match(/^\/metadata\/modal\/([A-Za-z][A-Za-z0-9_]*)$/);
255
+ if (modal)
256
+ return `mm:${modal[1]}`;
257
+ const list = path.match(/^\/data\/([A-Za-z][A-Za-z0-9_]*)$/);
258
+ if (list) {
259
+ const q = canonicalParams(params);
260
+ return q ? `t:${list[1]}?${q}` : `t:${list[1]}`;
261
+ }
262
+ const opt = path.match(/^\/options\/([A-Za-z][A-Za-z0-9_]*)$/);
263
+ if (!opt)
264
+ return null;
265
+ const search = new URLSearchParams(query);
266
+ if (params) {
267
+ for (const [key, value] of Object.entries(params)) {
268
+ if (value != null && value !== '')
269
+ search.set(key, String(value));
270
+ }
271
+ }
272
+ const limit = Number(search.get('limit'));
273
+ return optionsBatchToken(opt[1], search.get('field') || 'id', search.get('q') || search.get('search') || '', Number.isFinite(limit) && limit > 0 ? limit : undefined, search.get('filter_value') || undefined);
274
+ }
275
+ function batchForbidden(message) {
276
+ return /forbidden|permiso/i.test(message || '');
277
+ }
278
+ /**
279
+ * GET through the batch when the URL is a list, table metadata, modal metadata
280
+ * or options read. Anything the batch refuses falls back to the original GET.
281
+ */
282
+ export async function batchGet(api, url, config) {
283
+ const token = tokenForGet(url, config?.params);
284
+ if (!token)
285
+ return api.get(url, config);
286
+ try {
287
+ const part = await loadQueryPart(api, token);
288
+ if (!part.success) {
289
+ if (batchForbidden(part.message)) {
290
+ const err = new Error(part.message || 'forbidden');
291
+ err.status = 403;
292
+ err.response = { status: 403 };
293
+ throw err;
294
+ }
295
+ return api.get(url, config);
296
+ }
297
+ return { data: { success: true, data: part.data, meta: part.meta } };
298
+ }
299
+ catch (err) {
300
+ const status = err?.status
301
+ ?? err?.response?.status;
302
+ if (status === 403)
303
+ throw err;
304
+ return api.get(url, config);
305
+ }
306
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "39.2.7",
3
+ "version": "39.2.8",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -2,7 +2,8 @@
2
2
  // runtime-react components (DynamicTable, dialogs, action dispatcher) can
3
3
  // talk to the backend without a bundler alias to `@/lib/api`. Hosts wrap
4
4
  // their app in <ApiProvider value={axiosInstance}> once at the root.
5
- import React, { createContext, useContext } from 'react'
5
+ import React, { createContext, useContext, useMemo } from 'react'
6
+ import { batchGet } from './query-batch'
6
7
 
7
8
  /** Minimal axios-compatible client shape consumed by runtime-react. */
8
9
  export interface ApiClient {
@@ -29,7 +30,14 @@ export function useApi(): ApiClient {
29
30
  if (!ctx) {
30
31
  throw new Error('useApi() requires an <ApiProvider> ancestor. Hosts must inject an axios-like client via runtime-react ApiProvider.')
31
32
  }
32
- return ctx
33
+ // List, metadata and options GETs share one POST /api/q. Mutations stay
34
+ // on the host client. The wrapper is stable while the host client is.
35
+ return useMemo<ApiClient>(() => ({
36
+ get: (url, config) => batchGet(ctx, url, config),
37
+ post: (url, body, config) => ctx.post(url, body, config),
38
+ put: (url, body, config) => ctx.put(url, body, config),
39
+ delete: (url, config) => ctx.delete(url, config),
40
+ }), [ctx])
33
41
  }
34
42
 
35
43
  /** Optional branch context — hosts that support tenant branches can supply
@@ -78,6 +78,7 @@ import {
78
78
  import { OptionsContext } from './options-context'
79
79
  import type { TableMetadata, ApiResponse, ColumnDefinition } from './types'
80
80
  import { getSearchableColumnKeys } from './column-visibility'
81
+ import { visibleRelationInclude } from './list-include'
81
82
  import { useDebouncedValue } from './use-debounced-value'
82
83
  import { useCan, usePermissionsActive, gateTableMetadata } from './permissions-context'
83
84
  import { useDynamicRowActions } from './dynamic-row-actions'
@@ -808,6 +809,11 @@ export function DynamicTable({
808
809
  return Array.from(set)
809
810
  }, [hiddenColumns, scopeHiddenColumns])
810
811
 
812
+ const relationInclude = useMemo(
813
+ () => visibleRelationInclude(metadata?.columns, effectiveHiddenColumns),
814
+ [metadata, effectiveHiddenColumns],
815
+ )
816
+
811
817
  const buildFilterParams = useCallback(() => {
812
818
  const params: Record<string, any> = {}
813
819
  if (sorting.length > 0) {
@@ -868,6 +874,7 @@ export function DynamicTable({
868
874
  per_page: pagination.pageSize,
869
875
  ...buildFilterParams(),
870
876
  }
877
+ if (relationInclude) params.include = relationInclude
871
878
  const res = await api.get(endpoint || `/data/${model}`, { params }) as { data: ApiResponse<any[]> }
872
879
  setForbidden(false)
873
880
  if (res.data.success) {
@@ -895,7 +902,7 @@ export function DynamicTable({
895
902
  } finally {
896
903
  setLoadingData(false)
897
904
  }
898
- }, [model, metadata, pagination, buildFilterParams, refreshTrigger, endpoint, currentBranch?.id, api, enableUrlSync])
905
+ }, [model, metadata, pagination, buildFilterParams, refreshTrigger, endpoint, currentBranch?.id, api, enableUrlSync, relationInclude])
899
906
 
900
907
  // Columns whose metadata opts into a footer total (display_config.aggregate
901
908
  // → styleConfig.aggregate). When empty, no footer row is rendered and no
@@ -936,6 +943,7 @@ export function DynamicTable({
936
943
  per_page: infPageSize,
937
944
  ...buildFilterParams(),
938
945
  }
946
+ if (relationInclude) params.include = relationInclude
939
947
  const res = (await api.get(endpoint || `/data/${model}`, {
940
948
  params,
941
949
  })) as { data: ApiResponse<any[]> }
@@ -974,7 +982,7 @@ export function DynamicTable({
974
982
  else setLoadingData(false)
975
983
  }
976
984
  },
977
- [metadata, buildFilterParams, endpoint, model, api, currentBranch?.id, enableUrlSync],
985
+ [metadata, buildFilterParams, endpoint, model, api, currentBranch?.id, enableUrlSync, relationInclude],
978
986
  )
979
987
 
980
988
  // Signature of everything that must reset the incremental list to page 1:
package/src/index.ts CHANGED
@@ -393,10 +393,18 @@ export {
393
393
  getSearchableColumnKeys,
394
394
  } from './column-visibility'
395
395
  export {
396
+ batchGet,
396
397
  loadQueryPart,
397
398
  nameBatchTokens,
399
+ narrowInToken,
398
400
  optionsBatchToken,
399
401
  optionsModelFromUrl,
402
+ rememberInRows,
403
+ resetQueryBatchCache,
404
+ splitInToken,
405
+ tokenForGet,
406
+ type InListToken,
407
+ type NarrowedIn,
400
408
  type QueryPart,
401
409
  } from './query-batch'
402
410
  export {
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { visibleRelationInclude } from './list-include'
3
+ import type { ColumnDefinition } from './types'
4
+
5
+ function col(partial: Partial<ColumnDefinition> & { key: string }): ColumnDefinition {
6
+ return { label: partial.key, type: 'text', sortable: false, filterable: false, ...partial }
7
+ }
8
+
9
+ describe('visibleRelationInclude', () => {
10
+ it('omits the parameter when the table has no relations', () => {
11
+ expect(visibleRelationInclude([col({ key: 'name' })])).toBeUndefined()
12
+ expect(visibleRelationInclude(undefined)).toBeUndefined()
13
+ })
14
+
15
+ it('lists only the visible relation columns', () => {
16
+ const include = visibleRelationInclude([
17
+ col({ key: 'name' }),
18
+ col({ key: 'customer_id', type: 'relation' }),
19
+ col({ key: 'warehouse_id', ref: 'inventory.Warehouse', hidden: true }),
20
+ col({ key: 'created_by', type: 'creator' }),
21
+ col({ key: 'items', item_fields: [{ key: 'product_id', label: 'Producto' }] }),
22
+ ])
23
+ expect(include).toBe('customer_id,created_by,items')
24
+ })
25
+
26
+ it('drops a column the view hid', () => {
27
+ const include = visibleRelationInclude(
28
+ [col({ key: 'customer_id', type: 'relation' }), col({ key: 'user_id', type: 'user' })],
29
+ ['user_id'],
30
+ )
31
+ expect(include).toBe('customer_id')
32
+ })
33
+ })
@@ -0,0 +1,34 @@
1
+ import type { ColumnDefinition } from './types'
2
+
3
+ /**
4
+ * Columns whose labels come from a relation lookup. The list asks the server
5
+ * to resolve only these, so hidden foreign keys do not each become a query.
6
+ * Returns undefined when nothing relational is visible: the server then keeps
7
+ * resolving everything, which is what hosts do before they send `include=`.
8
+ */
9
+ export function visibleRelationInclude(
10
+ columns: ColumnDefinition[] | undefined,
11
+ hidden: string[] = [],
12
+ ): string | undefined {
13
+ if (!columns?.length) return undefined
14
+ const hide = new Set(hidden)
15
+ const keys: string[] = []
16
+ for (const col of columns) {
17
+ if (!col?.key || col.hidden || hide.has(col.key)) continue
18
+ if (col.visibility === 'modal' || col.visibility === 'list') continue
19
+ if (!columnNeedsRelation(col)) continue
20
+ keys.push(col.key)
21
+ }
22
+ if (keys.length === 0) return undefined
23
+ return keys.join(',')
24
+ }
25
+
26
+ function columnNeedsRelation(col: ColumnDefinition): boolean {
27
+ if (col.type === 'relation' || col.type === 'relation-badge-list' || col.type === 'creator' || col.type === 'user') {
28
+ return true
29
+ }
30
+ if (col.cellStyle === 'relation' || col.cellStyle === 'reference') return true
31
+ if (col.ref) return true
32
+ if (col.itemFields?.length || col.item_fields?.length) return true
33
+ return false
34
+ }
@@ -1,5 +1,14 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { nameBatchTokens, optionsBatchToken, optionsModelFromUrl } from './query-batch'
2
+ import {
3
+ nameBatchTokens,
4
+ narrowInToken,
5
+ optionsBatchToken,
6
+ optionsModelFromUrl,
7
+ rememberInRows,
8
+ resetQueryBatchCache,
9
+ splitInToken,
10
+ tokenForGet,
11
+ } from './query-batch'
3
12
 
4
13
  describe('query batch tokens', () => {
5
14
  it('names distinct tokens and keeps duplicates as one part', () => {
@@ -21,4 +30,38 @@ describe('query batch tokens', () => {
21
30
  'o:Customer?field=id&q=lop&limit=20',
22
31
  )
23
32
  })
33
+
34
+ it('keeps each in: id and asks only for the ones that are missing', () => {
35
+ resetQueryBatchCache()
36
+ const token = 't:Stock?per_page=200&product_id=in:a,b,c&skip_refs=1'
37
+ expect(splitInToken(token)?.shape).toBe('t:Stock?per_page=200&product_id=in:$1&skip_refs=1')
38
+ expect(splitInToken('t:Stock?product_id=in:@p1.id')).toBeNull()
39
+
40
+ rememberInRows(token, ['a', 'b'], [{ product_id: 'a', quantity: 1 }])
41
+ const narrowed = narrowInToken(token)
42
+ expect(narrowed?.missingIds).toEqual(['c'])
43
+ expect(narrowed?.rows).toEqual([{ product_id: 'a', quantity: 1 }])
44
+ expect(narrowed?.wire).toContain('in%3Ac')
45
+ expect(narrowed?.wire).not.toContain('%2Cb')
46
+
47
+ rememberInRows(token, ['c'], [{ product_id: 'c', quantity: 4 }])
48
+ const done = narrowInToken('t:Stock?product_id=in:a,c&per_page=200&skip_refs=1')
49
+ expect(done?.wire).toBe('')
50
+ expect(done?.rows).toEqual([
51
+ { product_id: 'a', quantity: 1 },
52
+ { product_id: 'c', quantity: 4 },
53
+ ])
54
+ resetQueryBatchCache()
55
+ })
56
+
57
+ it('maps table reads onto batch tokens and leaves aggregates on GET', () => {
58
+ expect(tokenForGet('/metadata/table/Sale')).toBe('m:Sale')
59
+ expect(tokenForGet('/metadata/modal/Sale')).toBe('mm:Sale')
60
+ expect(tokenForGet('/data/Stock', { per_page: 20, page: 1, 'f_product_id': 'in:a,b' })).toBe(
61
+ 't:Stock?f_product_id=in%3Aa%2Cb&page=1&per_page=20',
62
+ )
63
+ expect(tokenForGet('/data/Stock/aggregate', { page: 1 })).toBeNull()
64
+ expect(tokenForGet('/data/Stock/me')).toBeNull()
65
+ expect(tokenForGet('/options/Customer?field=name&q=lop')).toBe('o:Customer?field=name&q=lop')
66
+ })
24
67
  })
@@ -18,20 +18,145 @@ export interface QueryPart {
18
18
 
19
19
  const WINDOW_MS = 8
20
20
  const PART_TTL_MS = 30_000
21
+ const ENTITY_TTL_MS = 30_000
21
22
 
22
23
  type Waiter = {
23
24
  token: string
25
+ wire: string
24
26
  resolve: (part: QueryPart) => void
25
27
  reject: (error: Error) => void
28
+ cachedRows?: Record<string, unknown>[]
29
+ missingIds?: string[]
30
+ field?: string
26
31
  }
27
32
 
28
33
  type CachedPart = { part: QueryPart; at: number }
34
+ type CachedEntity = { row: Record<string, unknown> | null; at: number }
29
35
 
30
36
  const partCache = new Map<string, CachedPart>()
37
+ const entityCache = new Map<string, CachedEntity>()
38
+
39
+ export interface InListToken {
40
+ field: string
41
+ ids: string[]
42
+ shape: string
43
+ }
44
+
45
+ /** A concrete `t:` `in:` list. `@` references stay whole: the ids are not known yet. */
46
+ export function splitInToken(token: string): InListToken | null {
47
+ const q = token.indexOf('?')
48
+ if (q < 0 || !token.startsWith('t:')) return null
49
+ const model = token.slice(2, q)
50
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(model)) return null
51
+ const params = new URLSearchParams(token.slice(q + 1))
52
+ let field = ''
53
+ let ids: string[] = []
54
+ for (const [key, value] of params.entries()) {
55
+ if (!value.startsWith('in:')) continue
56
+ if (field || value.includes('@')) return null
57
+ field = key
58
+ ids = value
59
+ .slice(3)
60
+ .split(',')
61
+ .map((id) => id.trim())
62
+ .filter(Boolean)
63
+ }
64
+ if (!field || ids.length === 0) return null
65
+ const keys = [...new Set(params.keys())].sort()
66
+ const bits = keys.map((key) => `${key}=${key === field ? 'in:$1' : (params.get(key) ?? '')}`)
67
+ return { field, ids, shape: `t:${model}?${bits.join('&')}` }
68
+ }
69
+
70
+ function entityKey(scope: string, shape: string, id: string): string {
71
+ return `${scope}\n${shape}\n${id}`
72
+ }
73
+
74
+ function replaceInIds(token: string, field: string, ids: string[]): string {
75
+ const q = token.indexOf('?')
76
+ const params = new URLSearchParams(token.slice(q + 1))
77
+ params.set(field, `in:${ids.join(',')}`)
78
+ return `${token.slice(0, q)}?${params.toString()}`
79
+ }
80
+
81
+ export interface NarrowedIn {
82
+ wire: string
83
+ rows: Record<string, unknown>[]
84
+ missingIds: string[]
85
+ field: string
86
+ shape: string
87
+ }
88
+
89
+ /** Drops ids already cached for this shape. An empty wire means nothing has to travel. */
90
+ export function narrowInToken(token: string, now = Date.now()): NarrowedIn | null {
91
+ const parsed = splitInToken(token)
92
+ if (!parsed) return null
93
+ const scope = cacheScope()
94
+ const rows: Record<string, unknown>[] = []
95
+ const missingIds: string[] = []
96
+ for (const id of parsed.ids) {
97
+ const hit = entityCache.get(entityKey(scope, parsed.shape, id))
98
+ if (!hit || now - hit.at >= ENTITY_TTL_MS) {
99
+ missingIds.push(id)
100
+ continue
101
+ }
102
+ if (hit.row) rows.push(hit.row)
103
+ }
104
+ if (missingIds.length === parsed.ids.length) return null
105
+ return {
106
+ wire: missingIds.length === 0 ? '' : replaceInIds(token, parsed.field, missingIds),
107
+ rows,
108
+ missingIds,
109
+ field: parsed.field,
110
+ shape: parsed.shape,
111
+ }
112
+ }
113
+
114
+ /** Stores one row per requested id. An id with no row is remembered as empty. */
115
+ export function rememberInRows(
116
+ token: string,
117
+ ids: string[],
118
+ rows: unknown,
119
+ now = Date.now(),
120
+ ): void {
121
+ const parsed = splitInToken(token)
122
+ if (!parsed) return
123
+ const scope = cacheScope()
124
+ const seen = new Set<string>()
125
+ if (Array.isArray(rows)) {
126
+ for (const row of rows) {
127
+ if (!row || typeof row !== 'object') continue
128
+ const id = String((row as Record<string, unknown>)[parsed.field] ?? '').trim()
129
+ if (!id) continue
130
+ seen.add(id)
131
+ entityCache.set(entityKey(scope, parsed.shape, id), {
132
+ row: row as Record<string, unknown>,
133
+ at: now,
134
+ })
135
+ }
136
+ }
137
+ for (const id of ids) {
138
+ if (seen.has(id)) continue
139
+ entityCache.set(entityKey(scope, parsed.shape, id), { row: null, at: now })
140
+ }
141
+ }
142
+
143
+ export function resetQueryBatchCache(): void {
144
+ partCache.clear()
145
+ entityCache.clear()
146
+ waiters = []
147
+ if (timer != null) clearTimeout(timer)
148
+ timer = null
149
+ }
31
150
  let waiters: Waiter[] = []
32
151
  let timer: ReturnType<typeof setTimeout> | null = null
33
152
  let transport: ApiClient | null = null
34
153
 
154
+ function mergeCachedRows(cached: Record<string, unknown>[] | undefined, part: QueryPart): QueryPart {
155
+ if (!cached?.length) return part
156
+ const fresh = Array.isArray(part.data) ? part.data : []
157
+ return { ...part, data: [...cached, ...fresh] }
158
+ }
159
+
35
160
  function cacheScope(): string {
36
161
  if (typeof localStorage === 'undefined') return ''
37
162
  try {
@@ -83,9 +208,21 @@ export function loadQueryPart(api: ApiClient, token: string): Promise<QueryPart>
83
208
  if (cached && Date.now() - cached.at < PART_TTL_MS && cached.part.success) {
84
209
  return Promise.resolve(cached.part)
85
210
  }
211
+ const narrowed = narrowInToken(token)
212
+ if (narrowed && narrowed.wire === '') {
213
+ return Promise.resolve({ success: true, data: narrowed.rows, meta: { total: narrowed.rows.length } })
214
+ }
86
215
  return new Promise((resolve, reject) => {
87
216
  transport = api
88
- waiters.push({ token, resolve, reject })
217
+ waiters.push({
218
+ token,
219
+ wire: narrowed?.wire ?? token,
220
+ resolve,
221
+ reject,
222
+ cachedRows: narrowed?.rows,
223
+ missingIds: narrowed?.missingIds,
224
+ field: narrowed?.field,
225
+ })
89
226
  if (timer == null) timer = setTimeout(() => void flushQueryBatch(), WINDOW_MS)
90
227
  })
91
228
  }
@@ -96,7 +233,7 @@ async function flushQueryBatch() {
96
233
  waiters = []
97
234
  const api = transport
98
235
  if (!api || batch.length === 0) return
99
- const named = nameBatchTokens(batch.map((waiter) => waiter.token))
236
+ const named = nameBatchTokens(batch.map((waiter) => waiter.wire))
100
237
  const byToken = new Map(named.map((part) => [part.token, part.name]))
101
238
  const inm: Record<string, string> = {}
102
239
  const scope = cacheScope()
@@ -111,7 +248,7 @@ async function flushQueryBatch() {
111
248
  throw new Error('batch unavailable')
112
249
  }
113
250
  for (const waiter of batch) {
114
- const name = byToken.get(waiter.token)
251
+ const name = byToken.get(waiter.wire)
115
252
  const part = name ? (body.parts[name] as QueryPart | undefined) : undefined
116
253
  if (!part) {
117
254
  waiter.reject(new Error('batch part missing'))
@@ -124,13 +261,94 @@ async function flushQueryBatch() {
124
261
  continue
125
262
  }
126
263
  }
264
+ const merged = mergeCachedRows(waiter.cachedRows, part)
265
+ if (waiter.missingIds && waiter.field) {
266
+ rememberInRows(waiter.token, waiter.missingIds, part.data)
267
+ }
127
268
  if (part.success) {
128
- partCache.set(`${scope}\n${waiter.token}`, { part, at: Date.now() })
269
+ partCache.set(`${scope}\n${waiter.token}`, { part: merged, at: Date.now() })
129
270
  }
130
- waiter.resolve(part)
271
+ waiter.resolve(merged)
131
272
  }
132
273
  } catch (err) {
133
274
  const error = err instanceof Error ? err : new Error(String(err))
134
275
  for (const waiter of batch) waiter.reject(error)
135
276
  }
136
277
  }
278
+
279
+ function canonicalParams(params?: Record<string, unknown>): string {
280
+ if (!params) return ''
281
+ const keys = Object.keys(params)
282
+ .filter((key) => params[key] != null && params[key] !== '')
283
+ .sort()
284
+ const search = new URLSearchParams()
285
+ for (const key of keys) search.set(key, String(params[key]))
286
+ return search.toString()
287
+ }
288
+
289
+ /**
290
+ * Token for a host GET that the batch can serve. Custom endpoints, aggregates
291
+ * and `/me` stay on the legacy GET.
292
+ */
293
+ export function tokenForGet(url: string, params?: Record<string, unknown>): string | null {
294
+ const [path, query = ''] = url.split('?')
295
+ const table = path.match(/^\/metadata\/table\/([A-Za-z][A-Za-z0-9_]*)$/)
296
+ if (table) return `m:${table[1]}`
297
+ const modal = path.match(/^\/metadata\/modal\/([A-Za-z][A-Za-z0-9_]*)$/)
298
+ if (modal) return `mm:${modal[1]}`
299
+ const list = path.match(/^\/data\/([A-Za-z][A-Za-z0-9_]*)$/)
300
+ if (list) {
301
+ const q = canonicalParams(params)
302
+ return q ? `t:${list[1]}?${q}` : `t:${list[1]}`
303
+ }
304
+ const opt = path.match(/^\/options\/([A-Za-z][A-Za-z0-9_]*)$/)
305
+ if (!opt) return null
306
+ const search = new URLSearchParams(query)
307
+ if (params) {
308
+ for (const [key, value] of Object.entries(params)) {
309
+ if (value != null && value !== '') search.set(key, String(value))
310
+ }
311
+ }
312
+ const limit = Number(search.get('limit'))
313
+ return optionsBatchToken(
314
+ opt[1],
315
+ search.get('field') || 'id',
316
+ search.get('q') || search.get('search') || '',
317
+ Number.isFinite(limit) && limit > 0 ? limit : undefined,
318
+ search.get('filter_value') || undefined,
319
+ )
320
+ }
321
+
322
+ function batchForbidden(message?: string): boolean {
323
+ return /forbidden|permiso/i.test(message || '')
324
+ }
325
+
326
+ /**
327
+ * GET through the batch when the URL is a list, table metadata, modal metadata
328
+ * or options read. Anything the batch refuses falls back to the original GET.
329
+ */
330
+ export async function batchGet(api: ApiClient, url: string, config?: { params?: Record<string, unknown> }) {
331
+ const token = tokenForGet(url, config?.params)
332
+ if (!token) return api.get(url, config)
333
+ try {
334
+ const part = await loadQueryPart(api, token)
335
+ if (!part.success) {
336
+ if (batchForbidden(part.message)) {
337
+ const err = new Error(part.message || 'forbidden') as Error & {
338
+ status: number
339
+ response: { status: number }
340
+ }
341
+ err.status = 403
342
+ err.response = { status: 403 }
343
+ throw err
344
+ }
345
+ return api.get(url, config)
346
+ }
347
+ return { data: { success: true, data: part.data, meta: part.meta } }
348
+ } catch (err) {
349
+ const status = (err as { status?: number; response?: { status?: number } })?.status
350
+ ?? (err as { response?: { status?: number } })?.response?.status
351
+ if (status === 403) throw err
352
+ return api.get(url, config)
353
+ }
354
+ }
package/vitest.config.ts CHANGED
@@ -4,5 +4,6 @@ export default defineConfig({
4
4
  test: {
5
5
  environment: 'node',
6
6
  include: ['src/**/*.test.ts', 'src/**/*.test.tsx'],
7
+ setupFiles: ['./vitest.setup.ts'],
7
8
  },
8
9
  })
@@ -0,0 +1,29 @@
1
+ // Node 26 exposes a global localStorage getter that returns undefined unless
2
+ // --localStorage-file is set, and that getter hides happy-dom's storage.
3
+ // Tests that persist page size or the metadata cache need a real Storage.
4
+
5
+ function installMemoryStorage(name: 'localStorage' | 'sessionStorage') {
6
+ const current = globalThis[name] as Storage | undefined
7
+ if (current && typeof current.clear === 'function' && typeof current.setItem === 'function') return
8
+ const store = new Map<string, string>()
9
+ const memory: Storage = {
10
+ getItem: (key) => (store.has(key) ? store.get(key)! : null),
11
+ setItem: (key, value) => {
12
+ store.set(String(key), String(value))
13
+ },
14
+ removeItem: (key) => {
15
+ store.delete(String(key))
16
+ },
17
+ clear: () => {
18
+ store.clear()
19
+ },
20
+ key: (index) => Array.from(store.keys())[index] ?? null,
21
+ get length() {
22
+ return store.size
23
+ },
24
+ }
25
+ Object.defineProperty(globalThis, name, { value: memory, configurable: true, writable: true })
26
+ }
27
+
28
+ installMemoryStorage('localStorage')
29
+ installMemoryStorage('sessionStorage')