@trypattern/sdk 0.1.31 → 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.
@@ -205,7 +205,7 @@ export declare const bookingServices: {
205
205
  closed: "closed";
206
206
  full: "full";
207
207
  }>;
208
- }, z.core.$strip>>>>;
208
+ }, z.core.$strip>>, false>>;
209
209
  readonly windows: NoInfer<import("./services.ts").ServiceCall<"/bookings/windows", {
210
210
  date: z.ZodString;
211
211
  }, z.ZodArray<z.ZodObject<{
@@ -214,7 +214,7 @@ export declare const bookingServices: {
214
214
  start: z.ZodString;
215
215
  end: z.ZodString;
216
216
  remaining: z.ZodNumber;
217
- }, z.core.$strip>>>>;
217
+ }, z.core.$strip>>, false>>;
218
218
  readonly request: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests", {
219
219
  offering: z.ZodString;
220
220
  date: z.ZodString;
@@ -242,7 +242,7 @@ export declare const bookingServices: {
242
242
  declined: "declined";
243
243
  cancelled: "cancelled";
244
244
  }>;
245
- }, z.core.$strip>>>;
245
+ }, z.core.$strip>, false>>;
246
246
  readonly requests: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests", {}, z.ZodArray<z.ZodObject<{
247
247
  id: z.ZodString;
248
248
  createdAt: z.ZodString;
@@ -261,7 +261,7 @@ export declare const bookingServices: {
261
261
  declined: "declined";
262
262
  cancelled: "cancelled";
263
263
  }>;
264
- }, z.core.$strip>>>>;
264
+ }, z.core.$strip>>, false>>;
265
265
  readonly accept: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests/:id/accept", {}, z.ZodObject<{
266
266
  id: z.ZodString;
267
267
  createdAt: z.ZodString;
@@ -280,7 +280,7 @@ export declare const bookingServices: {
280
280
  declined: "declined";
281
281
  cancelled: "cancelled";
282
282
  }>;
283
- }, z.core.$strip>>>;
283
+ }, z.core.$strip>, false>>;
284
284
  readonly decline: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests/:id/decline", {}, z.ZodObject<{
285
285
  id: z.ZodString;
286
286
  createdAt: z.ZodString;
@@ -299,7 +299,7 @@ export declare const bookingServices: {
299
299
  declined: "declined";
300
300
  cancelled: "cancelled";
301
301
  }>;
302
- }, z.core.$strip>>>;
302
+ }, z.core.$strip>, false>>;
303
303
  readonly rules: NoInfer<import("./services.ts").ServiceCall<"/bookings/rules", {}, z.ZodObject<{
304
304
  windows: z.ZodArray<z.ZodObject<{
305
305
  weekday: z.ZodEnum<{
@@ -323,7 +323,7 @@ export declare const bookingServices: {
323
323
  date: z.ZodString;
324
324
  reason: z.ZodString;
325
325
  }, z.core.$strip>>;
326
- }, z.core.$strip>>>;
326
+ }, z.core.$strip>, false>>;
327
327
  readonly saveRules: NoInfer<import("./services.ts").ServiceCall<"/bookings/rules", {
328
328
  windows: z.ZodArray<z.ZodObject<{
329
329
  weekday: z.ZodEnum<{
@@ -370,7 +370,7 @@ export declare const bookingServices: {
370
370
  date: z.ZodString;
371
371
  reason: z.ZodString;
372
372
  }, z.core.$strip>>;
373
- }, z.core.$strip>>>;
373
+ }, z.core.$strip>, false>>;
374
374
  };
375
375
  };
376
376
  export declare const FULL = "That window was just taken. Choose another window or another day.";
@@ -98,7 +98,7 @@ declare const Bookings_base: import("./store.ts").StoreClass<{
98
98
  closed: "closed";
99
99
  full: "full";
100
100
  }>;
101
- }, z.core.$strip>>>>;
101
+ }, z.core.$strip>>, false>>;
102
102
  readonly windows: NoInfer<import("./services.ts").ServiceCall<"/bookings/windows", {
103
103
  date: z.ZodString;
104
104
  }, z.ZodArray<z.ZodObject<{
@@ -107,7 +107,7 @@ declare const Bookings_base: import("./store.ts").StoreClass<{
107
107
  start: z.ZodString;
108
108
  end: z.ZodString;
109
109
  remaining: z.ZodNumber;
110
- }, z.core.$strip>>>>;
110
+ }, z.core.$strip>>, false>>;
111
111
  readonly request: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests", {
