Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions api_tests/api_test_live_suspended_caller/ext/api/activate.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* @Project: @cldmv/slothlet
* @Filename: /api_tests/api_test_live_suspended_caller/ext/api/activate.mjs
* @Date: 2026-09-28 18:00:00 -07:00 (1790644800)
* @Author: Nate Corcoran <CLDMV>
* @Email: <Shinrai@users.noreply.github.com>
* -----
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
* @Last modified time: 2026-09-28 18:00:00 -07:00 (1790644800)
* -----
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
*/

/**
* @fileoverview The extension's per-entity activation routine (`ext.activate`, invoked by the host
* as `ext.activate.for(id)`).
*
* @module api_test_live_suspended_caller.ext.activate
*/
import { self } from "@cldmv/slothlet/runtime";
import { afterGate, afterGateCaptured } from "./resume.mjs";
import { attempt } from "../../shared/attempt.mjs";

/**
* Activate for one scenario. A plain function on purpose: the `sibling` scenario hands back a
* promise created in a sibling file, so this file's frame is gone by the time that work resumes.
* @param {string} scenario - Which activation to perform.
* @param {Promise<void>} [gate] - Gate to wait on before acting.
* @returns {Promise<*>} The activation's result.
*/
export function activate(scenario, gate) {
if (scenario === "sibling") return afterGate(gate);
if (scenario === "siblingCaptured") return afterGateCaptured(gate);
if (scenario === "awaited") return awaitSibling(gate);
if (scenario === "hold") return holdThenRender(gate);
return registerThenSettle(scenario);
}

/**
* Register a pinned render callback (for `pinned`), then settle.
* @param {string} scenario - Activation scenario.
* @returns {Promise<string>} `"activated"`.
*/
async function registerThenSettle(scenario) {
if (scenario === "pinned") {
self.registry.register(self.slothlet.lockCaller(() => attempt(() => self.ext.views.running.render("pinned"))));
self.registry.register(
self.slothlet.lockCaller(() =>
attempt(
() => self.ext.usage.cache.context.subscribe(() => {}),
() => "subscribed"
)
)
);
}
await null;
return "activated";
}

/**
* Await the sibling file's work from this file, so a frame of this file stays on the resumed stack.
* @param {Promise<void>} gate - Released by the test.
* @returns {Promise<Array<string>>} The sibling's results.
*/
async function awaitSibling(gate) {
return await afterGate(gate);
}

/**
* Stay suspended on the gate, then reach the extension's own view from this file.
* @param {Promise<void>} gate - Released by the test.
* @returns {Promise<string>} The render result, or `"denied"`.
*/
async function holdThenRender(gate) {
await gate;
return attempt(() => self.ext.views.running.render("held"));
}
44 changes: 44 additions & 0 deletions api_tests/api_test_live_suspended_caller/ext/api/entry.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @Project: @cldmv/slothlet
* @Filename: /api_tests/api_test_live_suspended_caller/ext/api/entry.mjs
* @Date: 2026-09-28 18:00:00 -07:00 (1790644800)
* @Author: Nate Corcoran <CLDMV>
* @Email: <Shinrai@users.noreply.github.com>
* -----
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
* @Last modified time: 2026-09-28 18:00:00 -07:00 (1790644800)
* -----
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
*/

/**
* @fileoverview The extension's public entry β€” the one leaf the host may call directly.
* @module api_test_live_suspended_caller.ext.entry
*/
import { self } from "@cldmv/slothlet/runtime";
import { attempt } from "../../shared/attempt.mjs";

/**
* Synchronously reach the extension's own leaves. Both calls are made before this returns; only
* their results are awaited.
* @returns {Promise<Array<string>>} The render and subscribe results, each possibly `"denied"`.
*/
export function start() {
return Promise.all([
attempt(() => self.ext.views.running.render("nested")),
attempt(
() => self.ext.usage.cache.context.subscribe(() => {}),
() => "subscribed"
)
]);
}

