@open-mercato/core 0.8.1-develop.7263.1.bbc6db3437 → 0.8.1-develop.7267.1.2e95af80a6

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 (145) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities.ids.generated.js +0 -4
  3. package/dist/generated/entities.ids.generated.js.map +2 -2
  4. package/dist/generated/entity-fields-registry.js +0 -21
  5. package/dist/generated/entity-fields-registry.js.map +2 -2
  6. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +5 -0
  7. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
  8. package/dist/modules/communication_channels/commands/ingest-inbound-message.js +13 -0
  9. package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +2 -2
  10. package/dist/modules/communication_channels/lib/email-mime.js +19 -1
  11. package/dist/modules/communication_channels/lib/email-mime.js.map +2 -2
  12. package/dist/modules/customers/lib/link-channel-message-handler.js +17 -7
  13. package/dist/modules/customers/lib/link-channel-message-handler.js.map +3 -3
  14. package/dist/modules/messages/api/route.js +1 -0
  15. package/dist/modules/messages/api/route.js.map +2 -2
  16. package/dist/modules/messages/commands/messages.js +3 -1
  17. package/dist/modules/messages/commands/messages.js.map +2 -2
  18. package/dist/modules/messages/data/validators.js +10 -2
  19. package/dist/modules/messages/data/validators.js.map +2 -2
  20. package/dist/modules/wms/di.js +0 -3
  21. package/dist/modules/wms/di.js.map +2 -2
  22. package/generated/entities.ids.generated.ts +0 -4
  23. package/generated/entity-fields-registry.ts +0 -21
  24. package/package.json +7 -7
  25. package/src/modules/auth/i18n/de.json +0 -3
  26. package/src/modules/auth/i18n/en.json +0 -3
  27. package/src/modules/auth/i18n/es.json +0 -3
  28. package/src/modules/auth/i18n/ko.json +0 -3
  29. package/src/modules/auth/i18n/pl.json +0 -3
  30. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +8 -0
  31. package/src/modules/communication_channels/commands/ingest-inbound-message.ts +16 -0
  32. package/src/modules/communication_channels/lib/email-mime.ts +52 -2
  33. package/src/modules/customers/lib/link-channel-message-handler.ts +48 -5
  34. package/src/modules/messages/api/route.ts +6 -3
  35. package/src/modules/messages/commands/messages.ts +3 -1
  36. package/src/modules/messages/data/validators.ts +21 -12
  37. package/src/modules/wms/di.ts +0 -6
  38. package/dist/generated/entities/availability_policy/index.js +0 -41
  39. package/dist/generated/entities/availability_policy/index.js.map +0 -7
  40. package/dist/modules/availability/acl.js +0 -16
  41. package/dist/modules/availability/acl.js.map +0 -7
  42. package/dist/modules/availability/api/check/route.js +0 -95
  43. package/dist/modules/availability/api/check/route.js.map +0 -7
  44. package/dist/modules/availability/api/openapi.js +0 -27
  45. package/dist/modules/availability/api/openapi.js.map +0 -7
  46. package/dist/modules/availability/api/policies/resolve-preview/route.js +0 -58
  47. package/dist/modules/availability/api/policies/resolve-preview/route.js.map +0 -7
  48. package/dist/modules/availability/api/policies/route.js +0 -188
  49. package/dist/modules/availability/api/policies/route.js.map +0 -7
  50. package/dist/modules/availability/backend/availability/check/page.js +0 -185
  51. package/dist/modules/availability/backend/availability/check/page.js.map +0 -7
  52. package/dist/modules/availability/backend/availability/check/page.meta.js +0 -16
  53. package/dist/modules/availability/backend/availability/check/page.meta.js.map +0 -7
  54. package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js +0 -79
  55. package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js.map +0 -7
  56. package/dist/modules/availability/backend/availability/policies/ScopeFields.js +0 -53
  57. package/dist/modules/availability/backend/availability/policies/ScopeFields.js.map +0 -7
  58. package/dist/modules/availability/backend/availability/policies/[id]/page.js +0 -152
  59. package/dist/modules/availability/backend/availability/policies/[id]/page.js.map +0 -7
  60. package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js +0 -12
  61. package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js.map +0 -7
  62. package/dist/modules/availability/backend/availability/policies/create/page.js +0 -94
  63. package/dist/modules/availability/backend/availability/policies/create/page.js.map +0 -7
  64. package/dist/modules/availability/backend/availability/policies/create/page.meta.js +0 -12
  65. package/dist/modules/availability/backend/availability/policies/create/page.meta.js.map +0 -7
  66. package/dist/modules/availability/backend/availability/policies/formGroups.js +0 -75
  67. package/dist/modules/availability/backend/availability/policies/formGroups.js.map +0 -7
  68. package/dist/modules/availability/backend/availability/policies/page.js +0 -176
  69. package/dist/modules/availability/backend/availability/policies/page.js.map +0 -7
  70. package/dist/modules/availability/backend/availability/policies/page.meta.js +0 -16
  71. package/dist/modules/availability/backend/availability/policies/page.meta.js.map +0 -7
  72. package/dist/modules/availability/commands/policies.js +0 -312
  73. package/dist/modules/availability/commands/policies.js.map +0 -7
  74. package/dist/modules/availability/commands/scope.js +0 -19
  75. package/dist/modules/availability/commands/scope.js.map +0 -7
  76. package/dist/modules/availability/data/entities.js +0 -95
  77. package/dist/modules/availability/data/entities.js.map +0 -7
  78. package/dist/modules/availability/data/validators.js +0 -53
  79. package/dist/modules/availability/data/validators.js.map +0 -7
  80. package/dist/modules/availability/di.js +0 -41
  81. package/dist/modules/availability/di.js.map +0 -7
  82. package/dist/modules/availability/events.js +0 -18
  83. package/dist/modules/availability/events.js.map +0 -7
  84. package/dist/modules/availability/index.js +0 -15
  85. package/dist/modules/availability/index.js.map +0 -7
  86. package/dist/modules/availability/lib/policyResolution.js +0 -155
  87. package/dist/modules/availability/lib/policyResolution.js.map +0 -7
  88. package/dist/modules/availability/lib/tryResolve.js +0 -11
  89. package/dist/modules/availability/lib/tryResolve.js.map +0 -7
  90. package/dist/modules/availability/migrations/Migration20260922095844_availability.js +0 -16
  91. package/dist/modules/availability/migrations/Migration20260922095844_availability.js.map +0 -7
  92. package/dist/modules/availability/setup.js +0 -12
  93. package/dist/modules/availability/setup.js.map +0 -7
  94. package/dist/modules/wms/lib/availabilityCache.js +0 -85
  95. package/dist/modules/wms/lib/availabilityCache.js.map +0 -7
  96. package/dist/modules/wms/lib/availabilityCalculation.js +0 -178
  97. package/dist/modules/wms/lib/availabilityCalculation.js.map +0 -7
  98. package/dist/modules/wms/lib/availabilityProvider.js +0 -14
  99. package/dist/modules/wms/lib/availabilityProvider.js.map +0 -7
  100. package/dist/modules/wms/lib/sqlInClause.js +0 -9
  101. package/dist/modules/wms/lib/sqlInClause.js.map +0 -7
  102. package/dist/modules/wms/lib/tryResolve.js +0 -11
  103. package/dist/modules/wms/lib/tryResolve.js.map +0 -7
  104. package/dist/modules/wms/subscribers/invalidate-availability-cache.js +0 -16
  105. package/dist/modules/wms/subscribers/invalidate-availability-cache.js.map +0 -7
  106. package/generated/entities/availability_policy/index.ts +0 -19
  107. package/src/modules/availability/acl.ts +0 -12
  108. package/src/modules/availability/api/check/route.ts +0 -114
  109. package/src/modules/availability/api/openapi.ts +0 -28
  110. package/src/modules/availability/api/policies/resolve-preview/route.ts +0 -67
  111. package/src/modules/availability/api/policies/route.ts +0 -224
  112. package/src/modules/availability/backend/availability/check/page.meta.ts +0 -12
  113. package/src/modules/availability/backend/availability/check/page.tsx +0 -223
  114. package/src/modules/availability/backend/availability/policies/ResolutionPreviewPanel.tsx +0 -106
  115. package/src/modules/availability/backend/availability/policies/ScopeFields.tsx +0 -55
  116. package/src/modules/availability/backend/availability/policies/[id]/page.meta.ts +0 -8
  117. package/src/modules/availability/backend/availability/policies/[id]/page.tsx +0 -182
  118. package/src/modules/availability/backend/availability/policies/create/page.meta.ts +0 -8
  119. package/src/modules/availability/backend/availability/policies/create/page.tsx +0 -105
  120. package/src/modules/availability/backend/availability/policies/formGroups.ts +0 -77
  121. package/src/modules/availability/backend/availability/policies/page.meta.ts +0 -12
  122. package/src/modules/availability/backend/availability/policies/page.tsx +0 -196
  123. package/src/modules/availability/commands/policies.ts +0 -358
  124. package/src/modules/availability/commands/scope.ts +0 -26
  125. package/src/modules/availability/data/entities.ts +0 -92
  126. package/src/modules/availability/data/validators.ts +0 -70
  127. package/src/modules/availability/di.ts +0 -54
  128. package/src/modules/availability/events.ts +0 -17
  129. package/src/modules/availability/i18n/de.json +0 -81
  130. package/src/modules/availability/i18n/en.json +0 -81
  131. package/src/modules/availability/i18n/es.json +0 -81
  132. package/src/modules/availability/i18n/ko.json +0 -81
  133. package/src/modules/availability/i18n/pl.json +0 -81
  134. package/src/modules/availability/index.ts +0 -13
  135. package/src/modules/availability/lib/policyResolution.ts +0 -264
  136. package/src/modules/availability/lib/tryResolve.ts +0 -8
  137. package/src/modules/availability/migrations/.snapshot-open-mercato.json +0 -375
  138. package/src/modules/availability/migrations/Migration20260922095844_availability.ts +0 -13
  139. package/src/modules/availability/setup.ts +0 -10
  140. package/src/modules/wms/lib/availabilityCache.ts +0 -136
  141. package/src/modules/wms/lib/availabilityCalculation.ts +0 -289
  142. package/src/modules/wms/lib/availabilityProvider.ts +0 -29
  143. package/src/modules/wms/lib/sqlInClause.ts +0 -21
  144. package/src/modules/wms/lib/tryResolve.ts +0 -8
  145. package/src/modules/wms/subscribers/invalidate-availability-cache.ts +0 -30
