priority-pipeline 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -20,14 +20,136 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- noop: () => noop
23
+ Component: () => Component,
24
+ Orchestrator: () => Orchestrator,
25
+ PipelineError: () => PipelineError
24
26
  });
25
27
  module.exports = __toCommonJS(index_exports);
26
- function noop() {
27
- return;
28
- }
28
+
29
+ // src/error.ts
30
+ var PipelineError = class extends Error {
31
+ code;
32
+ details;
33
+ constructor(code, opts) {
34
+ super(code, { cause: opts?.cause });
35
+ this.name = new.target.name;
36
+ this.code = code;
37
+ this.details = opts?.details;
38
+ }
39
+ };
40
+
41
+ // src/orchestrator.ts
42
+ var Orchestrator = class {
43
+ // Surface endpoint types for consumers
44
+ _types = null;
45
+ components;
46
+ hasStarted = false;
47
+ componentError;
48
+ constructor(...components) {
49
+ if (components.length === 0) {
50
+ throw new Error("Orchestrator must have at least one component");
51
+ }
52
+ this.components = components;
53
+ }
54
+ async run(input) {
55
+ if (this.hasStarted) {
56
+ throw new PipelineError("PIPELINE_STARTED_TWICE");
57
+ }
58
+ this.hasStarted = true;
59
+ const priorities = Array.from(new Set(this.components.map((c) => c.priority))).sort((a, b) => b - a);
60
+ const tieredComponents = priorities.map((p) => ({
61
+ components: this.components.filter((c) => c.priority === p),
62
+ anyCouldRun: this.components.some((c) => c === this.firstComponent)
63
+ // First component in pipeline must run first
64
+ }));
65
+ this.tryRunComponent(this.firstComponent, input);
66
+ while (true) {
67
+ let anyUpperTierCouldRun = false;
68
+ outerLoop: for (const compsInTier of tieredComponents) {
69
+ for (const component of compsInTier.components) {
70
+ if (this.componentError) await this.handleError(this.componentError.component, this.componentError.error);
71
+ if (this.getDone(component) && this.isLast(component)) return component.give();
72
+ if (anyUpperTierCouldRun) break outerLoop;
73
+ const canRun = component.canRun({ upstreamCanGive: this.getUpstream(component)?.canGive() ?? true });
74
+ compsInTier.anyCouldRun = compsInTier.anyCouldRun || canRun;
75
+ if (canRun) {
76
+ this.tryRunComponent(component);
77
+ }
78
+ }
79
+ anyUpperTierCouldRun = compsInTier.anyCouldRun;
80
+ compsInTier.anyCouldRun = false;
81
+ }
82
+ await new Promise((resolve) => setTimeout(resolve, 0));
83
+ }
84
+ }
85
+ getDone(component) {
86
+ for (let comp = this.firstComponent; (comp ? this.getIndex(comp) : Infinity) <= this.getIndex(component); comp = this.getDownstream(comp)) {
87
+ if (!comp.isDone({ upstreamDone: true, upstreamCanGive: this.getUpstream(comp)?.canGive() ?? true })) return false;
88
+ }
89
+ return true;
90
+ }
91
+ tryRunComponent(component, input) {
92
+ const upstreamComponent = this.getUpstream(component);
93
+ try {
94
+ const maybePromise = component.run(input ?? upstreamComponent?.give(), {
95
+ upstreamDone: upstreamComponent ? this.getDone(upstreamComponent) : true
96
+ });
97
+ if (maybePromise instanceof Promise) {
98
+ maybePromise.catch((error) => this.componentError = { component, error });
99
+ }
100
+ } catch (error) {
101
+ this.componentError = { component, error };
102
+ }
103
+ }
104
+ async handleError(component, error) {
105
+ const pipelineError = new PipelineError("COMPONENT_FAILED", {
106
+ cause: error,
107
+ details: {
108
+ componentIndex: this.getIndex(component)
109
+ }
110
+ });
111
+ await Promise.all(this.components.map((c) => c.onPipelineError?.(pipelineError)));
112
+ throw pipelineError;
113
+ }
114
+ getUpstream(component) {
115
+ return this.components[this.getIndex(component) - 1];
116
+ }
117
+ getDownstream(component) {
118
+ return this.components[this.getIndex(component) + 1];
119
+ }
120
+ isLast(component) {
121
+ return this.getIndex(component) === this.components.length - 1;
122
+ }
123
+ get firstComponent() {
124
+ return this.components[0];
125
+ }
126
+ getIndex(component) {
127
+ return this.components.indexOf(component);
128
+ }
129
+ };
130
+
131
+ // src/component.ts
132
+ var DEFAULT_PRIORITY = 0;
133
+ var Component = class {
134
+ priority;
135
+ queue = [];
136
+ constructor({ priority = DEFAULT_PRIORITY }) {
137
+ this.priority = priority;
138
+ }
139
+ canGive() {
140
+ return this.queue.length > 0;
141
+ }
142
+ give() {
143
+ if (this.queue.length === 0) {
144
+ throw new PipelineError("COMPONENT_DONE_WITH_NOTHING_TO_GIVE");
145
+ }
146
+ return this.queue.shift();
147
+ }
148
+ };
29
149
  // Annotate the CommonJS export names for ESM import in node:
