@trypattern/sdk 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -12
- package/dist/access.d.ts +0 -4
- package/dist/access.js +0 -5
- package/dist/api.d.ts +3 -1
- package/dist/api.js +5 -23
- package/dist/app.d.ts +20 -5
- package/dist/app.js +32 -2
- package/dist/blocks.d.ts +18 -0
- package/dist/blocks.js +36 -0
- package/dist/bookings.d.ts +400 -0
- package/dist/bookings.js +300 -0
- package/dist/bookingsMemory.d.ts +9 -0
- package/dist/bookingsMemory.js +79 -0
- package/dist/bookingsStore.d.ts +698 -0
- package/dist/bookingsStore.js +129 -0
- package/dist/cli/app.js +144 -59
- package/dist/cli/publishing.d.ts +3 -1
- package/dist/cli/publishing.js +45 -10
- package/dist/cli/starter.d.ts +17 -0
- package/dist/cli/starter.js +140 -0
- package/dist/components/Calendar.d.ts +20 -0
- package/dist/components/Calendar.js +195 -0
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.js +7 -29
- package/dist/components/Choice.js +27 -13
- package/dist/components/ChoiceGroup.d.ts +3 -1
- package/dist/components/ChoiceGroup.js +3 -4
- package/dist/components/Dialog.d.ts +1 -1
- package/dist/components/Dialog.js +3 -1
- package/dist/components/ListRow.js +2 -1
- package/dist/components/RadioDescribed.d.ts +4 -0
- package/dist/components/RadioDescribed.js +20 -0
- package/dist/components/RadioGroup.d.ts +4 -1
- package/dist/components/RadioGroup.js +28 -3
- package/dist/components/Text.d.ts +1 -1
- package/dist/components/Text.js +43 -2
- package/dist/components/calendarDates.d.ts +14 -0
- package/dist/components/calendarDates.js +84 -0
- package/dist/components/controlLook.js +11 -11
- package/dist/components/icons.d.ts +2 -0
- package/dist/components/icons.js +2 -0
- package/dist/components/interaction.d.ts +1 -1
- package/dist/flows/browser.d.ts +7 -1
- package/dist/flows/browser.js +25 -7
- package/dist/flows/flowBrowser.d.ts +2 -2
- package/dist/flows/flowBrowser.js +2 -2
- package/dist/flows/flows.js +8 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/media.d.ts +11 -1
- package/dist/patternAccess.d.ts +11 -2
- package/dist/patternAccess.js +8 -4
- package/dist/publishWire.d.ts +35 -2
- package/dist/publishWire.js +26 -3
- package/dist/react.d.ts +2 -0
- package/dist/react.js +1 -0
- package/dist/rules/checkAllFiles.js +16 -2
- package/dist/rules/lintPlugin.js +1 -0
- package/dist/schema.d.ts +3 -1
- package/dist/schema.js +1 -0
- package/dist/server/bookings.d.ts +12 -0
- package/dist/server/bookings.js +142 -0
- package/dist/server/createServer.d.ts +3 -2
- package/dist/server/createServer.js +13 -4
- package/dist/server/env.d.ts +8 -0
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.js +1 -0
- package/dist/server/media.d.ts +16 -3
- package/dist/server/media.js +72 -23
- package/dist/server/patternViewer.js +16 -10
- package/dist/server/tables.d.ts +2 -0
- package/dist/server/tables.js +17 -1
- package/dist/start.js +12 -0
- package/dist/table.js +9 -3
- package/dist/theme/scales.d.ts +1 -1
- package/dist/theme/scales.js +1 -0
- package/dist/theme/tokens.stylex.d.ts +18 -0
- package/dist/theme/tokens.stylex.js +19 -1
- package/dist/vite/localWorker.d.ts +2 -0
- package/dist/vite/localWorker.js +48 -3
- package/dist/vite/projectFile.d.ts +5 -0
- package/dist/vite/projectFile.js +19 -1
- package/dist/vite/vite.js +5 -1
- package/package.json +1 -1
|
@@ -2,11 +2,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Radio as BaseRadio } from '@base-ui/react/radio';
|
|
3
3
|
import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group';
|
|
4
4
|
import * as stylex from '@stylexjs/stylex';
|
|
5
|
-
import { borders, colors, iconSizes, motion, radii, shadows, space } from "../theme/tokens.stylex.js";
|
|
5
|
+
import { borders, colors, iconSizes, lineHeights, motion, radii, shadows, space } from "../theme/tokens.stylex.js";
|
|
6
6
|
import { focusRing } from "./controlLook.js";
|
|
7
7
|
import { interact } from "./interaction.js";
|
|
8
|
+
import { RadioDescribed } from "./RadioDescribed.js";
|
|
8
9
|
import { Text } from "./Text.js";
|
|
9
|
-
export function RadioGroup({ label, value, options, onChange, style }) {
|
|
10
|
+
export function RadioGroup({ label, value, options, onChange, size = 'md', style }) {
|
|
10
11
|
return (_jsx(BaseRadioGroup, { "aria-label": label, value: value, onValueChange: (next) => {
|
|
11
12
|
const chosen = options.find((option) => option.value === next);
|
|
12
13
|
if (chosen === undefined)
|
|
@@ -17,7 +18,9 @@ export function RadioGroup({ label, value, options, onChange, style }) {
|
|
|
17
18
|
label: chosen.label
|
|
18
19
|
});
|
|
19
20
|
onChange(chosen.value);
|
|
20
|
-
}, className: stylex.props(styles.group,
|
|
21
|
+
}, className: stylex.props(styles.group, size === 'lg' && styles.groupLarge, style)
|
|
22
|
+
.className, children: options.map((option) => (_jsxs("label", { ...stylex.props(styles.row, size === 'lg' && styles.rowLarge, option.description !== undefined && styles.rowDescribed, option.isDisabled === true && styles.rowDisabled), children: [_jsx("span", { ...stylex.props(styles.dotLine), children: _jsx(BaseRadio.Root, { value: option.value, disabled: option.isDisabled === true, "aria-label": option.label, className: (state) => stylex.props(styles.dot, state.checked ? styles.dotOn : styles.dotOff, option.isDisabled === true && styles.dotDisabled, focusRing.visible).className, children: _jsx(BaseRadio.Indicator, { className: stylex.props(styles.mark).className }) }) }), _jsx("span", { ...stylex.props(styles.content), children: option.content ??
|
|
23
|
+
(option.description === undefined ? (_jsx(Text, { variant: size === 'lg' ? 'lead' : 'body', tone: option.isDisabled === true ? 'tertiary' : 'primary', isTruncated: true, children: option.label })) : (_jsx(RadioDescribed, { option: option }))) }), option.trailing === undefined ? null : (_jsx("span", { ...stylex.props(styles.trailing), children: option.trailing }))] }, option.value))) }));
|
|
21
24
|
}
|
|
22
25
|
const styles = stylex.create({
|
|
23
26
|
group: {
|
|
@@ -42,9 +45,27 @@ const styles = stylex.create({
|
|
|
42
45
|
},
|
|
43
46
|
borderTopWidth: borders.hairline
|
|
44
47
|
},
|
|
48
|
+
groupLarge: {
|
|
49
|
+
borderRadius: radii.xxl
|
|
50
|
+
},
|
|
51
|
+
rowLarge: {
|
|
52
|
+
paddingBlock: space.lg,
|
|
53
|
+
paddingInline: `calc(${space.md} + ${space.xxs})`
|
|
54
|
+
},
|
|
55
|
+
rowDescribed: {
|
|
56
|
+
gap: `calc(${space.sm} + ${space.xxs})`,
|
|
57
|
+
alignItems: 'flex-start',
|
|
58
|
+
paddingBlock: `calc(${space.md} + ${space.xxs})`
|
|
59
|
+
},
|
|
45
60
|
rowDisabled: {
|
|
46
61
|
color: colors.tertiaryForeground
|
|
47
62
|
},
|
|
63
|
+
dotLine: {
|
|
64
|
+
display: 'flex',
|
|
65
|
+
flexShrink: 0,
|
|
66
|
+
alignItems: 'center',
|
|
67
|
+
height: lineHeights.bodySm
|
|
68
|
+
},
|
|
48
69
|
dot: {
|
|
49
70
|
display: 'grid',
|
|
50
71
|
flexShrink: 0,
|
|
@@ -63,6 +84,10 @@ const styles = stylex.create({
|
|
|
63
84
|
backgroundColor: colors.componentOnContainerStrong,
|
|
64
85
|
borderColor: colors.borderStrong
|
|
65
86
|
},
|
|
87
|
+
dotDisabled: {
|
|
88
|
+
backgroundColor: colors.disabled,
|
|
89
|
+
borderColor: colors.borderDefault
|
|
90
|
+
},
|
|
66
91
|
dotOn: {
|
|
67
92
|
backgroundColor: colors.invertedSolid,
|
|
68
93
|
borderColor: colors.invertedSolid
|
|
@@ -3,7 +3,7 @@ import type { Placement } from './Placement.ts';
|
|
|
3
3
|
import type { Tone } from './tones.ts';
|
|
4
4
|
type TextProps = {
|
|
5
5
|
children: ReactNode;
|
|
6
|
-
variant?: 'heading' | 'subheading' | 'title' | 'lead' | 'label' | 'body' | 'caption' | 'code';
|
|
6
|
+
variant?: 'hero' | 'display' | 'section' | 'heading' | 'subheading' | 'title' | 'lead' | 'intro' | 'label' | 'body' | 'caption' | 'code';
|
|
7
7
|
tone?: Tone;
|
|
8
8
|
align?: 'start' | 'center' | 'end';
|
|
9
9
|
as?: 'span' | 'p' | 'h1' | 'h2' | 'h3';
|
package/dist/components/Text.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as stylex from '@stylexjs/stylex';
|
|
2
2
|
import { createElement } from 'react';
|
|
3
|
-
import { breakpoints, fontSizes, fontWeights, fonts, lineHeights } from "../theme/tokens.stylex.js";
|
|
3
|
+
import { breakpoints, fontSizes, fontWeights, fonts, lineHeights, trackings } from "../theme/tokens.stylex.js";
|
|
4
4
|
import { tones } from "./tones.js";
|
|
5
5
|
export function Text({ children, variant = 'body', tone = 'inherit', align, as = 'span', isTruncated = false, isSelectable = false, style }) {
|
|
6
6
|
return createElement(as, stylex.props(as !== 'span' && styles.block, variants[variant], tones[tone], align !== undefined && alignments[align], isTruncated && styles.truncated, isSelectable && styles.selectable, style), children);
|
|
@@ -21,11 +21,47 @@ const styles = stylex.create({
|
|
|
21
21
|
}
|
|
22
22
|
});
|
|
23
23
|
const variants = stylex.create({
|
|
24
|
+
hero: {
|
|
25
|
+
fontSize: {
|
|
26
|
+
default: fontSizes.heading1,
|
|
27
|
+
[breakpoints.compact]: fontSizes.heading2
|
|
28
|
+
},
|
|
29
|
+
fontWeight: fontWeights.medium,
|
|
30
|
+
lineHeight: {
|
|
31
|
+
default: lineHeights.heading1,
|
|
32
|
+
[breakpoints.compact]: lineHeights.heading2
|
|
33
|
+
},
|
|
34
|
+
letterSpacing: trackings.hero
|
|
35
|
+
},
|
|
36
|
+
display: {
|
|
37
|
+
fontSize: {
|
|
38
|
+
default: fontSizes.heading2,
|
|
39
|
+
[breakpoints.compact]: fontSizes.heading3
|
|
40
|
+
},
|
|
41
|
+
fontWeight: fontWeights.medium,
|
|
42
|
+
lineHeight: {
|
|
43
|
+
default: lineHeights.heading2,
|
|
44
|
+
[breakpoints.compact]: lineHeights.heading3
|
|
45
|
+
},
|
|
46
|
+
letterSpacing: trackings.display
|
|
47
|
+
},
|
|
48
|
+
section: {
|
|
49
|
+
fontSize: {
|
|
50
|
+
default: fontSizes.heading3,
|
|
51
|
+
[breakpoints.compact]: fontSizes.heading5
|
|
52
|
+
},
|
|
53
|
+
fontWeight: fontWeights.medium,
|
|
54
|
+
lineHeight: {
|
|
55
|
+
default: lineHeights.heading3,
|
|
56
|
+
[breakpoints.compact]: lineHeights.heading5
|
|
57
|
+
},
|
|
58
|
+
letterSpacing: trackings.section
|
|
59
|
+
},
|
|
24
60
|
heading: {
|
|
25
61
|
fontSize: fontSizes.heading5,
|
|
26
62
|
fontWeight: fontWeights.medium,
|
|
27
63
|
lineHeight: lineHeights.heading5,
|
|
28
|
-
letterSpacing:
|
|
64
|
+
letterSpacing: trackings.heading
|
|
29
65
|
},
|
|
30
66
|
subheading: {
|
|
31
67
|
fontSize: fontSizes.heading6,
|
|
@@ -42,6 +78,11 @@ const variants = stylex.create({
|
|
|
42
78
|
fontWeight: fontWeights.normal,
|
|
43
79
|
lineHeight: lineHeights.body
|
|
44
80
|
},
|
|
81
|
+
intro: {
|
|
82
|
+
fontSize: fontSizes.bodyLg,
|
|
83
|
+
fontWeight: fontWeights.normal,
|
|
84
|
+
lineHeight: lineHeights.bodyLg
|
|
85
|
+
},
|
|
45
86
|
label: {
|
|
46
87
|
fontSize: {
|
|
47
88
|
default: fontSizes.bodySm,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function monthOf(date: string): string;
|
|
2
|
+
export declare function addDays(date: string, days: number): string;
|
|
3
|
+
export declare function addMonths(date: string, months: number): string;
|
|
4
|
+
export declare function daysIn(month: string): number;
|
|
5
|
+
export declare function weekdayIndex(date: string): number;
|
|
6
|
+
export declare function weeksOf(month: string): readonly (readonly (string | null)[])[];
|
|
7
|
+
export declare function localToday(): string;
|
|
8
|
+
export declare function monthTitle(month: string, locale: string | undefined): string;
|
|
9
|
+
export declare function dayLabel(date: string, locale: string | undefined): string;
|
|
10
|
+
export declare function dayNumber(date: string, locale: string | undefined): string;
|
|
11
|
+
export declare function weekdayNames(locale: string | undefined): readonly {
|
|
12
|
+
short: string;
|
|
13
|
+
long: string;
|
|
14
|
+
}[];
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
2
|
+
function utc(date) {
|
|
3
|
+
return new Date(`${date}T00:00:00Z`);
|
|
4
|
+
}
|
|
5
|
+
function isoDate(time) {
|
|
6
|
+
return new Date(time).toISOString().slice(0, 10);
|
|
7
|
+
}
|
|
8
|
+
export function monthOf(date) {
|
|
9
|
+
return date.slice(0, 7);
|
|
10
|
+
}
|
|
11
|
+
export function addDays(date, days) {
|
|
12
|
+
return isoDate(utc(date).getTime() + days * DAY_MS);
|
|
13
|
+
}
|
|
14
|
+
export function addMonths(date, months) {
|
|
15
|
+
const start = utc(date);
|
|
16
|
+
const year = start.getUTCFullYear();
|
|
17
|
+
const month = start.getUTCMonth() + months;
|
|
18
|
+
const last = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
|
|
19
|
+
return isoDate(Date.UTC(year, month, Math.min(start.getUTCDate(), last)));
|
|
20
|
+
}
|
|
21
|
+
export function daysIn(month) {
|
|
22
|
+
const start = utc(`${month}-01`);
|
|
23
|
+
return new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + 1, 0)).getUTCDate();
|
|
24
|
+
}
|
|
25
|
+
export function weekdayIndex(date) {
|
|
26
|
+
return (utc(date).getUTCDay() + 6) % 7;
|
|
27
|
+
}
|
|
28
|
+
export function weeksOf(month) {
|
|
29
|
+
const first = `${month}-01`;
|
|
30
|
+
const cells = [
|
|
31
|
+
...Array.from({ length: weekdayIndex(first) }, () => null),
|
|
32
|
+
...Array.from({ length: daysIn(month) }, (_, index) => addDays(first, index))
|
|
33
|
+
];
|
|
34
|
+
const weeks = [];
|
|
35
|
+
for (let index = 0; index < cells.length; index += 7) {
|
|
36
|
+
weeks.push(cells.slice(index, index + 7));
|
|
37
|
+
}
|
|
38
|
+
return weeks;
|
|
39
|
+
}
|
|
40
|
+
export function localToday() {
|
|
41
|
+
const now = new Date();
|
|
42
|
+
return [
|
|
43
|
+
String(now.getFullYear()).padStart(4, '0'),
|
|
44
|
+
String(now.getMonth() + 1).padStart(2, '0'),
|
|
45
|
+
String(now.getDate()).padStart(2, '0')
|
|
46
|
+
].join('-');
|
|
47
|
+
}
|
|
48
|
+
export function monthTitle(month, locale) {
|
|
49
|
+
return new Intl.DateTimeFormat(locale, {
|
|
50
|
+
month: 'long',
|
|
51
|
+
year: 'numeric',
|
|
52
|
+
timeZone: 'UTC'
|
|
53
|
+
}).format(utc(`${month}-01`));
|
|
54
|
+
}
|
|
55
|
+
export function dayLabel(date, locale) {
|
|
56
|
+
return new Intl.DateTimeFormat(locale, {
|
|
57
|
+
dateStyle: 'full',
|
|
58
|
+
timeZone: 'UTC'
|
|
59
|
+
}).format(utc(date));
|
|
60
|
+
}
|
|
61
|
+
export function dayNumber(date, locale) {
|
|
62
|
+
return new Intl.DateTimeFormat(locale, {
|
|
63
|
+
day: 'numeric',
|
|
64
|
+
timeZone: 'UTC'
|
|
65
|
+
}).format(utc(date));
|
|
66
|
+
}
|
|
67
|
+
export function weekdayNames(locale) {
|
|
68
|
+
const monday = '2024-01-01';
|
|
69
|
+
const short = new Intl.DateTimeFormat(locale, {
|
|
70
|
+
weekday: 'short',
|
|
71
|
+
timeZone: 'UTC'
|
|
72
|
+
});
|
|
73
|
+
const long = new Intl.DateTimeFormat(locale, {
|
|
74
|
+
weekday: 'long',
|
|
75
|
+
timeZone: 'UTC'
|
|
76
|
+
});
|
|
77
|
+
return Array.from({ length: 7 }, (_, index) => {
|
|
78
|
+
const day = utc(addDays(monday, index));
|
|
79
|
+
return {
|
|
80
|
+
short: [...short.format(day)].slice(0, 2).join(''),
|
|
81
|
+
long: long.format(day)
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
import * as stylex from '@stylexjs/stylex';
|
|
2
|
-
import { borders, colors, controls,
|
|
2
|
+
import { borders, colors, controls, motion, radii } from "../theme/tokens.stylex.js";
|
|
3
3
|
export const controlSizes = stylex.create({
|
|
4
4
|
xs: {
|
|
5
5
|
gap: controls.gapXs,
|
|
6
6
|
height: controls.heightXs,
|
|
7
7
|
paddingInline: controls.paddingXs,
|
|
8
|
-
fontSize:
|
|
9
|
-
lineHeight:
|
|
8
|
+
fontSize: controls.fontSizeXs,
|
|
9
|
+
lineHeight: controls.lineHeightXs,
|
|
10
10
|
borderRadius: radii.sm
|
|
11
11
|
},
|
|
12
12
|
sm: {
|
|
13
13
|
gap: controls.gapSm,
|
|
14
14
|
height: controls.heightSm,
|
|
15
15
|
paddingInline: controls.paddingSm,
|
|
16
|
-
fontSize:
|
|
17
|
-
lineHeight:
|
|
16
|
+
fontSize: controls.fontSizeSm,
|
|
17
|
+
lineHeight: controls.lineHeightSm,
|
|
18
18
|
borderRadius: radii.md
|
|
19
19
|
},
|
|
20
20
|
md: {
|
|
21
21
|
gap: controls.gapMd,
|
|
22
22
|
height: controls.heightMd,
|
|
23
23
|
paddingInline: controls.paddingMd,
|
|
24
|
-
fontSize:
|
|
25
|
-
lineHeight:
|
|
24
|
+
fontSize: controls.fontSizeMd,
|
|
25
|
+
lineHeight: controls.lineHeightMd,
|
|
26
26
|
borderRadius: radii.md
|
|
27
27
|
},
|
|
28
28
|
lg: {
|
|
29
29
|
gap: controls.gapLg,
|
|
30
30
|
height: controls.heightLg,
|
|
31
31
|
paddingInline: controls.paddingLg,
|
|
32
|
-
fontSize:
|
|
33
|
-
lineHeight:
|
|
32
|
+
fontSize: controls.fontSizeLg,
|
|
33
|
+
lineHeight: controls.lineHeightLg,
|
|
34
34
|
borderRadius: radii.lg
|
|
35
35
|
},
|
|
36
36
|
xl: {
|
|
37
37
|
gap: controls.gapXl,
|
|
38
38
|
height: controls.heightXl,
|
|
39
39
|
paddingInline: controls.paddingXl,
|
|
40
|
-
fontSize:
|
|
41
|
-
lineHeight:
|
|
40
|
+
fontSize: controls.fontSizeXl,
|
|
41
|
+
lineHeight: controls.lineHeightXl,
|
|
42
42
|
borderRadius: radii.lg
|
|
43
43
|
}
|
|
44
44
|
});
|
package/dist/components/icons.js
CHANGED
|
@@ -62,6 +62,8 @@ const builtInIcons = {
|
|
|
62
62
|
],
|
|
63
63
|
chevronDown: 'M7 10l5 5 5-5',
|
|
64
64
|
chevronRight: 'M10 7l5 5-5 5',
|
|
65
|
+
previous: 'M15 18l-6-6 6-6',
|
|
66
|
+
next: 'M9 18l6-6-6-6',
|
|
65
67
|
search: ['M4.5 11a6.5 6.5 0 1 0 13 0a6.5 6.5 0 1 0 -13 0', 'M16 16l4 4'],
|
|
66
68
|
panelLeft: [
|
|
67
69
|
'M6.5 4.5h11a3 3 0 0 1 3 3v9a3 3 0 0 1 -3 3h-11a3 3 0 0 1 -3 -3v-9a3 3 0 0 1 3 -3Z',
|
|
@@ -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';
|
|
3
|
+
primitive: 'AppFrame' | 'Button' | 'Card' | 'IconButton' | 'Link' | 'ListRow' | 'Select' | 'Switch' | 'Checkbox' | 'SegmentedControl' | 'Menu' | 'ContextMenu' | 'CommandMenu' | 'TextField' | 'Form' | 'Dialog' | 'RadioGroup' | 'ChoiceGroup' | 'Calendar';
|
|
4
4
|
event: 'click' | 'change' | 'select' | 'open' | 'close' | 'submit' | 'navigate';
|
|
5
5
|
label: string;
|
|
6
6
|
page: string | null;
|
package/dist/flows/browser.d.ts
CHANGED
|
@@ -31,4 +31,10 @@ export type AppPage = {
|
|
|
31
31
|
captureAt: (viewport: ViewportName) => Promise<string>;
|
|
32
32
|
close: () => Promise<void>;
|
|
33
33
|
};
|
|
34
|
-
export
|
|
34
|
+
export type MemoryRun = {
|
|
35
|
+
seed: object;
|
|
36
|
+
admins: readonly string[];
|
|
37
|
+
title: string;
|
|
38
|
+
world: unknown;
|
|
39
|
+
};
|
|
40
|
+
export declare function openApp(browser: Browser, baseUrl: string, path: string, viewport: ViewportName, signedInAs: string | null, memory?: MemoryRun): Promise<AppPage>;
|
package/dist/flows/browser.js
CHANGED
|
@@ -59,15 +59,27 @@ function watchNetwork(page) {
|
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
+
function isMissingPage(response, page) {
|
|
63
|
+
const request = response.request();
|
|
64
|
+
return (request.isNavigationRequest() &&
|
|
65
|
+
request.frame() === page.mainFrame() &&
|
|
66
|
+
response.status() >= 400 &&
|
|
67
|
+
response.status() < 500);
|
|
68
|
+
}
|
|
62
69
|
function recordProblems(page, problems) {
|
|
70
|
+
const missingPages = new Set();
|
|
63
71
|
const record = (problem) => {
|
|
64
72
|
if (!problems.includes(problem))
|
|
65
73
|
problems.push(problem);
|
|
66
74
|
};
|
|
67
75
|
page.on('console', (message) => {
|
|
68
|
-
if (message.type()
|
|
69
|
-
|
|
76
|
+
if (message.type() !== 'error')
|
|
77
|
+
return;
|
|
78
|
+
if (missingPages.has(message.location().url) &&
|
|
79
|
+
message.text().startsWith('Failed to load resource')) {
|
|
80
|
+
return;
|
|
70
81
|
}
|
|
82
|
+
record(`console error: ${message.text()}`);
|
|
71
83
|
});
|
|
72
84
|
page.on('pageerror', (error) => {
|
|
73
85
|
record(`uncaught page error: ${error.message}`);
|
|
@@ -76,12 +88,16 @@ function recordProblems(page, problems) {
|
|
|
76
88
|
record(`request failed: ${request.method()} ${request.url()} (${request.failure()?.errorText ?? 'unknown'})`);
|
|
77
89
|
});
|
|
78
90
|
page.on('response', (response) => {
|
|
91
|
+
if (isMissingPage(response, page)) {
|
|
92
|
+
missingPages.add(response.url());
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
79
95
|
if (response.status() >= 400) {
|
|
80
96
|
record(`request failed: ${response.request().method()} ${response.url()} (HTTP ${response.status()})`);
|
|
81
97
|
}
|
|
82
98
|
});
|
|
83
99
|
}
|
|
84
|
-
export async function openApp(browser, baseUrl, path, viewport, signedInAs,
|
|
100
|
+
export async function openApp(browser, baseUrl, path, viewport, signedInAs, memory) {
|
|
85
101
|
const { width, height, deviceScaleFactor } = viewports[viewport];
|
|
86
102
|
const context = await browser.newContext({
|
|
87
103
|
viewport: { width, height },
|
|
@@ -89,20 +105,22 @@ export async function openApp(browser, baseUrl, path, viewport, signedInAs, seed
|
|
|
89
105
|
reducedMotion: 'reduce',
|
|
90
106
|
colorScheme: 'light'
|
|
91
107
|
});
|
|
92
|
-
if (signedInAs !== null) {
|
|
108
|
+
if (memory === undefined && signedInAs !== null) {
|
|
93
109
|
const signIn = await context.request.post(new URL('/api/auth/dev-sign-in', baseUrl).href, { data: { email: signedInAs } });
|
|
94
110
|
if (!signIn.ok()) {
|
|
95
111
|
throw new Error(`Dev sign-in as ${signedInAs} failed (HTTP ${signIn.status()}): ${await signIn.text()}`);
|
|
96
112
|
}
|
|
97
113
|
}
|
|
98
|
-
if (
|
|
114
|
+
if (memory !== undefined) {
|
|
99
115
|
await context.addInitScript((value) => {
|
|
100
116
|
Object.assign(globalThis, {
|
|
101
117
|
patternSeed: value.seed,
|
|
102
118
|
patternAdmins: value.admins,
|
|
103
|
-
|
|
119
|
+
patternTitle: value.title,
|
|
120
|
+
patternWorld: value.world,
|
|
121
|
+
patternSignedInAs: value.signedInAs
|
|
104
122
|
});
|
|
105
|
-
}, {
|
|
123
|
+
}, { ...memory, signedInAs });
|
|
106
124
|
}
|
|
107
125
|
const page = await context.newPage();
|
|
108
126
|
const problems = [];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ViewportName } from './browser.ts';
|
|
1
|
+
import type { MemoryRun, ViewportName } from './browser.ts';
|
|
2
2
|
export type VisualFlowSettings = {
|
|
3
3
|
url: string;
|
|
4
4
|
wsEndpoint: string;
|
|
@@ -11,7 +11,7 @@ declare module 'vitest' {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
export declare function closeFlowBrowser(): Promise<void>;
|
|
14
|
-
export declare function openFlowPage(settings: VisualFlowSettings, viewport: ViewportName, startAt: string, signedInAs: string | null,
|
|
14
|
+
export declare function openFlowPage(settings: VisualFlowSettings, viewport: ViewportName, startAt: string, signedInAs: string | null, memory: MemoryRun): Promise<{
|
|
15
15
|
send: (command: unknown) => Promise<void>;
|
|
16
16
|
snapshot: () => Promise<unknown>;
|
|
17
17
|
hold: (call: string) => Promise<void>;
|
|
@@ -5,9 +5,9 @@ export async function closeFlowBrowser() {
|
|
|
5
5
|
browser = undefined;
|
|
6
6
|
await (await connected)?.close();
|
|
7
7
|
}
|
|
8
|
-
export async function openFlowPage(settings, viewport, startAt, signedInAs,
|
|
8
|
+
export async function openFlowPage(settings, viewport, startAt, signedInAs, memory) {
|
|
9
9
|
browser ??= connectChrome(settings.wsEndpoint);
|
|
10
|
-
const app = await openApp(await browser, settings.url, startAt, viewport, signedInAs,
|
|
10
|
+
const app = await openApp(await browser, settings.url, startAt, viewport, signedInAs, memory);
|
|
11
11
|
return {
|
|
12
12
|
send: app.dispatch,
|
|
13
13
|
snapshot: app.snapshot,
|
package/dist/flows/flows.js
CHANGED
|
@@ -59,6 +59,12 @@ async function runSteps(name, steps, run) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
function leadingHolds(steps) {
|
|
63
|
+
const firstOther = steps.findIndex((step) => !('hold' in step));
|
|
64
|
+
return steps
|
|
65
|
+
.slice(0, firstOther === -1 ? steps.length : firstOther)
|
|
66
|
+
.flatMap((step) => ('hold' in step ? [step.hold] : []));
|
|
67
|
+
}
|
|
62
68
|
function withTestParams(path, failing, held) {
|
|
63
69
|
const params = new URLSearchParams([
|
|
64
70
|
['memory', ''],
|
|
@@ -133,8 +139,8 @@ export function defineFlow({ App }) {
|
|
|
133
139
|
for (const viewport of visual.viewports) {
|
|
134
140
|
test(`${name} [${viewport}]`, async () => {
|
|
135
141
|
const { openFlowPage } = await import("./flowBrowser.js");
|
|
136
|
-
const held = new Set();
|
|
137
|
-
const page = await openFlowPage(visual, viewport, withTestParams(startAt, failing, held), signedInAs, seed, admins, startingWorld);
|
|
142
|
+
const held = new Set(leadingHolds(steps));
|
|
143
|
+
const page = await openFlowPage(visual, viewport, withTestParams(startAt, failing, held), signedInAs, { seed, admins, title, world: startingWorld });
|
|
138
144
|
let stepFailure;
|
|
139
145
|
try {
|
|
140
146
|
await runSteps(name, steps, {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { defineApp } from './app.ts';
|
|
2
|
+
export { Bookings, bookings } from './bookingsStore.ts';
|
|
3
|
+
export type { BookingsWorld } from './bookingsMemory.ts';
|
|
4
|
+
export type { AppBlock } from './blocks.ts';
|
|
2
5
|
export { ActionsStore, bindActions, rankActions } from './actions.ts';
|
|
3
6
|
export type { Action, ActionsBuilder } from './actions.ts';
|
|
4
7
|
export { startApp } from './start.ts';
|
|
@@ -16,7 +19,7 @@ export { HttpSessionApi, MemorySessionApi } from './auth/sessionApi.ts';
|
|
|
16
19
|
export type { EmailCodeOutcome, SessionApi, SessionCall } from './auth/sessionApi.ts';
|
|
17
20
|
export { userSchema } from './auth/user.ts';
|
|
18
21
|
export type { User } from './auth/user.ts';
|
|
19
|
-
export type { AppClass, AppDefinition, AppDependencies, AppSnapshotShape, DataRouter, ModelServicesOf, NavigateCommand, PressKeyCommand, PublicStores, PlainJsonModel, RunningApp, StoreClasses, StoreInstances, StoresOf, TableStores } from './app.ts';
|
|
22
|
+
export type { AppClass, AppDefinition, AppDependencies, AppSnapshotShape, BlockStores, CheckedBlocks, DataRouter, ModelServicesOf, NavigateCommand, PressKeyCommand, PublicStores, PlainJsonModel, RunningApp, StoreClasses, StoreInstances, StoresOf, TableStores } from './app.ts';
|
|
20
23
|
export { defineRoutes } from './routes.ts';
|
|
21
24
|
export { ServiceRefused, refused } from './services.ts';
|
|
22
25
|
export type { ServiceClient, ServiceMemory } from './services.ts';
|
package/dist/index.js
CHANGED
package/dist/media.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import type { Row } from './table.ts';
|
|
2
|
+
import type { Fields, Row, Tables } from './table.ts';
|
|
3
3
|
export declare const media: import("./table.ts").TableOf<"media", {
|
|
4
4
|
readonly columns: {
|
|
5
5
|
readonly key: z.ZodString;
|
|
@@ -20,6 +20,16 @@ export declare const media: import("./table.ts").TableOf<"media", {
|
|
|
20
20
|
}>;
|
|
21
21
|
export type Media = Row<typeof media>;
|
|
22
22
|
export type MediaImage = Pick<Media, 'key' | 'width' | 'height' | 'placeholder' | 'alt'>;
|
|
23
|
+
export type StarterPhoto = {
|
|
24
|
+
id: string;
|
|
25
|
+
photo: string;
|
|
26
|
+
alt: string;
|
|
27
|
+
};
|
|
28
|
+
export type Starter<Group extends Tables> = {
|
|
29
|
+
[Key in keyof Group as Group[Key]['owned'] extends true ? never : Key]?: Group[Key] extends typeof media ? readonly StarterPhoto[] : readonly ({
|
|
30
|
+
id: string;
|
|
31
|
+
} & Fields<Group[Key]>)[];
|
|
32
|
+
};
|
|
23
33
|
export declare const MEDIA_LIMIT_BYTES: number;
|
|
24
34
|
export declare const IMAGE_WIDTHS: readonly [320, 640, 960, 1280, 1920];
|
|
25
35
|
export declare function mediaPath(key: string): string;
|
package/dist/patternAccess.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const TABLES_READ = "tables:read";
|
|
3
|
+
export declare const MEDIA_WRITE = "media:write";
|
|
3
4
|
export declare const ASSERTION_SECONDS = 60;
|
|
4
5
|
export declare const NEXT_PAGE_HEADER = "x-pattern-next";
|
|
5
6
|
export declare const assertionKey: z.ZodObject<{
|
|
@@ -10,12 +11,20 @@ export declare const assertionKey: z.ZodObject<{
|
|
|
10
11
|
d: z.ZodString;
|
|
11
12
|
}, z.core.$strip>;
|
|
12
13
|
export type AssertionKey = z.output<typeof assertionKey>;
|
|
13
|
-
|
|
14
|
+
declare const assertionScope: z.ZodEnum<{
|
|
15
|
+
"tables:read": "tables:read";
|
|
16
|
+
"media:write": "media:write";
|
|
17
|
+
}>;
|
|
18
|
+
export type AssertionScope = z.output<typeof assertionScope>;
|
|
19
|
+
export declare function signAssertion(key: AssertionKey, { sub, aud, scope }: {
|
|
14
20
|
sub: string;
|
|
15
21
|
aud: string;
|
|
22
|
+
scope: AssertionScope;
|
|
16
23
|
}, now: number): Promise<string>;
|
|
17
|
-
export declare function
|
|
24
|
+
export declare function verifiedAssertion(token: string, { hostname, keys, now, scope }: {
|
|
18
25
|
hostname: string;
|
|
19
26
|
keys: Readonly<Record<string, string>>;
|
|
20
27
|
now: number;
|
|
28
|
+
scope: AssertionScope;
|
|
21
29
|
}): Promise<string | undefined>;
|
|
30
|
+
export {};
|
package/dist/patternAccess.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export const TABLES_READ = 'tables:read';
|
|
3
|
+
export const MEDIA_WRITE = 'media:write';
|
|
3
4
|
export const ASSERTION_SECONDS = 60;
|
|
4
5
|
export const NEXT_PAGE_HEADER = 'x-pattern-next';
|
|
5
6
|
const CLOCK_SKEW_SECONDS = 30;
|
|
@@ -11,6 +12,7 @@ export const assertionKey = z.object({
|
|
|
11
12
|
x: z.string(),
|
|
12
13
|
d: z.string()
|
|
13
14
|
});
|
|
15
|
+
const assertionScope = z.enum([TABLES_READ, MEDIA_WRITE]);
|
|
14
16
|
const assertionHeader = z.object({
|
|
15
17
|
alg: z.literal('EdDSA'),
|
|
16
18
|
typ: z.literal('JWT'),
|
|
@@ -19,7 +21,7 @@ const assertionHeader = z.object({
|
|
|
19
21
|
const assertionClaims = z.object({
|
|
20
22
|
sub: z.email(),
|
|
21
23
|
aud: z.string(),
|
|
22
|
-
scope:
|
|
24
|
+
scope: assertionScope,
|
|
23
25
|
iat: z.int(),
|
|
24
26
|
exp: z.int()
|
|
25
27
|
});
|
|
@@ -53,12 +55,12 @@ function jsonOf(text) {
|
|
|
53
55
|
function encoded(value) {
|
|
54
56
|
return base64url(encoder.encode(JSON.stringify(value)));
|
|
55
57
|
}
|
|
56
|
-
export async function
|
|
58
|
+
export async function signAssertion(key, { sub, aud, scope }, now) {
|
|
57
59
|
const privateKey = await crypto.subtle.importKey('jwk', { kty: key.kty, crv: key.crv, x: key.x, d: key.d }, ED25519, false, ['sign']);
|
|
58
60
|
const signed = `${encoded({ alg: 'EdDSA', typ: 'JWT', kid: key.kid })}.${encoded({
|
|
59
61
|
sub,
|
|
60
62
|
aud,
|
|
61
|
-
scope
|
|
63
|
+
scope,
|
|
62
64
|
iat: now,
|
|
63
65
|
exp: now + ASSERTION_SECONDS
|
|
64
66
|
})}`;
|
|
@@ -80,7 +82,7 @@ async function isSignedBy(publicKey, signed, signature) {
|
|
|
80
82
|
return false;
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
|
-
export async function
|
|
85
|
+
export async function verifiedAssertion(token, { hostname, keys, now, scope }) {
|
|
84
86
|
const [header, claims, signature, ...rest] = token.split('.');
|
|
85
87
|
if (header === undefined ||
|
|
86
88
|
claims === undefined ||
|
|
@@ -102,6 +104,8 @@ export async function verifiedTablesAssertion(token, { hostname, keys, now }) {
|
|
|
102
104
|
if (!parsed.success)
|
|
103
105
|
return undefined;
|
|
104
106
|
const { sub, aud, iat, exp } = parsed.data;
|
|
107
|
+
if (parsed.data.scope !== scope)
|
|
108
|
+
return undefined;
|
|
105
109
|
const isFresh = exp > iat &&
|
|
106
110
|
exp - iat <= ASSERTION_SECONDS &&
|
|
107
111
|
iat <= now + CLOCK_SKEW_SECONDS &&
|