@reactivescript/runtime 0.0.0 → 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 CHANGED
@@ -1,45 +1,36 @@
1
1
  # @reactivescript/runtime
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
4
-
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
6
-
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
8
-
9
- ## Purpose
10
-
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@reactivescript/runtime`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
15
-
16
- ## What is OIDC Trusted Publishing?
17
-
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
19
-
20
- ## Setup Instructions
21
-
22
- To properly configure OIDC trusted publishing for this package:
23
-
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
28
-
29
- ## DO NOT USE THIS PACKAGE
30
-
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
-
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**
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>`.
@@ -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
+ };