@trypattern/sdk 0.1.31 → 0.1.33

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.
@@ -4,6 +4,7 @@ export const viewports = {
4
4
  compact: { width: 390, height: 844, deviceScaleFactor: 2 }
5
5
  };
6
6
  export const viewportNames = ['desktop', 'compact'];
7
+ const STAND_IN_PHOTO = '<svg xmlns="http://www.w3.org/2000/svg" width="4" height="3"><rect width="4" height="3" fill="#d9d4c8"/></svg>';
7
8
  const QUIET_NETWORK_MS = 250;
8
9
  const SETTLE_TIMEOUT_MS = 10_000;
9
10
  const HOOK_TIMEOUT_MS = 10_000;
@@ -122,6 +123,12 @@ export async function openApp(browser, baseUrl, path, viewport, signedInAs, memo
122
123
  });
123
124
  }, { ...memory, signedInAs });
124
125
  }
126
+ await context.route((url) => url.origin !== new URL(baseUrl).origin, (route) => route.request().resourceType() === 'image'
127
+ ? route.fulfill({
128
+ contentType: 'image/svg+xml',
129
+ body: STAND_IN_PHOTO
130
+ })
131
+ : route.continue());
125
132
  const page = await context.newPage();
126
133
  const problems = [];
127
134
  recordProblems(page, problems);
@@ -1,6 +1,7 @@
1
1
  import { expect } from 'vitest';
2
2
  import type { ApiCall } from '../api.ts';
3
3
  import type { AppClass, RunningApp } from '../app.ts';
4
+ import type { Services } from '../services.ts';
4
5
  import type { Seed, Tables } from '../table.ts';
5
6
  export { expect };
6
7
  export { accessFlows } from './accessFlows.ts';
@@ -39,9 +40,13 @@ export type FlowOptions<Call extends string> = {
39
40
  };
40
41
  export type FlowBinding<Group extends Tables, Command, Snapshot extends {
41
42
  readonly path: string;
42
- }> = {
43
+ }, AppServices extends Services = Record<never, never>> = {
43
44
  App: AppClass<Group, RunningApp<Command, Snapshot>>;
45
+ services?: AppServices;
44
46
  };
47
+ type ServiceCallName<Group extends Services> = {
48
+ [Name in keyof Group & string]: `${Name}.${keyof Group[Name] & string}`;
49
+ }[keyof Group & string];
45
50
  export declare function defineFlow<Group extends Tables, Command, Snapshot extends {
46
51
  readonly path: string;
47
- }>({ App }: FlowBinding<Group, Command, Snapshot>): (name: string, steps: readonly FlowStep<Command, Snapshot, ApiCall<Group>>[], { startAt, failing, signedInAs }?: FlowOptions<ApiCall<Group>>) => void;
52
+ }, AppServices extends Services = Record<never, never>>({ App }: FlowBinding<Group, Command, Snapshot, AppServices>): (name: string, steps: readonly FlowStep<Command, Snapshot, ApiCall<Group> | ServiceCallName<AppServices>>[], { startAt, failing, signedInAs }?: FlowOptions<ApiCall<Group> | ServiceCallName<AppServices>>) => void;
package/dist/index.d.ts CHANGED
@@ -22,7 +22,9 @@ export type { User } from './auth/user.ts';
22
22
  export type { AppClass, AppDefinition, AppDependencies, AppSnapshotShape, BlockStores, CheckedBlocks, DataRouter, ModelServicesOf, NavigateCommand, PressKeyCommand, PublicStores, PlainJsonModel, RunningApp, StoreClasses, StoreInstances, StoresOf, TableStores } from './app.ts';
23
23
  export { defineRoutes } from './routes.ts';
24
24
  export { ServiceRefused, refused } from './services.ts';
