@aderis-erp/cli 1.0.2 → 1.0.3

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 (85) hide show
  1. package/dist/commands/{processing-logs.d.ts → access-groups.d.ts} +1 -1
  2. package/dist/commands/{access-groups 2.js → access-groups.js} +30 -18
  3. package/dist/commands/account-transactions.d.ts +41 -0
  4. package/dist/commands/account-transactions.js +1032 -0
  5. package/dist/commands/accounts.d.ts +40 -0
  6. package/dist/commands/accounts.js +1024 -0
  7. package/dist/commands/agent-runs.d.ts +39 -0
  8. package/dist/commands/agent-runs.js +1013 -0
  9. package/dist/commands/{pubsub.d.ts → async-endpoints-internal.d.ts} +1 -1
  10. package/dist/commands/{pubsub.js → async-endpoints-internal.js} +37 -25
  11. package/dist/commands/audit-log.d.ts +39 -0
  12. package/dist/commands/{processing-logs.js → audit-log.js} +42 -28
  13. package/dist/commands/auth/login.d.ts +14 -0
  14. package/dist/commands/auth/login.js +116 -0
  15. package/dist/commands/auth/logout.d.ts +10 -0
  16. package/dist/commands/auth/logout.js +58 -0
  17. package/dist/commands/auth/status.d.ts +12 -0
  18. package/dist/commands/auth/status.js +101 -0
  19. package/dist/commands/auth/tenant.d.ts +19 -0
  20. package/dist/commands/auth/tenant.js +184 -0
  21. package/dist/commands/auth.d.ts +37 -0
  22. package/dist/commands/auth.js +871 -0
  23. package/dist/commands/bank-accounts.d.ts +41 -0
  24. package/dist/commands/bank-accounts.js +1032 -0
  25. package/dist/commands/bank-integrations.js +30 -18
  26. package/dist/commands/business-partners---contacts.js +30 -18
  27. package/dist/commands/business-partners.js +30 -18
  28. package/dist/commands/config/add.d.ts +13 -0
  29. package/dist/commands/config/add.js +120 -0
  30. package/dist/commands/config/current.d.ts +9 -0
  31. package/dist/commands/config/current.js +43 -0
  32. package/dist/commands/config/delete.d.ts +12 -0
  33. package/dist/commands/config/delete.js +67 -0
  34. package/dist/commands/config/list.d.ts +9 -0
  35. package/dist/commands/config/list.js +50 -0
  36. package/dist/commands/config/use.d.ts +9 -0
  37. package/dist/commands/config/use.js +40 -0
  38. package/dist/commands/credit-card-accounts.js +30 -18
  39. package/dist/commands/documents.js +30 -18
  40. package/dist/commands/dunning-configs.js +30 -18
  41. package/dist/commands/dunning-reminders.js +30 -18
  42. package/dist/commands/dunning-runs.js +30 -18
  43. package/dist/commands/employees.js +30 -18
  44. package/dist/commands/event-processor-mappings.js +30 -18
  45. package/dist/commands/files-internal.js +30 -18
  46. package/dist/commands/health.js +30 -18
  47. package/dist/commands/inbox.js +33 -19
  48. package/dist/commands/integrations.js +30 -18
  49. package/dist/commands/item-categories.js +30 -18
  50. package/dist/commands/items.js +30 -18
  51. package/dist/commands/journal-lines.js +30 -18
  52. package/dist/commands/journals.js +30 -18
  53. package/dist/commands/ledgers.js +30 -18
  54. package/dist/commands/office-locations.js +30 -18
  55. package/dist/commands/payment-method-ledger-mappings.js +30 -18
  56. package/dist/commands/payment-methods.js +30 -18
  57. package/dist/commands/payment-runs.js +30 -18
  58. package/dist/commands/payment-terms.js +30 -18
  59. package/dist/commands/price-lists.js +30 -18
  60. package/dist/commands/processor-mappings.js +30 -18
  61. package/dist/commands/processors.js +31 -19
  62. package/dist/commands/reconciliation.js +30 -18
  63. package/dist/commands/reports.js +30 -18
  64. package/dist/commands/salary-types.js +1018 -0
  65. package/dist/commands/sales-invoices.js +30 -18
  66. package/dist/commands/sales-orders.js +32 -19
  67. package/dist/commands/switch.d.ts +1 -0
  68. package/dist/commands/switch.js +58 -3
  69. package/dist/commands/tags.js +30 -18
  70. package/dist/commands/tax-codes.js +30 -18
  71. package/dist/commands/tax-matrix.js +30 -18
  72. package/dist/commands/tenants.js +30 -18
  73. package/dist/commands/text-templates.js +30 -18
  74. package/dist/commands/tools.js +30 -18
  75. package/dist/commands/vendor-invoices.js +33 -19
  76. package/dist/commands/webhooks.js +30 -18
  77. package/dist/commands/workflows.js +30 -18
  78. package/dist/index 2.js +1 -0
  79. package/dist/index.d 2.ts +1 -0
  80. package/dist/lib/auth.d.ts +16 -1
  81. package/dist/lib/auth.js +153 -45
  82. package/dist/lib/config.d.ts +7 -0
  83. package/dist/lib/config.js +6 -0
  84. package/oclif.manifest.json +2092 -669
  85. package/package.json +2 -2