112
112
  offering: z.ZodString;
113
113
  date: z.ZodString;
@@ -135,7 +135,7 @@ declare const Bookings_base: import("./store.ts").StoreClass<{
135
135
  declined: "declined";
136
136
  cancelled: "cancelled";
137
137
  }>;
138
- }, z.core.$strip>>>;
138
+ }, z.core.$strip>, false>>;
139
139
  readonly requests: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests", {}, z.ZodArray<z.ZodObject<{
140
140
  id: z.ZodString;
141
141
  createdAt: z.ZodString;
@@ -154,7 +154,7 @@ declare const Bookings_base: import("./store.ts").StoreClass<{
154
154
  declined: "declined";
155
155
  cancelled: "cancelled";
156
156
  }>;
157
- }, z.core.$strip>>>>;
157
+ }, z.core.$strip>>, false>>;
158
158
  readonly accept: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests/:id/accept", {}, z.ZodObject<{
159
159
  id: z.ZodString;
160
160
  createdAt: z.ZodString;
@@ -173,7 +173,7 @@ declare const Bookings_base: import("./store.ts").StoreClass<{
173
173
  declined: "declined";
174
174
  cancelled: "cancelled";
175
175
  }>;
176
- }, z.core.$strip>>>;
176
+ }, z.core.$strip>, false>>;
177
177
  readonly decline: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests/:id/decline", {}, z.ZodObject<{
178
178
  id: z.ZodString;
179
179
  createdAt: z.ZodString;
@@ -192,7 +192,7 @@ declare const Bookings_base: import("./store.ts").StoreClass<{
192
192
  declined: "declined";
193
193
  cancelled: "cancelled";
194
194
  }>;
195
- }, z.core.$strip>>>;
195
+ }, z.core.$strip>, false>>;
196
196
  readonly rules: NoInfer<import("./services.ts").ServiceCall<"/bookings/rules", {}, z.ZodObject<{
197
197
  windows: z.ZodArray<z.ZodObject<{
198
198
  weekday: z.ZodEnum<{
@@ -216,7 +216,7 @@ declare const Bookings_base: import("./store.ts").StoreClass<{
216
216
  date: z.ZodString;
217
217
  reason: z.ZodString;
218
218
  }, z.core.$strip>>;
219
- }, z.core.$strip>>>;
219
+ }, z.core.$strip>, false>>;
220
220
  readonly saveRules: NoInfer<import("./services.ts").ServiceCall<"/bookings/rules", {
221
221
  windows: z.ZodArray<z.ZodObject<{
222
222
  weekday: z.ZodEnum<{
@@ -263,7 +263,7 @@ declare const Bookings_base: import("./store.ts").StoreClass<{
263
263
  date: z.ZodString;
264
264
  reason: z.ZodString;
265
265
  }, z.core.$strip>>;
266
- }, z.core.$strip>>>;
266
+ }, z.core.$strip>, false>>;
267
267
  };
268
268
  }>>;
269
269
  export declare class Bookings extends Bookings_base {
@@ -346,7 +346,7 @@ export declare const bookings: {
346
346
  closed: "closed";
347
347
  full: "full";
348
348
  }>;
349
- }, z.core.$strip>>>>;
349
+ }, z.core.$strip>>, false>>;
350
350
  readonly windows: NoInfer<import("./services.ts").ServiceCall<"/bookings/windows", {
351
351
  date: z.ZodString;
352
352
  }, z.ZodArray<z.ZodObject<{
@@ -355,7 +355,7 @@ export declare const bookings: {
355
355
  start: z.ZodString;
356
356
  end: z.ZodString;
357
357
  remaining: z.ZodNumber;
358
- }, z.core.$strip>>>>;
358
+ }, z.core.$strip>>, false>>;
359
359
  readonly request: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests", {
360
360
  offering: z.ZodString;
361
361
  date: z.ZodString;
@@ -383,7 +383,7 @@ export declare const bookings: {
383
383
  declined: "declined";
384
384
  cancelled: "cancelled";
385
385
  }>;
386
- }, z.core.$strip>>>;
386
+ }, z.core.$strip>, false>>;
387
387
  readonly requests: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests", {}, z.ZodArray<z.ZodObject<{
388
388
  id: z.ZodString;
389
389
  createdAt: z.ZodString;
@@ -402,7 +402,7 @@ export declare const bookings: {
402
402
  declined: "declined";
403
403
  cancelled: "cancelled";
404
404
  }>;