25
- export type { ServiceClient, ServiceMemory } from './services.ts';
25
+ export type { ServiceClient, ServiceMemory, UploadOptions } from './services.ts';
26
+ export { pickedFile } from './files.ts';
27
+ export type { PickedFile } from './files.ts';
26
28
  export { manualClock, realClock } from './clock.ts';
27
29
  export type { Clock } from './clock.ts';
28
30
  export type { PageOf, PathOf, RouteDefinition } from './routes.ts';
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ export { HttpSessionApi, MemorySessionApi } from "./auth/sessionApi.js";
10
10
  export { userSchema } from "./auth/user.js";
11
11
  export { defineRoutes } from "./routes.js";
12
12
  export { ServiceRefused, refused } from "./services.js";
13
+ export { pickedFile } from "./files.js";
13
14
  export { manualClock, realClock } from "./clock.js";
14
15
  export { Model, Store, action, appPath, defineActions } from "./registered.js";
15
16
  export { Cache, Cached } from "./cache.js";
package/dist/media.d.ts CHANGED
@@ -40,7 +40,8 @@ export type ImageSources = {
40
40
  width: number;
41
41
  height: number;
42
42
  };
43
- export declare function imageSources(image: MediaImage, { aspect, transforms }: {
43
+ export declare function imageSources(image: MediaImage, { aspect, transforms, origin }: {
44
44
  aspect?: number | undefined;
45
45
  transforms: boolean;
46
+ origin?: string;
46
47
  }): ImageSources;
package/dist/media.js CHANGED
@@ -46,14 +46,14 @@ function transformed(key, width, aspect) {
46
46
  ];
47
47
  return `/cdn-cgi/image/${options.join(',')}${mediaPath(key)}`;
48
48
  }
49
- export function imageSources(image, { aspect, transforms }) {
49
+ export function imageSources(image, { aspect, transforms, origin = '' }) {
50
50
  const largest = largestWidth(image, aspect);
51
51
  const height = aspect === undefined
52
52
  ? image.height
53
53
  : Math.max(1, Math.round(largest / aspect));
54
54
  if (!transforms) {
55
55
  return {
56
- src: mediaPath(image.key),
56
+ src: `${origin}${mediaPath(image.key)}`,
57
57
  srcSet: undefined,
58
58
  width: largest,
59
59
  height
@@ -64,9 +64,9 @@ export function imageSources(image, { aspect, transforms }) {
64
64
  largest
65
65
  ].filter((width, index, all) => all.indexOf(width) === index);
66
66
  return {
67
- src: transformed(image.key, Math.min(FALLBACK_WIDTH, largest), aspect),
67
+ src: `${origin}${transformed(image.key, Math.min(FALLBACK_WIDTH, largest), aspect)}`,
68
68
  srcSet: widths
69
- .map((width) => `${transformed(image.key, width, aspect)} ${width}w`)
69
+ .map((width) => `${origin}${transformed(image.key, width, aspect)} ${width}w`)
70
70
  .join(', '),
71
71
  width: largest,
72
72
  height
@@ -438,7 +438,7 @@ export declare const publishServices: {
438
438
  startedAt: z.ZodString;
439
439
  finishedAt: z.ZodNullable<z.ZodString>;
440
440
  }, z.core.$strip>>;
441
- }, z.core.$strip>>>>;
441
+ }, z.core.$strip>>, false>>;
442
442
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/apps/:name", {}, z.ZodObject<{
443
443
  name: z.ZodString;
444
444
  title: z.ZodString;
@@ -465,7 +465,7 @@ export declare const publishServices: {
465
465
  startedAt: z.ZodString;
466
466
  finishedAt: z.ZodNullable<z.ZodString>;
467
467
  }, z.core.$strip>>;
468
- }, z.core.$strip>>>;
468
+ }, z.core.$strip>, false>>;
469
469
  readonly create: NoInfer<import("./services.ts").ServiceCall<"/apps", {
470
470
  name: z.ZodString;
471
471
  title: z.ZodString;
@@ -499,7 +499,7 @@ export declare const publishServices: {
499
499
  startedAt: z.ZodString;
500
500
  finishedAt: z.ZodNullable<z.ZodString>;
501
501
  }, z.core.$strip>>;
502
- }, z.core.$strip>>>;
502
+ }, z.core.$strip>, false>>;
503
503
  };
