create-mercato-app 0.5.1-develop.2756.cce1739df3 → 0.5.1-develop.2762.90c271efe2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mercato-app",
3
- "version": "0.5.1-develop.2756.cce1739df3",
3
+ "version": "0.5.1-develop.2762.90c271efe2",
4
4
  "type": "module",
5
5
  "description": "Create a new Open Mercato application",
6
6
  "main": "./dist/index.js",
@@ -27,13 +27,13 @@ import {
27
27
 
28
28
  export const todoCreateSchema = z.object({
29
29
  id: z.string().uuid().optional(),
30
- title: z.string().min(1),
30
+ title: z.string().min(1).max(200),
31
31
  is_done: z.boolean().optional(),
32
32
  })
33
33
 
34
34
  export const todoUpdateSchema = z.object({
35
35
  id: z.string().uuid(),
36
- title: z.string().min(1).optional(),
36
+ title: z.string().min(1).max(200).optional(),
37
37
  is_done: z.boolean().optional(),
38
38
  })
39
39
 
@@ -24,11 +24,11 @@ type MappingRow = {
24
24
  interaction_id: string
25
25
  todo_id: string
26
26
  sync_status: string
27
- last_synced_at: Date | null
27
+ last_synced_at: Date | string | null
28
28
  last_error: string | null
29
- source_updated_at: Date | null
30
- created_at: Date
31
- updated_at: Date
29
+ source_updated_at: Date | string | null
30
+ created_at: Date | string
31
+ updated_at: Date | string
32
32
  organization_id: string
33
33
  tenant_id: string
34
34
  }
@@ -53,6 +53,15 @@ function decodeCursor(token: string | undefined): CursorPayload | null {
53
53
  }
54
54
  }
55
55
 
