@trypattern/sdk 0.1.30 → 0.1.32

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.
@@ -176,6 +176,8 @@ export declare const domainSummary: z.ZodObject<{
176
176
  }>;
177
177
  error: z.ZodNullable<z.ZodString>;
178
178
  startedAt: z.ZodString;
179
+ registeredAt: z.ZodNullable<z.ZodString>;
180
+ expiresAt: z.ZodNullable<z.ZodString>;
179
181
  checkedAt: z.ZodNullable<z.ZodString>;
180
182
  finishedAt: z.ZodNullable<z.ZodString>;
181
183
  }, z.core.$strip>;
@@ -436,7 +438,7 @@ export declare const publishServices: {
436
438
  startedAt: z.ZodString;
437
439
  finishedAt: z.ZodNullable<z.ZodString>;
438
440
  }, z.core.$strip>>;
439
- }, z.core.$strip>>>>;
441
+ }, z.core.$strip>>, false>>;
440
442
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/apps/:name", {}, z.ZodObject<{
441
443
  name: z.ZodString;
442
444
  title: z.ZodString;
@@ -463,7 +465,7 @@ export declare const publishServices: {
463
465
  startedAt: z.ZodString;
464
466
  finishedAt: z.ZodNullable<z.ZodString>;
465
467
  }, z.core.$strip>>;
466
- }, z.core.$strip>>>;
468
+ }, z.core.$strip>, false>>;
467
469
  readonly create: NoInfer<import("./services.ts").ServiceCall<"/apps", {
468
470
  name: z.ZodString;
469
471
  title: z.ZodString;
@@ -497,7 +499,7 @@ export declare const publishServices: {
497
499
  startedAt: z.ZodString;
498
500
  finishedAt: z.ZodNullable<z.ZodString>;
499
501
  }, z.core.$strip>>;
500
- }, z.core.$strip>>>;
502
+ }, z.core.$strip>, false>>;
501
503
  };
502
504
  readonly names: {
503
505
  readonly suggest: NoInfer<import("./services.ts").ServiceCall<"/names", {
@@ -506,12 +508,12 @@ export declare const publishServices: {
506
508
  name: z.ZodString;
507
509
  available: z.ZodBoolean;
508
510
  problem: z.ZodNullable<z.ZodString>;
509
- }, z.core.$strip>>>>;
511
+ }, z.core.$strip>>, false>>;
510
512
  readonly check: NoInfer<import("./services.ts").ServiceCall<"/names/:name", {}, z.ZodObject<{
511
513
  name: z.ZodString;
512
514
  available: z.ZodBoolean;
513
515
  problem: z.ZodNullable<z.ZodString>;
514
- }, z.core.$strip>>>;
516
+ }, z.core.$strip>, false>>;
515
517
  };
516
518
  readonly publishes: {
517
519
  readonly list: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/publishes", {}, z.ZodArray<z.ZodObject<{
@@ -534,7 +536,7 @@ export declare const publishServices: {
534
536
  message: z.ZodNullable<z.ZodString>;
535
537
  startedAt: z.ZodString;
536
538
  finishedAt: z.ZodNullable<z.ZodString>;
537
- }, z.core.$strip>>>>;
539
+ }, z.core.$strip>>, false>>;
538
540
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/publishes/:number", {}, z.ZodObject<{
539
541
  number: z.ZodNumber;
540
542
  status: z.ZodEnum<{
@@ -555,7 +557,7 @@ export declare const publishServices: {
555
557
  message: z.ZodNullable<z.ZodString>;
556
558
  startedAt: z.ZodString;
557
559
  finishedAt: z.ZodNullable<z.ZodString>;
558
- }, z.core.$strip>>>;
560
+ }, z.core.$strip>, false>>;
559
561
  readonly retry: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/publishes/:number/retry", {}, z.ZodObject<{
560
562
  number: z.ZodNumber;
