@open-mercato/core 0.6.6-develop.6542.1.fee3e68aa6 → 0.6.6-develop.6551.1.1511bfa890

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 (162) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/gateway_session_initialization/index.js +23 -0
  3. package/dist/generated/entities/gateway_session_initialization/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +1 -0
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +12 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/api_keys/api/keys/route.js +7 -4
  9. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  10. package/dist/modules/attachments/components/AttachmentContentPreview.js +6 -35
  11. package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
  12. package/dist/modules/audit_logs/backend/audit-logs/page.js +14 -24
  13. package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
  14. package/dist/modules/auth/api/autologin.js +85 -0
  15. package/dist/modules/auth/api/autologin.js.map +7 -0
  16. package/dist/modules/auth/lib/autologin.js +19 -0
  17. package/dist/modules/auth/lib/autologin.js.map +7 -0
  18. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +1 -0
  19. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
  20. package/dist/modules/customer_accounts/api/admin/roles/[id].js +17 -3
  21. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  22. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +6 -1
  23. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  24. package/dist/modules/customer_accounts/api/portal/users-invite.js +6 -1
  25. package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
  26. package/dist/modules/customer_accounts/services/customerInvitationService.js +1 -0
  27. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
  28. package/dist/modules/customer_accounts/services/customerRbacService.js +6 -1
  29. package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
  30. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +2 -2
  31. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
  32. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +27 -37
  33. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  34. package/dist/modules/customers/components/detail/DealDetailTabs.js +23 -35
  35. package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
  36. package/dist/modules/customers/components/detail/DetailTabsLayout.js +6 -21
  37. package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
  38. package/dist/modules/customers/components/detail/MobilePersonDetail.js +26 -32
  39. package/dist/modules/customers/components/detail/MobilePersonDetail.js.map +2 -2
  40. package/dist/modules/customers/components/detail/PersonDetailTabs.js +29 -39
  41. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  42. package/dist/modules/dictionaries/commands/entries.js +7 -14
  43. package/dist/modules/dictionaries/commands/entries.js.map +2 -2
  44. package/dist/modules/dictionaries/commands/entry-operations.js +7 -16
  45. package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
  46. package/dist/modules/dictionaries/commands/factory.js +9 -3
  47. package/dist/modules/dictionaries/commands/factory.js.map +2 -2
  48. package/dist/modules/feature_toggles/api/check/boolean/route.js +0 -14
  49. package/dist/modules/feature_toggles/api/check/boolean/route.js.map +2 -2
  50. package/dist/modules/feature_toggles/defaults.json +8 -0
  51. package/dist/modules/integrations/backend/integrations/[id]/page.js +9 -80
  52. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  53. package/dist/modules/payment_gateways/data/entities.js +45 -0
  54. package/dist/modules/payment_gateways/data/entities.js.map +2 -2
  55. package/dist/modules/payment_gateways/lib/gateway-service.js +173 -38
  56. package/dist/modules/payment_gateways/lib/gateway-service.js.map +3 -3
  57. package/dist/modules/payment_gateways/lib/session-idempotency.js +104 -0
  58. package/dist/modules/payment_gateways/lib/session-idempotency.js.map +7 -0
  59. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js +11 -0
  60. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js.map +7 -0
  61. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +16 -14
  62. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
  63. package/dist/modules/planner/components/AvailabilityRulesEditor.js +18 -30
  64. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  65. package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -24
  66. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  67. package/dist/modules/sales/backend/sales/channels/create/page.js +6 -0
  68. package/dist/modules/sales/backend/sales/channels/create/page.js.map +2 -2
  69. package/dist/modules/sales/backend/sales/channels/create/page.meta.js +2 -0
  70. package/dist/modules/sales/backend/sales/channels/create/page.meta.js.map +2 -2
  71. package/dist/modules/sales/backend/sales/channels/offers/page.js +6 -0
  72. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  73. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js +2 -0
  74. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js.map +2 -2
  75. package/dist/modules/sales/backend/sales/channels/page.js +6 -0
  76. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  77. package/dist/modules/sales/backend/sales/channels/page.meta.js +2 -0
  78. package/dist/modules/sales/backend/sales/channels/page.meta.js.map +2 -2
  79. package/dist/modules/sales/backend/sales/documents/[id]/page.js +6 -4
  80. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  81. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js +15 -0
  82. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js.map +7 -0
  83. package/dist/modules/sales/components/documents/SalesDocumentForm.js +7 -3
  84. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  85. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -8
  86. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  87. package/dist/modules/sales/components/useSalesChannelsEnabled.js +44 -0
  88. package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +7 -0
  89. package/dist/modules/sales/lib/salesChannelsToggle.js +18 -0
  90. package/dist/modules/sales/lib/salesChannelsToggle.js.map +7 -0
  91. package/dist/modules/sales/lib/salesChannelsToggleId.js +5 -0
  92. package/dist/modules/sales/lib/salesChannelsToggleId.js.map +7 -0
  93. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +22 -25
  94. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  95. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -14
  96. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  97. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +10 -29
  98. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +2 -2
  99. package/dist/modules/sync_excel/lib/adapters/customers.js +19 -20
  100. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  101. package/dist/modules/sync_excel/lib/parser.js +185 -0
  102. package/dist/modules/sync_excel/lib/parser.js.map +2 -2
  103. package/dist/modules/sync_excel/lib/upload-storage.js +20 -1
  104. package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
  105. package/generated/entities/gateway_session_initialization/index.ts +10 -0
  106. package/generated/entities.ids.generated.ts +1 -0
  107. package/generated/entity-fields-registry.ts +12 -0
  108. package/package.json +7 -7
  109. package/src/modules/api_keys/api/keys/route.ts +9 -4
  110. package/src/modules/attachments/components/AttachmentContentPreview.tsx +9 -44
  111. package/src/modules/audit_logs/backend/audit-logs/page.tsx +12 -22
  112. package/src/modules/auth/api/autologin.ts +100 -0
  113. package/src/modules/auth/lib/autologin.ts +36 -0
  114. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +1 -0
  115. package/src/modules/customer_accounts/api/admin/roles/[id].ts +17 -3
  116. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +6 -1
  117. package/src/modules/customer_accounts/api/portal/users-invite.ts +6 -1
  118. package/src/modules/customer_accounts/services/customerInvitationService.ts +1 -0
  119. package/src/modules/customer_accounts/services/customerRbacService.ts +9 -2
  120. package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +6 -2
  121. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +27 -49
  122. package/src/modules/customers/components/detail/DealDetailTabs.tsx +24 -49
  123. package/src/modules/customers/components/detail/DetailTabsLayout.tsx +13 -24
  124. package/src/modules/customers/components/detail/MobilePersonDetail.tsx +25 -31
  125. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +29 -50
  126. package/src/modules/dictionaries/commands/entries.ts +7 -16
  127. package/src/modules/dictionaries/commands/entry-operations.ts +8 -18
  128. package/src/modules/dictionaries/commands/factory.ts +8 -2
  129. package/src/modules/feature_toggles/api/check/boolean/route.ts +0 -15
  130. package/src/modules/feature_toggles/defaults.json +8 -0
  131. package/src/modules/integrations/backend/integrations/[id]/page.tsx +16 -59
  132. package/src/modules/payment_gateways/data/entities.ts +39 -0
  133. package/src/modules/payment_gateways/lib/gateway-service.ts +211 -40
  134. package/src/modules/payment_gateways/lib/session-idempotency.ts +142 -0
  135. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +236 -0
  136. package/src/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.ts +10 -0
  137. package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +14 -20
  138. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +16 -34
  139. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +27 -37
  140. package/src/modules/sales/backend/sales/channels/create/page.meta.ts +3 -0
  141. package/src/modules/sales/backend/sales/channels/create/page.tsx +7 -0
  142. package/src/modules/sales/backend/sales/channels/offers/page.meta.ts +3 -0
  143. package/src/modules/sales/backend/sales/channels/offers/page.tsx +7 -0
  144. package/src/modules/sales/backend/sales/channels/page.meta.ts +4 -0
  145. package/src/modules/sales/backend/sales/channels/page.tsx +7 -0
  146. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +7 -5
  147. package/src/modules/sales/components/SalesChannelsDisabledNotice.tsx +21 -0
  148. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +7 -3
  149. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +10 -8
  150. package/src/modules/sales/components/useSalesChannelsEnabled.ts +59 -0
  151. package/src/modules/sales/i18n/de.json +1 -0
  152. package/src/modules/sales/i18n/en.json +1 -0
  153. package/src/modules/sales/i18n/es.json +1 -0
  154. package/src/modules/sales/i18n/pl.json +1 -0
  155. package/src/modules/sales/lib/salesChannelsToggle.ts +26 -0
  156. package/src/modules/sales/lib/salesChannelsToggleId.ts +1 -0
  157. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +25 -34
  158. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +11 -17
  159. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +11 -28
  160. package/src/modules/sync_excel/lib/adapters/customers.ts +20 -22
  161. package/src/modules/sync_excel/lib/parser.ts +245 -0
  162. package/src/modules/sync_excel/lib/upload-storage.ts +23 -1
@@ -6,8 +6,8 @@ import { CustomerAddress, CustomerEntity } from "../../../customers/data/entitie
6
6
  import { Attachment } from "../../../attachments/data/entities.js";
7
7
  import { CustomFieldDef } from "../../../entities/data/entities.js";
8
8
  import { SyncExcelUpload } from "../../data/entities.js";
9
- import { parseCsvDocument } from "../parser.js";
10
- import { readSyncExcelUploadBuffer } from "../upload-storage.js";
9
+ import { parseCsvDocumentBatches, parseCsvStreamMetadata } from "../parser.js";
10
+ import { createSyncExcelUploadReadStream } from "../upload-storage.js";
11
11
  import { E } from "../../../../generated/entities.ids.generated.js";
