acrylblends 0.7.0 → 0.7.2
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 +2 -2
- package/dist/index.js +75 -4
- package/dist/index.js.map +1 -1
- package/docs/framework/README.md +2 -1
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/api/summary.md +7 -4
- package/docs/framework/cordis/node-server.md +3 -0
- package/docs/framework/getting-started/deploy.md +133 -0
- package/docs/framework/getting-started/desktop.md +1 -1
- package/docs/framework/getting-started/nodejs.md +37 -16
- package/docs/framework/guides/client-modules.md +175 -0
- package/docs/framework/guides/forms-and-sessions.md +231 -0
- package/docs/framework/guides/jsx-dom.md +12 -7
- package/docs/framework/guides/validation.md +88 -1
- package/docs/framework/helpers/cookie.md +1 -1
- package/docs/framework/helpers/testing.md +17 -0
- package/docs/framework/upstream.json +3 -3
- package/lib/browser/client.js +307 -0
- package/lib/browser/jsx-dom.js +1432 -0
- package/lib/index.js +1 -25
- package/lib/node-server/register.js +7 -49
- package/lib/node-server.d.ts +74 -7
- package/lib/node-server.js +264 -7
- package/lib/schema-validator.d.ts +40 -14
- package/lib/schema-validator.js +2 -2
- package/lib/src-0hI7M7cN.js +27 -0
- package/lib/transpile-CTnWof4z.js +90 -0
- package/package.json +1 -1
- package/plugins/acryl-inspect/upstream.json +1 -1
- package/plugins/acryl-node-server/src/adapter/server.ts +28 -5
- package/plugins/acryl-node-server/src/adapter/types.ts +14 -2
- package/plugins/acryl-node-server/src/client-modules.ts +293 -0
- package/plugins/acryl-node-server/src/register.ts +5 -62
- package/plugins/acryl-node-server/src/transpile.ts +120 -0
- package/plugins/acryl-node-server/upstream.json +4 -3
- package/plugins/acryl-router/src/browser/client.ts +3 -0
- package/plugins/acryl-router/src/browser/jsx-dom.ts +9 -0
- package/plugins/acryl-router/src/helpers/index.ts +1 -1
- package/plugins/acryl-router/src/helpers/schema-validator.ts +39 -12
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/upstream.json +1 -1
- package/plugins/acryl-state/upstream.json +1 -1
- package/plugins/desktop/plugin.json +1 -1
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/plugin.json +1 -1
- package/plugins/desktop-web/upstream.json +1 -1
- package/templates/agent/package.json +4 -3
- package/templates/blank/package.json +4 -3
- package/templates/todo/package.json +4 -3
- package/templates/todo/upstream.json +1 -1
|
@@ -99,3 +99,20 @@ describe('Search Endpoint', () => {
|
|
|
99
99
|
})
|
|
100
100
|
})
|
|
101
101
|
```
|
|
102
|
+
|
|
103
|
+
### Default request options
|
|
104
|
+
|
|
105
|
+
`testClient(app, env?, executionCtx?, options?)` takes the client's request options as its fourth argument, so headers every request of a test
|
|
106
|
+
needs, such as a session cookie, are set once. Headers given to a single call are merged over them.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { Acryl } from 'acrylblends'
|
|
110
|
+
import { getCookie } from 'acrylblends/cookie'
|
|
111
|
+
import { testClient } from 'acrylblends/testing'
|
|
112
|
+
|
|
113
|
+
const app = new Acryl().get('/me', (c) => c.json({ session: getCookie(c, 'session') ?? null }))
|
|
114
|
+
|
|
115
|
+
const client = testClient(app, undefined, undefined, { headers: { cookie: 'session=abc' } })
|
|
116
|
+
await (await client.me.$get()).json() // { session: 'abc' }
|
|
117
|
+
await (await client.me.$get(undefined, { headers: { cookie: 'session=xyz' } })).json() // { session: 'xyz' }
|
|
118
|
+
```
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"source": {
|
|
3
3
|
"repo": "acryldev/acryl",
|
|
4
4
|
"path": "docs/framework",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "cdc083e1bbaedaeafa7494fe646bdb202ce86d09"
|
|
6
6
|
},
|
|
7
|
-
"files":
|
|
8
|
-
"digest": "sha256:
|
|
7
|
+
"files": 91,
|
|
8
|
+
"digest": "sha256:964e5ff58d487ce53394469d359a251265a3ef093354f55b6b4c47c55051774b"
|
|
9
9
|
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
//#region ../../plugins/acryl-router/src/core/utils/cookie.ts
|
|
2
|
+
const validCookieNameRegEx = /^[\w!#$%&'*.^`|~+-]+$/;
|
|
3
|
+
const _serialize = (name, value, opt = {}) => {
|
|
4
|
+
if (!validCookieNameRegEx.test(name)) throw new Error("Invalid cookie name");
|
|
5
|
+
let cookie = `${name}=${value}`;
|
|
6
|
+
if (name.startsWith("__Secure-") && !opt.secure) throw new Error("__Secure- Cookie must have Secure attributes");
|
|
7
|
+
if (name.startsWith("__Host-")) {
|
|
8
|
+
if (!opt.secure) throw new Error("__Host- Cookie must have Secure attributes");
|
|
9
|
+
if (opt.path !== "/") throw new Error("__Host- Cookie must have Path attributes with \"/\"");
|
|
10
|
+
if (opt.domain) throw new Error("__Host- Cookie must not have Domain attributes");
|
|
11
|
+
}
|
|
12
|
+
for (const key of [
|
|
13
|
+
"domain",
|
|
14
|
+
"path",
|
|
15
|
+
"sameSite",
|
|
16
|
+
"priority"
|
|
17
|
+
]) if (opt[key] && /[;\r\n]/.test(opt[key])) throw new Error(`${key} must not contain ";", "\\r", or "\\n"`);
|
|
18
|
+
if (opt && typeof opt.maxAge === "number" && opt.maxAge >= 0) {
|
|
19
|
+
if (opt.maxAge > 3456e4) throw new Error("Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.");
|
|
20
|
+
cookie += `; Max-Age=${opt.maxAge | 0}`;
|
|
21
|
+
}
|
|
22
|
+
if (opt.domain && opt.prefix !== "host") cookie += `; Domain=${opt.domain}`;
|
|
23
|
+
if (opt.path) cookie += `; Path=${opt.path}`;
|
|
24
|
+
if (opt.expires) {
|
|
25
|
+
if (opt.expires.getTime() - Date.now() > 3456e7) throw new Error("Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.");
|
|
26
|
+
cookie += `; Expires=${opt.expires.toUTCString()}`;
|
|
27
|
+
}
|
|
28
|
+
if (opt.httpOnly) cookie += "; HttpOnly";
|
|
29
|
+
if (opt.secure) cookie += "; Secure";
|
|
30
|
+
if (opt.sameSite) cookie += `; SameSite=${opt.sameSite.charAt(0).toUpperCase() + opt.sameSite.slice(1)}`;
|
|
31
|
+
if (opt.priority) cookie += `; Priority=${opt.priority.charAt(0).toUpperCase() + opt.priority.slice(1)}`;
|
|
32
|
+
if (opt.partitioned) {
|
|
33
|
+
if (!opt.secure) throw new Error("Partitioned Cookie must have Secure attributes");
|
|
34
|
+
cookie += "; Partitioned";
|
|
35
|
+
}
|
|
36
|
+
return cookie;
|
|
37
|
+
};
|
|
38
|
+
const serialize = (name, value, opt) => {
|
|
39
|
+
value = encodeURIComponent(value);
|
|
40
|
+
return _serialize(name, value, opt);
|
|
41
|
+
};
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region ../../plugins/acryl-router/src/core/client/fetch-result-please.ts
|
|
44
|
+
/**
|
|
45
|
+
* @description This file is a modified version of `fetch-result-please` (`ofetch`), minimalized and adapted to Acryl's custom needs.
|
|
46
|
+
*
|
|
47
|
+
* @link https://www.npmjs.com/package/fetch-result-please
|
|
48
|
+
*/
|
|
49
|
+
const nullBodyResponses = /* @__PURE__ */ new Set([
|
|
50
|
+
101,
|
|
51
|
+
204,
|
|
52
|
+
205,
|
|
53
|
+
304
|
|
54
|
+
]);
|
|
55
|
+
/**
|
|
56
|
+
* Smartly parses and return the consumable result from a fetch `Response`.
|
|
57
|
+
*
|
|
58
|
+
* Throwing a structured error if the response is not `ok`. ({@link DetailedError})
|
|
59
|
+
*/
|
|
60
|
+
async function fetchRP(fetchRes) {
|
|
61
|
+
const _fetchRes = await fetchRes;
|
|
62
|
+
if ((_fetchRes.body || _fetchRes._bodyInit) && !nullBodyResponses.has(_fetchRes.status)) _fetchRes._data = await _fetchRes[detectResponseType(_fetchRes)]();
|
|
63
|
+
if (!_fetchRes.ok) throw new DetailedError(`${_fetchRes.status} ${_fetchRes.statusText}`, {
|
|
64
|
+
statusCode: _fetchRes?.status,
|
|
65
|
+
detail: {
|
|
66
|
+
data: _fetchRes?._data,
|
|
67
|
+
statusText: _fetchRes?.statusText
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return _fetchRes._data;
|
|
71
|
+
}
|
|
72
|
+
var DetailedError = class extends Error {
|
|
73
|
+
/**
|
|
74
|
+
* Additional `message` that will be logged AND returned to client
|
|
75
|
+
*/
|
|
76
|
+
detail;
|
|
77
|
+
/**
|
|
78
|
+
* Additional `code` that will be logged AND returned to client
|
|
79
|
+
*/
|
|
80
|
+
code;
|
|
81
|
+
/**
|
|
82
|
+
* Additional value that will be logged AND NOT returned to client
|
|
83
|
+
*/
|
|
84
|
+
log;
|
|
85
|
+
/**
|
|
86
|
+
* Optionally set the status code to return, in a web server context
|
|
87
|
+
*/
|
|
88
|
+
statusCode;
|
|
89
|
+
constructor(message, options = {}) {
|
|
90
|
+
super(message);
|
|
91
|
+
this.name = "DetailedError";
|
|
92
|
+
this.log = options.log;
|
|
93
|
+
this.detail = options.detail;
|
|
94
|
+
this.code = options.code;
|
|
95
|
+
this.statusCode = options.statusCode;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const jsonRegex = /^application\/(?:[\w!#$%&*.^`~-]*\+)?json(?:;.+)?$/i;
|
|
99
|
+
function detectResponseType(response) {
|
|
100
|
+
const _contentType = response.headers.get("content-type");
|
|
101
|
+
if (!_contentType) return "text";
|
|
102
|
+
const contentType = _contentType.split(";").shift();
|
|
103
|
+
if (jsonRegex.test(contentType)) return "json";
|
|
104
|
+
return "text";
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region ../../plugins/acryl-router/src/core/client/utils.ts
|
|
108
|
+
const mergePath = (base, path) => {
|
|
109
|
+
base = base.replace(/\/+$/, "");
|
|
110
|
+
base = base + "/";
|
|
111
|
+
path = path.replace(/^\/+/, "");
|
|
112
|
+
return base + path;
|
|
113
|
+
};
|
|
114
|
+
const replaceUrlParam = (urlString, params) => {
|
|
115
|
+
for (const [k, v] of Object.entries(params)) {
|
|
116
|
+
const reg = new RegExp("/:" + k + "(?:{[^/]+})?\\??(?=/|$)");
|
|
117
|
+
urlString = urlString.replace(reg, () => v ? `/${v}` : "");
|
|
118
|
+
}
|
|
119
|
+
return urlString;
|
|
120
|
+
};
|
|
121
|
+
const buildSearchParams = (query) => {
|
|
122
|
+
const searchParams = new URLSearchParams();
|
|
123
|
+
for (const [k, v] of Object.entries(query)) {
|
|
124
|
+
if (v === void 0) continue;
|
|
125
|
+
if (Array.isArray(v)) for (const v2 of v) {
|
|
126
|
+
if (v2 === void 0) continue;
|
|
127
|
+
searchParams.append(k, v2);
|
|
128
|
+
}
|
|
129
|
+
else searchParams.set(k, v);
|
|
130
|
+
}
|
|
131
|
+
return searchParams;
|
|
132
|
+
};
|
|
133
|
+
const replaceUrlProtocol = (urlString, protocol) => {
|
|
134
|
+
switch (protocol) {
|
|
135
|
+
case "ws": return urlString.replace(/^http/, "ws");
|
|
136
|
+
case "http": return urlString.replace(/^ws/, "http");
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const removeIndexString = (urlString) => {
|
|
140
|
+
if (/^https?:\/\/[^\/]+?\/index(?=\?|$)/.test(urlString)) return urlString.replace(/\/index(?=\?|$)/, "/");
|
|
141
|
+
return urlString.replace(/\/index(?=\?|$)/, "");
|
|
142
|
+
};
|
|
143
|
+
function isObject(item) {
|
|
144
|
+
return typeof item === "object" && item !== null && !Array.isArray(item);
|
|
145
|
+
}
|
|
146
|
+
function deepMerge(target, source) {
|
|
147
|
+
if (!isObject(target) && !isObject(source)) return source;
|
|
148
|
+
const merged = { ...target };
|
|
149
|
+
for (const key in source) {
|
|
150
|
+
const value = source[key];
|
|
151
|
+
if (isObject(merged[key]) && isObject(value)) merged[key] = deepMerge(merged[key], value);
|
|
152
|
+
else merged[key] = value;
|
|
153
|
+
}
|
|
154
|
+
return merged;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Shortcut to get a consumable response from `hc`'s fetch calls (Response), with types inference.
|
|
158
|
+
*
|
|
159
|
+
* Smartly parse the response data, throwing a structured error if the response is not `ok`. ({@link DetailedError})
|
|
160
|
+
*
|
|
161
|
+
* @example const result = await parseResponse(client.posts.$get())
|
|
162
|
+
*/
|
|
163
|
+
async function parseResponse(fetchRes) {
|
|
164
|
+
return fetchRP(fetchRes);
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region ../../plugins/acryl-router/src/core/client/client.ts
|
|
168
|
+
const createProxy = (callback, path) => {
|
|
169
|
+
return new Proxy(() => {}, {
|
|
170
|
+
get(_obj, key) {
|
|
171
|
+
if (typeof key !== "string" || key === "then") return;
|
|
172
|
+
return createProxy(callback, [...path, key]);
|
|
173
|
+
},
|
|
174
|
+
apply(_1, _2, args) {
|
|
175
|
+
return callback({
|
|
176
|
+
path,
|
|
177
|
+
args
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
const appendQueryParams = (url, searchParams) => {
|
|
183
|
+
const queryString = searchParams.toString();
|
|
184
|
+
return queryString ? `${url}?${queryString}` : url;
|
|
185
|
+
};
|
|
186
|
+
var ClientRequestImpl = class {
|
|
187
|
+
url;
|
|
188
|
+
method;
|
|
189
|
+
buildSearchParams;
|
|
190
|
+
queryParams = void 0;
|
|
191
|
+
pathParams = {};
|
|
192
|
+
rBody;
|
|
193
|
+
cType = void 0;
|
|
194
|
+
constructor(url, method, options) {
|
|
195
|
+
this.url = url;
|
|
196
|
+
this.method = method;
|
|
197
|
+
this.buildSearchParams = options.buildSearchParams;
|
|
198
|
+
}
|
|
199
|
+
fetch = async (args, opt) => {
|
|
200
|
+
if (args) {
|
|
201
|
+
if (args.query) this.queryParams = this.buildSearchParams(args.query);
|
|
202
|
+
if (args.form) {
|
|
203
|
+
const form = new FormData();
|
|
204
|
+
for (const [k, v] of Object.entries(args.form)) {
|
|
205
|
+
if (v === void 0) continue;
|
|
206
|
+
if (Array.isArray(v)) for (const v2 of v) {
|
|
207
|
+
if (v2 === void 0) continue;
|
|
208
|
+
form.append(k, v2);
|
|
209
|
+
}
|
|
210
|
+
else form.append(k, v);
|
|
211
|
+
}
|
|
212
|
+
this.rBody = form;
|
|
213
|
+
}
|
|
214
|
+
if (args.json !== void 0) {
|
|
215
|
+
this.rBody = JSON.stringify(args.json);
|
|
216
|
+
this.cType = "application/json";
|
|
217
|
+
}
|
|
218
|
+
if (args.param) this.pathParams = args.param;
|
|
219
|
+
}
|
|
220
|
+
let methodUpperCase = this.method.toUpperCase();
|
|
221
|
+
const headerValues = {
|
|
222
|
+
...args?.header,
|
|
223
|
+
...typeof opt?.headers === "function" ? await opt.headers() : opt?.headers
|
|
224
|
+
};
|
|
225
|
+
if (args?.cookie) {
|
|
226
|
+
const cookies = [];
|
|
227
|
+
for (const [key, value] of Object.entries(args.cookie)) {
|
|
228
|
+
if (value === void 0) continue;
|
|
229
|
+
cookies.push(serialize(key, value));
|
|
230
|
+
}
|
|
231
|
+
if (cookies.length > 0) headerValues["Cookie"] = cookies.join("; ");
|
|
232
|
+
}
|
|
233
|
+
if (this.cType) headerValues["Content-Type"] = this.cType;
|
|
234
|
+
const headers = new Headers();
|
|
235
|
+
for (const [key, value] of Object.entries(headerValues)) if (value !== void 0) headers.set(key, value);
|
|
236
|
+
let url = this.url;
|
|
237
|
+
url = removeIndexString(url);
|
|
238
|
+
url = replaceUrlParam(url, this.pathParams);
|
|
239
|
+
if (this.queryParams) url = appendQueryParams(url, this.queryParams);
|
|
240
|
+
methodUpperCase = this.method.toUpperCase();
|
|
241
|
+
const setBody = !(methodUpperCase === "GET" || methodUpperCase === "HEAD");
|
|
242
|
+
return (opt?.fetch || fetch)(url, {
|
|
243
|
+
body: setBody ? this.rBody : void 0,
|
|
244
|
+
method: methodUpperCase,
|
|
245
|
+
headers,
|
|
246
|
+
...opt?.init
|
|
247
|
+
});
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
const hc = (baseUrl, options) => createProxy(function proxyCallback(opts) {
|
|
251
|
+
const buildSearchParamsOption = options?.buildSearchParams ?? buildSearchParams;
|
|
252
|
+
const parts = [...opts.path];
|
|
253
|
+
const lastParts = parts.slice(-3).reverse();
|
|
254
|
+
if (lastParts[0] === "toString") {
|
|
255
|
+
if (lastParts[1] === "name") return lastParts[2] || "";
|
|
256
|
+
return proxyCallback.toString();
|
|
257
|
+
}
|
|
258
|
+
if (lastParts[0] === "valueOf") {
|
|
259
|
+
if (lastParts[1] === "name") return lastParts[2] || "";
|
|
260
|
+
return proxyCallback;
|
|
261
|
+
}
|
|
262
|
+
let method = "";
|
|
263
|
+
if (/^\$/.test(lastParts[0])) {
|
|
264
|
+
const last = parts.pop();
|
|
265
|
+
if (last) method = last.replace(/^\$/, "");
|
|
266
|
+
}
|
|
267
|
+
const url = mergePath(baseUrl, parts.join("/"));
|
|
268
|
+
if (method === "url" || method === "path") {
|
|
269
|
+
let result = removeIndexString(url);
|
|
270
|
+
if (opts.args[0]) {
|
|
271
|
+
if (opts.args[0].param) result = replaceUrlParam(result, opts.args[0].param);
|
|
272
|
+
if (opts.args[0].query) result = appendQueryParams(result, buildSearchParamsOption(opts.args[0].query));
|
|
273
|
+
}
|
|
274
|
+
if (method === "url") return new URL(result);
|
|
275
|
+
return result.slice(baseUrl.replace(/\/+$/, "").length).replace(/^\/?/, "/");
|
|
276
|
+
}
|
|
277
|
+
if (method === "ws") {
|
|
278
|
+
const normalizedUrl = removeIndexString(url);
|
|
279
|
+
const webSocketUrl = replaceUrlProtocol(opts.args[0]?.param ? replaceUrlParam(normalizedUrl, opts.args[0].param) : normalizedUrl, "ws");
|
|
280
|
+
const targetUrl = new URL(webSocketUrl);
|
|
281
|
+
const queryParams = opts.args[0]?.query;
|
|
282
|
+
if (queryParams) buildSearchParamsOption(queryParams).forEach((value, key) => {
|
|
283
|
+
targetUrl.searchParams.append(key, value);
|
|
284
|
+
});
|
|
285
|
+
const establishWebSocket = (...args) => {
|
|
286
|
+
if (options?.webSocket !== void 0 && typeof options.webSocket === "function") return options.webSocket(...args);
|
|
287
|
+
return new WebSocket(...args);
|
|
288
|
+
};
|
|
289
|
+
return establishWebSocket(targetUrl.toString());
|
|
290
|
+
}
|
|
291
|
+
const req = new ClientRequestImpl(url, method, { buildSearchParams: buildSearchParamsOption });
|
|
292
|
+
if (method) {
|
|
293
|
+
options ??= {};
|
|
294
|
+
const reqOptions = { ...opts.args[1] };
|
|
295
|
+
const baseHeaders = options.headers;
|
|
296
|
+
const reqHeaders = reqOptions.headers;
|
|
297
|
+
if (baseHeaders && reqHeaders) reqOptions.headers = async () => ({
|
|
298
|
+
...typeof baseHeaders === "function" ? await baseHeaders() : baseHeaders,
|
|
299
|
+
...typeof reqHeaders === "function" ? await reqHeaders() : reqHeaders
|
|
300
|
+
});
|
|
301
|
+
const args = deepMerge(options, reqOptions);
|
|
302
|
+
return req.fetch(opts.args[0], args);
|
|
303
|
+
}
|
|
304
|
+
return req;
|
|
305
|
+
}, []);
|
|
306
|
+
//#endregion
|
|
307
|
+
export { DetailedError, hc, parseResponse };
|