561
563
  status: z.ZodEnum<{
@@ -576,20 +578,20 @@ export declare const publishServices: {
576
578
  message: z.ZodNullable<z.ZodString>;
577
579
  startedAt: z.ZodString;
578
580
  finishedAt: z.ZodNullable<z.ZodString>;
579
- }, z.core.$strip>>>;
581
+ }, z.core.$strip>, false>>;
580
582
  };
581
583
  readonly setup: {
582
584
  readonly newCode: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/setup-code", {}, z.ZodObject<{
583
585
  code: z.ZodString;
584
586
  expiresAt: z.ZodString;
585
- }, z.core.$strip>>>;
587
+ }, z.core.$strip>, false>>;
586
588
  readonly connection: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/connection", {}, z.ZodNullable<z.ZodObject<{
587
589
  label: z.ZodString;
588
590
  connectedAt: z.ZodString;
589
- }, z.core.$strip>>>>;
591
+ }, z.core.$strip>>, false>>;
590
592
  readonly disconnect: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/connection/disconnect", {}, z.ZodObject<{
591
593
  ok: z.ZodBoolean;
592
- }, z.core.$strip>>>;
594
+ }, z.core.$strip>, false>>;
593
595
  };
594
596
  readonly domains: {
595
597
  readonly search: NoInfer<import("./services.ts").ServiceCall<"/domains", {
@@ -598,7 +600,7 @@ export declare const publishServices: {
598
600
  domain: z.ZodString;
599
601
  priceUsd: z.ZodNullable<z.ZodString>;
600
602
  isAvailable: z.ZodBoolean;
601
- }, z.core.$strip>>>>;
603
+ }, z.core.$strip>>, false>>;
602
604
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/domain", {}, z.ZodNullable<z.ZodObject<{
603
605
  domain: z.ZodString;
604
606
  priceUsd: z.ZodString;
@@ -615,9 +617,11 @@ export declare const publishServices: {
615
617
  }>;
616
618
  error: z.ZodNullable<z.ZodString>;
617
619
  startedAt: z.ZodString;
620
+ registeredAt: z.ZodNullable<z.ZodString>;
621
+ expiresAt: z.ZodNullable<z.ZodString>;
618
622
  checkedAt: z.ZodNullable<z.ZodString>;
619
623
  finishedAt: z.ZodNullable<z.ZodString>;
620
- }, z.core.$strip>>>>;
624
+ }, z.core.$strip>>, false>>;
621
625
  readonly buy: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/domain", {
622
626
  domain: z.ZodString;
623
627
  priceUsd: z.ZodString;
@@ -637,9 +641,11 @@ export declare const publishServices: {
637
641
  }>;
638
642
  error: z.ZodNullable<z.ZodString>;
639
643
  startedAt: z.ZodString;
644
+ registeredAt: z.ZodNullable<z.ZodString>;
645
+ expiresAt: z.ZodNullable<z.ZodString>;
640
646
  checkedAt: z.ZodNullable<z.ZodString>;
641
647
  finishedAt: z.ZodNullable<z.ZodString>;
642
- }, z.core.$strip>>>;
648
+ }, z.core.$strip>, false>>;
643
649
  readonly retry: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/domain/retry", {}, z.ZodObject<{
644
650
  domain: z.ZodString;
645
651
  priceUsd: z.ZodString;
@@ -656,9 +662,11 @@ export declare const publishServices: {
656
662
  }>;
657
663
  error: z.ZodNullable<z.ZodString>;
658
664
  startedAt: z.ZodString;
665
+ registeredAt: z.ZodNullable<z.ZodString>;
666
+ expiresAt: z.ZodNullable<z.ZodString>;
659
667
  checkedAt: z.ZodNullable<z.ZodString>;
660
668
  finishedAt: z.ZodNullable<z.ZodString>;
661
- }, z.core.$strip>>>;
669
+ }, z.core.$strip>, false>>;
662
670
  };