504
504
  readonly names: {
505
505
  readonly suggest: NoInfer<import("./services.ts").ServiceCall<"/names", {
@@ -508,12 +508,12 @@ export declare const publishServices: {
508
508
  name: z.ZodString;
509
509
  available: z.ZodBoolean;
510
510
  problem: z.ZodNullable<z.ZodString>;
511
- }, z.core.$strip>>>>;
511
+ }, z.core.$strip>>, false>>;
512
512
  readonly check: NoInfer<import("./services.ts").ServiceCall<"/names/:name", {}, z.ZodObject<{
513
513
  name: z.ZodString;
514
514
  available: z.ZodBoolean;
515
515
  problem: z.ZodNullable<z.ZodString>;
516
- }, z.core.$strip>>>;
516
+ }, z.core.$strip>, false>>;
517
517
  };
518
518
  readonly publishes: {
519
519
  readonly list: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/publishes", {}, z.ZodArray<z.ZodObject<{
@@ -536,7 +536,7 @@ export declare const publishServices: {
536
536
  message: z.ZodNullable<z.ZodString>;
537
537
  startedAt: z.ZodString;
538
538
  finishedAt: z.ZodNullable<z.ZodString>;
539
- }, z.core.$strip>>>>;
539
+ }, z.core.$strip>>, false>>;
540
540
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/publishes/:number", {}, z.ZodObject<{
541
541
  number: z.ZodNumber;
542
542
  status: z.ZodEnum<{
@@ -557,7 +557,7 @@ export declare const publishServices: {
557
557
  message: z.ZodNullable<z.ZodString>;
558
558
  startedAt: z.ZodString;
559
559
  finishedAt: z.ZodNullable<z.ZodString>;
560
- }, z.core.$strip>>>;
560
+ }, z.core.$strip>, false>>;
561
561
  readonly retry: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/publishes/:number/retry", {}, z.ZodObject<{
562
562
  number: z.ZodNumber;
563
563
  status: z.ZodEnum<{
@@ -578,20 +578,20 @@ export declare const publishServices: {
578
578
  message: z.ZodNullable<z.ZodString>;
579
579
  startedAt: z.ZodString;
580
580
  finishedAt: z.ZodNullable<z.ZodString>;
581
- }, z.core.$strip>>>;
581
+ }, z.core.$strip>, false>>;
582
582
  };
583
583
  readonly setup: {
584
584
  readonly newCode: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/setup-code", {}, z.ZodObject<{
585
585
  code: z.ZodString;
586
586
  expiresAt: z.ZodString;
587
- }, z.core.$strip>>>;
587
+ }, z.core.$strip>, false>>;
588
588
  readonly connection: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/connection", {}, z.ZodNullable<z.ZodObject<{
589
589
  label: z.ZodString;
590
590
  connectedAt: z.ZodString;
591
- }, z.core.$strip>>>>;
591
+ }, z.core.$strip>>, false>>;
592
592
  readonly disconnect: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/connection/disconnect", {}, z.ZodObject<{
593
593
  ok: z.ZodBoolean;
594
- }, z.core.$strip>>>;
594
+ }, z.core.$strip>, false>>;
595
595
  };
