@trylight/app 0.3.2 → 0.3.5
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 +11 -1
- package/dist/auth/next-path.js +10 -4
- package/dist/client.js +2 -1
- package/dist/invoke/index.js +2 -18
- package/dist/platform/contract.d.ts +14 -0
- package/dist/platform/contract.js +23 -2
- package/dist/platform/live-data.js +3 -0
- package/dist/platform/pane-bridge.js +4 -0
- package/dist/platform/restart.d.ts +18 -0
- package/dist/platform/restart.js +70 -0
- package/dist/platform/timing.d.ts +5 -0
- package/dist/platform/timing.js +5 -0
- package/dist/platform.d.ts +1 -0
- package/dist/platform.js +3 -1
- package/package.json +10 -12
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ its tables and their typed client, its door, its contracts and its chat.
|
|
|
10
10
|
| `@trylight/app/schema` | `entity(name, columns, access)`: a Drizzle table plus `id`, `created_at`, `updated_at`, `created_by`, `field_sources`; `lightChanges` (the audit table); `lightImages` (the app's copy of its logos and photos); `fieldSource` (the `{url, evidence}` schema) |
|
|
11
11
|
| `@trylight/app` | `createApp<typeof contracts>()` → `app.table(entity)` (`list/get/create/update/delete`, `useList/useInfiniteList/useGet/useCreate/useUpdate/useDelete`, typed by the entity passed; `useList(...).rows` is never undefined), `app.contracts` (`useInvocation`), and `imageSrc(url)` (where a logo or photo loads from) |
|
|
12
12
|
| `@trylight/app/server` | `createLightServer({ schema, contracts })` → `light.db` (Drizzle), `light.session(request)`, `light.signIn(email, password)`, and the route handlers `light.entities`, `light.image`, `light.invoke.handler`, `light.chat` |
|
|
13
|
-
| `@trylight/app/platform` | the door for server functions, framing, analytics/error policies, the pane bridge |
|
|
13
|
+
| `@trylight/app/platform` | the door for server functions, framing, analytics/error policies, the pane bridge, `fetchThroughRestart` (`createStart`'s `serverFns.fetch`) |
|
|
14
14
|
| `@trylight/app/vite` | `lightDb()`: the dev server syncs `db/schema` into the database on every change |
|
|
15
15
|
| `light-db` (bin) | `sync`, `migrate`, `check`: the app's `pnpm db:*` scripts |
|
|
16
16
|
|
|
@@ -41,6 +41,16 @@ text column, case-insensitively, on the server.
|
|
|
41
41
|
|
|
42
42
|
Joins and aggregates stay typed server functions over `light.db`.
|
|
43
43
|
|
|
44
|
+
### Through a dev server restart
|
|
45
|
+
|
|
46
|
+
Table calls, contract calls and server functions (through
|
|
47
|
+
`fetchThroughRestart`) wait while the gateway answers that the app is starting
|
|
48
|
+
(503 + `x-mii-app-starting`, any method) or, for reads only, while the
|
|
49
|
+
connection fails, for up to `RESTART_WAIT_MS`; then the last answer or error
|
|
50
|
+
is the caller's. The pane hears each request that starts or stops waiting
|
|
51
|
+
(`light:requests-held`: `held` still waiting, `started` for a new one) and swaps
|
|
52
|
+
in a reloaded frame only once none is held.
|
|
53
|
+
|
|
44
54
|
### Live data
|
|
45
55
|
|
|
46
56
|
An open page shows other people's and agents' changes without a refresh.
|
package/dist/auth/next-path.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
1
|
+
import { SAME_ORIGIN_BASE } from "../platform/contract.js";
|
|
2
|
+
// Only a path on this app is a valid place to land after signing in. Parsed,
|
|
3
|
+
// not pattern-matched: browsers read `//host`, `/\host` and `/\t/host` as
|
|
4
|
+
// another site. The candidate is returned as given, not normalised: its
|
|
5
|
+
// pathname could read `//host`.
|
|
3
6
|
export function safeNext(candidate) {
|
|
4
7
|
if (candidate === null ||
|
|
5
8
|
!candidate.startsWith("/") ||
|
|
6
|
-
|
|
9
|
+
// The parser drops tabs and line breaks; a Location header refuses them.
|
|
10
|
+
[...candidate].some((character) => character < " ")) {
|
|
7
11
|
return "/";
|
|
8
12
|
}
|
|
9
|
-
return candidate
|
|
13
|
+
return new URL(candidate, SAME_ORIGIN_BASE).origin === SAME_ORIGIN_BASE
|
|
14
|
+
? candidate
|
|
15
|
+
: "/";
|
|
10
16
|
}
|
package/dist/client.js
CHANGED
|
@@ -12,6 +12,7 @@ import { getTableColumns, getTableName } from "drizzle-orm";
|
|
|
12
12
|
import { z } from "zod/v4";
|
|
13
13
|
import { ENTITIES_PATH } from "./platform/contract.js";
|
|
14
14
|
import { EntityError, LIST_LIMIT, tableQueryKey } from "./entities.js";
|
|
15
|
+
import { fetchThroughRestart } from "./platform/restart.js";
|
|
15
16
|
export { EntityError, LIST_LIMIT } from "./entities.js";
|
|
16
17
|
export { siteOf } from "./schema.js";
|
|
17
18
|
export { imageSrc } from "./images.js";
|
|
@@ -354,7 +355,7 @@ function json(body) {
|
|
|
354
355
|
}
|
|
355
356
|
const refusal = z.object({ error: z.string(), code: z.string() });
|
|
356
357
|
async function call(method, path, init = {}) {
|
|
357
|
-
const response = await
|
|
358
|
+
const response = await fetchThroughRestart(path, { ...init, method, credentials: "same-origin" });
|
|
358
359
|
if (response.ok)
|
|
359
360
|
return response.json();
|
|
360
361
|
const read = refusal.safeParse(await response.json().catch(() => null));
|
package/dist/invoke/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// the hook reports how long it has been and the app decides what that means.
|
|
4
4
|
import { useEffect, useMemo, useState } from "react";
|
|
5
5
|
import { z } from "zod/v4";
|
|
6
|
+
import { fetchThroughRestart, wait } from "../platform/restart.js";
|
|
6
7
|
import { errorBody } from "../request-error.js";
|
|
7
8
|
import { failedRun, InvocationError } from "./error.js";
|
|
8
9
|
import { INVOKE_BASE_PATH, STORAGE_KEYS } from "../platform/contract.js";
|
|
@@ -65,7 +66,7 @@ async function awaitInvocation(started, signal = null, onPoll = unwatched) {
|
|
|
65
66
|
async function call(method, path, body, signal) {
|
|
66
67
|
let response;
|
|
67
68
|
try {
|
|
68
|
-
response = await
|
|
69
|
+
response = await fetchThroughRestart(`${INVOKE_BASE_PATH}${path}`, {
|
|
69
70
|
method,
|
|
70
71
|
credentials: "same-origin",
|
|
71
72
|
...(body === null ? {} : { headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }),
|
|
@@ -90,23 +91,6 @@ async function call(method, path, body, signal) {
|
|
|
90
91
|
throw new InvocationError(response.status, "not_the_handler", "The app's invoke routes are not reachable.");
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
|
-
function wait(ms, signal) {
|
|
94
|
-
return new Promise((resolve, reject) => {
|
|
95
|
-
if (signal === null) {
|
|
96
|
-
setTimeout(resolve, ms);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const abort = () => {
|
|
100
|
-
clearTimeout(timer);
|
|
101
|
-
reject(signal.reason);
|
|
102
|
-
};
|
|
103
|
-
const timer = setTimeout(() => {
|
|
104
|
-
signal.removeEventListener("abort", abort);
|
|
105
|
-
resolve();
|
|
106
|
-
}, ms);
|
|
107
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
94
|
// This browser's note of a remembered run. Storage can be blocked or full;
|
|
111
95
|
// then the run is simply not picked up again. Whose run it is the platform decides:
|
|
112
96
|
// another person's id reads as not found, and the note is let go of.
|
|
@@ -5,17 +5,23 @@ export declare const IMAGE_PATH = "/api/light/image";
|
|
|
5
5
|
export declare const BUILD_PATH = "/api/light/build";
|
|
6
6
|
export declare const INVOKE_BASE_PATH = "/api/mii/invoke";
|
|
7
7
|
export declare const CHAT_BASE_PATH = "/api/mii/chat";
|
|
8
|
+
/** A base to resolve an app-relative path against when checking it stays on the app; never contacted. */
|
|
9
|
+
export declare const SAME_ORIGIN_BASE = "https://app.invalid";
|
|
8
10
|
/** The platform's sign-in API, called with an app's own id. */
|
|
9
11
|
export declare const PLATFORM_AUTH_PATHS: {
|
|
10
12
|
signIn: string;
|
|
11
13
|
requestPasswordReset: string;
|
|
12
14
|
redeemPaneCode: string;
|
|
13
15
|
config: string;
|
|
16
|
+
/** "Continue with Google / Microsoft": the platform signs the person in and returns with a one-time code. */
|
|
17
|
+
socialSignIn: string;
|
|
14
18
|
};
|
|
15
19
|
/** The platform's chat API: an app's chat route forwards any path beneath it, so a new widget route needs no SDK release. */
|
|
16
20
|
export declare const PLATFORM_CHAT_PATH = "/api/app-chat/v1/chat";
|
|
17
21
|
/** What this app's chat server forwards beyond the first contract: the widget, served to every app version, offers only these. */
|
|
18
22
|
export declare const CHAT_FEATURES_HEADER = "X-Mii-Chat-Features";
|
|
23
|
+
/** The gateway's mark on its 503 while the app's server is down: the request never reached the app. */
|
|
24
|
+
export declare const STARTING_HEADER = "x-mii-app-starting";
|
|
19
25
|
/** Every TanStack query the SDK owns sits under this key. */
|
|
20
26
|
export declare const QUERY_ROOT = "light";
|
|
21
27
|
export declare const SESSION_COOKIE = "mii_session";
|
|
@@ -39,4 +45,12 @@ export declare const PANE_MESSAGES: {
|
|
|
39
45
|
error: "mii-preview:error";
|
|
40
46
|
picked: "mii-preview:picked";
|
|
41
47
|
located: "mii-preview:located";
|
|
48
|
+
/** From @trylight/vite-plugin's client: the page mounted, or is about to reload. */
|
|
49
|
+
mounted: "light:mounted";
|
|
50
|
+
willReload: "light:will-reload";
|
|
51
|
+
/** From the gateway's page for a sandbox still waking. */
|
|
52
|
+
starting: "light:starting";
|
|
53
|
+
appError: "app_error";
|
|
54
|
+
/** How many requests are waiting out a dev server restart; the pane swaps in a reload only at zero. */
|
|
55
|
+
requestsHeld: "light:requests-held";
|
|
42
56
|
};
|
|
@@ -11,17 +11,23 @@ export const IMAGE_PATH = `/api/${NAMESPACE}/image`;
|
|
|
11
11
|
export const BUILD_PATH = `/api/${NAMESPACE}/build`;
|
|
12
12
|
export const INVOKE_BASE_PATH = `/api/${LEGACY_NAMESPACE}/invoke`;
|
|
13
13
|
export const CHAT_BASE_PATH = `/api/${LEGACY_NAMESPACE}/chat`;
|
|
14
|
+
/** A base to resolve an app-relative path against when checking it stays on the app; never contacted. */
|
|
15
|
+
export const SAME_ORIGIN_BASE = "https://app.invalid";
|
|
14
16
|
/** The platform's sign-in API, called with an app's own id. */
|
|
15
17
|
export const PLATFORM_AUTH_PATHS = {
|
|
16
18
|
signIn: "/api/app-auth/sign-in",
|
|
17
19
|
requestPasswordReset: "/api/app-auth/request-password-reset",
|
|
18
20
|
redeemPaneCode: "/api/app-auth/redeem",
|
|
19
|
-
config: "/api/app-auth/config"
|
|
21
|
+
config: "/api/app-auth/config",
|
|
22
|
+
/** "Continue with Google / Microsoft": the platform signs the person in and returns with a one-time code. */
|
|
23
|
+
socialSignIn: "/api/app-auth/social"
|
|
20
24
|
};
|
|
21
25
|
/** The platform's chat API: an app's chat route forwards any path beneath it, so a new widget route needs no SDK release. */
|
|
22
26
|
export const PLATFORM_CHAT_PATH = "/api/app-chat/v1/chat";
|
|
23
27
|
/** What this app's chat server forwards beyond the first contract: the widget, served to every app version, offers only these. */
|
|
24
28
|
export const CHAT_FEATURES_HEADER = "X-Mii-Chat-Features";
|
|
29
|
+
/** The gateway's mark on its 503 while the app's server is down: the request never reached the app. */
|
|
30
|
+
export const STARTING_HEADER = `x-${LEGACY_NAMESPACE}-app-starting`;
|
|
25
31
|
/** Every TanStack query the SDK owns sits under this key. */
|
|
26
32
|
export const QUERY_ROOT = NAMESPACE;
|
|
27
33
|
export const SESSION_COOKIE = `${LEGACY_NAMESPACE}_session`;
|
|
@@ -39,6 +45,13 @@ const PANE_PREFIX = `${LEGACY_NAMESPACE}-preview:`;
|
|
|
39
45
|
function paneMessage(name) {
|
|
40
46
|
return `${PANE_PREFIX}${name}`;
|
|
41
47
|
}
|
|
48
|
+
function namespaced(name) {
|
|
49
|
+
return `${NAMESPACE}:${name}`;
|
|
50
|
+
}
|
|
51
|
+
/** Keeps a message type's literal type in the object below. */
|
|
52
|
+
function exact(value) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
42
55
|
/** postMessage types between an app framed in the platform's App pane and the pane. */
|
|
43
56
|
export const PANE_MESSAGES = {
|
|
44
57
|
hello: paneMessage("hello"),
|
|
@@ -48,5 +61,13 @@ export const PANE_MESSAGES = {
|
|
|
48
61
|
route: paneMessage("route"),
|
|
49
62
|
error: paneMessage("error"),
|
|
50
63
|
picked: paneMessage("picked"),
|
|
51
|
-
located: paneMessage("located")
|
|
64
|
+
located: paneMessage("located"),
|
|
65
|
+
/** From @trylight/vite-plugin's client: the page mounted, or is about to reload. */
|
|
66
|
+
mounted: namespaced("mounted"),
|
|
67
|
+
willReload: namespaced("will-reload"),
|
|
68
|
+
/** From the gateway's page for a sandbox still waking. */
|
|
69
|
+
starting: namespaced("starting"),
|
|
70
|
+
appError: exact("app_error"),
|
|
71
|
+
/** How many requests are waiting out a dev server restart; the pane swaps in a reload only at zero. */
|
|
72
|
+
requestsHeld: namespaced("requests-held")
|
|
52
73
|
};
|
|
@@ -9,6 +9,7 @@ import { z } from "zod/v4";
|
|
|
9
9
|
import { EntityError, tableQueryKey } from "../entities.js";
|
|
10
10
|
import { CHANGES_PATH, QUERY_ROOT } from "./contract.js";
|
|
11
11
|
import { POLL_MS } from "./timing.js";
|
|
12
|
+
import { isStarting } from "./restart.js";
|
|
12
13
|
const changes = z.object({ cursor: z.string(), changedTables: z.array(z.string()) });
|
|
13
14
|
/**
|
|
14
15
|
* Polls `_changes` as a TanStack query. One the server breaks on (or answers
|
|
@@ -63,6 +64,8 @@ export async function fetchChanges(since) {
|
|
|
63
64
|
// fetch rejects only when no answer came back at all.
|
|
64
65
|
return "unreachable";
|
|
65
66
|
}
|
|
67
|
+
if (isStarting(response))
|
|
68
|
+
return "unreachable";
|
|
66
69
|
if (response.status === 401)
|
|
67
70
|
return "signed-out";
|
|
68
71
|
if (!response.ok)
|
|
@@ -20,6 +20,7 @@ import { z } from "zod/v4";
|
|
|
20
20
|
import { PANE_MESSAGES } from "./contract.js";
|
|
21
21
|
import { PLAZA_FRAME_ORIGINS } from "./index.js";
|
|
22
22
|
import { LiveData } from "./live-data-mount.js";
|
|
23
|
+
import { onHeldRequests } from "./restart.js";
|
|
23
24
|
const PLAZA_ORIGINS = PLAZA_FRAME_ORIGINS;
|
|
24
25
|
/** The platform → app. Anything else the platform posts is not for this bridge. */
|
|
25
26
|
const inbound = z.discriminatedUnion("type", [
|
|
@@ -163,6 +164,8 @@ export function MiiPaneBridge() {
|
|
|
163
164
|
return;
|
|
164
165
|
window.parent.postMessage({ type: PANE_MESSAGES.route, ...describe() }, plaza);
|
|
165
166
|
});
|
|
167
|
+
// The pane swaps in a reload only once nothing here is held.
|
|
168
|
+
const stopHeld = onHeldRequests(({ held, started }) => send({ type: PANE_MESSAGES.requestsHeld, held, started }));
|
|
166
169
|
document.body.appendChild(outline);
|
|
167
170
|
window.addEventListener("error", onError);
|
|
168
171
|
window.addEventListener("unhandledrejection", onRejection);
|
|
@@ -171,6 +174,7 @@ export function MiiPaneBridge() {
|
|
|
171
174
|
document.addEventListener("click", onClick, true);
|
|
172
175
|
return () => {
|
|
173
176
|
stopRouteWatch();
|
|
177
|
+
stopHeld();
|
|
174
178
|
window.removeEventListener("error", onError);
|
|
175
179
|
window.removeEventListener("unhandledrejection", onRejection);
|
|
176
180
|
window.removeEventListener("message", onMessage);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const isStarting: (response: Response) => boolean;
|
|
2
|
+
/** How many requests are waiting out a restart, and whether this change is one starting to. */
|
|
3
|
+
type HeldChange = {
|
|
4
|
+
readonly held: number;
|
|
5
|
+
readonly started: boolean;
|
|
6
|
+
};
|
|
7
|
+
/** Hears each request that starts or stops waiting out a restart. */
|
|
8
|
+
export declare function onHeldRequests(listener: (change: HeldChange) => void): () => void;
|
|
9
|
+
/**
|
|
10
|
+
* `fetch`, held through a restart: the gateway's starting answer is retried
|
|
11
|
+
* for any method, a failed connection only for a read (a write may have
|
|
12
|
+
* reached the app). Anything else, or the last try at RESTART_WAIT_MS, is
|
|
13
|
+
* the caller's as it came.
|
|
14
|
+
*/
|
|
15
|
+
export declare function fetchThroughRestart(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
16
|
+
/** Resolves after `ms`, or rejects with the signal's reason once it aborts. */
|
|
17
|
+
export declare function wait(ms: number, signal: AbortSignal | null): Promise<void>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// A request that meets the app's dev server restarting waits for it to come back.
|
|
2
|
+
import { STARTING_HEADER } from "./contract.js";
|
|
3
|
+
import { RESTART_FIRST_RETRY_MS, RESTART_MAX_RETRY_MS, RESTART_WAIT_MS } from "./timing.js";
|
|
4
|
+
export const isStarting = (response) => response.status === 503 && response.headers.has(STARTING_HEADER);
|
|
5
|
+
// File-local: HTTP's safe methods, which only this retry decision needs; resending one cannot repeat a write.
|
|
6
|
+
const SAFE_TO_RESEND = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
7
|
+
let held = 0;
|
|
8
|
+
const listeners = new Set();
|
|
9
|
+
/** Hears each request that starts or stops waiting out a restart. */
|
|
10
|
+
export function onHeldRequests(listener) {
|
|
11
|
+
listeners.add(listener);
|
|
12
|
+
return () => listeners.delete(listener);
|
|
13
|
+
}
|
|
14
|
+
function hold(started) {
|
|
15
|
+
held += started ? 1 : -1;
|
|
16
|
+
for (const listener of listeners)
|
|
17
|
+
listener({ held, started });
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* `fetch`, held through a restart: the gateway's starting answer is retried
|
|
21
|
+
* for any method, a failed connection only for a read (a write may have
|
|
22
|
+
* reached the app). Anything else, or the last try at RESTART_WAIT_MS, is
|
|
23
|
+
* the caller's as it came.
|
|
24
|
+
*/
|
|
25
|
+
export async function fetchThroughRestart(input, init = {}) {
|
|
26
|
+
const { method = input instanceof Request ? input.method : "GET", signal = null } = init;
|
|
27
|
+
const giveUpAt = Date.now() + RESTART_WAIT_MS;
|
|
28
|
+
let waiting = false;
|
|
29
|
+
try {
|
|
30
|
+
for (let attempt = 0;; attempt += 1) {
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(input instanceof Request ? input.clone() : input, init);
|
|
33
|
+
if (!isStarting(response) || Date.now() >= giveUpAt)
|
|
34
|
+
return response;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
// fetch rejects with a TypeError only when no answer came back; an abort is the caller's.
|
|
38
|
+
if (!(error instanceof TypeError) || !SAFE_TO_RESEND.has(method.toUpperCase()) || Date.now() >= giveUpAt)
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
if (!waiting) {
|
|
42
|
+
waiting = true;
|
|
43
|
+
hold(true);
|
|
44
|
+
}
|
|
45
|
+
await wait(Math.min(RESTART_FIRST_RETRY_MS * 2 ** attempt, RESTART_MAX_RETRY_MS), signal);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
if (waiting)
|
|
50
|
+
hold(false);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Resolves after `ms`, or rejects with the signal's reason once it aborts. */
|
|
54
|
+
export function wait(ms, signal) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
if (signal === null) {
|
|
57
|
+
setTimeout(resolve, ms);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const abort = () => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
reject(signal.reason);
|
|
63
|
+
};
|
|
64
|
+
const timer = setTimeout(() => {
|
|
65
|
+
signal.removeEventListener("abort", abort);
|
|
66
|
+
resolve();
|
|
67
|
+
}, ms);
|
|
68
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -6,6 +6,11 @@ export declare const CHECK_MS = 60000;
|
|
|
6
6
|
export declare const IDLE_MS = 5000;
|
|
7
7
|
/** After a reload: how long a list may take to be tall enough to scroll back; past it, it stays at the top. */
|
|
8
8
|
export declare const SCROLL_RESTORE_MS = 5000;
|
|
9
|
+
/** A request meeting a dev server restart waits this long for it at most, then fails with what it got. */
|
|
10
|
+
export declare const RESTART_WAIT_MS = 30000;
|
|
11
|
+
/** Backoff between a held request's tries: from the first, doubling up to the max. */
|
|
12
|
+
export declare const RESTART_FIRST_RETRY_MS = 250;
|
|
13
|
+
export declare const RESTART_MAX_RETRY_MS = 1000;
|
|
9
14
|
/** A contract read that failed: how long before asking again. */
|
|
10
15
|
export declare const INVOCATION_RETRY_MS = 1500;
|
|
11
16
|
/** How long a page remembers a contract invocation it started, across reloads. */
|
package/dist/platform/timing.js
CHANGED
|
@@ -7,6 +7,11 @@ export const CHECK_MS = 60_000;
|
|
|
7
7
|
export const IDLE_MS = 5_000;
|
|
8
8
|
/** After a reload: how long a list may take to be tall enough to scroll back; past it, it stays at the top. */
|
|
9
9
|
export const SCROLL_RESTORE_MS = 5_000;
|
|
10
|
+
/** A request meeting a dev server restart waits this long for it at most, then fails with what it got. */
|
|
11
|
+
export const RESTART_WAIT_MS = 30_000;
|
|
12
|
+
/** Backoff between a held request's tries: from the first, doubling up to the max. */
|
|
13
|
+
export const RESTART_FIRST_RETRY_MS = 250;
|
|
14
|
+
export const RESTART_MAX_RETRY_MS = 1_000;
|
|
10
15
|
/** A contract read that failed: how long before asking again. */
|
|
11
16
|
export const INVOCATION_RETRY_MS = 1500;
|
|
12
17
|
/** How long a page remembers a contract invocation it started, across reloads. */
|
package/dist/platform.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { FRAME_ANCESTORS, isAgentBrowser, mayCallServerFn, miiAnalyticsPolicy, miiErrorPolicy, PLAZA_FRAME_ORIGINS, type ServerFnName } from "./platform/index.js";
|
|
2
2
|
export { MiiPaneBridge } from "./platform/pane-bridge.js";
|
|
3
3
|
export { ReloadOnNewBuild } from "./platform/new-build-mount.js";
|
|
4
|
+
export { fetchThroughRestart } from "./platform/restart.js";
|
package/dist/platform.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// The platform contract a page and the global middleware need: framing by
|
|
2
2
|
// the platform's App pane, the server-function door, analytics and error policies,
|
|
3
|
-
// the pane bridge,
|
|
3
|
+
// the pane bridge, moving an open page onto a new build, and the fetch that
|
|
4
|
+
// waits out a dev server restart.
|
|
4
5
|
export { FRAME_ANCESTORS, isAgentBrowser, mayCallServerFn, miiAnalyticsPolicy, miiErrorPolicy, PLAZA_FRAME_ORIGINS } from "./platform/index.js";
|
|
5
6
|
export { MiiPaneBridge } from "./platform/pane-bridge.js";
|
|
6
7
|
export { ReloadOnNewBuild } from "./platform/new-build-mount.js";
|
|
8
|
+
export { fetchThroughRestart } from "./platform/restart.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trylight/app",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "The Light SDK every v2 org app depends on: entities (Drizzle tables with a typed client), sign-in, contracts and in-app chat.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"url": "https://github.com/shubh9/mii.git",
|
|
13
13
|
"directory": "packages/app-sdk"
|
|
14
14
|
},
|
|
15
|
-
"packageManager": "pnpm@11.0.9",
|
|
16
15
|
"files": [
|
|
17
16
|
"dist",
|
|
18
17
|
"bin"
|
|
@@ -42,15 +41,6 @@
|
|
|
42
41
|
"default": "./dist/vite.js"
|
|
43
42
|
}
|
|
44
43
|
},
|
|
45
|
-
"scripts": {
|
|
46
|
-
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
47
|
-
"typecheck": "tsc --noEmit",
|
|
48
|
-
"lint": "eslint src test --max-warnings 0",
|
|
49
|
-
"test": "node --test --import tsx \"test/**/*.test.ts\"",
|
|
50
|
-
"check": "tsx scripts/write-brand.ts --check && pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run build",
|
|
51
|
-
"prepack": "pnpm run build",
|
|
52
|
-
"brand": "tsx scripts/write-brand.ts"
|
|
53
|
-
},
|
|
54
44
|
"peerDependencies": {
|
|
55
45
|
"@tanstack/react-query": ">=5.69",
|
|
56
46
|
"@tanstack/react-router": ">=1.170",
|
|
@@ -84,5 +74,13 @@
|
|
|
84
74
|
"typescript-eslint": "^8.70.1",
|
|
85
75
|
"vite": "^8.2.0",
|
|
86
76
|
"zod": "^4.6.5"
|
|
77
|
+
},
|
|
78
|
+
"scripts": {
|
|
79
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
80
|
+
"typecheck": "tsc --noEmit",
|
|
81
|
+
"lint": "eslint src test --max-warnings 0",
|
|
82
|
+
"test": "node --test --import tsx \"test/**/*.test.ts\"",
|
|
83
|
+
"check": "tsx scripts/write-brand.ts --check && pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run build",
|
|
84
|
+
"brand": "tsx scripts/write-brand.ts"
|
|
87
85
|
}
|
|
88
|
-
}
|
|
86
|
+
}
|