@trypattern/sdk 0.1.32 → 0.1.34

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.
Files changed (57) hide show
  1. package/dist/app.d.ts +6 -2
  2. package/dist/app.js +6 -1
  3. package/dist/blocks.js +11 -3
  4. package/dist/bookings.d.ts +78 -0
  5. package/dist/bookings.js +7 -1
  6. package/dist/bookingsStore.d.ts +78 -0
  7. package/dist/cli/app.js +17 -2
  8. package/dist/cli/requests.d.ts +5 -0
  9. package/dist/cli/requests.js +115 -0
  10. package/dist/components/AppFrame.js +12 -2
  11. package/dist/components/FilePreview.d.ts +10 -0
  12. package/dist/components/FilePreview.js +36 -0
  13. package/dist/components/Image.d.ts +2 -1
  14. package/dist/components/Image.js +5 -2
  15. package/dist/components/Menu.js +2 -2
  16. package/dist/components/MenuItems.d.ts +1 -0
  17. package/dist/components/MenuItems.js +13 -2
  18. package/dist/components/SegmentedControl.d.ts +1 -0
  19. package/dist/components/SegmentedControl.js +10 -3
  20. package/dist/components/Sidebar.d.ts +1 -0
  21. package/dist/components/Sidebar.js +18 -2
  22. package/dist/components/TableRow.d.ts +2 -1
  23. package/dist/components/TableRow.js +5 -2
  24. package/dist/components/icons.js +1 -2
  25. package/dist/flows/browser.js +7 -0
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +1 -0
  28. package/dist/inquiriesStore.d.ts +134 -0
  29. package/dist/inquiriesStore.js +64 -0
  30. package/dist/media.d.ts +2 -1
  31. package/dist/media.js +4 -4
  32. package/dist/patternAccess.d.ts +8 -2
  33. package/dist/patternAccess.js +17 -5
  34. package/dist/publishWire.d.ts +866 -0
  35. package/dist/publishWire.js +78 -0
  36. package/dist/react.d.ts +1 -0
  37. package/dist/react.js +1 -0
  38. package/dist/requests.d.ts +361 -0
  39. package/dist/requests.js +180 -0
  40. package/dist/rules/lintPlugin.js +1 -0
  41. package/dist/schema.d.ts +2 -0
  42. package/dist/schema.js +1 -0
  43. package/dist/server/authService.d.ts +1 -0
  44. package/dist/server/bookings.d.ts +2 -9
  45. package/dist/server/bookings.js +149 -31
  46. package/dist/server/createServer.js +4 -0
  47. package/dist/server/env.d.ts +3 -0
  48. package/dist/server/index.d.ts +1 -0
  49. package/dist/server/index.js +1 -0
  50. package/dist/server/inquiries.d.ts +9 -0
  51. package/dist/server/inquiries.js +150 -0
  52. package/dist/server/patternViewer.js +7 -1
  53. package/dist/server/requests.d.ts +76 -0
  54. package/dist/server/requests.js +328 -0
  55. package/dist/server/services.js +9 -1
  56. package/dist/server/tables.js +8 -1
  57. package/package.json +1 -1
@@ -2,15 +2,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Radio } from '@base-ui/react/radio';
3
3
  import { RadioGroup } from '@base-ui/react/radio-group';
4
4
  import * as stylex from '@stylexjs/stylex';
5
- import { borders, colors, fontWeights, motion, radii } from "../theme/tokens.stylex.js";
5
+ import { borders, colors, fontSizes, fontWeights, lineHeights, motion, radii, space } from "../theme/tokens.stylex.js";
6
6
  import { interact } from "./interaction.js";