405
- }, z.core.$strip>>>>;
405
+ }, z.core.$strip>>, false>>;
406
406
  readonly accept: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests/:id/accept", {}, z.ZodObject<{
407
407
  id: z.ZodString;
408
408
  createdAt: z.ZodString;
@@ -421,7 +421,7 @@ export declare const bookings: {
421
421
  declined: "declined";
422
422
  cancelled: "cancelled";
423
423
  }>;
424
- }, z.core.$strip>>>;
424
+ }, z.core.$strip>, false>>;
425
425
  readonly decline: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests/:id/decline", {}, z.ZodObject<{
426
426
  id: z.ZodString;
427
427
  createdAt: z.ZodString;
@@ -440,7 +440,7 @@ export declare const bookings: {
440
440
  declined: "declined";
441
441
  cancelled: "cancelled";
442
442
  }>;
443
- }, z.core.$strip>>>;
443
+ }, z.core.$strip>, false>>;
444
444
  readonly rules: NoInfer<import("./services.ts").ServiceCall<"/bookings/rules", {}, z.ZodObject<{
445
445
  windows: z.ZodArray<z.ZodObject<{
446
446
  weekday: z.ZodEnum<{
@@ -464,7 +464,7 @@ export declare const bookings: {
464
464
  date: z.ZodString;
465
465
  reason: z.ZodString;
466
466
  }, z.core.$strip>>;
467
- }, z.core.$strip>>>;
467
+ }, z.core.$strip>, false>>;
468
468
  readonly saveRules: NoInfer<import("./services.ts").ServiceCall<"/bookings/rules", {
469
469
  windows: z.ZodArray<z.ZodObject<{
470
470
  weekday: z.ZodEnum<{
@@ -511,7 +511,7 @@ export declare const bookings: {
511
511
  date: z.ZodString;
512
512
  reason: z.ZodString;
513
513
  }, z.core.$strip>>;
514
- }, z.core.$strip>>>;
514
+ }, z.core.$strip>, false>>;
515
515
  };
516
516
  };
517
517
  readonly memory: import("./services.ts").ServiceMemory<{
@@ -525,7 +525,7 @@ export declare const bookings: {
525
525
  closed: "closed";
526
526
  full: "full";
527
527
  }>;
528
- }, z.core.$strip>>>>;
528
+ }, z.core.$strip>>, false>>;
529
529
  readonly windows: NoInfer<import("./services.ts").ServiceCall<"/bookings/windows", {
530
530
  date: z.ZodString;
531
531
  }, z.ZodArray<z.ZodObject<{
@@ -534,7 +534,7 @@ export declare const bookings: {
534
534
  start: z.ZodString;
535
535
  end: z.ZodString;
536
536
  remaining: z.ZodNumber;
537
- }, z.core.$strip>>>>;
537
+ }, z.core.$strip>>, false>>;
538
538
  readonly request: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests", {
539
539
  offering: z.ZodString;
540
540
  date: z.ZodString;
@@ -562,7 +562,7 @@ export declare const bookings: {
562
562
  declined: "declined";
563
563
  cancelled: "cancelled";
564
564
  }>;
565
- }, z.core.$strip>>>;
565
+ }, z.core.$strip>, false>>;
566
566
  readonly requests: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests", {}, z.ZodArray<z.ZodObject<{
567
567
  id: z.ZodString;
568
568
  createdAt: z.ZodString;
@@ -581,7 +581,7 @@ export declare const bookings: {
581
581
  declined: "declined";
582
582
  cancelled: "cancelled";
583
583
  }>;
584
- }, z.core.$strip>>>>;
584
+ }, z.core.$strip>>, false>>;
585
585
  readonly accept: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests/:id/accept", {}, z.ZodObject<{
586
586
  id: z.ZodString;
587
587
  createdAt: z.ZodString;
@@ -600,7 +600,7 @@ export declare const bookings: {
600
600
  declined: "declined";
601
601
  cancelled: "cancelled";
602
602
  }>;
