feat: add space functions to center, remove space remote template
This commit is contained in:
1 parent
e9956bd34f
commit
953976cfc1
9 files changed
+79
-21
No files matched your search
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
export const spec = {
|
export const spec = {
|
||||||
name: 'create_function',
|
name: 'create-function',
|
||||||
description: 'Create a new function on this remote by writing a function module.',
|
description: 'Create a new function on this remote by writing a function module.',
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
export const spec = {
|
export const spec = {
|
||||||
name: 'delete_file',
|
name: 'delete-file',
|
||||||
description: 'Delete a file (or directory) in the space.',
|
description: 'Delete a file (or directory) in the space.',
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
export const spec = {
|
export const spec = {
|
||||||
name: 'delete_function',
|
name: 'delete-function',
|
||||||
description: 'Delete a function module from this remote.',
|
description: 'Delete a function module from this remote.',
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
export const spec = {
|
export const spec = {
|
||||||
name: 'list_files',
|
name: 'list-files',
|
||||||
description: 'List every file in the space, as paths relative to the space directory.',
|
description: 'List every file in the space, as paths relative to the space directory.',
|
||||||
parameters: { type: 'object', properties: {} },
|
parameters: { type: 'object', properties: {} },
|
||||||
};
|
};
|
||||||
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
export const spec = {
|
export const spec = {
|
||||||
name: 'read_file',
|
name: 'read-file',
|
||||||
description: 'Read the text content of a file in the space.',
|
description: 'Read the text content of a file in the space.',
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
export const spec = {
|
export const spec = {
|
||||||
name: 'write_file',
|
name: 'write-file',
|
||||||
description: 'Write text content to a file in the space, creating parent directories as needed.',
|
description: 'Write text content to a file in the space, creating parent directories as needed.',
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
+1
-1
@@ -158,6 +158,6 @@ export class Horde {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (packet.error) console.error('Hub error:', packet.error);
|
if (packet.error) console.error('Hub error:', packet.error);
|
||||||
else console.warn('Ignored hub message:', raw);
|
else console.warn('Ignored hub message:', packet.action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+41
-9
@@ -10,7 +10,7 @@ import path from 'node:path';
|
|||||||
import { execFile } from 'node:child_process';
|
import { execFile } from 'node:child_process';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { Horde } from './horde.mjs';
|
import { Horde } from './horde.mjs';
|
||||||
import { Remote, Watcher } from './runtime.mjs';
|
import { Remote, Watcher, Functions } from './runtime.mjs';
|
||||||
|
|
||||||
const APP_DIR = path.resolve(fileURLToPath(new URL('..', import.meta.url)));
|
const APP_DIR = path.resolve(fileURLToPath(new URL('..', import.meta.url)));
|
||||||
const TEMPLATES_DIR = path.join(APP_DIR, 'templates');
|
const TEMPLATES_DIR = path.join(APP_DIR, 'templates');
|
||||||
@@ -52,12 +52,21 @@ function addSpaceRemote(spaceName, dir) {
|
|||||||
const remoteName = `${CENTER_NAME}/${spaceName}`;
|
const remoteName = `${CENTER_NAME}/${spaceName}`;
|
||||||
let remote = REMOTES.get(remoteName);
|
let remote = REMOTES.get(remoteName);
|
||||||
if (remote) return remote;
|
if (remote) return remote;
|
||||||
|
|
||||||
|
// Resolve center functions for this space
|
||||||
|
let spaceConfig = APP_CONFIG.spaces?.[spaceName] || {};
|
||||||
|
let centerFunctions = spaceConfig.centerFunctions;
|
||||||
|
if (centerFunctions == null) {
|
||||||
|
centerFunctions = CENTER ? CENTER.spaceFunctions.names() : [];
|
||||||
|
}
|
||||||
|
|
||||||
remote = new Remote({
|
remote = new Remote({
|
||||||
horde: HORDE,
|
horde: HORDE,
|
||||||
name: remoteName,
|
name: remoteName,
|
||||||
functionsDir: path.join(dir, '.remote', 'functions'),
|
functionsDir: path.join(dir, '.remote', 'functions'),
|
||||||
ctx: { dir, remoteName, spaceName, api: API },
|
ctx: { dir, remoteName, spaceName, api: API },
|
||||||
fallback: CENTER.functions,
|
centerFunctions,
|
||||||
|
fallback: CENTER && CENTER.spaceFunctions,
|
||||||
});
|
});
|
||||||
REMOTES.set(remoteName, remote);
|
REMOTES.set(remoteName, remote);
|
||||||
return remote;
|
return remote;
|
||||||
@@ -70,14 +79,30 @@ async function initSpace(spaceName, dir) {
|
|||||||
throw new Error(`Space directory does not exist: ${absoluteDir}`);
|
throw new Error(`Space directory does not exist: ${absoluteDir}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const remoteDir = path.join(absoluteDir, '.remote');
|
||||||
|
if (!fs.existsSync(remoteDir)) {
|
||||||
|
fs.mkdirSync(remoteDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create empty functions directory
|
||||||
|
const functionsDir = path.join(remoteDir, 'functions');
|
||||||
|
if (!fs.existsSync(functionsDir)) {
|
||||||
|
fs.mkdirSync(functionsDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get center function names for this space
|
||||||
|
const spaceConfig = APP_CONFIG.spaces?.[spaceName] || {};
|
||||||
|
const centerFunctions = spaceConfig.centerFunctions || [];
|
||||||
|
|
||||||
|
// Update config with center functions
|
||||||
|
APP_CONFIG = { ...APP_CONFIG, spaces: { ...APP_CONFIG.spaces, [spaceName]: { path: absoluteDir, centerFunctions } } };
|
||||||
|
fs.writeFileSync(path.join(WORK_DIR, '.remote', 'config.json'), JSON.stringify(APP_CONFIG, null, 2) + '\n');
|
||||||
|
|
||||||
if (REMOTES != null) {
|
if (REMOTES != null) {
|
||||||
const remote = addSpaceRemote(spaceName, absoluteDir);
|
const remote = addSpaceRemote(spaceName, absoluteDir);
|
||||||
if (!(await remote.register())) throw new Error('Failed to register space with the hub');
|
if (!(await remote.register())) throw new Error('Failed to register space with the hub');
|
||||||
}
|
}
|
||||||
|
|
||||||
APP_CONFIG = { ...APP_CONFIG, spaces: { ...APP_CONFIG.spaces, [spaceName]: { path: absoluteDir } } };
|
|
||||||
fs.writeFileSync(path.join(WORK_DIR, '.remote', 'config.json'), JSON.stringify(APP_CONFIG, null, 2) + '\n');
|
|
||||||
|
|
||||||
console.log(`Initialized space '${spaceName}' at ${absoluteDir}`);
|
console.log(`Initialized space '${spaceName}' at ${absoluteDir}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,9 +113,6 @@ async function createSpace(spaceName, dir = null) {
|
|||||||
if (fs.existsSync(absoluteDir)) throw new Error(`Space directory already exists: ${absoluteDir}`);
|
if (fs.existsSync(absoluteDir)) throw new Error(`Space directory already exists: ${absoluteDir}`);
|
||||||
fs.mkdirSync(absoluteDir, { recursive: true });
|
fs.mkdirSync(absoluteDir, { recursive: true });
|
||||||
|
|
||||||
fs.cpSync(path.join(TEMPLATES_DIR, 'space'), path.join(absoluteDir, '.remote'), { recursive: true });
|
|
||||||
fs.writeFileSync(path.join(absoluteDir, '.remote', 'config.json'), JSON.stringify({ name: spaceName }, null, 2) + '\n');
|
|
||||||
|
|
||||||
await initSpace(spaceName, absoluteDir);
|
await initSpace(spaceName, absoluteDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,9 +222,10 @@ function init(dir = '.') {
|
|||||||
|
|
||||||
// --- run ---
|
// --- run ---
|
||||||
|
|
||||||
function run() {
|
async function run() {
|
||||||
const token = process.env.HORDE_TOKEN || APP_CONFIG.token || null;
|
const token = process.env.HORDE_TOKEN || APP_CONFIG.token || null;
|
||||||
const spacesDir = path.join(WORK_DIR, 'spaces');
|
const spacesDir = path.join(WORK_DIR, 'spaces');
|
||||||
|
const spaceFunctionsDir = path.join(WORK_DIR, 'space-functions');
|
||||||
|
|
||||||
console.log(`Running remote-center '${CENTER_NAME}' on ${HOST}, working path ${WORK_DIR}`);
|
console.log(`Running remote-center '${CENTER_NAME}' on ${HOST}, working path ${WORK_DIR}`);
|
||||||
if (!token) console.warn('No token set in .remote/config.json');
|
if (!token) console.warn('No token set in .remote/config.json');
|
||||||
@@ -222,6 +245,15 @@ function run() {
|
|||||||
});
|
});
|
||||||
REMOTES = new Map([[CENTER_NAME, CENTER]]);
|
REMOTES = new Map([[CENTER_NAME, CENTER]]);
|
||||||
|
|
||||||
|
// Load space functions
|
||||||
|
CENTER.spaceFunctions = new Functions(spaceFunctionsDir);
|
||||||
|
await CENTER.spaceFunctions.scan();
|
||||||
|
|
||||||
|
new Watcher(spaceFunctionsDir, async () => {
|
||||||
|
CENTER.spaceFunctions.scan();
|
||||||
|
// TODO: Reconnect all space remotes
|
||||||
|
}).start();
|
||||||
|
|
||||||
reconcileSpaces();
|
reconcileSpaces();
|
||||||
new Watcher(spacesDir, () => reconcileSpaces()).start();
|
new Watcher(spacesDir, () => reconcileSpaces()).start();
|
||||||
HORDE.connect();
|
HORDE.connect();
|
||||||
|
|||||||
+31
-5
@@ -59,10 +59,18 @@ export class Functions {
|
|||||||
return [...this.map.values()].map((f) => f.spec);
|
return [...this.map.values()].map((f) => f.spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
names() {
|
||||||
|
return [...this.map.keys()];
|
||||||
|
}
|
||||||
|
|
||||||
has(name) {
|
has(name) {
|
||||||
return this.map.has(name);
|
return this.map.has(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spec(name) {
|
||||||
|
return this.map.get(name)?.spec;
|
||||||
|
}
|
||||||
|
|
||||||
async call(name, args, ctx) {
|
async call(name, args, ctx) {
|
||||||
const fn = this.map.get(name);
|
const fn = this.map.get(name);
|
||||||
if (!fn) throw new Error(`Unknown function: ${name}`);
|
if (!fn) throw new Error(`Unknown function: ${name}`);
|
||||||
@@ -128,12 +136,13 @@ export class Watcher {
|
|||||||
|
|
||||||
// A named collection of functions in a directory, registered on the hub.
|
// A named collection of functions in a directory, registered on the hub.
|
||||||
export class Remote {
|
export class Remote {
|
||||||
constructor({ horde, name, functionsDir, ctx = null, fallback = null }) {
|
constructor({ horde, name, functionsDir, ctx, centerFunctions = [], fallback }) {
|
||||||
this.horde = horde;
|
this.horde = horde;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.fallback = fallback; // shared Functions instance (center functions)
|
|
||||||
this.functions = new Functions(functionsDir);
|
this.functions = new Functions(functionsDir);
|
||||||
|
this.centerFunctions = centerFunctions;
|
||||||
|
this.fallback = fallback;
|
||||||
this.watcher = null;
|
this.watcher = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,17 +150,34 @@ export class Remote {
|
|||||||
async register() {
|
async register() {
|
||||||
await this.functions.scan();
|
await this.functions.scan();
|
||||||
try {
|
try {
|
||||||
|
let functions = this.functions.specs();
|
||||||
|
|
||||||
|
|
||||||
|
// Extend functions list with center functions
|
||||||
|
for (const fname of this.centerFunctions) {
|
||||||
|
// don't override local functions (should override?)
|
||||||
|
//if (this.functions.has(fname)) continue;
|
||||||
|
if (this.fallback && this.fallback.has(fname)) {
|
||||||
|
functions.push(this.fallback.spec(fname));
|
||||||
|
} else {
|
||||||
|
console.error(`Remote ${this.name}: center function ${fname} not found`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await this.horde.sendAction('remote-connect', {
|
await this.horde.sendAction('remote-connect', {
|
||||||
remotename: this.name,
|
remotename: this.name,
|
||||||
functions: this.functions.specs(),
|
functions,
|
||||||
});
|
});
|
||||||
console.log(`Registered remote '${this.name}' with ${this.functions.map.size} function(s)`);
|
console.log(`Registered remote '${this.name}' with ${functions.length} function(s) (${this.functions.map.size} defined)`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Failed to register remote '${this.name}': ${err.message}`);
|
console.error(`Failed to register remote '${this.name}': ${err.message}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!this.watcher) {
|
if (!this.watcher) {
|
||||||
this.watcher = new Watcher(this.functions.dir, () => this.register());
|
this.watcher = new Watcher(this.functions.dir, async () => {
|
||||||
|
await this.disconnect();
|
||||||
|
await this.register()
|
||||||
|
});
|
||||||
this.watcher.start();
|
this.watcher.start();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in new issue
Block a user