596
596
  readonly domains: {
597
597
  readonly search: NoInfer<import("./services.ts").ServiceCall<"/domains", {
@@ -600,7 +600,7 @@ export declare const publishServices: {
600
600
  domain: z.ZodString;
601
601
  priceUsd: z.ZodNullable<z.ZodString>;
602
602
  isAvailable: z.ZodBoolean;
603
- }, z.core.$strip>>>>;
603
+ }, z.core.$strip>>, false>>;
604
604
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/domain", {}, z.ZodNullable<z.ZodObject<{
605
605
  domain: z.ZodString;
606
606
  priceUsd: z.ZodString;
@@ -621,7 +621,7 @@ export declare const publishServices: {
621
621
  expiresAt: z.ZodNullable<z.ZodString>;
622
622
  checkedAt: z.ZodNullable<z.ZodString>;
623
623
  finishedAt: z.ZodNullable<z.ZodString>;
624
- }, z.core.$strip>>>>;
624
+ }, z.core.$strip>>, false>>;
625
625
  readonly buy: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/domain", {
626
626
  domain: z.ZodString;
627
627
  priceUsd: z.ZodString;
@@ -645,7 +645,7 @@ export declare const publishServices: {
645
645
  expiresAt: z.ZodNullable<z.ZodString>;
646
646
  checkedAt: z.ZodNullable<z.ZodString>;
647
647
  finishedAt: z.ZodNullable<z.ZodString>;
648
- }, z.core.$strip>>>;
648
+ }, z.core.$strip>, false>>;
649
649
  readonly retry: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/domain/retry", {}, z.ZodObject<{
650
650
  domain: z.ZodString;
651
651
  priceUsd: z.ZodString;
@@ -666,7 +666,7 @@ export declare const publishServices: {
666
666
  expiresAt: z.ZodNullable<z.ZodString>;
667
667
  checkedAt: z.ZodNullable<z.ZodString>;
668
668
  finishedAt: z.ZodNullable<z.ZodString>;
669
- }, z.core.$strip>>>;
669
+ }, z.core.$strip>, false>>;
670
670
  };
671
671
  readonly mailboxes: {
672
672
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/mailbox", {}, z.ZodNullable<z.ZodObject<{
@@ -678,7 +678,7 @@ export declare const publishServices: {
678
678
  verifying: "verifying";
679
679
  }>;
680
680
  error: z.ZodNullable<z.ZodString>;
681
- }, z.core.$strip>>>>;
681
+ }, z.core.$strip>>, false>>;
682
682
  readonly create: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/mailbox", {
683
683
  localPart: z.ZodString;
684
684
  destination: z.ZodEmail;
@@ -691,7 +691,7 @@ export declare const publishServices: {
691
691
  verifying: "verifying";
692
692
  }>;
693
693
  error: z.ZodNullable<z.ZodString>;
694
- }, z.core.$strip>>>;
694
+ }, z.core.$strip>, false>>;
695
695
  };
696
696
  readonly database: {
697
697
  readonly schema: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/database", {}, z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -735,13 +735,41 @@ export declare const publishServices: {
735
735
  }, z.core.$strip>>;
736
736
  }, z.core.$strip>, z.ZodObject<{
737
737
  status: z.ZodLiteral<"outdated">;
738
- }, z.core.$strip>], "status">>>;
738
+ }, z.core.$strip>], "status">, false>>;
739
739
  readonly rows: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/database/:table", {
740
740
  after: z.ZodOptional<z.ZodString>;
741
741
  }, z.ZodObject<{
742
742
  rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
743
743
  next: z.ZodNullable<z.ZodString>;
744
- }, 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>>;
745
773
  };
746
774
  readonly changes: {
747
775
  readonly list: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes", {}, z.ZodArray<z.ZodObject<{
@@ -784,7 +812,7 @@ export declare const publishServices: {
784
812
  reason: z.ZodNullable<z.ZodString>;
785
813
  row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
786
814
  }, z.core.$strip>>>;
787
- }, z.core.$strip>>>>;
815
+ }, z.core.$strip>>, false>>;
788
816
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes/:id", {}, z.ZodObject<{
789
817
  id: z.ZodString;