12
12
  const CUSTOMER_IMPORT_COVERAGE_ENTITY_TYPES = [
13
13
  E.customers.customer_entity,
@@ -580,14 +580,11 @@ async function resolveExistingPersonId(params) {
580
580
  if (!params.email) return null;
581
581
  return params.emailDedupeIndex.get(params.email) ?? null;
582
582
  }
583
+ function mappingHasEmailField(mapping) {
584
+ return mapping.fields.some((field) => field.localField === "person.primaryEmail");
585
+ }
583
586
  async function buildEmailDedupeIndex(params) {
584
- const emails = /* @__PURE__ */ new Set();
585
- for (const row of params.rows) {
586
- const rowValues = mapRowValues(row, params.mapping.fields);
587
- const email = rowValues.values.primaryEmail;
588
- if (email) emails.add(email);
589
- }
590
- if (emails.size === 0) return /* @__PURE__ */ new Map();
587
+ if (!mappingHasEmailField(params.mapping)) return /* @__PURE__ */ new Map();
591
588
  const candidates = await findWithDecryption(
592
589
  params.em,
593
590
  CustomerEntity,
@@ -608,7 +605,7 @@ async function buildEmailDedupeIndex(params) {
608
605
  const index = /* @__PURE__ */ new Map();
609
606
  for (const candidate of candidates) {
610
607
  const email = normalizeEmail(candidate.primaryEmail);
611
- if (!email || !emails.has(email) || index.has(email)) continue;
608
+ if (!email || index.has(email)) continue;
612
609
  index.set(email, candidate.id);
613
610
  }
614
611
  return index;
@@ -795,24 +792,26 @@ const syncExcelCustomersAdapter = {
795
792
  if (!attachment) {
796
793
  throw new Error("CSV upload attachment could not be found.");
797
794
  }
798
- const fileBuffer = await readSyncExcelUploadBuffer(attachment);
799
- const document = parseCsvDocument(fileBuffer);
795
+ const document = await parseCsvStreamMetadata(await createSyncExcelUploadReadStream(attachment));
800
796
  const startOffset = cursor?.uploadId === upload.id ? cursor.offset : 0;
801
797
  const commandContext = buildCommandContext(container, input.scope);
802
798
  const customFieldDefinitions = await loadImportCustomFieldDefinitions(em, input.scope);
803
799
  const emailDedupeIndex = await buildEmailDedupeIndex({
804
800
  em,
805
- rows: document.rows.slice(startOffset),
806
801
  mapping: input.mapping,
807
802
  scope: input.scope
808
803
  });
809
- for (let offset = startOffset, batchIndex = 0; offset < document.rows.length; offset += input.batchSize, batchIndex += 1) {
810
- const batchRows = document.rows.slice(offset, offset + input.batchSize);
804
+ let batchIndex = 0;
805
+ for await (const batch of parseCsvDocumentBatches(await createSyncExcelUploadReadStream(attachment), {
806
+ batchSize: input.batchSize,
807
+ startOffset
808
+ })) {
809
+ const batchRows = batch.rows;
811
810
  const items = [];
812
811
  for (let index = 0; index < batchRows.length; index += 1) {
813
812
  items.push(await processRow({
814
813
  row: batchRows[index],
815
- rowNumber: offset + index + 1,
814
+ rowNumber: batch.rowStart + index + 1,
816
815
  mapping: input.mapping,
817
816
  scope: input.scope,
818
817
  commandBus,
@@ -823,17 +822,17 @@ const syncExcelCustomersAdapter = {
823
822
  em
824
823
  }));
825
824
  }
826
- const nextOffset = offset + batchRows.length;
827
825
  yield {
828
826
  items,
829
- cursor: createCursor(upload.id, nextOffset),
830
- hasMore: nextOffset < document.rows.length,
827
+ cursor: createCursor(upload.id, batch.nextOffset),
828
+ hasMore: batch.nextOffset < document.totalRows,
831
829
  totalEstimate: document.totalRows,
832
830
  processedCount: batchRows.length,
833
831
  refreshCoverageEntityTypes: CUSTOMER_IMPORT_COVERAGE_ENTITY_TYPES,
834
832
  batchIndex,
835
- message: `Processed ${nextOffset} of ${document.totalRows} CSV rows`
833
+ message: `Processed ${batch.nextOffset} of ${document.totalRows} CSV rows`
836
834
  };
835
+ batchIndex += 1;
837
836
  }
838
837
  upload.status = "completed";
839
838
  await em.flush();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/sync_excel/lib/adapters/customers.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { extractPhoneDigits, validatePhoneNumber } from '@open-mercato/shared/lib/phone'\nimport type {\n DataMapping,\n DataSyncAdapter,\n FieldMapping,\n ImportBatch,\n ImportItem,\n TenantScope,\n} from '../../../data_sync/lib/adapter'\nimport type { ExternalIdMappingService } from '../../../data_sync/lib/id-mapping'\nimport { SyncMapping } from '../../../data_sync/data/entities'\nimport { CustomerAddress, CustomerEntity } from '../../../customers/data/entities'\nimport { Attachment } from '../../../attachments/data/entities'\nimport { CustomFieldDef } from '../../../entities/data/entities'\nimport { SyncExcelUpload } from '../../data/entities'\nimport { parseCsvDocument, type CsvPreviewRow } from '../parser'\nimport { readSyncExcelUploadBuffer } from '../upload-storage'\nimport { E } from '#generated/entities.ids.generated'\n\ntype SyncExcelCursor = {\n uploadId: string\n offset: number\n}\n\ntype Container = Awaited<ReturnType<typeof createRequestContainer>>\n\nconst CUSTOMER_IMPORT_COVERAGE_ENTITY_TYPES = [\n E.customers.customer_entity,\n E.customers.customer_person_profile,\n E.customers.customer_address,\n]\n\ntype PersonFieldValues = {\n externalId?: string | null\n firstName?: string | null\n lastName?: string | null\n displayName?: string | null\n primaryEmail?: string | null\n primaryPhone?: string | null\n jobTitle?: string | null\n status?: string | null\n source?: string | null\n description?: string | null\n}\n\ntype AddressFieldValues = {\n name?: string | null\n purpose?: string | null\n companyName?: string | null\n addressLine1?: string | null\n addressLine2?: string | null\n buildingNumber?: string | null\n flatNumber?: string | null\n city?: string | null\n region?: string | null\n postalCode?: string | null\n country?: string | null\n latitude?: number | null\n longitude?: number | null\n}\n\ntype PersonRowValues = {\n values: PersonFieldValues\n customFields: Record<string, unknown>\n addressValues: AddressFieldValues\n}\n\ntype ImportCustomFieldDefinition = {\n key: string\n kind: string\n entityId: string\n organizationId?: string | null\n tenantId?: string | null\n updatedAt?: Date | string | null\n}\n\ntype EmailDedupeIndex = Map<string, string>\n\ntype BuiltPersonPayload = {\n values: PersonFieldValues\n customFields: Record<string, unknown>\n addressValues: AddressFieldValues\n createInput: {\n organizationId: string\n tenantId: string\n firstName: string\n lastName: string\n displayName: string\n primaryEmail?: string\n primaryPhone?: string\n jobTitle?: string\n status?: string\n source?: string\n description?: string\n customFields?: Record<string, unknown>\n } | null\n updateInput: {\n organizationId: string\n tenantId: string\n primaryEmail?: string\n primaryPhone?: string\n jobTitle?: string\n status?: string\n source?: string\n description?: string\n firstName?: string\n lastName?: string\n displayName?: string\n customFields?: Record<string, unknown>\n }\n sourceIdentifier: string | null\n}\n\nfunction normalizeOptionalString(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction normalizeEmail(value: unknown): string | null {\n const normalized = normalizeOptionalString(value)\n return normalized ? normalized.toLowerCase() : null\n}\n\nconst COUNTRY_DIAL_CODES: Record<string, string> = {\n canada: '1',\n england: '44',\n greatbritain: '44',\n poland: '48',\n scotland: '44',\n uk: '44',\n unitedkingdom: '44',\n unitedstates: '1',\n unitedstatesofamerica: '1',\n usa: '1',\n wales: '44',\n}\n\nfunction normalizeCountryKey(value: unknown): string | null {\n const normalized = normalizeOptionalString(value)\n if (!normalized) return null\n return normalized.toLowerCase().replace(/[^a-z]/g, '')\n}\n\nfunction resolveCountryDialCode(row: CsvPreviewRow): string | null {\n const countryCandidates = [\n row.Country,\n row.country,\n row['Country/Region'],\n row['country/region'],\n ]\n\n for (const candidate of countryCandidates) {\n const countryKey = normalizeCountryKey(candidate)\n if (!countryKey) continue\n const dialCode = COUNTRY_DIAL_CODES[countryKey]\n if (dialCode) return dialCode\n }\n\n return null\n}\n\nfunction stripPhoneExtension(value: string): string {\n return value\n .replace(/\\s*(ext\\.?|extension|x)\\s*\\d+$/i, '')\n .replace(/^\\+\\s+/, '+')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\nfunction normalizeImportedPhone(value: unknown, row: CsvPreviewRow): string | null {\n const normalized = normalizeOptionalString(value)\n if (!normalized) return null\n\n const directValidation = validatePhoneNumber(normalized)\n if (directValidation.valid) {\n return directValidation.normalized\n }\n\n const sanitized = stripPhoneExtension(normalized)\n const sanitizedValidation = validatePhoneNumber(sanitized)\n if (sanitizedValidation.valid) {\n return sanitizedValidation.normalized\n }\n\n const digits = extractPhoneDigits(sanitized)\n if (!digits) return null\n\n if (digits.length >= 9 && digits.length <= 15 && sanitized.startsWith('00')) {\n const internationalCandidate = `+${digits.slice(2)}`\n const internationalValidation = validatePhoneNumber(internationalCandidate)\n if (internationalValidation.valid) {\n return internationalValidation.normalized\n }\n }\n\n const dialCode = resolveCountryDialCode(row)\n if (!dialCode) return null\n\n let nationalDigits = digits\n if (nationalDigits.startsWith(dialCode)) {\n nationalDigits = nationalDigits.slice(dialCode.length)\n } else {\n nationalDigits = nationalDigits.replace(/^0+/, '')\n }\n\n if (!nationalDigits) return null\n\n const localizedCandidate = `+${dialCode}${nationalDigits}`\n const localizedValidation = validatePhoneNumber(localizedCandidate)\n return localizedValidation.valid ? localizedValidation.normalized : null\n}\n\nfunction normalizeOptionalNumber(value: unknown): number | null {\n if (typeof value === 'number') {\n return Number.isFinite(value) ? value : null\n }\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n if (trimmed.length === 0) return null\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) ? parsed : null\n}\n\nfunction normalizeCustomFieldDate(value: string): string | null {\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return null\n return parsed.toISOString().slice(0, 10)\n}\n\nfunction normalizeCustomFieldBoolean(value: string): boolean | null {\n const normalized = value.trim().toLowerCase()\n if (['true', '1', 'yes', 'y', 't', 'tak'].includes(normalized)) return true\n if (['false', '0', 'no', 'n', 'f', 'nie'].includes(normalized)) return false\n return null\n}\n\nfunction coerceCustomFieldValue(\n key: string,\n value: unknown,\n definitions: Map<string, ImportCustomFieldDefinition>,\n): { ok: true; value: unknown } | { ok: false; error: string } {\n if (typeof value === 'string' && value.trim().length === 0) {\n return { ok: true, value: null }\n }\n\n const definition = definitions.get(key)\n if (!definition) {\n return { ok: true, value: typeof value === 'string' ? value.trim() : value }\n }\n\n if (value === null || value === undefined) {\n return { ok: true, value: null }\n }\n\n const text = typeof value === 'string' ? value.trim() : String(value)\n if (text.length === 0) {\n return { ok: true, value: null }\n }\n\n const kind = definition.kind.toLowerCase()\n if (kind === 'boolean') {\n const booleanValue = normalizeCustomFieldBoolean(text)\n if (booleanValue === null) {\n return { ok: false, error: `Custom field \"${key}\" expects a boolean value.` }\n }\n return { ok: true, value: booleanValue }\n }\n\n if (kind === 'integer') {\n const numberValue = Number(text)\n if (!Number.isInteger(numberValue)) {\n return { ok: false, error: `Custom field \"${key}\" expects an integer value.` }\n }\n return { ok: true, value: numberValue }\n }\n\n if (kind === 'float' || kind === 'currency' || kind === 'number') {\n const numberValue = Number(text)\n if (!Number.isFinite(numberValue)) {\n return { ok: false, error: `Custom field \"${key}\" expects a number value.` }\n }\n return { ok: true, value: numberValue }\n }\n\n if (kind === 'date' || kind === 'datetime') {\n const dateValue = normalizeCustomFieldDate(text)\n if (!dateValue) {\n return { ok: false, error: `Custom field \"${key}\" expects a date value.` }\n }\n return { ok: true, value: dateValue }\n }\n\n return { ok: true, value: text }\n}\n\nfunction coerceCustomFields(\n values: Record<string, unknown>,\n definitions: Map<string, ImportCustomFieldDefinition>,\n): { ok: true; values: Record<string, unknown> } | { ok: false; error: string } {\n const coerced: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(values)) {\n const result = coerceCustomFieldValue(key, value, definitions)\n if (!result.ok) return result\n coerced[key] = result.value\n }\n return { ok: true, values: coerced }\n}\n\nfunction customFieldDefinitionScopeScore(definition: ImportCustomFieldDefinition): number {\n return (definition.tenantId ? 2 : 0) + (definition.organizationId ? 1 : 0)\n}\n\nfunction customFieldDefinitionUpdatedAt(definition: ImportCustomFieldDefinition): number {\n if (!definition.updatedAt) return 0\n const value = definition.updatedAt instanceof Date\n ? definition.updatedAt.getTime()\n : new Date(definition.updatedAt).getTime()\n return Number.isFinite(value) ? value : 0\n}\n\nfunction preferCustomFieldDefinition(\n current: ImportCustomFieldDefinition | undefined,\n candidate: ImportCustomFieldDefinition,\n): boolean {\n if (!current) return true\n const candidateScore = customFieldDefinitionScopeScore(candidate)\n const currentScore = customFieldDefinitionScopeScore(current)\n if (candidateScore !== currentScore) return candidateScore > currentScore\n return customFieldDefinitionUpdatedAt(candidate) >= customFieldDefinitionUpdatedAt(current)\n}\n\nasync function loadImportCustomFieldDefinitions(\n em: EntityManager,\n scope: TenantScope,\n): Promise<Map<string, ImportCustomFieldDefinition>> {\n const definitions = await findWithDecryption(\n em,\n CustomFieldDef,\n {\n entityId: { $in: [E.customers.customer_entity, E.customers.customer_person_profile] as string[] },\n deletedAt: null,\n isActive: true,\n $and: [\n { $or: [{ tenantId: scope.tenantId }, { tenantId: null }] },\n { $or: [{ organizationId: scope.organizationId }, { organizationId: null }] },\n ],\n },\n undefined,\n scope,\n )\n\n const byKey = new Map<string, ImportCustomFieldDefinition>()\n for (const definition of definitions) {\n const candidate: ImportCustomFieldDefinition = {\n key: definition.key,\n kind: definition.kind,\n entityId: definition.entityId,\n organizationId: definition.organizationId ?? null,\n tenantId: definition.tenantId ?? null,\n updatedAt: definition.updatedAt ?? null,\n }\n if (preferCustomFieldDefinition(byKey.get(candidate.key), candidate)) {\n byKey.set(candidate.key, candidate)\n }\n }\n return byKey\n}\n\nfunction buildCommandContext(container: Container, scope: TenantScope): CommandRuntimeContext {\n return {\n container,\n auth: null,\n organizationScope: {\n selectedId: scope.organizationId,\n filterIds: [scope.organizationId],\n allowedIds: [scope.organizationId],\n tenantId: scope.tenantId,\n },\n selectedOrganizationId: scope.organizationId,\n organizationIds: [scope.organizationId],\n }\n}\n\nexport function createCursor(uploadId: string, offset: number): string {\n return JSON.stringify({\n uploadId,\n offset,\n })\n}\n\nexport function parseCursor(value: string | null | undefined): SyncExcelCursor | null {\n if (!value) return null\n try {\n const parsed = JSON.parse(value) as Partial<SyncExcelCursor> | null\n if (!parsed || typeof parsed !== 'object') return null\n if (typeof parsed.uploadId !== 'string' || parsed.uploadId.trim().length === 0) return null\n if (typeof parsed.offset !== 'number' || !Number.isFinite(parsed.offset) || parsed.offset < 0) return null\n return {\n uploadId: parsed.uploadId,\n offset: parsed.offset,\n }\n } catch {\n return null\n }\n}\n\nfunction mapRowValues(row: CsvPreviewRow, fields: FieldMapping[]): PersonRowValues {\n const values: PersonFieldValues = {}\n const customFields: Record<string, unknown> = {}\n const addressValues: AddressFieldValues = {}\n\n for (const field of fields) {\n if (field.mappingKind === 'ignore') continue\n const rawValue = row[field.externalField]\n if (rawValue === undefined || rawValue === null) continue\n if (field.mappingKind === 'custom_field' || field.localField.startsWith('cf:')) {\n const customFieldKey = field.localField.startsWith('cf:') ? field.localField.slice(3) : field.localField\n if (customFieldKey.trim().length > 0) {\n customFields[customFieldKey] = rawValue\n }\n continue\n }\n\n if (field.localField === 'person.externalId') {\n values.externalId = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.firstName') {\n values.firstName = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.lastName') {\n values.lastName = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.displayName') {\n values.displayName = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.primaryEmail') {\n values.primaryEmail = normalizeEmail(rawValue)\n continue\n }\n if (field.localField === 'person.primaryPhone') {\n values.primaryPhone = normalizeImportedPhone(rawValue, row)\n continue\n }\n if (field.localField === 'person.jobTitle') {\n values.jobTitle = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.status') {\n values.status = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.source') {\n values.source = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.description') {\n values.description = normalizeOptionalString(rawValue)\n continue\n }\n\n if (field.localField === 'address.name') {\n addressValues.name = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.purpose') {\n addressValues.purpose = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.companyName') {\n addressValues.companyName = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.addressLine1') {\n addressValues.addressLine1 = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.addressLine2') {\n addressValues.addressLine2 = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.buildingNumber') {\n addressValues.buildingNumber = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.flatNumber') {\n addressValues.flatNumber = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.city') {\n addressValues.city = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.region') {\n addressValues.region = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.postalCode') {\n addressValues.postalCode = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.country') {\n addressValues.country = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.latitude') {\n addressValues.latitude = normalizeOptionalNumber(rawValue)\n continue\n }\n if (field.localField === 'address.longitude') {\n addressValues.longitude = normalizeOptionalNumber(rawValue)\n }\n }\n\n return {\n values,\n customFields,\n addressValues,\n }\n}\n\nfunction derivePersonNames(values: PersonFieldValues): { firstName: string; lastName: string; displayName: string } | null {\n const firstName = values.firstName ?? null\n const lastName = values.lastName ?? null\n const explicitDisplayName = values.displayName ?? null\n\n if (firstName && lastName) {\n return {\n firstName,\n lastName,\n displayName: explicitDisplayName ?? `${firstName} ${lastName}`.trim(),\n }\n }\n\n if (explicitDisplayName) {\n const parts = explicitDisplayName.split(/\\s+/).filter((part) => part.length > 0)\n if (parts.length >= 2) {\n return {\n firstName: firstName ?? parts.slice(0, -1).join(' '),\n lastName: lastName ?? parts.at(-1) ?? explicitDisplayName,\n displayName: explicitDisplayName,\n }\n }\n return {\n firstName: firstName ?? explicitDisplayName,\n lastName: lastName ?? explicitDisplayName,\n displayName: explicitDisplayName,\n }\n }\n\n return null\n}\n\nexport function buildPersonPayload(row: CsvPreviewRow, mapping: DataMapping, scope: TenantScope): BuiltPersonPayload {\n const { values, customFields, addressValues } = mapRowValues(row, mapping.fields)\n const derivedNames = derivePersonNames(values)\n const sourceIdentifier = values.externalId ?? values.primaryEmail ?? values.displayName ?? null\n\n const updateInput: BuiltPersonPayload['updateInput'] = {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n }\n\n if (values.primaryEmail) updateInput.primaryEmail = values.primaryEmail\n if (values.primaryPhone) updateInput.primaryPhone = values.primaryPhone\n if (values.jobTitle) updateInput.jobTitle = values.jobTitle\n if (values.status) updateInput.status = values.status\n if (values.source) updateInput.source = values.source\n if (values.description) updateInput.description = values.description\n if (derivedNames?.firstName) updateInput.firstName = derivedNames.firstName\n if (derivedNames?.lastName) updateInput.lastName = derivedNames.lastName\n if (derivedNames?.displayName) updateInput.displayName = derivedNames.displayName\n\n if (!derivedNames) {\n return {\n values,\n customFields,\n addressValues,\n createInput: null,\n updateInput,\n sourceIdentifier,\n }\n }\n\n return {\n values,\n customFields,\n addressValues,\n createInput: {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n firstName: derivedNames.firstName,\n lastName: derivedNames.lastName,\n displayName: derivedNames.displayName,\n ...(values.primaryEmail ? { primaryEmail: values.primaryEmail } : {}),\n ...(values.primaryPhone ? { primaryPhone: values.primaryPhone } : {}),\n ...(values.jobTitle ? { jobTitle: values.jobTitle } : {}),\n ...(values.status ? { status: values.status } : {}),\n ...(values.source ? { source: values.source } : {}),\n ...(values.description ? { description: values.description } : {}),\n },\n updateInput,\n sourceIdentifier,\n }\n}\n\nfunction hasAddressValues(addressValues: AddressFieldValues): boolean {\n return Object.values(addressValues).some((value) => value !== null && value !== undefined)\n}\n\nfunction buildAddressCreateInput(addressValues: AddressFieldValues, entityId: string, scope: TenantScope) {\n if (!addressValues.addressLine1) return null\n\n return {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n entityId,\n addressLine1: addressValues.addressLine1,\n isPrimary: true,\n ...(addressValues.name ? { name: addressValues.name } : {}),\n ...(addressValues.purpose ? { purpose: addressValues.purpose } : {}),\n ...(addressValues.companyName ? { companyName: addressValues.companyName } : {}),\n ...(addressValues.addressLine2 ? { addressLine2: addressValues.addressLine2 } : {}),\n ...(addressValues.buildingNumber ? { buildingNumber: addressValues.buildingNumber } : {}),\n ...(addressValues.flatNumber ? { flatNumber: addressValues.flatNumber } : {}),\n ...(addressValues.city ? { city: addressValues.city } : {}),\n ...(addressValues.region ? { region: addressValues.region } : {}),\n ...(addressValues.postalCode ? { postalCode: addressValues.postalCode } : {}),\n ...(addressValues.country ? { country: addressValues.country } : {}),\n ...(addressValues.latitude !== null && addressValues.latitude !== undefined ? { latitude: addressValues.latitude } : {}),\n ...(addressValues.longitude !== null && addressValues.longitude !== undefined ? { longitude: addressValues.longitude } : {}),\n }\n}\n\nasync function upsertPrimaryAddress(params: {\n entityId: string\n addressValues: AddressFieldValues\n scope: TenantScope\n commandBus: CommandBus\n commandContext: CommandRuntimeContext\n em: EntityManager\n}): Promise<void> {\n if (!hasAddressValues(params.addressValues)) return\n if (!params.addressValues.addressLine1) return\n\n const [existingPrimaryAddress] = await findWithDecryption(\n params.em,\n CustomerAddress,\n {\n entity: params.entityId,\n organizationId: params.scope.organizationId,\n tenantId: params.scope.tenantId,\n isPrimary: true,\n },\n {\n orderBy: {\n createdAt: 'asc',\n },\n limit: 1,\n },\n params.scope,\n )\n\n if (existingPrimaryAddress) {\n await params.commandBus.execute('customers.addresses.update', {\n input: {\n id: existingPrimaryAddress.id,\n isPrimary: true,\n ...(params.addressValues.name ? { name: params.addressValues.name } : {}),\n ...(params.addressValues.purpose ? { purpose: params.addressValues.purpose } : {}),\n ...(params.addressValues.companyName ? { companyName: params.addressValues.companyName } : {}),\n ...(params.addressValues.addressLine1 ? { addressLine1: params.addressValues.addressLine1 } : {}),\n ...(params.addressValues.addressLine2 ? { addressLine2: params.addressValues.addressLine2 } : {}),\n ...(params.addressValues.buildingNumber ? { buildingNumber: params.addressValues.buildingNumber } : {}),\n ...(params.addressValues.flatNumber ? { flatNumber: params.addressValues.flatNumber } : {}),\n ...(params.addressValues.city ? { city: params.addressValues.city } : {}),\n ...(params.addressValues.region ? { region: params.addressValues.region } : {}),\n ...(params.addressValues.postalCode ? { postalCode: params.addressValues.postalCode } : {}),\n ...(params.addressValues.country ? { country: params.addressValues.country } : {}),\n ...(params.addressValues.latitude !== null && params.addressValues.latitude !== undefined\n ? { latitude: params.addressValues.latitude }\n : {}),\n ...(params.addressValues.longitude !== null && params.addressValues.longitude !== undefined\n ? { longitude: params.addressValues.longitude }\n : {}),\n },\n ctx: params.commandContext,\n })\n return\n }\n\n const createInput = buildAddressCreateInput(params.addressValues, params.entityId, params.scope)\n if (!createInput) return\n\n await params.commandBus.execute('customers.addresses.create', {\n input: createInput,\n ctx: params.commandContext,\n })\n}\n\nasync function loadStoredMapping(em: EntityManager, entityType: string, scope: TenantScope): Promise<DataMapping> {\n const stored = await findOneWithDecryption(\n em,\n SyncMapping,\n {\n integrationId: 'sync_excel',\n entityType,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n\n if (stored?.mapping && typeof stored.mapping === 'object') {\n return stored.mapping as unknown as DataMapping\n }\n\n return {\n entityType,\n fields: [],\n matchStrategy: 'custom',\n }\n}\n\nasync function resolveUpload(em: EntityManager, runId: string | undefined, cursor: SyncExcelCursor | null, scope: TenantScope): Promise<SyncExcelUpload | null> {\n if (runId) {\n const uploadByRun = await findOneWithDecryption(\n em,\n SyncExcelUpload,\n {\n syncRunId: runId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n if (uploadByRun) return uploadByRun\n }\n\n if (cursor?.uploadId) {\n return findOneWithDecryption(\n em,\n SyncExcelUpload,\n {\n id: cursor.uploadId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n }\n\n return null\n}\n\nasync function resolveExistingPersonId(params: {\n externalIdMappingService: ExternalIdMappingService\n externalId: string | null | undefined\n email: string | null | undefined\n emailDedupeIndex: EmailDedupeIndex\n scope: TenantScope\n}): Promise<string | null> {\n if (params.externalId) {\n const mappedLocalId = await params.externalIdMappingService.lookupLocalId(\n 'sync_excel',\n 'customers.person',\n params.externalId,\n params.scope,\n )\n if (mappedLocalId) return mappedLocalId\n }\n\n if (!params.email) return null\n return params.emailDedupeIndex.get(params.email) ?? null\n}\n\nasync function buildEmailDedupeIndex(params: {\n em: EntityManager\n rows: CsvPreviewRow[]\n mapping: DataMapping\n scope: TenantScope\n}): Promise<EmailDedupeIndex> {\n const emails = new Set<string>()\n for (const row of params.rows) {\n const rowValues = mapRowValues(row, params.mapping.fields)\n const email = rowValues.values.primaryEmail\n if (email) emails.add(email)\n }\n\n if (emails.size === 0) return new Map()\n\n const candidates = await findWithDecryption(\n params.em,\n CustomerEntity,\n {\n kind: 'person',\n organizationId: params.scope.organizationId,\n tenantId: params.scope.tenantId,\n deletedAt: null,\n isActive: true,\n },\n {\n orderBy: {\n createdAt: 'asc',\n },\n },\n params.scope,\n )\n\n const index: EmailDedupeIndex = new Map()\n for (const candidate of candidates) {\n const email = normalizeEmail(candidate.primaryEmail)\n if (!email || !emails.has(email) || index.has(email)) continue\n index.set(email, candidate.id)\n }\n return index\n}\n\nfunction isEmptyRow(row: CsvPreviewRow): boolean {\n return !Object.values(row).some((value) => normalizeOptionalString(value))\n}\n\nasync function processRow(params: {\n row: CsvPreviewRow\n rowNumber: number\n mapping: DataMapping\n scope: TenantScope\n commandBus: CommandBus\n commandContext: CommandRuntimeContext\n externalIdMappingService: ExternalIdMappingService\n emailDedupeIndex: EmailDedupeIndex\n customFieldDefinitions: Map<string, ImportCustomFieldDefinition>\n em: EntityManager\n}): Promise<ImportItem> {\n if (isEmptyRow(params.row)) {\n return {\n externalId: `row:${params.rowNumber}`,\n action: 'skip',\n data: {\n rowNumber: params.rowNumber,\n reason: 'empty_row',\n },\n }\n }\n\n const payload = buildPersonPayload(params.row, params.mapping, params.scope)\n const externalId = payload.values.externalId ?? null\n const sourceIdentifier = payload.sourceIdentifier ?? `row:${params.rowNumber}`\n const customFieldResult = coerceCustomFields(payload.customFields, params.customFieldDefinitions)\n if (!customFieldResult.ok) {\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'failed',\n data: {\n rowNumber: params.rowNumber,\n sourceIdentifier,\n errorMessage: customFieldResult.error,\n },\n }\n }\n const existingId = await resolveExistingPersonId({\n externalIdMappingService: params.externalIdMappingService,\n externalId,\n email: payload.values.primaryEmail,\n emailDedupeIndex: params.emailDedupeIndex,\n scope: params.scope,\n })\n\n if (!existingId && !payload.createInput) {\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'failed',\n data: {\n rowNumber: params.rowNumber,\n sourceIdentifier,\n errorMessage: 'Import row is missing a usable person name mapping.',\n },\n }\n }\n\n try {\n if (existingId) {\n const updateInput = {\n id: existingId,\n ...payload.updateInput,\n ...(Object.keys(customFieldResult.values).length > 0 ? { customFields: customFieldResult.values } : {}),\n }\n await params.commandBus.execute('customers.people.update', {\n input: updateInput,\n ctx: params.commandContext,\n })\n\n await upsertPrimaryAddress({\n entityId: existingId,\n addressValues: payload.addressValues,\n scope: params.scope,\n commandBus: params.commandBus,\n commandContext: params.commandContext,\n em: params.em,\n })\n\n if (externalId) {\n await params.externalIdMappingService.storeExternalIdMapping(\n 'sync_excel',\n 'customers.person',\n existingId,\n externalId,\n params.scope,\n )\n }\n if (payload.values.primaryEmail && !params.emailDedupeIndex.has(payload.values.primaryEmail)) {\n params.emailDedupeIndex.set(payload.values.primaryEmail, existingId)\n }\n\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'update',\n data: {\n localId: existingId,\n rowNumber: params.rowNumber,\n sourceIdentifier,\n },\n }\n }\n\n const commandResult = await params.commandBus.execute<\n NonNullable<BuiltPersonPayload['createInput']>,\n { entityId: string; personId: string }\n >('customers.people.create', {\n input: {\n ...payload.createInput!,\n ...(Object.keys(customFieldResult.values).length > 0 ? { customFields: customFieldResult.values } : {}),\n },\n ctx: params.commandContext,\n })\n\n await upsertPrimaryAddress({\n entityId: commandResult.result.entityId,\n addressValues: payload.addressValues,\n scope: params.scope,\n commandBus: params.commandBus,\n commandContext: params.commandContext,\n em: params.em,\n })\n\n if (externalId) {\n await params.externalIdMappingService.storeExternalIdMapping(\n 'sync_excel',\n 'customers.person',\n commandResult.result.entityId,\n externalId,\n params.scope,\n )\n }\n if (payload.values.primaryEmail && !params.emailDedupeIndex.has(payload.values.primaryEmail)) {\n params.emailDedupeIndex.set(payload.values.primaryEmail, commandResult.result.entityId)\n }\n\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'create',\n data: {\n localId: commandResult.result.entityId,\n personId: commandResult.result.personId,\n rowNumber: params.rowNumber,\n sourceIdentifier,\n },\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Import row failed'\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'failed',\n data: {\n rowNumber: params.rowNumber,\n sourceIdentifier,\n errorMessage,\n },\n }\n }\n}\n\nexport const syncExcelCustomersAdapter: DataSyncAdapter = {\n providerKey: 'excel',\n runMode: 'provider',\n operationalTelemetry: true,\n direction: 'import',\n supportedEntities: ['customers.person'],\n\n async getMapping(input): Promise<DataMapping> {\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n return loadStoredMapping(em, input.entityType, input.scope)\n },\n\n async *streamImport(input): AsyncIterable<ImportBatch> {\n if (input.entityType !== 'customers.person') {\n throw new Error(`Unsupported sync_excel entity type: ${input.entityType}`)\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandBus = container.resolve('commandBus') as CommandBus\n const externalIdMappingService = container.resolve('externalIdMappingService') as ExternalIdMappingService\n const cursor = parseCursor(input.cursor)\n const upload = await resolveUpload(em, input.runId, cursor, input.scope)\n\n if (!upload) {\n throw new Error('CSV upload session was not found for this sync run.')\n }\n\n if (input.runId) {\n upload.syncRunId = input.runId\n }\n upload.status = 'importing'\n await em.flush()\n\n try {\n const attachment = await findOneWithDecryption(\n em,\n Attachment,\n {\n id: upload.attachmentId,\n organizationId: input.scope.organizationId,\n tenantId: input.scope.tenantId,\n },\n undefined,\n input.scope,\n )\n\n if (!attachment) {\n throw new Error('CSV upload attachment could not be found.')\n }\n\n const fileBuffer = await readSyncExcelUploadBuffer(attachment)\n const document = parseCsvDocument(fileBuffer)\n const startOffset = cursor?.uploadId === upload.id ? cursor.offset : 0\n const commandContext = buildCommandContext(container, input.scope)\n const customFieldDefinitions = await loadImportCustomFieldDefinitions(em, input.scope)\n const emailDedupeIndex = await buildEmailDedupeIndex({\n em,\n rows: document.rows.slice(startOffset),\n mapping: input.mapping,\n scope: input.scope,\n })\n\n for (let offset = startOffset, batchIndex = 0; offset < document.rows.length; offset += input.batchSize, batchIndex += 1) {\n const batchRows = document.rows.slice(offset, offset + input.batchSize)\n const items: ImportItem[] = []\n\n for (let index = 0; index < batchRows.length; index += 1) {\n items.push(await processRow({\n row: batchRows[index],\n rowNumber: offset + index + 1,\n mapping: input.mapping,\n scope: input.scope,\n commandBus,\n commandContext,\n externalIdMappingService,\n emailDedupeIndex,\n customFieldDefinitions,\n em,\n }))\n }\n\n const nextOffset = offset + batchRows.length\n yield {\n items,\n cursor: createCursor(upload.id, nextOffset),\n hasMore: nextOffset < document.rows.length,\n totalEstimate: document.totalRows,\n processedCount: batchRows.length,\n refreshCoverageEntityTypes: CUSTOMER_IMPORT_COVERAGE_ENTITY_TYPES,\n batchIndex,\n message: `Processed ${nextOffset} of ${document.totalRows} CSV rows`,\n }\n }\n\n upload.status = 'completed'\n await em.flush()\n } catch (error) {\n upload.status = 'failed'\n await em.flush()\n throw error\n }\n },\n}\n"],
5
- "mappings": "AAEA,SAAS,8BAA8B;AACvC,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,oBAAoB,2BAA2B;AAUxD,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB,sBAAsB;AAChD,SAAS,kBAAkB;AAC3B,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAChC,SAAS,wBAA4C;AACrD,SAAS,iCAAiC;AAC1C,SAAS,SAAS;AASlB,MAAM,wCAAwC;AAAA,EAC5C,EAAE,UAAU;AAAA,EACZ,EAAE,UAAU;AAAA,EACZ,EAAE,UAAU;AACd;AAmFA,SAAS,wBAAwB,OAA+B;AAC9D,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;AAEA,SAAS,eAAe,OAA+B;AACrD,QAAM,aAAa,wBAAwB,KAAK;AAChD,SAAO,aAAa,WAAW,YAAY,IAAI;AACjD;AAEA,MAAM,qBAA6C;AAAA,EACjD,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,IAAI;AAAA,EACJ,eAAe;AAAA,EACf,cAAc;AAAA,EACd,uBAAuB;AAAA,EACvB,KAAK;AAAA,EACL,OAAO;AACT;AAEA,SAAS,oBAAoB,OAA+B;AAC1D,QAAM,aAAa,wBAAwB,KAAK;AAChD,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,WAAW,YAAY,EAAE,QAAQ,WAAW,EAAE;AACvD;AAEA,SAAS,uBAAuB,KAAmC;AACjE,QAAM,oBAAoB;AAAA,IACxB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI,gBAAgB;AAAA,IACpB,IAAI,gBAAgB;AAAA,EACtB;AAEA,aAAW,aAAa,mBAAmB;AACzC,UAAM,aAAa,oBAAoB,SAAS;AAChD,QAAI,CAAC,WAAY;AACjB,UAAM,WAAW,mBAAmB,UAAU;AAC9C,QAAI,SAAU,QAAO;AAAA,EACvB;AAEA,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAuB;AAClD,SAAO,MACJ,QAAQ,mCAAmC,EAAE,EAC7C,QAAQ,UAAU,GAAG,EACrB,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;AAEA,SAAS,uBAAuB,OAAgB,KAAmC;AACjF,QAAM,aAAa,wBAAwB,KAAK;AAChD,MAAI,CAAC,WAAY,QAAO;AAExB,QAAM,mBAAmB,oBAAoB,UAAU;AACvD,MAAI,iBAAiB,OAAO;AAC1B,WAAO,iBAAiB;AAAA,EAC1B;AAEA,QAAM,YAAY,oBAAoB,UAAU;AAChD,QAAM,sBAAsB,oBAAoB,SAAS;AACzD,MAAI,oBAAoB,OAAO;AAC7B,WAAO,oBAAoB;AAAA,EAC7B;AAEA,QAAM,SAAS,mBAAmB,SAAS;AAC3C,MAAI,CAAC,OAAQ,QAAO;AAEpB,MAAI,OAAO,UAAU,KAAK,OAAO,UAAU,MAAM,UAAU,WAAW,IAAI,GAAG;AAC3E,UAAM,yBAAyB,IAAI,OAAO,MAAM,CAAC,CAAC;AAClD,UAAM,0BAA0B,oBAAoB,sBAAsB;AAC1E,QAAI,wBAAwB,OAAO;AACjC,aAAO,wBAAwB;AAAA,IACjC;AAAA,EACF;AAEA,QAAM,WAAW,uBAAuB,GAAG;AAC3C,MAAI,CAAC,SAAU,QAAO;AAEtB,MAAI,iBAAiB;AACrB,MAAI,eAAe,WAAW,QAAQ,GAAG;AACvC,qBAAiB,eAAe,MAAM,SAAS,MAAM;AAAA,EACvD,OAAO;AACL,qBAAiB,eAAe,QAAQ,OAAO,EAAE;AAAA,EACnD;AAEA,MAAI,CAAC,eAAgB,QAAO;AAE5B,QAAM,qBAAqB,IAAI,QAAQ,GAAG,cAAc;AACxD,QAAM,sBAAsB,oBAAoB,kBAAkB;AAClE,SAAO,oBAAoB,QAAQ,oBAAoB,aAAa;AACtE;AAEA,SAAS,wBAAwB,OAA+B;AAC9D,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,SAAS,OAAO,OAAO;AAC7B,SAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;AAEA,SAAS,yBAAyB,OAA8B;AAC9D,QAAM,SAAS,IAAI,KAAK,KAAK;AAC7B,MAAI,OAAO,MAAM,OAAO,QAAQ,CAAC,EAAG,QAAO;AAC3C,SAAO,OAAO,YAAY,EAAE,MAAM,GAAG,EAAE;AACzC;AAEA,SAAS,4BAA4B,OAA+B;AAClE,QAAM,aAAa,MAAM,KAAK,EAAE,YAAY;AAC5C,MAAI,CAAC,QAAQ,KAAK,OAAO,KAAK,KAAK,KAAK,EAAE,SAAS,UAAU,EAAG,QAAO;AACvE,MAAI,CAAC,SAAS,KAAK,MAAM,KAAK,KAAK,KAAK,EAAE,SAAS,UAAU,EAAG,QAAO;AACvE,SAAO;AACT;AAEA,SAAS,uBACP,KACA,OACA,aAC6D;AAC7D,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,WAAW,GAAG;AAC1D,WAAO,EAAE,IAAI,MAAM,OAAO,KAAK;AAAA,EACjC;AAEA,QAAM,aAAa,YAAY,IAAI,GAAG;AACtC,MAAI,CAAC,YAAY;AACf,WAAO,EAAE,IAAI,MAAM,OAAO,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI,MAAM;AAAA,EAC7E;AAEA,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,WAAO,EAAE,IAAI,MAAM,OAAO,KAAK;AAAA,EACjC;AAEA,QAAM,OAAO,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI,OAAO,KAAK;AACpE,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO,EAAE,IAAI,MAAM,OAAO,KAAK;AAAA,EACjC;AAEA,QAAM,OAAO,WAAW,KAAK,YAAY;AACzC,MAAI,SAAS,WAAW;AACtB,UAAM,eAAe,4BAA4B,IAAI;AACrD,QAAI,iBAAiB,MAAM;AACzB,aAAO,EAAE,IAAI,OAAO,OAAO,iBAAiB,GAAG,6BAA6B;AAAA,IAC9E;AACA,WAAO,EAAE,IAAI,MAAM,OAAO,aAAa;AAAA,EACzC;AAEA,MAAI,SAAS,WAAW;AACtB,UAAM,cAAc,OAAO,IAAI;AAC/B,QAAI,CAAC,OAAO,UAAU,WAAW,GAAG;AAClC,aAAO,EAAE,IAAI,OAAO,OAAO,iBAAiB,GAAG,8BAA8B;AAAA,IAC/E;AACA,WAAO,EAAE,IAAI,MAAM,OAAO,YAAY;AAAA,EACxC;AAEA,MAAI,SAAS,WAAW,SAAS,cAAc,SAAS,UAAU;AAChE,UAAM,cAAc,OAAO,IAAI;AAC/B,QAAI,CAAC,OAAO,SAAS,WAAW,GAAG;AACjC,aAAO,EAAE,IAAI,OAAO,OAAO,iBAAiB,GAAG,4BAA4B;AAAA,IAC7E;AACA,WAAO,EAAE,IAAI,MAAM,OAAO,YAAY;AAAA,EACxC;AAEA,MAAI,SAAS,UAAU,SAAS,YAAY;AAC1C,UAAM,YAAY,yBAAyB,IAAI;AAC/C,QAAI,CAAC,WAAW;AACd,aAAO,EAAE,IAAI,OAAO,OAAO,iBAAiB,GAAG,0BAA0B;AAAA,IAC3E;AACA,WAAO,EAAE,IAAI,MAAM,OAAO,UAAU;AAAA,EACtC;AAEA,SAAO,EAAE,IAAI,MAAM,OAAO,KAAK;AACjC;AAEA,SAAS,mBACP,QACA,aAC8E;AAC9E,QAAM,UAAmC,CAAC;AAC1C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAM,SAAS,uBAAuB,KAAK,OAAO,WAAW;AAC7D,QAAI,CAAC,OAAO,GAAI,QAAO;AACvB,YAAQ,GAAG,IAAI,OAAO;AAAA,EACxB;AACA,SAAO,EAAE,IAAI,MAAM,QAAQ,QAAQ;AACrC;AAEA,SAAS,gCAAgC,YAAiD;AACxF,UAAQ,WAAW,WAAW,IAAI,MAAM,WAAW,iBAAiB,IAAI;AAC1E;AAEA,SAAS,+BAA+B,YAAiD;AACvF,MAAI,CAAC,WAAW,UAAW,QAAO;AAClC,QAAM,QAAQ,WAAW,qBAAqB,OAC1C,WAAW,UAAU,QAAQ,IAC7B,IAAI,KAAK,WAAW,SAAS,EAAE,QAAQ;AAC3C,SAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAC1C;AAEA,SAAS,4BACP,SACA,WACS;AACT,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,iBAAiB,gCAAgC,SAAS;AAChE,QAAM,eAAe,gCAAgC,OAAO;AAC5D,MAAI,mBAAmB,aAAc,QAAO,iBAAiB;AAC7D,SAAO,+BAA+B,SAAS,KAAK,+BAA+B,OAAO;AAC5F;AAEA,eAAe,iCACb,IACA,OACmD;AACnD,QAAM,cAAc,MAAM;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,MACE,UAAU,EAAE,KAAK,CAAC,EAAE,UAAU,iBAAiB,EAAE,UAAU,uBAAuB,EAAc;AAAA,MAChG,WAAW;AAAA,MACX,UAAU;AAAA,MACV,MAAM;AAAA,QACJ,EAAE,KAAK,CAAC,EAAE,UAAU,MAAM,SAAS,GAAG,EAAE,UAAU,KAAK,CAAC,EAAE;AAAA,QAC1D,EAAE,KAAK,CAAC,EAAE,gBAAgB,MAAM,eAAe,GAAG,EAAE,gBAAgB,KAAK,CAAC,EAAE;AAAA,MAC9E;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,QAAQ,oBAAI,IAAyC;AAC3D,aAAW,cAAc,aAAa;AACpC,UAAM,YAAyC;AAAA,MAC7C,KAAK,WAAW;AAAA,MAChB,MAAM,WAAW;AAAA,MACjB,UAAU,WAAW;AAAA,MACrB,gBAAgB,WAAW,kBAAkB;AAAA,MAC7C,UAAU,WAAW,YAAY;AAAA,MACjC,WAAW,WAAW,aAAa;AAAA,IACrC;AACA,QAAI,4BAA4B,MAAM,IAAI,UAAU,GAAG,GAAG,SAAS,GAAG;AACpE,YAAM,IAAI,UAAU,KAAK,SAAS;AAAA,IACpC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,WAAsB,OAA2C;AAC5F,SAAO;AAAA,IACL;AAAA,IACA,MAAM;AAAA,IACN,mBAAmB;AAAA,MACjB,YAAY,MAAM;AAAA,MAClB,WAAW,CAAC,MAAM,cAAc;AAAA,MAChC,YAAY,CAAC,MAAM,cAAc;AAAA,MACjC,UAAU,MAAM;AAAA,IAClB;AAAA,IACA,wBAAwB,MAAM;AAAA,IAC9B,iBAAiB,CAAC,MAAM,cAAc;AAAA,EACxC;AACF;AAEO,SAAS,aAAa,UAAkB,QAAwB;AACrE,SAAO,KAAK,UAAU;AAAA,IACpB;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,YAAY,OAA0D;AACpF,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,QAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAI,OAAO,OAAO,aAAa,YAAY,OAAO,SAAS,KAAK,EAAE,WAAW,EAAG,QAAO;AACvF,QAAI,OAAO,OAAO,WAAW,YAAY,CAAC,OAAO,SAAS,OAAO,MAAM,KAAK,OAAO,SAAS,EAAG,QAAO;AACtG,WAAO;AAAA,MACL,UAAU,OAAO;AAAA,MACjB,QAAQ,OAAO;AAAA,IACjB;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,aAAa,KAAoB,QAAyC;AACjF,QAAM,SAA4B,CAAC;AACnC,QAAM,eAAwC,CAAC;AAC/C,QAAM,gBAAoC,CAAC;AAE3C,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,gBAAgB,SAAU;AACpC,UAAM,WAAW,IAAI,MAAM,aAAa;AACxC,QAAI,aAAa,UAAa,aAAa,KAAM;AACjD,QAAI,MAAM,gBAAgB,kBAAkB,MAAM,WAAW,WAAW,KAAK,GAAG;AAC9E,YAAM,iBAAiB,MAAM,WAAW,WAAW,KAAK,IAAI,MAAM,WAAW,MAAM,CAAC,IAAI,MAAM;AAC9F,UAAI,eAAe,KAAK,EAAE,SAAS,GAAG;AACpC,qBAAa,cAAc,IAAI;AAAA,MACjC;AACA;AAAA,IACF;AAEA,QAAI,MAAM,eAAe,qBAAqB;AAC5C,aAAO,aAAa,wBAAwB,QAAQ;AACpD;AAAA,IACF;AACA,QAAI,MAAM,eAAe,oBAAoB;AAC3C,aAAO,YAAY,wBAAwB,QAAQ;AACnD;AAAA,IACF;AACA,QAAI,MAAM,eAAe,mBAAmB;AAC1C,aAAO,WAAW,wBAAwB,QAAQ;AAClD;AAAA,IACF;AACA,QAAI,MAAM,eAAe,sBAAsB;AAC7C,aAAO,cAAc,wBAAwB,QAAQ;AACrD;AAAA,IACF;AACA,QAAI,MAAM,eAAe,uBAAuB;AAC9C,aAAO,eAAe,eAAe,QAAQ;AAC7C;AAAA,IACF;AACA,QAAI,MAAM,eAAe,uBAAuB;AAC9C,aAAO,eAAe,uBAAuB,UAAU,GAAG;AAC1D;AAAA,IACF;AACA,QAAI,MAAM,eAAe,mBAAmB;AAC1C,aAAO,WAAW,wBAAwB,QAAQ;AAClD;AAAA,IACF;AACA,QAAI,MAAM,eAAe,iBAAiB;AACxC,aAAO,SAAS,wBAAwB,QAAQ;AAChD;AAAA,IACF;AACA,QAAI,MAAM,eAAe,iBAAiB;AACxC,aAAO,SAAS,wBAAwB,QAAQ;AAChD;AAAA,IACF;AACA,QAAI,MAAM,eAAe,sBAAsB;AAC7C,aAAO,cAAc,wBAAwB,QAAQ;AACrD;AAAA,IACF;AAEA,QAAI,MAAM,eAAe,gBAAgB;AACvC,oBAAc,OAAO,wBAAwB,QAAQ;AACrD;AAAA,IACF;AACA,QAAI,MAAM,eAAe,mBAAmB;AAC1C,oBAAc,UAAU,wBAAwB,QAAQ;AACxD;AAAA,IACF;AACA,QAAI,MAAM,eAAe,uBAAuB;AAC9C,oBAAc,cAAc,wBAAwB,QAAQ;AAC5D;AAAA,IACF;AACA,QAAI,MAAM,eAAe,wBAAwB;AAC/C,oBAAc,eAAe,wBAAwB,QAAQ;AAC7D;AAAA,IACF;AACA,QAAI,MAAM,eAAe,wBAAwB;AAC/C,oBAAc,eAAe,wBAAwB,QAAQ;AAC7D;AAAA,IACF;AACA,QAAI,MAAM,eAAe,0BAA0B;AACjD,oBAAc,iBAAiB,wBAAwB,QAAQ;AAC/D;AAAA,IACF;AACA,QAAI,MAAM,eAAe,sBAAsB;AAC7C,oBAAc,aAAa,wBAAwB,QAAQ;AAC3D;AAAA,IACF;AACA,QAAI,MAAM,eAAe,gBAAgB;AACvC,oBAAc,OAAO,wBAAwB,QAAQ;AACrD;AAAA,IACF;AACA,QAAI,MAAM,eAAe,kBAAkB;AACzC,oBAAc,SAAS,wBAAwB,QAAQ;AACvD;AAAA,IACF;AACA,QAAI,MAAM,eAAe,sBAAsB;AAC7C,oBAAc,aAAa,wBAAwB,QAAQ;AAC3D;AAAA,IACF;AACA,QAAI,MAAM,eAAe,mBAAmB;AAC1C,oBAAc,UAAU,wBAAwB,QAAQ;AACxD;AAAA,IACF;AACA,QAAI,MAAM,eAAe,oBAAoB;AAC3C,oBAAc,WAAW,wBAAwB,QAAQ;AACzD;AAAA,IACF;AACA,QAAI,MAAM,eAAe,qBAAqB;AAC5C,oBAAc,YAAY,wBAAwB,QAAQ;AAAA,IAC5D;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,kBAAkB,QAAgG;AACzH,QAAM,YAAY,OAAO,aAAa;AACtC,QAAM,WAAW,OAAO,YAAY;AACpC,QAAM,sBAAsB,OAAO,eAAe;AAElD,MAAI,aAAa,UAAU;AACzB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,aAAa,uBAAuB,GAAG,SAAS,IAAI,QAAQ,GAAG,KAAK;AAAA,IACtE;AAAA,EACF;AAEA,MAAI,qBAAqB;AACvB,UAAM,QAAQ,oBAAoB,MAAM,KAAK,EAAE,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;AAC/E,QAAI,MAAM,UAAU,GAAG;AACrB,aAAO;AAAA,QACL,WAAW,aAAa,MAAM,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG;AAAA,QACnD,UAAU,YAAY,MAAM,GAAG,EAAE,KAAK;AAAA,QACtC,aAAa;AAAA,MACf;AAAA,IACF;AACA,WAAO;AAAA,MACL,WAAW,aAAa;AAAA,MACxB,UAAU,YAAY;AAAA,MACtB,aAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,mBAAmB,KAAoB,SAAsB,OAAwC;AACnH,QAAM,EAAE,QAAQ,cAAc,cAAc,IAAI,aAAa,KAAK,QAAQ,MAAM;AAChF,QAAM,eAAe,kBAAkB,MAAM;AAC7C,QAAM,mBAAmB,OAAO,cAAc,OAAO,gBAAgB,OAAO,eAAe;AAE3F,QAAM,cAAiD;AAAA,IACrD,gBAAgB,MAAM;AAAA,IACtB,UAAU,MAAM;AAAA,EAClB;AAEA,MAAI,OAAO,aAAc,aAAY,eAAe,OAAO;AAC3D,MAAI,OAAO,aAAc,aAAY,eAAe,OAAO;AAC3D,MAAI,OAAO,SAAU,aAAY,WAAW,OAAO;AACnD,MAAI,OAAO,OAAQ,aAAY,SAAS,OAAO;AAC/C,MAAI,OAAO,OAAQ,aAAY,SAAS,OAAO;AAC/C,MAAI,OAAO,YAAa,aAAY,cAAc,OAAO;AACzD,MAAI,cAAc,UAAW,aAAY,YAAY,aAAa;AAClE,MAAI,cAAc,SAAU,aAAY,WAAW,aAAa;AAChE,MAAI,cAAc,YAAa,aAAY,cAAc,aAAa;AAEtE,MAAI,CAAC,cAAc;AACjB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,MACX,gBAAgB,MAAM;AAAA,MACtB,UAAU,MAAM;AAAA,MAChB,WAAW,aAAa;AAAA,MACxB,UAAU,aAAa;AAAA,MACvB,aAAa,aAAa;AAAA,MAC1B,GAAI,OAAO,eAAe,EAAE,cAAc,OAAO,aAAa,IAAI,CAAC;AAAA,MACnE,GAAI,OAAO,eAAe,EAAE,cAAc,OAAO,aAAa,IAAI,CAAC;AAAA,MACnE,GAAI,OAAO,WAAW,EAAE,UAAU,OAAO,SAAS,IAAI,CAAC;AAAA,MACvD,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;AAAA,MACjD,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;AAAA,MACjD,GAAI,OAAO,cAAc,EAAE,aAAa,OAAO,YAAY,IAAI,CAAC;AAAA,IAClE;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,eAA4C;AACpE,SAAO,OAAO,OAAO,aAAa,EAAE,KAAK,CAAC,UAAU,UAAU,QAAQ,UAAU,MAAS;AAC3F;AAEA,SAAS,wBAAwB,eAAmC,UAAkB,OAAoB;AACxG,MAAI,CAAC,cAAc,aAAc,QAAO;AAExC,SAAO;AAAA,IACL,gBAAgB,MAAM;AAAA,IACtB,UAAU,MAAM;AAAA,IAChB;AAAA,IACA,cAAc,cAAc;AAAA,IAC5B,WAAW;AAAA,IACX,GAAI,cAAc,OAAO,EAAE,MAAM,cAAc,KAAK,IAAI,CAAC;AAAA,IACzD,GAAI,cAAc,UAAU,EAAE,SAAS,cAAc,QAAQ,IAAI,CAAC;AAAA,IAClE,GAAI,cAAc,cAAc,EAAE,aAAa,cAAc,YAAY,IAAI,CAAC;AAAA,IAC9E,GAAI,cAAc,eAAe,EAAE,cAAc,cAAc,aAAa,IAAI,CAAC;AAAA,IACjF,GAAI,cAAc,iBAAiB,EAAE,gBAAgB,cAAc,eAAe,IAAI,CAAC;AAAA,IACvF,GAAI,cAAc,aAAa,EAAE,YAAY,cAAc,WAAW,IAAI,CAAC;AAAA,IAC3E,GAAI,cAAc,OAAO,EAAE,MAAM,cAAc,KAAK,IAAI,CAAC;AAAA,IACzD,GAAI,cAAc,SAAS,EAAE,QAAQ,cAAc,OAAO,IAAI,CAAC;AAAA,IAC/D,GAAI,cAAc,aAAa,EAAE,YAAY,cAAc,WAAW,IAAI,CAAC;AAAA,IAC3E,GAAI,cAAc,UAAU,EAAE,SAAS,cAAc,QAAQ,IAAI,CAAC;AAAA,IAClE,GAAI,cAAc,aAAa,QAAQ,cAAc,aAAa,SAAY,EAAE,UAAU,cAAc,SAAS,IAAI,CAAC;AAAA,IACtH,GAAI,cAAc,cAAc,QAAQ,cAAc,cAAc,SAAY,EAAE,WAAW,cAAc,UAAU,IAAI,CAAC;AAAA,EAC5H;AACF;AAEA,eAAe,qBAAqB,QAOlB;AAChB,MAAI,CAAC,iBAAiB,OAAO,aAAa,EAAG;AAC7C,MAAI,CAAC,OAAO,cAAc,aAAc;AAExC,QAAM,CAAC,sBAAsB,IAAI,MAAM;AAAA,IACrC,OAAO;AAAA,IACP;AAAA,IACA;AAAA,MACE,QAAQ,OAAO;AAAA,MACf,gBAAgB,OAAO,MAAM;AAAA,MAC7B,UAAU,OAAO,MAAM;AAAA,MACvB,WAAW;AAAA,IACb;AAAA,IACA;AAAA,MACE,SAAS;AAAA,QACP,WAAW;AAAA,MACb;AAAA,MACA,OAAO;AAAA,IACT;AAAA,IACA,OAAO;AAAA,EACT;AAEA,MAAI,wBAAwB;AAC1B,UAAM,OAAO,WAAW,QAAQ,8BAA8B;AAAA,MAC5D,OAAO;AAAA,QACL,IAAI,uBAAuB;AAAA,QAC3B,WAAW;AAAA,QACX,GAAI,OAAO,cAAc,OAAO,EAAE,MAAM,OAAO,cAAc,KAAK,IAAI,CAAC;AAAA,QACvE,GAAI,OAAO,cAAc,UAAU,EAAE,SAAS,OAAO,cAAc,QAAQ,IAAI,CAAC;AAAA,QAChF,GAAI,OAAO,cAAc,cAAc,EAAE,aAAa,OAAO,cAAc,YAAY,IAAI,CAAC;AAAA,QAC5F,GAAI,OAAO,cAAc,eAAe,EAAE,cAAc,OAAO,cAAc,aAAa,IAAI,CAAC;AAAA,QAC/F,GAAI,OAAO,cAAc,eAAe,EAAE,cAAc,OAAO,cAAc,aAAa,IAAI,CAAC;AAAA,QAC/F,GAAI,OAAO,cAAc,iBAAiB,EAAE,gBAAgB,OAAO,cAAc,eAAe,IAAI,CAAC;AAAA,QACrG,GAAI,OAAO,cAAc,aAAa,EAAE,YAAY,OAAO,cAAc,WAAW,IAAI,CAAC;AAAA,QACzF,GAAI,OAAO,cAAc,OAAO,EAAE,MAAM,OAAO,cAAc,KAAK,IAAI,CAAC;AAAA,QACvE,GAAI,OAAO,cAAc,SAAS,EAAE,QAAQ,OAAO,cAAc,OAAO,IAAI,CAAC;AAAA,QAC7E,GAAI,OAAO,cAAc,aAAa,EAAE,YAAY,OAAO,cAAc,WAAW,IAAI,CAAC;AAAA,QACzF,GAAI,OAAO,cAAc,UAAU,EAAE,SAAS,OAAO,cAAc,QAAQ,IAAI,CAAC;AAAA,QAChF,GAAI,OAAO,cAAc,aAAa,QAAQ,OAAO,cAAc,aAAa,SAC5E,EAAE,UAAU,OAAO,cAAc,SAAS,IAC1C,CAAC;AAAA,QACL,GAAI,OAAO,cAAc,cAAc,QAAQ,OAAO,cAAc,cAAc,SAC9E,EAAE,WAAW,OAAO,cAAc,UAAU,IAC5C,CAAC;AAAA,MACP;AAAA,MACA,KAAK,OAAO;AAAA,IACd,CAAC;AACD;AAAA,EACF;AAEA,QAAM,cAAc,wBAAwB,OAAO,eAAe,OAAO,UAAU,OAAO,KAAK;AAC/F,MAAI,CAAC,YAAa;AAElB,QAAM,OAAO,WAAW,QAAQ,8BAA8B;AAAA,IAC5D,OAAO;AAAA,IACP,KAAK,OAAO;AAAA,EACd,CAAC;AACH;AAEA,eAAe,kBAAkB,IAAmB,YAAoB,OAA0C;AAChH,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,MACE,eAAe;AAAA,MACf;AAAA,MACA,gBAAgB,MAAM;AAAA,MACtB,UAAU,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,OAAO,OAAO,YAAY,UAAU;AACzD,WAAO,OAAO;AAAA,EAChB;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,CAAC;AAAA,IACT,eAAe;AAAA,EACjB;AACF;AAEA,eAAe,cAAc,IAAmB,OAA2B,QAAgC,OAAqD;AAC9J,MAAI,OAAO;AACT,UAAM,cAAc,MAAM;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,QACE,WAAW;AAAA,QACX,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,YAAa,QAAO;AAAA,EAC1B;AAEA,MAAI,QAAQ,UAAU;AACpB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,QACE,IAAI,OAAO;AAAA,QACX,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,wBAAwB,QAMZ;AACzB,MAAI,OAAO,YAAY;AACrB,UAAM,gBAAgB,MAAM,OAAO,yBAAyB;AAAA,MAC1D;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AACA,QAAI,cAAe,QAAO;AAAA,EAC5B;AAEA,MAAI,CAAC,OAAO,MAAO,QAAO;AAC1B,SAAO,OAAO,iBAAiB,IAAI,OAAO,KAAK,KAAK;AACtD;AAEA,eAAe,sBAAsB,QAKP;AAC5B,QAAM,SAAS,oBAAI,IAAY;AAC/B,aAAW,OAAO,OAAO,MAAM;AAC7B,UAAM,YAAY,aAAa,KAAK,OAAO,QAAQ,MAAM;AACzD,UAAM,QAAQ,UAAU,OAAO;AAC/B,QAAI,MAAO,QAAO,IAAI,KAAK;AAAA,EAC7B;AAEA,MAAI,OAAO,SAAS,EAAG,QAAO,oBAAI,IAAI;AAEtC,QAAM,aAAa,MAAM;AAAA,IACvB,OAAO;AAAA,IACP;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,gBAAgB,OAAO,MAAM;AAAA,MAC7B,UAAU,OAAO,MAAM;AAAA,MACvB,WAAW;AAAA,MACX,UAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,SAAS;AAAA,QACP,WAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA,OAAO;AAAA,EACT;AAEA,QAAM,QAA0B,oBAAI,IAAI;AACxC,aAAW,aAAa,YAAY;AAClC,UAAM,QAAQ,eAAe,UAAU,YAAY;AACnD,QAAI,CAAC,SAAS,CAAC,OAAO,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,EAAG;AACtD,UAAM,IAAI,OAAO,UAAU,EAAE;AAAA,EAC/B;AACA,SAAO;AACT;AAEA,SAAS,WAAW,KAA6B;AAC/C,SAAO,CAAC,OAAO,OAAO,GAAG,EAAE,KAAK,CAAC,UAAU,wBAAwB,KAAK,CAAC;AAC3E;AAEA,eAAe,WAAW,QAWF;AACtB,MAAI,WAAW,OAAO,GAAG,GAAG;AAC1B,WAAO;AAAA,MACL,YAAY,OAAO,OAAO,SAAS;AAAA,MACnC,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,WAAW,OAAO;AAAA,QAClB,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,mBAAmB,OAAO,KAAK,OAAO,SAAS,OAAO,KAAK;AAC3E,QAAM,aAAa,QAAQ,OAAO,cAAc;AAChD,QAAM,mBAAmB,QAAQ,oBAAoB,OAAO,OAAO,SAAS;AAC5E,QAAM,oBAAoB,mBAAmB,QAAQ,cAAc,OAAO,sBAAsB;AAChG,MAAI,CAAC,kBAAkB,IAAI;AACzB,WAAO;AAAA,MACL,YAAY,cAAc;AAAA,MAC1B,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,WAAW,OAAO;AAAA,QAClB;AAAA,QACA,cAAc,kBAAkB;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AACA,QAAM,aAAa,MAAM,wBAAwB;AAAA,IAC/C,0BAA0B,OAAO;AAAA,IACjC;AAAA,IACA,OAAO,QAAQ,OAAO;AAAA,IACtB,kBAAkB,OAAO;AAAA,IACzB,OAAO,OAAO;AAAA,EAChB,CAAC;AAED,MAAI,CAAC,cAAc,CAAC,QAAQ,aAAa;AACvC,WAAO;AAAA,MACL,YAAY,cAAc;AAAA,MAC1B,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,WAAW,OAAO;AAAA,QAClB;AAAA,QACA,cAAc;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,QAAI,YAAY;AACd,YAAM,cAAc;AAAA,QAClB,IAAI;AAAA,QACJ,GAAG,QAAQ;AAAA,QACX,GAAI,OAAO,KAAK,kBAAkB,MAAM,EAAE,SAAS,IAAI,EAAE,cAAc,kBAAkB,OAAO,IAAI,CAAC;AAAA,MACvG;AACA,YAAM,OAAO,WAAW,QAAQ,2BAA2B;AAAA,QACzD,OAAO;AAAA,QACP,KAAK,OAAO;AAAA,MACd,CAAC;AAED,YAAM,qBAAqB;AAAA,QACzB,UAAU;AAAA,QACV,eAAe,QAAQ;AAAA,QACvB,OAAO,OAAO;AAAA,QACd,YAAY,OAAO;AAAA,QACnB,gBAAgB,OAAO;AAAA,QACvB,IAAI,OAAO;AAAA,MACb,CAAC;AAED,UAAI,YAAY;AACd,cAAM,OAAO,yBAAyB;AAAA,UACpC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,OAAO;AAAA,QACT;AAAA,MACF;AACA,UAAI,QAAQ,OAAO,gBAAgB,CAAC,OAAO,iBAAiB,IAAI,QAAQ,OAAO,YAAY,GAAG;AAC5F,eAAO,iBAAiB,IAAI,QAAQ,OAAO,cAAc,UAAU;AAAA,MACrE;AAEA,aAAO;AAAA,QACL,YAAY,cAAc;AAAA,QAC1B,QAAQ;AAAA,QACR,MAAM;AAAA,UACJ,SAAS;AAAA,UACT,WAAW,OAAO;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,gBAAgB,MAAM,OAAO,WAAW,QAG5C,2BAA2B;AAAA,MAC3B,OAAO;AAAA,QACL,GAAG,QAAQ;AAAA,QACX,GAAI,OAAO,KAAK,kBAAkB,MAAM,EAAE,SAAS,IAAI,EAAE,cAAc,kBAAkB,OAAO,IAAI,CAAC;AAAA,MACvG;AAAA,MACA,KAAK,OAAO;AAAA,IACd,CAAC;AAED,UAAM,qBAAqB;AAAA,MACzB,UAAU,cAAc,OAAO;AAAA,MAC/B,eAAe,QAAQ;AAAA,MACvB,OAAO,OAAO;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,gBAAgB,OAAO;AAAA,MACvB,IAAI,OAAO;AAAA,IACb,CAAC;AAED,QAAI,YAAY;AACd,YAAM,OAAO,yBAAyB;AAAA,QACpC;AAAA,QACA;AAAA,QACA,cAAc,OAAO;AAAA,QACrB;AAAA,QACA,OAAO;AAAA,MACT;AAAA,IACF;AACA,QAAI,QAAQ,OAAO,gBAAgB,CAAC,OAAO,iBAAiB,IAAI,QAAQ,OAAO,YAAY,GAAG;AAC5F,aAAO,iBAAiB,IAAI,QAAQ,OAAO,cAAc,cAAc,OAAO,QAAQ;AAAA,IACxF;AAEA,WAAO;AAAA,MACL,YAAY,cAAc;AAAA,MAC1B,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,SAAS,cAAc,OAAO;AAAA,QAC9B,UAAU,cAAc,OAAO;AAAA,QAC/B,WAAW,OAAO;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU;AAC9D,WAAO;AAAA,MACL,YAAY,cAAc;AAAA,MAC1B,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,WAAW,OAAO;AAAA,QAClB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,MAAM,4BAA6C;AAAA,EACxD,aAAa;AAAA,EACb,SAAS;AAAA,EACT,sBAAsB;AAAA,EACtB,WAAW;AAAA,EACX,mBAAmB,CAAC,kBAAkB;AAAA,EAEtC,MAAM,WAAW,OAA6B;AAC5C,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,WAAO,kBAAkB,IAAI,MAAM,YAAY,MAAM,KAAK;AAAA,EAC5D;AAAA,EAEA,OAAO,aAAa,OAAmC;AACrD,QAAI,MAAM,eAAe,oBAAoB;AAC3C,YAAM,IAAI,MAAM,uCAAuC,MAAM,UAAU,EAAE;AAAA,IAC3E;AAEA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,UAAM,aAAa,UAAU,QAAQ,YAAY;AACjD,UAAM,2BAA2B,UAAU,QAAQ,0BAA0B;AAC7E,UAAM,SAAS,YAAY,MAAM,MAAM;AACvC,UAAM,SAAS,MAAM,cAAc,IAAI,MAAM,OAAO,QAAQ,MAAM,KAAK;AAEvE,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,qDAAqD;AAAA,IACvE;AAEA,QAAI,MAAM,OAAO;AACf,aAAO,YAAY,MAAM;AAAA,IAC3B;AACA,WAAO,SAAS;AAChB,UAAM,GAAG,MAAM;AAEf,QAAI;AACF,YAAM,aAAa,MAAM;AAAA,QACvB;AAAA,QACA;AAAA,QACA;AAAA,UACE,IAAI,OAAO;AAAA,UACX,gBAAgB,MAAM,MAAM;AAAA,UAC5B,UAAU,MAAM,MAAM;AAAA,QACxB;AAAA,QACA;AAAA,QACA,MAAM;AAAA,MACR;AAEA,UAAI,CAAC,YAAY;AACf,cAAM,IAAI,MAAM,2CAA2C;AAAA,MAC7D;AAEA,YAAM,aAAa,MAAM,0BAA0B,UAAU;AAC7D,YAAM,WAAW,iBAAiB,UAAU;AAC5C,YAAM,cAAc,QAAQ,aAAa,OAAO,KAAK,OAAO,SAAS;AACrE,YAAM,iBAAiB,oBAAoB,WAAW,MAAM,KAAK;AACjE,YAAM,yBAAyB,MAAM,iCAAiC,IAAI,MAAM,KAAK;AACrF,YAAM,mBAAmB,MAAM,sBAAsB;AAAA,QACnD;AAAA,QACA,MAAM,SAAS,KAAK,MAAM,WAAW;AAAA,QACrC,SAAS,MAAM;AAAA,QACf,OAAO,MAAM;AAAA,MACf,CAAC;AAED,eAAS,SAAS,aAAa,aAAa,GAAG,SAAS,SAAS,KAAK,QAAQ,UAAU,MAAM,WAAW,cAAc,GAAG;AACxH,cAAM,YAAY,SAAS,KAAK,MAAM,QAAQ,SAAS,MAAM,SAAS;AACtE,cAAM,QAAsB,CAAC;AAE7B,iBAAS,QAAQ,GAAG,QAAQ,UAAU,QAAQ,SAAS,GAAG;AACxD,gBAAM,KAAK,MAAM,WAAW;AAAA,YAC1B,KAAK,UAAU,KAAK;AAAA,YACpB,WAAW,SAAS,QAAQ;AAAA,YAC5B,SAAS,MAAM;AAAA,YACf,OAAO,MAAM;AAAA,YACb;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC,CAAC;AAAA,QACJ;AAEA,cAAM,aAAa,SAAS,UAAU;AACtC,cAAM;AAAA,UACJ;AAAA,UACA,QAAQ,aAAa,OAAO,IAAI,UAAU;AAAA,UAC1C,SAAS,aAAa,SAAS,KAAK;AAAA,UACpC,eAAe,SAAS;AAAA,UACxB,gBAAgB,UAAU;AAAA,UAC1B,4BAA4B;AAAA,UAC5B;AAAA,UACA,SAAS,aAAa,UAAU,OAAO,SAAS,SAAS;AAAA,QAC3D;AAAA,MACF;AAEA,aAAO,SAAS;AAChB,YAAM,GAAG,MAAM;AAAA,IACjB,SAAS,OAAO;AACd,aAAO,SAAS;AAChB,YAAM,GAAG,MAAM;AACf,YAAM;AAAA,IACR;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { extractPhoneDigits, validatePhoneNumber } from '@open-mercato/shared/lib/phone'\nimport type {\n DataMapping,\n DataSyncAdapter,\n FieldMapping,\n ImportBatch,\n ImportItem,\n TenantScope,\n} from '../../../data_sync/lib/adapter'\nimport type { ExternalIdMappingService } from '../../../data_sync/lib/id-mapping'\nimport { SyncMapping } from '../../../data_sync/data/entities'\nimport { CustomerAddress, CustomerEntity } from '../../../customers/data/entities'\nimport { Attachment } from '../../../attachments/data/entities'\nimport { CustomFieldDef } from '../../../entities/data/entities'\nimport { SyncExcelUpload } from '../../data/entities'\nimport { parseCsvDocumentBatches, parseCsvStreamMetadata, type CsvPreviewRow } from '../parser'\nimport { createSyncExcelUploadReadStream } from '../upload-storage'\nimport { E } from '#generated/entities.ids.generated'\n\ntype SyncExcelCursor = {\n uploadId: string\n offset: number\n}\n\ntype Container = Awaited<ReturnType<typeof createRequestContainer>>\n\nconst CUSTOMER_IMPORT_COVERAGE_ENTITY_TYPES = [\n E.customers.customer_entity,\n E.customers.customer_person_profile,\n E.customers.customer_address,\n]\n\ntype PersonFieldValues = {\n externalId?: string | null\n firstName?: string | null\n lastName?: string | null\n displayName?: string | null\n primaryEmail?: string | null\n primaryPhone?: string | null\n jobTitle?: string | null\n status?: string | null\n source?: string | null\n description?: string | null\n}\n\ntype AddressFieldValues = {\n name?: string | null\n purpose?: string | null\n companyName?: string | null\n addressLine1?: string | null\n addressLine2?: string | null\n buildingNumber?: string | null\n flatNumber?: string | null\n city?: string | null\n region?: string | null\n postalCode?: string | null\n country?: string | null\n latitude?: number | null\n longitude?: number | null\n}\n\ntype PersonRowValues = {\n values: PersonFieldValues\n customFields: Record<string, unknown>\n addressValues: AddressFieldValues\n}\n\ntype ImportCustomFieldDefinition = {\n key: string\n kind: string\n entityId: string\n organizationId?: string | null\n tenantId?: string | null\n updatedAt?: Date | string | null\n}\n\ntype EmailDedupeIndex = Map<string, string>\n\ntype BuiltPersonPayload = {\n values: PersonFieldValues\n customFields: Record<string, unknown>\n addressValues: AddressFieldValues\n createInput: {\n organizationId: string\n tenantId: string\n firstName: string\n lastName: string\n displayName: string\n primaryEmail?: string\n primaryPhone?: string\n jobTitle?: string\n status?: string\n source?: string\n description?: string\n customFields?: Record<string, unknown>\n } | null\n updateInput: {\n organizationId: string\n tenantId: string\n primaryEmail?: string\n primaryPhone?: string\n jobTitle?: string\n status?: string\n source?: string\n description?: string\n firstName?: string\n lastName?: string\n displayName?: string\n customFields?: Record<string, unknown>\n }\n sourceIdentifier: string | null\n}\n\nfunction normalizeOptionalString(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction normalizeEmail(value: unknown): string | null {\n const normalized = normalizeOptionalString(value)\n return normalized ? normalized.toLowerCase() : null\n}\n\nconst COUNTRY_DIAL_CODES: Record<string, string> = {\n canada: '1',\n england: '44',\n greatbritain: '44',\n poland: '48',\n scotland: '44',\n uk: '44',\n unitedkingdom: '44',\n unitedstates: '1',\n unitedstatesofamerica: '1',\n usa: '1',\n wales: '44',\n}\n\nfunction normalizeCountryKey(value: unknown): string | null {\n const normalized = normalizeOptionalString(value)\n if (!normalized) return null\n return normalized.toLowerCase().replace(/[^a-z]/g, '')\n}\n\nfunction resolveCountryDialCode(row: CsvPreviewRow): string | null {\n const countryCandidates = [\n row.Country,\n row.country,\n row['Country/Region'],\n row['country/region'],\n ]\n\n for (const candidate of countryCandidates) {\n const countryKey = normalizeCountryKey(candidate)\n if (!countryKey) continue\n const dialCode = COUNTRY_DIAL_CODES[countryKey]\n if (dialCode) return dialCode\n }\n\n return null\n}\n\nfunction stripPhoneExtension(value: string): string {\n return value\n .replace(/\\s*(ext\\.?|extension|x)\\s*\\d+$/i, '')\n .replace(/^\\+\\s+/, '+')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\nfunction normalizeImportedPhone(value: unknown, row: CsvPreviewRow): string | null {\n const normalized = normalizeOptionalString(value)\n if (!normalized) return null\n\n const directValidation = validatePhoneNumber(normalized)\n if (directValidation.valid) {\n return directValidation.normalized\n }\n\n const sanitized = stripPhoneExtension(normalized)\n const sanitizedValidation = validatePhoneNumber(sanitized)\n if (sanitizedValidation.valid) {\n return sanitizedValidation.normalized\n }\n\n const digits = extractPhoneDigits(sanitized)\n if (!digits) return null\n\n if (digits.length >= 9 && digits.length <= 15 && sanitized.startsWith('00')) {\n const internationalCandidate = `+${digits.slice(2)}`\n const internationalValidation = validatePhoneNumber(internationalCandidate)\n if (internationalValidation.valid) {\n return internationalValidation.normalized\n }\n }\n\n const dialCode = resolveCountryDialCode(row)\n if (!dialCode) return null\n\n let nationalDigits = digits\n if (nationalDigits.startsWith(dialCode)) {\n nationalDigits = nationalDigits.slice(dialCode.length)\n } else {\n nationalDigits = nationalDigits.replace(/^0+/, '')\n }\n\n if (!nationalDigits) return null\n\n const localizedCandidate = `+${dialCode}${nationalDigits}`\n const localizedValidation = validatePhoneNumber(localizedCandidate)\n return localizedValidation.valid ? localizedValidation.normalized : null\n}\n\nfunction normalizeOptionalNumber(value: unknown): number | null {\n if (typeof value === 'number') {\n return Number.isFinite(value) ? value : null\n }\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n if (trimmed.length === 0) return null\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) ? parsed : null\n}\n\nfunction normalizeCustomFieldDate(value: string): string | null {\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return null\n return parsed.toISOString().slice(0, 10)\n}\n\nfunction normalizeCustomFieldBoolean(value: string): boolean | null {\n const normalized = value.trim().toLowerCase()\n if (['true', '1', 'yes', 'y', 't', 'tak'].includes(normalized)) return true\n if (['false', '0', 'no', 'n', 'f', 'nie'].includes(normalized)) return false\n return null\n}\n\nfunction coerceCustomFieldValue(\n key: string,\n value: unknown,\n definitions: Map<string, ImportCustomFieldDefinition>,\n): { ok: true; value: unknown } | { ok: false; error: string } {\n if (typeof value === 'string' && value.trim().length === 0) {\n return { ok: true, value: null }\n }\n\n const definition = definitions.get(key)\n if (!definition) {\n return { ok: true, value: typeof value === 'string' ? value.trim() : value }\n }\n\n if (value === null || value === undefined) {\n return { ok: true, value: null }\n }\n\n const text = typeof value === 'string' ? value.trim() : String(value)\n if (text.length === 0) {\n return { ok: true, value: null }\n }\n\n const kind = definition.kind.toLowerCase()\n if (kind === 'boolean') {\n const booleanValue = normalizeCustomFieldBoolean(text)\n if (booleanValue === null) {\n return { ok: false, error: `Custom field \"${key}\" expects a boolean value.` }\n }\n return { ok: true, value: booleanValue }\n }\n\n if (kind === 'integer') {\n const numberValue = Number(text)\n if (!Number.isInteger(numberValue)) {\n return { ok: false, error: `Custom field \"${key}\" expects an integer value.` }\n }\n return { ok: true, value: numberValue }\n }\n\n if (kind === 'float' || kind === 'currency' || kind === 'number') {\n const numberValue = Number(text)\n if (!Number.isFinite(numberValue)) {\n return { ok: false, error: `Custom field \"${key}\" expects a number value.` }\n }\n return { ok: true, value: numberValue }\n }\n\n if (kind === 'date' || kind === 'datetime') {\n const dateValue = normalizeCustomFieldDate(text)\n if (!dateValue) {\n return { ok: false, error: `Custom field \"${key}\" expects a date value.` }\n }\n return { ok: true, value: dateValue }\n }\n\n return { ok: true, value: text }\n}\n\nfunction coerceCustomFields(\n values: Record<string, unknown>,\n definitions: Map<string, ImportCustomFieldDefinition>,\n): { ok: true; values: Record<string, unknown> } | { ok: false; error: string } {\n const coerced: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(values)) {\n const result = coerceCustomFieldValue(key, value, definitions)\n if (!result.ok) return result\n coerced[key] = result.value\n }\n return { ok: true, values: coerced }\n}\n\nfunction customFieldDefinitionScopeScore(definition: ImportCustomFieldDefinition): number {\n return (definition.tenantId ? 2 : 0) + (definition.organizationId ? 1 : 0)\n}\n\nfunction customFieldDefinitionUpdatedAt(definition: ImportCustomFieldDefinition): number {\n if (!definition.updatedAt) return 0\n const value = definition.updatedAt instanceof Date\n ? definition.updatedAt.getTime()\n : new Date(definition.updatedAt).getTime()\n return Number.isFinite(value) ? value : 0\n}\n\nfunction preferCustomFieldDefinition(\n current: ImportCustomFieldDefinition | undefined,\n candidate: ImportCustomFieldDefinition,\n): boolean {\n if (!current) return true\n const candidateScore = customFieldDefinitionScopeScore(candidate)\n const currentScore = customFieldDefinitionScopeScore(current)\n if (candidateScore !== currentScore) return candidateScore > currentScore\n return customFieldDefinitionUpdatedAt(candidate) >= customFieldDefinitionUpdatedAt(current)\n}\n\nasync function loadImportCustomFieldDefinitions(\n em: EntityManager,\n scope: TenantScope,\n): Promise<Map<string, ImportCustomFieldDefinition>> {\n const definitions = await findWithDecryption(\n em,\n CustomFieldDef,\n {\n entityId: { $in: [E.customers.customer_entity, E.customers.customer_person_profile] as string[] },\n deletedAt: null,\n isActive: true,\n $and: [\n { $or: [{ tenantId: scope.tenantId }, { tenantId: null }] },\n { $or: [{ organizationId: scope.organizationId }, { organizationId: null }] },\n ],\n },\n undefined,\n scope,\n )\n\n const byKey = new Map<string, ImportCustomFieldDefinition>()\n for (const definition of definitions) {\n const candidate: ImportCustomFieldDefinition = {\n key: definition.key,\n kind: definition.kind,\n entityId: definition.entityId,\n organizationId: definition.organizationId ?? null,\n tenantId: definition.tenantId ?? null,\n updatedAt: definition.updatedAt ?? null,\n }\n if (preferCustomFieldDefinition(byKey.get(candidate.key), candidate)) {\n byKey.set(candidate.key, candidate)\n }\n }\n return byKey\n}\n\nfunction buildCommandContext(container: Container, scope: TenantScope): CommandRuntimeContext {\n return {\n container,\n auth: null,\n organizationScope: {\n selectedId: scope.organizationId,\n filterIds: [scope.organizationId],\n allowedIds: [scope.organizationId],\n tenantId: scope.tenantId,\n },\n selectedOrganizationId: scope.organizationId,\n organizationIds: [scope.organizationId],\n }\n}\n\nexport function createCursor(uploadId: string, offset: number): string {\n return JSON.stringify({\n uploadId,\n offset,\n })\n}\n\nexport function parseCursor(value: string | null | undefined): SyncExcelCursor | null {\n if (!value) return null\n try {\n const parsed = JSON.parse(value) as Partial<SyncExcelCursor> | null\n if (!parsed || typeof parsed !== 'object') return null\n if (typeof parsed.uploadId !== 'string' || parsed.uploadId.trim().length === 0) return null\n if (typeof parsed.offset !== 'number' || !Number.isFinite(parsed.offset) || parsed.offset < 0) return null\n return {\n uploadId: parsed.uploadId,\n offset: parsed.offset,\n }\n } catch {\n return null\n }\n}\n\nfunction mapRowValues(row: CsvPreviewRow, fields: FieldMapping[]): PersonRowValues {\n const values: PersonFieldValues = {}\n const customFields: Record<string, unknown> = {}\n const addressValues: AddressFieldValues = {}\n\n for (const field of fields) {\n if (field.mappingKind === 'ignore') continue\n const rawValue = row[field.externalField]\n if (rawValue === undefined || rawValue === null) continue\n if (field.mappingKind === 'custom_field' || field.localField.startsWith('cf:')) {\n const customFieldKey = field.localField.startsWith('cf:') ? field.localField.slice(3) : field.localField\n if (customFieldKey.trim().length > 0) {\n customFields[customFieldKey] = rawValue\n }\n continue\n }\n\n if (field.localField === 'person.externalId') {\n values.externalId = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.firstName') {\n values.firstName = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.lastName') {\n values.lastName = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.displayName') {\n values.displayName = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.primaryEmail') {\n values.primaryEmail = normalizeEmail(rawValue)\n continue\n }\n if (field.localField === 'person.primaryPhone') {\n values.primaryPhone = normalizeImportedPhone(rawValue, row)\n continue\n }\n if (field.localField === 'person.jobTitle') {\n values.jobTitle = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.status') {\n values.status = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.source') {\n values.source = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'person.description') {\n values.description = normalizeOptionalString(rawValue)\n continue\n }\n\n if (field.localField === 'address.name') {\n addressValues.name = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.purpose') {\n addressValues.purpose = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.companyName') {\n addressValues.companyName = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.addressLine1') {\n addressValues.addressLine1 = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.addressLine2') {\n addressValues.addressLine2 = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.buildingNumber') {\n addressValues.buildingNumber = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.flatNumber') {\n addressValues.flatNumber = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.city') {\n addressValues.city = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.region') {\n addressValues.region = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.postalCode') {\n addressValues.postalCode = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.country') {\n addressValues.country = normalizeOptionalString(rawValue)\n continue\n }\n if (field.localField === 'address.latitude') {\n addressValues.latitude = normalizeOptionalNumber(rawValue)\n continue\n }\n if (field.localField === 'address.longitude') {\n addressValues.longitude = normalizeOptionalNumber(rawValue)\n }\n }\n\n return {\n values,\n customFields,\n addressValues,\n }\n}\n\nfunction derivePersonNames(values: PersonFieldValues): { firstName: string; lastName: string; displayName: string } | null {\n const firstName = values.firstName ?? null\n const lastName = values.lastName ?? null\n const explicitDisplayName = values.displayName ?? null\n\n if (firstName && lastName) {\n return {\n firstName,\n lastName,\n displayName: explicitDisplayName ?? `${firstName} ${lastName}`.trim(),\n }\n }\n\n if (explicitDisplayName) {\n const parts = explicitDisplayName.split(/\\s+/).filter((part) => part.length > 0)\n if (parts.length >= 2) {\n return {\n firstName: firstName ?? parts.slice(0, -1).join(' '),\n lastName: lastName ?? parts.at(-1) ?? explicitDisplayName,\n displayName: explicitDisplayName,\n }\n }\n return {\n firstName: firstName ?? explicitDisplayName,\n lastName: lastName ?? explicitDisplayName,\n displayName: explicitDisplayName,\n }\n }\n\n return null\n}\n\nexport function buildPersonPayload(row: CsvPreviewRow, mapping: DataMapping, scope: TenantScope): BuiltPersonPayload {\n const { values, customFields, addressValues } = mapRowValues(row, mapping.fields)\n const derivedNames = derivePersonNames(values)\n const sourceIdentifier = values.externalId ?? values.primaryEmail ?? values.displayName ?? null\n\n const updateInput: BuiltPersonPayload['updateInput'] = {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n }\n\n if (values.primaryEmail) updateInput.primaryEmail = values.primaryEmail\n if (values.primaryPhone) updateInput.primaryPhone = values.primaryPhone\n if (values.jobTitle) updateInput.jobTitle = values.jobTitle\n if (values.status) updateInput.status = values.status\n if (values.source) updateInput.source = values.source\n if (values.description) updateInput.description = values.description\n if (derivedNames?.firstName) updateInput.firstName = derivedNames.firstName\n if (derivedNames?.lastName) updateInput.lastName = derivedNames.lastName\n if (derivedNames?.displayName) updateInput.displayName = derivedNames.displayName\n\n if (!derivedNames) {\n return {\n values,\n customFields,\n addressValues,\n createInput: null,\n updateInput,\n sourceIdentifier,\n }\n }\n\n return {\n values,\n customFields,\n addressValues,\n createInput: {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n firstName: derivedNames.firstName,\n lastName: derivedNames.lastName,\n displayName: derivedNames.displayName,\n ...(values.primaryEmail ? { primaryEmail: values.primaryEmail } : {}),\n ...(values.primaryPhone ? { primaryPhone: values.primaryPhone } : {}),\n ...(values.jobTitle ? { jobTitle: values.jobTitle } : {}),\n ...(values.status ? { status: values.status } : {}),\n ...(values.source ? { source: values.source } : {}),\n ...(values.description ? { description: values.description } : {}),\n },\n updateInput,\n sourceIdentifier,\n }\n}\n\nfunction hasAddressValues(addressValues: AddressFieldValues): boolean {\n return Object.values(addressValues).some((value) => value !== null && value !== undefined)\n}\n\nfunction buildAddressCreateInput(addressValues: AddressFieldValues, entityId: string, scope: TenantScope) {\n if (!addressValues.addressLine1) return null\n\n return {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n entityId,\n addressLine1: addressValues.addressLine1,\n isPrimary: true,\n ...(addressValues.name ? { name: addressValues.name } : {}),\n ...(addressValues.purpose ? { purpose: addressValues.purpose } : {}),\n ...(addressValues.companyName ? { companyName: addressValues.companyName } : {}),\n ...(addressValues.addressLine2 ? { addressLine2: addressValues.addressLine2 } : {}),\n ...(addressValues.buildingNumber ? { buildingNumber: addressValues.buildingNumber } : {}),\n ...(addressValues.flatNumber ? { flatNumber: addressValues.flatNumber } : {}),\n ...(addressValues.city ? { city: addressValues.city } : {}),\n ...(addressValues.region ? { region: addressValues.region } : {}),\n ...(addressValues.postalCode ? { postalCode: addressValues.postalCode } : {}),\n ...(addressValues.country ? { country: addressValues.country } : {}),\n ...(addressValues.latitude !== null && addressValues.latitude !== undefined ? { latitude: addressValues.latitude } : {}),\n ...(addressValues.longitude !== null && addressValues.longitude !== undefined ? { longitude: addressValues.longitude } : {}),\n }\n}\n\nasync function upsertPrimaryAddress(params: {\n entityId: string\n addressValues: AddressFieldValues\n scope: TenantScope\n commandBus: CommandBus\n commandContext: CommandRuntimeContext\n em: EntityManager\n}): Promise<void> {\n if (!hasAddressValues(params.addressValues)) return\n if (!params.addressValues.addressLine1) return\n\n const [existingPrimaryAddress] = await findWithDecryption(\n params.em,\n CustomerAddress,\n {\n entity: params.entityId,\n organizationId: params.scope.organizationId,\n tenantId: params.scope.tenantId,\n isPrimary: true,\n },\n {\n orderBy: {\n createdAt: 'asc',\n },\n limit: 1,\n },\n params.scope,\n )\n\n if (existingPrimaryAddress) {\n await params.commandBus.execute('customers.addresses.update', {\n input: {\n id: existingPrimaryAddress.id,\n isPrimary: true,\n ...(params.addressValues.name ? { name: params.addressValues.name } : {}),\n ...(params.addressValues.purpose ? { purpose: params.addressValues.purpose } : {}),\n ...(params.addressValues.companyName ? { companyName: params.addressValues.companyName } : {}),\n ...(params.addressValues.addressLine1 ? { addressLine1: params.addressValues.addressLine1 } : {}),\n ...(params.addressValues.addressLine2 ? { addressLine2: params.addressValues.addressLine2 } : {}),\n ...(params.addressValues.buildingNumber ? { buildingNumber: params.addressValues.buildingNumber } : {}),\n ...(params.addressValues.flatNumber ? { flatNumber: params.addressValues.flatNumber } : {}),\n ...(params.addressValues.city ? { city: params.addressValues.city } : {}),\n ...(params.addressValues.region ? { region: params.addressValues.region } : {}),\n ...(params.addressValues.postalCode ? { postalCode: params.addressValues.postalCode } : {}),\n ...(params.addressValues.country ? { country: params.addressValues.country } : {}),\n ...(params.addressValues.latitude !== null && params.addressValues.latitude !== undefined\n ? { latitude: params.addressValues.latitude }\n : {}),\n ...(params.addressValues.longitude !== null && params.addressValues.longitude !== undefined\n ? { longitude: params.addressValues.longitude }\n : {}),\n },\n ctx: params.commandContext,\n })\n return\n }\n\n const createInput = buildAddressCreateInput(params.addressValues, params.entityId, params.scope)\n if (!createInput) return\n\n await params.commandBus.execute('customers.addresses.create', {\n input: createInput,\n ctx: params.commandContext,\n })\n}\n\nasync function loadStoredMapping(em: EntityManager, entityType: string, scope: TenantScope): Promise<DataMapping> {\n const stored = await findOneWithDecryption(\n em,\n SyncMapping,\n {\n integrationId: 'sync_excel',\n entityType,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n\n if (stored?.mapping && typeof stored.mapping === 'object') {\n return stored.mapping as unknown as DataMapping\n }\n\n return {\n entityType,\n fields: [],\n matchStrategy: 'custom',\n }\n}\n\nasync function resolveUpload(em: EntityManager, runId: string | undefined, cursor: SyncExcelCursor | null, scope: TenantScope): Promise<SyncExcelUpload | null> {\n if (runId) {\n const uploadByRun = await findOneWithDecryption(\n em,\n SyncExcelUpload,\n {\n syncRunId: runId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n if (uploadByRun) return uploadByRun\n }\n\n if (cursor?.uploadId) {\n return findOneWithDecryption(\n em,\n SyncExcelUpload,\n {\n id: cursor.uploadId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n }\n\n return null\n}\n\nasync function resolveExistingPersonId(params: {\n externalIdMappingService: ExternalIdMappingService\n externalId: string | null | undefined\n email: string | null | undefined\n emailDedupeIndex: EmailDedupeIndex\n scope: TenantScope\n}): Promise<string | null> {\n if (params.externalId) {\n const mappedLocalId = await params.externalIdMappingService.lookupLocalId(\n 'sync_excel',\n 'customers.person',\n params.externalId,\n params.scope,\n )\n if (mappedLocalId) return mappedLocalId\n }\n\n if (!params.email) return null\n return params.emailDedupeIndex.get(params.email) ?? null\n}\n\nfunction mappingHasEmailField(mapping: DataMapping): boolean {\n return mapping.fields.some((field) => field.localField === 'person.primaryEmail')\n}\n\nasync function buildEmailDedupeIndex(params: {\n em: EntityManager\n mapping: DataMapping\n scope: TenantScope\n}): Promise<EmailDedupeIndex> {\n if (!mappingHasEmailField(params.mapping)) return new Map()\n\n const candidates = await findWithDecryption(\n params.em,\n CustomerEntity,\n {\n kind: 'person',\n organizationId: params.scope.organizationId,\n tenantId: params.scope.tenantId,\n deletedAt: null,\n isActive: true,\n },\n {\n orderBy: {\n createdAt: 'asc',\n },\n },\n params.scope,\n )\n\n const index: EmailDedupeIndex = new Map()\n for (const candidate of candidates) {\n const email = normalizeEmail(candidate.primaryEmail)\n if (!email || index.has(email)) continue\n index.set(email, candidate.id)\n }\n return index\n}\n\nfunction isEmptyRow(row: CsvPreviewRow): boolean {\n return !Object.values(row).some((value) => normalizeOptionalString(value))\n}\n\nasync function processRow(params: {\n row: CsvPreviewRow\n rowNumber: number\n mapping: DataMapping\n scope: TenantScope\n commandBus: CommandBus\n commandContext: CommandRuntimeContext\n externalIdMappingService: ExternalIdMappingService\n emailDedupeIndex: EmailDedupeIndex\n customFieldDefinitions: Map<string, ImportCustomFieldDefinition>\n em: EntityManager\n}): Promise<ImportItem> {\n if (isEmptyRow(params.row)) {\n return {\n externalId: `row:${params.rowNumber}`,\n action: 'skip',\n data: {\n rowNumber: params.rowNumber,\n reason: 'empty_row',\n },\n }\n }\n\n const payload = buildPersonPayload(params.row, params.mapping, params.scope)\n const externalId = payload.values.externalId ?? null\n const sourceIdentifier = payload.sourceIdentifier ?? `row:${params.rowNumber}`\n const customFieldResult = coerceCustomFields(payload.customFields, params.customFieldDefinitions)\n if (!customFieldResult.ok) {\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'failed',\n data: {\n rowNumber: params.rowNumber,\n sourceIdentifier,\n errorMessage: customFieldResult.error,\n },\n }\n }\n const existingId = await resolveExistingPersonId({\n externalIdMappingService: params.externalIdMappingService,\n externalId,\n email: payload.values.primaryEmail,\n emailDedupeIndex: params.emailDedupeIndex,\n scope: params.scope,\n })\n\n if (!existingId && !payload.createInput) {\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'failed',\n data: {\n rowNumber: params.rowNumber,\n sourceIdentifier,\n errorMessage: 'Import row is missing a usable person name mapping.',\n },\n }\n }\n\n try {\n if (existingId) {\n const updateInput = {\n id: existingId,\n ...payload.updateInput,\n ...(Object.keys(customFieldResult.values).length > 0 ? { customFields: customFieldResult.values } : {}),\n }\n await params.commandBus.execute('customers.people.update', {\n input: updateInput,\n ctx: params.commandContext,\n })\n\n await upsertPrimaryAddress({\n entityId: existingId,\n addressValues: payload.addressValues,\n scope: params.scope,\n commandBus: params.commandBus,\n commandContext: params.commandContext,\n em: params.em,\n })\n\n if (externalId) {\n await params.externalIdMappingService.storeExternalIdMapping(\n 'sync_excel',\n 'customers.person',\n existingId,\n externalId,\n params.scope,\n )\n }\n if (payload.values.primaryEmail && !params.emailDedupeIndex.has(payload.values.primaryEmail)) {\n params.emailDedupeIndex.set(payload.values.primaryEmail, existingId)\n }\n\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'update',\n data: {\n localId: existingId,\n rowNumber: params.rowNumber,\n sourceIdentifier,\n },\n }\n }\n\n const commandResult = await params.commandBus.execute<\n NonNullable<BuiltPersonPayload['createInput']>,\n { entityId: string; personId: string }\n >('customers.people.create', {\n input: {\n ...payload.createInput!,\n ...(Object.keys(customFieldResult.values).length > 0 ? { customFields: customFieldResult.values } : {}),\n },\n ctx: params.commandContext,\n })\n\n await upsertPrimaryAddress({\n entityId: commandResult.result.entityId,\n addressValues: payload.addressValues,\n scope: params.scope,\n commandBus: params.commandBus,\n commandContext: params.commandContext,\n em: params.em,\n })\n\n if (externalId) {\n await params.externalIdMappingService.storeExternalIdMapping(\n 'sync_excel',\n 'customers.person',\n commandResult.result.entityId,\n externalId,\n params.scope,\n )\n }\n if (payload.values.primaryEmail && !params.emailDedupeIndex.has(payload.values.primaryEmail)) {\n params.emailDedupeIndex.set(payload.values.primaryEmail, commandResult.result.entityId)\n }\n\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'create',\n data: {\n localId: commandResult.result.entityId,\n personId: commandResult.result.personId,\n rowNumber: params.rowNumber,\n sourceIdentifier,\n },\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Import row failed'\n return {\n externalId: externalId ?? sourceIdentifier,\n action: 'failed',\n data: {\n rowNumber: params.rowNumber,\n sourceIdentifier,\n errorMessage,\n },\n }\n }\n}\n\nexport const syncExcelCustomersAdapter: DataSyncAdapter = {\n providerKey: 'excel',\n runMode: 'provider',\n operationalTelemetry: true,\n direction: 'import',\n supportedEntities: ['customers.person'],\n\n async getMapping(input): Promise<DataMapping> {\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n return loadStoredMapping(em, input.entityType, input.scope)\n },\n\n async *streamImport(input): AsyncIterable<ImportBatch> {\n if (input.entityType !== 'customers.person') {\n throw new Error(`Unsupported sync_excel entity type: ${input.entityType}`)\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandBus = container.resolve('commandBus') as CommandBus\n const externalIdMappingService = container.resolve('externalIdMappingService') as ExternalIdMappingService\n const cursor = parseCursor(input.cursor)\n const upload = await resolveUpload(em, input.runId, cursor, input.scope)\n\n if (!upload) {\n throw new Error('CSV upload session was not found for this sync run.')\n }\n\n if (input.runId) {\n upload.syncRunId = input.runId\n }\n upload.status = 'importing'\n await em.flush()\n\n try {\n const attachment = await findOneWithDecryption(\n em,\n Attachment,\n {\n id: upload.attachmentId,\n organizationId: input.scope.organizationId,\n tenantId: input.scope.tenantId,\n },\n undefined,\n input.scope,\n )\n\n if (!attachment) {\n throw new Error('CSV upload attachment could not be found.')\n }\n\n const document = await parseCsvStreamMetadata(await createSyncExcelUploadReadStream(attachment))\n const startOffset = cursor?.uploadId === upload.id ? cursor.offset : 0\n const commandContext = buildCommandContext(container, input.scope)\n const customFieldDefinitions = await loadImportCustomFieldDefinitions(em, input.scope)\n const emailDedupeIndex = await buildEmailDedupeIndex({\n em,\n mapping: input.mapping,\n scope: input.scope,\n })\n let batchIndex = 0\n\n for await (const batch of parseCsvDocumentBatches(await createSyncExcelUploadReadStream(attachment), {\n batchSize: input.batchSize,\n startOffset,\n })) {\n const batchRows = batch.rows\n const items: ImportItem[] = []\n\n for (let index = 0; index < batchRows.length; index += 1) {\n items.push(await processRow({\n row: batchRows[index],\n rowNumber: batch.rowStart + index + 1,\n mapping: input.mapping,\n scope: input.scope,\n commandBus,\n commandContext,\n externalIdMappingService,\n emailDedupeIndex,\n customFieldDefinitions,\n em,\n }))\n }\n\n yield {\n items,\n cursor: createCursor(upload.id, batch.nextOffset),\n hasMore: batch.nextOffset < document.totalRows,\n totalEstimate: document.totalRows,\n processedCount: batchRows.length,\n refreshCoverageEntityTypes: CUSTOMER_IMPORT_COVERAGE_ENTITY_TYPES,\n batchIndex,\n message: `Processed ${batch.nextOffset} of ${document.totalRows} CSV rows`,\n }\n batchIndex += 1\n }\n\n upload.status = 'completed'\n await em.flush()\n } catch (error) {\n upload.status = 'failed'\n await em.flush()\n throw error\n }\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,6 @@
1
+ import { TextDecoder } from "util";
2
+ const CSV_DELIMITER_PROBE_ROWS = 5;
3
+ const CSV_DELIMITER_PROBE_MAX_CHARS = 64 * 1024;
1
4
  function stripUtf8Bom(value) {
2
5
  return value.charCodeAt(0) === 65279 ? value.slice(1) : value;
3
6
  }
@@ -87,6 +90,186 @@ function rowsToObjects(headers, rows, maxRows) {
87
90
  return record;
88
91
  });
89
92
  }
93
+ async function createCsvRowStream(input) {
94
+ const iterator = input[Symbol.asyncIterator]();
95
+ const decoder = new TextDecoder("utf-8");
96
+ const prefixTexts = [];
97
+ let probeText = "";
98
+ while (probeText.length < CSV_DELIMITER_PROBE_MAX_CHARS) {
99
+ const next = await iterator.next();
100
+ if (next.done) break;
101
+ const text = typeof next.value === "string" ? next.value : decoder.decode(next.value, { stream: true });
102
+ prefixTexts.push(text);
103
+ probeText += text;
104
+ if (parseCsvText(probeText, ",").length >= CSV_DELIMITER_PROBE_ROWS) {
105
+ break;
106
+ }
107
+ }
108
+ const delimiter = detectCsvDelimiter(probeText);
109
+ async function* decodedChunks() {
110
+ for (const text of prefixTexts) {
111
+ yield text;
112
+ }
113
+ while (true) {
114
+ const next = await iterator.next();
115
+ if (next.done) break;
116
+ yield typeof next.value === "string" ? next.value : decoder.decode(next.value, { stream: true });
117
+ }
118
+ const finalChunk = decoder.decode();
119
+ if (finalChunk.length > 0) {
120
+ yield finalChunk;
121
+ }
122
+ }
123
+ return {
124
+ delimiter,
125
+ rows: parseCsvRowsFromTextChunks(decodedChunks(), delimiter)
126
+ };
127
+ }
128
+ async function* parseCsvRowsFromTextChunks(chunks, delimiter) {
129
+ let currentRow = [];
130
+ let currentValue = "";
131
+ let inQuotes = false;
132
+ let isFirstChunk = true;
133
+ let previousWasCarriageReturn = false;
134
+ let pendingQuote = false;
135
+ let pendingQuoteWasInQuotes = false;
136
+ const completeRow = () => {
137
+ currentRow.push(currentValue);
138
+ const completedRow = currentRow;
139
+ currentRow = [];
140
+ currentValue = "";
141
+ return completedRow.some((value) => value.trim().length > 0) ? completedRow : null;
142
+ };
143
+ for await (const chunk of chunks) {
144
+ const text = isFirstChunk ? stripUtf8Bom(chunk) : chunk;
145
+ isFirstChunk = false;
146
+ for (let index = 0; index < text.length; index += 1) {
147
+ const character = text[index];
148
+ if (pendingQuote) {
149
+ pendingQuote = false;
150
+ if (pendingQuoteWasInQuotes && character === '"') {
151
+ currentValue += '"';
152
+ continue;
153
+ }
154
+ inQuotes = !pendingQuoteWasInQuotes;
155
+ }
156
+ if (previousWasCarriageReturn) {
157
+ previousWasCarriageReturn = false;
158
+ if (character === "\n") {
159
+ continue;
160
+ }
161
+ }
162
+ if (character === '"') {
163
+ if (index + 1 >= text.length) {
164
+ pendingQuote = true;
165
+ pendingQuoteWasInQuotes = inQuotes;
166
+ continue;
167
+ }
168
+ if (inQuotes && text[index + 1] === '"') {
169
+ currentValue += '"';
170
+ index += 1;
171
+ continue;
172
+ }
173
+ inQuotes = !inQuotes;
174
+ continue;
175
+ }
176
+ if (!inQuotes && character === delimiter) {
177
+ currentRow.push(currentValue);
178
+ currentValue = "";
179
+ continue;
180
+ }
181
+ if (character === "\r") {
182
+ previousWasCarriageReturn = true;
183
+ if (inQuotes) {
184
+ currentValue += "\n";
185
+ } else {
186
+ const row = completeRow();
187
+ if (row) yield row;
188
+ }
189
+ continue;
190
+ }
191
+ if (character === "\n") {
192
+ if (inQuotes) {
193
+ currentValue += "\n";
194
+ } else {
195
+ const row = completeRow();
196
+ if (row) yield row;
197
+ }
198
+ continue;
199
+ }
200
+ currentValue += character;
201
+ }
202
+ }
203
+ if (currentValue.length > 0 || currentRow.length > 0) {
204
+ const row = completeRow();
205
+ if (row) yield row;
206
+ }
207
+ }
208
+ async function parseCsvStreamMetadata(input) {
209
+ const stream = await createCsvRowStream(input);
210
+ let headers = [];
211
+ let hasHeaders = false;
212
+ let totalRows = 0;
213
+ for await (const row of stream.rows) {
214
+ if (!hasHeaders) {
215
+ headers = normalizeHeaders(row);
216
+ hasHeaders = true;
217
+ continue;
218
+ }
219
+ totalRows += 1;
220
+ }
221
+ return {
222
+ headers,
223
+ totalRows,
224
+ delimiter: stream.delimiter,
225
+ encoding: "utf-8"
226
+ };
227
+ }
228
+ async function* parseCsvDocumentBatches(input, options) {
229
+ const stream = await createCsvRowStream(input);
230
+ const batchSize = Math.max(1, Math.floor(options.batchSize));
231
+ const startOffset = Math.max(0, Math.floor(options.startOffset ?? 0));
232
+ let headers = null;
233
+ let rowOffset = 0;
234
+ let batchRows = [];
235
+ let batchStart = startOffset;
236
+ for await (const row of stream.rows) {
237
+ if (!headers) {
238
+ headers = normalizeHeaders(row);
239
+ continue;
240
+ }
241
+ if (rowOffset < startOffset) {
242
+ rowOffset += 1;
243
+ continue;
244
+ }
245
+ if (batchRows.length === 0) {
246
+ batchStart = rowOffset;
247
+ }
248
+ batchRows.push(row);
249
+ rowOffset += 1;
250
+ if (batchRows.length >= batchSize) {
251
+ yield {
252
+ headers,
253
+ rows: rowsToObjects(headers, batchRows, batchRows.length),
254
+ rowStart: batchStart,
255
+ nextOffset: rowOffset,
256
+ delimiter: stream.delimiter,
257
+ encoding: "utf-8"
258
+ };
259
+ batchRows = [];
260
+ }
261
+ }
262
+ if (headers && batchRows.length > 0) {
263
+ yield {
264
+ headers,
265
+ rows: rowsToObjects(headers, batchRows, batchRows.length),
266
+ rowStart: batchStart,
267
+ nextOffset: rowOffset,
268
+ delimiter: stream.delimiter,
269
+ encoding: "utf-8"
270
+ };
271
+ }
272
+ }
90
273
  function parseCsvPreview(buffer, options = {}) {
91
274
  const text = buffer.toString("utf-8");
92
275
  const delimiter = detectCsvDelimiter(text);
@@ -119,7 +302,9 @@ function parseCsvDocument(buffer) {
119
302
  export {
120
303
  detectCsvDelimiter,
121
304
  parseCsvDocument,
305
+ parseCsvDocumentBatches,
122
306
  parseCsvPreview,
307
+ parseCsvStreamMetadata,
123
308
  parseCsvText
124
309
  };
125
310
  //# sourceMappingURL=parser.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/sync_excel/lib/parser.ts"],
4
- "sourcesContent": ["export type CsvPreviewRow = Record<string, string | null>\n\nexport type CsvPreview = {\n headers: string[]\n sampleRows: CsvPreviewRow[]\n totalRows: number\n delimiter: ',' | ';'\n encoding: 'utf-8'\n}\n\nexport type CsvDocument = {\n headers: string[]\n rows: CsvPreviewRow[]\n totalRows: number\n delimiter: ',' | ';'\n encoding: 'utf-8'\n}\n\ntype ParseCsvPreviewOptions = {\n maxRows?: number\n}\n\nfunction stripUtf8Bom(value: string): string {\n return value.charCodeAt(0) === 0xfeff ? value.slice(1) : value\n}\n\nfunction countFields(line: string, delimiter: ',' | ';'): number {\n let count = 1\n let inQuotes = false\n\n for (let index = 0; index < line.length; index += 1) {\n const character = line[index]\n if (character === '\"') {\n if (inQuotes && line[index + 1] === '\"') {\n index += 1\n continue\n }\n inQuotes = !inQuotes\n continue\n }\n if (!inQuotes && character === delimiter) {\n count += 1\n }\n }\n\n return count\n}\n\nexport function detectCsvDelimiter(text: string): ',' | ';' {\n const normalized = stripUtf8Bom(text).replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n')\n const lines = normalized\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line.length > 0)\n .slice(0, 5)\n\n if (lines.length === 0) return ','\n\n const commaScore = lines.reduce((accumulator, line) => accumulator + countFields(line, ','), 0)\n const semicolonScore = lines.reduce((accumulator, line) => accumulator + countFields(line, ';'), 0)\n\n return semicolonScore > commaScore ? ';' : ','\n}\n\nexport function parseCsvText(text: string, delimiter: ',' | ';'): string[][] {\n const normalized = stripUtf8Bom(text).replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n')\n const rows: string[][] = []\n let currentRow: string[] = []\n let currentValue = ''\n let inQuotes = false\n\n for (let index = 0; index < normalized.length; index += 1) {\n const character = normalized[index]\n\n if (character === '\"') {\n if (inQuotes && normalized[index + 1] === '\"') {\n currentValue += '\"'\n index += 1\n continue\n }\n inQuotes = !inQuotes\n continue\n }\n\n if (!inQuotes && character === delimiter) {\n currentRow.push(currentValue)\n currentValue = ''\n continue\n }\n\n if (!inQuotes && character === '\\n') {\n currentRow.push(currentValue)\n const isMeaningfulRow = currentRow.some((value) => value.trim().length > 0)\n if (isMeaningfulRow) {\n rows.push(currentRow)\n }\n currentRow = []\n currentValue = ''\n continue\n }\n\n currentValue += character\n }\n\n if (currentValue.length > 0 || currentRow.length > 0) {\n currentRow.push(currentValue)\n const isMeaningfulRow = currentRow.some((value) => value.trim().length > 0)\n if (isMeaningfulRow) {\n rows.push(currentRow)\n }\n }\n\n return rows\n}\n\nfunction normalizeHeaders(headers: string[]): string[] {\n return headers.map((header, index) => {\n const trimmed = header.trim()\n return trimmed.length > 0 ? trimmed : `Column ${index + 1}`\n })\n}\n\nfunction rowsToObjects(headers: string[], rows: string[][], maxRows: number): CsvPreviewRow[] {\n return rows.slice(0, maxRows).map((row) => {\n const record: CsvPreviewRow = {}\n headers.forEach((header, index) => {\n const value = row[index]\n record[header] = typeof value === 'string' && value.length > 0 ? value : null\n })\n return record\n })\n}\n\nexport function parseCsvPreview(buffer: Buffer, options: ParseCsvPreviewOptions = {}): CsvPreview {\n const text = buffer.toString('utf-8')\n const delimiter = detectCsvDelimiter(text)\n const rows = parseCsvText(text, delimiter)\n const [rawHeaders = [], ...dataRows] = rows\n const headers = normalizeHeaders(rawHeaders)\n const maxRows = options.maxRows ?? 5\n\n return {\n headers,\n sampleRows: rowsToObjects(headers, dataRows, maxRows),\n totalRows: dataRows.length,\n delimiter,\n encoding: 'utf-8',\n }\n}\n\nexport function parseCsvDocument(buffer: Buffer): CsvDocument {\n const text = buffer.toString('utf-8')\n const delimiter = detectCsvDelimiter(text)\n const rows = parseCsvText(text, delimiter)\n const [rawHeaders = [], ...dataRows] = rows\n const headers = normalizeHeaders(rawHeaders)\n\n return {\n headers,\n rows: rowsToObjects(headers, dataRows, dataRows.length),\n totalRows: dataRows.length,\n delimiter,\n encoding: 'utf-8',\n }\n}\n"],
5
- "mappings": "AAsBA,SAAS,aAAa,OAAuB;AAC3C,SAAO,MAAM,WAAW,CAAC,MAAM,QAAS,MAAM,MAAM,CAAC,IAAI;AAC3D;AAEA,SAAS,YAAY,MAAc,WAA8B;AAC/D,MAAI,QAAQ;AACZ,MAAI,WAAW;AAEf,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,YAAY,KAAK,KAAK;AAC5B,QAAI,cAAc,KAAK;AACrB,UAAI,YAAY,KAAK,QAAQ,CAAC,MAAM,KAAK;AACvC,iBAAS;AACT;AAAA,MACF;AACA,iBAAW,CAAC;AACZ;AAAA,IACF;AACA,QAAI,CAAC,YAAY,cAAc,WAAW;AACxC,eAAS;AAAA,IACX;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,mBAAmB,MAAyB;AAC1D,QAAM,aAAa,aAAa,IAAI,EAAE,QAAQ,SAAS,IAAI,EAAE,QAAQ,OAAO,IAAI;AAChF,QAAM,QAAQ,WACX,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAChC,MAAM,GAAG,CAAC;AAEb,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,aAAa,MAAM,OAAO,CAAC,aAAa,SAAS,cAAc,YAAY,MAAM,GAAG,GAAG,CAAC;AAC9F,QAAM,iBAAiB,MAAM,OAAO,CAAC,aAAa,SAAS,cAAc,YAAY,MAAM,GAAG,GAAG,CAAC;AAElG,SAAO,iBAAiB,aAAa,MAAM;AAC7C;AAEO,SAAS,aAAa,MAAc,WAAkC;AAC3E,QAAM,aAAa,aAAa,IAAI,EAAE,QAAQ,SAAS,IAAI,EAAE,QAAQ,OAAO,IAAI;AAChF,QAAM,OAAmB,CAAC;AAC1B,MAAI,aAAuB,CAAC;AAC5B,MAAI,eAAe;AACnB,MAAI,WAAW;AAEf,WAAS,QAAQ,GAAG,QAAQ,WAAW,QAAQ,SAAS,GAAG;AACzD,UAAM,YAAY,WAAW,KAAK;AAElC,QAAI,cAAc,KAAK;AACrB,UAAI,YAAY,WAAW,QAAQ,CAAC,MAAM,KAAK;AAC7C,wBAAgB;AAChB,iBAAS;AACT;AAAA,MACF;AACA,iBAAW,CAAC;AACZ;AAAA,IACF;AAEA,QAAI,CAAC,YAAY,cAAc,WAAW;AACxC,iBAAW,KAAK,YAAY;AAC5B,qBAAe;AACf;AAAA,IACF;AAEA,QAAI,CAAC,YAAY,cAAc,MAAM;AACnC,iBAAW,KAAK,YAAY;AAC5B,YAAM,kBAAkB,WAAW,KAAK,CAAC,UAAU,MAAM,KAAK,EAAE,SAAS,CAAC;AAC1E,UAAI,iBAAiB;AACnB,aAAK,KAAK,UAAU;AAAA,MACtB;AACA,mBAAa,CAAC;AACd,qBAAe;AACf;AAAA,IACF;AAEA,oBAAgB;AAAA,EAClB;AAEA,MAAI,aAAa,SAAS,KAAK,WAAW,SAAS,GAAG;AACpD,eAAW,KAAK,YAAY;AAC5B,UAAM,kBAAkB,WAAW,KAAK,CAAC,UAAU,MAAM,KAAK,EAAE,SAAS,CAAC;AAC1E,QAAI,iBAAiB;AACnB,WAAK,KAAK,UAAU;AAAA,IACtB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,iBAAiB,SAA6B;AACrD,SAAO,QAAQ,IAAI,CAAC,QAAQ,UAAU;AACpC,UAAM,UAAU,OAAO,KAAK;AAC5B,WAAO,QAAQ,SAAS,IAAI,UAAU,UAAU,QAAQ,CAAC;AAAA,EAC3D,CAAC;AACH;AAEA,SAAS,cAAc,SAAmB,MAAkB,SAAkC;AAC5F,SAAO,KAAK,MAAM,GAAG,OAAO,EAAE,IAAI,CAAC,QAAQ;AACzC,UAAM,SAAwB,CAAC;AAC/B,YAAQ,QAAQ,CAAC,QAAQ,UAAU;AACjC,YAAM,QAAQ,IAAI,KAAK;AACvB,aAAO,MAAM,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AAAA,IAC3E,CAAC;AACD,WAAO;AAAA,EACT,CAAC;AACH;AAEO,SAAS,gBAAgB,QAAgB,UAAkC,CAAC,GAAe;AAChG,QAAM,OAAO,OAAO,SAAS,OAAO;AACpC,QAAM,YAAY,mBAAmB,IAAI;AACzC,QAAM,OAAO,aAAa,MAAM,SAAS;AACzC,QAAM,CAAC,aAAa,CAAC,GAAG,GAAG,QAAQ,IAAI;AACvC,QAAM,UAAU,iBAAiB,UAAU;AAC3C,QAAM,UAAU,QAAQ,WAAW;AAEnC,SAAO;AAAA,IACL;AAAA,IACA,YAAY,cAAc,SAAS,UAAU,OAAO;AAAA,IACpD,WAAW,SAAS;AAAA,IACpB;AAAA,IACA,UAAU;AAAA,EACZ;AACF;AAEO,SAAS,iBAAiB,QAA6B;AAC5D,QAAM,OAAO,OAAO,SAAS,OAAO;AACpC,QAAM,YAAY,mBAAmB,IAAI;AACzC,QAAM,OAAO,aAAa,MAAM,SAAS;AACzC,QAAM,CAAC,aAAa,CAAC,GAAG,GAAG,QAAQ,IAAI;AACvC,QAAM,UAAU,iBAAiB,UAAU;AAE3C,SAAO;AAAA,IACL;AAAA,IACA,MAAM,cAAc,SAAS,UAAU,SAAS,MAAM;AAAA,IACtD,WAAW,SAAS;AAAA,IACpB;AAAA,IACA,UAAU;AAAA,EACZ;AACF;",
4
+ "sourcesContent": ["import { TextDecoder } from 'util'\n\nexport type CsvPreviewRow = Record<string, string | null>\n\nexport type CsvPreview = {\n headers: string[]\n sampleRows: CsvPreviewRow[]\n totalRows: number\n delimiter: ',' | ';'\n encoding: 'utf-8'\n}\n\nexport type CsvDocument = {\n headers: string[]\n rows: CsvPreviewRow[]\n totalRows: number\n delimiter: ',' | ';'\n encoding: 'utf-8'\n}\n\nexport type CsvStreamMetadata = {\n headers: string[]\n totalRows: number\n delimiter: ',' | ';'\n encoding: 'utf-8'\n}\n\nexport type CsvDocumentBatch = {\n headers: string[]\n rows: CsvPreviewRow[]\n rowStart: number\n nextOffset: number\n delimiter: ',' | ';'\n encoding: 'utf-8'\n}\n\ntype ParseCsvPreviewOptions = {\n maxRows?: number\n}\n\ntype ParseCsvBatchOptions = {\n batchSize: number\n startOffset?: number\n}\n\ntype CsvInputChunk = Buffer | Uint8Array | string\n\nconst CSV_DELIMITER_PROBE_ROWS = 5\nconst CSV_DELIMITER_PROBE_MAX_CHARS = 64 * 1024\n\nfunction stripUtf8Bom(value: string): string {\n return value.charCodeAt(0) === 0xfeff ? value.slice(1) : value\n}\n\nfunction countFields(line: string, delimiter: ',' | ';'): number {\n let count = 1\n let inQuotes = false\n\n for (let index = 0; index < line.length; index += 1) {\n const character = line[index]\n if (character === '\"') {\n if (inQuotes && line[index + 1] === '\"') {\n index += 1\n continue\n }\n inQuotes = !inQuotes\n continue\n }\n if (!inQuotes && character === delimiter) {\n count += 1\n }\n }\n\n return count\n}\n\nexport function detectCsvDelimiter(text: string): ',' | ';' {\n const normalized = stripUtf8Bom(text).replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n')\n const lines = normalized\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line.length > 0)\n .slice(0, 5)\n\n if (lines.length === 0) return ','\n\n const commaScore = lines.reduce((accumulator, line) => accumulator + countFields(line, ','), 0)\n const semicolonScore = lines.reduce((accumulator, line) => accumulator + countFields(line, ';'), 0)\n\n return semicolonScore > commaScore ? ';' : ','\n}\n\nexport function parseCsvText(text: string, delimiter: ',' | ';'): string[][] {\n const normalized = stripUtf8Bom(text).replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n')\n const rows: string[][] = []\n let currentRow: string[] = []\n let currentValue = ''\n let inQuotes = false\n\n for (let index = 0; index < normalized.length; index += 1) {\n const character = normalized[index]\n\n if (character === '\"') {\n if (inQuotes && normalized[index + 1] === '\"') {\n currentValue += '\"'\n index += 1\n continue\n }\n inQuotes = !inQuotes\n continue\n }\n\n if (!inQuotes && character === delimiter) {\n currentRow.push(currentValue)\n currentValue = ''\n continue\n }\n\n if (!inQuotes && character === '\\n') {\n currentRow.push(currentValue)\n const isMeaningfulRow = currentRow.some((value) => value.trim().length > 0)\n if (isMeaningfulRow) {\n rows.push(currentRow)\n }\n currentRow = []\n currentValue = ''\n continue\n }\n\n currentValue += character\n }\n\n if (currentValue.length > 0 || currentRow.length > 0) {\n currentRow.push(currentValue)\n const isMeaningfulRow = currentRow.some((value) => value.trim().length > 0)\n if (isMeaningfulRow) {\n rows.push(currentRow)\n }\n }\n\n return rows\n}\n\nfunction normalizeHeaders(headers: string[]): string[] {\n return headers.map((header, index) => {\n const trimmed = header.trim()\n return trimmed.length > 0 ? trimmed : `Column ${index + 1}`\n })\n}\n\nfunction rowsToObjects(headers: string[], rows: string[][], maxRows: number): CsvPreviewRow[] {\n return rows.slice(0, maxRows).map((row) => {\n const record: CsvPreviewRow = {}\n headers.forEach((header, index) => {\n const value = row[index]\n record[header] = typeof value === 'string' && value.length > 0 ? value : null\n })\n return record\n })\n}\n\nasync function createCsvRowStream(input: AsyncIterable<CsvInputChunk>): Promise<{\n delimiter: ',' | ';'\n rows: AsyncIterable<string[]>\n}> {\n const iterator = input[Symbol.asyncIterator]()\n const decoder = new TextDecoder('utf-8')\n const prefixTexts: string[] = []\n let probeText = ''\n\n while (probeText.length < CSV_DELIMITER_PROBE_MAX_CHARS) {\n const next = await iterator.next()\n if (next.done) break\n\n const text = typeof next.value === 'string' ? next.value : decoder.decode(next.value, { stream: true })\n prefixTexts.push(text)\n probeText += text\n\n if (parseCsvText(probeText, ',').length >= CSV_DELIMITER_PROBE_ROWS) {\n break\n }\n }\n\n const delimiter = detectCsvDelimiter(probeText)\n\n async function* decodedChunks(): AsyncIterable<string> {\n for (const text of prefixTexts) {\n yield text\n }\n while (true) {\n const next = await iterator.next()\n if (next.done) break\n yield typeof next.value === 'string' ? next.value : decoder.decode(next.value, { stream: true })\n }\n const finalChunk = decoder.decode()\n if (finalChunk.length > 0) {\n yield finalChunk\n }\n }\n\n return {\n delimiter,\n rows: parseCsvRowsFromTextChunks(decodedChunks(), delimiter),\n }\n}\n\nasync function* parseCsvRowsFromTextChunks(\n chunks: AsyncIterable<string>,\n delimiter: ',' | ';',\n): AsyncIterable<string[]> {\n let currentRow: string[] = []\n let currentValue = ''\n let inQuotes = false\n let isFirstChunk = true\n let previousWasCarriageReturn = false\n let pendingQuote = false\n let pendingQuoteWasInQuotes = false\n\n const completeRow = (): string[] | null => {\n currentRow.push(currentValue)\n const completedRow = currentRow\n currentRow = []\n currentValue = ''\n return completedRow.some((value) => value.trim().length > 0) ? completedRow : null\n }\n\n for await (const chunk of chunks) {\n const text = isFirstChunk ? stripUtf8Bom(chunk) : chunk\n isFirstChunk = false\n\n for (let index = 0; index < text.length; index += 1) {\n const character = text[index]\n\n if (pendingQuote) {\n pendingQuote = false\n if (pendingQuoteWasInQuotes && character === '\"') {\n currentValue += '\"'\n continue\n }\n inQuotes = !pendingQuoteWasInQuotes\n }\n\n if (previousWasCarriageReturn) {\n previousWasCarriageReturn = false\n if (character === '\\n') {\n continue\n }\n }\n\n if (character === '\"') {\n if (index + 1 >= text.length) {\n pendingQuote = true\n pendingQuoteWasInQuotes = inQuotes\n continue\n }\n if (inQuotes && text[index + 1] === '\"') {\n currentValue += '\"'\n index += 1\n continue\n }\n inQuotes = !inQuotes\n continue\n }\n\n if (!inQuotes && character === delimiter) {\n currentRow.push(currentValue)\n currentValue = ''\n continue\n }\n\n if (character === '\\r') {\n previousWasCarriageReturn = true\n if (inQuotes) {\n currentValue += '\\n'\n } else {\n const row = completeRow()\n if (row) yield row\n }\n continue\n }\n\n if (character === '\\n') {\n if (inQuotes) {\n currentValue += '\\n'\n } else {\n const row = completeRow()\n if (row) yield row\n }\n continue\n }\n\n currentValue += character\n }\n }\n\n if (currentValue.length > 0 || currentRow.length > 0) {\n const row = completeRow()\n if (row) yield row\n }\n}\n\nexport async function parseCsvStreamMetadata(input: AsyncIterable<CsvInputChunk>): Promise<CsvStreamMetadata> {\n const stream = await createCsvRowStream(input)\n let headers: string[] = []\n let hasHeaders = false\n let totalRows = 0\n\n for await (const row of stream.rows) {\n if (!hasHeaders) {\n headers = normalizeHeaders(row)\n hasHeaders = true\n continue\n }\n totalRows += 1\n }\n\n return {\n headers,\n totalRows,\n delimiter: stream.delimiter,\n encoding: 'utf-8',\n }\n}\n\nexport async function* parseCsvDocumentBatches(\n input: AsyncIterable<CsvInputChunk>,\n options: ParseCsvBatchOptions,\n): AsyncIterable<CsvDocumentBatch> {\n const stream = await createCsvRowStream(input)\n const batchSize = Math.max(1, Math.floor(options.batchSize))\n const startOffset = Math.max(0, Math.floor(options.startOffset ?? 0))\n let headers: string[] | null = null\n let rowOffset = 0\n let batchRows: string[][] = []\n let batchStart = startOffset\n\n for await (const row of stream.rows) {\n if (!headers) {\n headers = normalizeHeaders(row)\n continue\n }\n\n if (rowOffset < startOffset) {\n rowOffset += 1\n continue\n }\n\n if (batchRows.length === 0) {\n batchStart = rowOffset\n }\n batchRows.push(row)\n rowOffset += 1\n\n if (batchRows.length >= batchSize) {\n yield {\n headers,\n rows: rowsToObjects(headers, batchRows, batchRows.length),\n rowStart: batchStart,\n nextOffset: rowOffset,\n delimiter: stream.delimiter,\n encoding: 'utf-8',\n }\n batchRows = []\n }\n }\n\n if (headers && batchRows.length > 0) {\n yield {\n headers,\n rows: rowsToObjects(headers, batchRows, batchRows.length),\n rowStart: batchStart,\n nextOffset: rowOffset,\n delimiter: stream.delimiter,\n encoding: 'utf-8',\n }\n }\n}\n\nexport function parseCsvPreview(buffer: Buffer, options: ParseCsvPreviewOptions = {}): CsvPreview {\n const text = buffer.toString('utf-8')\n const delimiter = detectCsvDelimiter(text)\n const rows = parseCsvText(text, delimiter)\n const [rawHeaders = [], ...dataRows] = rows\n const headers = normalizeHeaders(rawHeaders)\n const maxRows = options.maxRows ?? 5\n\n return {\n headers,\n sampleRows: rowsToObjects(headers, dataRows, maxRows),\n totalRows: dataRows.length,\n delimiter,\n encoding: 'utf-8',\n }\n}\n\nexport function parseCsvDocument(buffer: Buffer): CsvDocument {\n const text = buffer.toString('utf-8')\n const delimiter = detectCsvDelimiter(text)\n const rows = parseCsvText(text, delimiter)\n const [rawHeaders = [], ...dataRows] = rows\n const headers = normalizeHeaders(rawHeaders)\n\n return {\n headers,\n rows: rowsToObjects(headers, dataRows, dataRows.length),\n totalRows: dataRows.length,\n delimiter,\n encoding: 'utf-8',\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }