ciorent 0.1.5 → 0.2.1

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/README.md CHANGED
@@ -7,89 +7,103 @@ A lightweight, low-overhead concurrency library.
7
7
  - Fully type-safe.
8
8
 
9
9
  ## Examples
10
- ### Fibers
11
- Virtual threads with more controlled execution.
12
-
10
+ ### Utilities
11
+ #### Spawning tasks
12
+ Utilities to create and run tasks.
13
13
  ```ts
14
14
  import * as co from 'ciorent';
15
- import * as fiber from 'ciorent/fiber';
16
15
 
17
- const f1 = fiber.fn(function* () {
18
- // Wait for a promise
19
- yield co.sleep(1000);
16
+ const task = async (id: number) => {
17
+ await co.sleep((10 - id) * 20 + 50);
18
+ console.log('Task', id, 'done');
19
+ }
20
20
 
21
- // Wait for a promise and return its result
22
- const res = yield* fiber.unwrap(Promise.resolve(1));
23
- console.log('Fiber 1 recieved:', res);
21
+ // Spawn and run 5 tasks sequentially
22
+ console.log('Running 5 tasks sequentially:');
23
+ await co.sequential(5, task);
24
24
 
25
- return Math.random();
26
- });
25
+ // Spawn and run 5 tasks concurrently
26
+ console.log('Running 5 tasks concurrently:');
27
+ await Promise.all(co.spawn(5, task));
28
+ ```
27
29
 
