feat: engine directory setup — independent bun package + ping-pong test

- package.json: file: link to pulse repo (not workspace:*)
- .gitignore: node_modules + .pulse/ + dist/
- ping-pong workflow + test: validates engine can import pulse & run workflows
- bun install: 9 packages via hardlink

Ref: pulse#3
This commit is contained in:
小橘 2026-04-18 00:46:16 +00:00
parent d81bbe223b
commit a2b6731aa5
6 changed files with 116 additions and 1 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules/
.pulse/
dist/

35
bun.lock Normal file
View File

@ -0,0 +1,35 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "@upulse/engine",
"dependencies": {
"@uncaged/pulse": "file:../../repos/pulse/packages/pulse",
},
"devDependencies": {
"@types/bun": "^1.2.0",
"typescript": "^5.8.0",
},
},
},
"packages": {
"@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="],
"@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
"@uncaged/pulse": ["@uncaged/pulse@file:../../repos/pulse/packages/pulse", { "dependencies": { "jsonata": "^2.1.0", "zod": "^4.3.6" }, "devDependencies": { "@types/node": "^25.6.0", "bun-types": "latest", "typescript": "^6.0.2" } }],
"bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="],
"jsonata": ["jsonata@2.1.0", "", {}, "sha512-OCzaRMK8HobtX8fp37uIVmL8CY1IGc/a6gLsDqz3quExFR09/U78HUzWYr7T31UEB6+Eu0/8dkVD5fFDOl9a8w=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@uncaged/pulse/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
}
}

View File

@ -7,7 +7,7 @@
"test": "bun test"
},
"dependencies": {
"@uncaged/pulse": "workspace:*"
"@uncaged/pulse": "file:../../repos/pulse/packages/pulse"
},
"devDependencies": {
"typescript": "^5.8.0",

View File

View File

@ -0,0 +1,49 @@
/**
* ping-pong workflow test validates engine can run workflows.
*
* 🍊 (NEKO Team)
*/
import { afterEach, describe, expect, it } from 'bun:test';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createStore, createWorkflowRule } from '@uncaged/pulse';
import { pingPong } from './ping-pong.js';
describe('ping-pong workflow', () => {
let tmpDir: string;
afterEach(() => {
if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
});
it('START → pong → END', async () => {
tmpDir = mkdtempSync(join(tmpdir(), 'engine-test-'));
const store = createStore({
eventsDbPath: join(tmpDir, 'test.db'),
objectsDir: join(tmpDir, 'objects'),
});
const rule = createWorkflowRule(pingPong, store);
// Trigger
const hash = store.putObject('ping');
store.appendEvent({
occurredAt: Date.now(),
kind: 'ping-pong.__start__',
key: 't1',
hash,
});
const r1 = await rule.tick();
expect(r1.executed).toMatchObject([{ topicId: 't1', role: 'pong' }]);
expect(r1.executed[0].content).toBe('pong: ping');
// No more work
const r2 = await rule.tick();
expect(r2.executed).toEqual([]);
store.close();
});
});

View File

@ -0,0 +1,28 @@
/**
* Example: ping-pong workflow validates engine setup.
*
* 🍊 (NEKO Team)
*/
import { END, START, type Role, type WorkflowType } from '@uncaged/pulse/src/workflows/workflow-type.js';
type PingPongRoles = {
pong: Role<{ echo: true }>;
};
export const pingPong: WorkflowType<PingPongRoles> = {
name: 'ping-pong',
roles: {
pong: async (chain) => {
const start = chain.find((m) => m.role === '__start__');
return {
content: `pong: ${start?.content ?? ''}`,
meta: { echo: true as const },
};
},
},
moderator: (output) => {
if (output.role === START) return 'pong';
return END;
},
};