30
150
  0 && (module.exports = {
31
- noop
151
+ Component,
152
+ Orchestrator,
153
+ PipelineError
32
154
  });
33
155
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export function noop() {\n return;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,SAAS,OAAO;AACrB;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/error.ts","../src/orchestrator.ts","../src/component.ts"],"sourcesContent":["export * from \"./orchestrator\";\nexport * from \"./component\";\nexport * from \"./error\";\nexport * from \"./models\";\n","export type PipelineErrorCode =\n | \"PIPELINE_STARTED_TWICE\"\n | \"COMPONENT_FAILED\"\n | \"COMPONENT_DONE_WITH_NOTHING_TO_GIVE\";\n\nexport class PipelineError extends Error {\n readonly code: PipelineErrorCode;\n readonly details?: Record<string, unknown>;\n\n constructor(\n code: PipelineErrorCode,\n opts?: { cause?: unknown; details?: Record<string, unknown> },\n ) {\n super(code, { cause: opts?.cause });\n this.name = new.target.name;\n this.code = code;\n this.details = opts?.details;\n }\n}","import { PipelineError } from \"./error\";\nimport { ComponentInterface, Chainable, First, In, Last, Out } from \"./models\";\n\nexport class Orchestrator<\n const T extends readonly ComponentInterface<any, any>[]\n> {\n // Surface endpoint types for consumers\n public _types = null as unknown as {\n input: In<First<T>>;\n output: Out<Last<T>>;\n };\n\n private components: Chainable<T>;\n private hasStarted = false;\n private componentError: { component: ComponentInterface<any, any>, error: unknown } | undefined;\n\n constructor(...components: Chainable<T>) {\n if (components.length === 0) {\n throw new Error('Orchestrator must have at least one component');\n }\n\n this.components = components;\n }\n\n public async run(input?: In<First<T>>): Promise<Out<Last<T>>> {\n if (this.hasStarted) {\n throw new PipelineError('PIPELINE_STARTED_TWICE');\n }\n\n this.hasStarted = true;\n\n const priorities = Array.from(new Set(this.components.map(c => c.priority))).sort((a, b) => b - a);\n\n const tieredComponents = priorities.map((p) => ({\n components: this.components.filter(c => c.priority === p),\n anyCouldRun: this.components.some(c => c === this.firstComponent) // First component in pipeline must run first\n }));\n\n this.tryRunComponent(this.firstComponent, input);\n\n while (true) {\n let anyUpperTierCouldRun = false;\n\n outerLoop: for (const compsInTier of tieredComponents) {\n for (const component of compsInTier.components) {\n if (this.componentError) await this.handleError(this.componentError.component, this.componentError.error);\n\n if (this.getDone(component) && this.isLast(component)) return component.give();\n\n if (anyUpperTierCouldRun) break outerLoop;\n\n const canRun = component.canRun({ upstreamCanGive: this.getUpstream(component)?.canGive() ?? true });\n compsInTier.anyCouldRun = compsInTier.anyCouldRun || canRun;\n\n if (canRun) {\n this.tryRunComponent(component);\n }\n }\n\n anyUpperTierCouldRun = compsInTier.anyCouldRun;\n compsInTier.anyCouldRun = false;\n }\n\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n }\n\n private getDone(component: ComponentInterface<any, any>): boolean {\n for (let comp: ComponentInterface<any, any> | undefined = this.firstComponent; (comp ? this.getIndex(comp) : Infinity) <= this.getIndex(component); comp = this.getDownstream(comp!)) {\n if (!comp!.isDone({ upstreamDone: true, upstreamCanGive: this.getUpstream(comp!)?.canGive() ?? true })) return false;\n }\n\n return true;\n }\n\n private tryRunComponent(component: ComponentInterface<any, any>, input?: In<First<T>>) {\n const upstreamComponent = this.getUpstream(component);\n\n try {\n const maybePromise = component.run(input ?? upstreamComponent?.give(), {\n upstreamDone: upstreamComponent ? this.getDone(upstreamComponent) : true,\n });\n\n if (maybePromise instanceof Promise) {\n maybePromise.catch((error) => this.componentError = { component, error });\n }\n } catch (error) {\n this.componentError = { component, error };\n }\n }\n\n private async handleError(component: ComponentInterface<any, any>, error: unknown) {\n const pipelineError = new PipelineError('COMPONENT_FAILED', {\n cause: error,\n details: {\n componentIndex: this.getIndex(component),\n }\n });\n\n await Promise.all(this.components.map(c => c.onPipelineError?.(pipelineError)));\n\n throw pipelineError;\n }\n\n private getUpstream(component: ComponentInterface<any, any>): ComponentInterface<any, any> | undefined {\n return this.components[this.getIndex(component) - 1];\n }\n\n private getDownstream(component: ComponentInterface<any, any>): ComponentInterface<any, any> | undefined {\n return this.components[this.getIndex(component) + 1];\n }\n\n private isLast(component: ComponentInterface<any, any>) {\n return this.getIndex(component) === this.components.length - 1;\n }\n\n private get firstComponent() {\n return this.components[0];\n }\n\n private getIndex(component: ComponentInterface<any, any>) {\n return this.components.indexOf(component);\n }\n}\n","import { PipelineError } from \"./error\";\n\nimport type { ComponentInterface } from \"./models\";\n\nconst DEFAULT_PRIORITY = 0;\n\nexport abstract class Component<I, O> implements ComponentInterface<I, O> {\n public readonly priority: number;\n\n protected readonly queue: O[] = [];\n\n constructor({ priority = DEFAULT_PRIORITY }: { priority?: number }) {\n this.priority = priority;\n }\n\n abstract isDone(ctx: { upstreamDone: boolean; upstreamCanGive: boolean }): boolean;\n abstract canRun(ctx: { upstreamCanGive: boolean }): boolean;\n abstract run(input: I | undefined, ctx: { upstreamDone: boolean }): void | Promise<void>;\n\n canGive(): boolean {\n return this.queue.length > 0;\n }\n\n give(): O {\n if (this.queue.length === 0) {\n throw new PipelineError('COMPONENT_DONE_WITH_NOTHING_TO_GIVE');\n }\n\n // It is legitimate that O == undefined\n return this.queue.shift() as O;\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,3 +1,77 @@
1
- declare function noop(): void;
1
+ type PipelineErrorCode = "PIPELINE_STARTED_TWICE" | "COMPONENT_FAILED" | "COMPONENT_DONE_WITH_NOTHING_TO_GIVE";
2
+ declare class PipelineError extends Error {
3
+ readonly code: PipelineErrorCode;
4
+ readonly details?: Record<string, unknown>;
5
+ constructor(code: PipelineErrorCode, opts?: {
6
+ cause?: unknown;
7
+ details?: Record<string, unknown>;
8
+ });
9
+ }
2
10
 
3
- export { noop };
11
+ interface ComponentInterface<I, O> {
12
+ readonly priority: number;
13
+ readonly canRun: (ctx: {
14
+ upstreamCanGive: boolean;
15
+ }) => boolean;
16
+ readonly run: (input: I, ctx: {
17
+ upstreamDone: boolean;
18
+ }) => void | Promise<void>;
19
+ readonly canGive: () => boolean;
20
+ readonly give: () => O;
21
+ readonly isDone: (ctx: {
22
+ upstreamDone: boolean;
23
+ upstreamCanGive: boolean;
24
+ }) => boolean;
25
+ readonly onPipelineError?: (error: PipelineError) => void;
26
+ }
27
+ type In<C> = C extends ComponentInterface<infer I, any> ? I : never;
28
+ type Out<C> = C extends ComponentInterface<any, infer O> ? O : never;
29
+ type Last<T extends readonly unknown[]> = T extends readonly [...infer _R, infer L] ? L : never;
30
+ type First<T extends readonly unknown[]> = T extends readonly [infer F, ...infer _R] ? F : never;
31
+ type Chainable<T extends readonly ComponentInterface<any, any>[]> = T extends readonly [
32
+ infer C1 extends ComponentInterface<any, any>,
33
+ infer C2 extends ComponentInterface<any, any>,
34
+ ...infer Rest extends readonly ComponentInterface<any, any>[]
35
+ ] ? Out<C1> extends In<C2> ? readonly [C1, ...Chainable<readonly [C2, ...Rest]>] : never : T;
36
+
37
+ declare class Orchestrator<const T extends readonly ComponentInterface<any, any>[]> {
38
+ _types: {
39
+ input: In<First<T>>;
40
+ output: Out<Last<T>>;
41
+ };
42
+ private components;
43
+ private hasStarted;
44
+ private componentError;
45
+ constructor(...components: Chainable<T>);
46
+ run(input?: In<First<T>>): Promise<Out<Last<T>>>;
47
+ private getDone;
48
+ private tryRunComponent;
49
+ private handleError;
50
+ private getUpstream;
51
+ private getDownstream;
52
+ private isLast;
53
+ private get firstComponent();
54
+ private getIndex;
55
+ }
56
+
57
+ declare abstract class Component<I, O> implements ComponentInterface<I, O> {
58
+ readonly priority: number;
59
+ protected readonly queue: O[];
60
+ constructor({ priority }: {
61
+ priority?: number;
62
+ });
63
+ abstract isDone(ctx: {
64
+ upstreamDone: boolean;
65
+ upstreamCanGive: boolean;
66
+ }): boolean;
67
+ abstract canRun(ctx: {
68
+ upstreamCanGive: boolean;
69
+ }): boolean;
70
+ abstract run(input: I | undefined, ctx: {
71
+ upstreamDone: boolean;
72
+ }): void | Promise<void>;
73
+ canGive(): boolean;
74
+ give(): O;
75
+ }
76
+
77
+ export { type Chainable, Component, type ComponentInterface, type First, type In, type Last, Orchestrator, type Out, PipelineError, type PipelineErrorCode };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,77 @@
1
- declare function noop(): void;
1
+ type PipelineErrorCode = "PIPELINE_STARTED_TWICE" | "COMPONENT_FAILED" | "COMPONENT_DONE_WITH_NOTHING_TO_GIVE";
2
+ declare class PipelineError extends Error {
3
+ readonly code: PipelineErrorCode;
4
+ readonly details?: Record<string, unknown>;
5
+ constructor(code: PipelineErrorCode, opts?: {
6
+ cause?: unknown;
7
+ details?: Record<string, unknown>;
8
+ });
9
+ }
2
10
 
3
- export { noop };
11
+ interface ComponentInterface<I, O> {
12
+ readonly priority: number;
13
+ readonly canRun: (ctx: {
14
+ upstreamCanGive: boolean;
15
+ }) => boolean;
16
+ readonly run: (input: I, ctx: {
17
+ upstreamDone: boolean;
18
+ }) => void | Promise<void>;
19
+ readonly canGive: () => boolean;
20
+ readonly give: () => O;
21
+ readonly isDone: (ctx: {
22
+ upstreamDone: boolean;
23
+ upstreamCanGive: boolean;
24
+ }) => boolean;
25
+ readonly onPipelineError?: (error: PipelineError) => void;
26
+ }
27
+ type In<C> = C extends ComponentInterface<infer I, any> ? I : never;
28
+ type Out<C> = C extends ComponentInterface<any, infer O> ? O : never;
29
+ type Last<T extends readonly unknown[]> = T extends readonly [...infer _R, infer L] ? L : never;
30
+ type First<T extends readonly unknown[]> = T extends readonly [infer F, ...infer _R] ? F : never;
31
+ type Chainable<T extends readonly ComponentInterface<any, any>[]> = T extends readonly [
32
+ infer C1 extends ComponentInterface<any, any>,
33
+ infer C2 extends ComponentInterface<any, any>,
34
+ ...infer Rest extends readonly ComponentInterface<any, any>[]
35
+ ] ? Out<C1> extends In<C2> ? readonly [C1, ...Chainable<readonly [C2, ...Rest]>] : never : T;
36
+
37
+ declare class Orchestrator<const T extends readonly ComponentInterface<any, any>[]> {
38
+ _types: {
39
+ input: In<First<T>>;
40
+ output: Out<Last<T>>;
41
+ };
42
+ private components;
43
+ private hasStarted;
44
+ private componentError;
45
+ constructor(...components: Chainable<T>);
46
+ run(input?: In<First<T>>): Promise<Out<Last<T>>>;
47
+ private getDone;
48
+ private tryRunComponent;
49
+ private handleError;
50
+ private getUpstream;
51
+ private getDownstream;
52
+ private isLast;
53
+ private get firstComponent();
54
+ private getIndex;
55
+ }
56
+
57
+ declare abstract class Component<I, O> implements ComponentInterface<I, O> {
58
+ readonly priority: number;
59
+ protected readonly queue: O[];
60
+ constructor({ priority }: {
61
+ priority?: number;
62
+ });
63
+ abstract isDone(ctx: {
64
+ upstreamDone: boolean;
65
+ upstreamCanGive: boolean;
66
+ }): boolean;
67
+ abstract canRun(ctx: {
68
+ upstreamCanGive: boolean;
69
+ }): boolean;
70
+ abstract run(input: I | undefined, ctx: {
71
+ upstreamDone: boolean;
72
+ }): void | Promise<void>;
73
+ canGive(): boolean;
74
+ give(): O;
75
+ }
76
+
77
+ export { type Chainable, Component, type ComponentInterface, type First, type In, type Last, Orchestrator, type Out, PipelineError, type PipelineErrorCode };
package/dist/index.js CHANGED
@@ -1,8 +1,126 @@
1
- // src/index.ts
2
- function noop() {
3
- return;
4
- }
1
+ // src/error.ts
2
+ var PipelineError = class extends Error {
3
+ code;
4
+ details;
5
+ constructor(code, opts) {
6
+ super(code, { cause: opts?.cause });
7
+ this.name = new.target.name;
8
+ this.code = code;
9
+ this.details = opts?.details;
10
+ }
11
+ };
12
+
13
+ // src/orchestrator.ts
14
+ var Orchestrator = class {
15
+ // Surface endpoint types for consumers
16
+ _types = null;
17
+ components;
18
+ hasStarted = false;
19
+ componentError;
20
+ constructor(...components) {
21
+ if (components.length === 0) {
22
+ throw new Error("Orchestrator must have at least one component");
23
+ }
24
+ this.components = components;
25
+ }
26
+ async run(input) {
27
+ if (this.hasStarted) {
28
+ throw new PipelineError("PIPELINE_STARTED_TWICE");
29
+ }
30
+ this.hasStarted = true;
31
+ const priorities = Array.from(new Set(this.components.map((c) => c.priority))).sort((a, b) => b - a);
32
+ const tieredComponents = priorities.map((p) => ({
33
+ components: this.components.filter((c) => c.priority === p),
34
+ anyCouldRun: this.components.some((c) => c === this.firstComponent)
35
+ // First component in pipeline must run first
36
+ }));
37
+ this.tryRunComponent(this.firstComponent, input);
38
+ while (true) {
39
+ let anyUpperTierCouldRun = false;
40
+ outerLoop: for (const compsInTier of tieredComponents) {
41
+ for (const component of compsInTier.components) {
42
+ if (this.componentError) await this.handleError(this.componentError.component, this.componentError.error);
43
+ if (this.getDone(component) && this.isLast(component)) return component.give();
44
+ if (anyUpperTierCouldRun) break outerLoop;
45
+ const canRun = component.canRun({ upstreamCanGive: this.getUpstream(component)?.canGive() ?? true });
46
+ compsInTier.anyCouldRun = compsInTier.anyCouldRun || canRun;
47
+ if (canRun) {
48
+ this.tryRunComponent(component);
49
+ }
50
+ }
51
+ anyUpperTierCouldRun = compsInTier.anyCouldRun;
52
+ compsInTier.anyCouldRun = false;
53
+ }
54
+ await new Promise((resolve) => setTimeout(resolve, 0));
55
+ }
56
+ }
57
+ getDone(component) {
58
+ for (let comp = this.firstComponent; (comp ? this.getIndex(comp) : Infinity) <= this.getIndex(component); comp = this.getDownstream(comp)) {
59
+ if (!comp.isDone({ upstreamDone: true, upstreamCanGive: this.getUpstream(comp)?.canGive() ?? true })) return false;
60
+ }
61
+ return true;
62
+ }
63
+ tryRunComponent(component, input) {
64
+ const upstreamComponent = this.getUpstream(component);
65
+ try {
66
+ const maybePromise = component.run(input ?? upstreamComponent?.give(), {
67
+ upstreamDone: upstreamComponent ? this.getDone(upstreamComponent) : true
68
+ });
69
+ if (maybePromise instanceof Promise) {
70
+ maybePromise.catch((error) => this.componentError = { component, error });
71
+ }
72
+ } catch (error) {
73
+ this.componentError = { component, error };
74
+ }
75
+ }
76
+ async handleError(component, error) {
77
+ const pipelineError = new PipelineError("COMPONENT_FAILED", {
78
+ cause: error,
79
+ details: {
80
+ componentIndex: this.getIndex(component)
81
+ }
82
+ });
83
+ await Promise.all(this.components.map((c) => c.onPipelineError?.(pipelineError)));
84
+ throw pipelineError;
85
+ }
86
+ getUpstream(component) {
87
+ return this.components[this.getIndex(component) - 1];
88
+ }
89
+ getDownstream(component) {
90
+ return this.components[this.getIndex(component) + 1];
91
+ }
92
+ isLast(component) {
93
+ return this.getIndex(component) === this.components.length - 1;
94
+ }
95
+ get firstComponent() {
96
+ return this.components[0];
97
+ }
98
+ getIndex(component) {
99
+ return this.components.indexOf(component);
100
+ }
101
+ };
102
+
103
+ // src/component.ts
104
+ var DEFAULT_PRIORITY = 0;
105
+ var Component = class {
106
+ priority;
107
+ queue = [];
108
+ constructor({ priority = DEFAULT_PRIORITY }) {
109
+ this.priority = priority;
110
+ }
111
+ canGive() {
112
+ return this.queue.length > 0;
113
+ }
114
+ give() {
115
+ if (this.queue.length === 0) {
116
+ throw new PipelineError("COMPONENT_DONE_WITH_NOTHING_TO_GIVE");
117
+ }
118
+ return this.queue.shift();
119
+ }
120
+ };
5
121
  export {
6
- noop
122
+ Component,
123
+ Orchestrator,
124
+ PipelineError
7
125
  };
8
126
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export function noop() {\n return;\n}\n"],"mappings":";AAAO,SAAS,OAAO;AACrB;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/error.ts","../src/orchestrator.ts","../src/component.ts"],"sourcesContent":["export type PipelineErrorCode =\n | \"PIPELINE_STARTED_TWICE\"\n | \"COMPONENT_FAILED\"\n | \"COMPONENT_DONE_WITH_NOTHING_TO_GIVE\";\n\nexport class PipelineError extends Error {\n readonly code: PipelineErrorCode;\n readonly details?: Record<string, unknown>;\n\n constructor(\n code: PipelineErrorCode,\n opts?: { cause?: unknown; details?: Record<string, unknown> },\n ) {\n super(code, { cause: opts?.cause });\n this.name = new.target.name;\n this.code = code;\n this.details = opts?.details;\n }\n}","import { PipelineError } from \"./error\";\nimport { ComponentInterface, Chainable, First, In, Last, Out } from \"./models\";\n\nexport class Orchestrator<\n const T extends readonly ComponentInterface<any, any>[]\n> {\n // Surface endpoint types for consumers\n public _types = null as unknown as {\n input: In<First<T>>;\n output: Out<Last<T>>;\n };\n\n private components: Chainable<T>;\n private hasStarted = false;\n private componentError: { component: ComponentInterface<any, any>, error: unknown } | undefined;\n\n constructor(...components: Chainable<T>) {\n if (components.length === 0) {\n throw new Error('Orchestrator must have at least one component');\n }\n\n this.components = components;\n }\n\n public async run(input?: In<First<T>>): Promise<Out<Last<T>>> {\n if (this.hasStarted) {\n throw new PipelineError('PIPELINE_STARTED_TWICE');\n }\n\n this.hasStarted = true;\n\n const priorities = Array.from(new Set(this.components.map(c => c.priority))).sort((a, b) => b - a);\n\n const tieredComponents = priorities.map((p) => ({\n components: this.components.filter(c => c.priority === p),\n anyCouldRun: this.components.some(c => c === this.firstComponent) // First component in pipeline must run first\n }));\n\n this.tryRunComponent(this.firstComponent, input);\n\n while (true) {\n let anyUpperTierCouldRun = false;\n\n outerLoop: for (const compsInTier of tieredComponents) {\n for (const component of compsInTier.components) {\n if (this.componentError) await this.handleError(this.componentError.component, this.componentError.error);\n\n if (this.getDone(component) && this.isLast(component)) return component.give();\n\n if (anyUpperTierCouldRun) break outerLoop;\n\n const canRun = component.canRun({ upstreamCanGive: this.getUpstream(component)?.canGive() ?? true });\n compsInTier.anyCouldRun = compsInTier.anyCouldRun || canRun;\n\n if (canRun) {\n this.tryRunComponent(component);\n }\n }\n\n anyUpperTierCouldRun = compsInTier.anyCouldRun;\n compsInTier.anyCouldRun = false;\n }\n\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n }\n\n private getDone(component: ComponentInterface<any, any>): boolean {\n for (let comp: ComponentInterface<any, any> | undefined = this.firstComponent; (comp ? this.getIndex(comp) : Infinity) <= this.getIndex(component); comp = this.getDownstream(comp!)) {\n if (!comp!.isDone({ upstreamDone: true, upstreamCanGive: this.getUpstream(comp!)?.canGive() ?? true })) return false;\n }\n\n return true;\n }\n\n private tryRunComponent(component: ComponentInterface<any, any>, input?: In<First<T>>) {\n const upstreamComponent = this.getUpstream(component);\n\n try {\n const maybePromise = component.run(input ?? upstreamComponent?.give(), {\n upstreamDone: upstreamComponent ? this.getDone(upstreamComponent) : true,\n });\n\n if (maybePromise instanceof Promise) {\n maybePromise.catch((error) => this.componentError = { component, error });\n }\n } catch (error) {\n this.componentError = { component, error };\n }\n }\n\n private async handleError(component: ComponentInterface<any, any>, error: unknown) {\n const pipelineError = new PipelineError('COMPONENT_FAILED', {\n cause: error,\n details: {\n componentIndex: this.getIndex(component),\n }\n });\n\n await Promise.all(this.components.map(c => c.onPipelineError?.(pipelineError)));\n\n throw pipelineError;\n }\n\n private getUpstream(component: ComponentInterface<any, any>): ComponentInterface<any, any> | undefined {\n return this.components[this.getIndex(component) - 1];\n }\n\n private getDownstream(component: ComponentInterface<any, any>): ComponentInterface<any, any> | undefined {\n return this.components[this.getIndex(component) + 1];\n }\n\n private isLast(component: ComponentInterface<any, any>) {\n return this.getIndex(component) === this.components.length - 1;\n }\n\n private get firstComponent() {\n return this.components[0];\n }\n\n private getIndex(component: ComponentInterface<any, any>) {\n return this.components.indexOf(component);\n }\n}\n","import { PipelineError } from \"./error\";\n\nimport type { ComponentInterface } from \"./models\";\n\nconst DEFAULT_PRIORITY = 0;\n\nexport abstract class Component<I, O> implements ComponentInterface<I, O> {\n public readonly priority: number;\n\n protected readonly queue: O[] = [];\n\n constructor({ priority = DEFAULT_PRIORITY }: { priority?: number }) {\n this.priority = priority;\n }\n\n abstract isDone(ctx: { upstreamDone: boolean; upstreamCanGive: boolean }): boolean;\n abstract canRun(ctx: { upstreamCanGive: boolean }): boolean;\n abstract run(input: I | undefined, ctx: { upstreamDone: boolean }): void | Promise<void>;\n\n canGive(): boolean {\n return this.queue.length > 0;\n }\n\n give(): O {\n if (this.queue.length === 0) {\n throw new PipelineError('COMPONENT_DONE_WITH_NOTHING_TO_GIVE');\n }\n\n // It is legitimate that O == undefined\n return this.queue.shift() as O;\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "priority-pipeline",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Typescript prioritized pipeline of workers",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -20,6 +20,7 @@
20
20
  "scripts": {
21
21
  "dev": "tsup --watch",
22
22
  "build": "tsup",
23
+ "prepack": "npm run build",
23
24
  "typecheck": "tsc --noEmit",
24
25
  "test": "vitest run",
25
26
  "test:watch": "vitest",