@marcoappio/marco-config 2.0.528 → 2.0.530

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.
@@ -1,4 +1,5 @@
1
1
  import * as v from 'valibot';
2
+ import { marcoConstants } from '../constants';
2
3
  import { marcoSchemas } from '../schemas';
3
4
  import { socketTypeSchema } from '../schemas/emailAccount';
4
5
  import { DRAFT_ATTACHMENT_UPLOAD_STATUSES, DRAFT_STATUSES, DRAFT_TYPES } from '../types';
@@ -53,6 +54,11 @@ const connectionConfigImapRawSchema = v.object({
53
54
  smtpSocketType: socketTypeSchema,
54
55
  smtpUser: marcoSchemas.string.required(),
55
56
  });
57
+ const userViewSchema = v.object({
58
+ aliasEmails: v.pipe(v.array(marcoSchemas.string.email()), v.minLength(1), v.maxLength(marcoConstants.user.views.maxAliasEmailsPerView)),
59
+ id: marcoSchemas.string.required(),
60
+ name: marcoSchemas.string.required(),
61
+ });
56
62
  export const mutatorSchemas = {
57
63
  account: {
58
64
  createAccount: v.object({
@@ -178,10 +184,18 @@ export const mutatorSchemas = {
178
184
  setTrash: baseThreadSchema,
179
185
  },
180
186
  user: {
187
+ createView: v.object({
188
+ id: marcoSchemas.string.required(),
189
+ view: userViewSchema,
190
+ }),
181
191
  deleteSettingsPushNotificationToken: v.object({
182
192
  id: marcoSchemas.string.required(),
183
193
  token: marcoSchemas.string.required(),
184
194
  }),
195
+ deleteView: v.object({
196
+ id: marcoSchemas.string.required(),
197
+ viewId: marcoSchemas.string.required(),
198
+ }),
185
199
  setSettingsName: v.object({
186
200
  id: marcoSchemas.string.required(),
187
201
  name: marcoSchemas.string.nullable(),
@@ -190,5 +204,13 @@ export const mutatorSchemas = {
190
204
  id: marcoSchemas.string.required(),
191
205
  pushNotificationToken: userPushNotificationTokenSchema,
192
206
  }),
207
+ updateView: v.object({
208
+ id: marcoSchemas.string.required(),
209
+ updates: v.partial(v.object({
210
+ aliasEmails: v.pipe(v.array(marcoSchemas.string.email()), v.minLength(1), v.maxLength(marcoConstants.user.views.maxAliasEmailsPerView)),
211
+ name: marcoSchemas.string.required(),
212
+ })),
213
+ viewId: marcoSchemas.string.required(),
214
+ }),
193
215
  },
194
216
  };
@@ -406,6 +406,21 @@ export declare const mutators: import("@rocicorp/zero").MutatorRegistry<{
406
406
  }, Context, unknown>;
407
407
  };
408
408
  readonly user: {
409
+ readonly createView: import("@rocicorp/zero").MutatorDefinition<{
410
+ id: string;
411
+ view: {
412
+ aliasEmails: string[];
413
+ id: string;
414
+ name: string;
415
+ };
416
+ }, {
417
+ id: string;
418
+ view: {
419
+ aliasEmails: string[];
420
+ id: string;
421
+ name: string;
422
+ };
423
+ }, Context, unknown>;
409
424
  readonly deleteSettingsPushNotificationToken: import("@rocicorp/zero").MutatorDefinition<{
410
425
  id: string;
411
426
  token: string;
@@ -413,6 +428,13 @@ export declare const mutators: import("@rocicorp/zero").MutatorRegistry<{
413
428
  id: string;
414
429
  token: string;
415
430
  }, Context, unknown>;
431
+ readonly deleteView: import("@rocicorp/zero").MutatorDefinition<{
432
+ id: string;
433
+ viewId: string;
434
+ }, {
435
+ id: string;
436
+ viewId: string;
437
+ }, Context, unknown>;
416
438
  readonly setSettingsName: import("@rocicorp/zero").MutatorDefinition<{
417
439
  id: string;
418
440
  name: string | null;
@@ -435,6 +457,21 @@ export declare const mutators: import("@rocicorp/zero").MutatorRegistry<{
435
457
  token: string;
436
458
  };
437
459
  }, Context, unknown>;
460
+ readonly updateView: import("@rocicorp/zero").MutatorDefinition<{
461
+ id: string;
462
+ updates: {
463
+ aliasEmails?: string[] | undefined;
464
+ name?: string | undefined;
465
+ };
466
+ viewId: string;
467
+ }, {
468
+ id: string;
469
+ updates: {
470
+ aliasEmails?: string[] | undefined;
471
+ name?: string | undefined;
472
+ };
473
+ viewId: string;
474
+ }, Context, unknown>;
438
475
  };
439
476
  }, {
440
477
  tables: {
@@ -469,6 +506,15 @@ export declare const mutators: import("@rocicorp/zero").MutatorRegistry<{
469
506
  } & {
470
507
  serverName: string;
471
508
  };
509
+ readonly views: {
510
+ type: "json";
511
+ optional: false;
512
+ customType: {
513
+ id: string;
514
+ name: string;
515
+ aliasEmails: string[];
516
+ }[];
517
+ };
472
518
  };
473
519
  primaryKey: readonly [string, ...string[]];
474
520
  } & {
@@ -1207,31 +1253,31 @@ export declare const mutators: import("@rocicorp/zero").MutatorRegistry<{
1207
1253
  relationships: {
1208
1254
  readonly user: {
1209
1255
  accounts: [{
1210
- readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1256
+ readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1211
1257
  readonly destField: readonly ("id" | "userId" | "color" | "displayName" | "imapConnectionStatus" | "mailProcessedCount" | "mailTotalCount" | "primaryAliasId")[];
1212
1258
  readonly destSchema: "account";
1213
1259
  readonly cardinality: "many";
1214
1260
  }];
1215
1261
  contacts: [{
1216
- readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1262
+ readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1217
1263
  readonly destField: readonly ("id" | "name" | "userId" | "emailAddress")[];
1218
1264
  readonly destSchema: "contact";
1219
1265
  readonly cardinality: "many";
1220
1266
  }];
1221
1267
  drafts: [{
1222
- readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1268
+ readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1223
1269
  readonly destField: readonly ("type" | "status" | "id" | "userId" | "accountId" | "fromAliasId" | "fromEmail" | "fromName" | "referencedMessageId" | "scheduledFor" | "updatedAt" | "body" | "error" | "subject")[];
1224
1270
  readonly destSchema: "draft";
1225
1271
  readonly cardinality: "many";
1226
1272
  }];
1227
1273
  pushNotificationTokens: [{
1228
- readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1274
+ readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1229
1275
  readonly destField: readonly ("id" | "createdAt" | "token" | "userId")[];
1230
1276
  readonly destSchema: "userPushNotificationToken";
1231
1277
  readonly cardinality: "many";
1232
1278
  }];
1233
1279
  threads: [{
1234
- readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1280
+ readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1235
1281
  readonly destField: readonly ("id" | "userId" | "accountId" | "subject" | "flagged" | "hasAttachments" | "labelIdList" | "latestMessageDate" | "latestMessageId" | "messageCount" | "previewText" | "seen" | "senderEmail" | "senderName" | "words")[];
1236
1282
  readonly destSchema: "thread";
1237
1283
  readonly cardinality: "many";
@@ -1240,7 +1286,7 @@ export declare const mutators: import("@rocicorp/zero").MutatorRegistry<{
1240
1286
  readonly userPushNotificationToken: {
1241
1287
  user: [{
1242
1288
  readonly sourceField: readonly ("id" | "createdAt" | "token" | "userId")[];
1243
- readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1289
+ readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1244
1290
  readonly destSchema: "user";
1245
1291
  readonly cardinality: "one";
1246
1292
  }];
@@ -1248,7 +1294,7 @@ export declare const mutators: import("@rocicorp/zero").MutatorRegistry<{
1248
1294
  readonly contact: {
1249
1295
  user: [{
1250
1296
  readonly sourceField: readonly ("id" | "name" | "userId" | "emailAddress")[];
1251
- readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1297
+ readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1252
1298
  readonly destSchema: "user";
1253
1299
  readonly cardinality: "one";
1254
1300
  }];
@@ -1286,7 +1332,7 @@ export declare const mutators: import("@rocicorp/zero").MutatorRegistry<{
1286
1332
  }];
1287
1333
  user: [{
1288
1334
  readonly sourceField: readonly ("id" | "userId" | "color" | "displayName" | "imapConnectionStatus" | "mailProcessedCount" | "mailTotalCount" | "primaryAliasId")[];
1289
- readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1335
+ readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1290
1336
  readonly destSchema: "user";
1291
1337
  readonly cardinality: "one";
1292
1338
  }];
@@ -1333,7 +1379,7 @@ export declare const mutators: import("@rocicorp/zero").MutatorRegistry<{
1333
1379
  }];
1334
1380
  user: [{
1335
1381
  readonly sourceField: readonly ("type" | "status" | "id" | "userId" | "accountId" | "fromAliasId" | "fromEmail" | "fromName" | "referencedMessageId" | "scheduledFor" | "updatedAt" | "body" | "error" | "subject")[];
1336
- readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1382
+ readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1337
1383
  readonly destSchema: "user";
1338
1384
  readonly cardinality: "one";
1339
1385
  }];
@@ -1378,7 +1424,7 @@ export declare const mutators: import("@rocicorp/zero").MutatorRegistry<{
1378
1424
  }];
1379
1425
  user: [{
1380
1426
  readonly sourceField: readonly ("id" | "userId" | "accountId" | "subject" | "flagged" | "hasAttachments" | "labelIdList" | "latestMessageDate" | "latestMessageId" | "messageCount" | "previewText" | "seen" | "senderEmail" | "senderName" | "words")[];
1381
- readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1427
+ readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1382
1428
  readonly destSchema: "user";
1383
1429
  readonly cardinality: "one";
1384
1430
  }];
@@ -1 +1 @@
1
- {"version":3,"file":"mutators.d.ts","sourceRoot":"","sources":["../../src/zero/mutators.ts"],"names":[],"mappings":"AAQA,KAAK,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAqGjC,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6fnB,CAAA"}
1
+ {"version":3,"file":"mutators.d.ts","sourceRoot":"","sources":["../../src/zero/mutators.ts"],"names":[],"mappings":"AASA,KAAK,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAqGjC,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0iBnB,CAAA"}
@@ -1,4 +1,5 @@
1
1
  import { createBuilder, defineMutators, defineMutatorWithType } from '@rocicorp/zero';
2
+ import { marcoConstants } from '../constants';
2
3
  import { stringPatch } from '../utils';
3
4
  import { threadsUtils } from '../utils/threads';
4
5
  import { mutatorSchemas } from '../zero/mutatorSchemas';
@@ -487,11 +488,33 @@ export const mutators = defineMutators({
487
488
  }),
488
489
  },
489
490
  user: {
491
+ createView: defineMutator(mutatorSchemas.user.createView, async ({ tx, args }) => {
492
+ const user = await tx.run(zql.user.where('id', args.id).one());
493
+ if (user) {
494
+ const currentViews = user.views ?? [];
495
+ const existingView = currentViews.find(x => x.id === args.view.id);
496
+ if (!existingView && currentViews.length < marcoConstants.user.views.maxViews) {
497
+ await tx.mutate.user.update({
498
+ id: args.id,
499
+ views: [...currentViews, { ...args.view, aliasEmails: [...new Set(args.view.aliasEmails)] }],
500
+ });
501
+ }
502
+ }
503
+ }),
490
504
  deleteSettingsPushNotificationToken: defineMutator(mutatorSchemas.user.deleteSettingsPushNotificationToken, async ({ tx, args }) => {
491
505
  await tx.mutate.userPushNotificationToken.delete({
492
506
  id: args.id,
493
507
  });
494
508
  }),
509
+ deleteView: defineMutator(mutatorSchemas.user.deleteView, async ({ tx, args }) => {
510
+ const user = await tx.run(zql.user.where('id', args.id).one());
511
+ if (user) {
512
+ await tx.mutate.user.update({
513
+ id: args.id,
514
+ views: (user.views ?? []).filter(x => x.id !== args.viewId),
515
+ });
516
+ }
517
+ }),
495
518
  setSettingsName: defineMutator(mutatorSchemas.user.setSettingsName, async ({ tx, args }) => {
496
519
  await tx.mutate.user.update({
497
520
  id: args.id,
@@ -509,5 +532,20 @@ export const mutators = defineMutators({
509
532
  });
510
533
  }
511
534
  }),
535
+ updateView: defineMutator(mutatorSchemas.user.updateView, async ({ tx, args }) => {
536
+ const user = await tx.run(zql.user.where('id', args.id).one());
537
+ if (user) {
538
+ const viewExists = (user.views ?? []).some(x => x.id === args.viewId);
539
+ if (viewExists) {
540
+ const deduplicatedEmails = args.updates.aliasEmails ? [...new Set(args.updates.aliasEmails)] : undefined;
541
+ await tx.mutate.user.update({
542
+ id: args.id,
543
+ views: (user.views ?? []).map(x => x.id === args.viewId
544
+ ? { ...x, aliasEmails: deduplicatedEmails ?? x.aliasEmails, name: args.updates.name ?? x.name }
545
+ : x),
546
+ });
547
+ }
548
+ }
549
+ }),
512
550
  },
513
551
  });
@@ -1,4 +1,6 @@
1
1
  import { beforeEach, describe, expect, it, mock } from 'bun:test';
2
+ import * as v from 'valibot';
3
+ import { mutatorSchemas } from './mutatorSchemas';
2
4
  import { mutators } from './mutators';
3
5
  const createMockTx = () => ({
4
6
  mutate: {
@@ -605,5 +607,203 @@ describe('mutators', () => {
605
607
  expect(tx.mutate.userPushNotificationToken.insert).not.toHaveBeenCalled();
606
608
  });
607
609
  });
610
+ describe('createView', () => {
611
+ it('creates a new view', async () => {
612
+ tx.run = mock(() => Promise.resolve({ id: 'user-1', views: [] }));
613
+ const args = {
614
+ id: 'user-1',
615
+ view: { aliasEmails: ['a@example.com', 'b@example.com'], id: 'view-1', name: 'Work' },
616
+ };
617
+ await mutators.user.createView.fn({ args, ctx, tx: tx });
618
+ expect(tx.mutate.user.update).toHaveBeenCalledWith({
619
+ id: 'user-1',
620
+ views: [{ aliasEmails: ['a@example.com', 'b@example.com'], id: 'view-1', name: 'Work' }],
621
+ });
622
+ });
623
+ it('deduplicates aliasEmails', async () => {
624
+ tx.run = mock(() => Promise.resolve({ id: 'user-1', views: [] }));
625
+ const args = {
626
+ id: 'user-1',
627
+ view: { aliasEmails: ['a@example.com', 'b@example.com', 'a@example.com'], id: 'view-1', name: 'Work' },
628
+ };
629
+ await mutators.user.createView.fn({ args, ctx, tx: tx });
630
+ expect(tx.mutate.user.update).toHaveBeenCalledWith({
631
+ id: 'user-1',
632
+ views: [{ aliasEmails: ['a@example.com', 'b@example.com'], id: 'view-1', name: 'Work' }],
633
+ });
634
+ });
635
+ it('appends to existing views', async () => {
636
+ tx.run = mock(() => Promise.resolve({
637
+ id: 'user-1',
638
+ views: [{ aliasEmails: ['x@example.com'], id: 'view-0', name: 'Personal' }],
639
+ }));
640
+ const args = {
641
+ id: 'user-1',
642
+ view: { aliasEmails: ['a@example.com'], id: 'view-1', name: 'Work' },
643
+ };
644
+ await mutators.user.createView.fn({ args, ctx, tx: tx });
645
+ expect(tx.mutate.user.update).toHaveBeenCalledWith({
646
+ id: 'user-1',
647
+ views: [
648
+ { aliasEmails: ['x@example.com'], id: 'view-0', name: 'Personal' },
649
+ { aliasEmails: ['a@example.com'], id: 'view-1', name: 'Work' },
650
+ ],
651
+ });
652
+ });
653
+ it('does nothing if user not found', async () => {
654
+ tx.run = mock(() => Promise.resolve(null));
655
+ const args = {
656
+ id: 'user-1',
657
+ view: { aliasEmails: ['a@example.com'], id: 'view-1', name: 'Work' },
658
+ };
659
+ await mutators.user.createView.fn({ args, ctx, tx: tx });
660
+ expect(tx.mutate.user.update).not.toHaveBeenCalled();
661
+ });
662
+ it('does nothing if view with same id already exists', async () => {
663
+ tx.run = mock(() => Promise.resolve({
664
+ id: 'user-1',
665
+ views: [{ aliasEmails: ['x@example.com'], id: 'view-1', name: 'Existing' }],
666
+ }));
667
+ const args = {
668
+ id: 'user-1',
669
+ view: { aliasEmails: ['a@example.com'], id: 'view-1', name: 'Work' },
670
+ };
671
+ await mutators.user.createView.fn({ args, ctx, tx: tx });
672
+ expect(tx.mutate.user.update).not.toHaveBeenCalled();
673
+ });
674
+ it('does nothing if max views limit reached', async () => {
675
+ const existingViews = Array.from({ length: 25 }, (_, i) => ({
676
+ aliasEmails: [`email${i}@example.com`],
677
+ id: `view-${i}`,
678
+ name: `View ${i}`,
679
+ }));
680
+ tx.run = mock(() => Promise.resolve({ id: 'user-1', views: existingViews }));
681
+ const args = {
682
+ id: 'user-1',
683
+ view: { aliasEmails: ['new@example.com'], id: 'view-new', name: 'New View' },
684
+ };
685
+ await mutators.user.createView.fn({ args, ctx, tx: tx });
686
+ expect(tx.mutate.user.update).not.toHaveBeenCalled();
687
+ });
688
+ });
689
+ describe('deleteView', () => {
690
+ it('deletes a view', async () => {
691
+ tx.run = mock(() => Promise.resolve({
692
+ id: 'user-1',
693
+ views: [
694
+ { aliasEmails: ['a@example.com'], id: 'view-1', name: 'Work' },
695
+ { aliasEmails: ['b@example.com'], id: 'view-2', name: 'Personal' },
696
+ ],
697
+ }));
698
+ const args = { id: 'user-1', viewId: 'view-1' };
699
+ await mutators.user.deleteView.fn({ args, ctx, tx: tx });
700
+ expect(tx.mutate.user.update).toHaveBeenCalledWith({
701
+ id: 'user-1',
702
+ views: [{ aliasEmails: ['b@example.com'], id: 'view-2', name: 'Personal' }],
703
+ });
704
+ });
705
+ it('does nothing if user not found', async () => {
706
+ tx.run = mock(() => Promise.resolve(null));
707
+ const args = { id: 'user-1', viewId: 'view-1' };
708
+ await mutators.user.deleteView.fn({ args, ctx, tx: tx });
709
+ expect(tx.mutate.user.update).not.toHaveBeenCalled();
710
+ });
711
+ });
712
+ describe('updateView', () => {
713
+ it('updates view name', async () => {
714
+ tx.run = mock(() => Promise.resolve({
715
+ id: 'user-1',
716
+ views: [{ aliasEmails: ['a@example.com'], id: 'view-1', name: 'Work' }],
717
+ }));
718
+ const args = { id: 'user-1', updates: { name: 'Updated Work' }, viewId: 'view-1' };
719
+ await mutators.user.updateView.fn({ args, ctx, tx: tx });
720
+ expect(tx.mutate.user.update).toHaveBeenCalledWith({
721
+ id: 'user-1',
722
+ views: [{ aliasEmails: ['a@example.com'], id: 'view-1', name: 'Updated Work' }],
723
+ });
724
+ });
725
+ it('updates view aliasEmails', async () => {
726
+ tx.run = mock(() => Promise.resolve({
727
+ id: 'user-1',
728
+ views: [{ aliasEmails: ['a@example.com'], id: 'view-1', name: 'Work' }],
729
+ }));
730
+ const args = { id: 'user-1', updates: { aliasEmails: ['x@example.com', 'y@example.com'] }, viewId: 'view-1' };
731
+ await mutators.user.updateView.fn({ args, ctx, tx: tx });
732
+ expect(tx.mutate.user.update).toHaveBeenCalledWith({
733
+ id: 'user-1',
734
+ views: [{ aliasEmails: ['x@example.com', 'y@example.com'], id: 'view-1', name: 'Work' }],
735
+ });
736
+ });
737
+ it('deduplicates aliasEmails on update', async () => {
738
+ tx.run = mock(() => Promise.resolve({
739
+ id: 'user-1',
740
+ views: [{ aliasEmails: ['a@example.com'], id: 'view-1', name: 'Work' }],
741
+ }));
742
+ const args = {
743
+ id: 'user-1',
744
+ updates: { aliasEmails: ['x@example.com', 'y@example.com', 'x@example.com'] },
745
+ viewId: 'view-1',
746
+ };
747
+ await mutators.user.updateView.fn({ args, ctx, tx: tx });
748
+ expect(tx.mutate.user.update).toHaveBeenCalledWith({
749
+ id: 'user-1',
750
+ views: [{ aliasEmails: ['x@example.com', 'y@example.com'], id: 'view-1', name: 'Work' }],
751
+ });
752
+ });
753
+ it('updates both name and aliasEmails', async () => {
754
+ tx.run = mock(() => Promise.resolve({
755
+ id: 'user-1',
756
+ views: [{ aliasEmails: ['a@example.com'], id: 'view-1', name: 'Work' }],
757
+ }));
758
+ const args = {
759
+ id: 'user-1',
760
+ updates: { aliasEmails: ['x@example.com'], name: 'New Name' },
761
+ viewId: 'view-1',
762
+ };
763
+ await mutators.user.updateView.fn({ args, ctx, tx: tx });
764
+ expect(tx.mutate.user.update).toHaveBeenCalledWith({
765
+ id: 'user-1',
766
+ views: [{ aliasEmails: ['x@example.com'], id: 'view-1', name: 'New Name' }],
767
+ });
768
+ });
769
+ it('does nothing if user not found', async () => {
770
+ tx.run = mock(() => Promise.resolve(null));
771
+ const args = { id: 'user-1', updates: { name: 'Updated' }, viewId: 'view-1' };
772
+ await mutators.user.updateView.fn({ args, ctx, tx: tx });
773
+ expect(tx.mutate.user.update).not.toHaveBeenCalled();
774
+ });
775
+ it('does nothing if view does not exist', async () => {
776
+ tx.run = mock(() => Promise.resolve({
777
+ id: 'user-1',
778
+ views: [{ aliasEmails: ['a@example.com'], id: 'view-1', name: 'Work' }],
779
+ }));
780
+ const args = { id: 'user-1', updates: { name: 'Updated' }, viewId: 'non-existent-view' };
781
+ await mutators.user.updateView.fn({ args, ctx, tx: tx });
782
+ expect(tx.mutate.user.update).not.toHaveBeenCalled();
783
+ });
784
+ it('leaves non-matching views unchanged', async () => {
785
+ tx.run = mock(() => Promise.resolve({
786
+ id: 'user-1',
787
+ views: [
788
+ { aliasEmails: ['a@example.com'], id: 'view-1', name: 'Work' },
789
+ { aliasEmails: ['b@example.com'], id: 'view-2', name: 'Personal' },
790
+ ],
791
+ }));
792
+ const args = { id: 'user-1', updates: { name: 'Updated Work' }, viewId: 'view-1' };
793
+ await mutators.user.updateView.fn({ args, ctx, tx: tx });
794
+ expect(tx.mutate.user.update).toHaveBeenCalledWith({
795
+ id: 'user-1',
796
+ views: [
797
+ { aliasEmails: ['a@example.com'], id: 'view-1', name: 'Updated Work' },
798
+ { aliasEmails: ['b@example.com'], id: 'view-2', name: 'Personal' },
799
+ ],
800
+ });
801
+ });
802
+ it('schema rejects empty aliasEmails array', () => {
803
+ const args = { id: 'user-1', updates: { aliasEmails: [] }, viewId: 'view-1' };
804
+ const result = v.safeParse(mutatorSchemas.user.updateView, args);
805
+ expect(result.success).toBe(false);
806
+ });
807
+ });
608
808
  });
609
809
  });
@@ -267,6 +267,11 @@ export declare const queries: import("@rocicorp/zero").QueryRegistry<{
267
267
  readonly name: string | null;
268
268
  readonly profilePicture: string | null;
269
269
  readonly undoSendEnabled: boolean;
270
+ readonly views: {
271
+ id: string;
272
+ name: string;
273
+ aliasEmails: string[];
274
+ }[];
270
275
  } & {
271
276
  readonly accounts: readonly ({
272
277
  readonly color: string;
@@ -345,6 +350,15 @@ export declare const queries: import("@rocicorp/zero").QueryRegistry<{
345
350
  } & {
346
351
  serverName: string;
347
352
  };
353
+ readonly views: {
354
+ type: "json";
355
+ optional: false;
356
+ customType: {
357
+ id: string;
358
+ name: string;
359
+ aliasEmails: string[];
360
+ }[];
361
+ };
348
362
  };
349
363
  primaryKey: readonly [string, ...string[]];
350
364
  } & {
@@ -1083,31 +1097,31 @@ export declare const queries: import("@rocicorp/zero").QueryRegistry<{
1083
1097
  relationships: {
1084
1098
  readonly user: {
1085
1099
  accounts: [{
1086
- readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1100
+ readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1087
1101
  readonly destField: readonly ("id" | "userId" | "color" | "displayName" | "imapConnectionStatus" | "mailProcessedCount" | "mailTotalCount" | "primaryAliasId")[];
1088
1102
  readonly destSchema: "account";
1089
1103
  readonly cardinality: "many";
1090
1104
  }];
1091
1105
  contacts: [{
1092
- readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1106
+ readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1093
1107
  readonly destField: readonly ("id" | "name" | "userId" | "emailAddress")[];
1094
1108
  readonly destSchema: "contact";
1095
1109
  readonly cardinality: "many";
1096
1110
  }];
1097
1111
  drafts: [{
1098
- readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1112
+ readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1099
1113
  readonly destField: readonly ("type" | "status" | "id" | "userId" | "accountId" | "fromAliasId" | "fromEmail" | "fromName" | "referencedMessageId" | "scheduledFor" | "updatedAt" | "body" | "error" | "subject")[];
1100
1114
  readonly destSchema: "draft";
1101
1115
  readonly cardinality: "many";
1102
1116
  }];
1103
1117
  pushNotificationTokens: [{
1104
- readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1118
+ readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1105
1119
  readonly destField: readonly ("id" | "createdAt" | "token" | "userId")[];
1106
1120
  readonly destSchema: "userPushNotificationToken";
1107
1121
  readonly cardinality: "many";
1108
1122
  }];
1109
1123
  threads: [{
1110
- readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1124
+ readonly sourceField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1111
1125
  readonly destField: readonly ("id" | "userId" | "accountId" | "subject" | "flagged" | "hasAttachments" | "labelIdList" | "latestMessageDate" | "latestMessageId" | "messageCount" | "previewText" | "seen" | "senderEmail" | "senderName" | "words")[];
1112
1126
  readonly destSchema: "thread";
1113
1127
  readonly cardinality: "many";
@@ -1116,7 +1130,7 @@ export declare const queries: import("@rocicorp/zero").QueryRegistry<{
1116
1130
  readonly userPushNotificationToken: {
1117
1131
  user: [{
1118
1132
  readonly sourceField: readonly ("id" | "createdAt" | "token" | "userId")[];
1119
- readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1133
+ readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1120
1134
  readonly destSchema: "user";
1121
1135
  readonly cardinality: "one";
1122
1136
  }];
@@ -1124,7 +1138,7 @@ export declare const queries: import("@rocicorp/zero").QueryRegistry<{
1124
1138
  readonly contact: {
1125
1139
  user: [{
1126
1140
  readonly sourceField: readonly ("id" | "name" | "userId" | "emailAddress")[];
1127
- readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1141
+ readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1128
1142
  readonly destSchema: "user";
1129
1143
  readonly cardinality: "one";
1130
1144
  }];
@@ -1162,7 +1176,7 @@ export declare const queries: import("@rocicorp/zero").QueryRegistry<{
1162
1176
  }];
1163
1177
  user: [{
1164
1178
  readonly sourceField: readonly ("id" | "userId" | "color" | "displayName" | "imapConnectionStatus" | "mailProcessedCount" | "mailTotalCount" | "primaryAliasId")[];
1165
- readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1179
+ readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1166
1180
  readonly destSchema: "user";
1167
1181
  readonly cardinality: "one";
1168
1182
  }];
@@ -1209,7 +1223,7 @@ export declare const queries: import("@rocicorp/zero").QueryRegistry<{
1209
1223
  }];
1210
1224
  user: [{
1211
1225
  readonly sourceField: readonly ("type" | "status" | "id" | "userId" | "accountId" | "fromAliasId" | "fromEmail" | "fromName" | "referencedMessageId" | "scheduledFor" | "updatedAt" | "body" | "error" | "subject")[];
1212
- readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1226
+ readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1213
1227
  readonly destSchema: "user";
1214
1228
  readonly cardinality: "one";
1215
1229
  }];
@@ -1254,7 +1268,7 @@ export declare const queries: import("@rocicorp/zero").QueryRegistry<{
1254
1268
  }];
1255
1269
  user: [{
1256
1270
  readonly sourceField: readonly ("id" | "userId" | "accountId" | "subject" | "flagged" | "hasAttachments" | "labelIdList" | "latestMessageDate" | "latestMessageId" | "messageCount" | "previewText" | "seen" | "senderEmail" | "senderName" | "words")[];
1257
- readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name")[];
1271
+ readonly destField: readonly ("id" | "profilePicture" | "undoSendEnabled" | "name" | "views")[];
1258
1272
  readonly destSchema: "user";
1259
1273
  readonly cardinality: "one";
1260
1274
  }];
@@ -1 +1 @@
1
- {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/zero/queries.ts"],"names":[],"mappings":"AAOA,KAAK,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAQjC,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoOlB,CAAA"}
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/zero/queries.ts"],"names":[],"mappings":"AAOA,KAAK,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAQjC,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoOlB,CAAA"}