@convex-dev/workos-authkit 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +23 -0
  2. package/dist/client/index.d.ts +3 -0
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +24 -5
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/component/_generated/api.d.ts +592 -0
  7. package/dist/component/_generated/api.d.ts.map +1 -1
  8. package/dist/component/_generated/api.js.map +1 -1
  9. package/dist/component/_generated/component.d.ts +18 -7
  10. package/dist/component/_generated/component.d.ts.map +1 -1
  11. package/dist/component/backfill.d.ts +60 -0
  12. package/dist/component/backfill.d.ts.map +1 -0
  13. package/dist/component/backfill.js +171 -0
  14. package/dist/component/backfill.js.map +1 -0
  15. package/dist/component/convex.config.d.ts.map +1 -1
  16. package/dist/component/convex.config.js +2 -0
  17. package/dist/component/convex.config.js.map +1 -1
  18. package/dist/component/lib.d.ts +27 -10
  19. package/dist/component/lib.d.ts.map +1 -1
  20. package/dist/component/lib.js +93 -80
  21. package/dist/component/lib.js.map +1 -1
  22. package/dist/component/schema.d.ts +7 -2
  23. package/dist/component/schema.d.ts.map +1 -1
  24. package/dist/component/schema.js +3 -0
  25. package/dist/component/schema.js.map +1 -1
  26. package/package.json +10 -9
  27. package/src/client/index.ts +24 -5
  28. package/src/component/_generated/api.ts +518 -0
  29. package/src/component/_generated/component.ts +25 -12
  30. package/src/component/backfill.test.ts +335 -0
  31. package/src/component/backfill.ts +217 -0
  32. package/src/component/convex.config.ts +2 -0
  33. package/src/component/lib.ts +103 -81
  34. package/src/component/schema.ts +3 -0
  35. package/src/test.ts +4 -0
