diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5b4d969 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.pulse/ +dist/ diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..fb7cecf --- /dev/null +++ b/bun.lock @@ -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=="], + } +} diff --git a/package.json b/package.json index 52a401d..b79038b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "test": "bun test" }, "dependencies": { - "@uncaged/pulse": "workspace:*" + "@uncaged/pulse": "file:../../repos/pulse/packages/pulse" }, "devDependencies": { "typescript": "^5.8.0", diff --git a/src/workflows/.gitkeep b/src/workflows/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/workflows/ping-pong.test.ts b/src/workflows/ping-pong.test.ts new file mode 100644 index 0000000..a1a418c --- /dev/null +++ b/src/workflows/ping-pong.test.ts @@ -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(); + }); +}); diff --git a/src/workflows/ping-pong.ts b/src/workflows/ping-pong.ts new file mode 100644 index 0000000..0aeca36 --- /dev/null +++ b/src/workflows/ping-pong.ts @@ -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 = { + 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; + }, +};