@@ -847,32 +847,44 @@ export default class SalesInvoices extends Command {
847
847
  filterFields(data, fields) {
848
848
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
849
849
  const pick = (obj, keys) => {
850
- const result = {};
850
+ // Group keys by their first segment so sibling sub-paths
851
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
852
+ // and don't clobber each other on merge.
853
+ const directKeys = new Set();
854
+ const nestedKeys = new Map();
851
855
  for (const key of keys) {
852
856
  if (key.split('.').some(isUnsafeKey))
853
857
  continue;
854
858
  if (key.includes('.')) {
855
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
856
859
  const [first, ...rest] = key.split('.');
857
- const val = obj[first];
858
- if (val && typeof val === 'object') {
859
- const restKey = rest.join('.');
860
- if (Array.isArray(val)) {
861
- // Map over array elements (e.g., allocations[].salesInvoiceId)
862
- const existing = Array.isArray(result[first]) ? result[first] : [];
863
- result[first] = val.map((item, i) => ({
864
- ...(existing[i] || {}),
865
- ...pick(item, [restKey]),
866
- }));
867
- }
868
- else {
869
- const nested = pick(val, [restKey]);
870
- result[first] = { ...(result[first] || {}), ...nested };
871
- }
860
+ if (isUnsafeKey(first))
861
+ continue;
862
+ const list = nestedKeys.get(first);
863
+ if (list) {
864
+ list.push(rest.join('.'));
872
865
  }
866
+ else {
867
+ nestedKeys.set(first, [rest.join('.')]);
868
+ }
869
+ }
870
+ else {
871
+ directKeys.add(key);
873
872
  }
874
- else if (key in obj) {
873
+ }
874
+ const result = {};
875
+ for (const key of directKeys) {
876
+ if (key in obj)
875
877
  result[key] = obj[key];
878
+ }
879
+ for (const [first, restKeys] of nestedKeys) {
880
+ const val = obj[first];
881
+ if (!val || typeof val !== 'object')
882
+ continue;
883
+ if (Array.isArray(val)) {
884
+ result[first] = val.map(item => pick(item, restKeys));
885
+ }
886
+ else {
887
+ result[first] = pick(val, restKeys);
876
888
  }
877
889
  }
878
890
  return result;
@@ -6,13 +6,14 @@ const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = ["line-items", "notes", "files"];
9
- const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "submit", "approve", "accept", "terminate", "cancel", "replace", "create-invoice", "create-line-items", "update-line-items-reorder", "update-line-items", "delete-line-items", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "pdf", "submit", "approve", "accept", "terminate", "cancel", "replace", "create-invoice", "create-line-items", "update-line-items-reorder", "update-line-items", "delete-line-items", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
11
  'list': { method: 'GET', path: '/v1/sales-orders', summary: 'Get all sales orders with pagination', pathParams: [], hasBody: false, isMultipart: false },
12
12
  'create': { method: 'POST', path: '/v1/sales-orders', summary: 'Create a new sales order', pathParams: [], hasBody: true, isMultipart: false },
13
13
  'get': { method: 'GET', path: '/v1/sales-orders/{id}', summary: 'Get a sales order by ID', pathParams: ['id'], hasBody: false, isMultipart: false },
14
14
  'update': { method: 'PATCH', path: '/v1/sales-orders/{id}', summary: 'Update a sales order', pathParams: ['id'], hasBody: true, isMultipart: false },
15
15
  'delete': { method: 'DELETE', path: '/v1/sales-orders/{id}', summary: 'Delete a sales order', pathParams: ['id'], hasBody: false, isMultipart: false },
16
+ 'pdf': { method: 'GET', path: '/v1/sales-orders/{id}/pdf', summary: 'Generate a PDF preview (delivery slip) of a sales order', pathParams: ['id'], hasBody: false, isMultipart: false },
16
17
  'submit': { method: 'POST', path: '/v1/sales-orders/{id}/submit', summary: 'Submit order for approval (DRAFT → PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false },
17
18
  'approve': { method: 'POST', path: '/v1/sales-orders/{id}/approve', summary: 'Approve order (PENDING_APPROVAL → APPROVED). Generates order number.', pathParams: ['id'], hasBody: false, isMultipart: false },
18
19
  'accept': { method: 'POST', path: '/v1/sales-orders/{id}/accept', summary: 'Accept order (APPROVED → ACCEPTED). Adjusts billing start date.', pathParams: ['id'], hasBody: false, isMultipart: false },
@@ -844,32 +845,44 @@ export default class SalesOrders extends Command {
844
845
  filterFields(data, fields) {
845
846
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
846
847
  const pick = (obj, keys) => {
847
- const result = {};
848
+ // Group keys by their first segment so sibling sub-paths
849
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
850
+ // and don't clobber each other on merge.
851
+ const directKeys = new Set();
852
+ const nestedKeys = new Map();
848
853
  for (const key of keys) {
849
854
  if (key.split('.').some(isUnsafeKey))
850
855
  continue;
851
856
  if (key.includes('.')) {
852
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
853
857
  const [first, ...rest] = key.split('.');
854
- const val = obj[first];
855
- if (val && typeof val === 'object') {
856
- const restKey = rest.join('.');
857
- if (Array.isArray(val)) {
858
- // Map over array elements (e.g., allocations[].salesInvoiceId)
859
- const existing = Array.isArray(result[first]) ? result[first] : [];
860
- result[first] = val.map((item, i) => ({
861
- ...(existing[i] || {}),
862
- ...pick(item, [restKey]),
863
- }));
864
- }
865
- else {
866
- const nested = pick(val, [restKey]);
867
- result[first] = { ...(result[first] || {}), ...nested };
868
- }
858
+ if (isUnsafeKey(first))
859
+ continue;
860
+ const list = nestedKeys.get(first);
861
+ if (list) {
862
+ list.push(rest.join('.'));
869
863
  }
864
+ else {
865
+ nestedKeys.set(first, [rest.join('.')]);
866
+ }
867
+ }
868
+ else {
869
+ directKeys.add(key);
870
870
  }
871
- else if (key in obj) {
871
+ }
872
+ const result = {};
873
+ for (const key of directKeys) {
874
+ if (key in obj)
872
875
  result[key] = obj[key];
876
+ }
877
+ for (const [first, restKeys] of nestedKeys) {
878
+ const val = obj[first];
879
+ if (!val || typeof val !== 'object')
880
+ continue;
881
+ if (Array.isArray(val)) {
882
+ result[first] = val.map(item => pick(item, restKeys));
883
+ }
884
+ else {
885
+ result[first] = pick(val, restKeys);
873
886
  }
874
887
  }
875
888
  return result;
@@ -3,4 +3,5 @@ export default class Switch extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  run(): Promise<void>;
6
+ private handleSessionExpired;
6
7
  }
@@ -1,6 +1,7 @@
1
- import { select } from '@inquirer/prompts';
1
+ import { confirm, select } from '@inquirer/prompts';
2
2
  import { Command } from '@oclif/core';
3
- import { getCurrentContext, listContexts, setCurrentContext } from '../lib/config.js';
3
+ import { loginAndSwitchTenant } from '../lib/auth.js';
4
+ import { getContext, getCurrentContext, listContexts, setCurrentContext } from '../lib/config.js';
4
5
  export default class Switch extends Command {
5
6
  static description = 'Switch context and tenant interactively';
6
7
  static examples = ['<%= config.bin %> switch'];
@@ -34,8 +35,62 @@ export default class Switch extends Command {
34
35
  setCurrentContext(selectedContext);
35
36
  this.log(`Switched to context: ${selectedContext}`);
36
37
  }
37
- // Step 2: Select tenant within chosen context
38
+ // Step 2: Select tenant within chosen context.
39
+ // If the session is expired, prompt the user to re-login and then retry.
38
40
  const { default: AuthTenant } = await import('./auth/tenant.js');
41
+ try {
42
+ await AuthTenant.run([`--context=${selectedContext}`]);
43
+ }
44
+ catch (error) {
45
+ if (!isSessionExpired(error))
46
+ throw error;
47
+ await this.handleSessionExpired(selectedContext);
48
+ }
49
+ }
50
+ async handleSessionExpired(selectedContext) {
51
+ const shouldLogin = await confirm({
52
+ default: true,
53
+ message: 'Session expired. Do you want to login again?',
54
+ });
55
+ if (!shouldLogin)
56
+ return;
57
+ // If we have a cached tenant list from a previous session, let the user pick
58
+ // tenant FIRST. Then run the combined login + tenant-switch flow which uses
59
+ // a single browser tab (chained PKCE rounds with a meta-refresh redirect).
60
+ const ctx = getContext(selectedContext);
61
+ const cached = ctx?.cachedTenants ?? [];
62
+ if (cached.length > 1) {
63
+ const sorted = [...cached].sort((a, b) => {
64
+ if (a.tenantId === ctx?.tenant_id)
65
+ return -1;
66
+ if (b.tenantId === ctx?.tenant_id)
67
+ return 1;
68
+ return a.name.localeCompare(b.name);
69
+ });
70
+ const selectedTenant = await select({
71
+ choices: sorted.map((t) => ({
72
+ name: t.tenantId === ctx?.tenant_id ? `${t.name} (previous)` : t.name,
73
+ value: t.tenantId,
74
+ })),
75
+ message: 'Select a tenant:',
76
+ });
77
+ const selectedName = cached.find((t) => t.tenantId === selectedTenant)?.name;
78
+ this.log(`Logging in and switching to ${selectedName ?? selectedTenant}…`);
79
+ if (ctx) {
80
+ await loginAndSwitchTenant(ctx, selectedTenant);
81
+ }
82
+ this.log(`Successfully switched to tenant ${selectedName ? `${selectedName} (${selectedTenant})` : selectedTenant}`);
83
+ return;
84
+ }
85
+ // No cached tenant list (or only one tenant) — fall back to the two-step
86
+ // flow: login first, then run the normal tenant selector.
87
+ const { default: AuthLogin } = await import('./auth/login.js');
88
+ const { default: AuthTenant } = await import('./auth/tenant.js');
89
+ await AuthLogin.run([`--context=${selectedContext}`]);
39
90
  await AuthTenant.run([`--context=${selectedContext}`]);
40
91
  }
41
92
  }
93
+ function isSessionExpired(error) {
94
+ const message = error instanceof Error ? error.message : String(error);
95
+ return /session expired/i.test(message) || /not logged in/i.test(message);
96
+ }
@@ -819,32 +819,44 @@ export default class Tags extends Command {
819
819
  filterFields(data, fields) {
820
820
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
821
821
  const pick = (obj, keys) => {
822
- const result = {};
822
+ // Group keys by their first segment so sibling sub-paths
823
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
824
+ // and don't clobber each other on merge.
825
+ const directKeys = new Set();
826
+ const nestedKeys = new Map();
823
827
  for (const key of keys) {
824
828
  if (key.split('.').some(isUnsafeKey))
825
829
  continue;
826
830
  if (key.includes('.')) {
827
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
828
831
  const [first, ...rest] = key.split('.');
829
- const val = obj[first];
830
- if (val && typeof val === 'object') {
831
- const restKey = rest.join('.');
832
- if (Array.isArray(val)) {
833
- // Map over array elements (e.g., allocations[].salesInvoiceId)
834
- const existing = Array.isArray(result[first]) ? result[first] : [];
835
- result[first] = val.map((item, i) => ({
836
- ...(existing[i] || {}),
837
- ...pick(item, [restKey]),
838
- }));
839
- }
840
- else {
841
- const nested = pick(val, [restKey]);
842
- result[first] = { ...(result[first] || {}), ...nested };
843
- }
832
+ if (isUnsafeKey(first))
833
+ continue;
834
+ const list = nestedKeys.get(first);
835
+ if (list) {
836
+ list.push(rest.join('.'));
844
837
  }
838
+ else {
839
+ nestedKeys.set(first, [rest.join('.')]);
840
+ }
841
+ }
842
+ else {
843
+ directKeys.add(key);
845
844
  }
846
- else if (key in obj) {
845
+ }
846
+ const result = {};
847
+ for (const key of directKeys) {
848
+ if (key in obj)
847
849
  result[key] = obj[key];
850
+ }
851
+ for (const [first, restKeys] of nestedKeys) {
852
+ const val = obj[first];
853
+ if (!val || typeof val !== 'object')
854
+ continue;
855
+ if (Array.isArray(val)) {
856
+ result[first] = val.map(item => pick(item, restKeys));
857
+ }
858
+ else {
859
+ result[first] = pick(val, restKeys);
848
860
  }
849
861
  }
850
862
  return result;
@@ -822,32 +822,44 @@ export default class TaxCodes extends Command {
822
822
  filterFields(data, fields) {
823
823
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
824
824
  const pick = (obj, keys) => {
825
- const result = {};
825
+ // Group keys by their first segment so sibling sub-paths
826
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
827
+ // and don't clobber each other on merge.
828
+ const directKeys = new Set();
829
+ const nestedKeys = new Map();
826
830
  for (const key of keys) {
827
831
  if (key.split('.').some(isUnsafeKey))
828
832
  continue;
829
833
  if (key.includes('.')) {
830
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
831
834
  const [first, ...rest] = key.split('.');
832
- const val = obj[first];
833
- if (val && typeof val === 'object') {
834
- const restKey = rest.join('.');
835
- if (Array.isArray(val)) {
836
- // Map over array elements (e.g., allocations[].salesInvoiceId)
837
- const existing = Array.isArray(result[first]) ? result[first] : [];
838
- result[first] = val.map((item, i) => ({
839
- ...(existing[i] || {}),
840
- ...pick(item, [restKey]),
841
- }));
842
- }
843
- else {
844
- const nested = pick(val, [restKey]);
845
- result[first] = { ...(result[first] || {}), ...nested };
846
- }
835
+ if (isUnsafeKey(first))
836
+ continue;
837
+ const list = nestedKeys.get(first);
838
+ if (list) {
839
+ list.push(rest.join('.'));
847
840
  }
841
+ else {
842
+ nestedKeys.set(first, [rest.join('.')]);
843
+ }
844
+ }
845
+ else {
846
+ directKeys.add(key);
848
847
  }
849
- else if (key in obj) {
848
+ }
849
+ const result = {};
850
+ for (const key of directKeys) {
851
+ if (key in obj)
850
852
  result[key] = obj[key];
853
+ }
854
+ for (const [first, restKeys] of nestedKeys) {
855
+ const val = obj[first];
856
+ if (!val || typeof val !== 'object')
857
+ continue;
858
+ if (Array.isArray(val)) {
859
+ result[first] = val.map(item => pick(item, restKeys));
860
+ }
861
+ else {
862
+ result[first] = pick(val, restKeys);
851
863
  }
852
864
  }
853
865
  return result;
@@ -819,32 +819,44 @@ export default class TaxMatrix extends Command {
819
819
  filterFields(data, fields) {
820
820
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
821
821
  const pick = (obj, keys) => {
822
- const result = {};
822
+ // Group keys by their first segment so sibling sub-paths
823
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
824
+ // and don't clobber each other on merge.
825
+ const directKeys = new Set();
826
+ const nestedKeys = new Map();
823
827
  for (const key of keys) {
824
828
  if (key.split('.').some(isUnsafeKey))
825
829
  continue;
826
830
  if (key.includes('.')) {
827
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
828
831
  const [first, ...rest] = key.split('.');
829
- const val = obj[first];
830
- if (val && typeof val === 'object') {
831
- const restKey = rest.join('.');
832
- if (Array.isArray(val)) {
833
- // Map over array elements (e.g., allocations[].salesInvoiceId)
834
- const existing = Array.isArray(result[first]) ? result[first] : [];
835
- result[first] = val.map((item, i) => ({
836
- ...(existing[i] || {}),
837
- ...pick(item, [restKey]),
838
- }));
839
- }
840
- else {
841
- const nested = pick(val, [restKey]);
842
- result[first] = { ...(result[first] || {}), ...nested };
843
- }
832
+ if (isUnsafeKey(first))
833
+ continue;
834
+ const list = nestedKeys.get(first);
835
+ if (list) {
836
+ list.push(rest.join('.'));
844
837
  }
838
+ else {
839
+ nestedKeys.set(first, [rest.join('.')]);
840
+ }
841
+ }
842
+ else {
843
+ directKeys.add(key);
845
844
  }
846
- else if (key in obj) {
845
+ }
846
+ const result = {};
847
+ for (const key of directKeys) {
848
+ if (key in obj)
847
849
  result[key] = obj[key];
850
+ }
851
+ for (const [first, restKeys] of nestedKeys) {
852
+ const val = obj[first];
853
+ if (!val || typeof val !== 'object')
854
+ continue;
855
+ if (Array.isArray(val)) {
856
+ result[first] = val.map(item => pick(item, restKeys));
857
+ }
858
+ else {
859
+ result[first] = pick(val, restKeys);
848
860
  }
849
861
  }
850
862
  return result;
@@ -819,32 +819,44 @@ export default class Tenants extends Command {
819
819
  filterFields(data, fields) {
820
820
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
821
821
  const pick = (obj, keys) => {
822
- const result = {};
822
+ // Group keys by their first segment so sibling sub-paths
823
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
824
+ // and don't clobber each other on merge.
825
+ const directKeys = new Set();
826
+ const nestedKeys = new Map();
823
827
  for (const key of keys) {
824
828
  if (key.split('.').some(isUnsafeKey))
825
829
  continue;
826
830
  if (key.includes('.')) {
827
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
828
831
  const [first, ...rest] = key.split('.');
829
- const val = obj[first];
830
- if (val && typeof val === 'object') {
831
- const restKey = rest.join('.');
832
- if (Array.isArray(val)) {
833
- // Map over array elements (e.g., allocations[].salesInvoiceId)
834
- const existing = Array.isArray(result[first]) ? result[first] : [];
835
- result[first] = val.map((item, i) => ({
836
- ...(existing[i] || {}),
837
- ...pick(item, [restKey]),
838
- }));
839
- }
840
- else {
841
- const nested = pick(val, [restKey]);
842
- result[first] = { ...(result[first] || {}), ...nested };
843
- }
832
+ if (isUnsafeKey(first))
833
+ continue;
834
+ const list = nestedKeys.get(first);
835
+ if (list) {
836
+ list.push(rest.join('.'));
844
837
  }
838
+ else {
839
+ nestedKeys.set(first, [rest.join('.')]);
840
+ }
841
+ }
842
+ else {
843
+ directKeys.add(key);
845
844
  }
846
- else if (key in obj) {
845
+ }
846
+ const result = {};
847
+ for (const key of directKeys) {
848
+ if (key in obj)
847
849
  result[key] = obj[key];
850
+ }
851
+ for (const [first, restKeys] of nestedKeys) {
852
+ const val = obj[first];
853
+ if (!val || typeof val !== 'object')
854
+ continue;
855
+ if (Array.isArray(val)) {
856
+ result[first] = val.map(item => pick(item, restKeys));
857
+ }
858
+ else {
859
+ result[first] = pick(val, restKeys);
848
860
  }
849
861
  }
850
862
  return result;
@@ -819,32 +819,44 @@ export default class TextTemplates extends Command {
819
819
  filterFields(data, fields) {
820
820
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
821
821
  const pick = (obj, keys) => {
822
- const result = {};
822
+ // Group keys by their first segment so sibling sub-paths
823
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
824
+ // and don't clobber each other on merge.
825
+ const directKeys = new Set();
826
+ const nestedKeys = new Map();
823
827
  for (const key of keys) {
824
828
  if (key.split('.').some(isUnsafeKey))
825
829
  continue;
826
830
  if (key.includes('.')) {
827
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
828
831
  const [first, ...rest] = key.split('.');
829
- const val = obj[first];
830
- if (val && typeof val === 'object') {
831
- const restKey = rest.join('.');
832
- if (Array.isArray(val)) {
833
- // Map over array elements (e.g., allocations[].salesInvoiceId)
834
- const existing = Array.isArray(result[first]) ? result[first] : [];
835
- result[first] = val.map((item, i) => ({
836
- ...(existing[i] || {}),
837
- ...pick(item, [restKey]),
838
- }));
839
- }
840
- else {
841
- const nested = pick(val, [restKey]);
842
- result[first] = { ...(result[first] || {}), ...nested };
843
- }
832
+ if (isUnsafeKey(first))
833
+ continue;
834
+ const list = nestedKeys.get(first);
835
+ if (list) {
836
+ list.push(rest.join('.'));
844
837
  }
838
+ else {
839
+ nestedKeys.set(first, [rest.join('.')]);
840
+ }
841
+ }
842
+ else {
843
+ directKeys.add(key);
845
844
  }
846
- else if (key in obj) {
845
+ }
846
+ const result = {};
847
+ for (const key of directKeys) {
848
+ if (key in obj)
847
849
  result[key] = obj[key];
850
+ }
851
+ for (const [first, restKeys] of nestedKeys) {
852
+ const val = obj[first];
853
+ if (!val || typeof val !== 'object')
854
+ continue;
855
+ if (Array.isArray(val)) {
856
+ result[first] = val.map(item => pick(item, restKeys));
857
+ }
858
+ else {
859
+ result[first] = pick(val, restKeys);
848
860
  }
849
861
  }
850
862
  return result;
@@ -812,32 +812,44 @@ export default class Tools extends Command {
812
812
  filterFields(data, fields) {
813
813
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
814
814
  const pick = (obj, keys) => {
815
- const result = {};
815
+ // Group keys by their first segment so sibling sub-paths
816
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
817
+ // and don't clobber each other on merge.
818
+ const directKeys = new Set();
819
+ const nestedKeys = new Map();
816
820
  for (const key of keys) {
817
821
  if (key.split('.').some(isUnsafeKey))
818
822
  continue;
819
823
  if (key.includes('.')) {
820
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
821
824
  const [first, ...rest] = key.split('.');
822
- const val = obj[first];
823
- if (val && typeof val === 'object') {
824
- const restKey = rest.join('.');
825
- if (Array.isArray(val)) {
826
- // Map over array elements (e.g., allocations[].salesInvoiceId)
827
- const existing = Array.isArray(result[first]) ? result[first] : [];
828
- result[first] = val.map((item, i) => ({
829
- ...(existing[i] || {}),
830
- ...pick(item, [restKey]),
831
- }));
832
- }
833
- else {
834
- const nested = pick(val, [restKey]);
835
- result[first] = { ...(result[first] || {}), ...nested };
836
- }
825
+ if (isUnsafeKey(first))
826
+ continue;
827
+ const list = nestedKeys.get(first);
828
+ if (list) {
829
+ list.push(rest.join('.'));
837
830
  }
831
+ else {
832
+ nestedKeys.set(first, [rest.join('.')]);
833
+ }
834
+ }
835
+ else {
836
+ directKeys.add(key);
838
837
  }
839
- else if (key in obj) {
838
+ }
839
+ const result = {};
840
+ for (const key of directKeys) {
841
+ if (key in obj)
840
842
  result[key] = obj[key];
843
+ }
844
+ for (const [first, restKeys] of nestedKeys) {
845
+ const val = obj[first];
846
+ if (!val || typeof val !== 'object')
847
+ continue;
848
+ if (Array.isArray(val)) {
849
+ result[first] = val.map(item => pick(item, restKeys));
850
+ }
851
+ else {
852
+ result[first] = pick(val, restKeys);
841
853
  }
842
854
  }
843
855
  return result;