@@ -1,4 +1,4 @@
1
- import { v } from "convex/values";
1
+ import { type Infer, v } from "convex/values";
2
2
  import {
3
3
  internalAction,
4
4
  internalMutation,
@@ -6,6 +6,7 @@ import {
6
6
  mutation,
7
7
  query,
8
8
  } from "./_generated/server.js";
9
+ import type { MutationCtx } from "./_generated/server.js";
9
10
  import { components, internal } from "./_generated/api.js";
10
11
  import { omit, withoutSystemFields } from "convex-helpers";
11
12
  import { WorkOS, type Event as WorkOSEvent } from "@workos-inc/node";
@@ -18,7 +19,7 @@ const eventWorkpool = new Workpool(components.eventWorkpool, {
18
19
  maxParallelism: 1,
19
20
  });
20
21
 
21
- const vEvent = v.object({
22
+ export const vEvent = v.object({
22
23
  id: v.string(),
23
24
  createdAt: v.string(),
24
25
  event: v.string(),
@@ -26,24 +27,109 @@ const vEvent = v.object({
26
27
  context: v.optional(v.record(v.string(), v.any())),
27
28
  });
28
29
 
29
- export const enqueueWebhookEvent = mutation({
30
+ async function processEventHandler(
31
+ ctx: MutationCtx,
32
+ args: {
33
+ event: Infer<typeof vEvent>;
34
+ logLevel?: "DEBUG";
35
+ onEventHandle?: string;
36
+ }
37
+ ) {
38
+ if (args.logLevel === "DEBUG") {
39
+ console.log("processing event", args.event);
40
+ }
41
+ const dbEvent = await ctx.db
42
+ .query("events")
43
+ .withIndex("eventId", (q) => q.eq("eventId", args.event.id))
44
+ .unique();
45
+ if (dbEvent) {
46
+ console.log("event already processed", args.event.id);
47
+ return;
48
+ }
49
+ await ctx.db.insert("events", {
50
+ eventId: args.event.id,
51
+ event: args.event.event,
52
+ updatedAt: args.event.data.updatedAt as string | undefined,
53
+ });
54
+ const event = args.event as WorkOSEvent;
55
+ switch (event.event) {
56
+ case "user.created": {
57
+ const data = omit(event.data, ["object"]);
58
+ const existingUser = await ctx.db
59
+ .query("users")
60
+ .withIndex("id", (q) => q.eq("id", data.id))
61
+ .unique();
62
+ if (existingUser) {
63
+ console.warn("user already exists", data.id);
64
+ return;
65
+ }
66
+ await ctx.db.insert("users", data);
67
+ break;
68
+ }
69
+ case "user.updated": {
70
+ const data = omit(event.data, ["object"]);
71
+ const user = await ctx.db
72
+ .query("users")
73
+ .withIndex("id", (q) => q.eq("id", data.id))
74
+ .unique();
75
+ if (!user) {
76
+ console.error("user not found", data.id);
77
+ return;
78
+ }
79
+ if (user.updatedAt >= data.updatedAt) {
80
+ console.warn(`user already updated for event ${event.id}, skipping`);
81
+ return;
82
+ }
83
+ await ctx.db.patch(user._id, data);
84
+ break;
85
+ }
86
+ case "user.deleted": {
87
+ const data = omit(event.data, ["object"]);
88
+ const user = await ctx.db
89
+ .query("users")
90
+ .withIndex("id", (q) => q.eq("id", data.id))
91
+ .unique();
92
+ if (!user) {
93
+ console.warn("user not found", data.id);
94
+ return;
95
+ }
96
+ await ctx.db.delete(user._id);
97
+ break;
98
+ }
99
+ }
100
+ if (args.onEventHandle) {
101
+ await ctx.runMutation(args.onEventHandle as FunctionHandle<"mutation">, {
102
+ event: args.event.event,
103
+ data: args.event.data,
104
+ });
105
+ }
106
+ }
107
+
108
+ export const onWebhookEvent = mutation({
30
109
  args: {
31
110
  apiKey: v.string(),
32
- eventId: v.string(),
33
- event: v.string(),
34
- updatedAt: v.optional(v.string()),
111
+ event: vEvent,
35
112
  onEventHandle: v.optional(v.string()),
36
113
  eventTypes: v.optional(v.array(v.string())),
37
114
  logLevel: v.optional(v.literal("DEBUG")),
38
115
  },
116
+ returns: v.null(),
39
117
  handler: async (ctx, args) => {
40
- await eventWorkpool.cancelAll(ctx);
41
- await eventWorkpool.enqueueAction(ctx, internal.lib.updateEvents, {
42
- apiKey: args.apiKey,
43
- onEventHandle: args.onEventHandle,
44
- eventTypes: args.eventTypes,
45
- logLevel: args.logLevel,
46
- });
118
+ const isCreateEvent = args.event.event.endsWith(".created");
119
+
120
+ if (isCreateEvent) {
121
+ // Process create events immediately
122
+ await processEventHandler(ctx, args);
123
+ } else {
124
+ // Enqueue update/delete events to workpool
125
+ await eventWorkpool.enqueueAction(ctx, internal.lib.updateEvents, {
126
+ apiKey: args.apiKey,
127
+ onEventHandle: args.onEventHandle,
128
+ eventTypes: args.eventTypes,
129
+ logLevel: args.logLevel,
130
+ });
131
+ }
132
+ return null;
47
133
  },
48
134
  });
49
135
 
@@ -67,6 +153,9 @@ export const updateEvents = internalAction({
67
153
  logLevel: v.optional(v.literal("DEBUG")),
68
154
  },
69
155
  handler: async (ctx, args) => {
156
+ // Cancel other pending workpool jobs since this run will
157
+ // process all available events from the WorkOS API.
158
+ await eventWorkpool.cancelAll(ctx);
70
159
  const workos = new WorkOS(args.apiKey);
71
160
  const cursor = await ctx.runQuery(internal.lib.getCursor);
72
161
  let nextCursor = cursor ?? undefined;
@@ -107,74 +196,7 @@ export const processEvent = internalMutation({
107
196
  onEventHandle: v.optional(v.string()),
108
197
  },
109
198
  handler: async (ctx, args) => {
110
- if (args.logLevel === "DEBUG") {
111
- console.log("processing event", args.event);
112
- }
113
- const dbEvent = await ctx.db
114
- .query("events")
115
- .withIndex("eventId", (q) => q.eq("eventId", args.event.id))
116
- .unique();
117
- if (dbEvent) {
118
- console.log("event already processed", args.event.id);
119
- return;
120
- }
121
- await ctx.db.insert("events", {
122
- eventId: args.event.id,
123
- event: args.event.event,
124
- updatedAt: args.event.data.updatedAt,
125
- });
126
- const event = args.event as WorkOSEvent;
127
- switch (event.event) {
128
- case "user.created": {
129
- const data = omit(event.data, ["object"]);
130
- const existingUser = await ctx.db
131
- .query("users")
132
- .withIndex("id", (q) => q.eq("id", data.id))
133
- .unique();
134
- if (existingUser) {
135
- console.warn("user already exists", data.id);
136
- break;
137
- }
138
- await ctx.db.insert("users", data);
139
- break;
140
- }
141
- case "user.updated": {
142
- const data = omit(event.data, ["object"]);
143
- const user = await ctx.db
144
- .query("users")
145
- .withIndex("id", (q) => q.eq("id", data.id))
146
- .unique();
147
- if (!user) {
148
- console.error("user not found", data.id);
149
- break;
150
- }
151
- if (user.updatedAt >= data.updatedAt) {
152
- console.warn(`user already updated for event ${event.id}, skipping`);
153
- break;
154
- }
155
- await ctx.db.patch(user._id, data);
156
- break;
157
- }
158
- case "user.deleted": {
159
- const data = omit(event.data, ["object"]);
160
- const user = await ctx.db
161
- .query("users")
162
- .withIndex("id", (q) => q.eq("id", data.id))
163
- .unique();
164
- if (!user) {
165
- console.warn("user not found", data.id);
166
- break;
167
- }
168
- await ctx.db.delete(user._id);
169
- break;
170
- }
171
- }
172
- if (args.onEventHandle) {
173
- await ctx.runMutation(args.onEventHandle as FunctionHandle<"mutation">, {
174
- event: args.event.event,
175
- data: args.event.data,
176
- });
177
- }
199
+ await processEventHandler(ctx, args);
178
200
  },
179
201
  });
180
202
 
@@ -7,6 +7,9 @@ export default defineSchema({
7
7
  event: v.string(),
8
8
  updatedAt: v.optional(v.string()),
9
9
  }).index("eventId", ["eventId"]),
10
+ backfillState: defineTable({
11
+ apiKey: v.string(),
12
+ }),
10
13
  users: defineTable({
11
14
  id: v.string(),
12
15
  email: v.string(),
package/src/test.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { TestConvex } from "convex-test";
2
2
  import type { GenericSchema, SchemaDefinition } from "convex/server";
3
+ import workpool from "@convex-dev/workpool/test";
4
+ import workflow from "@convex-dev/workflow/test";
3
5
  import schema from "./component/schema.js";
4
6
  const modules = import.meta.glob("./component/**/*.ts");
5
7
 
@@ -13,5 +15,7 @@ function register(
13
15
  name: string = "workOSAuthKit"
14
16
  ) {
15
17
  t.registerComponent(name, schema, modules);
18
+ workpool.register(t, `${name}/eventWorkpool`);
19
+ workflow.register(t, `${name}/backfillWorkflow`);
16
20
  }
17
21
  export default { register, schema, modules };