@reactivescript/runtime 0.0.0-stage → 0.1.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 +35 -2
- package/dist/server.d.ts +1 -0
- package/dist/server.js +470 -0
- package/dist/types/rt-server.d.ts +161 -0
- package/dist/types/rt-web.d.ts +186 -0
- package/dist/web.d.ts +1 -0
- package/dist/web.js +615 -0
- package/package.json +62 -4
package/README.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @reactivescript/runtime
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
ReactiveScript's runtime (spec/11 CP-4a): `createApp` and the `__rt` helpers
|
|
4
|
+
that compiled `.rx` calls, over Solid. Each helper is a Solid primitive, a
|
|
5
|
+
composition of them, or plain JavaScript; nothing in it adds run-time
|
|
6
|
+
behaviour beyond Solid.
|
|
7
|
+
|
|
8
|
+
There is a browser build and a server build, chosen by export condition as
|
|
9
|
+
`@solidjs/web` chooses (`browser` → the browser build; `node`, `deno`,
|
|
10
|
+
`worker` → the server build; `./web` and `./server` name one explicitly).
|
|
11
|
+
Both create the same owners in the same order, so a page the server streamed
|
|
12
|
+
hydrates (spec/10).
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
import { createApp } from "@reactivescript/runtime";
|
|
16
|
+
import main from "./app.rx"; // compiled by @reactivescript/compiler (Solid's Vite plugin loads it)
|
|
17
|
+
|
|
18
|
+
const app = createApp({ root: document.getElementById("app") });
|
|
19
|
+
app.mount(main(app));
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Solid is a peer: `solid-js`, `@solidjs/web` and `@solidjs/signals` (the
|
|
23
|
+
runtime reads owner context from `@solidjs/signals`, which `solid-js` runs on;
|
|
24
|
+
with pnpm, add it to the app).
|
|
25
|
+
|
|
26
|
+
The source is the reference's runtime, `ref/compile/rt-web.ts` and
|
|
27
|
+
`rt-server.ts`, the one every scenario runs on; `npm run build` transpiles it
|
|
28
|
+
to `dist/` with its declarations. `npm test` checks both builds and renders
|
|
29
|
+
a program compiled by the crate on the server build (it needs the compiler's
|
|
30
|
+
Node-API build: `npm run build` in `../compiler`).
|
|
31
|
+
|
|
32
|
+
Not yet: the server build's `stream` takes no request (it provides a
|
|
33
|
+
placeholder one); a framework's request handling is to come.
|
|
34
|
+
|
|
35
|
+
Releases: as `@reactivescript/compiler`'s (`../compiler/README.md`,
|
|
36
|
+
Releasing), by the `runtime` workflow, tagged `runtime-v<version>`.
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./types/rt-server.js";
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
// ../ref/compile/rt-server.ts
|
|
2
|
+
import { Portal as WebPortal } from "@solidjs/web";
|
|
3
|
+
import { getObserver } from "solid-js";
|
|
4
|
+
import { provideRequestEvent } from "@solidjs/web/storage";
|
|
5
|
+
import { Errored, createComponent, createSignal, createMemo, createEffect, createOptimistic, createOptimisticStore, createStore, createProjection, createDeferred, untrack, latest, isPending, deep, NotReadyError, getOwner, runWithOwner, getNextChildId, runInServerComponentScope } from "solid-js";
|
|
6
|
+
import { escape, renderToStream, markSafeError, generateHydrationScript } from "@solidjs/web";
|
|
7
|
+
import { $TARGET } from "@solidjs/signals";
|
|
8
|
+
var readWhole = (v) => {
|
|
9
|
+
if (v !== null && typeof v === "object") Reflect.ownKeys(v);
|
|
10
|
+
return v;
|
|
11
|
+
};
|
|
12
|
+
var latestWhole = (v) => v !== null && typeof v === "object" && v[$TARGET] !== void 0 ? deep(v) : v;
|
|
13
|
+
var ERR = { toString: () => "#error" };
|
|
14
|
+
var PENDING_MARK = { toString: () => "#pending" };
|
|
15
|
+
var fmt = (v) => v === ERR ? "#error" : v === PENDING_MARK ? "#pending" : v === void 0 ? "undefined" : typeof v === "string" ? v : JSON.stringify(v);
|
|
16
|
+
var isNode = (v) => typeof v === "function" || typeof v === "object" && v !== null && "t" in v;
|
|
17
|
+
var isView = (v) => isNode(v) || Array.isArray(v) && (v.length === 0 || v.every((x) => x == null || typeof x === "string" || isView(x)) && v.some((x) => x != null && typeof x !== "string"));
|
|
18
|
+
var reserve = (n = 1) => {
|
|
19
|
+
const o = getOwner();
|
|
20
|
+
if (o?.id != null) for (let i = 0; i < n; i++) getNextChildId(o);
|
|
21
|
+
};
|
|
22
|
+
var onServer = (what) => () => {
|
|
23
|
+
throw new Error(`SV-2: ${what} does not run on the server`);
|
|
24
|
+
};
|
|
25
|
+
function errorClass(name, params, message, id) {
|
|
26
|
+
const C = class extends Error {
|
|
27
|
+
constructor(...args) {
|
|
28
|
+
super("");
|
|
29
|
+
this.name = name;
|
|
30
|
+
params.forEach((p, i) => {
|
|
31
|
+
this[p] = args[i];
|
|
32
|
+
});
|
|
33
|
+
this.message = String(untrack(() => message(...args)));
|
|
34
|
+
if (id) this.$class = id;
|
|
35
|
+
markSafeError(this);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(C, "name", { value: name });
|
|
39
|
+
Object.defineProperty(C, "declared", { value: true });
|
|
40
|
+
const f = ((...args) => new C(...args));
|
|
41
|
+
f.cls = C;
|
|
42
|
+
f.errorClass = name;
|
|
43
|
+
f.$class = id;
|
|
44
|
+
return f;
|
|
45
|
+
}
|
|
46
|
+
var PANIC = { panic: true };
|
|
47
|
+
var isA = (err, classes) => !classes || classes.includes(PANIC) || classes.some((c) => {
|
|
48
|
+
const k = c?.cls ?? c;
|
|
49
|
+
return typeof k === "function" && err instanceof k;
|
|
50
|
+
});
|
|
51
|
+
function catchAcc(f, match, alt) {
|
|
52
|
+
reserve(2);
|
|
53
|
+
return () => {
|
|
54
|
+
try {
|
|
55
|
+
return f();
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (err instanceof NotReadyError || !match(err)) throw err;
|
|
58
|
+
return alt(err);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function tryCatch(f, match, alt) {
|
|
63
|
+
try {
|
|
64
|
+
return f();
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err instanceof NotReadyError || !match(err)) throw err;
|
|
67
|
+
return alt(err);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function wireCopy(v, where, fail) {
|
|
71
|
+
if (v === null || v === void 0 || typeof v === "string" || typeof v === "number" || typeof v === "boolean") return v;
|
|
72
|
+
if (Array.isArray(v)) return v.map((x, i) => wireCopy(x, `${where}[${i}]`, fail));
|
|
73
|
+
if (typeof v === "object") {
|
|
74
|
+
const proto = Object.getPrototypeOf(v);
|
|
75
|
+
if (proto === Object.prototype || proto === null) {
|
|
76
|
+
const o = {};
|
|
77
|
+
for (const k of Object.keys(v)) o[k] = wireCopy(v[k], `${where}.${k}`, fail);
|
|
78
|
+
return o;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return fail(`${where} is ${typeof v === "function" ? "a function" : `an instance of ${v.constructor?.name ?? "a class"}`}, which the wire cannot carry: send data`);
|
|
82
|
+
}
|
|
83
|
+
var wireFailure = (err) => err instanceof Error && err.constructor.declared ? err : new Error("Internal Server Error");
|
|
84
|
+
var Flight = class {
|
|
85
|
+
key;
|
|
86
|
+
first;
|
|
87
|
+
settleFirst;
|
|
88
|
+
rejectFirst;
|
|
89
|
+
queue = [];
|
|
90
|
+
waiters = [];
|
|
91
|
+
started = false;
|
|
92
|
+
constructor(key) {
|
|
93
|
+
this.key = key;
|
|
94
|
+
this.first = new Promise((r, j) => {
|
|
95
|
+
this.settleFirst = r;
|
|
96
|
+
this.rejectFirst = j;
|
|
97
|
+
});
|
|
98
|
+
this.first.catch(() => {
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
push(x) {
|
|
102
|
+
const w = this.waiters.shift();
|
|
103
|
+
if (w) w(x);
|
|
104
|
+
else this.queue.push(x);
|
|
105
|
+
}
|
|
106
|
+
resolve(v) {
|
|
107
|
+
if (!this.started) {
|
|
108
|
+
this.started = true;
|
|
109
|
+
this.settleFirst(v);
|
|
110
|
+
}
|
|
111
|
+
this.push({ v });
|
|
112
|
+
}
|
|
113
|
+
reject(e) {
|
|
114
|
+
if (!this.started) {
|
|
115
|
+
this.started = true;
|
|
116
|
+
this.rejectFirst(e);
|
|
117
|
+
}
|
|
118
|
+
this.push({ e });
|
|
119
|
+
}
|
|
120
|
+
[Symbol.asyncIterator]() {
|
|
121
|
+
const next = () => new Promise((res, rej) => {
|
|
122
|
+
const take = (x) => {
|
|
123
|
+
if ("e" in x) rej(x.e);
|
|
124
|
+
else res({ value: x.v, done: false });
|
|
125
|
+
};
|
|
126
|
+
const q = this.queue.shift();
|
|
127
|
+
if (q) take(q);
|
|
128
|
+
else this.waiters.push(take);
|
|
129
|
+
});
|
|
130
|
+
return { next, return: async () => ({ value: void 0, done: true }) };
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
function createApp(opts = {}) {
|
|
134
|
+
const flights = /* @__PURE__ */ new Map();
|
|
135
|
+
const lastKey = /* @__PURE__ */ new Map();
|
|
136
|
+
const registry = /* @__PURE__ */ new Map();
|
|
137
|
+
const modules = /* @__PURE__ */ new Map();
|
|
138
|
+
const log = [];
|
|
139
|
+
const declared = [];
|
|
140
|
+
let currentEq = null;
|
|
141
|
+
let serverSeq = 0;
|
|
142
|
+
let classSeq = 0;
|
|
143
|
+
const jsErrors = /* @__PURE__ */ new Map();
|
|
144
|
+
const flight = (key) => {
|
|
145
|
+
let f = flights.get(key);
|
|
146
|
+
if (!f) {
|
|
147
|
+
f = new Flight(key);
|
|
148
|
+
flights.set(key, f);
|
|
149
|
+
}
|
|
150
|
+
if (currentEq) lastKey.set(currentEq, key);
|
|
151
|
+
return f;
|
|
152
|
+
};
|
|
153
|
+
const named = (name, fn) => name ? ((...a) => {
|
|
154
|
+
const was = currentEq;
|
|
155
|
+
currentEq = name;
|
|
156
|
+
try {
|
|
157
|
+
return fn(...a);
|
|
158
|
+
} finally {
|
|
159
|
+
currentEq = was;
|
|
160
|
+
}
|
|
161
|
+
}) : fn;
|
|
162
|
+
const ro = (get) => [get, { set: onServer("a write"), path: onServer("a write"), guess: onServer("a write") }];
|
|
163
|
+
const app = {
|
|
164
|
+
log,
|
|
165
|
+
flights,
|
|
166
|
+
lastKey,
|
|
167
|
+
asyncError: null,
|
|
168
|
+
/** Render `view` as a stream: `write` receives each chunk, `end` the close. */
|
|
169
|
+
/** One request (a request event, as Solid's integrations provide one — a server function called during the render runs
|
|
170
|
+
* in it, SF-2b). */
|
|
171
|
+
stream(view, sink) {
|
|
172
|
+
return provideRequestEvent({ request: new Request("http://127.0.0.1/"), locals: {} }, () => renderToStream(process.env.SV_SCOPE ? () => runInServerComponentScope(view) : view).pipe(sink));
|
|
173
|
+
},
|
|
174
|
+
B: {
|
|
175
|
+
len: (x) => Array.isArray(x) || typeof x === "string" ? x.length : typeof x === "object" && x ? Object.keys(x).length : 0,
|
|
176
|
+
str: (x) => fmt(x),
|
|
177
|
+
map: (xs, f) => xs.map(f),
|
|
178
|
+
filter: (xs, f) => xs.filter(f),
|
|
179
|
+
sum: (xs) => xs.reduce((a, b) => a + b, 0),
|
|
180
|
+
every: (xs, f) => xs.every(f),
|
|
181
|
+
indexOf: (xs, f) => xs.findIndex(f),
|
|
182
|
+
fail: (msg) => {
|
|
183
|
+
throw Object.assign(new Error(msg), { panic: true });
|
|
184
|
+
},
|
|
185
|
+
fetch: (key) => opts.api?.fetch ? opts.api.fetch(key) : flight(String(key)),
|
|
186
|
+
log: (...a) => {
|
|
187
|
+
log.push(`log(${a.map(fmt).join(", ")})`);
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
/** MO-8: a class a `.d.rx` declares — a failure the server sends as it is (SV-5). */
|
|
191
|
+
jsError(name) {
|
|
192
|
+
if (jsErrors.has(name)) return jsErrors.get(name);
|
|
193
|
+
const C = opts.api?.[name] ?? errorClass(name, ["message"], (m) => m ?? name, `${name}#${classSeq++}`);
|
|
194
|
+
declared.push(C);
|
|
195
|
+
jsErrors.set(name, C);
|
|
196
|
+
return C;
|
|
197
|
+
},
|
|
198
|
+
/** A JS function's promise: a rejection with a declared class is marked safe to send; any other is redacted (SV-5). */
|
|
199
|
+
api: (name) => {
|
|
200
|
+
if (!opts.api) return (...a) => flight(`${name}:${a.map(fmt).join(",")}`);
|
|
201
|
+
const f = opts.api[name] ?? (() => {
|
|
202
|
+
throw new Error(`./api has no ${name}`);
|
|
203
|
+
});
|
|
204
|
+
return (...a) => {
|
|
205
|
+
const r = f(...a);
|
|
206
|
+
return r && typeof r.then === "function" ? Promise.resolve(r).catch((e) => {
|
|
207
|
+
if (isA(e, declared)) markSafeError(e);
|
|
208
|
+
throw e;
|
|
209
|
+
}) : r;
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
reg(name, e) {
|
|
213
|
+
let l = registry.get(name);
|
|
214
|
+
if (!l) registry.set(name, l = []);
|
|
215
|
+
l.push(e);
|
|
216
|
+
},
|
|
217
|
+
dget(name) {
|
|
218
|
+
const e = app.lookup(name);
|
|
219
|
+
return e.get ? app.snapshot(e.get) : e.value;
|
|
220
|
+
},
|
|
221
|
+
lookup(name) {
|
|
222
|
+
const l = registry.get(name);
|
|
223
|
+
if (!l?.length) {
|
|
224
|
+
if (jsErrors.has(name)) return { value: jsErrors.get(name) };
|
|
225
|
+
throw new Error(`unbound ${name}`);
|
|
226
|
+
}
|
|
227
|
+
return l[l.length - 1];
|
|
228
|
+
},
|
|
229
|
+
// a server function's request has its module's error classes, not the page's registry
|
|
230
|
+
// ---- declarations: the same primitives, from the server build. Writes do not happen on the server (SV-2). ----
|
|
231
|
+
source(name, init) {
|
|
232
|
+
void name;
|
|
233
|
+
if (typeof init === "object" && init !== null) {
|
|
234
|
+
const [store] = createStore(init);
|
|
235
|
+
const [get2] = createSignal(store);
|
|
236
|
+
return ro(get2);
|
|
237
|
+
}
|
|
238
|
+
const [get] = createSignal(init);
|
|
239
|
+
return ro(get);
|
|
240
|
+
},
|
|
241
|
+
question(name, compute, o, mode, key) {
|
|
242
|
+
const fn = named(name, compute);
|
|
243
|
+
if (mode.optimistic && mode.structured) {
|
|
244
|
+
const [store] = createOptimisticStore((draft) => {
|
|
245
|
+
const r = fn(draft);
|
|
246
|
+
if (r instanceof Flight || r instanceof Promise) return (async () => assignDraft(draft, r instanceof Flight ? await r.first : await r))();
|
|
247
|
+
assignDraft(draft, r);
|
|
248
|
+
}, key ? [] : {}, key ? { key } : void 0);
|
|
249
|
+
return ro(() => store);
|
|
250
|
+
}
|
|
251
|
+
if (mode.optimistic) {
|
|
252
|
+
const [truth] = createSignal(fn, o);
|
|
253
|
+
const [get2] = createOptimistic(() => truth());
|
|
254
|
+
return ro(get2);
|
|
255
|
+
}
|
|
256
|
+
if (mode.structured) {
|
|
257
|
+
const [store] = createStore(async (draft) => {
|
|
258
|
+
const r = fn(draft);
|
|
259
|
+
const v = r instanceof Flight ? await r.first : await r;
|
|
260
|
+
if (v && typeof v === "object") {
|
|
261
|
+
for (const k of Object.keys(draft)) if (!(k in v)) delete draft[k];
|
|
262
|
+
Object.assign(draft, v);
|
|
263
|
+
}
|
|
264
|
+
}, {});
|
|
265
|
+
return ro(() => store);
|
|
266
|
+
}
|
|
267
|
+
const [get] = createSignal(fn, o);
|
|
268
|
+
return ro(get);
|
|
269
|
+
},
|
|
270
|
+
memo: (name, fn, o) => createMemo(named(name, fn), o),
|
|
271
|
+
deferred: (name, fn, o) => createDeferred(named(name, fn), o),
|
|
272
|
+
keyed: (name, key, fn) => {
|
|
273
|
+
const src = createMemo(named(name, () => fn()));
|
|
274
|
+
const p = createProjection(() => src(), [], { key });
|
|
275
|
+
return () => p;
|
|
276
|
+
},
|
|
277
|
+
draft: (seed, body) => {
|
|
278
|
+
const p = createProjection(body, seed);
|
|
279
|
+
return () => p;
|
|
280
|
+
},
|
|
281
|
+
pre: (get) => {
|
|
282
|
+
const m = createMemo((st) => ({ cur: get(), prev: st ? st.cur : void 0 }));
|
|
283
|
+
return () => m().prev;
|
|
284
|
+
},
|
|
285
|
+
firstPaint: (f) => ({ loadingValue: untrack(f) }),
|
|
286
|
+
/** SV-2: the apply half runs on the client, after hydration. The compute half is reactive scope — Solid's server runs it;
|
|
287
|
+
* an input's failure is an answer (E-12), not a server error. */
|
|
288
|
+
effect(_n, inputs, _apply) {
|
|
289
|
+
createEffect((() => {
|
|
290
|
+
try {
|
|
291
|
+
return inputs();
|
|
292
|
+
} catch (e) {
|
|
293
|
+
if (e instanceof NotReadyError) throw e;
|
|
294
|
+
return ERR;
|
|
295
|
+
}
|
|
296
|
+
}), { effect() {
|
|
297
|
+
}, error() {
|
|
298
|
+
} });
|
|
299
|
+
},
|
|
300
|
+
action(_gen) {
|
|
301
|
+
return onServer("an action");
|
|
302
|
+
},
|
|
303
|
+
awaited: (v) => v instanceof Flight ? v.first : v,
|
|
304
|
+
/** RG-8: Solid's Portal — client-only: the server renders nothing for it, the browser renders it once it has the page. */
|
|
305
|
+
Portal: WebPortal,
|
|
306
|
+
/** CSS-2: a dynamic class with the view's scope class after it. */
|
|
307
|
+
scopedClass: (v, hash) => v == null || v === false || v === "" ? hash : `${v} ${hash}`,
|
|
308
|
+
/** SV-7b: a client question — not asked on the server; the browser asks it once hydrated (Solid's \`ssrSource: "client"\`). */
|
|
309
|
+
clientQuestion: (o) => ({ ...o ?? {}, ssrSource: "client" }),
|
|
310
|
+
/** 12 SF-2: a server fn as the client calls it — every call a flight. Asked in reactive scope it is a question, one
|
|
311
|
+
* flight per arguments, as a JS import's; in imperative scope each call is a new request. \`fn\` is the module-level
|
|
312
|
+
* \`"use server"\` function (SF-7a): the body in place, or — once Solid's \`transformDirectives\` has made it a reference
|
|
313
|
+
* — a request to the server. */
|
|
314
|
+
serverRef(name, fn) {
|
|
315
|
+
return (...args) => {
|
|
316
|
+
const key = `${name}:${args.map(fmt).join(",")}`;
|
|
317
|
+
const question = !!getObserver();
|
|
318
|
+
if (question && flights.has(key)) return flights.get(key);
|
|
319
|
+
const fl = question ? flight(key) : new Flight(`${key}#${serverSeq++}`);
|
|
320
|
+
globalThis.__rx_app = app;
|
|
321
|
+
let p;
|
|
322
|
+
try {
|
|
323
|
+
p = Promise.resolve(fn(...args));
|
|
324
|
+
} catch (e) {
|
|
325
|
+
p = Promise.reject(e);
|
|
326
|
+
}
|
|
327
|
+
p.then((v) => fl.resolve(v), (e) => fl.reject(e));
|
|
328
|
+
return fl;
|
|
329
|
+
};
|
|
330
|
+
},
|
|
331
|
+
/** SF-6: a server fn's body (a generator, as an action's), driven by promises — not by an action: it is no change of
|
|
332
|
+
* the client's — behind a wire that copies arguments and result and lets only declared failures through. */
|
|
333
|
+
serverCall(name, gen, args) {
|
|
334
|
+
return new Promise((resolve, reject) => {
|
|
335
|
+
const fail = (msg) => {
|
|
336
|
+
throw new Error(`WIRE_VALUE: ${name}: ${msg}`);
|
|
337
|
+
};
|
|
338
|
+
let g;
|
|
339
|
+
try {
|
|
340
|
+
g = gen(...args.map((a, i) => wireCopy(a, `argument ${i + 1}`, fail)));
|
|
341
|
+
} catch (e) {
|
|
342
|
+
reject(e);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const step = (input, thrown) => {
|
|
346
|
+
const was = currentEq;
|
|
347
|
+
currentEq = null;
|
|
348
|
+
let r;
|
|
349
|
+
try {
|
|
350
|
+
r = untrack(() => thrown ? g.throw(input) : g.next(input));
|
|
351
|
+
} catch (e) {
|
|
352
|
+
reject(wireFailure(e));
|
|
353
|
+
return;
|
|
354
|
+
} finally {
|
|
355
|
+
currentEq = was;
|
|
356
|
+
}
|
|
357
|
+
if (r.done) {
|
|
358
|
+
try {
|
|
359
|
+
resolve(wireCopy(r.value, "its result", fail));
|
|
360
|
+
} catch (e) {
|
|
361
|
+
reject(e);
|
|
362
|
+
}
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
Promise.resolve(r.value).then((v) => step(v, false), (e) => step(e, true));
|
|
366
|
+
};
|
|
367
|
+
step(void 0, false);
|
|
368
|
+
});
|
|
369
|
+
},
|
|
370
|
+
hoisted(_name, _get) {
|
|
371
|
+
},
|
|
372
|
+
refresh: onServer("refresh"),
|
|
373
|
+
affects: onServer("affects"),
|
|
374
|
+
onSettled: onServer("onSettled"),
|
|
375
|
+
// EF-7: imperative scope only, and no imperative run happens here (SV-2a)
|
|
376
|
+
onFlush: onServer("onFlush"),
|
|
377
|
+
// EF-8
|
|
378
|
+
preOr: (v, init) => v === void 0 ? init() : v,
|
|
379
|
+
module(key, factory) {
|
|
380
|
+
let m = modules.get(key);
|
|
381
|
+
if (!m) {
|
|
382
|
+
m = factory(app);
|
|
383
|
+
modules.set(key, m);
|
|
384
|
+
}
|
|
385
|
+
return m;
|
|
386
|
+
},
|
|
387
|
+
// ---- context (RG-7): the server build's owner context, keyed by the declaration ----
|
|
388
|
+
owner: () => getOwner(),
|
|
389
|
+
context(name, dflt) {
|
|
390
|
+
return { name, id: Symbol(name), dflt, owner: getOwner(), dv: dflt ? { get: createMemo(dflt) } : null };
|
|
391
|
+
},
|
|
392
|
+
provide(c, e) {
|
|
393
|
+
const o = getOwner();
|
|
394
|
+
o._context = { ...o._context, [c.id]: e };
|
|
395
|
+
},
|
|
396
|
+
use(c, owner) {
|
|
397
|
+
const e = (owner ?? getOwner())?._context?.[c.id];
|
|
398
|
+
if (e) return e;
|
|
399
|
+
if (c.dv) return c.dv;
|
|
400
|
+
throw new Error(`RG-7b: no provision for ${c.name}`);
|
|
401
|
+
},
|
|
402
|
+
useGet(c, owner) {
|
|
403
|
+
const e = app.use(c, owner);
|
|
404
|
+
return e.get ? e.get() : e.value;
|
|
405
|
+
},
|
|
406
|
+
runWithOwner,
|
|
407
|
+
snapshot(get) {
|
|
408
|
+
try {
|
|
409
|
+
return untrack(get);
|
|
410
|
+
} catch (e) {
|
|
411
|
+
if (e instanceof NotReadyError) return PENDING_MARK;
|
|
412
|
+
throw e;
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
errorClass: (name, params, message) => errorClass(name, params, message, `${name}#${classSeq++}`),
|
|
416
|
+
// a declaration's identity, sent with its failures (SV-5a)
|
|
417
|
+
isA,
|
|
418
|
+
PANIC,
|
|
419
|
+
catchMemo: catchAcc,
|
|
420
|
+
tryCatch,
|
|
421
|
+
catchRead: (f, match, alt) => catchAcc(f, match, alt)(),
|
|
422
|
+
throw: (e) => {
|
|
423
|
+
throw e;
|
|
424
|
+
},
|
|
425
|
+
eq: (a, b) => a === b,
|
|
426
|
+
untrack,
|
|
427
|
+
latest: (f) => latest(() => latestWhole(f())),
|
|
428
|
+
isPending: (f) => isPending(() => readWhole(f())),
|
|
429
|
+
fmt,
|
|
430
|
+
// ---- views: compiled JSX (compile/jsx.ts, generate "ssr", hydratable) does the rest ----
|
|
431
|
+
show: (v) => isView(v) ? v : escape(fmt(v)),
|
|
432
|
+
/** A failure of another class passes to the boundary above (R-f-4): on the server, a rethrow from the fallback. The
|
|
433
|
+
* client's unwrapping memo has no counterpart here: its id is reserved. */
|
|
434
|
+
TypedErrored(props) {
|
|
435
|
+
const out = createComponent(Errored, {
|
|
436
|
+
fallback: (err) => {
|
|
437
|
+
const ok = createMemo(() => isA(err(), props.classes));
|
|
438
|
+
if (!ok()) throw err();
|
|
439
|
+
return props.fallback(err);
|
|
440
|
+
},
|
|
441
|
+
get children() {
|
|
442
|
+
return props.children;
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
reserve();
|
|
446
|
+
return out;
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
return app;
|
|
450
|
+
}
|
|
451
|
+
function assignDraft(draft, v) {
|
|
452
|
+
if (!v || typeof v !== "object") return;
|
|
453
|
+
if (Array.isArray(draft) && Array.isArray(v)) {
|
|
454
|
+
draft.length = v.length;
|
|
455
|
+
v.forEach((x, i) => {
|
|
456
|
+
draft[i] = x;
|
|
457
|
+
});
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
for (const k of Object.keys(draft)) if (!(k in v)) delete draft[k];
|
|
461
|
+
Object.assign(draft, v);
|
|
462
|
+
}
|
|
463
|
+
export {
|
|
464
|
+
ERR,
|
|
465
|
+
Flight,
|
|
466
|
+
PENDING_MARK,
|
|
467
|
+
createApp,
|
|
468
|
+
fmt,
|
|
469
|
+
generateHydrationScript
|
|
470
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { getOwner } from "solid-js";
|
|
2
|
+
import { generateHydrationScript } from "@solidjs/web";
|
|
3
|
+
export type Acc = () => unknown;
|
|
4
|
+
export type Writer = {
|
|
5
|
+
set: (v: unknown) => void;
|
|
6
|
+
path: (keys: (string | number)[], v: unknown) => void;
|
|
7
|
+
guess: (v: unknown, keys?: (string | number)[]) => void;
|
|
8
|
+
};
|
|
9
|
+
export type Entry = {
|
|
10
|
+
get?: Acc;
|
|
11
|
+
w?: Writer;
|
|
12
|
+
value?: unknown;
|
|
13
|
+
};
|
|
14
|
+
export { generateHydrationScript };
|
|
15
|
+
export declare const ERR: {
|
|
16
|
+
toString: () => string;
|
|
17
|
+
};
|
|
18
|
+
export declare const PENDING_MARK: {
|
|
19
|
+
toString: () => string;
|
|
20
|
+
};
|
|
21
|
+
export declare const fmt: (v: unknown) => string;
|
|
22
|
+
type ErrorClass = ((...args: unknown[]) => Error) & {
|
|
23
|
+
cls: new (...args: unknown[]) => Error;
|
|
24
|
+
errorClass: string;
|
|
25
|
+
};
|
|
26
|
+
/** The server renders once: an errored read throws where it is read, so a catch is an ordinary try around the read. (The
|
|
27
|
+
* client's is an error boundary: its ids are reserved.) */
|
|
28
|
+
declare function catchAcc(f: () => unknown, match: (err: unknown) => boolean, alt: (err: unknown) => unknown): () => unknown;
|
|
29
|
+
declare function tryCatch(f: () => unknown, match: (err: unknown) => boolean, alt: (err: unknown) => unknown): unknown;
|
|
30
|
+
/** As rt.ts's Flight. A rejection is sent as it is only when its class is declared (SV-5): `errorClass` marks those. */
|
|
31
|
+
export declare class Flight {
|
|
32
|
+
key: string;
|
|
33
|
+
first: Promise<unknown>;
|
|
34
|
+
private settleFirst;
|
|
35
|
+
private rejectFirst;
|
|
36
|
+
private queue;
|
|
37
|
+
private waiters;
|
|
38
|
+
private started;
|
|
39
|
+
constructor(key: string);
|
|
40
|
+
private push;
|
|
41
|
+
resolve(v: unknown): void;
|
|
42
|
+
reject(e: unknown): void;
|
|
43
|
+
[Symbol.asyncIterator](): {
|
|
44
|
+
next: () => Promise<IteratorResult<unknown>>;
|
|
45
|
+
return: () => Promise<{
|
|
46
|
+
value: undefined;
|
|
47
|
+
done: true;
|
|
48
|
+
}>;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
type Owner = ReturnType<typeof getOwner>;
|
|
52
|
+
type Ctx = {
|
|
53
|
+
name: string;
|
|
54
|
+
id: symbol;
|
|
55
|
+
dflt: (() => unknown) | null;
|
|
56
|
+
owner: Owner;
|
|
57
|
+
dv: Entry | null;
|
|
58
|
+
};
|
|
59
|
+
export declare function createApp(opts?: {
|
|
60
|
+
api?: Record<string, (...a: any[]) => unknown>;
|
|
61
|
+
}): {
|
|
62
|
+
log: string[];
|
|
63
|
+
flights: Map<string, Flight>;
|
|
64
|
+
lastKey: Map<string, string>;
|
|
65
|
+
asyncError: unknown;
|
|
66
|
+
/** Render `view` as a stream: `write` receives each chunk, `end` the close. */
|
|
67
|
+
/** One request (a request event, as Solid's integrations provide one — a server function called during the render runs
|
|
68
|
+
* in it, SF-2b). */
|
|
69
|
+
stream(view: () => unknown, sink: {
|
|
70
|
+
write: (s: string) => void;
|
|
71
|
+
end: () => void;
|
|
72
|
+
}): any;
|
|
73
|
+
B: Record<string, any>;
|
|
74
|
+
/** MO-8: a class a `.d.rx` declares — a failure the server sends as it is (SV-5). */
|
|
75
|
+
jsError(name: string): unknown;
|
|
76
|
+
/** A JS function's promise: a rejection with a declared class is marked safe to send; any other is redacted (SV-5). */
|
|
77
|
+
api: (name: string) => (...a: unknown[]) => unknown;
|
|
78
|
+
reg(name: string, e: Entry): void;
|
|
79
|
+
dget(name: string): any;
|
|
80
|
+
lookup(name: string): Entry;
|
|
81
|
+
source(name: string, init: unknown): [Acc, Writer];
|
|
82
|
+
question(name: string, compute: (prev: unknown) => unknown, o: {
|
|
83
|
+
loadingValue?: unknown;
|
|
84
|
+
} | undefined, mode: {
|
|
85
|
+
optimistic: boolean;
|
|
86
|
+
structured: boolean;
|
|
87
|
+
}, key?: string): [Acc, Writer];
|
|
88
|
+
memo: (name: string | null, fn: (prev: unknown) => unknown, o?: {
|
|
89
|
+
loadingValue?: unknown;
|
|
90
|
+
}) => Acc;
|
|
91
|
+
deferred: (name: string, fn: (prev: unknown) => unknown, o?: {
|
|
92
|
+
loadingValue?: unknown;
|
|
93
|
+
}) => Acc;
|
|
94
|
+
keyed: (name: string, key: string, fn: () => unknown) => Acc;
|
|
95
|
+
draft: (seed: unknown, body: (d: any) => void) => Acc;
|
|
96
|
+
pre: (get: Acc) => Acc;
|
|
97
|
+
firstPaint: (f: () => unknown) => {
|
|
98
|
+
loadingValue: any;
|
|
99
|
+
};
|
|
100
|
+
/** SV-2: the apply half runs on the client, after hydration. The compute half is reactive scope — Solid's server runs it;
|
|
101
|
+
* an input's failure is an answer (E-12), not a server error. */
|
|
102
|
+
effect(_n: number, inputs: () => unknown[], _apply: (p: unknown[]) => unknown): void;
|
|
103
|
+
action(_gen: (...a: unknown[]) => Generator<unknown, unknown, unknown>): () => never;
|
|
104
|
+
awaited: (v: unknown) => unknown;
|
|
105
|
+
/** RG-8: Solid's Portal — client-only: the server renders nothing for it, the browser renders it once it has the page. */
|
|
106
|
+
Portal: any;
|
|
107
|
+
/** CSS-2: a dynamic class with the view's scope class after it. */
|
|
108
|
+
scopedClass: (v: unknown, hash: string) => string;
|
|
109
|
+
/** SV-7b: a client question — not asked on the server; the browser asks it once hydrated (Solid's \`ssrSource: "client"\`). */
|
|
110
|
+
clientQuestion: (o?: {
|
|
111
|
+
loadingValue?: unknown;
|
|
112
|
+
}) => {
|
|
113
|
+
ssrSource: string;
|
|
114
|
+
loadingValue?: unknown;
|
|
115
|
+
};
|
|
116
|
+
/** 12 SF-2: a server fn as the client calls it — every call a flight. Asked in reactive scope it is a question, one
|
|
117
|
+
* flight per arguments, as a JS import's; in imperative scope each call is a new request. \`fn\` is the module-level
|
|
118
|
+
* \`"use server"\` function (SF-7a): the body in place, or — once Solid's \`transformDirectives\` has made it a reference
|
|
119
|
+
* — a request to the server. */
|
|
120
|
+
serverRef(name: string, fn: (...a: unknown[]) => Promise<unknown>): (...args: unknown[]) => Flight;
|
|
121
|
+
/** SF-6: a server fn's body (a generator, as an action's), driven by promises — not by an action: it is no change of
|
|
122
|
+
* the client's — behind a wire that copies arguments and result and lets only declared failures through. */
|
|
123
|
+
serverCall(name: string, gen: (...a: unknown[]) => Generator<unknown, unknown, unknown>, args: unknown[]): Promise<unknown>;
|
|
124
|
+
hoisted(_name: string, _get: Acc): void;
|
|
125
|
+
refresh: () => never;
|
|
126
|
+
affects: () => never;
|
|
127
|
+
onSettled: () => never;
|
|
128
|
+
onFlush: () => never;
|
|
129
|
+
preOr: (v: unknown, init: () => unknown) => unknown;
|
|
130
|
+
module(key: unknown, factory: (rt: unknown) => Record<string, unknown>): Record<string, unknown>;
|
|
131
|
+
owner: () => any;
|
|
132
|
+
context(name: string, dflt: (() => unknown) | null): Ctx;
|
|
133
|
+
provide(c: Ctx, e: Entry): void;
|
|
134
|
+
use(c: Ctx, owner: Owner): Entry;
|
|
135
|
+
useGet(c: Ctx, owner: Owner): unknown;
|
|
136
|
+
runWithOwner: any;
|
|
137
|
+
snapshot(get: Acc): any;
|
|
138
|
+
errorClass: (name: string, params: string[], message: (...args: unknown[]) => unknown) => ErrorClass;
|
|
139
|
+
isA: (err: unknown, classes: unknown[] | undefined) => boolean;
|
|
140
|
+
PANIC: {
|
|
141
|
+
readonly panic: true;
|
|
142
|
+
};
|
|
143
|
+
catchMemo: typeof catchAcc;
|
|
144
|
+
tryCatch: typeof tryCatch;
|
|
145
|
+
catchRead: (f: () => unknown, match: (err: unknown) => boolean, alt: (err: unknown) => unknown) => unknown;
|
|
146
|
+
throw: (e: unknown) => never;
|
|
147
|
+
eq: (a: unknown, b: unknown) => boolean;
|
|
148
|
+
untrack: any;
|
|
149
|
+
latest: (f: () => unknown) => any;
|
|
150
|
+
isPending: (f: () => unknown) => any;
|
|
151
|
+
fmt: (v: unknown) => string;
|
|
152
|
+
show: (v: unknown) => any;
|
|
153
|
+
/** A failure of another class passes to the boundary above (R-f-4): on the server, a rethrow from the fallback. The
|
|
154
|
+
* client's unwrapping memo has no counterpart here: its id is reserved. */
|
|
155
|
+
TypedErrored(props: {
|
|
156
|
+
classes: unknown[];
|
|
157
|
+
fallback: (err: Acc) => unknown;
|
|
158
|
+
children: unknown;
|
|
159
|
+
}): any;
|
|
160
|
+
};
|
|
161
|
+
export type ServerApp = ReturnType<typeof createApp>;
|