790
818
  status: z.ZodEnum<{
@@ -825,7 +853,7 @@ export declare const publishServices: {
825
853
  reason: z.ZodNullable<z.ZodString>;
826
854
  row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
827
855
  }, z.core.$strip>>>;
828
- }, z.core.$strip>>>;
856
+ }, z.core.$strip>, false>>;
829
857
  readonly create: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes", {
830
858
  asked: z.ZodString;
831
859
  changes: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -884,7 +912,7 @@ export declare const publishServices: {
884
912
  reason: z.ZodNullable<z.ZodString>;
885
913
  row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
886
914
  }, z.core.$strip>>>;
887
- }, z.core.$strip>>>;
915
+ }, z.core.$strip>, false>>;
888
916
  readonly approve: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes/:id/approve", {}, z.ZodObject<{
889
917
  id: z.ZodString;
890
918
  status: z.ZodEnum<{
@@ -925,7 +953,7 @@ export declare const publishServices: {
925
953
  reason: z.ZodNullable<z.ZodString>;
926
954
  row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
927
955
  }, z.core.$strip>>>;
928
- }, z.core.$strip>>>;
956
+ }, z.core.$strip>, false>>;
929
957
  readonly decline: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes/:id/decline", {}, z.ZodObject<{
930
958
  id: z.ZodString;
931
959
  status: z.ZodEnum<{
@@ -966,7 +994,7 @@ export declare const publishServices: {
966
994
  reason: z.ZodNullable<z.ZodString>;
967
995
  row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
968
996
  }, z.core.$strip>>>;
969
- }, z.core.$strip>>>;
997
+ }, z.core.$strip>, false>>;
970
998
  readonly undo: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes/:id/undo", {}, z.ZodObject<{
971
999
  id: z.ZodString;
972
1000
  status: z.ZodEnum<{
@@ -1007,15 +1035,15 @@ export declare const publishServices: {
1007
1035
  reason: z.ZodNullable<z.ZodString>;
1008
1036
  row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1009
1037
  }, z.core.$strip>>>;
1010
- }, z.core.$strip>>>;
1038
+ }, z.core.$strip>, false>>;
1011
1039
  };
1012
1040
  readonly logins: {
1013
1041
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/cli/logins/:approval", {}, z.ZodObject<{
1014
1042
  code: z.ZodString;
1015
1043
  label: z.ZodString;
1016
- }, z.core.$strip>>>;
1044
+ }, z.core.$strip>, false>>;
1017
1045
  readonly approve: NoInfer<import("./services.ts").ServiceCall<"/cli/logins/:approval/approve", {}, z.ZodObject<{
1018
1046
  ok: z.ZodBoolean;
1019
- }, z.core.$strip>>>;
1047
+ }, z.core.$strip>, false>>;
1020
1048
  };
1021
1049
  };
@@ -376,6 +376,18 @@ export const publishServices = defineServices({
376
376
  get: '/apps/:name/database/:table',
377
377
  input: { after: z.string().optional() },
378
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
379
391
  })
380
392
  },
