feat: add standalone and HiveCast SDK demos
This commit is contained in:
parent
6f516fe2f8
commit
f3fd4e109f
33
README.md
33
README.md
@ -156,6 +156,37 @@ configure broker access, run actors, and invoke them without importing provider
|
|||||||
overlay packages into the SDK layer. Managed-host promotion is optional and is
|
overlay packages into the SDK layer. Managed-host promotion is optional and is
|
||||||
only exercised when `MATRIX_SDK_RUNTIME_HOST_BIN` is supplied.
|
only exercised when `MATRIX_SDK_RUNTIME_HOST_BIN` is supplied.
|
||||||
|
|
||||||
|
## Demos
|
||||||
|
|
||||||
|
Standalone SDK demo, no provider account or key required:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm demo:standalone
|
||||||
|
```
|
||||||
|
|
||||||
|
This builds the SDK and `examples/standalone-greeter`, starts the actor with
|
||||||
|
`matrix run`, calls it with `matrix invoke`, and shuts it down.
|
||||||
|
|
||||||
|
HiveCast API-key login/config demo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export MATRIX_CLOUD=https://dev.hivecast.ai
|
||||||
|
export MATRIX_SPACE=<your-space-root>
|
||||||
|
export MATRIX_API_KEY=<your-api-key>
|
||||||
|
pnpm demo:hivecast-login
|
||||||
|
```
|
||||||
|
|
||||||
|
This validates the key with HiveCast and writes the SDK package-local
|
||||||
|
`.matrix/config.json` plus `.matrix/credentials/matrix-api-key.json` shape
|
||||||
|
without printing or committing the key. By default the demo uses a temporary
|
||||||
|
home and deletes it after success.
|
||||||
|
|
||||||
|
Current boundary: this HiveCast demo proves SDK-side key validation and config
|
||||||
|
storage. It does not yet prove a package running directly on a HiveCast-issued
|
||||||
|
broker credential. That requires the provider-owned login/credential-exchange
|
||||||
|
slice so `matrix login` can replace manual `matrix configure` and issue/store
|
||||||
|
the broker credentials needed by the SDK transport.
|
||||||
|
|
||||||
## Repository Layout
|
## Repository Layout
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@ -169,6 +200,8 @@ packages/
|
|||||||
omega-core/
|
omega-core/
|
||||||
sdk/
|
sdk/
|
||||||
scripts/
|
scripts/
|
||||||
|
demo:standalone
|
||||||
|
demo:hivecast-login
|
||||||
prove-external-consumer.mjs
|
prove-external-consumer.mjs
|
||||||
prove-sdk-cli-uat.mjs
|
prove-sdk-cli-uat.mjs
|
||||||
```
|
```
|
||||||
|
|||||||
@ -23,6 +23,13 @@ The SDK package surface is standalone enough for the current authoring proof:
|
|||||||
The proof script also checks that generated author-package files do not contain
|
The proof script also checks that generated author-package files do not contain
|
||||||
legacy monorepo paths or upper-layer package dependencies.
|
legacy monorepo paths or upper-layer package dependencies.
|
||||||
|
|
||||||
|
Checked-in demos now cover the same boundary in user-facing form:
|
||||||
|
|
||||||
|
- `pnpm demo:standalone` runs `examples/standalone-greeter` without provider
|
||||||
|
credentials.
|
||||||
|
- `pnpm demo:hivecast-login` validates a user-supplied HiveCast API key and
|
||||||
|
writes package-local SDK config/credentials in a temporary demo home.
|
||||||
|
|
||||||
## Cleanup Completed
|
## Cleanup Completed
|
||||||
|
|
||||||
- Root README now describes this as a provider-neutral SDK, not a HiveCast
|
- Root README now describes this as a provider-neutral SDK, not a HiveCast
|
||||||
@ -87,3 +94,14 @@ Expected:
|
|||||||
- `legacyMonorepoPathReferences` is `false`.
|
- `legacyMonorepoPathReferences` is `false`.
|
||||||
- `managedHostProof` is skipped unless `MATRIX_SDK_RUNTIME_HOST_BIN` is supplied.
|
- `managedHostProof` is skipped unless `MATRIX_SDK_RUNTIME_HOST_BIN` is supplied.
|
||||||
- `prove` builds, tests, type-checks, and packs all workspace packages.
|
- `prove` builds, tests, type-checks, and packs all workspace packages.
|
||||||
|
|
||||||
|
Demo checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm demo:standalone
|
||||||
|
pnpm demo:hivecast-login -- --check
|
||||||
|
```
|
||||||
|
|
||||||
|
`demo:hivecast-login -- --check` proves the demo wiring without requiring a
|
||||||
|
secret. A real HiveCast proof requires `MATRIX_API_KEY`, `MATRIX_CLOUD`, and
|
||||||
|
`MATRIX_SPACE` from the user.
|
||||||
|
|||||||
5
examples/hivecast-login/.env.example
Normal file
5
examples/hivecast-login/.env.example
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Copy this file outside git-tracked secrets handling or export these variables
|
||||||
|
# in your shell. Do not commit real keys.
|
||||||
|
MATRIX_CLOUD=https://dev.hivecast.ai
|
||||||
|
MATRIX_SPACE=your-space-root
|
||||||
|
MATRIX_API_KEY=hc_your_key_here
|
||||||
3
examples/hivecast-login/.gitignore
vendored
Normal file
3
examples/hivecast-login/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.env
|
||||||
|
.matrix/
|
||||||
|
demo-home/
|
||||||
42
examples/hivecast-login/README.md
Normal file
42
examples/hivecast-login/README.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# HiveCast API-Key Login Demo
|
||||||
|
|
||||||
|
This demo proves the SDK-side HiveCast credential shape without committing a
|
||||||
|
secret.
|
||||||
|
|
||||||
|
It validates a user-supplied HiveCast API key against the configured HiveCast
|
||||||
|
cloud, then writes the same package-local `.matrix` config/credential files that
|
||||||
|
`matrix configure` writes. This is the current API-key login shape.
|
||||||
|
|
||||||
|
It does **not** prove hosted broker execution yet. The SDK still needs a
|
||||||
|
provider-owned credential exchange/login command before a package can use a
|
||||||
|
HiveCast-issued broker JWT directly from this repo.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export MATRIX_CLOUD=https://dev.hivecast.ai
|
||||||
|
export MATRIX_SPACE=<your-space-root>
|
||||||
|
export MATRIX_API_KEY=<your-api-key>
|
||||||
|
|
||||||
|
pnpm demo:hivecast-login
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MATRIX_DEMO_HOME=/tmp/my-sdk-hivecast-demo pnpm demo:hivecast-login
|
||||||
|
MATRIX_DEMO_KEEP=1 pnpm demo:hivecast-login
|
||||||
|
```
|
||||||
|
|
||||||
|
The key is never printed. By default the temporary demo home is deleted after a
|
||||||
|
successful run. Set `MATRIX_DEMO_KEEP=1` to inspect the generated `.matrix`
|
||||||
|
files locally.
|
||||||
|
|
||||||
|
## Check Script Wiring Without A Key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm demo:hivecast-login -- --check
|
||||||
|
```
|
||||||
|
|
||||||
|
This only verifies that the demo is installed and intentionally refuses to run
|
||||||
|
without credentials.
|
||||||
120
examples/hivecast-login/run-demo.mjs
Normal file
120
examples/hivecast-login/run-demo.mjs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { mkdtempSync, rmSync, existsSync, readFileSync } from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { resolve, join } from 'node:path';
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
|
||||||
|
const args = new Set(process.argv.slice(2));
|
||||||
|
const checkOnly = args.has('--check');
|
||||||
|
const repoRoot = resolve(new URL('../..', import.meta.url).pathname);
|
||||||
|
const matrixCli = resolve(repoRoot, 'packages', 'cli', 'dist', 'bin', 'matrix.js');
|
||||||
|
|
||||||
|
function envString(...names) {
|
||||||
|
for (const name of names) {
|
||||||
|
const value = process.env[name];
|
||||||
|
if (typeof value === 'string' && value.trim()) return value.trim();
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(command, commandArgs, options = {}) {
|
||||||
|
const result = spawnSync(command, commandArgs, {
|
||||||
|
cwd: options.cwd ?? repoRoot,
|
||||||
|
stdio: options.stdio ?? 'pipe',
|
||||||
|
env: process.env,
|
||||||
|
encoding: 'utf8',
|
||||||
|
});
|
||||||
|
if (result.error) throw result.error;
|
||||||
|
if (result.status !== 0) {
|
||||||
|
throw new Error(`${command} ${commandArgs.join(' ')} failed with exit ${result.status}\n${result.stderr}${result.stdout}`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveConfig() {
|
||||||
|
const key = envString('MATRIX_API_KEY', 'HIVECAST_API_KEY', 'HIVECAST_TEST_KEY');
|
||||||
|
const cloud = envString('MATRIX_CLOUD', 'HIVECAST_CLOUD', 'HIVECAST_TEST_CLOUD');
|
||||||
|
const space = envString('MATRIX_SPACE', 'HIVECAST_SPACE', 'HIVECAST_TEST_SPACE');
|
||||||
|
const provided = [key, cloud, space].filter(Boolean).length;
|
||||||
|
if (provided > 0 && provided < 3) {
|
||||||
|
throw new Error('Provide MATRIX_API_KEY, MATRIX_CLOUD, and MATRIX_SPACE together');
|
||||||
|
}
|
||||||
|
return key && cloud && space
|
||||||
|
? { key, cloud: cloud.replace(/\/+$/g, ''), space }
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateHiveCastKey(config) {
|
||||||
|
const response = await fetch(`${config.cloud}/api/install/key-info`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${config.key}`,
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ space: config.space }),
|
||||||
|
});
|
||||||
|
const text = await response.text();
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HiveCast key validation failed (${response.status}): ${text}`);
|
||||||
|
}
|
||||||
|
const body = text.trim() ? JSON.parse(text) : {};
|
||||||
|
if (body?.ok !== true) {
|
||||||
|
throw new Error(`HiveCast key validation did not return ok:true: ${text}`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
keyId: body.key?.id ?? body.id ?? null,
|
||||||
|
fingerprint: body.key?.fingerprint ?? body.fingerprint ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = resolveConfig();
|
||||||
|
if (!config) {
|
||||||
|
const message = 'HiveCast demo requires MATRIX_API_KEY, MATRIX_CLOUD, and MATRIX_SPACE';
|
||||||
|
if (checkOnly) {
|
||||||
|
console.log(JSON.stringify({ ok: true, demo: 'hivecast-login', credentialsRequired: true, message }, null, 2));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
if (checkOnly) {
|
||||||
|
console.log(JSON.stringify({ ok: true, demo: 'hivecast-login', credentialsAvailable: true }, null, 2));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const demoHome = process.env.MATRIX_DEMO_HOME
|
||||||
|
? resolve(process.env.MATRIX_DEMO_HOME)
|
||||||
|
: mkdtempSync(join(tmpdir(), 'matrix-sdk-hivecast-login.'));
|
||||||
|
const keep = process.env.MATRIX_DEMO_KEEP === '1' || Boolean(process.env.MATRIX_DEMO_HOME);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const validation = await validateHiveCastKey(config);
|
||||||
|
run(process.execPath, [
|
||||||
|
matrixCli, 'configure',
|
||||||
|
'--dir', demoHome,
|
||||||
|
'--key', config.key,
|
||||||
|
'--cloud', config.cloud,
|
||||||
|
'--space', config.space,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const credentialPath = join(demoHome, '.matrix', 'credentials', 'matrix-api-key.json');
|
||||||
|
const configPath = join(demoHome, '.matrix', 'config.json');
|
||||||
|
if (!existsSync(credentialPath)) throw new Error(`Credential file was not written: ${credentialPath}`);
|
||||||
|
if (!existsSync(configPath)) throw new Error(`Config file was not written: ${configPath}`);
|
||||||
|
const writtenCredential = JSON.parse(readFileSync(credentialPath, 'utf8'));
|
||||||
|
if (writtenCredential.key !== config.key) throw new Error('Written credential key does not match input key');
|
||||||
|
if (writtenCredential.cloud !== config.cloud) throw new Error('Written credential cloud does not match input cloud');
|
||||||
|
if (writtenCredential.space !== config.space) throw new Error('Written credential space does not match input space');
|
||||||
|
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
ok: true,
|
||||||
|
demo: 'hivecast-login',
|
||||||
|
cloud: config.cloud,
|
||||||
|
space: config.space,
|
||||||
|
keyValidated: true,
|
||||||
|
keyId: validation.keyId,
|
||||||
|
fingerprint: validation.fingerprint,
|
||||||
|
demoHome: keep ? demoHome : '(temporary, removed)',
|
||||||
|
credentialPath: keep ? credentialPath : '(temporary, removed)',
|
||||||
|
}, null, 2));
|
||||||
|
} finally {
|
||||||
|
if (!keep) rmSync(demoHome, { recursive: true, force: true });
|
||||||
|
}
|
||||||
2
examples/standalone-greeter/.gitignore
vendored
Normal file
2
examples/standalone-greeter/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.matrix/
|
||||||
|
dist/
|
||||||
20
examples/standalone-greeter/README.md
Normal file
20
examples/standalone-greeter/README.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Standalone Greeter Demo
|
||||||
|
|
||||||
|
This demo proves the SDK can run without HiveCast, without a managed platform,
|
||||||
|
and without committed credentials.
|
||||||
|
|
||||||
|
Run from the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm demo:standalone
|
||||||
|
```
|
||||||
|
|
||||||
|
What it proves:
|
||||||
|
|
||||||
|
- the SDK packages build from this repo,
|
||||||
|
- this package builds against `@open-matrix/core`,
|
||||||
|
- `matrix run` starts the actor from the package manifest,
|
||||||
|
- `matrix invoke demo.greeter echo ...` calls the actor over the SDK local
|
||||||
|
runtime path,
|
||||||
|
- all runtime state is written under the demo package's local `.matrix/`
|
||||||
|
directory.
|
||||||
28
examples/standalone-greeter/matrix.json
Normal file
28
examples/standalone-greeter/matrix.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "matrix-sdk-example-standalone-greeter",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"runtime": {
|
||||||
|
"language": "typescript",
|
||||||
|
"entry": "./dist/index.js",
|
||||||
|
"factory": {
|
||||||
|
"kind": "factory",
|
||||||
|
"export": "createStandaloneGreeterActor",
|
||||||
|
"instance": {
|
||||||
|
"id": "RUNTIME-HOST-DEMO-GREETER",
|
||||||
|
"class": "service",
|
||||||
|
"rootKind": "component",
|
||||||
|
"componentType": "GreeterActor",
|
||||||
|
"autoStart": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"type": "GreeterActor",
|
||||||
|
"export": "GreeterActor",
|
||||||
|
"mount": "demo.greeter",
|
||||||
|
"surface": "headless",
|
||||||
|
"accepts": ["echo", "getStatus"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
15
examples/standalone-greeter/package.json
Normal file
15
examples/standalone-greeter/package.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "matrix-sdk-example-standalone-greeter",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -p tsconfig.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@open-matrix/core": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
113
examples/standalone-greeter/run-demo.mjs
Normal file
113
examples/standalone-greeter/run-demo.mjs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { spawn, spawnSync } from 'node:child_process';
|
||||||
|
import { createServer } from 'node:http';
|
||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const repoRoot = resolve(__dirname, '..', '..');
|
||||||
|
const demoDir = __dirname;
|
||||||
|
const matrixCli = resolve(repoRoot, 'packages', 'cli', 'dist', 'bin', 'matrix.js');
|
||||||
|
|
||||||
|
function run(command, args, options = {}) {
|
||||||
|
const result = spawnSync(command, args, {
|
||||||
|
cwd: options.cwd ?? repoRoot,
|
||||||
|
stdio: options.stdio ?? 'inherit',
|
||||||
|
env: process.env,
|
||||||
|
encoding: 'utf8',
|
||||||
|
});
|
||||||
|
if (result.error) throw result.error;
|
||||||
|
if (result.status !== 0) {
|
||||||
|
throw new Error(`${command} ${args.join(' ')} failed with exit ${result.status}`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function freePort() {
|
||||||
|
return await new Promise((resolvePort, reject) => {
|
||||||
|
const server = createServer();
|
||||||
|
server.on('error', reject);
|
||||||
|
server.listen(0, '127.0.0.1', () => {
|
||||||
|
const address = server.address();
|
||||||
|
if (!address || typeof address === 'string') {
|
||||||
|
server.close(() => reject(new Error('Could not allocate demo port')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { port } = address;
|
||||||
|
server.close(() => resolvePort(port));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForHealth(port, child) {
|
||||||
|
const deadline = Date.now() + 10_000;
|
||||||
|
let lastError;
|
||||||
|
while (Date.now() < deadline) {
|
||||||
|
if (child.exitCode !== null) throw new Error(`matrix run exited early with ${child.exitCode}`);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`http://127.0.0.1:${port}/healthz`);
|
||||||
|
if (response.ok) return;
|
||||||
|
lastError = new Error(`healthz returned ${response.status}`);
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
}
|
||||||
|
await new Promise((resolveDelay) => setTimeout(resolveDelay, 100));
|
||||||
|
}
|
||||||
|
throw lastError ?? new Error('Timed out waiting for matrix run healthz');
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop(child) {
|
||||||
|
return new Promise((resolveStop) => {
|
||||||
|
if (child.exitCode !== null) {
|
||||||
|
resolveStop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
child.once('exit', () => resolveStop());
|
||||||
|
child.kill('SIGTERM');
|
||||||
|
setTimeout(() => {
|
||||||
|
if (child.exitCode === null) child.kill('SIGKILL');
|
||||||
|
}, 3000).unref();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = await freePort();
|
||||||
|
let runner;
|
||||||
|
|
||||||
|
try {
|
||||||
|
run('pnpm', ['run', 'build']);
|
||||||
|
run('pnpm', ['--filter', 'matrix-sdk-example-standalone-greeter', 'run', 'build']);
|
||||||
|
|
||||||
|
runner = spawn(process.execPath, [
|
||||||
|
matrixCli, 'run', demoDir,
|
||||||
|
'--dir', demoDir,
|
||||||
|
'--port', String(port),
|
||||||
|
], {
|
||||||
|
cwd: repoRoot,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
env: process.env,
|
||||||
|
});
|
||||||
|
runner.stderr.on('data', (chunk) => process.stderr.write(chunk));
|
||||||
|
await waitForHealth(port, runner);
|
||||||
|
|
||||||
|
const invoked = run(process.execPath, [
|
||||||
|
matrixCli, 'invoke',
|
||||||
|
'demo.greeter',
|
||||||
|
'echo',
|
||||||
|
'{"message":"hello-from-standalone-demo"}',
|
||||||
|
'--dir', demoDir,
|
||||||
|
'--port', String(port),
|
||||||
|
], { cwd: repoRoot, stdio: 'pipe' });
|
||||||
|
const body = JSON.parse(invoked.stdout.trim());
|
||||||
|
if (body?.ok !== true || body?.result?.message !== 'hello-from-standalone-demo') {
|
||||||
|
throw new Error(`Unexpected invoke result: ${invoked.stdout}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
ok: true,
|
||||||
|
demo: 'standalone-greeter',
|
||||||
|
mount: 'demo.greeter',
|
||||||
|
port,
|
||||||
|
invoke: body,
|
||||||
|
}, null, 2));
|
||||||
|
} finally {
|
||||||
|
if (runner) await stop(runner);
|
||||||
|
}
|
||||||
24
examples/standalone-greeter/src/GreeterActor.ts
Normal file
24
examples/standalone-greeter/src/GreeterActor.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { MatrixActor } from '@open-matrix/core';
|
||||||
|
|
||||||
|
export class GreeterActor extends MatrixActor {
|
||||||
|
static accepts = {
|
||||||
|
echo: {},
|
||||||
|
getStatus: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
onEcho(payload: { message?: string }) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
message: payload.message ?? null,
|
||||||
|
actor: 'GreeterActor',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onGetStatus() {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
actor: 'GreeterActor',
|
||||||
|
mount: 'demo.greeter',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
import { InMemoryBroker, InMemoryTransport, MatrixRuntime } from '@open-matrix/core';
|
||||||
|
|
||||||
|
import { GreeterActor } from './GreeterActor.js';
|
||||||
|
|
||||||
|
interface StandaloneBootContext {
|
||||||
|
readonly root?: string;
|
||||||
|
readonly mount?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StandaloneRunnerServices {
|
||||||
|
readonly runtime?: MatrixRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readTrimmedString(value: unknown): string | undefined {
|
||||||
|
if (typeof value !== 'string') return undefined;
|
||||||
|
const trimmed = value.trim();
|
||||||
|
return trimmed.length > 0 ? trimmed : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requiredStandaloneRoot(requestedRoot: string | undefined, runtime: MatrixRuntime): string {
|
||||||
|
const runtimeRoot = readTrimmedString(runtime.transport.root);
|
||||||
|
const root = requestedRoot ?? runtimeRoot;
|
||||||
|
if (!root) throw new Error('createStandaloneGreeterActor requires bootContext.root or a runtime transport root');
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createStandaloneGreeterActor(
|
||||||
|
_overrides: Record<string, unknown> = {},
|
||||||
|
services?: StandaloneRunnerServices,
|
||||||
|
_config?: unknown,
|
||||||
|
bootContext: StandaloneBootContext = {},
|
||||||
|
): Promise<{
|
||||||
|
runtime: MatrixRuntime;
|
||||||
|
actor: GreeterActor;
|
||||||
|
root: string;
|
||||||
|
mount: string;
|
||||||
|
}> {
|
||||||
|
const requestedRoot = readTrimmedString(bootContext.root);
|
||||||
|
const runtime = services?.runtime ?? new MatrixRuntime({
|
||||||
|
transport: new InMemoryTransport(new InMemoryBroker(), {
|
||||||
|
name: 'demo.greeter-standalone',
|
||||||
|
...(requestedRoot ? { root: requestedRoot } : {}),
|
||||||
|
}),
|
||||||
|
logging: false,
|
||||||
|
});
|
||||||
|
const root = requiredStandaloneRoot(requestedRoot, runtime);
|
||||||
|
const mount = readTrimmedString(bootContext.mount) ?? 'demo.greeter';
|
||||||
|
const actor = await runtime.createSupervised(GreeterActor, mount);
|
||||||
|
return { runtime, actor, root, mount };
|
||||||
|
}
|
||||||
2
examples/standalone-greeter/src/index.ts
Normal file
2
examples/standalone-greeter/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { GreeterActor } from './GreeterActor.js';
|
||||||
|
export { createStandaloneGreeterActor } from './create-standalone-GreeterActor.js';
|
||||||
11
examples/standalone-greeter/tsconfig.json
Normal file
11
examples/standalone-greeter/tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
@ -9,7 +9,9 @@
|
|||||||
"build": "pnpm -r --sort --if-present run build",
|
"build": "pnpm -r --sort --if-present run build",
|
||||||
"test": "pnpm -r --sort --if-present run test",
|
"test": "pnpm -r --sort --if-present run test",
|
||||||
"type-check": "pnpm -r --sort --if-present run type-check",
|
"type-check": "pnpm -r --sort --if-present run type-check",
|
||||||
"pack:all": "pnpm -r exec pnpm pack",
|
"pack:all": "pnpm --filter './packages/*' -r exec pnpm pack",
|
||||||
|
"demo:standalone": "node examples/standalone-greeter/run-demo.mjs",
|
||||||
|
"demo:hivecast-login": "node examples/hivecast-login/run-demo.mjs",
|
||||||
"prove:external-consumer": "node scripts/prove-external-consumer.mjs",
|
"prove:external-consumer": "node scripts/prove-external-consumer.mjs",
|
||||||
"prove:cli-uat": "node scripts/prove-sdk-cli-uat.mjs",
|
"prove:cli-uat": "node scripts/prove-sdk-cli-uat.mjs",
|
||||||
"prove": "pnpm run clean && pnpm run build && pnpm run test && pnpm run type-check && pnpm run pack:all"
|
"prove": "pnpm run clean && pnpm run build && pnpm run test && pnpm run type-check && pnpm run pack:all"
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@ -40,6 +40,16 @@ importers:
|
|||||||
specifier: ^5.7.0
|
specifier: ^5.7.0
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
||||||
|
examples/standalone-greeter:
|
||||||
|
dependencies:
|
||||||
|
'@open-matrix/core':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/core
|
||||||
|
devDependencies:
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.7.0
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
packages/browser-host:
|
packages/browser-host:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@open-matrix/contracts':
|
'@open-matrix/contracts':
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
packages:
|
packages:
|
||||||
- "packages/*"
|
- "packages/*"
|
||||||
|
- "examples/*"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user