@trypattern/sdk 0.1.31 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bookings.d.ts +8 -8
- package/dist/bookingsStore.d.ts +24 -24
- package/dist/components/FilePicker.d.ts +13 -0
- package/dist/components/FilePicker.js +81 -0
- package/dist/components/FilePreview.d.ts +10 -0
- package/dist/components/FilePreview.js +36 -0
- package/dist/components/Image.d.ts +2 -1
- package/dist/components/Image.js +5 -2
- package/dist/components/Menu.js +2 -2
- package/dist/components/ProgressBar.d.ts +8 -0
- package/dist/components/ProgressBar.js +29 -0
- package/dist/components/SegmentedControl.js +2 -2
- package/dist/components/TableRow.d.ts +9 -0
- package/dist/components/TableRow.js +36 -0
- package/dist/components/icons.d.ts +1 -0
- package/dist/components/icons.js +5 -0
- package/dist/components/interaction.d.ts +1 -1
- package/dist/files.d.ts +11 -0
- package/dist/files.js +19 -0
- package/dist/flows/browser.js +7 -0
- package/dist/flows/flows.d.ts +7 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/media.d.ts +2 -1
- package/dist/media.js +4 -4
- package/dist/publishWire.d.ts +55 -27
- package/dist/publishWire.js +12 -0
- package/dist/react.d.ts +4 -0
- package/dist/react.js +4 -0
- package/dist/rules/lintPlugin.js +4 -0
- package/dist/schema.d.ts +1 -1
- package/dist/server/services.d.ts +2 -2
- package/dist/server/services.js +21 -4
- package/dist/server/tables.js +8 -1
- package/dist/serviceClients.js +107 -23
- package/dist/services.d.ts +28 -4
- package/dist/services.js +8 -4
- package/package.json +1 -1
package/dist/serviceClients.js
CHANGED
|
@@ -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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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 (
|
|
45
|
-
throw new ServiceRefused(
|
|
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) => [
|
|
65
|
-
|
|
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
|
}
|
package/dist/services.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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> =
|
|
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
|
|
4
|
-
|
|
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) {
|