28
- {
29
- const main = fiber.spawn(function* (proc) {
30
- // Start f1, wait for it to finish and get the result
31
- const res = yield* fiber.join(fiber.spawn(f1));
32
- console.log('Fiber 2 recieved:', res);
30
+ #### Sleep
31
+ Cross-runtime synchronous and asynchronous sleep functions.
32
+ ```ts
33
+ import * as co from 'ciorent';
33
34
 
34
- // Start f1 and make its lifetime depends on current fiber
35
- const childProc = fiber.spawn(f1);
36
- fiber.mount(childProc, proc);
37
- });
35
+ const logTime = (label: string) => console.log(label + ':', Math.floor(performance.now()) + 'ms');
38
36
 
39
- console.log('Fiber 2 started:', fiber.resumed(main));
37
+ logTime('Start');
40
38
 
41
- // Pause the current fiber process
42
- fiber.pause(main);
43
- console.log('Fiber 2 is paused:', fiber.paused(main));
39
+ // Non-blocking
40
+ await co.sleep(500);
41
+ logTime('After about 0.5s');
44
42
 
45
- // Resume the fiber
46
- fiber.resume(main);
47
- console.log('Fiber 2 is resumed:', fiber.resumed(main));
43
+ // This blocks the event loop
44
+ // On the browser this only works in workers and blocks the worker thread
45
+ co.sleepSync(500);
46
+ logTime('After another 0.5s');
47
+ ```
48
48
 
49
- // Wait for the fiber process to finish
50
- await fiber.done(main);
49
+ #### Debounce
50
+ Postpones execution until after an idle period.
51
+ ```ts
52
+ import * as co from 'ciorent';
51
53
 
52
- // Check finish status
53
- console.log('Fiber 2 completed', fiber.completed(main));
54
- }
54
+ const fn = co.debounce((id: number) => {
55
+ console.log('ID:', id);
56
+ }, 500);
55
57
 
56
- {
57
- console.log('------------------------');
58
+ fn(1); // fn(1) gets skipped
59
+ await co.sleep(100);
60
+ fn(2); // fn(2) gets executed
61
+ ```
58
62
 
59
- const main = fiber.spawn(f1);
60
- console.log('Fiber 1 started:', fiber.resumed(main));
63
+ #### Throttle
64
+ Executes a function at a regular interval.
65
+ ```ts
66
+ import * as co from 'ciorent';
61
67
 
62
- // Stop a fiber
63
- fiber.stop(main);
64
- console.log('Fiber 1 stopped:', fiber.stopped(main));
65
- }
66
- ```
68
+ // Allow 2 calls in 500ms
69
+ const throttle = co.throttle(500, 2);
67
70
 
68
- ### Latch
69
- Latch is a synchronization primitive that allows one process to wait until another completes an operation before continuing execution.
71
+ co.spawn(8, async (id) => {
72
+ await throttle();
73
+ console.log(id + ': ' + Math.floor(performance.now()) + 'ms');
74
+ });
75
+ ```
70
76
 
77
+ #### Pausing
78
+ Delay the execution of a function for other asynchronous tasks to run.
71
79
  ```ts
72
- import * as latch from 'ciorent/latch';
80
+ import * as co from 'ciorent';
73
81
 
74
- const startFetch = latch.init();
82
+ // Expensive sync task
83
+ const task1 = async () => {
84
+ let x = 0;
75
85
 
76
- const task = async () => {
77
- // Blocks until the latch is open
78
- await latch.pause(startFetch);
86
+ // Yield control back to the runtime, allowing it to
87
+ // schedule other tasks
88
+ await co.pause;
79
89
 
80
- console.log('Start fetching...');
81
- const res = await fetch('http://example.com');
82
- console.log('Fetch status:', res.status);
83
- }
90
+ // Simulate heavy operation
91
+ for (let i = 0; i < (Math.random() + 15) * 1e6; i++)
92
+ x += Math.random() * 32 + i * Math.round(Math.random() * 16);
84
93
 
85
- const prepare = () => {
86
- // This always run first
87
- console.log('Run before fetch:', performance.now().toFixed(2));
88
- latch.open(startFetch);
89
- }
94
+ console.log('Finish task 1:', x);
95
+ };
90
96
 
91
- task();
92
- prepare();
97
+ // Short async task
98
+ const task2 = async () => {
99
+ console.log('Start fetching...');
100
+ const txt = await fetch('http://example.com');
101
+ console.log('Fetch status', txt.status);
102
+ };
103
+
104
+ // Task 2 will not get blocked by task 1
105
+ task1();
106
+ task2();
93
107
  ```
94
108
 
95
109
  ### Channel
@@ -130,40 +144,31 @@ run();
130
144
  console.log('Starting...');
131
145
  ```
132
146
 
133
- ### Pubsub
134
- A fast, simple publish-subscribe API.
147
+ ### Latch
148
+ Latch is a synchronization primitive that allows one process to wait until another completes an operation before continuing execution.
135
149
 
136
150
  ```ts
137
- import * as topic from 'ciorent/topic';
138
- import * as co from 'ciorent';
151
+ import * as latch from 'ciorent/latch';
139
152
 
140
- const messages = topic.init<number>();
153
+ const startFetch = latch.init();
141
154
 
142
- // A task that publish messages
143
- const publisher = async () => {
144
- for (let i = 0; i < 3; i++) {
145
- await co.sleep(100);
146
- topic.publish(messages, i);
147
- }
155
+ const task = async () => {
156
+ // Blocks until the latch is open
157
+ await latch.pause(startFetch);
148
158
 
149
- // Resolve all waiting promises
150
- // And clear the value queue
151
- topic.flush(messages);
159
+ console.log('Start fetching...');
160
+ const res = await fetch('http://example.com');
161
+ console.log('Fetch status:', res.status);
152
162
  }
153
163
 
154
- // Spawn 3 tasks that recieve messages
155
- co.spawn(3, async (id: number) => {
156
- const sub = topic.subscribe(messages);
157
-
158
- while (true) {
159
- // Block until the value is sent
160
- const x = await topic.recieve(sub);
161
- if (x == null) break;
162
- console.log(`Task ${id} recieved: ${x}`);
163
- }
164
- });
164
+ const prepare = () => {
165
+ // This always run first
166
+ console.log('Run before fetch:', performance.now().toFixed(2));
167
+ latch.open(startFetch);
168
+ }
165
169
 
166
- publisher();
170
+ task();
171
+ prepare();
167
172
  ```
168
173
 
169
174
  ### Semaphore
@@ -195,101 +200,108 @@ const task = async (id: number) => {
195
200
  co.spawn(5, task);
196
201
  ```
197
202
 
198
- ### Utilities
199
- #### Pausing
200
- Delay the execution of a function for other asynchronous tasks to run.
203
+ ### Pubsub
204
+ A fast, simple publish-subscribe API.
205
+
201
206
  ```ts
207
+ import * as topic from 'ciorent/topic';
202
208
  import * as co from 'ciorent';
203
209
 
204
- // Expensive sync task
205
- const task1 = async () => {
206
- let x = 0;
210
+ const messages = topic.init<number>();
207
211
 
208
- // Yield control back to the runtime, allowing it to
209
- // schedule other tasks
210
- await co.pause;
212
+ // A task that publish messages
213
+ const publisher = async () => {
214
+ for (let i = 0; i < 3; i++) {
215
+ await co.sleep(100);
216
+ topic.publish(messages, i);
217
+ }
211
218
 
212
- // Simulate heavy operation
213
- for (let i = 0; i < (Math.random() + 15) * 1e6; i++)
214
- x += Math.random() * 32 + i * Math.round(Math.random() * 16);
219
+ // Resolve all waiting promises
220
+ // And clear the value queue
221
+ topic.flush(messages);
222
+ }
215
223
 
216
- console.log('Finish task 1:', x);
217
- };
224
+ // Spawn 3 tasks that recieve messages
225
+ co.spawn(3, async (id: number) => {
226
+ const sub = topic.subscribe(messages);
218
227
 
219
- // Short async task
220
- const task2 = async () => {
221
- console.log('Start fetching...');
222
- const txt = await fetch('http://example.com');
223
- console.log('Fetch status', txt.status);
224
- };
228
+ while (true) {
229
+ // Block until the value is sent
230
+ const x = await topic.recieve(sub);
231
+ if (x == null) break;
232
+ console.log(`Task ${id} recieved: ${x}`);
233
+ }
234
+ });
225
235
 
226
- // Task 2 will not get blocked by task 1
227
- task1();
228
- task2();
236
+ publisher();
229
237
  ```
230
238
 
231
- #### Sleep
232
- Cross-runtime synchronous and asynchronous sleep functions.
239
+ ### Fibers
240
+ Virtual threads with more controlled execution.
241
+
233
242
  ```ts
234
243
  import * as co from 'ciorent';
244
+ import * as fiber from 'ciorent/fiber';
235
245
 
236
- const logTime = (label: string) => console.log(label + ':', Math.floor(performance.now()) + 'ms');
246
+ const f1 = fiber.fn(function* () {
247
+ // Wait for a promise
248
+ yield co.sleep(1000);
237
249
 
238
- logTime('Start');
250
+ // Wait for a promise and return its result
251
+ const res = yield* fiber.unwrap(Promise.resolve(1));
252
+ console.log('Fiber 1 recieved:', res);
239
253
 
240
- // Non-blocking
241
- await co.sleep(500);
242
- logTime('After about 0.5s');
254
+ return Math.random();
255
+ });
243
256
 
244
- // This blocks the event loop
245
- // On the browser this only works in workers and blocks the worker thread
246
- co.sleepSync(500);
247
- logTime('After another 0.5s');
248
- ```
257
+ {
258
+ const main = fiber.spawn(function* (proc) {
259
+ // Start f1, wait for the process to complete and get the result
260
+ const res = yield* fiber.join(fiber.spawn(f1));
261
+ console.log('Fiber 2 recieved:', res);
249
262
 
250
- #### Spawning tasks
251
- Utilities to create and run tasks.
252
- ```ts
253
- import * as co from 'ciorent';
263
+ // Start f1 and make its lifetime depends on current fiber
264
+ const childProc = fiber.spawn(f1);
265
+ fiber.mount(childProc, proc);
266
+ });
254
267
 
255
- const task = async (id: number) => {
256
- await co.sleep((10 - id) * 20 + 50);
257
- console.log('Task', id, 'done');
258
- }
268
+ console.log('Fiber 2 started:', fiber.resumed(main));
259
269
 
260
- // Spawn and run 5 tasks sequentially
261
- console.log('Running 5 tasks sequentially:');
262
- await co.sequential(5, task);
270
+ // Pause the current fiber process
271
+ fiber.pause(main);
272
+ console.log('Fiber 2 is paused:', fiber.paused(main));
263
273
 
264
- // Spawn and run 5 tasks concurrently
265
- console.log('Running 5 tasks concurrently:');
266
- await Promise.all(co.spawn(5, task));
267
- ```
274
+ // Resume the fiber
275
+ fiber.resume(main);
276
+ console.log('Fiber 2 is resumed:', fiber.resumed(main));
268
277
 
269
- #### Debounce
270
- Postpones execution until after an idle period.
271
- ```ts
272
- import * as co from 'ciorent';
278
+ // Wait for the fiber process to finish
279
+ await fiber.done(main);
273
280
 
274
- const fn = co.debounce((id: number) => {
275
- console.log('ID:', id);
276
- }, 500);
281
+ // Check finish status
282
+ console.log('Fiber 2 completed:', fiber.completed(main));
283
+ }
277
284
 
278
- fn(1); // fn(1) gets skipped
279
- await co.sleep(100);
280
- fn(2); // fn(2) gets executed
281
- ```
285
+ {
286
+ console.log('------------------------');
282
287
 
283
- #### Throttle
284
- Executes a function at a regular interval.
285
- ```ts
286
- import * as co from 'ciorent';
288
+ const main = fiber.spawn(f1);
289
+ console.log('Fiber 1 started:', fiber.resumed(main));
287
290
 
288
- // Allow 2 calls in 500ms
289
- const fn = co.throttle((id: number) => {
290
- console.log(id + ': ' + Math.floor(performance.now()) + 'ms');
291
- }, 500, 2);
291
+ // Stop a fiber
292
+ fiber.interrupt(main);
293
+ console.log('Fiber 1 interrupted:', fiber.interrupted(main));
294
+ }
295
+
296
+ {
297
+ console.log('------------------------');
292
298
 
293
- co.spawn(8, fn);
299
+ const main = fiber.spawn(f1);
300
+ console.log('Fiber 1 started:', fiber.resumed(main));
301
+
302
+ // Timeout a fiber
303
+ await fiber.timeout(main, 500);
304
+ console.log('Fiber 1 stopped:', fiber.interrupted(main));
305
+ }
294
306
  ```
295
307
 
package/fiber.d.ts CHANGED
@@ -41,11 +41,7 @@ export declare const completed: (t: Process) => boolean;
41
41
  /**
42
42
  * Check whether the fiber has been interrupted
43
43
  */
44
- export declare const stopped: (t: Process) => boolean;
45
- /**
46
- * Create a fiber function
47
- * @param f
48
- */
44
+ export declare const interrupted: (t: Process) => boolean;
49
45
  export declare const fn: <const Fn extends (thread: Process, ...args: any[]) => Generator>(f: Fn) => Fn;
50
46
  /**
51
47
  * A basic fiber runtime
@@ -63,10 +59,16 @@ export declare const pause: (t: Process) => void;
63
59
  */
64
60
  export declare const resume: (t: Process) => void;
65
61
  /**
66
- * Stop the execution of a fiber
62
+ * Interrupt the execution of a fiber
63
+ * @param t
64
+ */
65
+ export declare const interrupt: (t: Process) => void;
66
+ /**
67
+ * Timeout a fiber
67
68
  * @param t
69
+ * @param ms
68
70
  */
69
- export declare const stop: (t: Process) => void;
71
+ export declare const timeout: (t: Process, ms: number) => Promise<void>;
70
72
  /**
71
73
  * Wait for a fiber and retrieve its result
72
74
  * @param t
package/fiber.js CHANGED
@@ -1 +1 @@
1
- export let paused=(t)=>t[1]===0;export let resumed=(t)=>t[1]===1;export let completed=(t)=>t[1]===2;export let stopped=(t)=>t[1]===3;let invoke=async(g,thread)=>{try{let t=g.next();while(!t.done){let v=await t.value;if(thread[1]===0){let r;let p=new Promise((res)=>{r=res});thread[2]=r;await p}if(thread[1]===3)return;t=g.next(v)}thread[1]=2;return t.value}finally{if(thread[1]!==2)thread[1]=3;thread[3].forEach(stop)}};export let fn=(f)=>f;export let spawn=(f,...args)=>{let thread=[null,1,null,[]];thread[0]=invoke(f(thread,...args),thread);return thread};export let pause=(t)=>{if(t[1]===1)t[1]=0};export let resume=(t)=>{if(t[1]===0){t[1]=1;t[2]?.()}};export let stop=(t)=>{if(t[1]!==2){if(t[1]===0)t[2]?.();t[1]=3}};export function*join(t){return yield t[0]}export let done=(t)=>t[0];export let mount=(child,parent)=>{parent[3].push(child)};export let control=(t,signal)=>{signal.addEventListener("abort",()=>{stop(t)})};export function*unwrap(t){return yield t}
1
+ import{sleep}from"./index.js";export let paused=(t)=>t[1]===0;export let resumed=(t)=>t[1]===1;export let completed=(t)=>t[1]===2;export let interrupted=(t)=>t[1]===3;let invoke=async(g,thread)=>{try{let t=g.next();while(!t.done){let v=await t.value;if(thread[1]===0){let r;let p=new Promise((res)=>{r=res});thread[2]=r;await p}if(thread[1]===3)return;t=g.next(v)}thread[1]=2;return t.value}finally{if(thread[1]!==2)thread[1]=3;thread[3].forEach(stop)}};export let fn=(f)=>f;export let spawn=(f,...args)=>{let thread=[null,1,null,[]];thread[0]=invoke(f(thread,...args),thread);return thread};export let pause=(t)=>{if(t[1]===1)t[1]=0};export let resume=(t)=>{if(t[1]===0){t[1]=1;t[2]?.()}};export let interrupt=(t)=>{if(t[1]!==2){if(t[1]===0)t[2]?.();t[1]=3}};export let timeout=async(t,ms)=>{await sleep(ms);interrupt(t)};export function*join(t){return yield t[0]}export let done=(t)=>t[0];export let mount=(child,parent)=>{parent[3].push(child)};export let control=(t,signal)=>{signal.addEventListener("abort",()=>{interrupt(t)})};export function*unwrap(t){return yield t}
package/index.d.ts CHANGED
@@ -24,7 +24,7 @@ export declare const sleep: (ms: number) => Promise<void>;
24
24
  */
25
25
  export declare const sleepSync: (ms: number) => void;
26
26
  /**
27
- * Spawn n tasks that runs sequentially
27
+ * Spawn n sequential task
28
28
  * @param n
29
29
  * @param task - The function to run
30
30
  */
@@ -47,4 +47,4 @@ export declare const debounce: <const Args extends any[]>(f: (...args: Args) =>
47
47
  * @param ms - The time in milliseconds
48
48
  * @param limit - The call limit in the time period
49
49
  */
50
- export declare const throttle: <const Args extends any[], const R>(f: (...args: Args) => R, ms: number, limit: number) => ((...args: Args) => Promise<Awaited<R>>);
50
+ export declare const throttle: (ms: number, limit: number) => (() => Promise<void>);
package/index.js CHANGED
@@ -1 +1 @@
1
- export let pause=Promise.resolve();export let sleep=globalThis.Bun?.sleep??globalThis.process?.getBuiltinModule?.("timers/promises").setTimeout??((ms)=>new Promise((res)=>{setTimeout(res,ms)}));let sharedBuf=new Int32Array(new SharedArrayBuffer(4));export let sleepSync=globalThis.Bun?.sleepSync??((ms)=>{Atomics.wait(sharedBuf,0,0,ms)});export let sequential=async(n,task,...args)=>{for(let i=0;i<n;i++)await task(...args,i)};export let spawn=(n,task,...args)=>{let arr=new Array(n);for(let i=0;i<n;i++)arr[i]=task(...args,i);return arr};export let debounce=(f,ms)=>{let id;return(...a)=>{clearTimeout(id);id=setTimeout(f,ms,...a)}};export let throttle=(f,ms,limit)=>{let head=[null];let tail=head;let cur=limit;let scheduled=false;let unlock=()=>{cur=limit;while(cur>0){if(tail===head){scheduled=false;return}cur--;tail=tail[0];tail[1](f(...tail[2]))}setTimeout(unlock,ms)};return(...a)=>{if(cur===0){let r;let p=new Promise((res)=>{r=res});head=head[0]=[null,r,a];return p}if(!scheduled){scheduled=true;setTimeout(unlock,ms)}cur--;return f(...a)}};
1
+ export let pause=Promise.resolve();export let sleep=globalThis.Bun?.sleep??globalThis.process?.getBuiltinModule?.("timers/promises").setTimeout??((ms)=>new Promise((res)=>{setTimeout(res,ms)}));let sharedBuf=new Int32Array(new SharedArrayBuffer(4));export let sleepSync=globalThis.Bun?.sleepSync??((ms)=>{Atomics.wait(sharedBuf,0,0,ms)});export let sequential=async(n,task,...args)=>{for(let i=0;i<n;i++)await task(...args,i)};export let spawn=(n,task,...args)=>{let arr=new Array(n);for(let i=0;i<n;i++)arr[i]=task(...args,i);return arr};export let debounce=(f,ms)=>{let id;return(...a)=>{clearTimeout(id);id=setTimeout(f,ms,...a)}};export let throttle=(ms,limit)=>{let head=[null];let tail=head;let cur=limit;let scheduled=false;let unlock=()=>{cur=limit;if(tail===head){scheduled=false;return}do{cur--;(tail=tail[0])[1]()}while(cur>0&&tail!==head);setTimeout(unlock,ms)};return()=>{if(cur===0){let r;let p=new Promise((res)=>{r=res});head=head[0]=[null,r];return p}if(!scheduled){scheduled=true;setTimeout(unlock,ms)}cur--;return pause}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ciorent",
3
- "version": "0.1.5",
3
+ "version": "0.2.1",
4
4
  "description": "A lightweight, low-overhead concurrency library",
5
5
  "homepage": "https://ciorent.netlify.app",
6
6
  "repository": {
@@ -18,14 +18,14 @@
18
18
  "main": "./index.js",
19
19
  "types": "./index.d.ts",
20
20
  "exports": {
21
- "./fixed-queue": "./fixed-queue.js",
22
21
  "./sliding-queue": "./sliding-queue.js",
23
- "./latch": "./latch.js",
24
- "./channel": "./channel.js",
22
+ "./fixed-queue": "./fixed-queue.js",
25
23
  "./fiber": "./fiber.js",
26
- "./dropping-queue": "./dropping-queue.js",
27
24
  ".": "./index.js",
25
+ "./dropping-queue": "./dropping-queue.js",
26
+ "./channel": "./channel.js",
28
27
  "./topic": "./topic.js",
29
- "./semaphore": "./semaphore.js"
28
+ "./semaphore": "./semaphore.js",
29
+ "./latch": "./latch.js"
30
30
  }
31
31
  }