mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
chord/next/app: startApp and startWorker with the fixed app layout, worker kinds, AppInfo and WorkerInfo; sessions (hashed tokens, rolling cookies, bearer tokens), the one-time owner link, the owner token file, device pairing; Files per host with ordered chunked uploads; export and import per address; plugin roots, installs and applyChanges; optional users, invite and ephemeral login facets. HTTP: deny by default (public routes and sockets), remoteAddress, readSignedCookie. Examples 53-55.
66 lines
3 KiB
TypeScript
66 lines
3 KiB
TypeScript
// Export and import per address (slice 13 §4), with example 53's notes app. `app.exportAddress` stops a project's
|
|
// worker and writes one archive: a manifest (app, API version, format), the project's store export and its files.
|
|
// `app.importAddress` makes another address from it (refusing one that has data) and the next request starts its
|
|
// worker on the imported store and files. Archives of another app are refused.
|
|
// Run from packages/chord:
|
|
// node --conditions=source --experimental-strip-types test/next/examples/55-app-export/main.ts
|
|
import { mkdtemp, rm, stat } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { BACKGROUND_CONTEXT as context } from "../../../../src/context/index.ts";
|
|
import { method, obj, Project, remote, str } from "../53-app/app/v1.ts";
|
|
import { startNotes } from "../53-app/start.ts";
|
|
|
|
// The notes plugin's contract, declared again with the API's builders (contracts are data: the same name, version and
|
|
// schema bind). The plugin's own files import `@notes/app/v1`, a host package this process has no module path for.
|
|
const Notes = remote("notes.notes", {
|
|
version: 1,
|
|
params: { id: str() },
|
|
hostAddress: ({ id }) => Project({ id }),
|
|
variants: {
|
|
main: {
|
|
state: { text: str(), start: str() },
|
|
methods: {
|
|
write: method(str()),
|
|
attach: method(obj({ name: str(), body: str() }), str()),
|
|
attachment: method(str(), str()),
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const base = await mkdtemp(join(tmpdir(), "notes-"));
|
|
const app = await startNotes(join(base, "data"), context);
|
|
|
|
// 1. Project p1 gets a text and an attachment (its worker's store and files). The server reaches it as `Notes`.
|
|
const p1 = await app.host.get(Notes, { id: "p1" }, context);
|
|
await p1.write("the plan", context);
|
|
const attachment = await p1.attach({ name: "plan.txt", body: "step 1, step 2" }, context);
|
|
p1.release();
|
|
|
|
// 2. Export p1: its worker stops, the archive is written (mode 0600), and p1 goes on (the next hold starts it).
|
|
const archive = join(base, "p1.tar");
|
|
await app.exportAddress(Project({ id: "p1" }), archive, context);
|
|
console.log(
|
|
"1. exported:",
|
|
`${(await stat(archive)).size} bytes`,
|
|
`mode ${((await stat(archive)).mode & 0o777).toString(8)}`,
|
|
);
|
|
|
|
// 3. Import it as p2: a copy with p1's text and attachment, its own worker and directory.
|
|
await app.importAddress(archive, Project({ id: "p2" }), context);
|
|
const p2 = await app.host.get(Notes, { id: "p2" }, context);
|
|
console.log("2. p2:", JSON.stringify(p2.text.value), JSON.stringify(await p2.attachment(attachment, context)));
|
|
await p2.write("p2's own plan", context);
|
|
p2.release();
|
|
const again = await app.host.get(Notes, { id: "p1" }, context);
|
|
console.log(" p1 unchanged:", JSON.stringify(again.text.value));
|
|
again.release();
|
|
|
|
// 4. An address that has data is never overwritten.
|
|
await app
|
|
.importAddress(archive, Project({ id: "p1" }), context)
|
|
.catch((error: Error) => console.log("3. refused:", error.message.replace(base, "…")));
|
|
|
|
await app.close(context);
|
|
await rm(base, { recursive: true, force: true });
|