@marcoappio/marco-config 4.0.3 → 4.0.5

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.
@@ -14,45 +14,47 @@ const validRows = {
14
14
  ...metadata,
15
15
  $tableId: 'accounts',
16
16
  colour: '#000000',
17
- credential: 'account-1',
17
+ credential: null,
18
18
  displayName: null,
19
19
  initialSyncCompletedAt: null,
20
20
  lastErrorCode: null,
21
21
  lastSuccessfulSyncAt: null,
22
- mailboxSyncState: 'account-1',
22
+ mailboxSyncState: null,
23
23
  mailProcessedCount: 0,
24
24
  mailTotalCount: 0,
25
- pollShard: null,
25
+ pollShardAssignmentId: null,
26
26
  pollShardId: null,
27
27
  primaryAlias: 'alias-1',
28
- primaryAliasId: 'alias-1',
29
28
  primaryEmail: 'marco@example.com',
30
29
  profile: 'user-1',
30
+ profileAssignmentKey: 'user-1',
31
31
  provider: 'GMAIL',
32
32
  state: 'CONNECTED',
33
- userId: 'user-1',
34
33
  },
35
34
  aliases: {
36
35
  ...metadata,
37
36
  $tableId: 'aliases',
38
- accountId: 'account-1',
37
+ account: 'account-1',
38
+ accountAssignmentKey: 'account-1',
39
39
  email: 'marco@example.com',
40
40
  isPrimary: true,
41
41
  name: 'Marco',
42
42
  providerId: 'provider-1',
43
43
  signature: '<p>Test signature</p>',
44
- userId: 'user-1',
45
44
  },