/**
* Stay suspended on the gate β€” a second api path of this module in flight at the same time.
* @param {Promise<void>} gate - Released by the test.
* @returns {Promise<string>} `"waited"`.
*/
export async function wait(gate) {
await gate;
return "waited";
}
60 changes: 60 additions & 0 deletions api_tests/api_test_live_suspended_caller/ext/api/resume.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @Project: @cldmv/slothlet
* @Filename: /api_tests/api_test_live_suspended_caller/ext/api/resume.mjs
* @Date: 2026-09-28 18:00:00 -07:00 (1790644800)
* @Author: Nate Corcoran <CLDMV>
* @Email: <Shinrai@users.noreply.github.com>
* -----
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
* @Last modified time: 2026-09-28 18:00:00 -07:00 (1790644800)
* -----
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
*/

/**
* @fileoverview A sibling file of the extension where an activation resumes after its `await`.
* @module api_test_live_suspended_caller.ext.resume
*/
import { self } from "@cldmv/slothlet/runtime";
import { attempt } from "../../shared/attempt.mjs";

/**
* Wait on the gate, then reach the extension's own leaves from this sibling file.
* @param {Promise<void>} gate - Released by the test.
* @returns {Promise<Array<string>>} The render and subscribe results, each possibly `"denied"`.
*/
export async function afterGate(gate) {
await gate;
return Promise.all([
attempt(() => self.ext.views.running.render("sibling")),
attempt(
() => self.ext.usage.cache.context.subscribe(() => {}),
() => "subscribed"
)
]);
}