56
+ function toIsoString(value: Date | string | null | undefined): string | null {
57
+ if (!value) return null
58
+ if (value instanceof Date) {
59
+ return Number.isNaN(value.getTime()) ? null : value.toISOString()
60
+ }
61
+ const parsed = new Date(value)
62
+ return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()
63
+ }
64
+
56
65
  export async function GET(request: Request) {
57
66
  const { translate } = await resolveTranslations()
58
67
  try {
@@ -163,11 +172,11 @@ export async function GET(request: Request) {
163
172
  interactionId: row.interaction_id,
164
173
  todoId: row.todo_id,
165
174
  syncStatus: row.sync_status,
166
- lastSyncedAt: row.last_synced_at?.toISOString() ?? null,
175
+ lastSyncedAt: toIsoString(row.last_synced_at),
167
176
  lastError: row.last_error ?? null,
168
- sourceUpdatedAt: row.source_updated_at?.toISOString() ?? null,
169
- createdAt: row.created_at.toISOString(),
170
- updatedAt: row.updated_at.toISOString(),
177
+ sourceUpdatedAt: toIsoString(row.source_updated_at),
178
+ createdAt: toIsoString(row.created_at),
179
+ updatedAt: toIsoString(row.updated_at),
171
180
  organizationId: row.organization_id,
172
181
  tenantId: row.tenant_id,
173
182
  exampleHref: `/backend/todos/${encodeURIComponent(row.todo_id)}/edit`,
@@ -186,7 +195,7 @@ export async function GET(request: Request) {
186
195
 
187
196
  return NextResponse.json({
188
197
  items,
189
- ...(last ? { nextCursor: encodeCursor({ updatedAt: last.updated_at.toISOString(), id: last.id }) } : {}),
198
+ ...(last ? { nextCursor: encodeCursor({ updatedAt: toIsoString(last.updated_at) ?? new Date(0).toISOString(), id: last.id }) } : {}),
190
199
  })
191
200
  } catch (error) {
192
201
  if (error instanceof z.ZodError) {
@@ -1,6 +1,8 @@
1
+ import { setTimeout as sleep } from 'node:timers/promises'
1
2
  import type { EntityManager } from '@mikro-orm/postgresql'
2
3
  import { type Kysely } from 'kysely'
3
4
  import type { CommandBus } from '@open-mercato/shared/lib/commands'
5
+ import '@open-mercato/core/modules/customers/commands/index'
4
6
  import { loadCustomFieldSnapshot } from '@open-mercato/shared/lib/commands/customFieldSnapshots'
5
7
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
6
8
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
@@ -66,7 +68,7 @@ type LegacyExampleTodoLinkRow = {
66
68
  entityId: string
67
69
  todoId: string
68
70
  createdByUserId: string | null
69
- createdAt: Date
71
+ createdAt: Date | string
70
72
  }
71
73
 
72
74
  export type ExampleCustomersSyncReconcileItem = {
@@ -92,6 +94,8 @@ type CursorPayload = {
92
94
  }
93
95
 
94
96
  const DEFAULT_TASK_TITLE = 'Untitled task'
97
+ const LEGACY_INBOUND_BOOTSTRAP_ATTEMPTS = 10
98
+ const LEGACY_INBOUND_BOOTSTRAP_DELAY_MS = 100
95
99
 
96
100
  function isSyncOriginFromBridge(syncOrigin: unknown): boolean {
97
101
  return typeof syncOrigin === 'string' && syncOrigin.startsWith('example_customers_sync:')
@@ -110,6 +114,10 @@ function parseDateOrNull(value: string | Date | null | undefined): Date | null {
110
114
  return Number.isNaN(parsed.getTime()) ? null : parsed
111
115
  }
112
116
 
117
+ function toIsoString(value: Date | string | null | undefined): string | null {
118
+ return parseDateOrNull(value)?.toISOString() ?? null
119
+ }
120
+
113
121
  function trimErrorMessage(value: unknown): string {
114
122
  const message = value instanceof Error ? value.message : String(value ?? 'Unknown sync error')
115
123
  return message.length > 2000 ? `${message.slice(0, 1997)}...` : message
@@ -394,6 +402,38 @@ async function loadLegacyExampleTodoLinkRow(
394
402
  }
395
403
  }
396
404
 
405
+ async function waitForLegacyExampleTodoLinkRow(
406
+ em: EntityManager,
407
+ scope: ExampleCustomersSyncScope,
408
+ todoId: string,
409
+ ): Promise<LegacyExampleTodoLinkRow | null> {
410
+ for (let attempt = 0; attempt < LEGACY_INBOUND_BOOTSTRAP_ATTEMPTS; attempt += 1) {
411
+ const link = await loadLegacyExampleTodoLinkRow(em, scope, todoId)
412
+ if (link) return link
413
+ if (attempt < LEGACY_INBOUND_BOOTSTRAP_ATTEMPTS - 1) {
414
+ await sleep(LEGACY_INBOUND_BOOTSTRAP_DELAY_MS)
415
+ em.clear()
416
+ }
417
+ }
418
+ return null
419
+ }
420
+
421
+ async function waitForExampleTodoSnapshot(
422
+ em: EntityManager,
423
+ scope: ExampleCustomersSyncScope,
424
+ todoId: string,
425
+ ): Promise<ExampleTodoSnapshot | null> {
426
+ for (let attempt = 0; attempt < LEGACY_INBOUND_BOOTSTRAP_ATTEMPTS; attempt += 1) {
427
+ const todo = await loadExampleTodoSnapshot(em, scope, todoId)
428
+ if (todo) return todo
429
+ if (attempt < LEGACY_INBOUND_BOOTSTRAP_ATTEMPTS - 1) {
430
+ await sleep(LEGACY_INBOUND_BOOTSTRAP_DELAY_MS)
431
+ em.clear()
432
+ }
433
+ }
434
+ return null
435
+ }
436
+
397
437
  async function ensureLegacyExampleMapping(
398
438
  em: EntityManager,
399
439
  scope: ExampleCustomersSyncScope,
@@ -416,7 +456,7 @@ async function ensureLegacyExampleMapping(
416
456
  ...scope,
417
457
  interactionId,
418
458
  todoId: legacyLink.todoId,
419
- sourceUpdatedAt: legacyLink.createdAt ?? null,
459
+ sourceUpdatedAt: parseDateOrNull(legacyLink.createdAt),
420
460
  })
421
461
  }
422
462
 
@@ -755,7 +795,7 @@ async function loadLegacyExampleTodoLinks(
755
795
  const next = rows.length > limit ? pageRows[pageRows.length - 1] : null
756
796
  return {
757
797
  rows: pageRows,
758
- ...(next ? { nextCursor: encodeCursor({ createdAt: next.createdAt.toISOString(), id: next.id }) } : {}),
798
+ ...(next ? { nextCursor: encodeCursor({ createdAt: toIsoString(next.createdAt) ?? new Date(0).toISOString(), id: next.id }) } : {}),
759
799
  }
760
800
  }
761
801
 
@@ -781,14 +821,14 @@ async function ensureCanonicalInteractionForLegacyLink(
781
821
  return { interactionId: existing.id, created: false }
782
822
  }
783
823
 
784
- const todo = await loadExampleTodoSnapshot(em, scope, link.todoId)
824
+ const todo = await waitForExampleTodoSnapshot(em, scope, link.todoId)
785
825
  if (!todo) return null
786
826
 
787
827
  const patch = buildInteractionUpdateFromExampleTodo({
788
828
  title: todo.title,
789
829
  isDone: todo.isDone,
790
830
  customValues: todo.customValues,
791
- occurredAt: todo.isDone ? (todo.updatedAt ?? link.createdAt) : null,
831
+ occurredAt: todo.isDone ? parseDateOrNull(todo.updatedAt ?? link.createdAt) : null,
792
832
  })
793
833
 
794
834
  const commandBus = container.resolve('commandBus') as CommandBus
@@ -801,6 +841,8 @@ async function ensureCanonicalInteractionForLegacyLink(
801
841
  const result = await commandBus.execute<Record<string, unknown>, { interactionId: string }>('customers.interactions.create', {
802
842
  input: {
803
843
  id: link.todoId,
844
+ tenantId: scope.tenantId,
845
+ organizationId: scope.organizationId,
804
846
  entityId: link.entityId,
805
847
  interactionType: CUSTOMER_INTERACTION_TASK_TYPE,
806
848
  title: patch.title,
@@ -840,16 +882,16 @@ async function ensureMappingForLegacyExampleTodo(
840
882
  todoId: string,
841
883
  ): Promise<ExampleCustomerInteractionMapping | null> {
842
884
  const em = (container.resolve('em') as EntityManager).fork()
843
- const legacyLink = await loadLegacyExampleTodoLinkRow(em, scope, todoId)
885
+ const legacyLink = await waitForLegacyExampleTodoLinkRow(em, scope, todoId)
844
886
  if (!legacyLink) return null
845
887
  const canonical = await ensureCanonicalInteractionForLegacyLink(container, scope, legacyLink)
846
888
  if (!canonical) return null
847
- const todo = await loadExampleTodoSnapshot(em, scope, todoId)
889
+ const todo = await waitForExampleTodoSnapshot(em, scope, todoId)
848
890
  return await updateMappingAfterSync(em, {
849
891
  ...scope,
850
892
  interactionId: canonical.interactionId,
851
893
  todoId,
852
- sourceUpdatedAt: todo?.updatedAt ?? legacyLink.createdAt,
894
+ sourceUpdatedAt: parseDateOrNull(todo?.updatedAt ?? legacyLink.createdAt),
853
895
  })
854
896
  }
855
897
 
@@ -888,7 +930,7 @@ export async function reconcileLegacyExampleTodoLinks(
888
930
  ...scope,
889
931
  interactionId: canonical.interactionId,
890
932
  todoId: row.todoId,
891
- sourceUpdatedAt: todo?.updatedAt ?? row.createdAt,
933
+ sourceUpdatedAt: parseDateOrNull(todo?.updatedAt ?? row.createdAt),
892
934
  })
893
935
  items.push({
894
936
  linkId: row.id,