46
45
  commands: {
47
46
  ...metadata,
48
47
  $tableId: 'commands_v2',
48
+ account: null,
49
49
  accountId: null,
50
50
  attempts: 0,
51
51
  canonicalRevision: null,
52
+ draftId: null,
52
53
  errorCode: null,
53
54
  kind: 'thread.archive',
54
55
  payload: '{"threadId":"thread-1"}',
55
56
  payloadHash: 'hash-1',
57
+ profile: 'user-1',
56
58
  state: 'ACCEPTED',
57
59
  submittedAt: '2026-07-15T00:00:00.000Z',
58
60
  userId: 'user-1',
@@ -64,8 +66,9 @@ const validRows = {
64
66
  lastContactedAt: '2026-07-15T00:00:00.000Z',
65
67
  messageCount: 0,
66
68
  normalisedAddress: 'marco@example.com',
69
+ profile: 'user-1',
70
+ profileAssignmentKey: 'user-1',
67
71
  searchText: 'marco',
68
- userId: 'user-1',
69
72
  },
70
73
  credentials: {
71
74
  ...metadata,
@@ -74,25 +77,29 @@ const validRows = {
74
77
  encryptedPayload: 'encrypted-payload',
75
78
  expiresAt: null,
76
79
  provider: 'GMAIL',
80
+ refreshLeaseExpiresAt: null,
81
+ refreshLeaseId: null,
77
82
  userId: 'user-1',
78
83
  },
79
84
  drafts: {
80
85
  ...metadata,
81
86
  $tableId: 'drafts',
82
- accountId: 'account-1',
87
+ account: 'account-1',
88
+ accountAssignmentKey: 'account-1',
83
89
  attachmentMetadata: '[]',
84
90
  contentHash: 'hash-1',
85
91
  envelope: '{"bcc":[],"cc":[],"subject":null,"to":[]}',
86
92
  errorCode: null,
87
- fromAliasId: null,
93
+ executionLeaseExpiresAt: null,
94
+ executionLeaseId: null,
95
+ fromAlias: null,
88
96
  html: '<p>Draft</p>',
89
- referencedMessageId: null,
97
+ referencedMessage: null,
90
98
  revision: 0,
91
99
  scheduledAt: null,
92
100
  sequence: 0,
93
101
  status: 'DRAFT',
94
102
  type: 'COMPOSE',
95
- userId: 'user-1',
96
103
  },
97
104
  jobs: {
98
105
  ...metadata,
@@ -114,21 +121,41 @@ const validRows = {
114
121
  labels: {
115
122
  ...metadata,
116
123
  $tableId: 'labels',
117
- accountId: 'account-1',
124
+ account: 'account-1',
125
+ accountAssignmentKey: 'account-1',
118
126
  name: null,
119
127
  path: 'Inbox',
120
128
  providerId: 'provider-1',
121
129
  specialUse: 'INBOX',
122
130
  totalCount: 0,
123
131
  unreadCount: 0,
132
+ },
133
+ mailboxSyncCandidates: {
134
+ ...metadata,
135
+ $tableId: 'mailbox_sync_candidates_v2',
136
+ account: 'account-1',
137
+ accountId: 'account-1',
138
+ generation: 1,
139
+ label: 'label-1',
140
+ labelId: 'label-1',
141
+ lane: 'HEAD',
142
+ mailboxPath: 'INBOX',
143
+ mailboxPriority: 1,
144
+ mailboxSortKey: '0000000001:INBOX',
145
+ providerId: 'provider-message-1',
146
+ receivedAt: '2026-07-15T00:00:00.000Z',
147
+ specialUse: 'INBOX',
148
+ uid: 1,
149
+ uidValidity: 1,
124
150
  userId: 'user-1',
125
151
  },
126
152
  mailboxSyncLanes: {
127
153
  ...metadata,
128
154
  $permissions: [],
129
155
  $tableId: 'mailbox_sync_lanes',
156
+ account: 'test-account-id-1',
130
157
  accountId: 'test-account-id-1',
131
- commandId: '0197f3d4-1b22-7abc-8def-123456789abc',
158
+ commandId: 'sync_test-command-id-1',
132
159
  cursor: null,
133
160
  errorCode: null,
134
161
  generation: 1,
@@ -139,9 +166,12 @@ const validRows = {
139
166
  mailboxSyncStates: {
140
167
  ...metadata,
141
168
  $tableId: 'mailbox_sync_states',
169
+ accountId: 'account-1',
142
170
  errorCode: null,
143
171
  lastEventAt: null,
144
172
  lastSyncAt: null,
173
+ materialisationLeaseExpiresAt: null,
174
+ materialisationLeaseId: null,
145
175
  nextPollAt: null,
146
176
  provider: 'GMAIL',
147
177
  providerCursor: null,
@@ -149,10 +179,27 @@ const validRows = {
149
179
  uidValidity: null,
150
180
  userId: 'user-1',
151
181
  },
182
+ messageLocations: {
183
+ ...metadata,
184
+ $tableId: 'message_locations',
185
+ account: 'account-1',
186
+ accountAssignmentKey: 'account-1',
187
+ flagged: false,
188
+ label: 'label-1',
189
+ labelAssignmentKey: 'label-1',
190
+ message: 'message-1',
191
+ messageAt: '2026-07-15T00:00:00.000Z',
192
+ seen: true,
193
+ thread: 'thread-1',
194
+ threadAssignmentKey: 'thread-1',
195
+ uid: 1,
196
+ uidValidity: 1,
197
+ },
152
198
  messages: {
153
199
  ...metadata,
154
200
  $tableId: 'messages',
155
- accountId: 'account-1',
201
+ account: 'account-1',
202
+ accountAssignmentKey: 'account-1',
156
203
  attachmentMetadata: '[]',
157
204
  htmlFileId: null,
158
205
  preview: 'Preview',
@@ -167,8 +214,8 @@ const validRows = {
167
214
  sentAt: '2026-07-15T00:00:00.000Z',
168
215
  size: 1,
169
216
  subject: 'Test subject',
170
- threadId: 'thread-1',
171
- userId: 'user-1',
217
+ thread: 'thread-1',
218
+ threadAssignmentKey: 'thread-1',
172
219
  },
173
220
  oauthStates: {
174
221
  ...metadata,
@@ -198,6 +245,15 @@ const validRows = {
198
245
  scheduledExecutionId: null,
199
246
  state: 'ACTIVE',
200
247
  },
248
+ profileAvatarCleanups: {
249
+ ...metadata,
250
+ $permissions: [],
251
+ $tableId: 'profile_avatar_cleanups',
252
+ claimedAt: null,
253
+ claimId: null,
254
+ fileId: 'test-file-id-1',
255
+ profile: 'user-1',
256
+ },
201
257
  profiles: {
202
258
  ...metadata,
203
259
  $tableId: 'profiles',
@@ -211,6 +267,7 @@ const validRows = {
211
267
  pushNotificationTokens: {
212
268
  ...metadata,
213
269
  $tableId: 'push_notification_tokens',
270
+ profile: 'user-1',
214
271
  token: 'ExponentPushToken[token]',
215
272
  userId: 'user-1',
216
273
  },
@@ -220,30 +277,32 @@ const validRows = {
220
277
  aliasIds: ['alias-1'],
221
278
  icon: 'mail',
222
279
  name: 'Unread',
280
+ profile: 'user-1',
223
281
  },
224
282
  threadAliases: {
225
283
  ...metadata,
226
284
  $tableId: 'thread_aliases',
227
- accountId: 'account-1',
228
- aliasId: 'alias-1',
285
+ alias: 'alias-1',
286
+ aliasAssignmentKey: 'alias-1',
229
287
  latestMessageAt: '2026-07-15T00:00:00.000Z',
230
- threadId: 'thread-1',
231
- userId: 'user-1',
288
+ thread: 'thread-1',
289
+ threadAssignmentKey: 'thread-1',
232
290
  },
233
291
  threadLabels: {
234
292
  ...metadata,
235
293
  $tableId: 'thread_labels',
236
- accountId: 'account-1',
237
- labelId: 'label-1',
294
+ label: 'label-1',
295
+ labelAssignmentKey: 'label-1',
238
296
  latestMessageAt: '2026-07-15T00:00:00.000Z',
239
297
  locationCount: 1,
240
- threadId: 'thread-1',
241
- userId: 'user-1',
298
+ thread: 'thread-1',
299
+ threadAssignmentKey: 'thread-1',
242
300
  },
243
301
  threads: {
244
302
  ...metadata,
245
303
  $tableId: 'threads',
246
- accountId: 'account-1',
304
+ account: 'account-1',
305
+ accountAssignmentKey: 'account-1',
247
306
  archived: false,
248
307
  firstMessageAt: '2026-07-14T00:00:00.000Z',
249
308
  flagged: false,
@@ -266,7 +325,6 @@ const validRows = {
266
325
  subject: null,
267
326
  trashed: false,
268
327
  unseenLocationCount: 0,
269
- userId: 'user-1',
270
328
  },
271
329
  };
272
330
  const validPollShardCoordinatorRow = {
@@ -307,12 +365,15 @@ describe('appwrite', () => {
307
365
  drafts: 'drafts',
308
366
  jobs: 'jobs_v2',
309
367
  labels: 'labels',
368
+ mailboxSyncCandidates: 'mailbox_sync_candidates_v2',
310
369
  mailboxSyncLanes: 'mailbox_sync_lanes',
311
370
  mailboxSyncStates: 'mailbox_sync_states',
371
+ messageLocations: 'message_locations',
312
372
  messages: 'messages',
313
373
  oauthStates: 'oauth_states',
314
374
  pollShardCoordinators: 'poll_shard_coordinators',
315
375
  pollShards: 'poll_shards',
376
+ profileAvatarCleanups: 'profile_avatar_cleanups',
316
377
  profiles: 'profiles',
317
378
  pushNotificationTokens: 'push_notification_tokens',
318
379
  savedViews: 'saved_views',
@@ -360,10 +421,10 @@ describe('appwrite', () => {
360
421
  expect(v.safeParse(appwrite.rowSchemas.savedViews, validRows.savedViews).success).toBe(true);
361
422
  expect(v.safeParse(appwrite.rowSchemas.savedViews, { ...validRows.savedViews, aliasEmails: ['marco@example.com'] }).success).toBe(false);
362
423
  });
363
- it('rejects a mailbox sync lane without a command UUID', () => {
424
+ it('rejects a mailbox sync lane without a command identifier', () => {
364
425
  expect(v.safeParse(appwrite.rowSchemas.mailboxSyncLanes, {
365
426
  ...validRows.mailboxSyncLanes,
366
- commandId: 'not-a-command-uuid',
427
+ commandId: '',
367
428
  }).success).toBe(false);
368
429
  });
369
430
  it('rejects a poll shard with more than ten accounts', () => {
@@ -400,7 +461,159 @@ describe('appwrite', () => {
400
461
  });
401
462
  }
402
463
  const rowTestCases = Object.keys(validRows);
464
+ const metadataKeys = ['$createdAt', '$databaseId', '$id', '$permissions', '$sequence', '$tableId', '$updatedAt'];
465
+ const expectedRowDataKeys = {
466
+ accounts: [
467
+ 'colour',
468
+ 'credential',
469
+ 'displayName',
470
+ 'initialSyncCompletedAt',
471
+ 'lastErrorCode',
472
+ 'lastSuccessfulSyncAt',
473
+ 'mailboxSyncState',
474
+ 'mailProcessedCount',
475
+ 'mailTotalCount',
476
+ 'pollShardAssignmentId',
477
+ 'pollShardId',
478
+ 'primaryAlias',
479
+ 'primaryEmail',
480
+ 'profile',
481
+ 'profileAssignmentKey',
482
+ 'provider',
483
+ 'state',
484
+ ],
485
+ aliases: ['account', 'accountAssignmentKey', 'email', 'isPrimary', 'name', 'providerId', 'signature'],
486
+ commands: ['account', 'accountId', 'attempts', 'canonicalRevision', 'draftId', 'errorCode', 'kind', 'payload', 'payloadHash', 'profile', 'state', 'submittedAt', 'userId'],
487
+ contacts: ['displayName', 'lastContactedAt', 'messageCount', 'normalisedAddress', 'profile', 'profileAssignmentKey', 'searchText'],
488
+ credentials: ['accountId', 'encryptedPayload', 'expiresAt', 'provider', 'refreshLeaseExpiresAt', 'refreshLeaseId', 'userId'],
489
+ drafts: [
490
+ 'account',
491
+ 'accountAssignmentKey',
492
+ 'attachmentMetadata',
493
+ 'contentHash',
494
+ 'envelope',
495
+ 'errorCode',
496
+ 'executionLeaseExpiresAt',
497
+ 'executionLeaseId',
498
+ 'fromAlias',
499
+ 'html',
500
+ 'referencedMessage',
501
+ 'revision',
502
+ 'scheduledAt',
503
+ 'sequence',
504
+ 'status',
505
+ 'type',
506
+ ],
507
+ jobs: ['accountId', 'attempts', 'commandId', 'dedupeKey', 'errorCode', 'kind', 'leaseExpiresAt', 'leaseId', 'maxAttempts', 'nextRunAt', 'payload', 'state', 'userId'],
508
+ labels: ['account', 'accountAssignmentKey', 'name', 'path', 'providerId', 'specialUse', 'totalCount', 'unreadCount'],
509
+ mailboxSyncCandidates: [
510
+ 'account',
511
+ 'accountId',
512
+ 'generation',
513
+ 'label',
514
+ 'labelId',
515
+ 'lane',
516
+ 'mailboxPath',
517
+ 'mailboxPriority',
518
+ 'mailboxSortKey',
519
+ 'providerId',
520
+ 'receivedAt',
521
+ 'specialUse',
522
+ 'uid',
523
+ 'uidValidity',
524
+ 'userId',
525
+ ],
526
+ mailboxSyncLanes: ['account', 'accountId', 'commandId', 'cursor', 'errorCode', 'generation', 'lane', 'state', 'userId'],
527
+ mailboxSyncStates: [
528
+ 'accountId',
529
+ 'errorCode',
530
+ 'lastEventAt',
531
+ 'lastSyncAt',
532
+ 'materialisationLeaseExpiresAt',
533
+ 'materialisationLeaseId',
534
+ 'nextPollAt',
535
+ 'provider',
536
+ 'providerCursor',
537
+ 'state',
538
+ 'uidValidity',
539
+ 'userId',
540
+ ],
541
+ messageLocations: [
542
+ 'account',
543
+ 'accountAssignmentKey',
544
+ 'flagged',
545
+ 'label',
546
+ 'labelAssignmentKey',
547
+ 'message',
548
+ 'messageAt',
549
+ 'seen',
550
+ 'thread',
551
+ 'threadAssignmentKey',
552
+ 'uid',
553
+ 'uidValidity',
554
+ ],
555
+ messages: [
556
+ 'account',
557
+ 'accountAssignmentKey',
558
+ 'attachmentMetadata',
559
+ 'htmlFileId',
560
+ 'preview',
561
+ 'providerId',
562
+ 'rawFileId',
563
+ 'receivedAt',
564
+ 'recipients',
565
+ 'revision',
566
+ 'rfcMessageId',
567
+ 'sender',
568
+ 'senderName',
569
+ 'sentAt',
570
+ 'size',
571
+ 'subject',
572
+ 'thread',
573
+ 'threadAssignmentKey',
574
+ ],
575
+ oauthStates: ['accountId', 'email', 'expiresAt', 'nonceHash', 'operation', 'profile', 'provider', 'returnUri', 'userId'],
576
+ pollShards: ['accountIds', 'errorCode', 'generation', 'lastStartedAt', 'leaseExpiresAt', 'leaseId', 'nextStartAt', 'pollingSnapshot', 'scheduledExecutionId', 'state'],
577
+ profileAvatarCleanups: ['claimedAt', 'claimId', 'fileId', 'profile'],
578
+ profiles: ['avatarFileId', 'deletionRequestedAt', 'email', 'name', 'onboardingState', 'timezone'],
579
+ pushNotificationTokens: ['profile', 'token', 'userId'],
580
+ savedViews: ['aliasIds', 'icon', 'name', 'profile'],
581
+ threadAliases: ['alias', 'aliasAssignmentKey', 'latestMessageAt', 'thread', 'threadAssignmentKey'],
582
+ threadLabels: ['label', 'labelAssignmentKey', 'latestMessageAt', 'locationCount', 'thread', 'threadAssignmentKey'],
583
+ threads: [
584
+ 'account',
585
+ 'accountAssignmentKey',
586
+ 'archived',
587
+ 'firstMessageAt',
588
+ 'flagged',
589
+ 'flaggedLocationCount',
590
+ 'hasAttachments',
591
+ 'labelIds',
592
+ 'latestMessageAt',
593
+ 'latestMessageId',
594
+ 'messageCount',
595
+ 'participants',
596
+ 'preview',
597
+ 'providerId',
598
+ 'revision',
599
+ 'searchText',
600
+ 'seen',
601
+ 'senderEmail',
602
+ 'senderName',
603
+ 'snoozedUntil',
604
+ 'spam',
605
+ 'subject',
606
+ 'trashed',
607
+ 'unseenLocationCount',
608
+ ],
609
+ };
403
610
  for (const table of rowTestCases) {
611
+ it(`exposes backend-aligned ${table} row keys`, () => {
612
+ const dataKeys = Object.keys(appwrite.rowSchemas[table].entries)
613
+ .filter(key => !metadataKeys.includes(key))
614
+ .sort();
615
+ expect(dataKeys).toEqual([...expectedRowDataKeys[table]].sort());
616
+ });
404
617
  it(`accepts the ${table} wire row`, () => {
405
618
  expect(v.safeParse(appwrite.rowSchemas[table], validRows[table]).success).toBe(true);
406
619
  });
@@ -409,6 +622,141 @@ describe('appwrite', () => {
409
622
  expect(v.safeParse(appwrite.rowSchemas[table], rowWithoutId).success).toBe(false);
410
623
  });
411
624
  }
625
+ it('normalises Appwrite relationship objects to row IDs', () => {
626
+ const account = v.parse(appwrite.rowSchemas.accounts, {
627
+ ...validRows.accounts,
628
+ credential: { $id: 'credential-1' },
629
+ mailboxSyncState: { $id: 'mailbox-sync-state-1' },
630
+ primaryAlias: { $id: 'alias-1' },
631
+ profile: { $id: 'user-1' },
632
+ });
633
+ const alias = v.parse(appwrite.rowSchemas.aliases, { ...validRows.aliases, account: { $id: 'account-1' } });
634
+ const command = v.parse(appwrite.rowSchemas.commands, { ...validRows.commands, account: { $id: 'account-1' }, profile: { $id: 'user-1' } });
635
+ const contact = v.parse(appwrite.rowSchemas.contacts, { ...validRows.contacts, profile: { $id: 'user-1' } });
636
+ const draft = v.parse(appwrite.rowSchemas.drafts, {
637
+ ...validRows.drafts,
638
+ account: { $id: 'account-1' },
639
+ fromAlias: { $id: 'alias-1' },
640
+ referencedMessage: { $id: 'message-1' },
641
+ });
642
+ const label = v.parse(appwrite.rowSchemas.labels, { ...validRows.labels, account: { $id: 'account-1' } });
643
+ const mailboxSyncCandidate = v.parse(appwrite.rowSchemas.mailboxSyncCandidates, {
644
+ ...validRows.mailboxSyncCandidates,
645
+ account: { $id: 'account-1' },
646
+ label: { $id: 'label-1' },
647
+ });
648
+ const mailboxSyncLane = v.parse(appwrite.rowSchemas.mailboxSyncLanes, { ...validRows.mailboxSyncLanes, account: { $id: 'account-1' } });
649
+ const messageLocation = v.parse(appwrite.rowSchemas.messageLocations, {
650
+ ...validRows.messageLocations,
651
+ account: { $id: 'account-1' },
652
+ label: { $id: 'label-1' },
653
+ message: { $id: 'message-1' },
654
+ thread: { $id: 'thread-1' },
655
+ });
656
+ const oauthState = v.parse(appwrite.rowSchemas.oauthStates, { ...validRows.oauthStates, profile: { $id: 'user-1' } });
657
+ const profileAvatarCleanup = v.parse(appwrite.rowSchemas.profileAvatarCleanups, { ...validRows.profileAvatarCleanups, profile: { $id: 'user-1' } });
658
+ const pushNotificationToken = v.parse(appwrite.rowSchemas.pushNotificationTokens, { ...validRows.pushNotificationTokens, profile: { $id: 'user-1' } });
659
+ const savedView = v.parse(appwrite.rowSchemas.savedViews, { ...validRows.savedViews, profile: { $id: 'user-1' } });
660
+ const thread = v.parse(appwrite.rowSchemas.threads, { ...validRows.threads, account: { $id: 'account-1' } });
661
+ const message = v.parse(appwrite.rowSchemas.messages, {
662
+ ...validRows.messages,
663
+ account: { $id: 'account-1' },
664
+ thread: { $id: 'thread-1' },
665
+ });
666
+ const threadAlias = v.parse(appwrite.rowSchemas.threadAliases, {
667
+ ...validRows.threadAliases,
668
+ alias: { $id: 'alias-1' },
669
+ thread: { $id: 'thread-1' },
670
+ });
671
+ const threadLabel = v.parse(appwrite.rowSchemas.threadLabels, {
672
+ ...validRows.threadLabels,
673
+ label: { $id: 'label-1' },
674
+ thread: { $id: 'thread-1' },
675
+ });
676
+ expect(account.credential).toBe('credential-1');
677
+ expect(account.mailboxSyncState).toBe('mailbox-sync-state-1');
678
+ expect(account.primaryAlias).toBe('alias-1');
679
+ expect(account.profile).toBe('user-1');
680
+ expect(alias.account).toBe('account-1');
681
+ expect(command.account).toBe('account-1');
682
+ expect(command.profile).toBe('user-1');
683
+ expect(contact.profile).toBe('user-1');
684
+ expect(draft.account).toBe('account-1');
685
+ expect(draft.fromAlias).toBe('alias-1');
686
+ expect(draft.referencedMessage).toBe('message-1');
687
+ expect(label.account).toBe('account-1');
688
+ expect(mailboxSyncCandidate.account).toBe('account-1');
689
+ expect(mailboxSyncCandidate.label).toBe('label-1');
690
+ expect(mailboxSyncLane.account).toBe('account-1');
691
+ expect(messageLocation.account).toBe('account-1');
692
+ expect(messageLocation.label).toBe('label-1');
693
+ expect(messageLocation.message).toBe('message-1');
694
+ expect(messageLocation.thread).toBe('thread-1');
695
+ expect(oauthState.profile).toBe('user-1');
696
+ expect(profileAvatarCleanup.profile).toBe('user-1');
697
+ expect(pushNotificationToken.profile).toBe('user-1');
698
+ expect(savedView.profile).toBe('user-1');
699
+ expect(thread.account).toBe('account-1');
700
+ expect(message.account).toBe('account-1');
701
+ expect(message.thread).toBe('thread-1');
702
+ expect(threadAlias.alias).toBe('alias-1');
703
+ expect(threadAlias.thread).toBe('thread-1');
704
+ expect(threadLabel.label).toBe('label-1');
705
+ expect(threadLabel.thread).toBe('thread-1');
706
+ });
707
+ it('accepts backend message sender fields as plain nullable text', () => {
708
+ expect(v.safeParse(appwrite.rowSchemas.messages, {
709
+ ...validRows.messages,
710
+ sender: 'not-an-email',
711
+ senderName: '',
712
+ }).success).toBe(true);
713
+ });
714
+ const requiredRelationshipFieldTestCases = [
715
+ { field: 'account', row: { ...validRows.messages, account: null }, schema: appwrite.rowSchemas.messages, table: 'messages' },
716
+ { field: 'thread', row: { ...validRows.messages, thread: null }, schema: appwrite.rowSchemas.messages, table: 'messages' },
717
+ { field: 'account', row: { ...validRows.threads, account: null }, schema: appwrite.rowSchemas.threads, table: 'threads' },
718
+ { field: 'alias', row: { ...validRows.threadAliases, alias: null }, schema: appwrite.rowSchemas.threadAliases, table: 'threadAliases' },
719
+ { field: 'thread', row: { ...validRows.threadAliases, thread: null }, schema: appwrite.rowSchemas.threadAliases, table: 'threadAliases' },
720
+ { field: 'label', row: { ...validRows.threadLabels, label: null }, schema: appwrite.rowSchemas.threadLabels, table: 'threadLabels' },
721
+ { field: 'thread', row: { ...validRows.threadLabels, thread: null }, schema: appwrite.rowSchemas.threadLabels, table: 'threadLabels' },
722
+ ];
723
+ for (const { field, row, schema, table } of requiredRelationshipFieldTestCases) {
724
+ it(`rejects nullable ${field} relationships on ${table}`, () => {
725
+ expect(v.safeParse(schema, row).success).toBe(false);
726
+ });
727
+ }
728
+ const obsoleteRelationshipFieldTestCases = [
729
+ { field: 'pollShard', row: { ...validRows.accounts, pollShard: null }, schema: appwrite.rowSchemas.accounts, table: 'accounts' },
730
+ { field: 'primaryAliasId', row: { ...validRows.accounts, primaryAliasId: 'alias-1' }, schema: appwrite.rowSchemas.accounts, table: 'accounts' },
731
+ { field: 'userId', row: { ...validRows.accounts, userId: 'user-1' }, schema: appwrite.rowSchemas.accounts, table: 'accounts' },
732
+ { field: 'accountId', row: { ...validRows.aliases, accountId: 'account-1' }, schema: appwrite.rowSchemas.aliases, table: 'aliases' },
733
+ { field: 'userId', row: { ...validRows.aliases, userId: 'user-1' }, schema: appwrite.rowSchemas.aliases, table: 'aliases' },
734
+ { field: 'userId', row: { ...validRows.contacts, userId: 'user-1' }, schema: appwrite.rowSchemas.contacts, table: 'contacts' },
735
+ { field: 'accountId', row: { ...validRows.drafts, accountId: 'account-1' }, schema: appwrite.rowSchemas.drafts, table: 'drafts' },
736
+ { field: 'fromAliasId', row: { ...validRows.drafts, fromAliasId: 'alias-1' }, schema: appwrite.rowSchemas.drafts, table: 'drafts' },
737
+ { field: 'referencedMessageId', row: { ...validRows.drafts, referencedMessageId: 'message-1' }, schema: appwrite.rowSchemas.drafts, table: 'drafts' },
738
+ { field: 'userId', row: { ...validRows.drafts, userId: 'user-1' }, schema: appwrite.rowSchemas.drafts, table: 'drafts' },
739
+ { field: 'accountId', row: { ...validRows.labels, accountId: 'account-1' }, schema: appwrite.rowSchemas.labels, table: 'labels' },
740
+ { field: 'userId', row: { ...validRows.labels, userId: 'user-1' }, schema: appwrite.rowSchemas.labels, table: 'labels' },
741
+ { field: 'accountId', row: { ...validRows.messages, accountId: 'account-1' }, schema: appwrite.rowSchemas.messages, table: 'messages' },
742
+ { field: 'threadId', row: { ...validRows.messages, threadId: 'thread-1' }, schema: appwrite.rowSchemas.messages, table: 'messages' },
743
+ { field: 'userId', row: { ...validRows.messages, userId: 'user-1' }, schema: appwrite.rowSchemas.messages, table: 'messages' },
744
+ { field: 'accountId', row: { ...validRows.threads, accountId: 'account-1' }, schema: appwrite.rowSchemas.threads, table: 'threads' },
745
+ { field: 'userId', row: { ...validRows.threads, userId: 'user-1' }, schema: appwrite.rowSchemas.threads, table: 'threads' },
746
+ { field: 'accountId', row: { ...validRows.threadAliases, accountId: 'account-1' }, schema: appwrite.rowSchemas.threadAliases, table: 'threadAliases' },
747
+ { field: 'aliasId', row: { ...validRows.threadAliases, aliasId: 'alias-1' }, schema: appwrite.rowSchemas.threadAliases, table: 'threadAliases' },
748
+ { field: 'threadId', row: { ...validRows.threadAliases, threadId: 'thread-1' }, schema: appwrite.rowSchemas.threadAliases, table: 'threadAliases' },
749
+ { field: 'userId', row: { ...validRows.threadAliases, userId: 'user-1' }, schema: appwrite.rowSchemas.threadAliases, table: 'threadAliases' },
750
+ { field: 'accountId', row: { ...validRows.threadLabels, accountId: 'account-1' }, schema: appwrite.rowSchemas.threadLabels, table: 'threadLabels' },
751
+ { field: 'labelId', row: { ...validRows.threadLabels, labelId: 'label-1' }, schema: appwrite.rowSchemas.threadLabels, table: 'threadLabels' },
752
+ { field: 'threadId', row: { ...validRows.threadLabels, threadId: 'thread-1' }, schema: appwrite.rowSchemas.threadLabels, table: 'threadLabels' },
753
+ { field: 'userId', row: { ...validRows.threadLabels, userId: 'user-1' }, schema: appwrite.rowSchemas.threadLabels, table: 'threadLabels' },
754
+ ];
755
+ for (const { field, row, schema, table } of obsoleteRelationshipFieldTestCases) {
756
+ it(`rejects obsolete ${field} on ${table}`, () => {
757
+ expect(v.safeParse(schema, row).success).toBe(false);
758
+ });
759
+ }
412
760
  const nullableColumnTestCases = [
413
761
  {
414
762
  description: 'alias name',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marcoappio/marco-config",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "author": "team@marcoapp.io",
5
5
  "main": "dist/index.js",
6
6
  "repository": "git@github.com:marcoappio/marco-config.git",