603
- }, z.core.$strip>>>;
603
+ }, z.core.$strip>, false>>;
604
604
  readonly decline: NoInfer<import("./services.ts").ServiceCall<"/bookings/requests/:id/decline", {}, z.ZodObject<{
605
605
  id: z.ZodString;
606
606
  createdAt: z.ZodString;
@@ -619,7 +619,7 @@ export declare const bookings: {
619
619
  declined: "declined";
620
620
  cancelled: "cancelled";
621
621
  }>;
622
- }, z.core.$strip>>>;
622
+ }, z.core.$strip>, false>>;
623
623
  readonly rules: NoInfer<import("./services.ts").ServiceCall<"/bookings/rules", {}, z.ZodObject<{
624
624
  windows: z.ZodArray<z.ZodObject<{
625
625
  weekday: z.ZodEnum<{
@@ -643,7 +643,7 @@ export declare const bookings: {
643
643
  date: z.ZodString;
644
644
  reason: z.ZodString;
645
645
  }, z.core.$strip>>;
646
- }, z.core.$strip>>>;
646
+ }, z.core.$strip>, false>>;
647
647
  readonly saveRules: NoInfer<import("./services.ts").ServiceCall<"/bookings/rules", {
648
648
  windows: z.ZodArray<z.ZodObject<{
649
649
  weekday: z.ZodEnum<{
@@ -690,7 +690,7 @@ export declare const bookings: {
690
690
  date: z.ZodString;
691
691
  reason: z.ZodString;
692
692
  }, z.core.$strip>>;
693
- }, z.core.$strip>>>;
693
+ }, z.core.$strip>, false>>;
694
694
  };
695
695
  }, unknown>;
696
696
  readonly Store: typeof Bookings;
