diff --git a/package.json b/package.json index 8996086..e5f1d9a 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,10 @@ "version": "0.0.0", "type": "module", "exports": "./src/index.ts", + "engines": { + "node": ">=22" + }, + "packageManager": "pnpm@10.33.0", "scripts": { "test": "node --experimental-strip-types --test test/**/*.test.ts" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..9b60ae1 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,9 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..57ddd22 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export { mo } from './mo.ts'; +export { Task } from './task.ts'; diff --git a/src/mo.ts b/src/mo.ts new file mode 100644 index 0000000..feea66e --- /dev/null +++ b/src/mo.ts @@ -0,0 +1,18 @@ +import { registry } from './registry.ts'; +import { Task } from './task.ts'; + +const counters = new Map(); + +export function mo( + importMeta: ImportMeta, + fn: (...args: A) => R, +): (...args: A) => Task { + const url = importMeta.url; + const index = counters.get(url) ?? 0; + counters.set(url, index + 1); + const id = `${url}#${index}`; + + registry.set(id, fn); + + return (...args: A) => new Task(id, args); +} diff --git a/src/registry.ts b/src/registry.ts new file mode 100644 index 0000000..2431bb0 --- /dev/null +++ b/src/registry.ts @@ -0,0 +1 @@ +export const registry = new Map any>(); diff --git a/src/task.ts b/src/task.ts new file mode 100644 index 0000000..16a3140 --- /dev/null +++ b/src/task.ts @@ -0,0 +1,16 @@ +export class Task { + readonly id: string; + readonly args: unknown[]; + + constructor(id: string, args: unknown[]) { + this.id = id; + this.args = args; + } + + then( + onfulfilled?: ((value: T) => T1 | PromiseLike) | null, + onrejected?: ((reason: any) => T2 | PromiseLike) | null, + ): Promise { + throw new Error('Not implemented: wire up dedicated runner'); + } +} diff --git a/test/mo.test.ts b/test/mo.test.ts new file mode 100644 index 0000000..d5f57ea --- /dev/null +++ b/test/mo.test.ts @@ -0,0 +1,33 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { mo } from 'moroutine'; + +describe('mo()', () => { + it('returns a function', () => { + const double = mo(import.meta, (x: number) => 2 * x); + assert.equal(typeof double, 'function'); + }); + + it('calling it returns a Task with correct args', () => { + const double = mo(import.meta, (x: number) => 2 * x); + const task = double(2); + assert.deepEqual(task.args, [2]); + }); + + it('Task is a thenable', () => { + const double = mo(import.meta, (x: number) => 2 * x); + const task = double(2); + assert.equal(typeof task.then, 'function'); + }); + + it('tasks from the same moroutine share an id', () => { + const double = mo(import.meta, (x: number) => 2 * x); + assert.equal(double(1).id, double(2).id); + }); + + it('different moroutines have different ids', () => { + const a = mo(import.meta, (x: number) => x + 1); + const b = mo(import.meta, (x: number) => x + 2); + assert.notEqual(a(1).id, b(1).id); + }); +});