Skip to content

结构与资源 ​

字典是最小受控资源;Sequence 固定完整步骤,Dynamic 由普通成员决定下一步。预设导出工厂,需调用返回的 Runner。

先执行 pnpm build,再执行下列命令。所有示例均在本机 Node.js 中运行。

把字典查询开放为受控操作 ​

沿 Offer、节点 deps 和宿主登记看一条最小资源接线。工厂返回已构建的 Runner;调用 run('hello') 得到「你好」,字典中没有的词保留原值。

由 pnpm examples:check 显式调用工厂并检查结果。

js
import { Node, Scene } from '@weiliang0121/capix-structure';
import { Resources, Runner } from '@weiliang0121/capix-runtime';
import { Resource, ResourceOffer } from '@weiliang0121/capix-resources';

export function createDictionaryRunner(words = new Map([['hello', '你好']])) {
  const dictionary = new Resource(words);
  const lookup = new ResourceOffer({
    methods: {
      translate(entries, word) {
        return entries.get(word) ?? word;
      },
    },
  });
  const translate = new Node({ deps: { dictionary: lookup.request() }, run: (work) => work.resources.dictionary.translate(work.input) });
  const scene = new Scene(translate);
  return new Runner(scene, new Resources().register(dictionary, lookup)).build();
}

复用同一结构执行多次调用 ​

观察同一个 Runner 如何先翻译已知词,再处理未知词。运行后终端打印「你好」,断言同时核对第二次返回原词,以及执行结束后 Runner 已释放占用。

sh
node examples/runner.mjs
js
import assert from 'node:assert/strict';
import { createDictionaryRunner } from './dictionary.mjs';

// build 固定结构与资源接线;每次 run 才建立独立调用。
const runner = createDictionaryRunner();
const result = await runner.run('hello');
assert.equal(result, '你好');
assert.equal(await runner.run('unknown'), 'unknown');
assert.equal(runner.isRunning, false);
console.log(result); // 你好

组合固定步骤与按结果选择的步骤 ​

Sequence 将读取、转大写和保存串起来;Dynamic 由决策节点选择下一步,并为每次组合调用捕获新的临时材料。工厂验证核对内存文档写入大写结果,以及 Dynamic 连续两次运行分别返回各自输入的大写形式。

由 pnpm examples:check 显式调用工厂并检查结果。

js
import { Node, Scene, Sequence, Dynamic, withDependencies } from '@weiliang0121/capix-structure';
import { Resource, ResourceOffer } from '@weiliang0121/capix-resources';
import { Resources, Runner } from '@weiliang0121/capix-runtime';

// A local, model-free resource. No files or external services are touched.
const texts = new ResourceOffer({
  methods: {
    read(origin, path) {
      if (!origin.has(path)) throw new Error(`Missing text: ${path}`);
      return origin.get(path);
    },
    write(origin, { path, text }) {
      origin.set(path, text);
      return { path, text };
    },
  },
});

export function createSequenceRunner(documents) {
  const read = new Node({ deps: { texts: texts.request() }, run: (work) => work.resources.texts.read(work.input) });
  const uppercase = new Node({ run: (work) => work.input.toUpperCase() });
  const save = new Node({ deps: { texts: texts.request() }, run: (work) => work.resources.texts.write(work.input) });
  const processing = new Sequence([{ node: read, input: ({ input }) => input.source }, uppercase, { node: save, input: ({ input, previous }) => ({ path: input.destination, text: previous }) }]);
  const scene = new Scene(processing);
  return new Runner(scene, new Resources().register(new Resource(documents), texts)).build();
}

// The flow resource is ordinary material, separate from invocation or CP facts.
const flow = new ResourceOffer({
  methods: {
    read(origin) {
      return origin.value;
    },
    write(origin, value) {
      origin.value = value;
    },
  },
});

export function createDynamicRunner() {
  // Common deps are known while authoring each node; this creates no supply.
  const common = withDependencies({ flow: flow.request() });
  const uppercase = common.node()({
    async run(work) {
      const value = work.input.toUpperCase();
      await work.resources.flow.write(value);
      return value;
    },
  });
  const decide = common.node()({
    async run(work) {
      const value = await work.resources.flow.read();
      return value === undefined ? { kind: 'call', option: 'uppercase', input: work.input } : { kind: 'finish', value };
    },
  });
  const processing = new Dynamic({ options: { uppercase }, decide, maxSteps: 1 });
  const scene = new Scene(processing);
  const resources = new Resources();
  // New material for each Group call; its descendants share controlled access.
  resources.capture(processing, () => ({ value: undefined })).register(flow, (state) => state);
  return new Runner(scene, resources).build();
}