@@ -62,9 +62,6 @@
62
62
  "auth.acl.features.auth.users.delete": "Delete users",
63
63
  "auth.acl.features.auth.users.edit": "Edit users",
64
64
  "auth.acl.features.auth.users.list": "List users",
65
- "auth.acl.features.availability.check": "Run availability checks",
66
- "auth.acl.features.availability.policies.manage": "Manage availability policies",
67
- "auth.acl.features.availability.policies.view": "View availability policies",
68
65
  "auth.acl.features.business_rules.execute": "Execute business rules manually",
69
66
  "auth.acl.features.business_rules.manage": "Create, edit, and delete business rules",
70
67
  "auth.acl.features.business_rules.manage_sets": "Manage rule sets",
@@ -62,9 +62,6 @@
62
62
  "auth.acl.features.auth.users.delete": "Delete users",
63
63
  "auth.acl.features.auth.users.edit": "Edit users",
64
64
  "auth.acl.features.auth.users.list": "List users",
65
- "auth.acl.features.availability.check": "Run availability checks",
66
- "auth.acl.features.availability.policies.manage": "Manage availability policies",
67
- "auth.acl.features.availability.policies.view": "View availability policies",
68
65
  "auth.acl.features.business_rules.execute": "Execute business rules manually",
69
66
  "auth.acl.features.business_rules.manage": "Create, edit, and delete business rules",
70
67
  "auth.acl.features.business_rules.manage_sets": "Manage rule sets",
@@ -62,9 +62,6 @@
62
62
  "auth.acl.features.auth.users.delete": "Delete users",
63
63
  "auth.acl.features.auth.users.edit": "Edit users",
64
64
  "auth.acl.features.auth.users.list": "List users",