663
671
  readonly mailboxes: {
664
672
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/mailbox", {}, z.ZodNullable<z.ZodObject<{
@@ -670,7 +678,7 @@ export declare const publishServices: {
670
678
  verifying: "verifying";
671
679
  }>;
672
680
  error: z.ZodNullable<z.ZodString>;
673
- }, z.core.$strip>>>>;
681
+ }, z.core.$strip>>, false>>;
674
682
  readonly create: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/mailbox", {
675
683
  localPart: z.ZodString;
676
684
  destination: z.ZodEmail;
@@ -683,7 +691,7 @@ export declare const publishServices: {
683
691
  verifying: "verifying";
684
692
  }>;
685
693
  error: z.ZodNullable<z.ZodString>;
686
- }, z.core.$strip>>>;
694
+ }, z.core.$strip>, false>>;
687
695
  };
688
696
  readonly database: {
689
697
  readonly schema: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/database", {}, z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -727,13 +735,41 @@ export declare const publishServices: {
727
735
  }, z.core.$strip>>;
728
736
  }, z.core.$strip>, z.ZodObject<{
729
737
  status: z.ZodLiteral<"outdated">;
730
- }, z.core.$strip>], "status">>>;
738
+ }, z.core.$strip>], "status">, false>>;
731
739
  readonly rows: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/database/:table", {
732
740
  after: z.ZodOptional<z.ZodString>;
733
741
  }, z.ZodObject<{
734
742
  rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
735
743
  next: z.ZodNullable<z.ZodString>;
736
- }, z.core.$strip>>>;
744
+ }, z.core.$strip>, false>>;
745
+ readonly update: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/database/:table/:id", {
746
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
747
+ after: z.ZodRecord<z.ZodString, z.ZodUnknown>;
748
+ }, z.ZodObject<{
749
+ status: z.ZodEnum<{
750
+ failed: "failed";
751
+ applied: "applied";
752
+ skipped: "skipped";
753
+ }>;
754
+ reason: z.ZodNullable<z.ZodString>;
755
+ row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
756
+ }, z.core.$strip>, false>>;
757
+ };
758
+ readonly media: {
759
+ readonly add: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/media", {
760
+ alt: z.ZodString;
761
+ id: z.ZodOptional<z.ZodString>;
762
+ }, z.ZodObject<{
763
+ id: z.ZodString;
764
+ key: z.ZodString;
765
+ width: z.ZodNumber;
766
+ height: z.ZodNumber;
767
+ placeholder: z.ZodNullable<z.ZodString>;
768
+ alt: z.ZodString;
769
+ type: z.ZodString;
770
+ bytes: z.ZodNumber;
771
+ createdAt: z.ZodString;
772
+ }, z.core.$strip>, true>>;
737
773
  };
738
774
  readonly changes: {
739
775
  readonly list: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes", {}, z.ZodArray<z.ZodObject<{
@@ -776,7 +812,7 @@ export declare const publishServices: {
776
812
  reason: z.ZodNullable<z.ZodString>;
777
813
  row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
778
814
  }, z.core.$strip>>>;
779
- }, z.core.$strip>>>>;
815
+ }, z.core.$strip>>, false>>;
780
816
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes/:id", {}, z.ZodObject<{
781
817
  id: z.ZodString;
782
818
  status: z.ZodEnum<{
@@ -817,7 +853,7 @@ export declare const publishServices: {
817
853
  reason: z.ZodNullable<z.ZodString>;
818
854
  row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
819
855
  }, z.core.$strip>>>;
820
- }, z.core.$strip>>>;
856
+ }, z.core.$strip>, false>>;
821
857
  readonly create: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes", {
822
858
  asked: z.ZodString;
823
859
  changes: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -876,7 +912,7 @@ export declare const publishServices: {
876
912
  reason: z.ZodNullable<z.ZodString>;
877
913
  row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
878
914
  }, z.core.$strip>>>;
879
- }, z.core.$strip>>>;
915
+ }, z.core.$strip>, false>>;
880
916
  readonly approve: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes/:id/approve", {}, z.ZodObject<{
881
917
  id: z.ZodString;