@@ -0,0 +1,13 @@
1
+ import type { PickedFile } from '../files.ts';
2
+ import type { Placement } from './Placement.ts';
3
+ type FilePickerProps = {
4
+ title: string;
5
+ hint?: string;
6
+ buttonLabel: string;
7
+ accept: readonly string[];
8
+ onPick: (file: PickedFile) => void;
9
+ disabled?: boolean;
10
+ style?: Placement;
11
+ };
12
+ export declare function FilePicker({ title, hint, buttonLabel, accept, onPick, disabled, style }: FilePickerProps): import("react").JSX.Element;
13
+ export {};
@@ -0,0 +1,81 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as stylex from '@stylexjs/stylex';
3
+ import { useRef, useState } from 'react';
4
+ import { rememberFile } from "../files.js";
5
+ import { borders, colors, motion, radii, shadows, space } from "../theme/tokens.stylex.js";
6
+ import { Button } from "./Button.js";
7
+ import { Icon } from "./Icon.js";
8
+ import { interact } from "./interaction.js";
9
+ import { Text } from "./Text.js";
10
+ export function FilePicker({ title, hint, buttonLabel, accept, onPick, disabled = false, style }) {
11
+ const input = useRef(null);
12
+ const [isOver, setIsOver] = useState(false);
13
+ const pick = (file, event) => {
14
+ if (file === undefined || disabled)
15
+ return;
16
+ interact({
17
+ primitive: 'FilePicker',
18
+ event: event === 'drop' ? 'change' : 'select',
19
+ label: title
20
+ });
21
+ onPick(rememberFile(file));
22
+ };
23
+ const over = (event) => {
24
+ if (disabled || !event.dataTransfer.types.includes('Files'))
25
+ return;
26
+ event.preventDefault();
27
+ event.dataTransfer.dropEffect = 'copy';
28
+ setIsOver(true);
29
+ };
30
+ return (_jsxs("div", { onDragEnter: over, onDragOver: over, onDragLeave: () => setIsOver(false), onDrop: (event) => {
31
+ event.preventDefault();
32
+ setIsOver(false);
33
+ pick(event.dataTransfer.files[0], 'drop');
34
+ }, ...stylex.props(styles.zone, isOver && styles.over, style), children: [_jsx("span", { ...stylex.props(styles.mark), children: _jsx(Icon, { name: "image", size: "xl", tone: "secondary" }) }), _jsxs("span", { ...stylex.props(styles.words), children: [_jsx(Text, { variant: "label", tone: "primary", align: "center", children: title }), hint === undefined ? null : (_jsx(Text, { variant: "caption", tone: "tertiary", align: "center", children: hint }))] }), _jsx(Button, { variant: "secondary", size: "md", disabled: disabled, onClick: () => input.current?.click(), children: buttonLabel }), _jsx("input", { ref: input, type: "file", accept: accept.join(','), tabIndex: -1, "aria-hidden": true, disabled: disabled, onChange: (event) => {
35
+ pick(event.target.files?.[0], 'choose');
36
+ event.target.value = '';
37
+ }, ...stylex.props(styles.input) })] }));
38
+ }
39
+ const styles = stylex.create({
40
+ zone: {
41
+ display: 'flex',
42
+ flexDirection: 'column',
43
+ gap: `calc(${space.sm} + ${space.xxs})`,
44
+ alignItems: 'center',
45
+ justifyContent: 'center',
46
+ minHeight: `calc(${space.xxxl} * 3 + ${space.xl} + ${space.sm} + ${space.xs})`,
47
+ padding: `calc(${space.lg} + ${space.xs})`,
48
+ backgroundColor: colors.containerRecessed,
49
+ borderColor: colors.borderStrong,
50
+ borderStyle: 'dashed',
51
+ borderWidth: `calc(${borders.thin} + ${borders.hairline})`,
52
+ borderRadius: radii.lg,
53
+ transitionTimingFunction: motion.easeOut,
54
+ transitionDuration: motion.fast,
55
+ transitionProperty: 'background-color, border-color'
56
+ },
57
+ over: {
58
+ backgroundColor: colors.brandSubtle,
59
+ borderColor: colors.brandBorderFocus
60
+ },
61
+ mark: {
62
+ display: 'flex',
63
+ flexShrink: 0,
64
+ alignItems: 'center',
65
+ justifyContent: 'center',
66
+ width: `calc(${space.xxl} + ${space.xs})`,
67
+ height: `calc(${space.xxl} + ${space.xs})`,
68
+ backgroundColor: colors.componentOnContainerStrong,
69
+ borderRadius: radii.full,
70
+ boxShadow: shadows.default
71
+ },
72
+ words: {
73
+ display: 'flex',
74
+ flexDirection: 'column',
75
+ gap: space.xxs,
76
+ alignItems: 'center'
77
+ },
78
+ input: {
79
+ display: 'none'
80
+ }
81
+ });
@@ -0,0 +1,8 @@
1
+ import type { Placement } from './Placement.ts';
2
+ type ProgressBarProps = {
3
+ label: string;
4
+ value: number;
5
+ style?: Placement;
6
+ };
7
+ export declare function ProgressBar({ label, value, style }: ProgressBarProps): import("react").JSX.Element;
8
+ export {};
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as stylex from '@stylexjs/stylex';
3
+ import { colors, motion, radii, space } from "../theme/tokens.stylex.js";
4
+ export function ProgressBar({ label, value, style }) {
5
+ const percent = Math.round(Math.min(1, Math.max(0, value)) * 100);
6
+ return (_jsx("div", { role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": percent, ...stylex.props(styles.track, style), children: _jsx("div", { ...stylex.props(styles.fill, fills.width(`${percent}%`)) }) }));
7
+ }
8
+ const styles = stylex.create({
9
+ track: {
10
+ display: 'flex',
11
+ flexShrink: 0,
12
+ width: '100%',
13
+ height: space.xs,
14
+ overflow: 'clip',
15
+ backgroundColor: colors.component,
16
+ borderRadius: radii.full
17
+ },
18
+ fill: {
19
+ height: '100%',
20
+ backgroundColor: colors.brandSolid,
21
+ borderRadius: radii.full,
22
+ transitionTimingFunction: motion.easeOut,
23
+ transitionDuration: motion.fast,
24
+ transitionProperty: 'width'
25
+ }
26
+ });
27
+ const fills = stylex.create({
28
+ width: (width) => ({ width })
29
+ });
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react';
2
+ type TableRowProps = {
3
+ label: string;
4
+ isCurrent?: boolean;
5
+ onClick: () => void;
6
+ children: ReactNode;
7
+ };
8
+ export declare function TableRow({ label, isCurrent, onClick, children }: TableRowProps): import("react").JSX.Element;
9
+ export {};
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Button as BaseButton } from '@base-ui/react/button';
3
+ import * as stylex from '@stylexjs/stylex';
4
+ import { borders, colors, space } from "../theme/tokens.stylex.js";
5
+ import { focusRing } from "./controlLook.js";
6
+ import { interact } from "./interaction.js";
7
+ export function TableRow({ label, isCurrent = false, onClick, children }) {
8
+ return (_jsx(BaseButton, { "aria-label": label, "aria-pressed": isCurrent, onClick: () => {
9
+ interact({ primitive: 'TableRow', event: 'click', label });
10
+ onClick();
11
+ }, className: stylex.props(styles.row, isCurrent && styles.current, focusRing.visible, focusRing.inside).className, children: children }));
12
+ }
13
+ const styles = stylex.create({
14
+ row: {
15
+ display: 'flex',
16
+ alignItems: 'center',
17
+ width: '100%',
18
+ height: `calc(${space.xxl} + ${space.xs})`,
19
+ paddingInline: space.xs,
20
+ color: colors.primaryForeground,
21
+ textAlign: 'start',
22
+ backgroundColor: {
23
+ default: 'transparent',
24
+ ':hover': colors.transparentHover
25
+ },
26
+ borderBottomColor: {
27
+ default: colors.borderDefault,
28
+ ':last-child': colors.transparent
29
+ },
30
+ borderBottomStyle: 'solid',
31
+ borderBottomWidth: borders.thin
32
+ },
33
+ current: {
34
+ backgroundColor: colors.transparentHover
35
+ }
36
+ });
@@ -28,6 +28,7 @@ declare const builtInIcons: {
28
28
  chevronUpDown: string;
29
29
  check: string;
30
30
  plus: string;
31
+ image: string[];
31
32
  trash: string[];
32
33
  checkCircle: string[];
33
34
  alertCircle: string[];
@@ -46,6 +46,11 @@ const builtInIcons = {
46
46
  chevronUpDown: 'M8 9.5 12 5.5l4 4M8 14.5l4 4 4-4',
47
47
  check: 'M5 12.5 9.5 17 19 7.5',
48
48
  plus: 'M12 5v14M5 12h14',
49
+ image: [
50
+ 'M6.25 4.75h11.5a2.5 2.5 0 0 1 2.5 2.5v9.5a2.5 2.5 0 0 1-2.5 2.5H6.25a2.5 2.5 0 0 1-2.5-2.5v-9.5a2.5 2.5 0 0 1 2.5-2.5z',
51
+ 'M7.25 10a1.75 1.75 0 1 0 3.5 0a1.75 1.75 0 1 0 -3.5 0',
52
+ 'M4.5 17.5l4.5-4.25 3 2.75 3.5-3.75 4 4'
53
+ ],
49
54
  trash: [
50
55
  'M4.75 6.75h14.5',
51
56
  'M9.75 6.75v-1a2 2 0 0 1 2-2h.5a2 2 0 0 1 2 2v1',
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
2
  export type Interaction = {
3
- primitive: 'AppFrame' | 'Button' | 'Card' | 'IconButton' | 'Link' | 'ListRow' | 'Select' | 'Switch' | 'Checkbox' | 'SegmentedControl' | 'Menu' | 'ContextMenu' | 'CommandMenu' | 'TextField' | 'Form' | 'Dialog' | 'RadioGroup' | 'ChoiceGroup' | 'Calendar' | 'Gallery';
3
+ primitive: 'AppFrame' | 'Button' | 'Card' | 'IconButton' | 'Link' | 'ListRow' | 'Select' | 'Switch' | 'Checkbox' | 'SegmentedControl' | 'Menu' | 'ContextMenu' | 'CommandMenu' | 'TextField' | 'Form' | 'Dialog' | 'RadioGroup' | 'ChoiceGroup' | 'Calendar' | 'Gallery' | 'FilePicker' | 'TableRow';
4
4
  event: 'click' | 'change' | 'select' | 'open' | 'close' | 'submit' | 'navigate';
5
5
  label: string;
6
6
  page: string | null;
@@ -0,0 +1,11 @@
1
+ import { z } from 'zod';
2
+ export declare const pickedFile: z.ZodObject<{
3
+ id: z.ZodString;
4
+ name: z.ZodString;
5
+ type: z.ZodString;
6
+ size: z.ZodNumber;
7
+ }, z.core.$strip>;
8
+ export type PickedFile = z.output<typeof pickedFile>;
9
+ export declare function rememberFile(file: File): PickedFile;
10
+ export declare function pickedFileOf(id: string): File | undefined;
11
+ export declare function forgetFile(id: string): void;
package/dist/files.js ADDED
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+ export const pickedFile = z.object({
3
+ id: z.string(),
4
+ name: z.string(),
5
+ type: z.string(),
6
+ size: z.number()
7
+ });
8
+ const picked = new Map();
9
+ export function rememberFile(file) {
10
+ const id = crypto.randomUUID();
11
+ picked.set(id, file);
12
+ return { id, name: file.name, type: file.type, size: file.size };
13
+ }
14
+ export function pickedFileOf(id) {
15
+ return picked.get(id);
16
+ }
17
+ export function forgetFile(id) {
18
+ picked.delete(id);
19
+ }
@@ -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";
@@ -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,7 @@ 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';
27
28
  export { Gallery } from './components/Gallery.tsx';
28
29
  export { Header } from './components/Header.tsx';
29
30
  export { Image } from './components/Image.tsx';
@@ -36,6 +37,7 @@ export { Menu } from './components/Menu.tsx';
36
37
  export type { MenuEntry } from './components/MenuItems.tsx';
37
38
  export type { SidebarLink } from './components/Sidebar.tsx';
38
39
  export type { Placement } from './components/Placement.ts';
40
+ export { ProgressBar } from './components/ProgressBar.tsx';
39
41
  export { PropertyRow } from './components/PropertyRow.tsx';
40
42
  export { RadioGroup } from './components/RadioGroup.tsx';
41
43
  export type { RadioOption } from './components/RadioGroup.tsx';
@@ -53,6 +55,7 @@ export type { SpaceName } from './components/spacing.ts';
53
55
  export { Spinner } from './components/Spinner.tsx';
54
56
  export { Stack } from './components/Stack.tsx';
55
57
  export { Switch } from './components/Switch.tsx';
58
+ export { TableRow } from './components/TableRow.tsx';
56
59
  export { Text } from './components/Text.tsx';
57
60
  export { TextField } from './components/TextField.tsx';
58
61
  export { Toaster } from './components/Toaster.tsx';
package/dist/react.js CHANGED
@@ -22,6 +22,7 @@ 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";
25
26
  export { Gallery } from "./components/Gallery.js";
26
27
  export { Header } from "./components/Header.js";
27
28
  export { Image } from "./components/Image.js";
@@ -30,6 +31,7 @@ export { Link } from "./components/Link.js";
30
31
  export { ListRow } from "./components/ListRow.js";
31
32
  export { LoadingRows } from "./components/LoadingRows.js";
32
33
  export { Menu } from "./components/Menu.js";
34
+ export { ProgressBar } from "./components/ProgressBar.js";
33
35
  export { PropertyRow } from "./components/PropertyRow.js";
34
36
  export { RadioGroup } from "./components/RadioGroup.js";
35
37
  export { Row } from "./components/Row.js";
@@ -43,6 +45,7 @@ export { Skeleton } from "./components/Skeleton.js";
43
45
  export { Spinner } from "./components/Spinner.js";
44
46
  export { Stack } from "./components/Stack.js";
45
47
  export { Switch } from "./components/Switch.js";
48
+ export { TableRow } from "./components/TableRow.js";
46
49
  export { Text } from "./components/Text.js";
47
50
  export { TextField } from "./components/TextField.js";
48
51
  export { Toaster } from "./components/Toaster.js";
@@ -65,6 +65,7 @@ const PRIMITIVES = new Set([
65
65
  'Divider',
66
66
  'CopyButton',
67
67
  'EmptyState',
68
+ 'FilePicker',
68
69
  'Form',
69
70
  'Gallery',
70
71
  'Header',
@@ -76,6 +77,7 @@ const PRIMITIVES = new Set([
76
77
  'ListRow',
77
78
  'LoadingRows',
78
79
  'Menu',
80
+ 'ProgressBar',
79
81
  'PropertyRow',
80
82
  'RadioGroup',
81
83
  'Row',
@@ -89,6 +91,7 @@ const PRIMITIVES = new Set([
89
91
  'Spinner',
90
92
  'Stack',
91
93
  'Switch',
94
+ 'TableRow',
92
95
  'Text',
93
96
  'TextField',
94
97
  '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, {
@@ -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
  }
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import type { PickedFile } from './files.ts';
2
3
  import type { SameOnTheWire } from './wire.ts';
3
4
  type Shape = z.ZodRawShape;
4
5
  type NoShape = Record<never, never>;
@@ -6,11 +7,16 @@ type ParamNames<Path extends string> = Path extends `${string}:${infer Param}/${
6
7
  export type ParamsOf<Path extends string> = {
7
8
  readonly [Name in ParamNames<Path>]: string;
8
9
  };
9
- export type ServiceCall<Path extends string = string, S extends Shape = Shape, Reply extends z.ZodType = z.ZodType> = {
10
+ export type ServiceCall<Path extends string = string, S extends Shape = Shape, Reply extends z.ZodType = z.ZodType, Upload extends boolean = boolean> = {
10
11
  readonly method: 'get' | 'post';
11
12
  readonly path: Path;
12
13
  readonly input: z.ZodObject<S>;
13
14
  readonly reply: Reply;
15
+ readonly isUpload: Upload;
16
+ };
17
+ export type UploadOptions = {
18
+ readonly onProgress?: (sent: number, total: number) => void;
19
+ readonly signal?: AbortSignal;
14
20
  };
15
21
  type StaticTypeError<Message extends string> = Message;
16
22
  type NotText = StaticTypeError<'A get call sends its input in the address, where everything is text: use z.string() or z.enum() here, or make it a post call'>;
@@ -26,12 +32,30 @@ type CallOptions<Path extends string, S extends Shape, Reply extends z.ZodType>
26
32
  input?: SameOnTheWire<S>;
27
33
  reply: Reply;
28
34
  };
29
- export declare function call<Path extends string, Reply extends z.ZodType, S extends Shape = NoShape>(options: CallOptions<Path, S, Reply>): NoInfer<ServiceCall<Path, S, Reply>>;
35
+ type UploadCallOptions<Path extends string, S extends Shape, Reply extends z.ZodType> = {
36
+ upload: Path;
37
+ input?: TextOnly<S> & SameOnTheWire<S>;
38
+ reply: Reply;
39
+ };
40
+ export declare function call<Path extends string, Reply extends z.ZodType, S extends Shape = NoShape>(options: UploadCallOptions<Path, S, Reply>): NoInfer<ServiceCall<Path, S, Reply, true>>;
41
+ export declare function call<Path extends string, Reply extends z.ZodType, S extends Shape = NoShape>(options: CallOptions<Path, S, Reply>): NoInfer<ServiceCall<Path, S, Reply, false>>;
30
42
  export type Services = Readonly<Record<string, Readonly<Record<string, ServiceCall>>>>;
31
43
  export declare function defineServices<const Group extends Services>(services: Group): Group;
32
- export type CallInput<Call> = Call extends ServiceCall<infer Path, infer S, z.ZodType> ? ParamsOf<Path> & (keyof S extends never ? NoShape : z.output<z.ZodObject<S>>) : never;
44
+ type FieldsOf<Call> = Call extends ServiceCall<infer Path, infer S, z.ZodType> ? ParamsOf<Path> & (keyof S extends never ? NoShape : z.output<z.ZodObject<S>>) : never;
45
+ export type CallInput<Call> = FieldsOf<Call> & (Call extends {
46
+ readonly isUpload: true;
47
+ } ? {
48
+ file: PickedFile;
49
+ } : NoShape);
50
+ export type HandlerInput<Call> = FieldsOf<Call> & (Call extends {
51
+ readonly isUpload: true;
52
+ } ? {
53
+ file: File;
54
+ } : NoShape);
33
55
  export type CallReply<Call> = Call extends ServiceCall<string, Shape, infer Reply> ? z.output<Reply> : never;
34
- type ClientCall<Call> = keyof CallInput<Call> extends never ? (input?: CallInput<Call>) => Promise<CallReply<Call>> : (input: CallInput<Call>) => Promise<CallReply<Call>>;
56
+ type ClientCall<Call> = Call extends {
57
+ readonly isUpload: true;
58
+ } ? (input: CallInput<Call>, options?: UploadOptions) => Promise<CallReply<Call>> : keyof CallInput<Call> extends never ? (input?: CallInput<Call>) => Promise<CallReply<Call>> : (input: CallInput<Call>) => Promise<CallReply<Call>>;
35
59
  export type ServiceClient<Group extends Services> = {
36
60
  readonly [Name in keyof Group]: {
37
61
  readonly [Call in keyof Group[Name]]: ClientCall<Group[Name][Call]>;
package/dist/services.js CHANGED
@@ -1,12 +1,16 @@
1
1
  import { z } from 'zod';
2
2
  export function call(options) {
3
- const method = 'get' in options ? 'get' : 'post';
4
- const path = 'get' in options ? options.get : options.post;
3
+ const path = 'get' in options
4
+ ? options.get
5
+ : 'post' in options
6
+ ? options.post
7
+ : options.upload;
5
8
  return {
6
- method,
9
+ method: 'get' in options ? 'get' : 'post',
7
10
  path,
8
11
  input: z.object((options.input ?? {})),
9
- reply: options.reply
12
+ reply: options.reply,
13
+ isUpload: 'upload' in options
10
14
  };
11
15
  }
12
16
  export function defineServices(services) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trypattern/sdk",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",