65
- "auth.acl.features.availability.check": "Run availability checks",
66
- "auth.acl.features.availability.policies.manage": "Manage availability policies",
67
- "auth.acl.features.availability.policies.view": "View availability policies",
68
65
  "auth.acl.features.business_rules.execute": "Execute business rules manually",
69
66
  "auth.acl.features.business_rules.manage": "Create, edit, and delete business rules",
70
67
  "auth.acl.features.business_rules.manage_sets": "Manage rule sets",
@@ -62,9 +62,6 @@
62
62
  "auth.acl.features.auth.users.delete": "Delete users",
63
63
  "auth.acl.features.auth.users.edit": "Edit users",
64
64
  "auth.acl.features.auth.users.list": "List users",
65
- "auth.acl.features.availability.check": "Run availability checks",
66
- "auth.acl.features.availability.policies.manage": "Manage availability policies",
67
- "auth.acl.features.availability.policies.view": "View availability policies",
68
65
  "auth.acl.features.business_rules.execute": "Execute business rules manually",
69
66
  "auth.acl.features.business_rules.manage": "Create, edit, and delete business rules",
70
67
  "auth.acl.features.business_rules.manage_sets": "Manage rule sets",
@@ -62,9 +62,6 @@
62
62
  "auth.acl.features.auth.users.delete": "Usuwaj użytkowników",
63
63
  "auth.acl.features.auth.users.edit": "Edytuj użytkowników",
64
64
  "auth.acl.features.auth.users.list": "Wyświetl listę użytkowników",
65
- "auth.acl.features.availability.check": "Uruchamiaj sprawdzenia dostępności",
66
- "auth.acl.features.availability.policies.manage": "Zarządzaj politykami dostępności",
67
- "auth.acl.features.availability.policies.view": "Wyświetl polityki dostępności",
68
65
  "auth.acl.features.business_rules.execute": "Uruchamiaj reguły biznesowe ręcznie",
69
66
  "auth.acl.features.business_rules.manage": "Twórz, edytuj i usuwaj reguły biznesowe",
70
67
  "auth.acl.features.business_rules.manage_sets": "Zarządzaj zestawami reguł",
@@ -530,6 +530,14 @@ const deliverOutboundMessageCommand: CommandHandler<
530
530
  providerKey: channel.providerKey,
531
531
  channelType: channel.channelType,
532
532
  direction: 'outbound',
533
+ // #6095: same contract as the inbound path — the send time travels on
534
+ // the event so consumers (the customers timeline) date their rows
535
+ // from it instead of reading the ExternalMessage row across the
536
+ // module boundary. ISO string because this event is persistent.
537
+ providerTimestamp:
538
+ externalMessage.providerTimestamp instanceof Date
539
+ ? externalMessage.providerTimestamp.toISOString()
540
+ : null,
533
541
  tenantId: input.scope.tenantId,
534
542
  organizationId: input.scope.organizationId ?? null,
535
543
  },
@@ -417,6 +417,12 @@ const ingestInboundMessageCommand: CommandHandler<IngestInboundMessageInput, Ing
417
417
  // when the matcher returned null (no token / JWZ / subject hit).
418
418
  parentMessageId: threadMatch?.messageThreadId ?? mapping?.messageThreadId,
419
419
  isDraft: false,
420
+ // #6095: the platform message is dated when the provider received the
421
+ // mail, not when this worker ran. Otherwise a history import lands every
422
+ // message on the import minute and the inbox (sorted on `sent_at`) shows
423
+ // a 90-day mailbox as one block in import order. Adapters without a
424
+ // timestamp fall through to compose's own `new Date()`.
425
+ sentAt: m.timestamp ?? undefined,
420
426
  // Stable dedup key so a retried ingest (after a transient failure between
421
427
  // compose and the ExternalMessage anchor insert) reuses the message
422
428
  // composed by the first attempt instead of duplicating it. Mirrors the
@@ -576,6 +582,16 @@ const ingestInboundMessageCommand: CommandHandler<IngestInboundMessageInput, Ing
576
582
  providerKey: input.providerKey,
577
583
  channelType: input.channelType,
578
584
  direction: 'inbound',
585
+ // #6095: subscribers that date their own rows from this message (the
586
+ // customers timeline) need the provider's receive time, not the moment
587
+ // this worker ran. Carried here so no consumer has to read the
588
+ // ExternalMessage row across the module boundary. ISO string because a
589
+ // persistent event is serialized onto the queue; null when the adapter
590
+ // supplied no timestamp, which leaves the consumer's own fallback.
591
+ providerTimestamp:
592
+ externalMessage.providerTimestamp instanceof Date
593
+ ? externalMessage.providerTimestamp.toISOString()
594
+ : null,
579
595
  tenantId: input.scope.tenantId,
580
596
  organizationId: input.scope.organizationId ?? null,
581
597
  },