7
- import { controlSizes, focusRing } from "./controlLook.js";
7
+ import { buttonCorners, controlSizes, focusRing } from "./controlLook.js";
8
8
  export function SegmentedControl({ label, value, options, size = 'md', disabled = false, onChange, style }) {
9
9
  const index = Math.max(0, options.findIndex((option) => option.value === value));
10
10
  return (_jsxs(RadioGroup, { "aria-label": label, value: value, disabled: disabled, onValueChange: (next) => {
11
11
  interact({ primitive: 'SegmentedControl', event: 'change', label });
12
12
  onChange(next);
13
- }, className: stylex.props(styles.group, style).className, children: [_jsx("span", { ...stylex.props(styles.indicator(index, options.length)) }), options.map((option) => (_jsx(Radio.Root, { value: option.value, className: (state) => stylex.props(styles.option, controlSizes[size], !disabled && styles.hoverable, state.checked && styles.optionChecked, disabled && styles.disabled, focusRing.visible).className, children: option.label }, option.value)))] }));
13
+ }, className: stylex.props(styles.group, style).className, children: [_jsx("span", { ...stylex.props(styles.indicator(index, options.length)) }), options.map((option) => (_jsxs(Radio.Root, { value: option.value, className: (state) => stylex.props(styles.option, controlSizes[size], buttonCorners[size], !disabled && styles.hoverable, state.checked && styles.optionChecked, disabled && styles.disabled, focusRing.visible).className, children: [option.label, option.count === undefined ? null : (_jsx("span", { ...stylex.props(styles.count), children: option.count }))] }, option.value)))] }));
14
14
  }
15
15
  const styles = stylex.create({
16
16
  group: {
@@ -44,6 +44,7 @@ const styles = stylex.create({
44
44
  position: 'relative',
45
45
  zIndex: 1,
46
46
  display: 'inline-flex',
47
+ gap: `calc(${space.xs} + ${space.xxs})`,
47
48
  alignItems: 'center',
48
49
  justifyContent: 'center',
49
50
  color: colors.secondaryForeground,
@@ -64,5 +65,11 @@ const styles = stylex.create({
64
65
  },
65
66
  disabled: {
66
67
  color: colors.disabledForeground
68
+ },
69
+ count: {
70
+ fontSize: fontSizes.captionLg,
71
+ fontWeight: fontWeights.normal,
72
+ lineHeight: lineHeights.caption,
73
+ color: colors.tertiaryForeground
67
74
  }
68
75
  });
@@ -6,6 +6,7 @@ export type SidebarLink = {
6
6
  to: AppPath;
7
7
  icon: IconName;
8
8
  count?: number | null;
9
+ countTone?: 'quiet' | 'brand';
9
10
  };
10
11
  type SidebarProps = {
11
12
  workspace: string;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import * as stylex from '@stylexjs/stylex';
3
3
  import { NavLink } from 'react-router';
4
- import { breakpoints, colors, controls, iconSizes, layers, motion, radii, shadows, space, widths } from "../theme/tokens.stylex.js";
4
+ import { breakpoints, colors, controls, fontSizes, fontWeights, iconSizes, layers, lineHeights, motion, radii, shadows, space, widths } from "../theme/tokens.stylex.js";
5
5
  import { Badge } from "./Badge.js";
6
6
  import { focusRing } from "./controlLook.js";
7
7
  import { Button } from "./Button.js";
@@ -16,7 +16,8 @@ export function Sidebar({ workspace, workspaceMenu, links, isToggled, isAnimatin
16
16
  onFollowLink();
17
17
  };
18
18
  return (_jsxs("nav", { ...stylex.props(styles.sidebar, isToggled && styles.sidebarToggled, isAnimating && styles.sidebarAnimating), children: [_jsx("div", { ...stylex.props(styles.header), children: _jsx(Menu, { trigger: _jsxs(_Fragment, { children: [_jsx("span", { ...stylex.props(styles.logo), children: _jsx(Text, { variant: "caption", children: workspace.charAt(0) }) }), _jsx(Text, { variant: "label", isTruncated: true, children: workspace }), _jsx(Icon, { name: "chevronDown" })] }), items: workspaceMenu }) }), onSearch === undefined ? null : (_jsx(Button, { variant: "secondary", icon: "search", shortcut: ['mod', 'k'], isFullWidth: true, align: "start", onClick: onSearch, children: "Search" })), _jsx("div", { ...stylex.props(styles.body), children: _jsx(ScrollArea, { children: _jsxs("div", { ...stylex.props(styles.group), children: [_jsx("span", { ...stylex.props(styles.groupLabel), children: _jsx(Text, { variant: "label", tone: "tertiary", children: "Workspace" }) }), links.map((item) => (_jsxs(NavLink, { onClick: () => followLink(item), to: item.to, end: item.to === '/' ||
19
- links.some((other) => String(other.to).startsWith(`${String(item.to)}/`)), className: ({ isActive }) => stylex.props(styles.item, isActive && styles.itemCurrent, focusRing.visible, focusRing.inside).className ?? '', children: [_jsx(Icon, { name: item.icon }), _jsx(Text, { variant: "label", isTruncated: true, style: styles.itemLabel, children: item.label }), typeof item.count !== 'number' ? null : (_jsx(Badge, { variant: "transparent", children: item.count }))] }, item.to)))] }) }) })] }));
19
+ links.some((other) => String(other.to).startsWith(`${String(item.to)}/`)), className: ({ isActive }) => stylex.props(styles.item, isActive && styles.itemCurrent, focusRing.visible, focusRing.inside).className ?? '', children: [_jsx(Icon, { name: item.icon }), _jsx(Text, { variant: "label", isTruncated: true, style: styles.itemLabel, children: item.label }), typeof item.count !== 'number' ? null : item.countTone ===
20
+ 'brand' ? (_jsx("span", { ...stylex.props(styles.count), children: item.count })) : (_jsx(Badge, { variant: "transparent", children: item.count }))] }, item.to)))] }) }) })] }));
20
21
  }
