@devwithbobby/loops 0.1.13 → 0.1.14

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,30 +1,10 @@
1
1
  import { z } from "zod";
2
+ import { internalLib } from "../types";
2
3
  import { za, zm, zq } from "../utils.js";
3
- import { internal } from "./_generated/api";
4
4
  import type { Doc } from "./_generated/dataModel.js";
5
+ import { loopsFetch, sanitizeLoopsError } from "./helpers";
5
6
  import { contactValidator } from "./validators.js";
6
7
 
7
- const LOOPS_API_BASE_URL = "https://app.loops.so/api/v1";
8
-
9
- /**
10
- * Sanitize error messages to avoid leaking sensitive information
11
- */
12
- const sanitizeError = (status: number, _errorText: string): Error => {
13
- if (status === 401 || status === 403) {
14
- return new Error("Authentication failed. Please check your API key.");
15
- }
16
- if (status === 404) {
17
- return new Error("Resource not found.");
18
- }
19
- if (status === 429) {
20
- return new Error("Rate limit exceeded. Please try again later.");
21
- }
22
- if (status >= 500) {
23
- return new Error("Loops service error. Please try again later.");
24
- }
25
- return new Error(`Loops API error (${status}). Please try again.`);
26
- };
27
-
28
8
  /**
29
9
  * Internal mutation to store/update a contact in the database
30
10
  */
@@ -113,23 +93,24 @@ export const logEmailOperation = zm({
113
93
  }),
114
94
  returns: z.void(),
115
95
  handler: async (ctx, args) => {
116
- const operationData: Record<string, any> = {
96
+ const operationData: Omit<
97
+ Doc<"emailOperations">,
98
+ "_id" | "_creationTime"
99
+ > = {
117
100
  operationType: args.operationType,
118
101
  email: args.email,
119
102
  timestamp: Date.now(),
120
103
  success: args.success,
104
+ actorId: args.actorId,
105
+ transactionalId: args.transactionalId,
106
+ campaignId: args.campaignId,
107
+ loopId: args.loopId,
108
+ eventName: args.eventName,
109
+ messageId: args.messageId,
110
+ metadata: args.metadata,
121
111
  };
122
112
 
123
- if (args.actorId) operationData.actorId = args.actorId;
124
- if (args.transactionalId)
125
- operationData.transactionalId = args.transactionalId;
126
- if (args.campaignId) operationData.campaignId = args.campaignId;
127
- if (args.loopId) operationData.loopId = args.loopId;
128
- if (args.eventName) operationData.eventName = args.eventName;
129
- if (args.messageId) operationData.messageId = args.messageId;
130
- if (args.metadata) operationData.metadata = args.metadata;
131
-
132
- await ctx.db.insert("emailOperations", operationData as any);
113
+ await ctx.db.insert("emailOperations", operationData);
133
114
  },
134
115
  });
135
116
 
@@ -284,32 +265,23 @@ export const addContact = za({
284
265
  id: z.string().optional(),
285
266
  }),