381
393
  changes: {
package/dist/react.d.ts CHANGED
@@ -24,6 +24,8 @@ 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 { FilePreview } from './components/FilePreview.tsx';
27
29
  export { Gallery } from './components/Gallery.tsx';
28
30
  export { Header } from './components/Header.tsx';
29
31
  export { Image } from './components/Image.tsx';
@@ -36,6 +38,7 @@ export { Menu } from './components/Menu.tsx';
36
38
  export type { MenuEntry } from './components/MenuItems.tsx';
37
39
  export type { SidebarLink } from './components/Sidebar.tsx';
38
40
  export type { Placement } from './components/Placement.ts';
41
+ export { ProgressBar } from './components/ProgressBar.tsx';
39
42
  export { PropertyRow } from './components/PropertyRow.tsx';
40
43
  export { RadioGroup } from './components/RadioGroup.tsx';
41
44
  export type { RadioOption } from './components/RadioGroup.tsx';
@@ -53,6 +56,7 @@ export type { SpaceName } from './components/spacing.ts';
53
56
  export { Spinner } from './components/Spinner.tsx';
54
57
  export { Stack } from './components/Stack.tsx';
55
58
  export { Switch } from './components/Switch.tsx';
59
+ export { TableRow } from './components/TableRow.tsx';
56
60
  export { Text } from './components/Text.tsx';
57
61
  export { TextField } from './components/TextField.tsx';
58
62
  export { Toaster } from './components/Toaster.tsx';
package/dist/react.js CHANGED
@@ -22,6 +22,8 @@ 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 { FilePreview } from "./components/FilePreview.js";
25
27
  export { Gallery } from "./components/Gallery.js";
26
28
  export { Header } from "./components/Header.js";
27
29
  export { Image } from "./components/Image.js";
@@ -30,6 +32,7 @@ export { Link } from "./components/Link.js";
30
32
  export { ListRow } from "./components/ListRow.js";
31
33
  export { LoadingRows } from "./components/LoadingRows.js";
32
34
  export { Menu } from "./components/Menu.js";
35
+ export { ProgressBar } from "./components/ProgressBar.js";
33
36
  export { PropertyRow } from "./components/PropertyRow.js";
34
37
  export { RadioGroup } from "./components/RadioGroup.js";
35
38
  export { Row } from "./components/Row.js";
@@ -43,6 +46,7 @@ export { Skeleton } from "./components/Skeleton.js";
43
46
  export { Spinner } from "./components/Spinner.js";
44
47
  export { Stack } from "./components/Stack.js";
45
48
  export { Switch } from "./components/Switch.js";
49
+ export { TableRow } from "./components/TableRow.js";
46
50
  export { Text } from "./components/Text.js";
47
51
  export { TextField } from "./components/TextField.js";
48
52
  export { Toaster } from "./components/Toaster.js";
@@ -65,6 +65,8 @@ const PRIMITIVES = new Set([
65
65
  'Divider',
66
66
  'CopyButton',
67
67
  'EmptyState',
68
+ 'FilePicker',
69
+ 'FilePreview',
68
70
  'Form',
69
71
  'Gallery',
70
72
  'Header',
@@ -76,6 +78,7 @@ const PRIMITIVES = new Set([
76
78
  'ListRow',
77
79
  'LoadingRows',
78
80
  'Menu',
81
+ 'ProgressBar',
79
82
  'PropertyRow',
80
83
  'RadioGroup',
81
84
  'Row',
@@ -89,6 +92,7 @@ const PRIMITIVES = new Set([
89
92
  'Spinner',
90
93
  'Stack',
91
94
  'Switch',
95
+ 'TableRow',
92
96
  'Text',
93
97
  'TextField',
94
98
  'Toaster',
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, {
@@ -100,6 +100,9 @@ export function seedSql(tables, seed) {
100
100
  });
101
101
  return [...users, ...rowsSql].join('\n');
102
102
  }
103
+ function startedAt(secondsAgo) {
104
+ return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-${secondsAgo} seconds')`;
105
+ }
103
106
  export function starterSql(tables, rows) {
104
107
  return Object.values(tables)
105
108
  .flatMap((table) => {
@@ -108,7 +111,11 @@ export function starterSql(tables, rows) {
108
111
  return [];
109
112
  const keys = ['id', 'createdAt', ...Object.keys(table.columns)];
110
113
  const values = listed
111
- .map((row) => `(${keys.map((key) => sqlLiteral((row[key] ?? null))).join(', ')})`)
114
+ .map((row, index) => `(${keys
115
+ .map((key) => key === 'createdAt'
116
+ ? startedAt(listed.length - 1 - index)
117
+ : sqlLiteral((row[key] ?? null)))
118
+ .join(', ')})`)
112
119
  .join(',\n ');
113
120
  return [
114
121
  `INSERT OR IGNORE INTO ${snakeCase(table.name)} (${keys.map(snakeCase).join(', ')}) VALUES\n ${values};`