882
918
  status: z.ZodEnum<{
@@ -917,7 +953,7 @@ export declare const publishServices: {
917
953
  reason: z.ZodNullable<z.ZodString>;
918
954
  row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
919
955
  }, z.core.$strip>>>;
920
- }, z.core.$strip>>>;
956
+ }, z.core.$strip>, false>>;
921
957
  readonly decline: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes/:id/decline", {}, z.ZodObject<{
922
958
  id: z.ZodString;
923
959
  status: z.ZodEnum<{
@@ -958,7 +994,7 @@ export declare const publishServices: {
958
994
  reason: z.ZodNullable<z.ZodString>;
959
995
  row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
960
996
  }, z.core.$strip>>>;
961
- }, z.core.$strip>>>;
997
+ }, z.core.$strip>, false>>;
962
998
  readonly undo: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes/:id/undo", {}, z.ZodObject<{
963
999
  id: z.ZodString;
964
1000
  status: z.ZodEnum<{
@@ -999,15 +1035,15 @@ export declare const publishServices: {
999
1035
  reason: z.ZodNullable<z.ZodString>;
1000
1036
  row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1001
1037
  }, z.core.$strip>>>;
1002
- }, z.core.$strip>>>;
1038
+ }, z.core.$strip>, false>>;
1003
1039
  };
1004
1040
  readonly logins: {
1005
1041
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/cli/logins/:approval", {}, z.ZodObject<{
1006
1042
  code: z.ZodString;
1007
1043
  label: z.ZodString;
1008
- }, z.core.$strip>>>;
1044
+ }, z.core.$strip>, false>>;
1009
1045
  readonly approve: NoInfer<import("./services.ts").ServiceCall<"/cli/logins/:approval/approve", {}, z.ZodObject<{
1010
1046
  ok: z.ZodBoolean;
1011
- }, z.core.$strip>>>;
1047
+ }, z.core.$strip>, false>>;
1012
1048
  };
1013
1049
  };
@@ -217,6 +217,8 @@ export const domainSummary = z.object({
217
217
  step: z.enum(domainSteps),
218
218
  error: z.string().nullable(),
219
219
  startedAt: z.string(),
220
+ registeredAt: z.string().nullable(),
221
+ expiresAt: z.string().nullable(),
220
222
  checkedAt: z.string().nullable(),
221
223
  finishedAt: z.string().nullable()
222
224
  });
@@ -374,6 +376,18 @@ export const publishServices = defineServices({
374
376
  get: '/apps/:name/database/:table',
375
377
  input: { after: z.string().optional() },
376
378
  reply: databaseRows
379
+ }),
380
+ update: call({
381
+ post: '/apps/:name/database/:table/:id',
382
+ input: { before: databaseRow, after: databaseRow },
383
+ reply: changeOutcome
384
+ })
385
+ },
386
+ media: {
387
+ add: call({
388
+ upload: '/apps/:name/media',
389
+ input: { alt: z.string(), id: z.string().optional() },
390
+ reply: mediaRow
377
391
  })
378
392
  },