/**
* Take references to the extension's own leaves and to the permissions namespace up front, wait on
* the gate, then use them from this sibling file. Each use is gated on whoever is running when it is
* made.
* @param {Promise<void>} gate - Released by the test.
* @returns {Promise<Array<string>>} The render, subscribe and rule-read results, each possibly `"denied"`.
*/
export async function afterGateCaptured(gate) {
const render = self.ext.views.running.render;
const subscribe = self.ext.usage.cache.context.subscribe;
const permissions = self.slothlet.permissions;
await gate;
return Promise.all([
attempt(() => render("sibling")),
attempt(
() => subscribe(() => {}),
() => "subscribed"
),
attempt(
() => permissions.global.rulesForPath("ext.views.running.render"),
() => "read"
)
]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @Project: @cldmv/slothlet
* @Filename: /api_tests/api_test_live_suspended_caller/ext/api/usage/cache/context.mjs
* @Date: 2026-09-28 18:00:00 -07:00 (1790644800)
* @Author: Nate Corcoran <CLDMV>
* @Email: <Shinrai@users.noreply.github.com>
* -----
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
* @Last modified time: 2026-09-28 18:00:00 -07:00 (1790644800)
* -----
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
*/

/**
* @fileoverview The extension's context cache β€” reachable by the extension itself only.
* @module api_test_live_suspended_caller.ext.usage.cache.context
*/

const subscribers = new Set();

/**
* Subscribe to updates.
* @param {Function} fn - Subscriber.
* @returns {Function} Unsubscribe.
*/
export function subscribe(fn) {
subscribers.add(fn);
return () => subscribers.delete(fn);
}
26 changes: 26 additions & 0 deletions api_tests/api_test_live_suspended_caller/ext/api/views/running.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* @Project: @cldmv/slothlet
* @Filename: /api_tests/api_test_live_suspended_caller/ext/api/views/running.mjs
* @Date: 2026-09-28 18:00:00 -07:00 (1790644800)
* @Author: Nate Corcoran <CLDMV>
* @Email: <Shinrai@users.noreply.github.com>
* -----
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
* @Last modified time: 2026-09-28 18:00:00 -07:00 (1790644800)
* -----
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
*/

/**
* @fileoverview The extension's view β€” reachable by the extension itself, never by the host's `run.*`.
* @module api_test_live_suspended_caller.ext.views.running
*/

/**
* Render a label.
* @param {string} label - What to render.
* @returns {string} `"rendered:<label>"`.
*/
export function render(label) {
return `rendered:${label}`;
}
51 changes: 51 additions & 0 deletions api_tests/api_test_live_suspended_caller/registry.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @Project: @cldmv/slothlet
* @Filename: /api_tests/api_test_live_suspended_caller/registry.mjs
* @Date: 2026-09-28 18:00:00 -07:00 (1790644800)
* @Author: Nate Corcoran <CLDMV>
* @Email: <Shinrai@users.noreply.github.com>
* -----
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
* @Last modified time: 2026-09-28 18:00:00 -07:00 (1790644800)
* -----
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
*/

/**
* @fileoverview Host registry: holds callbacks an extension registers and invokes them later.
*
* @description
* Mirrors a host view registry β€” an extension registers a render callback during activation and the
* host renders it later, from the host's own flow, after the activation has settled.
*
* @module api_test_live_suspended_caller.registry
*/
import { attempt } from "./shared/attempt.mjs";

const callbacks = [];

/**
* Store a callback for a later {@link renderAll}.
* @param {Function} callback - Callback to hold.
* @returns {number} Number of held callbacks.
*/
export function register(callback) {
callbacks.push(callback);
return callbacks.length;
}

/**
* Invoke and drop every held callback. Each is invoked synchronously; only the results are awaited.
* @returns {Promise<Array<*>>} Each callback's result, or `"denied"`.
*/
export function renderAll() {
return Promise.all(callbacks.splice(0).map((callback) => attempt(callback)));
}

/**
* Number of held callbacks β€” a call the host's own leaves are permitted to make.
* @returns {number} Held callback count.
*/
export function list() {
return callbacks.length;
}
31 changes: 31 additions & 0 deletions api_tests/api_test_live_suspended_caller/rogue/api/fanout.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @Project: @cldmv/slothlet
* @Filename: /api_tests/api_test_live_suspended_caller/rogue/api/fanout.mjs
* @Date: 2026-09-28 18:00:00 -07:00 (1790644800)
* @Author: Nate Corcoran <CLDMV>
* @Email: <Shinrai@users.noreply.github.com>
* -----
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
* @Last modified time: 2026-09-28 18:00:00 -07:00 (1790644800)
* -----
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
*/

/**
* @fileoverview Unprivileged leaf that starts a privileged call and, without awaiting it, makes its own.
* @module api_test_live_suspended_caller.rogue.fanout
*/
import { self } from "@cldmv/slothlet/runtime";
import { attempt } from "../../shared/attempt.mjs";

/**
* Start `run.quick` (privileged, suspends at once), then synchronously try the extension's view.
* The live runtime's caller field names `run.quick` at that moment; the call is still this leaf's.
* @returns {Promise<string>} The render result, or `"denied"`.
*/
export async function fanout() {
const pending = self.run.quick();
const result = attempt(() => self.ext.views.running.render("fanout"));
await pending;
return result;
}
27 changes: 27 additions & 0 deletions api_tests/api_test_live_suspended_caller/rogue/api/forge.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @Project: @cldmv/slothlet
* @Filename: /api_tests/api_test_live_suspended_caller/rogue/api/forge.mjs
* @Date: 2026-09-28 18:00:00 -07:00 (1790644800)
* @Author: Nate Corcoran <CLDMV>
* @Email: <Shinrai@users.noreply.github.com>
* -----
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
* @Last modified time: 2026-09-28 18:00:00 -07:00 (1790644800)
* -----
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
*/

/**
* @fileoverview Unprivileged leaf whose call resumes in a function NAMED after the extension's source path.
* @module api_test_live_suspended_caller.rogue.forge
*/
import { runForged } from "./forged.mjs";

/**
* Hand back the promise of the path-named function in the sibling file.
* @param {Promise<void>} gate - Released by the test.
* @returns {Promise<string>} The render result, or `"denied"`.
*/
export function forge(gate) {
return runForged(gate);
}
Loading
Loading