@@ -414,7 +414,19 @@ export interface NormalizeMimeInboundOptions {
414
414
  fallbackMessageId: string
415
415
  /** Compute the conversation grouping id from the resolved message id + references. */
416
416
  resolveConversationId: (context: { messageId: string; references: string[] }) => string
417
- /** Fallback timestamp when the parsed message has no Date header. */
417
+ /**
418
+ * When the provider received the message (Gmail `internalDate`, IMAP
419
+ * `INTERNALDATE`). Preferred over the MIME `Date` header for the message
420
+ * timestamp: the header is written by the sender's client and can be forged
421
+ * or skewed, and since #6095 the timestamp also dates the platform message
422
+ * and the CRM interaction, so a bad header would pin a message in the future
423
+ * or hide it in the past.
424
+ */
425
+ receivedAt?: Date
426
+ /**
427
+ * Fallback timestamp when neither `receivedAt` nor a usable Date header is
428
+ * available.
429
+ */
418
430
  fallbackDate?: Date
419
431
  /** Provider-specific fields merged into `channelMetadata`. */
420
432
  channelMetadata?: (parsed: ParsedMail) => Record<string, unknown>
@@ -422,6 +434,40 @@ export interface NormalizeMimeInboundOptions {
422
434
  channelPayload?: (parsed: ParsedMail) => Record<string, unknown>
423
435
  }
424
436
 
437
+ /**
438
+ * A MIME `Date` header this far ahead of the clock is treated as bogus. Clock
439
+ * skew between real mail servers is seconds to minutes; a day of tolerance
440
+ * keeps legitimate mail while stopping a forged header from pinning a message
441
+ * to the top of a `sent_at`-sorted inbox for weeks.
442
+ */
443
+ const MAX_MIME_DATE_FUTURE_SKEW_MS = 24 * 60 * 60 * 1000
444
+
445
+ function isValidDate(value: unknown): value is Date {
446
+ return value instanceof Date && !Number.isNaN(value.getTime())
447
+ }
448
+
449
+ /**
450
+ * Pick the timestamp for an inbound message.
451
+ *
452
+ * Order: the provider's receipt time; then the sender's MIME `Date` header,
453
+ * unless it is unparsable or more than a day in the future; then the caller's
454
+ * fallback; then now. Exported for tests.
455
+ */
456
+ export function resolveInboundTimestamp(input: {
457
+ receivedAt?: Date
458
+ mimeDate?: Date
459
+ fallbackDate?: Date
460
+ now?: Date
461
+ }): Date {
462
+ if (isValidDate(input.receivedAt)) return input.receivedAt
463
+ const now = input.now ?? new Date()
464
+ if (isValidDate(input.mimeDate) && input.mimeDate.getTime() <= now.getTime() + MAX_MIME_DATE_FUTURE_SKEW_MS) {
465
+ return input.mimeDate
466
+ }
467
+ if (isValidDate(input.fallbackDate)) return input.fallbackDate
468
+ return now
469
+ }
470
+
425
471
  /**
426
472
  * Build the hub's canonical `NormalizedInboundMessage` from a parsed MIME
427
473
  * message. Providers supply the bits that genuinely differ (message-id
@@ -474,7 +520,11 @@ export function normalizeMimeInbound(options: NormalizeMimeInboundOptions): Norm
474
520
  body,
475
521
  bodyFormat,
476
522
  attachments,
477
- timestamp: parsed.date ? new Date(parsed.date) : options.fallbackDate ?? new Date(),
523
+ timestamp: resolveInboundTimestamp({
524
+ receivedAt: options.receivedAt,
525
+ mimeDate: parsed.date ? new Date(parsed.date) : undefined,
526
+ fallbackDate: options.fallbackDate,
527
+ }),
478
528
  replyToExternalId: inReplyTo ?? undefined,
479
529
  channelPayload,
480
530
  channelContentType: 'email/mime',
@@ -34,6 +34,13 @@ type LinkChannelMessagePayload = {
34
34
  organizationId?: string | null
35
35
  providerKey?: string | null
36
36
  direction?: 'inbound' | 'outbound' | null
37
+ /**
38
+ * The provider's own receive/send time, carried by the hub on the event
39
+ * (#6095). An ISO string when the event rode the queue, a Date for an
40
+ * in-process emit. Absent on events enqueued before the field existed and on
41
+ * adapters that supply no timestamp.
42
+ */
43
+ providerTimestamp?: string | Date | null
37
44
  }
38
45
 