379
393
  changes: {
package/dist/react.d.ts CHANGED
@@ -24,6 +24,9 @@ export { EmptyState } from './components/EmptyState.tsx';
24
24
  export { Form } from './components/Form.tsx';
25
25
  export { Icon } from './components/Icon.tsx';
26
26
  export { IconButton } from './components/IconButton.tsx';
27
+ export { FilePicker } from './components/FilePicker.tsx';
28
+ export { Gallery } from './components/Gallery.tsx';
29
+ export { Header } from './components/Header.tsx';
27
30
  export { Image } from './components/Image.tsx';
28
31
  export type { IconName } from './components/icons.ts';
29
32
  export { Kbd } from './components/Kbd.tsx';
@@ -34,6 +37,7 @@ export { Menu } from './components/Menu.tsx';
34
37
  export type { MenuEntry } from './components/MenuItems.tsx';
35
38
  export type { SidebarLink } from './components/Sidebar.tsx';
36
39
  export type { Placement } from './components/Placement.ts';
40
+ export { ProgressBar } from './components/ProgressBar.tsx';
37
41
  export { PropertyRow } from './components/PropertyRow.tsx';
38
42
  export { RadioGroup } from './components/RadioGroup.tsx';
39
43
  export type { RadioOption } from './components/RadioGroup.tsx';
@@ -51,6 +55,7 @@ export type { SpaceName } from './components/spacing.ts';
51
55
  export { Spinner } from './components/Spinner.tsx';
52
56
  export { Stack } from './components/Stack.tsx';
53
57
  export { Switch } from './components/Switch.tsx';
58
+ export { TableRow } from './components/TableRow.tsx';
54
59
  export { Text } from './components/Text.tsx';
55
60
  export { TextField } from './components/TextField.tsx';
56
61
  export { Toaster } from './components/Toaster.tsx';
package/dist/react.js CHANGED
@@ -22,12 +22,16 @@ export { EmptyState } from "./components/EmptyState.js";
22
22
  export { Form } from "./components/Form.js";
23
23
  export { Icon } from "./components/Icon.js";
24
24
  export { IconButton } from "./components/IconButton.js";
25
+ export { FilePicker } from "./components/FilePicker.js";
26
+ export { Gallery } from "./components/Gallery.js";
27
+ export { Header } from "./components/Header.js";
25
28
  export { Image } from "./components/Image.js";
26
29
  export { Kbd } from "./components/Kbd.js";
27
30
  export { Link } from "./components/Link.js";
28
31
  export { ListRow } from "./components/ListRow.js";
29
32
  export { LoadingRows } from "./components/LoadingRows.js";
30
33
  export { Menu } from "./components/Menu.js";
34
+ export { ProgressBar } from "./components/ProgressBar.js";
31
35
  export { PropertyRow } from "./components/PropertyRow.js";
32
36
  export { RadioGroup } from "./components/RadioGroup.js";
33
37
  export { Row } from "./components/Row.js";
@@ -41,6 +45,7 @@ export { Skeleton } from "./components/Skeleton.js";
41
45
  export { Spinner } from "./components/Spinner.js";
42
46
  export { Stack } from "./components/Stack.js";
43
47
  export { Switch } from "./components/Switch.js";
48
+ export { TableRow } from "./components/TableRow.js";
44
49
  export { Text } from "./components/Text.js";
45
50
  export { TextField } from "./components/TextField.js";
46
51
  export { Toaster } from "./components/Toaster.js";
@@ -3,6 +3,7 @@ const TOKENS_FILE = 'src/theme/tokens.stylex.ts';
3
3
  const APP_THEME_FILE = 'src/theme/theme.ts';
4
4
  const APP_COLORS_FILE = 'src/theme/colors.stylex.ts';
5
5
  const SCROLL_AREA_FILE = 'src/components/ScrollArea.tsx';
6
+ const GALLERY_FILE = 'src/components/Gallery.tsx';
6
7
  const SELECTABLE_FILE = 'src/components/Text.tsx';
7
8
  const PRIMITIVE_ELEMENTS = new Set([
8
9
  'button',
@@ -64,7 +65,10 @@ const PRIMITIVES = new Set([
64
65
  'Divider',
65
66
  'CopyButton',
66
67
  'EmptyState',
68
+ 'FilePicker',
67
69
  'Form',
70
+ 'Gallery',
71
+ 'Header',
68
72
  'Icon',
69
73
  'IconButton',
70
74
  'Image',
@@ -73,6 +77,7 @@ const PRIMITIVES = new Set([
73
77
  'ListRow',
74
78
  'LoadingRows',
75
79
  'Menu',
80
+ 'ProgressBar',
76
81
  'PropertyRow',
77
82
  'RadioGroup',
78
83
  'Row',
@@ -86,6 +91,7 @@ const PRIMITIVES = new Set([
86
91
  'Spinner',
87
92
  'Stack',
88
93
  'Switch',
94
+ 'TableRow',
89
95
  'Text',
90
96
  'TextField',
91
97
  'Toaster',
@@ -374,8 +380,9 @@ const hoverUsesHighlight = styleRule((declarations) => declarations
374
380
  message: HOVER_USES_HIGHLIGHT_MESSAGE
375
381
  })));
376
382
  const scrollViewOnly = styleRule((declarations, context) => {
377
- if (inFile(context, SCROLL_AREA_FILE))
383
+ if (inFile(context, SCROLL_AREA_FILE) || inFile(context, GALLERY_FILE)) {
378
384
  return [];
385
+ }
379
386
  return declarations
380
387
  .filter((d) => SCROLLING_OVERFLOW_PROPERTIES.has(d.property) &&
381
388
  ['auto', 'scroll'].includes(stringValue(d.value) ?? ''))
package/dist/schema.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { call, defineServices } from './services.ts';
2
- export type { CallInput, CallReply, ServiceCall, ServiceMemory, Services } from './services.ts';
2
+ export type { CallInput, CallReply, HandlerInput, ServiceCall, ServiceMemory, Services, UploadOptions } from './services.ts';
3
3
  export { media } from './media.ts';
4
4
  export { bookingTables } from './bookings.ts';
5
5
  export type { BookingDay, BookingRequest, BookingRequestFields, BookingRules, BookingSlot, BookingStatus, BookingWindow } from './bookings.ts';
@@ -1,9 +1,9 @@
1
1
  import type { Context, Env } from 'hono';
2
2
  import { Hono } from 'hono';
3
- import type { CallInput, CallReply, Services } from '../services.ts';
3
+ import type { CallReply, HandlerInput, Services } from '../services.ts';
4
4
  export type ServiceHandlers<Group extends Services, E extends Env> = {
5
5
  readonly [Name in keyof Group]: {
6
- readonly [Call in keyof Group[Name]]: (c: Context<E>, input: CallInput<Group[Name][Call]>) => CallReply<Group[Name][Call]> | Promise<CallReply<Group[Name][Call]>>;
6
+ readonly [Call in keyof Group[Name]]: (c: Context<E>, input: HandlerInput<Group[Name][Call]>) => CallReply<Group[Name][Call]> | Promise<CallReply<Group[Name][Call]>>;
7
7
  };
8
8
  };
9
9
  export declare function serveServices<Group extends Services, E extends Env>(services: Group, handlers: ServiceHandlers<Group, E>): Hono<E>;
@@ -1,9 +1,27 @@
1
1
  import { Hono } from 'hono';
2
2
  import { z } from 'zod';
3
3
  import { ServiceRefused, paramNamesOf } from "../services.js";
4
+ const NO_FILE = 'Send the file as the form field "file".';
5
+ async function uploadOf(c, call) {
6
+ const form = await c.req.formData().catch(() => undefined);
7
+ const file = form?.get('file');
8
+ if (form === undefined || !(file instanceof File)) {
9
+ return { ok: false, problem: NO_FILE };
10
+ }
11
+ const fields = Object.fromEntries([...form.entries()].filter((entry) => entry[0] !== 'file' && typeof entry[1] === 'string'));
12
+ const parsed = call.input.safeParse(fields);
13
+ return parsed.success
14
+ ? { ok: true, data: { ...parsed.data, file } }
15
+ : { ok: false, problem: z.prettifyError(parsed.error) };
16
+ }
4
17
  async function inputOf(c, call) {
18
+ if (call.isUpload)
19
+ return uploadOf(c, call);
5
20
  const raw = call.method === 'get' ? c.req.query() : await c.req.json().catch(() => ({}));
6
- return call.input.safeParse(raw);
21
+ const parsed = call.input.safeParse(raw);
22
+ return parsed.success
23
+ ? { ok: true, data: parsed.data }
24
+ : { ok: false, problem: z.prettifyError(parsed.error) };
7
25
  }
8
26
  const BUILT_IN_PATHS = ['/auth', '/health', '/tables', '/app', '/me', '/schema'];
9
27
  function builtInPathOf(path) {
@@ -24,9 +42,8 @@ export function serveServices(services, handlers) {
24
42
  const handler = Reflect.get(Reflect.get(handlers, group), name);
25
43
  server.on(call.method.toUpperCase(), call.path, async (c) => {
26
44
  const parsed = await inputOf(c, call);
27
- if (!parsed.success) {
28
- return c.json({ error: z.prettifyError(parsed.error) }, 400);
29
- }
45
+ if (!parsed.ok)
46
+ return c.json({ error: parsed.problem }, 400);
30
47
  const params = Object.fromEntries(paramNamesOf(call.path).map((key) => [key, c.req.param(key) ?? '']));
31
48
  try {
32
49
  const reply = await handler(c, {
@@ -1,5 +1,8 @@
1
+ import { pickedFile, pickedFileOf } from "./files.js";
1
2
  import { MemoryServer } from "./memoryApi.js";
2
3
  import { ServiceRefused, paramNamesOf } from "./services.js";
4
+ const FILE_GONE = 'Choose the file again.';
5
+ const NOT_SENT = 'Couldn’t send it. Check your connection and try again.';
3
6
  function buildClient(services, make) {
4
7
  return Object.fromEntries(Object.entries(services).map(([group, calls]) => [
5
8
  group,
@@ -21,38 +24,109 @@ function errorOf(body, status) {
21
24
  : undefined;
22
25
  return typeof error === 'string' ? error : `HTTP ${status}`;
23
26
  }
24
- export function httpServices(services, gate, onSignedOut) {
25
- return buildClient(services, (group, name, call) => (input = {}) => gate.pass(`${group}.${name}`, async () => {
26
- const { path, rest } = splitInput(call, input);
27
- const sent = call.input.parse(rest);
28
- const fields = Object.entries(sent).filter(([, value]) => value !== undefined);
29
- const query = call.method === 'get' && fields.length > 0
30
- ? `?${new URLSearchParams(fields.map(([key, value]) => [key, String(value)])).toString()}`
31
- : '';
32
- const response = await fetch(`/api${path}${query}`, {
33
- method: call.method.toUpperCase(),
34
- ...(call.method === 'post'
35
- ? {
36
- headers: { 'content-type': 'application/json' },
37
- body: JSON.stringify(sent)
38
- }
39
- : {})
27
+ function fileIdOf(input) {
28
+ const file = input['file'];
29
+ const id = typeof file === 'object' && file !== null
30
+ ? Reflect.get(file, 'id')
31
+ : undefined;
32
+ return typeof id === 'string' ? id : '';
33
+ }
34
+ function formOf(fields, file) {
35
+ const form = new FormData();
36
+ for (const [key, value] of Object.entries(fields)) {
37
+ if (value !== undefined)
38
+ form.set(key, String(value));
39
+ }
40
+ form.set('file', file, file.name);
41
+ return form;
42
+ }
43
+ function bodyOf(text) {
44
+ try {
45
+ return JSON.parse(text);
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ function sendForm(url, form, { onProgress, signal }) {
52
+ return new Promise((resolve, reject) => {
53
+ const request = new XMLHttpRequest();
54
+ const abort = () => request.abort();
55
+ request.open('POST', url);
56
+ request.upload.addEventListener('progress', (event) => {
57
+ if (event.lengthComputable)
58
+ onProgress?.(event.loaded, event.total);
40
59
  });
41
- const body = await response.json().catch(() => null);
42
- if (response.status === 401)
60
+ request.addEventListener('load', () => {
61
+ signal?.removeEventListener('abort', abort);
62
+ resolve({ status: request.status, body: bodyOf(request.responseText) });
63
+ });
64
+ request.addEventListener('error', () => {
65
+ signal?.removeEventListener('abort', abort);
66
+ reject(new ServiceRefused(0, NOT_SENT));
67
+ });
68
+ request.addEventListener('abort', () => {
69
+ reject(new DOMException('The upload was cancelled', 'AbortError'));
70
+ });
71
+ if (signal?.aborted === true) {
72
+ reject(new DOMException('The upload was cancelled', 'AbortError'));
73
+ return;
74
+ }
75
+ signal?.addEventListener('abort', abort);
76
+ request.send(form);
77
+ });
78
+ }
79
+ async function upload(call, input, options) {
80
+ const { path, rest } = splitInput(call, input);
81
+ const file = pickedFileOf(fileIdOf(input));
82
+ if (file === undefined)
83
+ throw new ServiceRefused(400, FILE_GONE);
84
+ const fields = call.input.parse(Object.fromEntries(Object.entries(rest).filter(([key]) => key !== 'file')));
85
+ return sendForm(`/api${path}`, formOf(fields, file), options);
86
+ }
87
+ async function request(call, input) {
88
+ const { path, rest } = splitInput(call, input);
89
+ const sent = call.input.parse(rest);
90
+ const fields = Object.entries(sent).filter(([, value]) => value !== undefined);
91
+ const query = call.method === 'get' && fields.length > 0
92
+ ? `?${new URLSearchParams(fields.map(([key, value]) => [key, String(value)])).toString()}`
93
+ : '';
94
+ const response = await fetch(`/api${path}${query}`, {
95
+ method: call.method.toUpperCase(),
96
+ ...(call.method === 'post'
97
+ ? {
98
+ headers: { 'content-type': 'application/json' },
99
+ body: JSON.stringify(sent)
100
+ }
101
+ : {})
102
+ });
103
+ return {
104
+ status: response.status,
105
+ body: await response.json().catch(() => null)
106
+ };
107
+ }
108
+ export function httpServices(services, gate, onSignedOut) {
109
+ return buildClient(services, (group, name, call) => (input = {}, options = {}) => gate.pass(`${group}.${name}`, async () => {
110
+ const { status, body } = call.isUpload
111
+ ? await upload(call, input, options)
112
+ : await request(call, input);
113
+ if (status === 401)
43
114
  onSignedOut();
44
- if (!response.ok) {
45
- throw new ServiceRefused(response.status, errorOf(body, response.status));
115
+ if (status < 200 || status >= 300) {
116
+ throw new ServiceRefused(status, errorOf(body, status));
46
117
  }
47
118
  return call.reply.parse(body);
48
119
  }));
49
120
  }
50
121
  export function memoryServices(services, memory, world, options) {
51
122
  const servers = new Map();
52
- return buildClient(services, (group, name, call) => (input = {}) => {
123
+ return buildClient(services, (group, name, call) => (input = {}, upload = {}) => {
53
124
  const server = servers.get(group) ?? new MemoryServer(group, options);
54
125
  servers.set(group, server);
55
126
  return server.reply(name, () => {
127
+ if (upload.signal?.aborted === true) {
128
+ throw new DOMException('The upload was cancelled', 'AbortError');
129
+ }
56
130
  const handlers = Reflect.get(memory, group);
57
131
  const handler = typeof handlers === 'object' && handlers !== null
58
132
  ? Reflect.get(handlers, name)
@@ -61,8 +135,18 @@ export function memoryServices(services, memory, world, options) {
61
135
  throw new Error(`The memory fake has no ${group}.${name}`);
62
136
  }
63
137
  const { rest } = splitInput(call, input);
64
- const params = Object.fromEntries(paramNamesOf(call.path).map((key) => [key, String(input[key] ?? '')]));
65
- return call.reply.parse(handler(world, { ...params, ...call.input.parse(rest) }));
138
+ const params = Object.fromEntries(paramNamesOf(call.path).map((key) => [
139
+ key,
140
+ String(input[key] ?? '')
141
+ ]));
142
+ const file = call.isUpload
143
+ ? pickedFile.parse(input['file'])
144
+ : undefined;
145
+ return call.reply.parse(handler(world, {
146
+ ...params,
147
+ ...call.input.parse(rest),
148
+ ...(file === undefined ? {} : { file })
149
+ }));
66
150
  });
67
151
  });
68
152
  }