summitjs 0.1.0 → 0.3.0
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 +38 -0
- package/dist/net.cjs +259 -0
- package/dist/net.cjs.map +1 -0
- package/dist/net.d.cts +134 -0
- package/dist/net.d.ts +134 -0
- package/dist/net.js +252 -0
- package/dist/net.js.map +1 -0
- package/dist/summit.cjs +147 -18
- package/dist/summit.cjs.map +1 -1
- package/dist/summit.js +147 -18
- package/dist/summit.js.map +1 -1
- package/dist/summit.min.js +7 -6
- package/dist/summit.min.js.map +1 -1
- package/package.json +11 -1
package/dist/net.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { signal } from 'summitjs';
|
|
2
|
+
|
|
3
|
+
// src/net/client.ts
|
|
4
|
+
var NetError = class extends Error {
|
|
5
|
+
constructor(message, init) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "NetError";
|
|
8
|
+
this.status = init.status;
|
|
9
|
+
this.body = init.body;
|
|
10
|
+
this.url = init.url;
|
|
11
|
+
this.method = init.method;
|
|
12
|
+
this.aborted = init.aborted;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function createClient(defaults = {}) {
|
|
16
|
+
async function request(path, req = {}) {
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const timeoutMs = req.timeout ?? defaults.timeout;
|
|
19
|
+
const timer = timeoutMs ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
|
|
20
|
+
if (req.signal) {
|
|
21
|
+
if (req.signal.aborted) controller.abort();
|
|
22
|
+
else req.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
23
|
+
}
|
|
24
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
25
|
+
const url = withParams(joinURL(defaults.baseURL, path), req.params);
|
|
26
|
+
const baseHeaders = typeof defaults.headers === "function" ? defaults.headers() : defaults.headers;
|
|
27
|
+
const headers = { ...baseHeaders, ...req.headers };
|
|
28
|
+
let init = { url, method, headers, signal: controller.signal };
|
|
29
|
+
if (req.body !== void 0 && method !== "GET" && method !== "HEAD") {
|
|
30
|
+
if (!hasHeader(headers, "content-type") && typeof req.body !== "string") {
|
|
31
|
+
headers["content-type"] = "application/json";
|
|
32
|
+
}
|
|
33
|
+
init.body = typeof req.body === "string" ? req.body : JSON.stringify(req.body);
|
|
34
|
+
}
|
|
35
|
+
const patched = defaults.beforeRequest?.(init);
|
|
36
|
+
if (patched) init = patched;
|
|
37
|
+
try {
|
|
38
|
+
let res = await fetch(init.url, init);
|
|
39
|
+
const swapped = defaults.afterResponse?.(res);
|
|
40
|
+
if (swapped) res = swapped;
|
|
41
|
+
const body = await readBody(res);
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
throw new NetError(`Request failed with status ${res.status}`, {
|
|
44
|
+
status: res.status,
|
|
45
|
+
body,
|
|
46
|
+
url: init.url,
|
|
47
|
+
method,
|
|
48
|
+
aborted: false
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return body;
|
|
52
|
+
} catch (err) {
|
|
53
|
+
const e = normalizeError(err, init.url, method);
|
|
54
|
+
defaults.onError?.(e);
|
|
55
|
+
throw e;
|
|
56
|
+
} finally {
|
|
57
|
+
if (timer) clearTimeout(timer);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const client = {
|
|
61
|
+
request,
|
|
62
|
+
get: (path, options) => request(path, { ...options, method: "GET" }),
|
|
63
|
+
post: (path, body, options) => request(path, { ...options, method: "POST", body }),
|
|
64
|
+
put: (path, body, options) => request(path, { ...options, method: "PUT", body }),
|
|
65
|
+
patch: (path, body, options) => request(path, { ...options, method: "PATCH", body }),
|
|
66
|
+
delete: (path, options) => request(path, { ...options, method: "DELETE" }),
|
|
67
|
+
options: defaults
|
|
68
|
+
};
|
|
69
|
+
return client;
|
|
70
|
+
}
|
|
71
|
+
function normalizeError(err, url, method) {
|
|
72
|
+
if (err instanceof NetError) return err;
|
|
73
|
+
const aborted = err?.name === "AbortError";
|
|
74
|
+
return new NetError(aborted ? "Request aborted" : err?.message ?? "Network request failed", {
|
|
75
|
+
status: 0,
|
|
76
|
+
body: void 0,
|
|
77
|
+
url,
|
|
78
|
+
method,
|
|
79
|
+
aborted
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async function readBody(res) {
|
|
83
|
+
const type = res.headers.get("content-type") ?? "";
|
|
84
|
+
const text = await res.text();
|
|
85
|
+
if (text === "") return void 0;
|
|
86
|
+
if (type.includes("application/json") || type.includes("+json")) {
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(text);
|
|
89
|
+
} catch {
|
|
90
|
+
return text;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return text;
|
|
94
|
+
}
|
|
95
|
+
function hasHeader(headers, name) {
|
|
96
|
+
const lower = name.toLowerCase();
|
|
97
|
+
return Object.keys(headers).some((k) => k.toLowerCase() === lower);
|
|
98
|
+
}
|
|
99
|
+
function joinURL(base, path) {
|
|
100
|
+
if (!base || /^[a-z][a-z0-9+.-]*:\/\//i.test(path)) return path;
|
|
101
|
+
return `${base.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
102
|
+
}
|
|
103
|
+
function withParams(url, params) {
|
|
104
|
+
if (!params) return url;
|
|
105
|
+
const query = new URLSearchParams();
|
|
106
|
+
for (const [key, value] of Object.entries(params)) {
|
|
107
|
+
if (value != null) query.set(key, String(value));
|
|
108
|
+
}
|
|
109
|
+
const suffix = query.toString();
|
|
110
|
+
if (!suffix) return url;
|
|
111
|
+
return `${url}${url.includes("?") ? "&" : "?"}${suffix}`;
|
|
112
|
+
}
|
|
113
|
+
function createResource(client, url, options = {}) {
|
|
114
|
+
const dataSig = signal(void 0);
|
|
115
|
+
const errorSig = signal(void 0);
|
|
116
|
+
const loadingSig = signal(false);
|
|
117
|
+
const statusSig = signal("idle");
|
|
118
|
+
const { immediate, onCleanup, ...requestDefaults } = options;
|
|
119
|
+
let seq = 0;
|
|
120
|
+
let controller;
|
|
121
|
+
async function refetch(overrides = {}) {
|
|
122
|
+
const current = ++seq;
|
|
123
|
+
controller?.abort();
|
|
124
|
+
controller = new AbortController();
|
|
125
|
+
loadingSig.set(true);
|
|
126
|
+
statusSig.set("loading");
|
|
127
|
+
errorSig.set(void 0);
|
|
128
|
+
try {
|
|
129
|
+
const target = typeof url === "function" ? url() : url;
|
|
130
|
+
const result = await client.request(target, {
|
|
131
|
+
...requestDefaults,
|
|
132
|
+
...overrides,
|
|
133
|
+
signal: controller.signal
|
|
134
|
+
});
|
|
135
|
+
if (current !== seq) return void 0;
|
|
136
|
+
dataSig.set(result);
|
|
137
|
+
statusSig.set("success");
|
|
138
|
+
return result;
|
|
139
|
+
} catch (err) {
|
|
140
|
+
if (current !== seq) return void 0;
|
|
141
|
+
const e = err;
|
|
142
|
+
if (e.aborted) {
|
|
143
|
+
statusSig.set("idle");
|
|
144
|
+
return void 0;
|
|
145
|
+
}
|
|
146
|
+
errorSig.set(e);
|
|
147
|
+
statusSig.set("error");
|
|
148
|
+
return void 0;
|
|
149
|
+
} finally {
|
|
150
|
+
if (current === seq) loadingSig.set(false);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const resource = {
|
|
154
|
+
get data() {
|
|
155
|
+
return dataSig();
|
|
156
|
+
},
|
|
157
|
+
get error() {
|
|
158
|
+
return errorSig();
|
|
159
|
+
},
|
|
160
|
+
get loading() {
|
|
161
|
+
return loadingSig();
|
|
162
|
+
},
|
|
163
|
+
get status() {
|
|
164
|
+
return statusSig();
|
|
165
|
+
},
|
|
166
|
+
refetch,
|
|
167
|
+
mutate(next, mutateOptions = {}) {
|
|
168
|
+
const previous = dataSig.peek();
|
|
169
|
+
dataSig.set((prev) => typeof next === "function" ? next(prev) : next);
|
|
170
|
+
if (!mutateOptions.request) return;
|
|
171
|
+
void Promise.resolve().then(mutateOptions.request).catch((err) => {
|
|
172
|
+
if (mutateOptions.rollbackOnError !== false) dataSig.set(previous);
|
|
173
|
+
errorSig.set(err);
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
abort() {
|
|
177
|
+
seq++;
|
|
178
|
+
controller?.abort();
|
|
179
|
+
controller = void 0;
|
|
180
|
+
loadingSig.set(false);
|
|
181
|
+
if (statusSig.peek() === "loading") statusSig.set("idle");
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
onCleanup?.(() => resource.abort());
|
|
185
|
+
if (immediate !== false) void refetch();
|
|
186
|
+
return resource;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/net/directive.ts
|
|
190
|
+
function makeResourceDirective(client) {
|
|
191
|
+
return (el, meta, utils) => {
|
|
192
|
+
const name = meta.value;
|
|
193
|
+
if (!name) {
|
|
194
|
+
console.warn(`[summit] s-resource needs a name, e.g. s-resource:users="{ url: '/api/users' }".`, el);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const scope = utils.scopes[utils.scopes.length - 1];
|
|
198
|
+
let resource;
|
|
199
|
+
let timer;
|
|
200
|
+
const readConfig = () => utils.evaluate(meta.expression) || {};
|
|
201
|
+
utils.effect(() => {
|
|
202
|
+
const cfg = readConfig();
|
|
203
|
+
if (!cfg.url) return;
|
|
204
|
+
const req = { params: cfg.params, headers: cfg.headers, method: cfg.method, body: cfg.body, timeout: cfg.timeout };
|
|
205
|
+
if (!resource) {
|
|
206
|
+
resource = createResource(client, () => readConfig().url ?? cfg.url, {
|
|
207
|
+
...req,
|
|
208
|
+
immediate: cfg.immediate !== false,
|
|
209
|
+
onCleanup: utils.cleanup
|
|
210
|
+
});
|
|
211
|
+
if (scope) scope[name] = resource;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const run = () => resource.refetch(req);
|
|
215
|
+
if (cfg.debounce) {
|
|
216
|
+
if (timer) clearTimeout(timer);
|
|
217
|
+
timer = setTimeout(run, cfg.debounce);
|
|
218
|
+
} else {
|
|
219
|
+
run();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
utils.cleanup(() => {
|
|
223
|
+
if (timer) clearTimeout(timer);
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/net/index.ts
|
|
229
|
+
var http = createClient();
|
|
230
|
+
function bindClient(client, ctx) {
|
|
231
|
+
return {
|
|
232
|
+
...client,
|
|
233
|
+
resource(url, options = {}) {
|
|
234
|
+
return createResource(client, url, { ...options, onCleanup: options.onCleanup ?? ctx.cleanup });
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function register(summit, client) {
|
|
239
|
+
summit.magic("fetch", (ctx) => bindClient(client, ctx));
|
|
240
|
+
summit.directive("resource", makeResourceDirective(client));
|
|
241
|
+
}
|
|
242
|
+
function net(summit) {
|
|
243
|
+
register(summit, http);
|
|
244
|
+
}
|
|
245
|
+
function createNet(options) {
|
|
246
|
+
const client = createClient(options);
|
|
247
|
+
return (summit) => register(summit, client);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export { NetError, createClient, createNet, createResource, http, net };
|
|
251
|
+
//# sourceMappingURL=net.js.map
|
|
252
|
+
//# sourceMappingURL=net.js.map
|
package/dist/net.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/net/client.ts","../src/net/resource.ts","../src/net/directive.ts","../src/net/index.ts"],"names":[],"mappings":"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","file":"net.js","sourcesContent":["/**\n * summitjs/net — the HTTP client.\n *\n * A thin, dependency-free wrapper over `fetch` that fixes fetch's real-world\n * footguns: it throws a structured error on non-2xx, sends and parses JSON\n * automatically, serializes params, supports a baseURL, default headers,\n * per-request timeout, and cancellation. No eval, no codegen, so it stays\n * CSP-safe like the rest of Summit.\n */\n\n/** A structured error thrown for non-2xx responses, timeouts, and aborts. */\nexport class NetError extends Error {\n status: number;\n body: unknown;\n url: string;\n method: string;\n aborted: boolean;\n\n constructor(\n message: string,\n init: { status: number; body: unknown; url: string; method: string; aborted: boolean },\n ) {\n super(message);\n this.name = \"NetError\";\n this.status = init.status;\n this.body = init.body;\n this.url = init.url;\n this.method = init.method;\n this.aborted = init.aborted;\n }\n}\n\nexport type NetParams = Record<string, string | number | boolean | null | undefined>;\n\nexport interface NetRequest {\n method?: string;\n headers?: Record<string, string>;\n params?: NetParams;\n /** A value: objects/arrays are JSON-encoded; strings are sent as-is. */\n body?: unknown;\n /** Abort this request when the given signal aborts. */\n signal?: AbortSignal;\n /** Abort automatically after this many milliseconds. */\n timeout?: number;\n}\n\nexport interface NetClientOptions {\n baseURL?: string;\n /** Static headers, or a function evaluated per request (e.g. for auth tokens). */\n headers?: Record<string, string> | (() => Record<string, string>);\n timeout?: number;\n /** Inspect or replace the request just before it is sent. */\n beforeRequest?: (req: NetInit) => void | NetInit;\n /** Inspect or replace the response before the body is read. */\n afterResponse?: (res: Response) => void | Response;\n /** Called with every NetError (e.g. redirect on 401). Re-throws afterward. */\n onError?: (err: NetError) => void;\n}\n\nexport type NetInit = RequestInit & { url: string };\n\nexport interface NetClient {\n request<T = unknown>(path: string, options?: NetRequest): Promise<T>;\n get<T = unknown>(path: string, options?: NetRequest): Promise<T>;\n post<T = unknown>(path: string, body?: unknown, options?: NetRequest): Promise<T>;\n put<T = unknown>(path: string, body?: unknown, options?: NetRequest): Promise<T>;\n patch<T = unknown>(path: string, body?: unknown, options?: NetRequest): Promise<T>;\n delete<T = unknown>(path: string, options?: NetRequest): Promise<T>;\n /** The client's live defaults; mutate to reconfigure baseURL/headers at runtime. */\n options: NetClientOptions;\n}\n\nexport function createClient(defaults: NetClientOptions = {}): NetClient {\n async function request<T = unknown>(path: string, req: NetRequest = {}): Promise<T> {\n const controller = new AbortController();\n const timeoutMs = req.timeout ?? defaults.timeout;\n const timer = timeoutMs ? setTimeout(() => controller.abort(), timeoutMs) : undefined;\n // Chain an external signal onto our controller so both timeout and the\n // caller's own cancellation abort the same request.\n if (req.signal) {\n if (req.signal.aborted) controller.abort();\n else req.signal.addEventListener(\"abort\", () => controller.abort(), { once: true });\n }\n\n const method = (req.method ?? \"GET\").toUpperCase();\n const url = withParams(joinURL(defaults.baseURL, path), req.params);\n const baseHeaders = typeof defaults.headers === \"function\" ? defaults.headers() : defaults.headers;\n const headers: Record<string, string> = { ...baseHeaders, ...req.headers };\n\n let init: NetInit = { url, method, headers, signal: controller.signal };\n if (req.body !== undefined && method !== \"GET\" && method !== \"HEAD\") {\n if (!hasHeader(headers, \"content-type\") && typeof req.body !== \"string\") {\n headers[\"content-type\"] = \"application/json\";\n }\n init.body = typeof req.body === \"string\" ? req.body : JSON.stringify(req.body);\n }\n\n const patched = defaults.beforeRequest?.(init);\n if (patched) init = patched;\n\n try {\n let res = await fetch(init.url, init);\n const swapped = defaults.afterResponse?.(res);\n if (swapped) res = swapped;\n const body = await readBody(res);\n if (!res.ok) {\n throw new NetError(`Request failed with status ${res.status}`, {\n status: res.status,\n body,\n url: init.url,\n method,\n aborted: false,\n });\n }\n return body as T;\n } catch (err) {\n const e = normalizeError(err, init.url, method);\n defaults.onError?.(e);\n throw e;\n } finally {\n if (timer) clearTimeout(timer);\n }\n }\n\n const client: NetClient = {\n request,\n get: (path, options) => request(path, { ...options, method: \"GET\" }),\n post: (path, body, options) => request(path, { ...options, method: \"POST\", body }),\n put: (path, body, options) => request(path, { ...options, method: \"PUT\", body }),\n patch: (path, body, options) => request(path, { ...options, method: \"PATCH\", body }),\n delete: (path, options) => request(path, { ...options, method: \"DELETE\" }),\n options: defaults,\n };\n return client;\n}\n\nfunction normalizeError(err: unknown, url: string, method: string): NetError {\n if (err instanceof NetError) return err;\n const aborted = (err as { name?: string })?.name === \"AbortError\";\n return new NetError(aborted ? \"Request aborted\" : (err as Error)?.message ?? \"Network request failed\", {\n status: 0,\n body: undefined,\n url,\n method,\n aborted,\n });\n}\n\nasync function readBody(res: Response): Promise<unknown> {\n const type = res.headers.get(\"content-type\") ?? \"\";\n const text = await res.text();\n if (text === \"\") return undefined;\n if (type.includes(\"application/json\") || type.includes(\"+json\")) {\n try {\n return JSON.parse(text);\n } catch {\n return text; // malformed JSON: hand back the raw text rather than throwing\n }\n }\n return text;\n}\n\nfunction hasHeader(headers: Record<string, string>, name: string): boolean {\n const lower = name.toLowerCase();\n return Object.keys(headers).some((k) => k.toLowerCase() === lower);\n}\n\nfunction joinURL(base: string | undefined, path: string): string {\n if (!base || /^[a-z][a-z0-9+.-]*:\\/\\//i.test(path)) return path;\n return `${base.replace(/\\/+$/, \"\")}/${path.replace(/^\\/+/, \"\")}`;\n}\n\nfunction withParams(url: string, params?: NetParams): string {\n if (!params) return url;\n const query = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value != null) query.set(key, String(value));\n }\n const suffix = query.toString();\n if (!suffix) return url;\n return `${url}${url.includes(\"?\") ? \"&\" : \"?\"}${suffix}`;\n}\n","/**\n * summitjs/net — the reactive resource.\n *\n * A resource wraps a request in Summit signals: `data`, `error`, `loading`,\n * and `status` are exposed as auto-unwrapping getters, so reading them inside a\n * directive (`s-text=\"user.data.name\"`, `s-show=\"user.loading\"`) subscribes the\n * DOM to exactly that field. This is the key win over a promise wrapper: the\n * view updates itself.\n *\n * It also closes the footguns the stress test found:\n * - out-of-order responses: a monotonic sequence means the latest wins;\n * - non-2xx: surfaces through `error` (the client already throws);\n * - unmount leaks: `onCleanup` aborts the in-flight request;\n * - unhandled rejections: refetch never rejects — failures land in `error`.\n */\n\n// Import the signal factory from the public entry (not a relative core path) so\n// the net bundle shares Summit's ONE reactivity runtime instead of bundling a\n// second copy — otherwise resource signals would not drive the core's DOM.\nimport { signal } from \"summitjs\";\nimport type { NetClient, NetError, NetRequest } from \"./client.js\";\n\nexport type ResourceStatus = \"idle\" | \"loading\" | \"success\" | \"error\";\n\nexport interface ResourceOptions extends NetRequest {\n /** Fetch immediately on creation. Default true. */\n immediate?: boolean;\n /** Register a teardown callback (Summit passes the element's cleanup here). */\n onCleanup?: (fn: () => void) => void;\n}\n\nexport interface MutateOptions {\n /** Persist the optimistic change; on rejection the previous value is restored. */\n request?: () => Promise<unknown>;\n /** Roll back on a failed request. Default true. */\n rollbackOnError?: boolean;\n}\n\nexport interface Resource<T> {\n readonly data: T | undefined;\n readonly error: NetError | undefined;\n readonly loading: boolean;\n readonly status: ResourceStatus;\n /** Re-run the request (optionally overriding params/headers). Never rejects. */\n refetch(overrides?: NetRequest): Promise<T | undefined>;\n /** Optimistically set data; with `request`, roll back if the request fails. */\n mutate(next: T | ((prev: T | undefined) => T), options?: MutateOptions): void;\n /** Cancel the in-flight request and return to idle. */\n abort(): void;\n}\n\nexport function createResource<T = unknown>(\n client: NetClient,\n url: string | (() => string),\n options: ResourceOptions = {},\n): Resource<T> {\n const dataSig = signal<T | undefined>(undefined);\n const errorSig = signal<NetError | undefined>(undefined);\n const loadingSig = signal(false);\n const statusSig = signal<ResourceStatus>(\"idle\");\n\n const { immediate, onCleanup, ...requestDefaults } = options;\n let seq = 0;\n let controller: AbortController | undefined;\n\n async function refetch(overrides: NetRequest = {}): Promise<T | undefined> {\n const current = ++seq;\n controller?.abort();\n controller = new AbortController();\n loadingSig.set(true);\n statusSig.set(\"loading\");\n errorSig.set(undefined);\n try {\n const target = typeof url === \"function\" ? url() : url;\n const result = await client.request<T>(target, {\n ...requestDefaults,\n ...overrides,\n signal: controller.signal,\n });\n if (current !== seq) return undefined; // a newer request superseded this one\n dataSig.set(result);\n statusSig.set(\"success\");\n return result;\n } catch (err) {\n if (current !== seq) return undefined; // stale failure, ignore\n const e = err as NetError;\n if (e.aborted) {\n statusSig.set(\"idle\");\n return undefined;\n }\n errorSig.set(e);\n statusSig.set(\"error\");\n return undefined;\n } finally {\n if (current === seq) loadingSig.set(false);\n }\n }\n\n const resource: Resource<T> = {\n get data() {\n return dataSig();\n },\n get error() {\n return errorSig();\n },\n get loading() {\n return loadingSig();\n },\n get status() {\n return statusSig();\n },\n refetch,\n mutate(next, mutateOptions = {}) {\n const previous = dataSig.peek();\n dataSig.set((prev) => (typeof next === \"function\" ? (next as (p: T | undefined) => T)(prev) : next));\n if (!mutateOptions.request) return;\n // Persist the optimistic change; roll back and surface the error on failure.\n void Promise.resolve()\n .then(mutateOptions.request)\n .catch((err) => {\n if (mutateOptions.rollbackOnError !== false) dataSig.set(previous);\n errorSig.set(err as NetError);\n });\n },\n abort() {\n seq++;\n controller?.abort();\n controller = undefined;\n loadingSig.set(false);\n if (statusSig.peek() === \"loading\") statusSig.set(\"idle\");\n },\n };\n\n onCleanup?.(() => resource.abort());\n if (immediate !== false) void refetch();\n return resource;\n}\n","/**\n * summitjs/net — the `s-resource` directive.\n *\n * <div s-resource:users=\"{ url: '/api/users', immediate: true }\">\n * <p s-show=\"users.loading\">Loading…</p>\n * <p s-show=\"users.error\" s-text=\"users.error.message\"></p>\n * <template s-for=\"u in users.data || []\" :key=\"u.id\"><li s-text=\"u.name\"></li></template>\n * </div>\n *\n * The config expression is evaluated reactively: when a value it reads changes\n * (e.g. a `params: { q }` bound to state), the resource refetches with the new\n * params, and latest-wins cancellation makes stale responses harmless. An\n * optional `debounce` throttles those refetches for search-as-you-type.\n */\n\nimport type { DirectiveHandler } from \"summitjs\";\nimport { createResource, type Resource } from \"./resource.js\";\nimport type { NetClient, NetParams } from \"./client.js\";\n\ninterface ResourceConfig {\n url?: string;\n params?: NetParams;\n headers?: Record<string, string>;\n method?: string;\n body?: unknown;\n immediate?: boolean;\n debounce?: number;\n timeout?: number;\n}\n\nexport function makeResourceDirective(client: NetClient): DirectiveHandler {\n return (el, meta, utils) => {\n const name = meta.value;\n if (!name) {\n console.warn(\"[summit] s-resource needs a name, e.g. s-resource:users=\\\"{ url: '/api/users' }\\\".\", el);\n return;\n }\n\n const scope = utils.scopes[utils.scopes.length - 1] as Record<string, unknown> | undefined;\n let resource: Resource<unknown> | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const readConfig = (): ResourceConfig => (utils.evaluate(meta.expression) || {}) as ResourceConfig;\n\n utils.effect(() => {\n const cfg = readConfig(); // subscribes to reactive params (q, page, …)\n if (!cfg.url) return;\n const req = { params: cfg.params, headers: cfg.headers, method: cfg.method, body: cfg.body, timeout: cfg.timeout };\n\n if (!resource) {\n resource = createResource(client, () => readConfig().url ?? cfg.url!, {\n ...req,\n immediate: cfg.immediate !== false,\n onCleanup: utils.cleanup,\n });\n if (scope) scope[name] = resource;\n return;\n }\n\n // Config changed after creation: refetch with the new request options,\n // debounced when asked. Latest-wins means out-of-order replies are safe.\n const run = () => resource!.refetch(req);\n if (cfg.debounce) {\n if (timer) clearTimeout(timer);\n timer = setTimeout(run, cfg.debounce);\n } else {\n run();\n }\n });\n\n utils.cleanup(() => {\n if (timer) clearTimeout(timer);\n });\n };\n}\n","/**\n * summitjs/net — a tiny reactive data layer for Summit.\n *\n * Optional, shipped as its own entry so the core stays lean. Use it either\n * imperatively (`createClient`, `createResource`) or through Summit by\n * registering the plugin, which adds the `$fetch` magic and the `s-resource`\n * directive:\n *\n * import Summit from \"summitjs\";\n * import { net } from \"summitjs/net\";\n * Summit.plugin(net); // or Summit.plugin(createNet({ baseURL: \"/api\" }))\n * Summit.start();\n */\n\nimport type { SummitGlobal, MagicContext } from \"summitjs\";\nimport { createClient, NetError } from \"./client.js\";\nimport type { NetClient, NetClientOptions, NetRequest, NetParams, NetInit } from \"./client.js\";\nimport { createResource } from \"./resource.js\";\nimport type { Resource, ResourceOptions, ResourceStatus, MutateOptions } from \"./resource.js\";\nimport { makeResourceDirective } from \"./directive.js\";\n\nexport { createClient, createResource, NetError };\nexport type {\n NetClient,\n NetClientOptions,\n NetRequest,\n NetParams,\n NetInit,\n Resource,\n ResourceOptions,\n ResourceStatus,\n MutateOptions,\n};\n\n/** The default client shared by the `$fetch` magic and `s-resource` directive. */\nexport const http: NetClient = createClient();\n\n/** A client bound to an element: resources it creates auto-abort on unmount. */\nexport interface BoundClient extends NetClient {\n resource<T = unknown>(url: string | (() => string), options?: ResourceOptions): Resource<T>;\n}\n\nfunction bindClient(client: NetClient, ctx: MagicContext): BoundClient {\n return {\n ...client,\n resource(url, options = {}) {\n return createResource(client, url, { ...options, onCleanup: options.onCleanup ?? ctx.cleanup });\n },\n };\n}\n\nfunction register(summit: SummitGlobal, client: NetClient): void {\n summit.magic(\"fetch\", (ctx) => bindClient(client, ctx as MagicContext));\n summit.directive(\"resource\", makeResourceDirective(client));\n}\n\n/** Ready-to-use plugin backed by the default client: `Summit.plugin(net)`. */\nexport function net(summit: SummitGlobal): void {\n register(summit, http);\n}\n\n/** A configured plugin: `Summit.plugin(createNet({ baseURL: \"/api\" }))`. */\nexport function createNet(options: NetClientOptions): (summit: SummitGlobal) => void {\n const client = createClient(options);\n return (summit) => register(summit, client);\n}\n"]}
|
package/dist/summit.cjs
CHANGED
|
@@ -343,6 +343,35 @@ function createScope(raw) {
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
// src/evaluator/lexer.ts
|
|
346
|
+
var REGEX_PRECEDING_KEYWORDS = /* @__PURE__ */ new Set([
|
|
347
|
+
"return",
|
|
348
|
+
"typeof",
|
|
349
|
+
"void",
|
|
350
|
+
"in",
|
|
351
|
+
"instanceof",
|
|
352
|
+
"new",
|
|
353
|
+
"of",
|
|
354
|
+
"delete",
|
|
355
|
+
"do",
|
|
356
|
+
"else"
|
|
357
|
+
]);
|
|
358
|
+
function regexAllowed(prev) {
|
|
359
|
+
if (!prev) return true;
|
|
360
|
+
switch (prev.type) {
|
|
361
|
+
case "num":
|
|
362
|
+
case "str":
|
|
363
|
+
case "tmpl":
|
|
364
|
+
case "regex":
|
|
365
|
+
return false;
|
|
366
|
+
// a value precedes -> division
|
|
367
|
+
case "ident":
|
|
368
|
+
return REGEX_PRECEDING_KEYWORDS.has(prev.value);
|
|
369
|
+
case "punc":
|
|
370
|
+
return prev.value !== ")" && prev.value !== "]";
|
|
371
|
+
default:
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
346
375
|
var PUNCTUATORS = [
|
|
347
376
|
"...",
|
|
348
377
|
"===",
|
|
@@ -489,6 +518,35 @@ function tokenize(input) {
|
|
|
489
518
|
tokens2.push({ type: "ident", value: input.slice(start2, i), start: start2, end: i });
|
|
490
519
|
continue;
|
|
491
520
|
}
|
|
521
|
+
if (ch === "/" && regexAllowed(tokens2[tokens2.length - 1])) {
|
|
522
|
+
const start2 = i;
|
|
523
|
+
i++;
|
|
524
|
+
let body = "";
|
|
525
|
+
let inClass = false;
|
|
526
|
+
while (i < n) {
|
|
527
|
+
const c = input[i];
|
|
528
|
+
if (c === "\\") {
|
|
529
|
+
body += c + (input[i + 1] ?? "");
|
|
530
|
+
i += 2;
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
if (c === "\n") throw new LexError(`Unterminated regex in expression: ${input}`);
|
|
534
|
+
if (c === "[") inClass = true;
|
|
535
|
+
else if (c === "]") inClass = false;
|
|
536
|
+
else if (c === "/" && !inClass) break;
|
|
537
|
+
body += c;
|
|
538
|
+
i++;
|
|
539
|
+
}
|
|
540
|
+
if (input[i] !== "/") throw new LexError(`Unterminated regex in expression: ${input}`);
|
|
541
|
+
i++;
|
|
542
|
+
let flags = "";
|
|
543
|
+
while (i < n && /[a-z]/i.test(input[i])) {
|
|
544
|
+
flags += input[i];
|
|
545
|
+
i++;
|
|
546
|
+
}
|
|
547
|
+
tokens2.push({ type: "regex", value: body, flags, start: start2, end: i });
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
492
550
|
let matched = false;
|
|
493
551
|
for (const p of PUNCTUATORS) {
|
|
494
552
|
if (input.startsWith(p, i)) {
|
|
@@ -832,8 +890,34 @@ var Parser = class {
|
|
|
832
890
|
}
|
|
833
891
|
return node;
|
|
834
892
|
}
|
|
893
|
+
parseNewExpr() {
|
|
894
|
+
this.pos++;
|
|
895
|
+
let callee = this.parsePrimary();
|
|
896
|
+
for (; ; ) {
|
|
897
|
+
if (this.isPunc(".")) {
|
|
898
|
+
this.pos++;
|
|
899
|
+
const name = this.next();
|
|
900
|
+
callee = {
|
|
901
|
+
type: "MemberExpression",
|
|
902
|
+
object: callee,
|
|
903
|
+
property: { type: "Identifier", name: name.value },
|
|
904
|
+
computed: false,
|
|
905
|
+
optional: false
|
|
906
|
+
};
|
|
907
|
+
} else if (this.isPunc("[")) {
|
|
908
|
+
this.pos++;
|
|
909
|
+
const property = this.parseExpression();
|
|
910
|
+
this.eatPunc("]");
|
|
911
|
+
callee = { type: "MemberExpression", object: callee, property, computed: true, optional: false };
|
|
912
|
+
} else {
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
const args = this.isPunc("(") ? this.parseArguments() : [];
|
|
917
|
+
return { type: "NewExpression", callee, arguments: args };
|
|
918
|
+
}
|
|
835
919
|
parseCallMember() {
|
|
836
|
-
let node = this.parsePrimary();
|
|
920
|
+
let node = this.isKeyword("new") ? this.parseNewExpr() : this.parsePrimary();
|
|
837
921
|
for (; ; ) {
|
|
838
922
|
if (this.isPunc(".")) {
|
|
839
923
|
this.pos++;
|
|
@@ -908,6 +992,10 @@ var Parser = class {
|
|
|
908
992
|
this.pos++;
|
|
909
993
|
return this.parseTemplate(t.raw ?? "");
|
|
910
994
|
}
|
|
995
|
+
if (t.type === "regex") {
|
|
996
|
+
this.pos++;
|
|
997
|
+
return { type: "RegexLiteral", pattern: t.value, flags: t.flags ?? "" };
|
|
998
|
+
}
|
|
911
999
|
if (t.type === "ident") {
|
|
912
1000
|
if (t.value === "true" || t.value === "false") {
|
|
913
1001
|
this.pos++;
|
|
@@ -989,6 +1077,11 @@ var Parser = class {
|
|
|
989
1077
|
return { type: "ObjectExpression", properties };
|
|
990
1078
|
}
|
|
991
1079
|
parseObjectProperty() {
|
|
1080
|
+
if (this.isKeyword("async") && this.peek(1).type === "ident") {
|
|
1081
|
+
throw new ParseError(
|
|
1082
|
+
"async methods are not supported in Summit expressions. Use a regular method that returns a Promise and chain .then()."
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
992
1085
|
if ((this.isKeyword("get") || this.isKeyword("set")) && !this.isPunc(":", 1) && !this.isPunc(",", 1) && !this.isPunc("(", 1) && !this.isPunc("}", 1)) {
|
|
993
1086
|
const kind = this.next().value;
|
|
994
1087
|
const { key: key2, computed: computed3 } = this.parsePropertyKey();
|
|
@@ -1272,6 +1365,10 @@ var GLOBAL_ALLOW = /* @__PURE__ */ new Set([
|
|
|
1272
1365
|
"requestAnimationFrame",
|
|
1273
1366
|
"cancelAnimationFrame",
|
|
1274
1367
|
"fetch",
|
|
1368
|
+
"AbortController",
|
|
1369
|
+
"Headers",
|
|
1370
|
+
"Request",
|
|
1371
|
+
"Response",
|
|
1275
1372
|
"alert",
|
|
1276
1373
|
"confirm",
|
|
1277
1374
|
"prompt",
|
|
@@ -1399,6 +1496,8 @@ var Interpreter = class {
|
|
|
1399
1496
|
return null;
|
|
1400
1497
|
case "UndefinedLiteral":
|
|
1401
1498
|
return void 0;
|
|
1499
|
+
case "RegexLiteral":
|
|
1500
|
+
return new RegExp(node.pattern, node.flags);
|
|
1402
1501
|
case "ThisExpression":
|
|
1403
1502
|
return thisVal;
|
|
1404
1503
|
case "Identifier":
|
|
@@ -1468,6 +1567,14 @@ var Interpreter = class {
|
|
|
1468
1567
|
}
|
|
1469
1568
|
case "CallExpression":
|
|
1470
1569
|
return this.evalCall(node, scope, thisVal);
|
|
1570
|
+
case "NewExpression": {
|
|
1571
|
+
const ctor = this.evalExpr(node.callee, scope, thisVal);
|
|
1572
|
+
const args = this.evalArguments(node.arguments, scope, thisVal);
|
|
1573
|
+
if (typeof ctor !== "function") {
|
|
1574
|
+
throw new TypeError("Expression value is not a constructor");
|
|
1575
|
+
}
|
|
1576
|
+
return Reflect.construct(ctor, args);
|
|
1577
|
+
}
|
|
1471
1578
|
case "ArrowFunction":
|
|
1472
1579
|
return this.makeFunction(
|
|
1473
1580
|
node,
|
|
@@ -1709,6 +1816,10 @@ function evaluateAction(source, root, thisVal) {
|
|
|
1709
1816
|
const program = parse(source, "program");
|
|
1710
1817
|
return new Interpreter(root).run(program, thisVal);
|
|
1711
1818
|
}
|
|
1819
|
+
function compileExpression(source) {
|
|
1820
|
+
const program = parse(source, "expression");
|
|
1821
|
+
return (root, thisVal) => new Interpreter(root).run(program, thisVal);
|
|
1822
|
+
}
|
|
1712
1823
|
|
|
1713
1824
|
// src/errors.ts
|
|
1714
1825
|
var DOCS = "https://velofy.github.io/summit";
|
|
@@ -2047,15 +2158,26 @@ function initData(el, dmeta, parentScopes) {
|
|
|
2047
2158
|
const parentEnv = makeEnv(el);
|
|
2048
2159
|
const expr = dmeta.expression.trim();
|
|
2049
2160
|
let raw;
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2161
|
+
try {
|
|
2162
|
+
const match = expr.match(DATA_PROVIDER_RE);
|
|
2163
|
+
if (match && getData(match[1])) {
|
|
2164
|
+
const provider = getData(match[1]);
|
|
2165
|
+
const args = match[2] ? parentEnv.evaluate("[" + (match[3] ?? "") + "]") : [];
|
|
2166
|
+
raw = provider(...args);
|
|
2167
|
+
} else if (expr === "") {
|
|
2168
|
+
raw = {};
|
|
2169
|
+
} else {
|
|
2170
|
+
raw = parentEnv.evaluate("(" + expr + ")");
|
|
2171
|
+
}
|
|
2172
|
+
} catch (err) {
|
|
2173
|
+
fail("E104", "s-data could not be evaluated; the component starts with empty state.", {
|
|
2174
|
+
el,
|
|
2175
|
+
expression: expr,
|
|
2176
|
+
doc: "s-data",
|
|
2177
|
+
hint: "Check the object for unsupported syntax, e.g. an async method.",
|
|
2178
|
+
cause: err
|
|
2179
|
+
});
|
|
2056
2180
|
raw = {};
|
|
2057
|
-
} else {
|
|
2058
|
-
raw = parentEnv.evaluate("(" + expr + ")");
|
|
2059
2181
|
}
|
|
2060
2182
|
if (raw === null || typeof raw !== "object") raw = {};
|
|
2061
2183
|
const scope = createScope(raw);
|
|
@@ -2775,11 +2897,13 @@ var sFor = (el, meta2, utils) => {
|
|
|
2775
2897
|
el.remove();
|
|
2776
2898
|
const parentScopes = utils.scopes;
|
|
2777
2899
|
let blocks = /* @__PURE__ */ new Map();
|
|
2778
|
-
const
|
|
2779
|
-
|
|
2780
|
-
|
|
2900
|
+
const keyEval = keyExpr ? compileExpression(keyExpr) : null;
|
|
2901
|
+
const keyFor = (it) => {
|
|
2902
|
+
if (!keyEval) return it.objectKey ?? it.index;
|
|
2903
|
+
const data = { [itemName]: it.value };
|
|
2904
|
+
if (indexName) data[indexName] = it.objectKey ?? it.index;
|
|
2781
2905
|
try {
|
|
2782
|
-
return
|
|
2906
|
+
return keyEval(makeEnv(el, data).env);
|
|
2783
2907
|
} catch {
|
|
2784
2908
|
return it.index;
|
|
2785
2909
|
}
|
|
@@ -2801,8 +2925,7 @@ var sFor = (el, meta2, utils) => {
|
|
|
2801
2925
|
const next = /* @__PURE__ */ new Map();
|
|
2802
2926
|
const orderedKeys = [];
|
|
2803
2927
|
for (const it of items) {
|
|
2804
|
-
|
|
2805
|
-
const key = keyFor(it, scope);
|
|
2928
|
+
const key = keyFor(it);
|
|
2806
2929
|
orderedKeys.push(key);
|
|
2807
2930
|
const existing = blocks.get(key);
|
|
2808
2931
|
if (existing) {
|
|
@@ -2811,7 +2934,9 @@ var sFor = (el, meta2, utils) => {
|
|
|
2811
2934
|
next.set(key, existing);
|
|
2812
2935
|
blocks.delete(key);
|
|
2813
2936
|
} else {
|
|
2937
|
+
const scope = makeBlockScope(it);
|
|
2814
2938
|
const node = blueprint.cloneNode(true);
|
|
2939
|
+
parent.insertBefore(node, anchor2);
|
|
2815
2940
|
utils.initTree(node, [...parentScopes, scope]);
|
|
2816
2941
|
next.set(key, { node, scope });
|
|
2817
2942
|
}
|
|
@@ -2820,9 +2945,13 @@ var sFor = (el, meta2, utils) => {
|
|
|
2820
2945
|
utils.destroyTree(block.node);
|
|
2821
2946
|
block.node.remove();
|
|
2822
2947
|
}
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2948
|
+
let expected = anchor2;
|
|
2949
|
+
for (let i = orderedKeys.length - 1; i >= 0; i--) {
|
|
2950
|
+
const node = next.get(orderedKeys[i]).node;
|
|
2951
|
+
if (node.nextSibling !== expected) {
|
|
2952
|
+
parent.insertBefore(node, expected);
|
|
2953
|
+
}
|
|
2954
|
+
expected = node;
|
|
2826
2955
|
}
|
|
2827
2956
|
blocks = next;
|
|
2828
2957
|
});
|