@epic-web/workshop-presence 4.0.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.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ ## 🎈 epic-web-presence
2
+
3
+ Welcome to the party, pal!
4
+
5
+ This is a [Partykit](https://partykit.io) project, which lets you create real-time collaborative applications with minimal coding effort.
6
+
7
+ [`server.ts`](./src/server.ts) is the server-side code, which is responsible for handling WebSocket events and HTTP requests. [`presence.tsx`](../workshop-app/app/utils/presence.tsx) is the client-side code, which connects to the server and listens for events.
8
+
9
+ You can start developing by running `npm run dev` and opening [http://localhost:1999](http://localhost:1999) in your browser. When you're ready, you can deploy your application on to the PartyKit cloud with `npm run deploy`.
10
+
11
+ Refer to our docs for more information: https://github.com/partykit/partykit/blob/main/README.md. For more help, reach out to us on [Discord](https://discord.gg/g5uqHQJc3z), [GitHub](https://github.com/partykit/partykit), or [Twitter](https://twitter.com/partykit_io).
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,390 @@
1
+ import { z } from 'zod';
2
+ export declare const partykitRoom = "epic-web-presence";
3
+ export declare const partykitBaseUrl = "https://epic-web-presence.kentcdodds.partykit.dev/parties/main/epic-web-presence";
4
+ export declare const UserSchema: z.ZodObject<{
5
+ id: z.ZodString;
6
+ avatarUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
7
+ name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
8
+ location: z.ZodOptional<z.ZodNullable<z.ZodObject<{
9
+ workshopTitle: z.ZodOptional<z.ZodNullable<z.ZodString>>;
10
+ exercise: z.ZodOptional<z.ZodNullable<z.ZodObject<{
11
+ type: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodLiteral<"problem">, z.ZodLiteral<"solution">]>>>;
12
+ exerciseNumber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
13
+ stepNumber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
14
+ }, "strip", z.ZodTypeAny, {
15
+ type?: "problem" | "solution" | null | undefined;
16
+ exerciseNumber?: number | null | undefined;
17
+ stepNumber?: number | null | undefined;
18
+ }, {
19
+ type?: "problem" | "solution" | null | undefined;
20
+ exerciseNumber?: number | null | undefined;
21
+ stepNumber?: number | null | undefined;
22
+ }>>>;
23
+ }, "strip", z.ZodTypeAny, {
24
+ workshopTitle?: string | null | undefined;
25
+ exercise?: {
26
+ type?: "problem" | "solution" | null | undefined;
27
+ exerciseNumber?: number | null | undefined;
28
+ stepNumber?: number | null | undefined;
29
+ } | null | undefined;
30
+ }, {
31
+ workshopTitle?: string | null | undefined;
32
+ exercise?: {
33
+ type?: "problem" | "solution" | null | undefined;
34
+ exerciseNumber?: number | null | undefined;
35
+ stepNumber?: number | null | undefined;
36
+ } | null | undefined;
37
+ }>>>;
38
+ }, "strip", z.ZodTypeAny, {
39
+ id: string;
40
+ avatarUrl?: string | null | undefined;
41
+ name?: string | null | undefined;
42
+ location?: {
43
+ workshopTitle?: string | null | undefined;
44
+ exercise?: {
45
+ type?: "problem" | "solution" | null | undefined;
46
+ exerciseNumber?: number | null | undefined;
47
+ stepNumber?: number | null | undefined;
48
+ } | null | undefined;
49
+ } | null | undefined;
50
+ }, {
51
+ id: string;
52
+ avatarUrl?: string | null | undefined;
53
+ name?: string | null | undefined;
54
+ location?: {
55
+ workshopTitle?: string | null | undefined;
56
+ exercise?: {
57
+ type?: "problem" | "solution" | null | undefined;
58
+ exerciseNumber?: number | null | undefined;
59
+ stepNumber?: number | null | undefined;
60
+ } | null | undefined;
61
+ } | null | undefined;
62
+ }>;
63
+ export declare const MessageSchema: z.ZodUnion<[z.ZodUnion<[z.ZodObject<{
64
+ type: z.ZodLiteral<"remove-user">;
65
+ payload: z.ZodObject<{
66
+ id: z.ZodString;
67
+ }, "strip", z.ZodTypeAny, {
68
+ id: string;
69
+ }, {
70
+ id: string;
71
+ }>;
72
+ }, "strip", z.ZodTypeAny, {
73
+ type: "remove-user";
74
+ payload: {
75
+ id: string;
76
+ };
77
+ }, {
78
+ type: "remove-user";
79
+ payload: {
80
+ id: string;
81
+ };
82
+ }>, z.ZodObject<{
83
+ type: z.ZodLiteral<"add-user">;
84
+ payload: z.ZodObject<{
85
+ id: z.ZodString;
86
+ avatarUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
87
+ name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
88
+ location: z.ZodOptional<z.ZodNullable<z.ZodObject<{
89
+ workshopTitle: z.ZodOptional<z.ZodNullable<z.ZodString>>;
90
+ exercise: z.ZodOptional<z.ZodNullable<z.ZodObject<{
91
+ type: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodLiteral<"problem">, z.ZodLiteral<"solution">]>>>;
92
+ exerciseNumber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
93
+ stepNumber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
94
+ }, "strip", z.ZodTypeAny, {
95
+ type?: "problem" | "solution" | null | undefined;
96
+ exerciseNumber?: number | null | undefined;
97
+ stepNumber?: number | null | undefined;
98
+ }, {
99
+ type?: "problem" | "solution" | null | undefined;
100
+ exerciseNumber?: number | null | undefined;
101
+ stepNumber?: number | null | undefined;
102
+ }>>>;
103
+ }, "strip", z.ZodTypeAny, {
104
+ workshopTitle?: string | null | undefined;
105
+ exercise?: {
106
+ type?: "problem" | "solution" | null | undefined;
107
+ exerciseNumber?: number | null | undefined;
108
+ stepNumber?: number | null | undefined;
109
+ } | null | undefined;
110
+ }, {
111
+ workshopTitle?: string | null | undefined;
112
+ exercise?: {
113
+ type?: "problem" | "solution" | null | undefined;
114
+ exerciseNumber?: number | null | undefined;
115
+ stepNumber?: number | null | undefined;
116
+ } | null | undefined;
117
+ }>>>;
118
+ }, "strip", z.ZodTypeAny, {
119
+ id: string;
120
+ avatarUrl?: string | null | undefined;
121
+ name?: string | null | undefined;
122
+ location?: {
123
+ workshopTitle?: string | null | undefined;
124
+ exercise?: {
125
+ type?: "problem" | "solution" | null | undefined;
126
+ exerciseNumber?: number | null | undefined;
127
+ stepNumber?: number | null | undefined;
128
+ } | null | undefined;
129
+ } | null | undefined;
130
+ }, {
131
+ id: string;
132
+ avatarUrl?: string | null | undefined;
133
+ name?: string | null | undefined;
134
+ location?: {
135
+ workshopTitle?: string | null | undefined;
136
+ exercise?: {
137
+ type?: "problem" | "solution" | null | undefined;
138
+ exerciseNumber?: number | null | undefined;
139
+ stepNumber?: number | null | undefined;
140
+ } | null | undefined;
141
+ } | null | undefined;
142
+ }>;
143
+ }, "strip", z.ZodTypeAny, {
144
+ type: "add-user";
145
+ payload: {
146
+ id: string;
147
+ avatarUrl?: string | null | undefined;
148
+ name?: string | null | undefined;
149
+ location?: {
150
+ workshopTitle?: string | null | undefined;
151
+ exercise?: {
152
+ type?: "problem" | "solution" | null | undefined;
153
+ exerciseNumber?: number | null | undefined;
154
+ stepNumber?: number | null | undefined;
155
+ } | null | undefined;
156
+ } | null | undefined;
157
+ };
158
+ }, {
159
+ type: "add-user";
160
+ payload: {
161
+ id: string;
162
+ avatarUrl?: string | null | undefined;
163
+ name?: string | null | undefined;
164
+ location?: {
165
+ workshopTitle?: string | null | undefined;
166
+ exercise?: {
167
+ type?: "problem" | "solution" | null | undefined;
168
+ exerciseNumber?: number | null | undefined;
169
+ stepNumber?: number | null | undefined;
170
+ } | null | undefined;
171
+ } | null | undefined;
172
+ };
173
+ }>]>, z.ZodObject<{
174
+ type: z.ZodLiteral<"presence">;
175
+ payload: z.ZodObject<{
176
+ users: z.ZodArray<z.ZodObject<{
177
+ id: z.ZodString;
178
+ avatarUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
179
+ name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
180
+ location: z.ZodOptional<z.ZodNullable<z.ZodObject<{
181
+ workshopTitle: z.ZodOptional<z.ZodNullable<z.ZodString>>;
182
+ exercise: z.ZodOptional<z.ZodNullable<z.ZodObject<{
183
+ type: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodLiteral<"problem">, z.ZodLiteral<"solution">]>>>;
184
+ exerciseNumber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
185
+ stepNumber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
186
+ }, "strip", z.ZodTypeAny, {
187
+ type?: "problem" | "solution" | null | undefined;
188
+ exerciseNumber?: number | null | undefined;
189
+ stepNumber?: number | null | undefined;
190
+ }, {
191
+ type?: "problem" | "solution" | null | undefined;
192
+ exerciseNumber?: number | null | undefined;
193
+ stepNumber?: number | null | undefined;
194
+ }>>>;
195
+ }, "strip", z.ZodTypeAny, {
196
+ workshopTitle?: string | null | undefined;
197
+ exercise?: {
198
+ type?: "problem" | "solution" | null | undefined;
199
+ exerciseNumber?: number | null | undefined;
200
+ stepNumber?: number | null | undefined;
201
+ } | null | undefined;
202
+ }, {
203
+ workshopTitle?: string | null | undefined;
204
+ exercise?: {
205
+ type?: "problem" | "solution" | null | undefined;
206
+ exerciseNumber?: number | null | undefined;
207
+ stepNumber?: number | null | undefined;
208
+ } | null | undefined;
209
+ }>>>;
210
+ }, "strip", z.ZodTypeAny, {
211
+ id: string;
212
+ avatarUrl?: string | null | undefined;
213
+ name?: string | null | undefined;
214
+ location?: {
215
+ workshopTitle?: string | null | undefined;
216
+ exercise?: {
217
+ type?: "problem" | "solution" | null | undefined;
218
+ exerciseNumber?: number | null | undefined;
219
+ stepNumber?: number | null | undefined;
220
+ } | null | undefined;
221
+ } | null | undefined;
222
+ }, {
223
+ id: string;
224
+ avatarUrl?: string | null | undefined;
225
+ name?: string | null | undefined;
226
+ location?: {
227
+ workshopTitle?: string | null | undefined;
228
+ exercise?: {
229
+ type?: "problem" | "solution" | null | undefined;
230
+ exerciseNumber?: number | null | undefined;
231
+ stepNumber?: number | null | undefined;
232
+ } | null | undefined;
233
+ } | null | undefined;
234
+ }>, "many">;
235
+ }, "strip", z.ZodTypeAny, {
236
+ users: {
237
+ id: string;
238
+ avatarUrl?: string | null | undefined;
239
+ name?: string | null | undefined;
240
+ location?: {
241
+ workshopTitle?: string | null | undefined;
242
+ exercise?: {
243
+ type?: "problem" | "solution" | null | undefined;
244
+ exerciseNumber?: number | null | undefined;
245
+ stepNumber?: number | null | undefined;
246
+ } | null | undefined;
247
+ } | null | undefined;
248
+ }[];
249
+ }, {
250
+ users: {
251
+ id: string;
252
+ avatarUrl?: string | null | undefined;
253
+ name?: string | null | undefined;
254
+ location?: {
255
+ workshopTitle?: string | null | undefined;
256
+ exercise?: {
257
+ type?: "problem" | "solution" | null | undefined;
258
+ exerciseNumber?: number | null | undefined;
259
+ stepNumber?: number | null | undefined;
260
+ } | null | undefined;
261
+ } | null | undefined;
262
+ }[];
263
+ }>;
264
+ }, "strip", z.ZodTypeAny, {
265
+ type: "presence";
266
+ payload: {
267
+ users: {
268
+ id: string;
269
+ avatarUrl?: string | null | undefined;
270
+ name?: string | null | undefined;
271
+ location?: {
272
+ workshopTitle?: string | null | undefined;
273
+ exercise?: {
274
+ type?: "problem" | "solution" | null | undefined;
275
+ exerciseNumber?: number | null | undefined;
276
+ stepNumber?: number | null | undefined;
277
+ } | null | undefined;
278
+ } | null | undefined;
279
+ }[];
280
+ };
281
+ }, {
282
+ type: "presence";
283
+ payload: {
284
+ users: {
285
+ id: string;
286
+ avatarUrl?: string | null | undefined;
287
+ name?: string | null | undefined;
288
+ location?: {
289
+ workshopTitle?: string | null | undefined;
290
+ exercise?: {
291
+ type?: "problem" | "solution" | null | undefined;
292
+ exerciseNumber?: number | null | undefined;
293
+ stepNumber?: number | null | undefined;
294
+ } | null | undefined;
295
+ } | null | undefined;
296
+ }[];
297
+ };
298
+ }>]>;
299
+ export type Message = z.infer<typeof MessageSchema>;
300
+ export type User = z.infer<typeof UserSchema>;
301
+ export declare const PresenceSchema: z.ZodObject<{
302
+ users: z.ZodArray<z.ZodObject<{
303
+ id: z.ZodString;
304
+ avatarUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
305
+ name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
306
+ location: z.ZodOptional<z.ZodNullable<z.ZodObject<{
307
+ workshopTitle: z.ZodOptional<z.ZodNullable<z.ZodString>>;
308
+ exercise: z.ZodOptional<z.ZodNullable<z.ZodObject<{
309
+ type: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodLiteral<"problem">, z.ZodLiteral<"solution">]>>>;
310
+ exerciseNumber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
311
+ stepNumber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
312
+ }, "strip", z.ZodTypeAny, {
313
+ type?: "problem" | "solution" | null | undefined;
314
+ exerciseNumber?: number | null | undefined;
315
+ stepNumber?: number | null | undefined;
316
+ }, {
317
+ type?: "problem" | "solution" | null | undefined;
318
+ exerciseNumber?: number | null | undefined;
319
+ stepNumber?: number | null | undefined;
320
+ }>>>;
321
+ }, "strip", z.ZodTypeAny, {
322
+ workshopTitle?: string | null | undefined;
323
+ exercise?: {
324
+ type?: "problem" | "solution" | null | undefined;
325
+ exerciseNumber?: number | null | undefined;
326
+ stepNumber?: number | null | undefined;
327
+ } | null | undefined;
328
+ }, {
329
+ workshopTitle?: string | null | undefined;
330
+ exercise?: {
331
+ type?: "problem" | "solution" | null | undefined;
332
+ exerciseNumber?: number | null | undefined;
333
+ stepNumber?: number | null | undefined;
334
+ } | null | undefined;
335
+ }>>>;
336
+ }, "strip", z.ZodTypeAny, {
337
+ id: string;
338
+ avatarUrl?: string | null | undefined;
339
+ name?: string | null | undefined;
340
+ location?: {
341
+ workshopTitle?: string | null | undefined;
342
+ exercise?: {
343
+ type?: "problem" | "solution" | null | undefined;
344
+ exerciseNumber?: number | null | undefined;
345
+ stepNumber?: number | null | undefined;
346
+ } | null | undefined;
347
+ } | null | undefined;
348
+ }, {
349
+ id: string;
350
+ avatarUrl?: string | null | undefined;
351
+ name?: string | null | undefined;
352
+ location?: {
353
+ workshopTitle?: string | null | undefined;
354
+ exercise?: {
355
+ type?: "problem" | "solution" | null | undefined;
356
+ exerciseNumber?: number | null | undefined;
357
+ stepNumber?: number | null | undefined;
358
+ } | null | undefined;
359
+ } | null | undefined;
360
+ }>, "many">;
361
+ }, "strip", z.ZodTypeAny, {
362
+ users: {
363
+ id: string;
364
+ avatarUrl?: string | null | undefined;
365
+ name?: string | null | undefined;
366
+ location?: {
367
+ workshopTitle?: string | null | undefined;
368
+ exercise?: {
369
+ type?: "problem" | "solution" | null | undefined;
370
+ exerciseNumber?: number | null | undefined;
371
+ stepNumber?: number | null | undefined;
372
+ } | null | undefined;
373
+ } | null | undefined;
374
+ }[];
375
+ }, {
376
+ users: {
377
+ id: string;
378
+ avatarUrl?: string | null | undefined;
379
+ name?: string | null | undefined;
380
+ location?: {
381
+ workshopTitle?: string | null | undefined;
382
+ exercise?: {
383
+ type?: "problem" | "solution" | null | undefined;
384
+ exerciseNumber?: number | null | undefined;
385
+ stepNumber?: number | null | undefined;
386
+ } | null | undefined;
387
+ } | null | undefined;
388
+ }[];
389
+ }>;
390
+ //# sourceMappingURL=presence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/presence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,YAAY,sBAAsB,CAAA;AAE/C,eAAO,MAAM,eAAe,qFAAmF,CAAA;AAE/G,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqBrB,CAAA;AAEF,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAWxB,CAAA;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AAEnD,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAA;AAE7C,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAA2C,CAAA"}
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ export const partykitRoom = 'epic-web-presence';
3
+ // export const partykitBaseUrl = `http://127.0.0.1:1999/parties/main/${partykitRoom}`
4
+ export const partykitBaseUrl = `https://epic-web-presence.kentcdodds.partykit.dev/parties/main/${partykitRoom}`;
5
+ export const UserSchema = z.object({
6
+ id: z.string(),
7
+ avatarUrl: z.string().nullable().optional(),
8
+ name: z.string().nullable().optional(),
9
+ location: z
10
+ .object({
11
+ workshopTitle: z.string().nullable().optional(),
12
+ exercise: z
13
+ .object({
14
+ type: z
15
+ .union([z.literal('problem'), z.literal('solution')])
16
+ .nullable()
17
+ .optional(),
18
+ exerciseNumber: z.number().nullable().optional(),
19
+ stepNumber: z.number().nullable().optional(),
20
+ })
21
+ .nullable()
22
+ .optional(),
23
+ })
24
+ .nullable()
25
+ .optional(),
26
+ });
27
+ export const MessageSchema = z
28
+ .object({
29
+ type: z.literal('remove-user'),
30
+ payload: z.object({ id: z.string() }),
31
+ })
32
+ .or(z.object({ type: z.literal('add-user'), payload: UserSchema }))
33
+ .or(z.object({
34
+ type: z.literal('presence'),
35
+ payload: z.object({ users: z.array(UserSchema) }),
36
+ }));
37
+ export const PresenceSchema = z.object({ users: z.array(UserSchema) });
38
+ //# sourceMappingURL=presence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence.js","sourceRoot":"","sources":["../../src/presence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,YAAY,GAAG,mBAAmB,CAAA;AAC/C,sFAAsF;AACtF,MAAM,CAAC,MAAM,eAAe,GAAG,kEAAkE,YAAY,EAAE,CAAA;AAE/G,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC3C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACtC,QAAQ,EAAE,CAAC;SACT,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QAC/C,QAAQ,EAAE,CAAC;aACT,MAAM,CAAC;YACP,IAAI,EAAE,CAAC;iBACL,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;iBACpD,QAAQ,EAAE;iBACV,QAAQ,EAAE;YACZ,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;YAChD,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;SAC5C,CAAC;aACD,QAAQ,EAAE;aACV,QAAQ,EAAE;KACZ,CAAC;SACD,QAAQ,EAAE;SACV,QAAQ,EAAE;CACZ,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC;KAC5B,MAAM,CAAC;IACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC;IAC9B,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;CACrC,CAAC;KACD,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;KAClE,EAAE,CACF,CAAC,CAAC,MAAM,CAAC;IACR,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;IAC3B,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;CACjD,CAAC,CACF,CAAA;AAMF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA","sourcesContent":["import { z } from 'zod'\n\nexport const partykitRoom = 'epic-web-presence'\n// export const partykitBaseUrl = `http://127.0.0.1:1999/parties/main/${partykitRoom}`\nexport const partykitBaseUrl = `https://epic-web-presence.kentcdodds.partykit.dev/parties/main/${partykitRoom}`\n\nexport const UserSchema = z.object({\n\tid: z.string(),\n\tavatarUrl: z.string().nullable().optional(),\n\tname: z.string().nullable().optional(),\n\tlocation: z\n\t\t.object({\n\t\t\tworkshopTitle: z.string().nullable().optional(),\n\t\t\texercise: z\n\t\t\t\t.object({\n\t\t\t\t\ttype: z\n\t\t\t\t\t\t.union([z.literal('problem'), z.literal('solution')])\n\t\t\t\t\t\t.nullable()\n\t\t\t\t\t\t.optional(),\n\t\t\t\t\texerciseNumber: z.number().nullable().optional(),\n\t\t\t\t\tstepNumber: z.number().nullable().optional(),\n\t\t\t\t})\n\t\t\t\t.nullable()\n\t\t\t\t.optional(),\n\t\t})\n\t\t.nullable()\n\t\t.optional(),\n})\n\nexport const MessageSchema = z\n\t.object({\n\t\ttype: z.literal('remove-user'),\n\t\tpayload: z.object({ id: z.string() }),\n\t})\n\t.or(z.object({ type: z.literal('add-user'), payload: UserSchema }))\n\t.or(\n\t\tz.object({\n\t\t\ttype: z.literal('presence'),\n\t\t\tpayload: z.object({ users: z.array(UserSchema) }),\n\t\t}),\n\t)\n\nexport type Message = z.infer<typeof MessageSchema>\n\nexport type User = z.infer<typeof UserSchema>\n\nexport const PresenceSchema = z.object({ users: z.array(UserSchema) })\n"]}
@@ -0,0 +1,37 @@
1
+ import { type Timings } from '@epic-web/workshop-utils/timing.server';
2
+ import { type User } from './presence.js';
3
+ export declare const presenceCache: {
4
+ name: string;
5
+ set: (key: string, value: import("@epic-web/cachified").CacheEntry<{
6
+ id: string;
7
+ avatarUrl: string;
8
+ name: string | null | undefined;
9
+ }[]>) => import("@epic-web/cachified").CacheEntry<{
10
+ id: string;
11
+ avatarUrl: string;
12
+ name: string | null | undefined;
13
+ }[]>;
14
+ get: (key: string) => import("@epic-web/cachified").CacheEntry<{
15
+ id: string;
16
+ avatarUrl: string;
17
+ name: string | null | undefined;
18
+ }[]> | undefined;
19
+ delete: (key: string) => boolean;
20
+ };
21
+ export declare function getPresentUsers(user?: User | null, { timings, request }?: {
22
+ timings?: Timings;
23
+ request?: Request;
24
+ }): Promise<{
25
+ id: string;
26
+ avatarUrl?: string | null | undefined;
27
+ name?: string | null | undefined;
28
+ location?: {
29
+ workshopTitle?: string | null | undefined;
30
+ exercise?: {
31
+ type?: "problem" | "solution" | null | undefined;
32
+ exerciseNumber?: number | null | undefined;
33
+ stepNumber?: number | null | undefined;
34
+ } | null | undefined;
35
+ } | null | undefined;
36
+ }[]>;
37
+ //# sourceMappingURL=presence.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence.server.d.ts","sourceRoot":"","sources":["../../src/presence.server.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,wCAAwC,CAAA;AAGrE,OAAO,EAEN,KAAK,IAAI,EAGT,MAAM,eAAe,CAAA;AAEtB,eAAO,MAAM,aAAa;;;YAEpB,MAAM;mBACC,MAAM;cACX,MAAM,GAAG,IAAI,GAAG,SAAS;;YAF3B,MAAM;mBACC,MAAM;cACX,MAAM,GAAG,IAAI,GAAG,SAAS;;;YAF3B,MAAM;mBACC,MAAM;cACX,MAAM,GAAG,IAAI,GAAG,SAAS;;;CAEf,CAAA;AAElB,wBAAsB,eAAe,CACpC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,EAClB,EAAE,OAAO,EAAE,OAAO,EAAE,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO;;;;;;;;;;;;KAgDnE"}
@@ -0,0 +1,63 @@
1
+ import { cachified, makeSingletonCache, } from '@epic-web/workshop-utils/cache.server';
2
+ import { getPreferences } from '@epic-web/workshop-utils/db.server';
3
+ import { checkConnection } from '@epic-web/workshop-utils/utils.server';
4
+ import { z } from 'zod';
5
+ import { PresenceSchema, UserSchema, partykitBaseUrl, } from './presence.js';
6
+ export const presenceCache = makeSingletonCache('PresenceCache');
7
+ export async function getPresentUsers(user, { timings, request } = {}) {
8
+ return cachified({
9
+ key: 'presence',
10
+ cache: presenceCache,
11
+ timings,
12
+ request,
13
+ ttl: 1000 * 60 * 5,
14
+ swr: 1000 * 60 * 60 * 24,
15
+ checkValue: z.array(UserSchema),
16
+ async getFreshValue(context) {
17
+ try {
18
+ const response = await Promise.race([
19
+ (async () => {
20
+ const connected = await checkConnection();
21
+ if (!connected)
22
+ throw new Error(`No internet connection`);
23
+ return fetch(`${partykitBaseUrl}/presence`);
24
+ })(),
25
+ new Promise(resolve => setTimeout(() => resolve(new Response('Timeout', { status: 500 })), 200)),
26
+ ]);
27
+ if (response.statusText === 'Timeout') {
28
+ throw new Error(`Timeout fetching partykit presence`);
29
+ }
30
+ if (!response.ok) {
31
+ throw new Error(`Unexpected response from partykit: ${response.status} ${response.statusText}`);
32
+ }
33
+ const presence = PresenceSchema.parse(await response.json());
34
+ const preferences = await getPreferences();
35
+ const users = presence.users;
36
+ if (preferences?.presence.optOut ?? !user) {
37
+ return uniqueUsers(users.filter(u => u.id !== user?.id));
38
+ }
39
+ else {
40
+ return uniqueUsers([...users, user]);
41
+ }
42
+ }
43
+ catch {
44
+ // console.error(err)
45
+ context.metadata.ttl = 300;
46
+ return [];
47
+ }
48
+ },
49
+ });
50
+ }
51
+ // A user maybe on the same page in multiple tabs
52
+ // so let's make sure we only show them once
53
+ function uniqueUsers(users) {
54
+ const seen = new Set();
55
+ return users.filter(user => {
56
+ if (seen.has(user.id)) {
57
+ return false;
58
+ }
59
+ seen.add(user.id);
60
+ return true;
61
+ });
62
+ }
63
+ //# sourceMappingURL=presence.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence.server.js","sourceRoot":"","sources":["../../src/presence.server.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tcachified,\n\tmakeSingletonCache,\n} from '@epic-web/workshop-utils/cache.server'\nimport { getPreferences } from '@epic-web/workshop-utils/db.server'\nimport { type Timings } from '@epic-web/workshop-utils/timing.server'\nimport { checkConnection } from '@epic-web/workshop-utils/utils.server'\nimport { z } from 'zod'\nimport {\n\tPresenceSchema,\n\ttype User,\n\tUserSchema,\n\tpartykitBaseUrl,\n} from './presence.js'\n\nexport const presenceCache = makeSingletonCache<\n\tArray<{\n\t\tid: string\n\t\tavatarUrl: string\n\t\tname: string | null | undefined\n\t}>\n>('PresenceCache')\n\nexport async function getPresentUsers(\n\tuser?: User | null,\n\t{ timings, request }: { timings?: Timings; request?: Request } = {},\n) {\n\treturn cachified({\n\t\tkey: 'presence',\n\t\tcache: presenceCache,\n\t\ttimings,\n\t\trequest,\n\t\tttl: 1000 * 60 * 5,\n\t\tswr: 1000 * 60 * 60 * 24,\n\t\tcheckValue: z.array(UserSchema),\n\t\tasync getFreshValue(context) {\n\t\t\ttry {\n\t\t\t\tconst response = await Promise.race([\n\t\t\t\t\t(async () => {\n\t\t\t\t\t\tconst connected = await checkConnection()\n\t\t\t\t\t\tif (!connected) throw new Error(`No internet connection`)\n\t\t\t\t\t\treturn fetch(`${partykitBaseUrl}/presence`)\n\t\t\t\t\t})(),\n\t\t\t\t\tnew Promise<Response>(resolve =>\n\t\t\t\t\t\tsetTimeout(\n\t\t\t\t\t\t\t() => resolve(new Response('Timeout', { status: 500 })),\n\t\t\t\t\t\t\t200,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t] as const)\n\t\t\t\tif (response.statusText === 'Timeout') {\n\t\t\t\t\tthrow new Error(`Timeout fetching partykit presence`)\n\t\t\t\t}\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Unexpected response from partykit: ${response.status} ${response.statusText}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tconst presence = PresenceSchema.parse(await response.json())\n\t\t\t\tconst preferences = await getPreferences()\n\t\t\t\tconst users = presence.users\n\t\t\t\tif (preferences?.presence.optOut ?? !user) {\n\t\t\t\t\treturn uniqueUsers(users.filter(u => u.id !== user?.id))\n\t\t\t\t} else {\n\t\t\t\t\treturn uniqueUsers([...users, user])\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// console.error(err)\n\t\t\t\tcontext.metadata.ttl = 300\n\t\t\t\treturn []\n\t\t\t}\n\t\t},\n\t})\n}\n\n// A user maybe on the same page in multiple tabs\n// so let's make sure we only show them once\nfunction uniqueUsers(users: Array<User>) {\n\tconst seen = new Set()\n\treturn users.filter(user => {\n\t\tif (seen.has(user.id)) {\n\t\t\treturn false\n\t\t}\n\t\tseen.add(user.id)\n\t\treturn true\n\t})\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import type * as Party from 'partykit/server';
2
+ declare const _default: {
3
+ new (party: Party.Party): {
4
+ options: Party.ServerOptions;
5
+ readonly party: Party.Party;
6
+ onClose(): void;
7
+ onError(): void;
8
+ updateUsers(): void;
9
+ getPresenceMessage(): {
10
+ type: "presence";
11
+ payload: {
12
+ users: {
13
+ id: string;
14
+ avatarUrl?: string | null | undefined;
15
+ name?: string | null | undefined;
16
+ location?: {
17
+ workshopTitle?: string | null | undefined;
18
+ exercise?: {
19
+ type?: "problem" | "solution" | null | undefined;
20
+ exerciseNumber?: number | null | undefined;
21
+ stepNumber?: number | null | undefined;
22
+ } | null | undefined;
23
+ } | null | undefined;
24
+ }[];
25
+ };
26
+ };
27
+ getUsers(): {
28
+ id: string;
29
+ avatarUrl?: string | null | undefined;
30
+ name?: string | null | undefined;
31
+ location?: {
32
+ workshopTitle?: string | null | undefined;
33
+ exercise?: {
34
+ type?: "problem" | "solution" | null | undefined;
35
+ exerciseNumber?: number | null | undefined;
36
+ stepNumber?: number | null | undefined;
37
+ } | null | undefined;
38
+ } | null | undefined;
39
+ }[];
40
+ onMessage(message: string, sender: Party.Connection): void;
41
+ onRequest(req: Party.Request): Response | Promise<Response>;
42
+ };
43
+ };
44
+ export default _default;
45
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,iBAAiB,CAAA;;gBAwCzB,MAAM,KAAK;iBANrB,MAAM,aAAa;wBAIZ,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAyCR,MAAM,UAAU,MAAM,UAAU;uBAepC,aAAa,GAAG,QAAQ,GAAG,QAAQ,QAAQ,CAAC;;;AA7D5D,wBAoEyB"}
@@ -0,0 +1,128 @@
1
+ import { z } from 'zod';
2
+ import { UserSchema } from './presence.js';
3
+ const ConnectionStateSchema = z
4
+ .object({
5
+ user: UserSchema.nullable().optional(),
6
+ })
7
+ .nullable();
8
+ const MessageSchema = z
9
+ .object({
10
+ type: z.literal('remove-user'),
11
+ payload: z.object({ id: z.string() }),
12
+ })
13
+ .or(z.object({ type: z.literal('add-user'), payload: UserSchema }))
14
+ .or(z.object({
15
+ type: z.literal('add-anonymous-user'),
16
+ payload: z.object({ id: z.string() }),
17
+ }))
18
+ .or(z.object({
19
+ type: z.literal('presence'),
20
+ payload: z.object({ users: z.array(UserSchema) }),
21
+ }));
22
+ export default (class Server {
23
+ options = {
24
+ hibernate: true,
25
+ };
26
+ party;
27
+ constructor(party) {
28
+ this.party = party;
29
+ }
30
+ onClose() {
31
+ this.updateUsers();
32
+ }
33
+ onError() {
34
+ this.updateUsers();
35
+ }
36
+ updateUsers() {
37
+ const presenceMessage = JSON.stringify(this.getPresenceMessage());
38
+ for (const connection of this.party.getConnections()) {
39
+ connection.send(presenceMessage);
40
+ }
41
+ }
42
+ getPresenceMessage() {
43
+ return {
44
+ type: 'presence',
45
+ payload: { users: this.getUsers() },
46
+ };
47
+ }
48
+ getUsers() {
49
+ const users = new Map();
50
+ for (const connection of this.party.getConnections()) {
51
+ const state = getConnectionState(connection);
52
+ if (state?.user) {
53
+ users.set(state.user.id, state.user);
54
+ }
55
+ }
56
+ return sortUsers(Array.from(users.values()));
57
+ }
58
+ onMessage(message, sender) {
59
+ const result = MessageSchema.safeParse(JSON.parse(message));
60
+ if (!result.success)
61
+ return;
62
+ if (result.data.type === 'add-user') {
63
+ shallowMergeConnectionState(sender, { user: result.data.payload });
64
+ this.updateUsers();
65
+ }
66
+ else if (result.data.type === 'remove-user') {
67
+ setConnectionState(sender, null);
68
+ this.updateUsers();
69
+ }
70
+ else if (result.data.type === 'add-anonymous-user') {
71
+ setConnectionState(sender, { user: result.data.payload });
72
+ }
73
+ }
74
+ onRequest(req) {
75
+ const url = new URL(req.url);
76
+ if (url.pathname.endsWith('/presence')) {
77
+ return Response.json(this.getPresenceMessage().payload);
78
+ }
79
+ return new Response('not found', { status: 404 });
80
+ }
81
+ });
82
+ function shallowMergeConnectionState(connection, state) {
83
+ setConnectionState(connection, prev => ({ ...prev, ...state }));
84
+ }
85
+ function setConnectionState(connection, state) {
86
+ if (typeof state !== 'function') {
87
+ return connection.setState(state);
88
+ }
89
+ connection.setState((prev) => {
90
+ const prevParseResult = ConnectionStateSchema.safeParse(prev);
91
+ if (prevParseResult.success) {
92
+ return state(prevParseResult.data);
93
+ }
94
+ else {
95
+ return state(null);
96
+ }
97
+ });
98
+ }
99
+ function getConnectionState(connection) {
100
+ const result = ConnectionStateSchema.safeParse(connection.state);
101
+ if (result.success) {
102
+ return result.data;
103
+ }
104
+ else {
105
+ setConnectionState(connection, null);
106
+ return null;
107
+ }
108
+ }
109
+ function sortUsers(users) {
110
+ return [...users].sort((a, b) => {
111
+ const aScore = getScore(a);
112
+ const bScore = getScore(b);
113
+ if (aScore === bScore)
114
+ return 0;
115
+ return aScore > bScore ? -1 : 1;
116
+ });
117
+ }
118
+ function getScore(user) {
119
+ let score = 0;
120
+ if (user.avatarUrl)
121
+ score += 1;
122
+ if (user.avatarUrl?.includes('discordapp'))
123
+ score += 0.5;
124
+ if (user.name)
125
+ score += 1;
126
+ return score;
127
+ }
128
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import type * as Party from 'partykit/server'\nimport { z } from 'zod'\nimport { UserSchema } from './presence.js'\n\ntype User = z.infer<typeof UserSchema>\nconst ConnectionStateSchema = z\n\t.object({\n\t\tuser: UserSchema.nullable().optional(),\n\t})\n\t.nullable()\n\ntype ConnectionState = z.infer<typeof ConnectionStateSchema>\n\nconst MessageSchema = z\n\t.object({\n\t\ttype: z.literal('remove-user'),\n\t\tpayload: z.object({ id: z.string() }),\n\t})\n\t.or(z.object({ type: z.literal('add-user'), payload: UserSchema }))\n\t.or(\n\t\tz.object({\n\t\t\ttype: z.literal('add-anonymous-user'),\n\t\t\tpayload: z.object({ id: z.string() }),\n\t\t}),\n\t)\n\t.or(\n\t\tz.object({\n\t\t\ttype: z.literal('presence'),\n\t\t\tpayload: z.object({ users: z.array(UserSchema) }),\n\t\t}),\n\t)\ntype Message = z.infer<typeof MessageSchema>\n\nexport default (class Server implements Party.Server {\n\toptions: Party.ServerOptions = {\n\t\thibernate: true,\n\t}\n\n\treadonly party: Party.Party\n\n\tconstructor(party: Party.Party) {\n\t\tthis.party = party\n\t}\n\n\tonClose() {\n\t\tthis.updateUsers()\n\t}\n\n\tonError() {\n\t\tthis.updateUsers()\n\t}\n\n\tupdateUsers() {\n\t\tconst presenceMessage = JSON.stringify(this.getPresenceMessage())\n\t\tfor (const connection of this.party.getConnections()) {\n\t\t\tconnection.send(presenceMessage)\n\t\t}\n\t}\n\n\tgetPresenceMessage() {\n\t\treturn {\n\t\t\ttype: 'presence',\n\t\t\tpayload: { users: this.getUsers() },\n\t\t} satisfies Message\n\t}\n\n\tgetUsers() {\n\t\tconst users = new Map<string, z.infer<typeof UserSchema>>()\n\n\t\tfor (const connection of this.party.getConnections()) {\n\t\t\tconst state = getConnectionState(connection)\n\t\t\tif (state?.user) {\n\t\t\t\tusers.set(state.user.id, state.user)\n\t\t\t}\n\t\t}\n\n\t\treturn sortUsers(Array.from(users.values()))\n\t}\n\n\tonMessage(message: string, sender: Party.Connection) {\n\t\tconst result = MessageSchema.safeParse(JSON.parse(message))\n\t\tif (!result.success) return\n\n\t\tif (result.data.type === 'add-user') {\n\t\t\tshallowMergeConnectionState(sender, { user: result.data.payload })\n\t\t\tthis.updateUsers()\n\t\t} else if (result.data.type === 'remove-user') {\n\t\t\tsetConnectionState(sender, null)\n\t\t\tthis.updateUsers()\n\t\t} else if (result.data.type === 'add-anonymous-user') {\n\t\t\tsetConnectionState(sender, { user: result.data.payload })\n\t\t}\n\t}\n\n\tonRequest(req: Party.Request): Response | Promise<Response> {\n\t\tconst url = new URL(req.url)\n\t\tif (url.pathname.endsWith('/presence')) {\n\t\t\treturn Response.json(this.getPresenceMessage().payload)\n\t\t}\n\t\treturn new Response('not found', { status: 404 })\n\t}\n} satisfies Party.Worker)\n\nfunction shallowMergeConnectionState(\n\tconnection: Party.Connection,\n\tstate: ConnectionState,\n) {\n\tsetConnectionState(connection, prev => ({ ...prev, ...state }))\n}\n\nfunction setConnectionState(\n\tconnection: Party.Connection,\n\tstate:\n\t\t| ConnectionState\n\t\t| ((prev: ConnectionState | null) => ConnectionState | null),\n) {\n\tif (typeof state !== 'function') {\n\t\treturn connection.setState(state)\n\t}\n\tconnection.setState((prev: unknown) => {\n\t\tconst prevParseResult = ConnectionStateSchema.safeParse(prev)\n\t\tif (prevParseResult.success) {\n\t\t\treturn state(prevParseResult.data)\n\t\t} else {\n\t\t\treturn state(null)\n\t\t}\n\t})\n}\n\nfunction getConnectionState(connection: Party.Connection) {\n\tconst result = ConnectionStateSchema.safeParse(connection.state)\n\tif (result.success) {\n\t\treturn result.data\n\t} else {\n\t\tsetConnectionState(connection, null)\n\t\treturn null\n\t}\n}\n\nfunction sortUsers(users: Array<User>) {\n\treturn [...users].sort((a, b) => {\n\t\tconst aScore = getScore(a)\n\t\tconst bScore = getScore(b)\n\t\tif (aScore === bScore) return 0\n\t\treturn aScore > bScore ? -1 : 1\n\t})\n}\n\nfunction getScore(user: User) {\n\tlet score = 0\n\tif (user.avatarUrl) score += 1\n\tif (user.avatarUrl?.includes('discordapp')) score += 0.5\n\tif (user.name) score += 1\n\treturn score\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@epic-web/workshop-presence",
3
+ "version": "4.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "type": "module",
8
+ "scripts": {
9
+ "build": "tshy",
10
+ "dev": "partykit dev",
11
+ "deploy": "partykit deploy"
12
+ },
13
+ "dependencies": {
14
+ "@epic-web/workshop-utils": "4.0.0",
15
+ "zod": "^3.22.4"
16
+ },
17
+ "devDependencies": {
18
+ "partykit": "0.0.99",
19
+ "typescript": "^5.4.2",
20
+ "tshy": "^1.11.1"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "tshy": {
26
+ "project": "./tsconfig.build.json",
27
+ "dialects": [
28
+ "esm"
29
+ ],
30
+ "exports": {
31
+ "./package.json": "./package.json",
32
+ "./presence.server": "./src/presence.server.ts",
33
+ "./presence": "./src/presence.ts"
34
+ }
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/epicweb-dev/workshop-app.git",
39
+ "directory": "packages/presence"
40
+ },
41
+ "exports": {
42
+ "./package.json": "./package.json",
43
+ "./presence.server": {
44
+ "import": {
45
+ "types": "./dist/esm/presence.server.d.ts",
46
+ "default": "./dist/esm/presence.server.js"
47
+ }
48
+ },
49
+ "./presence": {
50
+ "import": {
51
+ "types": "./dist/esm/presence.d.ts",
52
+ "default": "./dist/esm/presence.js"
53
+ }
54
+ }
55
+ }
56
+ }