286
267
  handler: async (ctx, args) => {
287
- const response = await fetch(`${LOOPS_API_BASE_URL}/contacts/create`, {
268
+ const createResponse = await loopsFetch(args.apiKey, "/contacts/create", {
288
269
  method: "POST",
289
- headers: {
290
- Authorization: `Bearer ${args.apiKey}`,
291
- "Content-Type": "application/json",
292
- },
293
- body: JSON.stringify(args.contact),
270
+ json: args.contact,
294
271
  });
295
272
 
296
- if (!response.ok) {
297
- const errorText = await response.text();
273
+ if (!createResponse.ok) {
274
+ const errorText = await createResponse.text();
298
275
 
299
- if (response.status === 409) {
276
+ if (createResponse.status === 409) {
300
277
  console.log(
301
278
  `Contact ${args.contact.email} already exists, updating instead`,
302
279
  );
303
280
 
304
- const findResponse = await fetch(
305
- `${LOOPS_API_BASE_URL}/contacts/find?email=${encodeURIComponent(args.contact.email)}`,
306
- {
307
- method: "GET",
308
- headers: {
309
- Authorization: `Bearer ${args.apiKey}`,
310
- "Content-Type": "application/json",
311
- },
312
- },
281
+ const findResponse = await loopsFetch(
282
+ args.apiKey,
283
+ `/contacts/find?email=${encodeURIComponent(args.contact.email)}`,
284
+ { method: "GET" },
313
285
  );
314
286
 
315
287
  if (!findResponse.ok) {
@@ -320,15 +292,12 @@ export const addContact = za({
320
292
  );
321
293
  }
322
294
 
323
- const updateResponse = await fetch(
324
- `${LOOPS_API_BASE_URL}/contacts/update`,
295
+ const updateResponse = await loopsFetch(
296
+ args.apiKey,
297
+ "/contacts/update",
325
298
  {
326
299
  method: "PUT",
327
- headers: {
328
- Authorization: `Bearer ${args.apiKey}`,
329
- "Content-Type": "application/json",
330
- },
331
- body: JSON.stringify({
300
+ json: {
332
301
  email: args.contact.email,
333
302
  firstName: args.contact.firstName,
334
303
  lastName: args.contact.lastName,
@@ -336,7 +305,7 @@ export const addContact = za({
336
305
  source: args.contact.source,
337
306
  subscribed: args.contact.subscribed,
338
307
  userGroup: args.contact.userGroup,
339
- }),
308
+ },
340
309
  },
341
310
  );
342
311
 
@@ -346,7 +315,7 @@ export const addContact = za({
346
315
  `Loops API error [${updateResponse.status}]:`,
347
316
  updateErrorText,
348
317
  );
349
- throw sanitizeError(updateResponse.status, updateErrorText);
318
+ throw sanitizeLoopsError(updateResponse.status, updateErrorText);
350
319
  }
351
320
 
352
321
  // Get contact ID if available
@@ -357,7 +326,7 @@ export const addContact = za({
357
326
  }
358
327
 
359
328
  // Store/update in our database
360
- await ctx.runMutation((internal as any).lib.storeContact as any, {
329
+ await ctx.runMutation(internalLib.storeContact, {
361
330
  email: args.contact.email,
362
331
  firstName: args.contact.firstName,
363
332
  lastName: args.contact.lastName,
@@ -374,15 +343,14 @@ export const addContact = za({
374
343
  };
375
344
  }
376
345
 
377
- // For other errors, throw as normal
378
- console.error(`Loops API error [${response.status}]:`, errorText);
379
- throw sanitizeError(response.status, errorText);
346
+ console.error(`Loops API error [${createResponse.status}]:`, errorText);
347
+ throw sanitizeLoopsError(createResponse.status, errorText);
380
348
  }
381
349
 
382
350
  // Contact was created successfully
383
- const data = (await response.json()) as { id?: string };
351
+ const data = (await createResponse.json()) as { id?: string };
384
352
 
385
- await ctx.runMutation((internal as any).lib.storeContact as any, {
353
+ await ctx.runMutation(internalLib.storeContact, {
386
354
  email: args.contact.email,
387
355
  firstName: args.contact.firstName,
388
356
  lastName: args.contact.lastName,
@@ -419,13 +387,9 @@ export const updateContact = za({
419
387
  success: z.boolean(),
420
388
  }),
421
389
  handler: async (ctx, args) => {
422
- const response = await fetch(`${LOOPS_API_BASE_URL}/contacts/update`, {
390
+ const response = await loopsFetch(args.apiKey, "/contacts/update", {
423
391
  method: "PUT",
424
- headers: {
425
- Authorization: `Bearer ${args.apiKey}`,
426
- "Content-Type": "application/json",
427
- },
428
- body: JSON.stringify({
392
+ json: {
429
393
  email: args.email,
430
394
  dataVariables: args.dataVariables,
431
395
  firstName: args.firstName,
@@ -434,16 +398,16 @@ export const updateContact = za({
434
398
  source: args.source,
435
399
  subscribed: args.subscribed,
436
400
  userGroup: args.userGroup,
437
- }),
401
+ },
438
402
  });
439
403
 
440
404
  if (!response.ok) {
441
405
  const errorText = await response.text();
442
406
  console.error(`Loops API error [${response.status}]:`, errorText);
443
- throw sanitizeError(response.status, errorText);
407
+ throw sanitizeLoopsError(response.status, errorText);
444
408
  }
445
409
 
446
- await ctx.runMutation((internal as any).lib.storeContact as any, {
410
+ await ctx.runMutation(internalLib.storeContact, {
447
411
  email: args.email,
448
412
  firstName: args.firstName,
449
413
  lastName: args.lastName,
@@ -472,35 +436,31 @@ export const sendTransactional = za({
472
436
  messageId: z.string().optional(),
473
437
  }),
474
438
  handler: async (ctx, args) => {
475
- const response = await fetch(`${LOOPS_API_BASE_URL}/transactional`, {
439
+ const response = await loopsFetch(args.apiKey, "/transactional", {
476
440
  method: "POST",
477
- headers: {
478
- Authorization: `Bearer ${args.apiKey}`,
479
- "Content-Type": "application/json",
480
- },
481
- body: JSON.stringify({
441
+ json: {
482
442
  transactionalId: args.transactionalId,
483
443
  email: args.email,
484
444
  dataVariables: args.dataVariables,
485
- }),
445
+ },
486
446
  });
487
447
 
488
448
  if (!response.ok) {
489
449
  const errorText = await response.text();
490
450
  console.error(`Loops API error [${response.status}]:`, errorText);
491
- await ctx.runMutation((internal as any).lib.logEmailOperation as any, {
451
+ await ctx.runMutation(internalLib.logEmailOperation, {
492
452
  operationType: "transactional",
493
453
  email: args.email,
494
454
  success: false,
495
455
  transactionalId: args.transactionalId,
496
456
  });
497
457
 
498
- throw sanitizeError(response.status, errorText);
458
+ throw sanitizeLoopsError(response.status, errorText);
499
459
  }
500
460
 
501
461
  const data = (await response.json()) as { messageId?: string };
502
462
 
503
- await ctx.runMutation((internal as any).lib.logEmailOperation as any, {
463
+ await ctx.runMutation(internalLib.logEmailOperation, {
504
464
  operationType: "transactional",
505
465
  email: args.email,
506
466
  success: true,
@@ -529,23 +489,19 @@ export const sendEvent = za({
529
489
  success: z.boolean(),
530
490
  }),
531
491
  handler: async (_ctx, args) => {
532
- const response = await fetch(`${LOOPS_API_BASE_URL}/events/send`, {
492
+ const response = await loopsFetch(args.apiKey, "/events/send", {
533
493
  method: "POST",
534
- headers: {
535
- Authorization: `Bearer ${args.apiKey}`,
536
- "Content-Type": "application/json",
537
- },
538
- body: JSON.stringify({
494
+ json: {
539
495
  email: args.email,
540
496
  eventName: args.eventName,
541
497
  eventProperties: args.eventProperties,
542
- }),
498
+ },
543
499
  });
544
500
 
545
501
  if (!response.ok) {
546
502
  const errorText = await response.text();
547
503
  console.error(`Loops API error [${response.status}]:`, errorText);
548
- throw sanitizeError(response.status, errorText);
504
+ throw sanitizeLoopsError(response.status, errorText);
549
505
  }
550
506
 
551
507
  return { success: true };
@@ -564,22 +520,18 @@ export const deleteContact = za({
564
520
  success: z.boolean(),
565
521
  }),
566
522
  handler: async (ctx, args) => {
567
- const response = await fetch(`${LOOPS_API_BASE_URL}/contacts/delete`, {
523
+ const response = await loopsFetch(args.apiKey, "/contacts/delete", {
568
524
  method: "POST",
569
- headers: {
570
- Authorization: `Bearer ${args.apiKey}`,
571
- "Content-Type": "application/json",
572
- },
573
- body: JSON.stringify({ email: args.email }),
525
+ json: { email: args.email },
574
526
  });
575
527
 
576
528
  if (!response.ok) {
577
529
  const errorText = await response.text();
578
530
  console.error(`Loops API error [${response.status}]:`, errorText);
579
- throw sanitizeError(response.status, errorText);
531
+ throw sanitizeLoopsError(response.status, errorText);
580
532
  }
581
533
 
582
- await ctx.runMutation((internal as any).lib.removeContact as any, {
534
+ await ctx.runMutation(internalLib.removeContact, {
583
535
  email: args.email,
584
536
  });
585
537
 
@@ -623,7 +575,7 @@ export const triggerLoop = za({
623
575
 
624
576
  try {
625
577
  // Send event to trigger the loop
626
- await ctx.runAction((internal as any).lib.sendEvent as any, {
578
+ await ctx.runAction(internalLib.sendEvent, {
627
579
  apiKey: args.apiKey,
628
580
  email: args.email,
629
581
  eventName,
@@ -634,7 +586,7 @@ export const triggerLoop = za({
634
586
  });
635
587
 
636
588
  // Log as loop operation
637
- await ctx.runMutation((internal as any).lib.logEmailOperation as any, {
589
+ await ctx.runMutation(internalLib.logEmailOperation, {
638
590
  operationType: "loop",
639
591
  email: args.email,
640
592
  success: true,
@@ -649,7 +601,7 @@ export const triggerLoop = za({
649
601
  };
650
602
  } catch (error) {
651
603
  // Log failed loop operation
652
- await ctx.runMutation((internal as any).lib.logEmailOperation as any, {
604
+ await ctx.runMutation(internalLib.logEmailOperation, {
653
605
  operationType: "loop",
654
606
  email: args.email,
655
607
  success: false,
@@ -692,15 +644,10 @@ export const findContact = za({
692
644
  .optional(),
693
645
  }),
694
646
  handler: async (_ctx, args) => {
695
- const response = await fetch(
696
- `${LOOPS_API_BASE_URL}/contacts/find?email=${encodeURIComponent(args.email)}`,
697
- {
698
- method: "GET",
699
- headers: {
700
- Authorization: `Bearer ${args.apiKey}`,
701
- "Content-Type": "application/json",
702
- },
703
- },
647
+ const response = await loopsFetch(
648
+ args.apiKey,
649
+ `/contacts/find?email=${encodeURIComponent(args.email)}`,
650
+ { method: "GET" },
704
651
  );
705
652
 
706
653
  if (!response.ok) {
@@ -709,12 +656,13 @@ export const findContact = za({
709
656
  }
710
657
  const errorText = await response.text();
711
658
  console.error(`Loops API error [${response.status}]:`, errorText);
712
- throw sanitizeError(response.status, errorText);
659
+ throw sanitizeLoopsError(response.status, errorText);
713
660
  }
714
661
 
662
+ type LoopsContactRecord = Record<string, unknown>;
715
663
  const data = (await response.json()) as
716
- | Record<string, any>
717
- | Array<Record<string, any>>;
664
+ | LoopsContactRecord
665
+ | Array<LoopsContactRecord>;
718
666
 
719
667
  // Handle case where Loops returns an array instead of a single object
720
668
  let contact = Array.isArray(data) ? data[0] : data;
@@ -726,12 +674,12 @@ export const findContact = za({
726
674
  key,
727
675
  value === null ? undefined : value,
728
676
  ]),
729
- ) as Record<string, any>;
677
+ ) as LoopsContactRecord;
730
678
  }
731
679
 
732
680
  return {
733
681
  success: true,
734
- contact: contact as Record<string, any> | undefined,
682
+ contact: contact as LoopsContactRecord | undefined,
735
683
  };
736
684
  },
737
685
  });
@@ -770,13 +718,10 @@ export const batchCreateContacts = za({
770
718
  for (const contact of args.contacts) {
771
719
  try {
772
720
  // Use the addContact function which handles create/update logic
773
- const result = await ctx.runAction(
774
- (internal as any).lib.addContact as any,
775
- {
776
- apiKey: args.apiKey,
777
- contact,
778
- },
779
- );
721
+ const result = await ctx.runAction(internalLib.addContact, {
722
+ apiKey: args.apiKey,
723
+ contact,
724
+ });
780
725
 
781
726
  if (result.success) {
782
727
  created++;
@@ -821,22 +766,18 @@ export const unsubscribeContact = za({
821
766
  success: z.boolean(),
822
767
  }),
823
768
  handler: async (ctx, args) => {
824
- const response = await fetch(`${LOOPS_API_BASE_URL}/contacts/unsubscribe`, {
769
+ const response = await loopsFetch(args.apiKey, "/contacts/unsubscribe", {
825
770
  method: "POST",
826
- headers: {
827
- Authorization: `Bearer ${args.apiKey}`,
828
- "Content-Type": "application/json",
829
- },
830
- body: JSON.stringify({ email: args.email }),
771
+ json: { email: args.email },
831
772
  });
832
773
 
833
774
  if (!response.ok) {
834
775
  const errorText = await response.text();
835
776
  console.error(`Loops API error [${response.status}]:`, errorText);
836
- throw sanitizeError(response.status, errorText);
777
+ throw sanitizeLoopsError(response.status, errorText);
837
778
  }
838
779
 
839
- await ctx.runMutation((internal as any).lib.storeContact as any, {
780
+ await ctx.runMutation(internalLib.storeContact, {
840
781
  email: args.email,
841
782
  subscribed: false,
842
783
  });
@@ -858,22 +799,18 @@ export const resubscribeContact = za({
858
799
  success: z.boolean(),
859
800
  }),
860
801
  handler: async (ctx, args) => {
861
- const response = await fetch(`${LOOPS_API_BASE_URL}/contacts/resubscribe`, {
802
+ const response = await loopsFetch(args.apiKey, "/contacts/resubscribe", {
862
803
  method: "POST",
863
- headers: {
864
- Authorization: `Bearer ${args.apiKey}`,
865
- "Content-Type": "application/json",
866
- },
867
- body: JSON.stringify({ email: args.email }),
804
+ json: { email: args.email },
868
805
  });
869
806
 
870
807
  if (!response.ok) {
871
808
  const errorText = await response.text();
872
809
  console.error(`Loops API error [${response.status}]:`, errorText);
873
- throw sanitizeError(response.status, errorText);
810
+ throw sanitizeLoopsError(response.status, errorText);
874
811
  }
875
812
 
876
- await ctx.runMutation((internal as any).lib.storeContact as any, {
813
+ await ctx.runMutation(internalLib.storeContact, {
877
814
  email: args.email,
878
815
  subscribed: true,
879
816
  });
package/src/types.ts ADDED
@@ -0,0 +1,168 @@
1
+ import type {
2
+ Expand,
3
+ FunctionArgs,
4
+ FunctionReference,
5
+ FunctionReturnType,
6
+ StorageActionWriter,
7
+ StorageReader,
8
+ } from "convex/server";
9
+ import type { GenericId } from "convex/values";
10
+ import { internal } from "./component/_generated/api";
11
+
12
+ export type RunQueryCtx = {
13
+ runQuery: <Query extends FunctionReference<"query", "internal">>(
14
+ query: Query,
15
+ args: FunctionArgs<Query>,
16
+ ) => Promise<FunctionReturnType<Query>>;
17
+ };
18
+
19
+ export type RunMutationCtx = RunQueryCtx & {
20
+ runMutation: <Mutation extends FunctionReference<"mutation", "internal">>(
21
+ mutation: Mutation,
22
+ args: FunctionArgs<Mutation>,
23
+ ) => Promise<FunctionReturnType<Mutation>>;
24
+ };
25
+
26
+ export type RunActionCtx = RunMutationCtx & {
27
+ runAction<Action extends FunctionReference<"action", "internal">>(
28
+ action: Action,
29
+ args: FunctionArgs<Action>,
30
+ ): Promise<FunctionReturnType<Action>>;
31
+ };
32
+
33
+ export type ActionCtx = RunActionCtx & {
34
+ storage: StorageActionWriter;
35
+ };
36
+
37
+ export type QueryCtx = RunQueryCtx & {
38
+ storage: StorageReader;
39
+ };
40
+
41
+ export type OpaqueIds<T> = T extends GenericId<infer _T>
42
+ ? string
43
+ : T extends (infer U)[]
44
+ ? OpaqueIds<U>[]
45
+ : T extends ArrayBuffer
46
+ ? ArrayBuffer
47
+ : T extends object
48
+ ? {
49
+ [K in keyof T]: OpaqueIds<T[K]>;
50
+ }
51
+ : T;
52
+
53
+ export type UseApi<API> = Expand<{
54
+ [mod in keyof API]: API[mod] extends FunctionReference<
55
+ infer FType,
56
+ "public",
57
+ infer FArgs,
58
+ infer FReturnType,
59
+ infer FComponentPath
60
+ >
61
+ ? FunctionReference<
62
+ FType,
63
+ "internal",
64
+ OpaqueIds<FArgs>,
65
+ OpaqueIds<FReturnType>,
66
+ FComponentPath
67
+ >
68
+ : UseApi<API[mod]>;
69
+ }>;
70
+
71
+ export type HeadersInitParam = ConstructorParameters<typeof Headers>[0];
72
+
73
+ export interface ContactPayload {
74
+ email: string;
75
+ firstName?: string;
76
+ lastName?: string;
77
+ userId?: string;
78
+ source?: string;
79
+ subscribed?: boolean;
80
+ userGroup?: string;
81
+ }
82
+
83
+ export interface UpdateContactPayload extends Partial<ContactPayload> {
84
+ email: string;
85
+ dataVariables?: Record<string, unknown>;
86
+ }
87
+
88
+ export interface DeleteContactPayload {
89
+ email?: string;
90
+ }
91
+
92
+ export interface TransactionalPayload {
93
+ transactionalId?: string;
94
+ email?: string;
95
+ dataVariables?: Record<string, unknown>;
96
+ }
97
+
98
+ export interface EventPayload {
99
+ email?: string;
100
+ eventName?: string;
101
+ eventProperties?: Record<string, unknown>;
102
+ }
103
+
104
+ export interface TriggerPayload {
105
+ loopId?: string;
106
+ email?: string;
107
+ dataVariables?: Record<string, unknown>;
108
+ eventName?: string;
109
+ }
110
+
111
+ export type InternalActionRef<
112
+ Args extends Record<string, unknown> = Record<string, unknown>,
113
+ Result = unknown,
114
+ > = FunctionReference<"action", "internal", Args, Result>;
115
+
116
+ export type InternalMutationRef<
117
+ Args extends Record<string, unknown> = Record<string, unknown>,
118
+ Result = unknown,
119
+ > = FunctionReference<"mutation", "internal", Args, Result>;
120
+
121
+ export type InternalQueryRef<
122
+ Args extends Record<string, unknown> = Record<string, unknown>,
123
+ Result = unknown,
124
+ > = FunctionReference<"query", "internal", Args, Result>;
125
+
126
+ type AddContactArgs = {
127
+ apiKey: string;
128
+ contact: ContactPayload;
129
+ };
130
+
131
+ type AddContactResult = {
132
+ success: boolean;
133
+ id?: string;
134
+ };
135
+
136
+ export interface InternalActionLib {
137
+ addContact: InternalActionRef<AddContactArgs, AddContactResult>;
138
+ updateContact: InternalActionRef;
139
+ findContact: InternalActionRef;
140
+ deleteContact: InternalActionRef;
141
+ sendTransactional: InternalActionRef;
142
+ sendEvent: InternalActionRef;
143
+ triggerLoop: InternalActionRef;
144
+ batchCreateContacts: InternalActionRef;
145
+ unsubscribeContact: InternalActionRef;
146
+ resubscribeContact: InternalActionRef;
147
+ storeContact: InternalMutationRef;
148
+ removeContact: InternalMutationRef;
149
+ logEmailOperation: InternalMutationRef;
150
+ }
151
+
152
+ export interface InternalQueryLib {
153
+ countContacts: InternalQueryRef;
154
+ listContacts: InternalQueryRef;
155
+ detectRecipientSpam: InternalQueryRef;
156
+ detectActorSpam: InternalQueryRef;
157
+ getEmailStats: InternalQueryRef;
158
+ detectRapidFirePatterns: InternalQueryRef;
159
+ checkRecipientRateLimit: InternalQueryRef;
160
+ checkActorRateLimit: InternalQueryRef;
161
+ checkGlobalRateLimit: InternalQueryRef;
162
+ }
163
+
164
+ export type InternalLibReferences = InternalActionLib & InternalQueryLib;
165
+
166
+ export const internalLib = (
167
+ internal as unknown as { lib: InternalLibReferences }
168
+ ).lib;
@@ -1,64 +0,0 @@
1
- import type {
2
- Expand,
3
- FunctionArgs,
4
- FunctionReference,
5
- FunctionReturnType,
6
- StorageActionWriter,
7
- StorageReader,
8
- } from "convex/server";
9
- import type { GenericId } from "convex/values";
10
-
11
- export type RunQueryCtx = {
12
- runQuery: <Query extends FunctionReference<"query", "internal">>(
13
- query: Query,
14
- args: FunctionArgs<Query>,
15
- ) => Promise<FunctionReturnType<Query>>;
16
- };
17
- export type RunMutationCtx = RunQueryCtx & {
18
- runMutation: <Mutation extends FunctionReference<"mutation", "internal">>(
19
- mutation: Mutation,
20
- args: FunctionArgs<Mutation>,
21
- ) => Promise<FunctionReturnType<Mutation>>;
22
- };
23
- export type RunActionCtx = RunMutationCtx & {
24
- runAction<Action extends FunctionReference<"action", "internal">>(
25
- action: Action,
26
- args: FunctionArgs<Action>,
27
- ): Promise<FunctionReturnType<Action>>;
28
- };
29
- export type ActionCtx = RunActionCtx & {
30
- storage: StorageActionWriter;
31
- };
32
- export type QueryCtx = RunQueryCtx & {
33
- storage: StorageReader;
34
- };
35
-
36
- export type OpaqueIds<T> = T extends GenericId<infer _T>
37
- ? string
38
- : T extends (infer U)[]
39
- ? OpaqueIds<U>[]
40
- : T extends ArrayBuffer
41
- ? ArrayBuffer
42
- : T extends object
43
- ? {
44
- [K in keyof T]: OpaqueIds<T[K]>;
45
- }
46
- : T;
47
-
48
- export type UseApi<API> = Expand<{
49
- [mod in keyof API]: API[mod] extends FunctionReference<
50
- infer FType,
51
- "public",
52
- infer FArgs,
53
- infer FReturnType,
54
- infer FComponentPath
55
- >
56
- ? FunctionReference<
57
- FType,
58
- "internal",
59
- OpaqueIds<FArgs>,
60
- OpaqueIds<FReturnType>,
61
- FComponentPath
62
- >
63
- : UseApi<API[mod]>;
64
- }>;