@ogygia/core 0.12.3 → 0.13.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/dist/LateIsland.svelte +3 -2
- package/dist/LateIslandAwait.svelte +6 -2
- package/dist/Region.svelte +22 -33
- package/dist/boundary.d.ts +0 -2
- package/dist/boundary.js +8 -6
- package/dist/compiler/debarrel/barrel.d.ts +15 -8
- package/dist/compiler/debarrel/barrel.js +92 -45
- package/dist/compiler/debarrel/plugin.js +41 -17
- package/dist/compiler/dev/dev-hmr.d.ts +3 -0
- package/dist/compiler/dev/dev-hmr.js +3 -0
- package/dist/compiler/driver.js +11 -4
- package/dist/compiler/flags.js +21 -7
- package/dist/compiler/fouc-css.js +5 -1
- package/dist/compiler/kit.js +5 -1
- package/dist/compiler/link/build-output.js +12 -3
- package/dist/compiler/macros/bake.js +18 -4
- package/dist/compiler/macros/dollar.js +6 -1
- package/dist/compiler/macros/source.js +6 -1
- package/dist/compiler/macros/store.js +10 -2
- package/dist/compiler/macros/wire.js +6 -1
- package/dist/compiler/parse/scan.js +10 -3
- package/dist/compiler/region/transform.js +188 -74
- package/dist/context-bridge.js +13 -5
- package/dist/context.d.ts +9 -0
- package/dist/context.js +30 -1
- package/dist/document.d.ts +1 -1
- package/dist/document.js +7 -1
- package/dist/early-holes-head.d.ts +5 -0
- package/dist/early-holes-head.js +20 -0
- package/dist/early-holes.d.ts +34 -0
- package/dist/early-holes.js +51 -0
- package/dist/entry-hydrate.js +9 -12
- package/dist/events/bus.js +7 -2
- package/dist/events/schema.d.ts +2 -2
- package/dist/fn-transport.js +4 -3
- package/dist/hooks.js +94 -163
- package/dist/insights/page-assets.js +11 -3
- package/dist/insights/page-insights.d.ts +0 -2
- package/dist/insights/page-insights.js +14 -10
- package/dist/insights/session-insights.js +2 -2
- package/dist/internal-instruments.d.ts +3 -1
- package/dist/internal-instruments.js +3 -1
- package/dist/internal-server.d.ts +3 -2
- package/dist/internal-server.js +3 -2
- package/dist/internal.d.ts +1 -2
- package/dist/internal.js +1 -2
- package/dist/keep-fallback.d.ts +0 -2
- package/dist/keep-fallback.js +4 -5
- package/dist/live-transport.js +3 -2
- package/dist/messages/build.d.ts +585 -0
- package/dist/messages/build.js +800 -0
- package/dist/messages/core.d.ts +464 -0
- package/dist/messages/core.js +843 -0
- package/dist/messages/define.d.ts +62 -0
- package/dist/messages/define.js +49 -0
- package/dist/messages/index.d.ts +6 -0
- package/dist/messages/index.js +6 -0
- package/dist/messages/island.d.ts +4 -0
- package/dist/messages/island.js +34 -0
- package/dist/messages/lazy.d.ts +3 -0
- package/dist/messages/lazy.js +22 -0
- package/dist/messages/prose.d.ts +15 -0
- package/dist/messages/prose.js +43 -0
- package/dist/messages/server.d.ts +142 -0
- package/dist/messages/server.js +246 -0
- package/dist/nested.d.ts +3 -0
- package/dist/nested.js +8 -1
- package/dist/plugin/css.js +14 -13
- package/dist/plugin/facts.d.ts +2 -0
- package/dist/plugin/facts.js +10 -6
- package/dist/plugin/hooks.d.ts +7 -17
- package/dist/plugin/hooks.js +32 -4
- package/dist/plugin/index.d.ts +9 -2
- package/dist/plugin/request-scope.d.ts +20 -0
- package/dist/plugin/request-scope.js +149 -0
- package/dist/plugin/server.d.ts +20 -2
- package/dist/plugin/server.js +98 -28
- package/dist/plugin/testing.d.ts +6 -0
- package/dist/plugin/testing.js +14 -2
- package/dist/policies.d.ts +21 -0
- package/dist/policies.js +66 -0
- package/dist/preference.js +6 -1
- package/dist/preload.js +6 -9
- package/dist/ref-watch.d.ts +0 -10
- package/dist/ref-watch.js +14 -1
- package/dist/ref.js +17 -5
- package/dist/region-snippet.js +9 -3
- package/dist/region.js +2 -1
- package/dist/report-thrown.d.ts +9 -0
- package/dist/report-thrown.js +15 -0
- package/dist/rules.d.ts +17 -0
- package/dist/rules.js +57 -0
- package/dist/runtime/beacon.d.ts +0 -4
- package/dist/runtime/beacon.js +58 -43
- package/dist/runtime/boot-link.js +19 -2
- package/dist/runtime/client-hooks.js +2 -1
- package/dist/runtime/concurrency.d.ts +16 -5
- package/dist/runtime/concurrency.js +123 -7
- package/dist/runtime/core.d.ts +3 -1
- package/dist/runtime/core.js +718 -384
- package/dist/runtime/devalue-lane.d.ts +13 -0
- package/dist/runtime/devalue-lane.js +49 -0
- package/dist/runtime/disclose-version.d.ts +1 -0
- package/dist/runtime/disclose-version.js +14 -0
- package/dist/runtime/early-holes.d.ts +4 -0
- package/dist/runtime/early-holes.js +40 -0
- package/dist/runtime/entry-locations.js +40 -49
- package/dist/runtime/foreign-props.d.ts +0 -7
- package/dist/runtime/foreign-props.js +2 -1
- package/dist/runtime/form-continuity.d.ts +2 -2
- package/dist/runtime/form-continuity.js +30 -8
- package/dist/runtime/frame-nav.d.ts +7 -1
- package/dist/runtime/frame-nav.js +84 -18
- package/dist/runtime/frame-store.d.ts +25 -16
- package/dist/runtime/frame-store.js +124 -98
- package/dist/runtime/frames.js +8 -4
- package/dist/runtime/hole-facts.js +6 -7
- package/dist/runtime/hole-fetch.d.ts +17 -0
- package/dist/runtime/hole-fetch.js +47 -0
- package/dist/runtime/hydrate-core.d.ts +4 -4
- package/dist/runtime/hydrate-core.js +80 -64
- package/dist/runtime/import-again.d.ts +33 -0
- package/dist/runtime/import-again.js +217 -0
- package/dist/runtime/interaction-replay.d.ts +5 -3
- package/dist/runtime/interaction-replay.js +6 -6
- package/dist/runtime/interaction.d.ts +14 -3
- package/dist/runtime/interaction.js +46 -23
- package/dist/runtime/kit-page-thread.svelte.d.ts +2 -1
- package/dist/runtime/kit-page-thread.svelte.js +44 -20
- package/dist/runtime/lakes.d.ts +1 -1
- package/dist/runtime/lakes.js +18 -5
- package/dist/runtime/live.d.ts +1 -2
- package/dist/runtime/live.js +6 -8
- package/dist/runtime/load-scheduler.d.ts +29 -18
- package/dist/runtime/load-scheduler.js +236 -136
- package/dist/runtime/nav-handle.js +25 -6
- package/dist/runtime/navigation.d.ts +24 -0
- package/dist/runtime/navigation.js +24 -0
- package/dist/runtime/ownership.d.ts +1 -1
- package/dist/runtime/page-defer.js +3 -3
- package/dist/runtime/parse-html.d.ts +15 -4
- package/dist/runtime/parse-html.js +24 -5
- package/dist/runtime/pushable-props.svelte.d.ts +21 -0
- package/dist/runtime/pushable-props.svelte.js +37 -0
- package/dist/runtime/region-endpoint-url.js +4 -6
- package/dist/runtime/region-machines.d.ts +111 -0
- package/dist/runtime/region-machines.js +97 -0
- package/dist/runtime/region-sheets.d.ts +31 -0
- package/dist/runtime/region-sheets.js +49 -0
- package/dist/runtime/restore-report.d.ts +1 -0
- package/dist/runtime/restore-report.js +36 -0
- package/dist/runtime/restore.d.ts +5 -2
- package/dist/runtime/restore.js +33 -14
- package/dist/runtime/router-nav.d.ts +32 -12
- package/dist/runtime/router-nav.js +320 -228
- package/dist/runtime/router.d.ts +83 -19
- package/dist/runtime/router.js +297 -129
- package/dist/runtime/rules.d.ts +35 -0
- package/dist/runtime/rules.js +137 -0
- package/dist/runtime/schedule.d.ts +6 -3
- package/dist/runtime/schedule.js +172 -57
- package/dist/runtime/seeds.d.ts +5 -1
- package/dist/runtime/seeds.js +11 -5
- package/dist/runtime/session.d.ts +46 -7
- package/dist/runtime/session.js +78 -10
- package/dist/runtime/slots.d.ts +53 -20
- package/dist/runtime/slots.js +2 -3
- package/dist/runtime/soft-refresh.d.ts +16 -0
- package/dist/runtime/soft-refresh.js +14 -0
- package/dist/runtime/svelte-warnings.d.ts +4 -10
- package/dist/runtime/svelte-warnings.js +7 -4
- package/dist/runtime/wire-classes.js +21 -8
- package/dist/runtime/wire-format.js +6 -3
- package/dist/script.js +8 -7
- package/dist/seed-ref-reviver.d.ts +0 -9
- package/dist/seed-ref-reviver.js +2 -1
- package/dist/seed-refs.d.ts +46 -36
- package/dist/seed-refs.js +131 -92
- package/dist/server/document-tail.d.ts +6 -0
- package/dist/server/document-tail.js +7 -1
- package/dist/server/early-holes-csp.d.ts +14 -0
- package/dist/server/early-holes-csp.js +73 -0
- package/dist/server/page-facts.d.ts +4 -1
- package/dist/server/page-facts.js +4 -1
- package/dist/server/page-seed.d.ts +2 -2
- package/dist/server/page-seed.js +104 -11
- package/dist/server/page-stream.d.ts +14 -1
- package/dist/server/page-stream.js +119 -2
- package/dist/server/personal-response.d.ts +0 -5
- package/dist/server/personal-response.js +6 -2
- package/dist/server/props-wire.js +5 -1
- package/dist/server/region-endpoint.js +10 -2
- package/dist/server/render-cache.d.ts +24 -20
- package/dist/server/render-cache.js +99 -40
- package/dist/server/render-page.js +46 -15
- package/dist/server/request-stats.d.ts +10 -8
- package/dist/server/request-stats.js +20 -12
- package/dist/server/rules.d.ts +11 -0
- package/dist/server/rules.js +64 -0
- package/dist/server/signing-key.js +31 -18
- package/dist/server/stream-regions.d.ts +14 -1
- package/dist/server/stream-regions.js +38 -2
- package/dist/shared-state.js +5 -1
- package/dist/shims/app-navigation.js +3 -2
- package/dist/shims/page-store.svelte.js +16 -2
- package/dist/source.js +7 -2
- package/dist/store-transport.js +12 -5
- package/dist/transport.js +3 -2
- package/dist/vite/index.js +17 -10
- package/dist/vite/kit-dirs.js +7 -1
- package/dist/vite/options.d.ts +2 -1
- package/dist/vite/options.js +11 -20
- package/dist/vite/package-files.js +13 -3
- package/dist/vite/paths.d.ts +2 -0
- package/dist/vite/paths.js +4 -4
- package/dist/vite/plugin-api.js +42 -14
- package/dist/vite/style-preprocess.js +10 -3
- package/package.json +8 -1
- package/dist/KeepHost.svelte +0 -19
- package/dist/KeepHost.svelte.d.ts +0 -10
- package/dist/LiveHost.svelte +0 -19
- package/dist/LiveHost.svelte.d.ts +0 -10
- package/dist/NestedProvider.svelte +0 -13
- package/dist/NestedProvider.svelte.d.ts +0 -8
- package/dist/runtime/page-cache.d.ts +0 -23
- package/dist/runtime/page-cache.js +0 -74
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PAGE_DEFER_KEY, PAGE_SETTLED_KEY } from "../page-defer.js";
|
|
1
|
+
import { PAGE_DEFER_GLOBAL, PAGE_DEFER_KEY, PAGE_SETTLED_KEY } from "../page-defer.js";
|
|
2
2
|
import { escape_script_text } from "../escape.js";
|
|
3
3
|
import { stringify } from "devalue";
|
|
4
4
|
// src/server/page-stream.ts
|
|
@@ -118,4 +118,121 @@ function normalize_error(err) {
|
|
|
118
118
|
if (err && typeof err === "object" && "message" in err) return { message: String(err.message) };
|
|
119
119
|
return { message: String(err) };
|
|
120
120
|
}
|
|
121
|
-
|
|
121
|
+
/** `</body>` as bytes (ASCII: the same in every UTF-8 document). */
|
|
122
|
+
const BODY_CLOSE_BYTES = [
|
|
123
|
+
60,
|
|
124
|
+
47,
|
|
125
|
+
98,
|
|
126
|
+
111,
|
|
127
|
+
100,
|
|
128
|
+
121,
|
|
129
|
+
62
|
|
130
|
+
];
|
|
131
|
+
/** Does `</body>` end in `chunk` — searched backwards from its end, where it sits — or straddle the
|
|
132
|
+
* boundary with the previous chunk's last bytes (`carry`, at most 6)? No decode. */
|
|
133
|
+
function has_body_close(carry, chunk) {
|
|
134
|
+
const at_bytes = (buf, i) => {
|
|
135
|
+
for (let k = 0; k < 7; k++) if (buf[i + k] !== BODY_CLOSE_BYTES[k]) return false;
|
|
136
|
+
return true;
|
|
137
|
+
};
|
|
138
|
+
for (let i = chunk.lastIndexOf(60); i !== -1; i = i === 0 ? -1 : chunk.lastIndexOf(60, i - 1)) if (at_bytes(chunk, i)) return true;
|
|
139
|
+
if (!carry.length) return false;
|
|
140
|
+
const seam = new Uint8Array(carry.length + Math.min(6, chunk.length));
|
|
141
|
+
seam.set(carry);
|
|
142
|
+
seam.set(chunk.subarray(0, seam.length - carry.length), carry.length);
|
|
143
|
+
for (let i = 0; i + 7 <= seam.length; i++) if (at_bytes(seam, i)) return true;
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Stream the captured `$page.data` promises into islands (csr=false, real browser load). The
|
|
148
|
+
* document — carrying the pending seed + resolve-global bootstrap — is already built by `resolve`;
|
|
149
|
+
* here we (1) forward it, (2) DRAIN Kit's own dead csr=false resolve tail so its
|
|
150
|
+
* `__sveltekit_<hash> is not defined` never reaches the browser, and (3) emit one
|
|
151
|
+
* `<script>__ogygia_page_resolve(id, ok, value)<\/script>` per promise AS IT SETTLES — completion
|
|
152
|
+
* order, non-blocking. Each island's `{#await page.data.x}` flips pending → resolved live.
|
|
153
|
+
*
|
|
154
|
+
* The stream is the request's (`request`, its owner): the visitor leaving, or the body cancelled,
|
|
155
|
+
* stops Kit's reader and drops what settles after.
|
|
156
|
+
*/
|
|
157
|
+
function stream_page_deferred(request, response, deferred, initial_next_id, reducers) {
|
|
158
|
+
const source = response.body;
|
|
159
|
+
if (!source) return response;
|
|
160
|
+
const reader = source.getReader();
|
|
161
|
+
const encoder = new TextEncoder();
|
|
162
|
+
const owner = request.child();
|
|
163
|
+
owner.defer(() => void reader.cancel().catch(() => {}));
|
|
164
|
+
const stream = new ReadableStream({
|
|
165
|
+
async start(controller) {
|
|
166
|
+
let carry = /* @__PURE__ */ new Uint8Array(0);
|
|
167
|
+
try {
|
|
168
|
+
for (;;) {
|
|
169
|
+
const { value, done } = await reader.read();
|
|
170
|
+
if (done) break;
|
|
171
|
+
controller.enqueue(value);
|
|
172
|
+
if (has_body_close(carry, value)) break;
|
|
173
|
+
carry = value.length >= 6 ? value.subarray(value.length - 6) : value;
|
|
174
|
+
}
|
|
175
|
+
} catch {}
|
|
176
|
+
(async () => {
|
|
177
|
+
try {
|
|
178
|
+
for (;;) {
|
|
179
|
+
const { done } = await reader.read();
|
|
180
|
+
if (done) break;
|
|
181
|
+
}
|
|
182
|
+
} catch {}
|
|
183
|
+
})();
|
|
184
|
+
let pending = deferred.length;
|
|
185
|
+
let next_id = initial_next_id;
|
|
186
|
+
let closed = false;
|
|
187
|
+
const maybe_close = () => {
|
|
188
|
+
if (pending === 0 && !closed) {
|
|
189
|
+
closed = true;
|
|
190
|
+
try {
|
|
191
|
+
controller.close();
|
|
192
|
+
} catch {}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const push = (id, ok, value) => {
|
|
196
|
+
if (owner.closed) return;
|
|
197
|
+
try {
|
|
198
|
+
controller.enqueue(encoder.encode(resolve_script(PAGE_DEFER_GLOBAL, id, {
|
|
199
|
+
ok,
|
|
200
|
+
value
|
|
201
|
+
}, reducers)));
|
|
202
|
+
} catch {}
|
|
203
|
+
};
|
|
204
|
+
const settled = (id, ok, value) => {
|
|
205
|
+
push(id, ok, value);
|
|
206
|
+
pending -= 1;
|
|
207
|
+
maybe_close();
|
|
208
|
+
};
|
|
209
|
+
const stream_one = (id, promise) => {
|
|
210
|
+
Promise.resolve(promise).then((value) => {
|
|
211
|
+
let staged;
|
|
212
|
+
try {
|
|
213
|
+
staged = stage_deferred(value, next_id);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
return settled(id, false, error);
|
|
216
|
+
}
|
|
217
|
+
next_id = staged.next_id;
|
|
218
|
+
pending += staged.deferred.length;
|
|
219
|
+
for (const n of staged.deferred) stream_one(n.id, n.promise);
|
|
220
|
+
settled(id, true, staged.staged);
|
|
221
|
+
}, (error) => settled(id, false, error));
|
|
222
|
+
};
|
|
223
|
+
for (const { id, promise } of deferred) stream_one(id, promise);
|
|
224
|
+
maybe_close();
|
|
225
|
+
},
|
|
226
|
+
cancel() {
|
|
227
|
+
owner.close();
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
const headers = new Headers(response.headers);
|
|
231
|
+
headers.delete("content-length");
|
|
232
|
+
return new Response(stream, {
|
|
233
|
+
status: response.status,
|
|
234
|
+
statusText: response.statusText,
|
|
235
|
+
headers
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
export { DeferRef, SettledRef, defer_reducer, is_thenable, normalize_error, page_seed_reducers, resolve_script, settle_deferred, stage_deferred, stream_page_deferred };
|
|
@@ -1,6 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A page made for ONE browser — its devtools events (hooks.ts), the profiler's beacon (profiler/), the
|
|
3
|
-
* switch's Set-Cookie — must never reach a shared cache, whatever `cache-control` the app set: a CDN that
|
|
4
|
-
* keeps HTML would hand that browser's page to every visitor.
|
|
5
|
-
*/
|
|
6
1
|
export declare function mark_personal(response: Response): void;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import { warn } from "../messages/server.js";
|
|
1
2
|
// src/server/personal-response.ts
|
|
2
3
|
/**
|
|
3
4
|
* A page made for ONE browser — its devtools events (hooks.ts), the profiler's beacon (profiler/), the
|
|
4
5
|
* switch's Set-Cookie — must never reach a shared cache, whatever `cache-control` the app set: a CDN that
|
|
5
|
-
* keeps HTML would hand that browser's page to every visitor.
|
|
6
|
+
* keeps HTML would hand that browser's page to every visitor. A response whose headers cannot be
|
|
7
|
+
* changed (a fetched or redirect Response handed back as it is) is left as it is, and said.
|
|
6
8
|
*/
|
|
7
9
|
function mark_personal(response) {
|
|
8
10
|
try {
|
|
9
11
|
response.headers.set("cache-control", "private, no-store");
|
|
10
12
|
response.headers.append("vary", "Cookie");
|
|
11
|
-
} catch {
|
|
13
|
+
} catch (error) {
|
|
14
|
+
warn("personal_response_unmarked", { status: response.status }, error);
|
|
15
|
+
}
|
|
12
16
|
}
|
|
13
17
|
export { mark_personal };
|
|
@@ -5,6 +5,7 @@ import { register_wire_kind } from "../live-transport.js";
|
|
|
5
5
|
import { register_derived_kind, register_store_kind } from "../store-transport.js";
|
|
6
6
|
import { register_snippet_kind } from "../region-snippet.js";
|
|
7
7
|
import { register_fn_kind } from "../fn-transport.js";
|
|
8
|
+
import { OgygiaError } from "../messages/server.js";
|
|
8
9
|
import { analyze, json_culprit, plan_seed_refs } from "../seed-refs.js";
|
|
9
10
|
import { stringify } from "devalue";
|
|
10
11
|
// src/server/props-wire.ts
|
|
@@ -85,7 +86,10 @@ function stringify_props(value, entry, seed_refs = null, wire_out = null) {
|
|
|
85
86
|
return stringify(value, reducers);
|
|
86
87
|
} catch (e) {
|
|
87
88
|
const detail = e instanceof Error ? e.message : String(e);
|
|
88
|
-
throw new
|
|
89
|
+
throw new OgygiaError("prop_not_serializable", {
|
|
90
|
+
entry,
|
|
91
|
+
detail
|
|
92
|
+
});
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
95
|
/**
|
|
@@ -5,6 +5,7 @@ import { encode_region_props, ensure_prop_kinds } from "./region-props.js";
|
|
|
5
5
|
import { record_server_event } from "../events/server-registry.js";
|
|
6
6
|
import { B64Url } from "./payload.js";
|
|
7
7
|
import { REF_WIRE_KEY, ref_reducer } from "../ref.js";
|
|
8
|
+
import { OgygiaError, warn } from "../messages/server.js";
|
|
8
9
|
import { island_fingerprint } from "./fingerprint.js";
|
|
9
10
|
import { stringify } from "devalue";
|
|
10
11
|
import { getRequestEvent } from "virtual:ogygia/request-event";
|
|
@@ -127,14 +128,21 @@ function mint_region_capability(entry, payload, ttl = 0) {
|
|
|
127
128
|
function mintServerIsland(entry, props, ttl = 0) {
|
|
128
129
|
ensure_prop_kinds();
|
|
129
130
|
const payload = B64Url.encode(stringify(props, { [REF_WIRE_KEY]: ref_reducer(PROP_FAMILIES) }));
|
|
130
|
-
if (payload.length > 8192) throw new
|
|
131
|
+
if (payload.length > 8192) throw new OgygiaError("hole_props_too_large", {
|
|
132
|
+
entry,
|
|
133
|
+
length: payload.length,
|
|
134
|
+
max: MAX_REGION_PROPS_LEN
|
|
135
|
+
});
|
|
131
136
|
return mint_region_capability(entry, payload, ttl);
|
|
132
137
|
}
|
|
133
138
|
/** HMAC-signed `<endpoint>?id&props&exp&sig` (`handle({ endpoint })`, default `/__ogygia__`) for re-rendering `entry` with `props`. */
|
|
134
139
|
function makeRegionEndpoint(entry, props = {}) {
|
|
135
140
|
const payload = encode_region_props(props);
|
|
136
141
|
if (payload == null) {
|
|
137
|
-
|
|
142
|
+
warn("live_props_not_serializable", {
|
|
143
|
+
entry,
|
|
144
|
+
max: MAX_REGION_PROPS_LEN
|
|
145
|
+
});
|
|
138
146
|
return "";
|
|
139
147
|
}
|
|
140
148
|
return mint_region_capability(entry, payload);
|
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export declare function render_cache_set(key: string, html: string, ttl_sec: number, now: number): void;
|
|
9
|
-
/** Test/ops hook: drop everything. */
|
|
10
|
-
export declare function render_cache_clear(): void;
|
|
11
|
-
/** Test/ops hook: live entries + resident bytes. */
|
|
12
|
-
export declare function render_cache_stats(): {
|
|
13
|
-
entries: number;
|
|
14
|
-
bytes: number;
|
|
1
|
+
/** What a region render is made from: the region, its props, the visitor's session seal, and (when
|
|
2
|
+
* its tree reads `page.data`) the page it renders for. */
|
|
3
|
+
export type RenderKey = {
|
|
4
|
+
id: string;
|
|
5
|
+
payload: string;
|
|
6
|
+
session: string;
|
|
7
|
+
page: string | null;
|
|
15
8
|
};
|
|
9
|
+
/** Cache key for a region render — id + props payload + session seal (+ the page, for a tree that
|
|
10
|
+
* reads `page.data`). `exp`/`sig` (which rotate per mint) and `ttl` (a cache-window declaration,
|
|
11
|
+
* not a render input) are DELIBERATELY excluded so two renders of the same region+props+user hit;
|
|
12
|
+
* session is INCLUDED so users never cross. Each part is its own key field, so two calls whose
|
|
13
|
+
* parts differ never share an entry, whatever the parts hold. */
|
|
14
|
+
export declare function render_cache_key(id: string, payload: string, session: string, page?: string | null): RenderKey;
|
|
15
|
+
/** Test/ops hook: the HTML kept for `key` now, or null. */
|
|
16
|
+
export declare function render_cache_get(key: RenderKey): string | null;
|
|
17
|
+
/** Test/ops hook: drop everything, the flights in progress too (what they render is not kept, and
|
|
18
|
+
* the calls waiting on them render afresh). */
|
|
19
|
+
export declare function render_cache_clear(): void;
|
|
16
20
|
/**
|
|
17
21
|
* THE shared cache-fronted render (G2) — the ONE seam both the deferred-region endpoint
|
|
18
22
|
* (#render_component) and the inline-island page render (Region.svelte) call. A hit returns the
|
|
19
|
-
* memo; a miss renders via `render_body()` (the caller supplies it — the endpoint wraps its
|
|
20
|
-
* in a concurrency gate + timeout, the inline path renders directly), then caches. `cache`
|
|
21
|
-
* `undefined`/`ttl<=0` for a dynamic hole → always renders, never stores.
|
|
23
|
+
* memo; a miss renders via `render_body(signal)` (the caller supplies it — the endpoint wraps its
|
|
24
|
+
* render in a concurrency gate + timeout, the inline path renders directly), then caches. `cache`
|
|
25
|
+
* is `undefined`/`ttl<=0` for a dynamic hole → always renders, never stores.
|
|
22
26
|
*/
|
|
23
|
-
export declare function cached_render(render_body: () => string | null | Promise<string | null>, cache: {
|
|
24
|
-
key:
|
|
27
|
+
export declare function cached_render(render_body: (signal?: AbortSignal) => string | null | Promise<string | null>, cache: {
|
|
28
|
+
key: RenderKey;
|
|
25
29
|
ttl: number;
|
|
26
|
-
} | undefined,
|
|
30
|
+
} | undefined, report?: (outcome: "hit" | "miss" | "none") => void, signal?: AbortSignal): Promise<string | null>;
|
|
@@ -1,51 +1,110 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { STALE, flights } from "@ogygia/machine";
|
|
2
|
+
// src/server/render-cache.ts
|
|
3
|
+
/**
|
|
4
|
+
* Server-side per-region HTML render cache (reconciler R6 — the SSR-compute lever).
|
|
5
|
+
*
|
|
6
|
+
* The deferred/held-region endpoint (`#render_component` in hooks.ts) is the ONE place ogygia
|
|
7
|
+
* renders a region out-of-band as (id + props) → HTML. Today it re-renders fresh every request.
|
|
8
|
+
* When a region opts into a positive `maxAge` (a signed `ttl` on its capability URL), the author
|
|
9
|
+
* has DECLARED its output stable for that window — so memoizing the render for `ttl` seconds turns
|
|
10
|
+
* an expensive region into a one-render-then-serve-cached region across ALL requests. That is the
|
|
11
|
+
* only server-compute win reachable without making the monolithic page render region-granular
|
|
12
|
+
* (which would need the off-limits compiler); a plain inline island never reaches this seam.
|
|
13
|
+
*
|
|
14
|
+
* CORRECTNESS: the cache key includes the SESSION seal, so a per-user (`private, max-age`) render
|
|
15
|
+
* is never served to another user — exactly matching the browser Cache-Control the same `ttl`
|
|
16
|
+
* produces. `ttl <= 0` (the default `no-store` hole) is never cached. Bounded in BYTES (a region
|
|
17
|
+
* render can be up to `MAX_REGION_BODY`, 2 MB) and in entries, TTL-expiring, so it can't grow
|
|
18
|
+
* unbounded across a long-lived server process.
|
|
19
|
+
*
|
|
20
|
+
* ONE RENDER PER KEY IN FLIGHT (#244): the renders are a `flights()`, one flight per key, and every
|
|
21
|
+
* call for that key while it renders joins it instead of rendering again, so a burst of one cached
|
|
22
|
+
* hole takes one render gate slot, not one per request.
|
|
23
|
+
*
|
|
24
|
+
* - A failed render fails the calls that joined it too, and is not kept: the next call renders
|
|
25
|
+
* afresh. They asked for the same render (same region, props and visitor), so it failed for them
|
|
26
|
+
* as it would have on their own; each trying again in turn would be the burst this stops, on the
|
|
27
|
+
* path that is already failing (a hung upstream would time each of them out, each holding a slot).
|
|
28
|
+
* - Each call joins with its own request's signal. A request that leaves (a cancelled batch, #245)
|
|
29
|
+
* leaves the flight alone: the render goes on, in its place in the render queue, for the calls
|
|
30
|
+
* still waiting on it, and is stopped only once every one of them has left. The render is handed
|
|
31
|
+
* the flight's signal, not its opener's.
|
|
32
|
+
*/
|
|
33
|
+
/** Resident budget for cached region HTML. */
|
|
34
|
+
const MAX_BYTES = 67108864;
|
|
35
|
+
const ENTRY_SHARE = Math.floor(MAX_BYTES / 500);
|
|
36
|
+
const renders = flights.keyed()({
|
|
37
|
+
name: "render cache",
|
|
38
|
+
key: [
|
|
39
|
+
"id",
|
|
40
|
+
"payload",
|
|
41
|
+
"session",
|
|
42
|
+
"page"
|
|
43
|
+
],
|
|
44
|
+
ignore: {
|
|
45
|
+
render_body: "the render of the key: the same region, props, visitor and page",
|
|
46
|
+
ttl: "how long the HTML stays, not what is rendered"
|
|
47
|
+
},
|
|
48
|
+
run: async (ask, signal) => ask.render_body(signal),
|
|
49
|
+
keep: (html, ask) => html === null ? 0 : ask.ttl * 1e3,
|
|
50
|
+
max: MAX_BYTES,
|
|
51
|
+
weigh: (html) => Math.max(html?.length ?? 0, ENTRY_SHARE)
|
|
52
|
+
});
|
|
53
|
+
/** Cache key for a region render — id + props payload + session seal (+ the page, for a tree that
|
|
54
|
+
* reads `page.data`). `exp`/`sig` (which rotate per mint) and `ttl` (a cache-window declaration,
|
|
55
|
+
* not a render input) are DELIBERATELY excluded so two renders of the same region+props+user hit;
|
|
56
|
+
* session is INCLUDED so users never cross. Each part is its own key field, so two calls whose
|
|
57
|
+
* parts differ never share an entry, whatever the parts hold. */
|
|
58
|
+
function render_cache_key(id, payload, session, page = null) {
|
|
59
|
+
return {
|
|
60
|
+
id,
|
|
61
|
+
payload,
|
|
62
|
+
session,
|
|
63
|
+
page
|
|
64
|
+
};
|
|
12
65
|
}
|
|
13
|
-
/**
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
66
|
+
/** Test/ops hook: the HTML kept for `key` now, or null. */
|
|
67
|
+
function render_cache_get(key) {
|
|
68
|
+
const seen = renders.peek(key);
|
|
69
|
+
return seen?.kind === "kept" ? seen.answer : null;
|
|
17
70
|
}
|
|
18
|
-
/** Test/ops hook: drop everything
|
|
71
|
+
/** Test/ops hook: drop everything, the flights in progress too (what they render is not kept, and
|
|
72
|
+
* the calls waiting on them render afresh). */
|
|
19
73
|
function render_cache_clear() {
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
/** Test/ops hook: live entries + resident bytes. */
|
|
23
|
-
function render_cache_stats() {
|
|
24
|
-
return {
|
|
25
|
-
entries: store.size,
|
|
26
|
-
bytes: store.bytes
|
|
27
|
-
};
|
|
74
|
+
renders.drop(() => true);
|
|
28
75
|
}
|
|
29
76
|
/**
|
|
30
77
|
* THE shared cache-fronted render (G2) — the ONE seam both the deferred-region endpoint
|
|
31
78
|
* (#render_component) and the inline-island page render (Region.svelte) call. A hit returns the
|
|
32
|
-
* memo; a miss renders via `render_body()` (the caller supplies it — the endpoint wraps its
|
|
33
|
-
* in a concurrency gate + timeout, the inline path renders directly), then caches. `cache`
|
|
34
|
-
* `undefined`/`ttl<=0` for a dynamic hole → always renders, never stores.
|
|
79
|
+
* memo; a miss renders via `render_body(signal)` (the caller supplies it — the endpoint wraps its
|
|
80
|
+
* render in a concurrency gate + timeout, the inline path renders directly), then caches. `cache`
|
|
81
|
+
* is `undefined`/`ttl<=0` for a dynamic hole → always renders, never stores.
|
|
35
82
|
*/
|
|
36
|
-
async function cached_render(render_body, cache,
|
|
37
|
-
if (cache
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
83
|
+
async function cached_render(render_body, cache, report, signal) {
|
|
84
|
+
if (cache === void 0 || cache.ttl <= 0) {
|
|
85
|
+
const body = await render_body(signal);
|
|
86
|
+
report?.("none");
|
|
87
|
+
return body;
|
|
88
|
+
}
|
|
89
|
+
const ask = {
|
|
90
|
+
...cache.key,
|
|
91
|
+
render_body,
|
|
92
|
+
ttl: cache.ttl
|
|
93
|
+
};
|
|
94
|
+
for (;;) {
|
|
95
|
+
const opens = renders.peek(cache.key) === void 0;
|
|
96
|
+
let html;
|
|
97
|
+
try {
|
|
98
|
+
html = await renders.get(ask, { signal });
|
|
99
|
+
} catch (error) {
|
|
100
|
+
report?.("none");
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
if (html !== STALE) {
|
|
104
|
+
report?.(html === null ? "none" : opens ? "miss" : "hit");
|
|
105
|
+
return html;
|
|
42
106
|
}
|
|
107
|
+
if (signal?.aborted) throw signal.reason;
|
|
43
108
|
}
|
|
44
|
-
const body = await render_body();
|
|
45
|
-
if (cache !== void 0 && cache.ttl > 0 && body !== null) {
|
|
46
|
-
render_cache_set(cache.key, body, cache.ttl, now);
|
|
47
|
-
report?.("miss");
|
|
48
|
-
} else report?.("none");
|
|
49
|
-
return body;
|
|
50
109
|
}
|
|
51
|
-
export { cached_render, render_cache_clear, render_cache_get, render_cache_key
|
|
110
|
+
export { cached_render, render_cache_clear, render_cache_get, render_cache_key };
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { request_local } from "../plugin/request-scope.js";
|
|
1
2
|
import { islands_endpoint } from "./endpoint.js";
|
|
2
3
|
import { look_up_page } from "./page-facts.js";
|
|
3
4
|
import { merge_seed_ask, shape_page_data } from "./seed-shape.js";
|
|
4
5
|
import { PAGE_FACTS_SCRIPT_TYPE } from "../page-facts-script.js";
|
|
6
|
+
import { line } from "../messages/server.js";
|
|
7
|
+
import { flights } from "@ogygia/machine";
|
|
5
8
|
import * as devalue from "devalue";
|
|
6
9
|
// src/server/render-page.ts
|
|
7
10
|
let warn = null;
|
|
@@ -25,8 +28,35 @@ function page_url_of(event) {
|
|
|
25
28
|
} catch {}
|
|
26
29
|
return event.url;
|
|
27
30
|
}
|
|
28
|
-
|
|
29
|
-
const
|
|
31
|
+
/** Why a page-facts look-up's inputs are not its key: a request has one look-up. */
|
|
32
|
+
const ONE_PER_REQUEST = "a request has one look-up";
|
|
33
|
+
/**
|
|
34
|
+
* Each request's look-up of its page's facts, a flight of one key the request owns: looked up once
|
|
35
|
+
* per request (a failed one is not kept), its answer the page's facts (null: unavailable), its
|
|
36
|
+
* streamed reader let go when the request ends.
|
|
37
|
+
*/
|
|
38
|
+
const looked_up = request_local((owner) => flights.keyed()({
|
|
39
|
+
name: "page facts",
|
|
40
|
+
key: [],
|
|
41
|
+
ignore: {
|
|
42
|
+
page: ONE_PER_REQUEST,
|
|
43
|
+
fetch: ONE_PER_REQUEST,
|
|
44
|
+
decoders: ONE_PER_REQUEST
|
|
45
|
+
},
|
|
46
|
+
run: ({ page, fetch, decoders }) => look_up_page(page, fetch, decoders, owner).then((facts) => {
|
|
47
|
+
if (!("miss" in facts)) return facts;
|
|
48
|
+
warn?.(line("outside_page_data_unavailable", {
|
|
49
|
+
path: page.pathname,
|
|
50
|
+
miss: facts.miss
|
|
51
|
+
}));
|
|
52
|
+
return null;
|
|
53
|
+
})
|
|
54
|
+
}));
|
|
55
|
+
/** The page's facts this request looked up, or undefined. */
|
|
56
|
+
function facts_of(request) {
|
|
57
|
+
const entry = looked_up(request).peek({});
|
|
58
|
+
return entry?.kind === "kept" ? entry.answer ?? void 0 : void 0;
|
|
59
|
+
}
|
|
30
60
|
/**
|
|
31
61
|
* Before an async render root starts: in a request rendering FOR a page, that page's facts, looked
|
|
32
62
|
* up once. `reads_data`: does this render read `page.data` (`null`: unknown here — a remote call
|
|
@@ -36,19 +66,17 @@ const facts_of = /* @__PURE__ */ new WeakMap();
|
|
|
36
66
|
function prepare_render_page(event, transport, reads_data) {
|
|
37
67
|
if (!event || !renders_for_a_page(event)) return;
|
|
38
68
|
if (!(reads_data ?? event.isRemoteRequest === true)) return;
|
|
39
|
-
const request = event.request;
|
|
40
|
-
const known = looked_up.get(request);
|
|
41
|
-
if (known) return known;
|
|
42
69
|
const page = page_url_of(event);
|
|
43
70
|
if (page === event.url) return;
|
|
71
|
+
const look_up = looked_up(event.request);
|
|
72
|
+
if (look_up.peek({})?.kind === "kept") return;
|
|
44
73
|
const decoders = {};
|
|
45
74
|
for (const name in transport ?? {}) decoders[name] = transport[name].decode;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return p;
|
|
75
|
+
return look_up.get({
|
|
76
|
+
page,
|
|
77
|
+
fetch: event.fetch,
|
|
78
|
+
decoders
|
|
79
|
+
}).then(() => {});
|
|
52
80
|
}
|
|
53
81
|
/**
|
|
54
82
|
* The page a render sees. `snapshot`: what this request's page render recorded, if anything. `null`
|
|
@@ -57,7 +85,7 @@ function prepare_render_page(event, transport, reads_data) {
|
|
|
57
85
|
function render_page(snapshot, event) {
|
|
58
86
|
if (!snapshot && !event) return null;
|
|
59
87
|
if (event && renders_for_a_page(event)) {
|
|
60
|
-
const facts = facts_of
|
|
88
|
+
const facts = facts_of(event.request);
|
|
61
89
|
return {
|
|
62
90
|
url: facts?.url ?? page_url_of(event),
|
|
63
91
|
params: facts?.params ?? {},
|
|
@@ -105,7 +133,10 @@ function watched_data(facts, say) {
|
|
|
105
133
|
if (!seen) warned_keys.set(facts, seen = /* @__PURE__ */ new Set());
|
|
106
134
|
if (!seen.has(key)) {
|
|
107
135
|
seen.add(key);
|
|
108
|
-
say(
|
|
136
|
+
say(line("outside_page_data_key", {
|
|
137
|
+
path: facts.url.pathname,
|
|
138
|
+
key
|
|
139
|
+
}));
|
|
109
140
|
}
|
|
110
141
|
}
|
|
111
142
|
return Reflect.get(target, key, receiver);
|
|
@@ -123,7 +154,7 @@ function render_page_version(event, snapshot) {
|
|
|
123
154
|
if (!event) return "";
|
|
124
155
|
const known = versions.get(event.request);
|
|
125
156
|
if (known !== void 0) return known;
|
|
126
|
-
const data = renders_for_a_page(event) ? facts_of
|
|
157
|
+
const data = renders_for_a_page(event) ? facts_of(event.request)?.data : snapshot?.data;
|
|
127
158
|
let v = "";
|
|
128
159
|
if (data && typeof data === "object") try {
|
|
129
160
|
v = fnv1a(JSON.stringify(data));
|
|
@@ -162,7 +193,7 @@ function note_render_page_ask(event, seed) {
|
|
|
162
193
|
*/
|
|
163
194
|
function render_page_facts_script(event, transport) {
|
|
164
195
|
if (!event || !renders_for_a_page(event)) return "";
|
|
165
|
-
const facts = facts_of
|
|
196
|
+
const facts = facts_of(event.request);
|
|
166
197
|
const keys = asks_of.get(event.request);
|
|
167
198
|
if (!facts || !keys) return "";
|
|
168
199
|
const encoders = {};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* WeakMap — the record dies with the request, nothing is deleted, and the two modules never
|
|
10
10
|
* import each other (hooks.ts imports the profiler lazily; the profiler must not import hooks).
|
|
11
11
|
*
|
|
12
|
-
* DETAIL is opt-in (`
|
|
12
|
+
* DETAIL is opt-in (`hold_request_stats_detail`): the profiler holds it on for the length of a
|
|
13
13
|
* recording, and only then does the handle spend anything on the per-island rows, the seed
|
|
14
14
|
* explainer and the hole notes below. Off, a request pays one WeakMap write of the totals.
|
|
15
15
|
*/
|
|
@@ -163,12 +163,14 @@ export declare function set_batch_hole_listener(fn: ((request: Request, s: Batch
|
|
|
163
163
|
export declare function has_batch_hole_listener(): boolean;
|
|
164
164
|
export declare function record_batch_hole_stats(request: Request, s: BatchHoleStats): void;
|
|
165
165
|
/**
|
|
166
|
-
* The profiler
|
|
167
|
-
* switch: the flag is the process's, while a recorder lock is one profiler instance's —
|
|
168
|
-
* live at once (the dev server re-runs the app's hooks on an edit and `ogygia.handle()`
|
|
169
|
-
* profiler, while the old one's sample and trap timers still record). The first window
|
|
170
|
-
* turned detail off under the other's render: its report kept no island rows ("islands
|
|
171
|
-
* and named its islands by their files. On while any
|
|
166
|
+
* The profiler asks for detail for a recording; with nobody asking, the handle records totals only.
|
|
167
|
+
* HELD, not a switch: the flag is the process's, while a recorder lock is one profiler instance's —
|
|
168
|
+
* and two can live at once (the dev server re-runs the app's hooks on an edit and `ogygia.handle()`
|
|
169
|
+
* builds a new profiler, while the old one's sample and trap timers still record). The first window
|
|
170
|
+
* to end once turned detail off under the other's render: its report kept no island rows ("islands
|
|
171
|
+
* 1, rows 0") and named its islands by their files. On while any hold stands; each hold is its
|
|
172
|
+
* caller's, and its release lets go of that hold only, once (a second call does nothing), so an
|
|
173
|
+
* extra "off" can never end someone else's.
|
|
172
174
|
*/
|
|
173
|
-
export declare function
|
|
175
|
+
export declare function hold_request_stats_detail(): () => void;
|
|
174
176
|
export declare function request_stats_detailed(): boolean;
|
|
@@ -36,19 +36,27 @@ function record_batch_hole_stats(request, s) {
|
|
|
36
36
|
batch_listener?.(request, s);
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
|
-
* The profiler
|
|
40
|
-
* switch: the flag is the process's, while a recorder lock is one profiler instance's —
|
|
41
|
-
* live at once (the dev server re-runs the app's hooks on an edit and `ogygia.handle()`
|
|
42
|
-
* profiler, while the old one's sample and trap timers still record). The first window
|
|
43
|
-
* turned detail off under the other's render: its report kept no island rows ("islands
|
|
44
|
-
* and named its islands by their files. On while any
|
|
39
|
+
* The profiler asks for detail for a recording; with nobody asking, the handle records totals only.
|
|
40
|
+
* HELD, not a switch: the flag is the process's, while a recorder lock is one profiler instance's —
|
|
41
|
+
* and two can live at once (the dev server re-runs the app's hooks on an edit and `ogygia.handle()`
|
|
42
|
+
* builds a new profiler, while the old one's sample and trap timers still record). The first window
|
|
43
|
+
* to end once turned detail off under the other's render: its report kept no island rows ("islands
|
|
44
|
+
* 1, rows 0") and named its islands by their files. On while any hold stands; each hold is its
|
|
45
|
+
* caller's, and its release lets go of that hold only, once (a second call does nothing), so an
|
|
46
|
+
* extra "off" can never end someone else's.
|
|
45
47
|
*/
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
function hold_request_stats_detail() {
|
|
49
|
+
const hold = {};
|
|
50
|
+
detail_holds.add(hold);
|
|
51
|
+
detailed = true;
|
|
52
|
+
return () => {
|
|
53
|
+
if (!detail_holds.delete(hold)) return;
|
|
54
|
+
detailed = detail_holds.size > 0;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Who asked for detail, each until its own release. */
|
|
58
|
+
const detail_holds = /* @__PURE__ */ new Set();
|
|
51
59
|
function request_stats_detailed() {
|
|
52
60
|
return detailed;
|
|
53
61
|
}
|
|
54
|
-
export { has_batch_hole_listener, hole_stats_of, record_batch_hole_stats, record_hole_stats, record_request_stats, request_stats_detailed, request_stats_of, request_stats_wanted, set_batch_hole_listener,
|
|
62
|
+
export { has_batch_hole_listener, hold_request_stats_detail, hole_stats_of, record_batch_hole_stats, record_hole_stats, record_request_stats, request_stats_detailed, request_stats_of, request_stats_wanted, set_batch_hole_listener, want_request_stats };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Rules } from "../rules.js";
|
|
2
|
+
import { Scope } from "@ogygia/machine";
|
|
3
|
+
type ServerRule = "rule_request_scope_open" | "rule_flight_kept_failure";
|
|
4
|
+
/** The server's rules. */
|
|
5
|
+
export declare const server_rules: Rules<ServerRule>;
|
|
6
|
+
/** A response ended (its body read to the end, cancelled, or none): its request's scope must be
|
|
7
|
+
* closed by the time the next check runs. */
|
|
8
|
+
export declare function note_response_end(owner: Scope): void;
|
|
9
|
+
/** Rules on: checked after each response, and what broke is said with its code. */
|
|
10
|
+
export declare function install_server_rules(): void;
|
|
11
|
+
export {};
|