39
46
  type SubscriberContext = {
@@ -88,6 +95,17 @@ export default async function handler(
88
95
  const metaJson = (link.channelMetadata ?? null) as Record<string, unknown> | null
89
96
  const payloadJson = (link.channelPayload ?? null) as Record<string, unknown> | null
90
97
 
98
+ // ── (1b) Resolve when the email actually happened ─────────────────────
99
+ //
100
+ // `link.createdAt` is when the hub ingested the message, which for a history
101
+ // import is the import minute, not the day the mail arrived (#6095). The hub
102
+ // carries the provider's own timestamp on the event payload, so take it from
103
+ // there: reading the communication_channels ExternalMessage row here would
104
+ // cross the storage boundary (Cross-Module Coupling in AGENTS.md) and add a
105
+ // peer-table query per event. Resolved once and threaded through every branch
106
+ // below so the address-match and threading-inheritance paths agree.
107
+ const occurredAt = resolveOccurredAt(payload, link)
108
+
91
109
  // ── (2) Resolve the channel to get its owner userId ───────────────────
92
110
  //
93
111
  // The channel.userId is needed for two purposes:
@@ -172,7 +190,7 @@ export default async function handler(
172
190
  // Early exit: no addresses AND no hint → nothing to link.
173
191
  if (normalized.length === 0 && !crmPersonId) {
174
192
  // Before giving up, try threading-inheritance (TC-CRM-EMAIL-005).
175
- await handleThreadingInheritance(em, link, linkId, tenantId, organizationId, channelUserId, metaJson, payloadJson)
193
+ await handleThreadingInheritance(em, link, linkId, tenantId, organizationId, channelUserId, metaJson, payloadJson, occurredAt)
176
194
  return
177
195
  }
178
196
 
@@ -183,7 +201,7 @@ export default async function handler(
183
201
 
184
202
  if (personIdSet.size === 0) {
185
203
  // Try threading-inheritance before giving up.
186
- await handleThreadingInheritance(em, link, linkId, tenantId, organizationId, channelUserId, metaJson, payloadJson)
204
+ await handleThreadingInheritance(em, link, linkId, tenantId, organizationId, channelUserId, metaJson, payloadJson, occurredAt)
187
205
  return
188
206
  }
189
207
 
@@ -211,7 +229,6 @@ export default async function handler(
211
229
  ? (payloadJson!.text as string)
212
230
  : null
213
231
 
214
- const occurredAt = link.createdAt instanceof Date ? link.createdAt : new Date()
215
232
  const providerKey =
216
233
  typeof link.providerKey === 'string' ? (link.providerKey as string) : null
217
234
 
@@ -257,6 +274,7 @@ async function handleThreadingInheritance(
257
274
  channelUserId: string | null,
258
275
  metaJson: Record<string, unknown> | null,
259
276
  payloadJson: Record<string, unknown> | null,
277
+ occurredAt: Date,
260
278
  ): Promise<void> {
261
279
  // ── Primary: inherit Person(s) from the hub's authoritative thread ──────
262
280
  //
@@ -325,7 +343,6 @@ async function handleThreadingInheritance(
325
343
  : typeof metaJson?.bodyText === 'string'
326
344
  ? (metaJson.bodyText as string)
327
345
  : null
328
- const occurredAt = _link.createdAt instanceof Date ? (_link.createdAt as Date) : new Date()
329
346
  const providerKey = typeof _link.providerKey === 'string' ? (_link.providerKey as string) : null
330
347
  await persistInteractions(em, threadPersonIds, {
331
348
  linkId,
@@ -420,7 +437,6 @@ async function handleThreadingInheritance(
420
437
  const inheritedMeta = (link.channelMetadata ?? null) as Record<string, unknown> | null
421
438
  const subject =
422
439
  typeof inheritedMeta?.subject === 'string' ? (inheritedMeta.subject as string) : null
423
- const occurredAt = link.createdAt instanceof Date ? (link.createdAt as Date) : new Date()
424
440
  const providerKey =
425
441
  typeof link.providerKey === 'string' ? (link.providerKey as string) : null
426
442
 
@@ -444,6 +460,33 @@ async function handleThreadingInheritance(
444
460
 
445
461
  // ── Helpers ───────────────────────────────────────────────────────────────
446
462
 
463
+ /**
464
+ * When the linked email happened, for `CustomerInteraction.occurredAt` (#6095).
465
+ *
466
+ * Preference order:
467
+ * 1. `payload.providerTimestamp` — the provider's own receive/send time, put
468
+ * on the hub event by ingest / delivery;
469
+ * 2. `MessageChannelLink.createdAt` — the ingest time, the only date this
470
+ * handler knew before #6095, and the compatibility path for events that
471
+ * were enqueued before the field existed;
472
+ * 3. now, for a link row with no usable `createdAt` (test stubs).
473
+ */
474
+ function resolveOccurredAt(
475
+ payload: LinkChannelMessagePayload,
476
+ link: Record<string, unknown>,
477
+ ): Date {
478
+ const ingestedAt = link.createdAt instanceof Date ? link.createdAt : new Date()
479
+ const carried = payload.providerTimestamp
480
+ if (carried instanceof Date) {
481
+ return Number.isNaN(carried.getTime()) ? ingestedAt : carried
482
+ }
483
+ if (typeof carried === 'string' && carried) {
484
+ const parsed = new Date(carried)
485
+ if (!Number.isNaN(parsed.getTime())) return parsed
486
+ }
487
+ return ingestedAt
488
+ }
489
+
447
490
  interface InteractionData {
448
491
  linkId: string
449
492
  tenantId: string
@@ -470,12 +470,15 @@ export async function POST(req: Request) {
470
470
  sourceHint.data,
471
471
  )
472
472
  : undefined
473
- // `inboundFromChannel` is likewise server-only (#6093): channel ingest sets
474
- // it so an inbound message can be addressed to the conversation's assignee,
475
- // and a request body must not be able to waive the recipients rule with it.
473
+ // `inboundFromChannel` and `sentAt` are likewise server-only (#6093, #6095):
474
+ // channel ingest sets them so an inbound message can be addressed to the
475
+ // conversation's assignee and stamped with the provider's receive time, but
476
+ // a request body must not be able to waive the recipients rule or backdate
477
+ // a message with them.
476
478
  const {
477
479
  sourceChannelType: _clientSuppliedChannelType,
478
480
  inboundFromChannel: _clientSuppliedInboundFlag,
481
+ sentAt: _clientSuppliedSentAt,
479
482
  ...clientBody
480
483
  } = (body ?? {}) as Record<string, unknown>
481
484
  const input = composeMessageSchema.parse({
@@ -333,7 +333,9 @@ const composeMessageCommand: CommandHandler<unknown, { id: string; threadId: str
333
333
  priority: input.priority,
334
334
  status: input.isDraft ? 'draft' : 'sent',
335
335
  isDraft: input.isDraft ?? false,
336
- sentAt: input.isDraft ? null : new Date(),
336
+ // #6095: channel ingest passes the provider's timestamp so an imported
337
+ // mailbox sorts by when the mail was received, not when it was imported.
338
+ sentAt: input.isDraft ? null : input.sentAt ?? new Date(),
337
339
  actionData: input.actionData as MessageActionData | undefined,
338
340
  sendViaEmail,
339
341
  idempotencyKey: input.idempotencyKey ?? null,
@@ -138,12 +138,20 @@ const composeMessageBaseSchema = z.object({
138
138
  sendViaEmail: z.boolean().optional().default(false),
139
139
  parentMessageId: z.string().uuid().optional(),
140
140
  isDraft: z.boolean().optional().default(false),
141
+ /**
142
+ * When the message was actually sent, if the caller knows better than "now"
143
+ * (#6095). Channel ingest sets it to the provider's timestamp so an imported
144
+ * mailbox keeps its real chronology instead of collapsing onto the import
145
+ * minute. Server-only: the HTTP route strips any client-sent value, so a
146
+ * browser caller cannot backdate a message. Ignored for drafts.
147
+ */
148
+ sentAt: z.coerce.date().optional(),
141
149
  })
142
150
 
143
151
  type ComposeMessageRefinementValue = Omit<
144
152
  z.infer<typeof composeMessageBaseSchema>,
145
- 'sourceChannelType'
146
- > & { sourceChannelType?: string }
153
+ 'sourceChannelType' | 'sentAt'
154
+ > & { sourceChannelType?: string; sentAt?: Date }
147
155
 
148
156
  function refineComposeMessage(value: ComposeMessageRefinementValue, ctx: z.RefinementCtx): void {
149
157
  const isDraft = value.isDraft ?? false
@@ -213,23 +221,24 @@ function refineComposeMessage(value: ComposeMessageRefinementValue, ctx: z.Refin
213
221
  }
214
222
 
215
223
  /**
216
- * Full compose contract, including the server-resolved `sourceChannelType` and
217
- * the ingest-only `inboundFromChannel`. Used by the `messages.messages.compose`
218
- * command and by the HTTP route AFTER it has resolved the channel type itself.
224
+ * Full compose contract, including the server-resolved `sourceChannelType`,
225
+ * the ingest-only `inboundFromChannel`, and the server-supplied `sentAt`. Used
226
+ * by the `messages.messages.compose` command and by the HTTP route AFTER it
227
+ * has resolved the channel type itself.
219
228
  */
220
229
  export const composeMessageSchema = composeMessageBaseSchema.superRefine(refineComposeMessage)
221
230
 
222
231
  /**
223
232
  * Client-facing compose contract — the same rules minus the server-only fields:
224
- * `sourceChannelType` is never accepted from a request body (#4975) and neither
225
- * is `inboundFromChannel` (#6093). Published in OpenAPI so the documented
226
- * request shape matches what `POST /api/messages` actually reads: the route
227
- * discards any client-sent value for these, resolves the real channel type from
228
- * the referenced conversation or parent message, and only then validates
229
- * against {@link composeMessageSchema}.
233
+ * `sourceChannelType` is never accepted from a request body (#4975), neither is
234
+ * `inboundFromChannel` (#6093) nor `sentAt` (#6095). Published in OpenAPI so the
235
+ * documented request shape matches what `POST /api/messages` actually reads:
236
+ * the route discards any client-sent value for these, resolves the real
237
+ * channel type from the referenced conversation or parent message, and only
238
+ * then validates against {@link composeMessageSchema}.
230
239
  */
231
240
  export const composeMessageRequestSchema = composeMessageBaseSchema
232
- .omit({ sourceChannelType: true, inboundFromChannel: true })
241
+ .omit({ sourceChannelType: true, inboundFromChannel: true, sentAt: true })
233
242
  .superRefine(refineComposeMessage)
234
243
 
235
244
  export const updateDraftSchema = z.object({
@@ -1,6 +1,5 @@
1
1
  import { asValue } from 'awilix'
2
2
  import type { AppContainer } from '@open-mercato/shared/lib/di/container'
3
- import { availabilityProviderRegistry } from '@open-mercato/shared/lib/availability'
4
3
  import {
5
4
  InventoryBalance,
6
5
  InventoryLot,
@@ -11,7 +10,6 @@ import {
11
10
  WarehouseLocation,
12
11
  WarehouseZone,
13
12
  } from './data/entities'
14
- import { createWmsAvailabilityProvider } from './lib/availabilityProvider'
15
13
 
16
14
  export function register(container: AppContainer) {
17
15
  container.register({
@@ -24,8 +22,4 @@ export function register(container: AppContainer) {
24
22
  InventoryReservation: asValue(InventoryReservation),
25
23
  InventoryMovement: asValue(InventoryMovement),
26
24
  })
27
-
28
- // Explicit, idempotent (replace-by-id) registration into the shared,
29
- // dependency-free registry — never "same DI key, load order wins" (§3.1).
30
- availabilityProviderRegistry.register(createWmsAvailabilityProvider(container))
31
25
  }
@@ -1,41 +0,0 @@
1
- const id = "id";
2
- const organization_id = "organization_id";
3
- const tenant_id = "tenant_id";
4
- const store_id = "store_id";
5
- const product_id = "product_id";
6
- const variant_id = "variant_id";
7
- const is_stock_managed = "is_stock_managed";
8
- const allow_backorder = "allow_backorder";
9
- const backorder_lead_time_days = "backorder_lead_time_days";
10
- const preorder_release_at = "preorder_release_at";
11
- const low_stock_threshold = "low_stock_threshold";
12
- const min_order_quantity = "min_order_quantity";
13
- const max_order_quantity = "max_order_quantity";
14
- const quantity_increment = "quantity_increment";
15
- const hide_when_out_of_stock = "hide_when_out_of_stock";
16
- const is_active = "is_active";
17
- const created_at = "created_at";
18
- const updated_at = "updated_at";
19
- const deleted_at = "deleted_at";
20
- export {
21
- allow_backorder,
22
- backorder_lead_time_days,
23
- created_at,
24
- deleted_at,
25
- hide_when_out_of_stock,
26
- id,
27
- is_active,
28
- is_stock_managed,
29
- low_stock_threshold,
30
- max_order_quantity,
31
- min_order_quantity,
32
- organization_id,
33
- preorder_release_at,
34
- product_id,
35
- quantity_increment,
36
- store_id,
37
- tenant_id,
38
- updated_at,
39
- variant_id
40
- };
41
- //# sourceMappingURL=index.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../generated/entities/availability_policy/index.ts"],
4
- "sourcesContent": ["export const id = \"id\";\nexport const organization_id = \"organization_id\";\nexport const tenant_id = \"tenant_id\";\nexport const store_id = \"store_id\";\nexport const product_id = \"product_id\";\nexport const variant_id = \"variant_id\";\nexport const is_stock_managed = \"is_stock_managed\";\nexport const allow_backorder = \"allow_backorder\";\nexport const backorder_lead_time_days = \"backorder_lead_time_days\";\nexport const preorder_release_at = \"preorder_release_at\";\nexport const low_stock_threshold = \"low_stock_threshold\";\nexport const min_order_quantity = \"min_order_quantity\";\nexport const max_order_quantity = \"max_order_quantity\";\nexport const quantity_increment = \"quantity_increment\";\nexport const hide_when_out_of_stock = \"hide_when_out_of_stock\";\nexport const is_active = \"is_active\";\nexport const created_at = \"created_at\";\nexport const updated_at = \"updated_at\";\nexport const deleted_at = \"deleted_at\";\n"],
5
- "mappings": "AAAO,MAAM,KAAK;AACX,MAAM,kBAAkB;AACxB,MAAM,YAAY;AAClB,MAAM,WAAW;AACjB,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,mBAAmB;AACzB,MAAM,kBAAkB;AACxB,MAAM,2BAA2B;AACjC,MAAM,sBAAsB;AAC5B,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAC3B,MAAM,yBAAyB;AAC/B,MAAM,YAAY;AAClB,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,aAAa;",
6
- "names": []
7
- }
@@ -1,16 +0,0 @@
1
- const features = [
2
- { id: "availability.policies.view", title: "View availability policies", module: "availability" },
3
- {
4
- id: "availability.policies.manage",
5
- title: "Manage availability policies",
6
- module: "availability",
7
- dependsOn: ["availability.policies.view"]
8
- },
9
- { id: "availability.check", title: "Run availability checks", module: "availability" }
10
- ];
11
- var acl_default = features;
12
- export {
13
- acl_default as default,
14
- features
15
- };
16
- //# sourceMappingURL=acl.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/modules/availability/acl.ts"],
4
- "sourcesContent": ["export const features = [\n { id: 'availability.policies.view', title: 'View availability policies', module: 'availability' },\n {\n id: 'availability.policies.manage',\n title: 'Manage availability policies',\n module: 'availability',\n dependsOn: ['availability.policies.view'],\n },\n { id: 'availability.check', title: 'Run availability checks', module: 'availability' },\n]\n\nexport default features\n"],
5
- "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,8BAA8B,OAAO,8BAA8B,QAAQ,eAAe;AAAA,EAChG;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,4BAA4B;AAAA,EAC1C;AAAA,EACA,EAAE,IAAI,sBAAsB,OAAO,2BAA2B,QAAQ,eAAe;AACvF;AAEA,IAAO,cAAQ;",
6
- "names": []
7
- }
@@ -1,95 +0,0 @@
1
- import { NextResponse } from "next/server";
2
- import { z } from "zod";
3
- import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
4
- import { resolveActiveOrganizationId, organizationScopeRequiredResponse } from "@open-mercato/shared/lib/auth/organizationScope";
5
- import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
6
- import { resolveAvailability, availabilityItemKey } from "@open-mercato/shared/lib/availability";
7
- import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
8
- import { tryResolve } from "../../lib/tryResolve.js";
9
- const routeMetadata = {
10
- POST: { requireAuth: true, requireFeatures: ["availability.check"] }
11
- };
12
- const metadata = routeMetadata;
13
- const openApi = {
14
- tags: ["Availability"],
15
- summary: "Reproduce the availability state and policy trace a buyer would see for one item.",
16
- description: "Admin/debug tool. Mirrors resolveAvailability() and includes the per-field policySourceId trace."
17
- };
18
- const checkSchema = z.object({
19
- productId: z.uuid(),
20
- variantId: z.uuid().nullable().optional(),
21
- quantity: z.coerce.number().int().min(1).default(1),
22
- storeId: z.uuid().nullable().optional()
23
- });
24
- async function POST(req) {
25
- const { translate } = await resolveTranslations();
26
- const auth = await getAuthFromRequest(req);
27
- if (!auth || !auth.tenantId) {
28
- return NextResponse.json({ error: translate("availability.errors.unauthorized", "Unauthorized") }, { status: 401 });
29
- }
30
- const organizationId = resolveActiveOrganizationId(auth);
31
- if (!organizationId) return organizationScopeRequiredResponse();
32
- const body = await req.json().catch(() => ({}));
33
- const parsed = checkSchema.safeParse(body);
34
- if (!parsed.success) {
35
- return NextResponse.json(
36
- { error: translate("availability.errors.invalidCheckRequest", "Invalid request") },
37
- { status: 400 }
38
- );
39
- }
40
- const { productId, variantId, quantity, storeId } = parsed.data;
41
- const tenantId = auth.tenantId;
42
- const container = await createRequestContainer();
43
- const em = container.resolve("em");
44
- const CatalogProduct = tryResolve(container, "CatalogProduct");
45
- if (CatalogProduct) {
46
- const product = await em.findOne(CatalogProduct, { id: productId, organizationId, tenantId, deletedAt: null });
47
- if (!product) {
48
- return NextResponse.json(
49
- { error: translate("availability.errors.productNotFound", "No such product") },
50
- { status: 404 }
51
- );
52
- }
53
- if (variantId) {
54
- const CatalogProductVariant = tryResolve(container, "CatalogProductVariant");
55
- if (CatalogProductVariant) {
56
- const variant = await em.findOne(CatalogProductVariant, {
57
- id: variantId,
58
- organizationId,
59
- tenantId,
60
- deletedAt: null
61
- });
62
- if (!variant) {
63
- return NextResponse.json(
64
- { error: translate("availability.errors.variantNotFound", "No such variant") },
65
- { status: 404 }
66
- );
67
- }
68
- }
69
- }
70
- }
71
- const query = {
72
- tenantId,
73
- organizationId,
74
- storeId: storeId ?? null,
75
- items: [{ catalogProductId: productId, catalogVariantId: variantId ?? null, quantity }]
76
- };
77
- const moduleConfig = tryResolve(container, "moduleConfigService");
78
- const result = await resolveAvailability(query, moduleConfig ? { moduleConfig } : void 0);
79
- const item = result.byItem[availabilityItemKey({ catalogProductId: productId, catalogVariantId: variantId ?? null })] ?? null;
80
- const policyResolutionService = tryResolve(container, "policyResolutionService");
81
- const policyTrace = policyResolutionService ? await policyResolutionService.resolve(em, {
82
- tenantId,
83
- organizationId,
84
- storeId: storeId ?? null,
85
- productId,
86
- variantId: variantId ?? null
87
- }) : null;
88
- return NextResponse.json({ availability: item, policyTrace });
89
- }
90
- export {
91
- POST,
92
- metadata,
93
- openApi
94
- };
95
- //# sourceMappingURL=route.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../../src/modules/availability/api/check/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveActiveOrganizationId, organizationScopeRequiredResponse } from '@open-mercato/shared/lib/auth/organizationScope'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveAvailability, availabilityItemKey } from '@open-mercato/shared/lib/availability'\nimport type { AvailabilityModuleConfigReader, AvailabilityQuery } from '@open-mercato/shared/lib/availability'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { tryResolve } from '../../lib/tryResolve'\nimport type { PolicyResolutionService } from '../../lib/policyResolution'\n\n/**\n * `POST /api/availability/check` \u2014 admin/debug reproduction of what a buyer saw,\n * including the per-field `policySourceId` trace. No public storefront endpoints\n * ship in this module (\u00A78).\n */\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['availability.check'] },\n}\n\nexport const metadata = routeMetadata\n\nexport const openApi = {\n tags: ['Availability'],\n summary: 'Reproduce the availability state and policy trace a buyer would see for one item.',\n description: 'Admin/debug tool. Mirrors resolveAvailability() and includes the per-field policySourceId trace.',\n}\n\nconst checkSchema = z.object({\n productId: z.uuid(),\n variantId: z.uuid().nullable().optional(),\n quantity: z.coerce.number().int().min(1).default(1),\n storeId: z.uuid().nullable().optional(),\n})\n\nexport async function POST(req: Request) {\n const { translate } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: translate('availability.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const organizationId = resolveActiveOrganizationId(auth)\n if (!organizationId) return organizationScopeRequiredResponse()\n\n const body = await req.json().catch(() => ({}))\n const parsed = checkSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json(\n { error: translate('availability.errors.invalidCheckRequest', 'Invalid request') },\n { status: 400 },\n )\n }\n const { productId, variantId, quantity, storeId } = parsed.data\n const tenantId = auth.tenantId\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n // Soft-resolved product/variant existence check \u2014 degrades gracefully when\n // `catalog` is ejected (Phase 1 gate: coherent behaviour without it).\n const CatalogProduct = tryResolve<new () => unknown>(container, 'CatalogProduct')\n if (CatalogProduct) {\n const product = await em.findOne(CatalogProduct as any, { id: productId, organizationId, tenantId, deletedAt: null })\n if (!product) {\n return NextResponse.json(\n { error: translate('availability.errors.productNotFound', 'No such product') },\n { status: 404 },\n )\n }\n if (variantId) {\n const CatalogProductVariant = tryResolve<new () => unknown>(container, 'CatalogProductVariant')\n if (CatalogProductVariant) {\n const variant = await em.findOne(CatalogProductVariant as any, {\n id: variantId,\n organizationId,\n tenantId,\n deletedAt: null,\n })\n if (!variant) {\n return NextResponse.json(\n { error: translate('availability.errors.variantNotFound', 'No such variant') },\n { status: 404 },\n )\n }\n }\n }\n }\n\n const query: AvailabilityQuery = {\n tenantId,\n organizationId,\n storeId: storeId ?? null,\n items: [{ catalogProductId: productId, catalogVariantId: variantId ?? null, quantity }],\n }\n\n const moduleConfig = tryResolve<AvailabilityModuleConfigReader>(container, 'moduleConfigService')\n const result = await resolveAvailability(query, moduleConfig ? { moduleConfig } : undefined)\n const item = result.byItem[availabilityItemKey({ catalogProductId: productId, catalogVariantId: variantId ?? null })] ?? null\n\n const policyResolutionService = tryResolve<PolicyResolutionService>(container, 'policyResolutionService')\n const policyTrace = policyResolutionService\n ? await policyResolutionService.resolve(em, {\n tenantId,\n organizationId,\n storeId: storeId ?? null,\n productId,\n variantId: variantId ?? null,\n })\n : null\n\n return NextResponse.json({ availability: item, policyTrace })\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,27 +0,0 @@
1
- import {
2
- createCrudOpenApiFactory,
3
- createPagedListResponseSchema as createSharedPagedListResponseSchema,
4
- defaultCreateResponseSchema as sharedDefaultCreateResponseSchema,
5
- defaultOkResponseSchema as sharedDefaultOkResponseSchema
6
- } from "@open-mercato/shared/lib/openapi/crud";
7
- const defaultCreateResponseSchema = sharedDefaultCreateResponseSchema;
8
- const defaultOkResponseSchema = sharedDefaultOkResponseSchema;
9
- function createPagedListResponseSchema(itemSchema) {
10
- return createSharedPagedListResponseSchema(itemSchema, { paginationMetaOptional: true });
11
- }
12
- const buildAvailabilityCrudOpenApi = createCrudOpenApiFactory({
13
- defaultTag: "Availability",
14
- defaultCreateResponseSchema,
15
- defaultOkResponseSchema,
16
- makeListDescription: ({ pluralLower }) => `Returns a paginated collection of ${pluralLower} scoped to the authenticated organization.`
17
- });
18
- function createAvailabilityCrudOpenApi(options) {
19
- return buildAvailabilityCrudOpenApi(options);
20
- }
21
- export {
22
- createAvailabilityCrudOpenApi,
23
- createPagedListResponseSchema,
24
- defaultCreateResponseSchema,
25
- defaultOkResponseSchema
26
- };
27
- //# sourceMappingURL=openapi.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/availability/api/openapi.ts"],
4
- "sourcesContent": ["import { type ZodTypeAny } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n createCrudOpenApiFactory,\n createPagedListResponseSchema as createSharedPagedListResponseSchema,\n defaultCreateResponseSchema as sharedDefaultCreateResponseSchema,\n defaultOkResponseSchema as sharedDefaultOkResponseSchema,\n type CrudOpenApiOptions,\n} from '@open-mercato/shared/lib/openapi/crud'\n\nexport const defaultCreateResponseSchema = sharedDefaultCreateResponseSchema\nexport const defaultOkResponseSchema = sharedDefaultOkResponseSchema\n\nexport function createPagedListResponseSchema(itemSchema: ZodTypeAny) {\n return createSharedPagedListResponseSchema(itemSchema, { paginationMetaOptional: true })\n}\n\nconst buildAvailabilityCrudOpenApi = createCrudOpenApiFactory({\n defaultTag: 'Availability',\n defaultCreateResponseSchema,\n defaultOkResponseSchema,\n makeListDescription: ({ pluralLower }) =>\n `Returns a paginated collection of ${pluralLower} scoped to the authenticated organization.`,\n})\n\nexport function createAvailabilityCrudOpenApi(options: CrudOpenApiOptions): OpenApiRouteDoc {\n return buildAvailabilityCrudOpenApi(options)\n}\n"],
5
- "mappings": "AAEA;AAAA,EACE;AAAA,EACA,iCAAiC;AAAA,EACjC,+BAA+B;AAAA,EAC/B,2BAA2B;AAAA,OAEtB;AAEA,MAAM,8BAA8B;AACpC,MAAM,0BAA0B;AAEhC,SAAS,8BAA8B,YAAwB;AACpE,SAAO,oCAAoC,YAAY,EAAE,wBAAwB,KAAK,CAAC;AACzF;AAEA,MAAM,+BAA+B,yBAAyB;AAAA,EAC5D,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,qBAAqB,CAAC,EAAE,YAAY,MAClC,qCAAqC,WAAW;AACpD,CAAC;AAEM,SAAS,8BAA8B,SAA8C;AAC1F,SAAO,6BAA6B,OAAO;AAC7C;",
6
- "names": []
7
- }