21
22
  const styles = stylex.create({
22
23
  sidebar: {
@@ -132,6 +133,21 @@ const styles = stylex.create({
132
133
  color: colors.primaryForeground,
133
134
  backgroundColor: colors.transparentHover
134
135
  },
136
+ count: {
137
+ display: 'inline-flex',
138
+ flexShrink: 0,
139
+ alignItems: 'center',
140
+ justifyContent: 'center',
141
+ minWidth: `calc(${space.lg} + ${space.xxs})`,
142
+ height: `calc(${space.lg} + ${space.xxs})`,
143
+ paddingInline: `calc(${space.xs} + ${space.xxs} / 2)`,
144
+ fontSize: fontSizes.captionSm,
145
+ fontWeight: fontWeights.medium,
146
+ lineHeight: lineHeights.caption,
147
+ color: colors.onBrandSolidForeground,
148
+ backgroundColor: colors.brandSolid,
149
+ borderRadius: radii.full
150
+ },
135
151
  itemLabel: {
136
152
  flexGrow: 1
137
153
  }
@@ -2,8 +2,9 @@ import type { ReactNode } from 'react';
2
2
  type TableRowProps = {
3
3
  label: string;
4
4
  isCurrent?: boolean;
5
+ size?: 'md' | 'lg';
5
6
  onClick: () => void;
6
7
  children: ReactNode;
7
8
  };
8
- export declare function TableRow({ label, isCurrent, onClick, children }: TableRowProps): import("react").JSX.Element;
9
+ export declare function TableRow({ label, isCurrent, size, onClick, children }: TableRowProps): import("react").JSX.Element;
9
10
  export {};
@@ -4,11 +4,11 @@ import * as stylex from '@stylexjs/stylex';
4
4
  import { borders, colors, space } from "../theme/tokens.stylex.js";
5
5
  import { focusRing } from "./controlLook.js";
6
6
  import { interact } from "./interaction.js";
7
- export function TableRow({ label, isCurrent = false, onClick, children }) {
7
+ export function TableRow({ label, isCurrent = false, size = 'md', onClick, children }) {
8
8
  return (_jsx(BaseButton, { "aria-label": label, "aria-pressed": isCurrent, onClick: () => {
9
9
  interact({ primitive: 'TableRow', event: 'click', label });
10
10
  onClick();
11
- }, className: stylex.props(styles.row, isCurrent && styles.current, focusRing.visible, focusRing.inside).className, children: children }));
11
+ }, className: stylex.props(styles.row, size === 'lg' && styles.large, isCurrent && styles.current, focusRing.visible, focusRing.inside).className, children: children }));
12
12
  }
13
13
  const styles = stylex.create({
14
14
  row: {
@@ -30,6 +30,9 @@ const styles = stylex.create({
30
30
  borderBottomStyle: 'solid',
31
31
  borderBottomWidth: borders.thin
32
32
  },
33
+ large: {
34
+ height: `calc(${space.xxxl} + ${space.sm})`
35
+ },
33
36
  current: {
34
37
  backgroundColor: colors.transparentHover
35
38
  }
@@ -102,8 +102,7 @@ const builtInIcons = {
102
102
  ],
103
103
  arrowDown: 'M12 5v14M6 13l6 6 6-6',
104
104
  logOut: [
105
- 'M14 4.5H7.5a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2H14',
106
- 'M19.5 12H10M16 8.5l3.5 3.5-3.5 3.5'
105
+ 'M20.25 12L9 12M20.25 12L15.75 16.5M20.25 12L15.75 7.5M11.25 20.25H5.75C4.64543 20.25 3.75 19.3546 3.75 18.25L3.75 5.75C3.75 4.64543 4.64543 3.75 5.75 3.75L11.25 3.75'
107
106
  ],
108
107
  today: 'M11.998 3.291V1.768M5.84 18.159L4.763 19.236M11.998 22.233V20.709M19.233 4.765L18.156 5.842M20.707 12H22.23M18.156 18.159L19.233 19.236M1.766 12H3.289M4.763 4.765L5.84 5.842M15.71 8.288C17.761 10.338 17.761 13.662 15.71 15.712C13.66 17.763 10.336 17.763 8.286 15.712C6.235 13.662 6.235 10.338 8.286 8.288C10.336 6.238 13.66 6.238 15.71 8.288Z',
109
108
  checklist: 'M11.75 16.25H20.25M11.75 7.75H20.25M3.75 8.625L5.417 9.75L8.75 5.25M3.75 17.625L5.417 18.75L8.75 14.25',
@@ -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);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { defineApp } from './app.ts';
2
2
  export { Bookings, bookings } from './bookingsStore.ts';
3
3
  export type { BookingsWorld } from './bookingsMemory.ts';
4
+ export { Inquiries, inquiries } from './inquiriesStore.ts';
5
+ export type { InquiriesWorld } from './inquiriesStore.ts';
4
6
  export type { AppBlock } from './blocks.ts';
5
7
  export { ActionsStore, bindActions, rankActions } from './actions.ts';
6
8
  export type { Action, ActionsBuilder } from './actions.ts';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { defineApp } from "./app.js";
2
2
  export { Bookings, bookings } from "./bookingsStore.js";
3
+ export { Inquiries, inquiries } from "./inquiriesStore.js";
3
4
  export { ActionsStore, bindActions, rankActions } from "./actions.js";
4
5
  export { startApp } from "./start.js";
5
6
  export { appearance } from "./appearance.js";
@@ -0,0 +1,134 @@
1
+ import { z } from 'zod';
2
+ import type { WriteOutcome } from './access.ts';
3
+ import { inquiryValue } from './requests.ts';
4
+ import type { ServiceClient, ServiceMemory } from './services.ts';
5
+ type Fields = Readonly<Record<string, z.output<typeof inquiryValue>>>;
6
+ export type InquiriesWorld = {
7
+ sent?: {
8
+ source: string;
9
+ id: string;
10
+ fields: Fields;
11
+ }[];
12
+ sources?: string[];
13
+ };
14
+ declare const Inquiries_base: import("./store.ts").StoreClass<{
15
+ sent: z.ZodDefault<z.ZodNullable<z.ZodObject<{
16
+ source: z.ZodString;
17
+ id: z.ZodString;
18
+ }, z.core.$strip>>>;
19
+ isSending: z.ZodDefault<z.ZodBoolean>;
20
+ }, [], ServiceClient<{
21
+ readonly inquiries: {
22
+ readonly send: NoInfer<import("./services.ts").ServiceCall<"/requests/:source", {
23
+ fields: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>;
24
+ }, z.ZodObject<{
25
+ id: z.ZodString;
26
+ }, z.core.$strip>, false>>;
27
+ };
28
+ }>>;
29
+ export declare class Inquiries extends Inquiries_base {
30
+ send(source: string, fields: Fields): Promise<WriteOutcome>;
31
+ }
32
+ export declare const inquiries: {
33
+ readonly name: "inquiries";
34
+ readonly tables: {
35
+ requestStates: import("./table.ts").TableOf<"requestStates", {
36
+ readonly columns: {
37
+ readonly source: z.ZodString;
38
+ readonly requestId: z.ZodString;
39
+ readonly state: z.ZodEnum<{
40
+ done: "done";
41
+ open: "open";
42
+ }>;
43
+ readonly revision: z.ZodNumber;
44
+ readonly sequence: z.ZodNumber;
45
+ readonly updatedAt: z.ZodString;
46
+ };
47
+ readonly access: {
48
+ readonly read: "server";
49
+ readonly create: "server";
50
+ readonly update: "server";
51
+ readonly delete: "server";
52
+ };
53
+ readonly writes: "confirmed";
54
+ }>;
55
+ requestEvents: import("./table.ts").TableOf<"requestEvents", {
56
+ readonly columns: {
57
+ readonly source: z.ZodString;
58
+ readonly requestId: z.ZodString;
59
+ readonly kind: z.ZodEnum<{
60
+ done: "done";
61
+ accepted: "accepted";
62
+ declined: "declined";
63
+ replied: "replied";
64
+ received: "received";
65
+ }>;
66
+ };
67
+ readonly access: {
68
+ readonly read: "server";
69
+ readonly create: "server";
70
+ readonly update: "server";
71
+ readonly delete: "server";
72
+ };
73
+ readonly writes: "confirmed";
74
+ }>;
75
+ requestOutbox: import("./table.ts").TableOf<"requestOutbox", {
76
+ readonly columns: {
77
+ readonly source: z.ZodString;
78
+ readonly requestId: z.ZodString;
79
+ readonly subject: z.ZodString;
80
+ readonly text: z.ZodString;
81
+ readonly replyTo: z.ZodString;
82
+ readonly status: z.ZodEnum<{
83
+ failed: "failed";
84
+ pending: "pending";
85
+ sent: "sent";
86
+ sending: "sending";
87
+ }>;
88
+ readonly attempts: z.ZodNumber;
89
+ readonly nextAt: z.ZodNullable<z.ZodString>;
90
+ readonly sentAt: z.ZodNullable<z.ZodString>;
91
+ readonly claimedAt: z.ZodNullable<z.ZodString>;
92
+ };
93
+ readonly access: {
94
+ readonly read: "server";
95
+ readonly create: "server";
96
+ readonly update: "server";
97
+ readonly delete: "server";
98
+ };
99
+ readonly writes: "confirmed";
100
+ }>;
101
+ requestOperations: import("./table.ts").TableOf<"requestOperations", {
102
+ readonly columns: {
103
+ readonly result: z.ZodString;
104
+ };
105
+ readonly access: {
106
+ readonly read: "server";
107
+ readonly create: "server";
108
+ readonly update: "server";
109
+ readonly delete: "server";
110
+ };
111
+ readonly writes: "confirmed";
112
+ }>;
113
+ };
114
+ readonly services: {
115
+ readonly inquiries: {
116
+ readonly send: NoInfer<import("./services.ts").ServiceCall<"/requests/:source", {
117
+ fields: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>;
118
+ }, z.ZodObject<{
119
+ id: z.ZodString;
120
+ }, z.core.$strip>, false>>;
121
+ };
122
+ };
123
+ readonly memory: ServiceMemory<{
124
+ readonly inquiries: {
125
+ readonly send: NoInfer<import("./services.ts").ServiceCall<"/requests/:source", {
126
+ fields: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>;
127
+ }, z.ZodObject<{
128
+ id: z.ZodString;
129
+ }, z.core.$strip>, false>>;
130
+ };
131
+ }, unknown>;
132
+ readonly Store: typeof Inquiries;
133
+ };
134
+ export {};
@@ -0,0 +1,64 @@
1
+ import { z } from 'zod';
2
+ import { inquiryServices, inquiryValue, requestTables } from "./requests.js";
3
+ import { ServiceRefused, refused } from "./services.js";
4
+ import { defineStore } from "./store.js";
5
+ const Store = defineStore();
6
+ function worldOf(world) {
7
+ const found = Reflect.get(Object(world), 'inquiries');
8
+ return typeof found === 'object' && found !== null ? found : {};
9
+ }
10
+ const inquiryMemory = {
11
+ inquiries: {
12
+ send: (world, { source, fields }) => {
13
+ const current = worldOf(world);
14
+ if (current.sources !== undefined && !current.sources.includes(source)) {
15
+ throw refused(404, 'No such form');
16
+ }
17
+ const sent = current.sent ?? [];
18
+ const id = `${source}-${sent.length + 1}`;
19
+ Reflect.set(Object(world), 'inquiries', {
20
+ ...current,
21
+ sent: [...sent, { source, id, fields }]
22
+ });
23
+ return { id };
24
+ }
25
+ }
26
+ };
27
+ function outcomeOf(error) {
28
+ if (!(error instanceof ServiceRefused))
29
+ return 'failed';
30
+ if (error.status === 400 || error.status === 404)
31
+ return 'forbidden';
32
+ return error.status === 409 ? 'conflict' : 'failed';
33
+ }
34
+ export class Inquiries extends Store({
35
+ state: {
36
+ sent: z
37
+ .object({ source: z.string(), id: z.string() })
38
+ .nullable()
39
+ .default(null),
40
+ isSending: z.boolean().default(false)
41
+ }
42
+ }) {
43
+ async send(source, fields) {
44
+ this.set({ isSending: true });
45
+ try {
46
+ const { id } = await this.services.inquiries.send({ source, fields });
47
+ this.set({ sent: { source, id } });
48
+ return 'saved';
49
+ }
50
+ catch (error) {
51
+ return outcomeOf(error);
52
+ }
53
+ finally {
54
+ this.set({ isSending: false });
55
+ }
56
+ }
57
+ }
58
+ export const inquiries = {
59
+ name: 'inquiries',
60
+ tables: requestTables,
61
+ services: inquiryServices,
62
+ memory: inquiryMemory,
63
+ Store: Inquiries
64
+ };
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
@@ -2,6 +2,8 @@ import { z } from 'zod';
2
2
  export declare const TABLES_READ = "tables:read";
3
3
  export declare const MEDIA_WRITE = "media:write";
4
4
  export declare const TABLES_WRITE = "tables:write";
5
+ export declare const REQUESTS_READ = "requests:read";
6
+ export declare const REQUESTS_ACT = "requests:act";
5
7
  export declare const ASSERTION_SECONDS = 60;
6
8
  export declare const NEXT_PAGE_HEADER = "x-pattern-next";
7
9
  export declare const assertionKey: z.ZodObject<{
@@ -16,17 +18,21 @@ declare const assertionScope: z.ZodEnum<{
16
18
  "tables:read": "tables:read";
17
19
  "media:write": "media:write";
18
20
  "tables:write": "tables:write";
21
+ "requests:read": "requests:read";
22
+ "requests:act": "requests:act";
19
23
  }>;
20
24
  export type AssertionScope = z.output<typeof assertionScope>;
21
- export declare function signAssertion(key: AssertionKey, { sub, aud, scope }: {
25
+ export declare function signAssertion(key: AssertionKey, { sub, aud, scope, bound }: {
22
26
  sub: string;
23
27
  aud: string;
24
28
  scope: AssertionScope;
29
+ bound?: string;
25
30
  }, now: number): Promise<string>;
26
- export declare function verifiedAssertion(token: string, { hostname, keys, now, scope }: {
31
+ export declare function verifiedAssertion(token: string, { hostname, keys, now, scope, bound }: {
27
32
  hostname: string;
28
33
  keys: Readonly<Record<string, string>>;
29
34
  now: number;
30
35
  scope: AssertionScope;
36
+ bound?: string;
31
37
  }): Promise<string | undefined>;
32
38
  export {};
@@ -2,6 +2,8 @@ import { z } from 'zod';
2
2
  export const TABLES_READ = 'tables:read';
3
3
  export const MEDIA_WRITE = 'media:write';
4
4
  export const TABLES_WRITE = 'tables:write';
5
+ export const REQUESTS_READ = 'requests:read';
6
+ export const REQUESTS_ACT = 'requests:act';
5
7
  export const ASSERTION_SECONDS = 60;
6
8
  export const NEXT_PAGE_HEADER = 'x-pattern-next';
7
9
  const CLOCK_SKEW_SECONDS = 30;
@@ -13,7 +15,13 @@ export const assertionKey = z.object({
13
15
  x: z.string(),
14
16
  d: z.string()
15
17
  });
16
- const assertionScope = z.enum([TABLES_READ, TABLES_WRITE, MEDIA_WRITE]);
18
+ const assertionScope = z.enum([
19
+ TABLES_READ,
20
+ TABLES_WRITE,
21
+ MEDIA_WRITE,
22
+ REQUESTS_READ,
23
+ REQUESTS_ACT
24
+ ]);
17
25
  const assertionHeader = z.object({
18
26
  alg: z.literal('EdDSA'),
19
27
  typ: z.literal('JWT'),
@@ -24,7 +32,8 @@ const assertionClaims = z.object({
24
32
  aud: z.string(),
25
33
  scope: assertionScope,
26
34
  iat: z.int(),
27
- exp: z.int()
35
+ exp: z.int(),
36
+ bnd: z.string().optional()
28
37
  });
29
38
  const encoder = new TextEncoder();
30
39
  function base64url(bytes) {
@@ -56,14 +65,15 @@ function jsonOf(text) {
56
65
  function encoded(value) {
57
66
  return base64url(encoder.encode(JSON.stringify(value)));
58
67
  }
59
- export async function signAssertion(key, { sub, aud, scope }, now) {
68
+ export async function signAssertion(key, { sub, aud, scope, bound }, now) {
60
69
  const privateKey = await crypto.subtle.importKey('jwk', { kty: key.kty, crv: key.crv, x: key.x, d: key.d }, ED25519, false, ['sign']);
61
70
  const signed = `${encoded({ alg: 'EdDSA', typ: 'JWT', kid: key.kid })}.${encoded({
62
71
  sub,
63
72
  aud,
64
73
  scope,
65
74
  iat: now,
66
- exp: now + ASSERTION_SECONDS
75
+ exp: now + ASSERTION_SECONDS,
76
+ ...(bound === undefined ? {} : { bnd: bound })
67
77
  })}`;
68
78
  const signature = await crypto.subtle.sign(ED25519, privateKey, encoder.encode(signed));
69
79
  return `${signed}.${base64url(new Uint8Array(signature))}`;
@@ -83,7 +93,7 @@ async function isSignedBy(publicKey, signed, signature) {
83
93
  return false;
84
94
  }
85
95
  }
86
- export async function verifiedAssertion(token, { hostname, keys, now, scope }) {
96
+ export async function verifiedAssertion(token, { hostname, keys, now, scope, bound }) {
87
97
  const [header, claims, signature, ...rest] = token.split('.');
88
98
  if (header === undefined ||
89
99
  claims === undefined ||
@@ -107,6 +117,8 @@ export async function verifiedAssertion(token, { hostname, keys, now, scope }) {
107
117
  const { sub, aud, iat, exp } = parsed.data;
108
118
  if (parsed.data.scope !== scope)
109
119
  return undefined;
120
+ if (bound !== undefined && parsed.data.bnd !== bound)
121
+ return undefined;
110
122
  const isFresh = exp > iat &&
111
123
  exp - iat <= ASSERTION_SECONDS &&
112
124
  iat <= now + CLOCK_SKEW_SECONDS &&