@qorejs/qore 0.7.1 → 0.7.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/README.md +139 -3
- package/dist/src/core/iterable.d.ts +1 -0
- package/dist/src/core/iterable.js +17 -0
- package/dist/src/core/response-runtime.d.ts +2 -0
- package/dist/src/core/response-runtime.js +233 -0
- package/dist/src/core/response-state.d.ts +7 -0
- package/dist/src/core/response-state.js +102 -0
- package/dist/src/core/response-types.d.ts +73 -0
- package/dist/src/core/response-types.js +1 -0
- package/dist/src/core/response.d.ts +4 -0
- package/dist/src/core/response.js +26 -0
- package/dist/src/core/signal-context.d.ts +10 -0
- package/dist/src/core/signal-context.js +69 -0
- package/dist/src/core/signal-nodes.d.ts +45 -0
- package/dist/src/core/signal-nodes.js +197 -0
- package/dist/src/core/signal-scheduler.d.ts +2 -0
- package/dist/src/core/signal-scheduler.js +19 -0
- package/dist/src/core/signal-types.d.ts +19 -0
- package/dist/src/core/signal-types.js +1 -0
- package/dist/src/core/signal.d.ts +19 -0
- package/dist/src/core/signal.js +41 -0
- package/dist/src/core/stream-backpressure.d.ts +3 -0
- package/dist/src/core/stream-backpressure.js +36 -0
- package/dist/src/core/stream-buffer.d.ts +17 -0
- package/dist/src/core/stream-buffer.js +162 -0
- package/dist/src/core/stream-iterator.d.ts +6 -0
- package/dist/src/core/stream-iterator.js +14 -0
- package/dist/src/core/stream-lifecycle.d.ts +10 -0
- package/dist/src/core/stream-lifecycle.js +28 -0
- package/dist/src/core/stream-queue.d.ts +17 -0
- package/dist/src/core/stream-queue.js +62 -0
- package/dist/src/core/stream-runtime.d.ts +2 -0
- package/dist/src/core/stream-runtime.js +127 -0
- package/dist/src/core/stream-source.d.ts +2 -0
- package/dist/src/core/stream-source.js +28 -0
- package/dist/src/core/stream-state.d.ts +4 -0
- package/dist/src/core/stream-state.js +20 -0
- package/dist/src/core/stream-types.d.ts +61 -0
- package/dist/src/core/stream-types.js +1 -0
- package/dist/src/core/stream.d.ts +11 -0
- package/dist/src/core/stream.js +90 -0
- package/dist/src/dom/app.d.ts +38 -0
- package/dist/src/dom/app.js +102 -0
- package/dist/src/dom/dom.d.ts +13 -0
- package/dist/src/dom/dom.js +197 -0
- package/dist/src/dom/properties.d.ts +3 -0
- package/dist/src/dom/properties.js +147 -0
- package/dist/src/dom/reactive.d.ts +6 -0
- package/dist/src/dom/reactive.js +14 -0
- package/dist/src/dom/response-view.d.ts +4 -0
- package/dist/src/dom/response-view.js +37 -0
- package/dist/src/dom/scope.d.ts +10 -0
- package/dist/src/dom/scope.js +49 -0
- package/dist/src/dom/types.d.ts +34 -0
- package/dist/src/dom/types.js +1 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +10 -0
- package/dist/src/providers/anthropic.d.ts +2 -0
- package/dist/src/providers/anthropic.js +102 -0
- package/dist/src/providers/openai.d.ts +2 -0
- package/dist/src/providers/openai.js +96 -0
- package/dist/src/providers/sse-adapter.d.ts +2 -0
- package/dist/src/providers/sse-adapter.js +83 -0
- package/dist/src/providers/sse-env.d.ts +4 -0
- package/dist/src/providers/sse-env.js +33 -0
- package/dist/src/providers/sse-parser.d.ts +5 -0
- package/dist/src/providers/sse-parser.js +99 -0
- package/dist/src/providers/sse.d.ts +4 -0
- package/dist/src/providers/sse.js +3 -0
- package/dist/src/providers/types.d.ts +94 -0
- package/dist/src/providers/types.js +1 -0
- package/dist/src/shared/utils.d.ts +2 -0
- package/dist/src/shared/utils.js +32 -0
- package/package.json +25 -11
- package/src/anthropic.js +0 -122
- package/src/app.js +0 -123
- package/src/dom.js +0 -527
- package/src/index.d.ts +0 -405
- package/src/index.js +0 -10
- package/src/iterable.js +0 -20
- package/src/openai.js +0 -112
- package/src/response.js +0 -312
- package/src/signal.js +0 -328
- package/src/sse.js +0 -264
- package/src/stream.js +0 -582
- package/src/utils.js +0 -39
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Use a sentinel so signals can still store undefined as a real value.
|
|
2
|
+
export const READ = Symbol('qore.signal.read');
|
|
3
|
+
let activeObserver = null;
|
|
4
|
+
let batchDepth = 0;
|
|
5
|
+
const pendingObservers = new Set();
|
|
6
|
+
export function getActiveObserver() {
|
|
7
|
+
return activeObserver;
|
|
8
|
+
}
|
|
9
|
+
export function setActiveObserver(observer) {
|
|
10
|
+
activeObserver = observer;
|
|
11
|
+
}
|
|
12
|
+
export function withActiveObserver(observer, fn) {
|
|
13
|
+
const previousObserver = activeObserver;
|
|
14
|
+
activeObserver = observer;
|
|
15
|
+
try {
|
|
16
|
+
return fn();
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
activeObserver = previousObserver;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Remove this observer from every dependency it tracked during the last run.
|
|
23
|
+
export function cleanupObserver(observer) {
|
|
24
|
+
for (const dep of observer.deps) {
|
|
25
|
+
dep.subscribers.delete(observer);
|
|
26
|
+
}
|
|
27
|
+
observer.deps.clear();
|
|
28
|
+
}
|
|
29
|
+
// Queue observer work during batching, otherwise notify immediately.
|
|
30
|
+
export function scheduleObserver(observer) {
|
|
31
|
+
if (!observer.active) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (batchDepth > 0) {
|
|
35
|
+
pendingObservers.add(observer);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
observer.schedule();
|
|
39
|
+
}
|
|
40
|
+
export function removePendingObserver(observer) {
|
|
41
|
+
pendingObservers.delete(observer);
|
|
42
|
+
}
|
|
43
|
+
// Flush batched observer work in FIFO-like waves until the queue is empty.
|
|
44
|
+
function flushObservers() {
|
|
45
|
+
while (pendingObservers.size > 0) {
|
|
46
|
+
const queue = Array.from(pendingObservers);
|
|
47
|
+
pendingObservers.clear();
|
|
48
|
+
for (const observer of queue) {
|
|
49
|
+
observer.schedule();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Batch synchronous updates so dependent observers only re-run once afterward.
|
|
54
|
+
export function batch(fn) {
|
|
55
|
+
batchDepth += 1;
|
|
56
|
+
try {
|
|
57
|
+
return fn();
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
batchDepth -= 1;
|
|
61
|
+
if (batchDepth === 0) {
|
|
62
|
+
flushObservers();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Read signals without subscribing the current observer to them.
|
|
67
|
+
export function untrack(fn) {
|
|
68
|
+
return withActiveObserver(null, fn);
|
|
69
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Cleanup, EffectCallback, EffectOptions, ObserverDependency, ReactiveObserver, SignalListener, SubscribeOptions } from './signal-types.js';
|
|
2
|
+
export declare class SignalNode<T> implements ObserverDependency {
|
|
3
|
+
value: T;
|
|
4
|
+
subscribers: Set<ReactiveObserver>;
|
|
5
|
+
listeners: Set<SignalListener<T>>;
|
|
6
|
+
constructor(initialValue: T);
|
|
7
|
+
get(): T;
|
|
8
|
+
peek(): T;
|
|
9
|
+
set(nextValue: T): T;
|
|
10
|
+
update(updater: (currentValue: T) => T): T;
|
|
11
|
+
subscribe(listener: SignalListener<T>, options?: SubscribeOptions): Cleanup;
|
|
12
|
+
emit(): void;
|
|
13
|
+
}
|
|
14
|
+
export declare class ComputedNode<T> implements ObserverDependency, ReactiveObserver {
|
|
15
|
+
getter: () => T;
|
|
16
|
+
subscribers: Set<ReactiveObserver>;
|
|
17
|
+
listeners: Set<SignalListener<T>>;
|
|
18
|
+
deps: Set<ObserverDependency>;
|
|
19
|
+
active: boolean;
|
|
20
|
+
initialized: boolean;
|
|
21
|
+
value: T;
|
|
22
|
+
constructor(getter: () => T);
|
|
23
|
+
get(): T;
|
|
24
|
+
peek(): T;
|
|
25
|
+
schedule(): void;
|
|
26
|
+
notify(): void;
|
|
27
|
+
subscribe(listener: SignalListener<T>, options?: SubscribeOptions): Cleanup;
|
|
28
|
+
recompute(): void;
|
|
29
|
+
stop(): void;
|
|
30
|
+
}
|
|
31
|
+
export declare class EffectNode implements ReactiveObserver {
|
|
32
|
+
fn: EffectCallback;
|
|
33
|
+
scheduler: EffectOptions['scheduler'];
|
|
34
|
+
deps: Set<ObserverDependency>;
|
|
35
|
+
active: boolean;
|
|
36
|
+
scheduled: boolean;
|
|
37
|
+
running: boolean;
|
|
38
|
+
needsRun: boolean;
|
|
39
|
+
cleanup: Cleanup | null;
|
|
40
|
+
constructor(fn: EffectCallback, options?: EffectOptions);
|
|
41
|
+
schedule(): void;
|
|
42
|
+
notify(): void;
|
|
43
|
+
run(): void;
|
|
44
|
+
stop(): void;
|
|
45
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { cleanupObserver, getActiveObserver, removePendingObserver, scheduleObserver, withActiveObserver } from './signal-context.js';
|
|
2
|
+
import { scheduleEffectRun } from './signal-scheduler.js';
|
|
3
|
+
// A mutable signal node stores a value and fan-outs updates to listeners and observers.
|
|
4
|
+
export class SignalNode {
|
|
5
|
+
value;
|
|
6
|
+
subscribers = new Set();
|
|
7
|
+
listeners = new Set();
|
|
8
|
+
constructor(initialValue) {
|
|
9
|
+
this.value = initialValue;
|
|
10
|
+
}
|
|
11
|
+
get() {
|
|
12
|
+
const activeObserver = getActiveObserver();
|
|
13
|
+
if (activeObserver) {
|
|
14
|
+
this.subscribers.add(activeObserver);
|
|
15
|
+
activeObserver.deps.add(this);
|
|
16
|
+
}
|
|
17
|
+
return this.value;
|
|
18
|
+
}
|
|
19
|
+
peek() {
|
|
20
|
+
return this.value;
|
|
21
|
+
}
|
|
22
|
+
set(nextValue) {
|
|
23
|
+
if (Object.is(this.value, nextValue)) {
|
|
24
|
+
return this.value;
|
|
25
|
+
}
|
|
26
|
+
this.value = nextValue;
|
|
27
|
+
this.emit();
|
|
28
|
+
return this.value;
|
|
29
|
+
}
|
|
30
|
+
update(updater) {
|
|
31
|
+
return this.set(updater(this.value));
|
|
32
|
+
}
|
|
33
|
+
subscribe(listener, options = {}) {
|
|
34
|
+
const { immediate = true } = options;
|
|
35
|
+
this.listeners.add(listener);
|
|
36
|
+
if (immediate) {
|
|
37
|
+
listener(this.value);
|
|
38
|
+
}
|
|
39
|
+
return () => {
|
|
40
|
+
this.listeners.delete(listener);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Snapshot listeners first so resubscription during notification cannot loop forever.
|
|
44
|
+
emit() {
|
|
45
|
+
for (const listener of Array.from(this.listeners)) {
|
|
46
|
+
listener(this.value);
|
|
47
|
+
}
|
|
48
|
+
for (const observer of Array.from(this.subscribers)) {
|
|
49
|
+
scheduleObserver(observer);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// A computed node re-runs its getter whenever one of its dependencies changes.
|
|
54
|
+
export class ComputedNode {
|
|
55
|
+
getter;
|
|
56
|
+
subscribers = new Set();
|
|
57
|
+
listeners = new Set();
|
|
58
|
+
deps = new Set();
|
|
59
|
+
active = true;
|
|
60
|
+
initialized = false;
|
|
61
|
+
value;
|
|
62
|
+
constructor(getter) {
|
|
63
|
+
this.getter = getter;
|
|
64
|
+
this.recompute();
|
|
65
|
+
}
|
|
66
|
+
get() {
|
|
67
|
+
const activeObserver = getActiveObserver();
|
|
68
|
+
if (activeObserver) {
|
|
69
|
+
this.subscribers.add(activeObserver);
|
|
70
|
+
activeObserver.deps.add(this);
|
|
71
|
+
}
|
|
72
|
+
return this.value;
|
|
73
|
+
}
|
|
74
|
+
peek() {
|
|
75
|
+
return this.value;
|
|
76
|
+
}
|
|
77
|
+
schedule() {
|
|
78
|
+
this.notify();
|
|
79
|
+
}
|
|
80
|
+
notify() {
|
|
81
|
+
this.recompute();
|
|
82
|
+
}
|
|
83
|
+
subscribe(listener, options = {}) {
|
|
84
|
+
const { immediate = true } = options;
|
|
85
|
+
this.listeners.add(listener);
|
|
86
|
+
if (immediate) {
|
|
87
|
+
listener(this.value);
|
|
88
|
+
}
|
|
89
|
+
return () => {
|
|
90
|
+
this.listeners.delete(listener);
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// Recompute under dependency tracking and notify downstream observers only on change.
|
|
94
|
+
recompute() {
|
|
95
|
+
if (!this.active) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const previousValue = this.value;
|
|
99
|
+
cleanupObserver(this);
|
|
100
|
+
withActiveObserver(this, () => {
|
|
101
|
+
const nextValue = this.getter();
|
|
102
|
+
const changed = !this.initialized || !Object.is(previousValue, nextValue);
|
|
103
|
+
this.value = nextValue;
|
|
104
|
+
this.initialized = true;
|
|
105
|
+
if (!changed) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
for (const listener of Array.from(this.listeners)) {
|
|
109
|
+
listener(this.value);
|
|
110
|
+
}
|
|
111
|
+
for (const observer of Array.from(this.subscribers)) {
|
|
112
|
+
scheduleObserver(observer);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
stop() {
|
|
117
|
+
if (!this.active) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.active = false;
|
|
121
|
+
cleanupObserver(this);
|
|
122
|
+
removePendingObserver(this);
|
|
123
|
+
this.subscribers.clear();
|
|
124
|
+
this.listeners.clear();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Effects are observers with optional cleanup that re-run when dependencies change.
|
|
128
|
+
export class EffectNode {
|
|
129
|
+
fn;
|
|
130
|
+
scheduler;
|
|
131
|
+
deps = new Set();
|
|
132
|
+
active = true;
|
|
133
|
+
scheduled = false;
|
|
134
|
+
running = false;
|
|
135
|
+
needsRun = false;
|
|
136
|
+
cleanup = null;
|
|
137
|
+
constructor(fn, options = {}) {
|
|
138
|
+
this.fn = fn;
|
|
139
|
+
this.scheduler = options.scheduler ?? 'sync';
|
|
140
|
+
this.run();
|
|
141
|
+
}
|
|
142
|
+
schedule() {
|
|
143
|
+
if (!this.active) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (this.running) {
|
|
147
|
+
this.needsRun = true;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (this.scheduled) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this.scheduled = true;
|
|
154
|
+
scheduleEffectRun(this.scheduler, () => {
|
|
155
|
+
this.scheduled = false;
|
|
156
|
+
this.run();
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
notify() {
|
|
160
|
+
this.run();
|
|
161
|
+
}
|
|
162
|
+
// Execute the effect under tracking and remember any returned cleanup callback.
|
|
163
|
+
run() {
|
|
164
|
+
if (!this.active) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
this.running = true;
|
|
168
|
+
this.needsRun = false;
|
|
169
|
+
cleanupObserver(this);
|
|
170
|
+
if (typeof this.cleanup === 'function') {
|
|
171
|
+
this.cleanup();
|
|
172
|
+
this.cleanup = null;
|
|
173
|
+
}
|
|
174
|
+
withActiveObserver(this, () => {
|
|
175
|
+
const maybeCleanup = this.fn();
|
|
176
|
+
this.cleanup = typeof maybeCleanup === 'function' ? maybeCleanup : null;
|
|
177
|
+
});
|
|
178
|
+
this.running = false;
|
|
179
|
+
if (this.needsRun && this.active) {
|
|
180
|
+
this.schedule();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
stop() {
|
|
184
|
+
if (!this.active) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
this.active = false;
|
|
188
|
+
this.scheduled = false;
|
|
189
|
+
this.needsRun = false;
|
|
190
|
+
cleanupObserver(this);
|
|
191
|
+
removePendingObserver(this);
|
|
192
|
+
if (typeof this.cleanup === 'function') {
|
|
193
|
+
this.cleanup();
|
|
194
|
+
this.cleanup = null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function scheduleEffectRun(scheduler, run) {
|
|
2
|
+
if (typeof scheduler === 'function') {
|
|
3
|
+
scheduler(run);
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
if (scheduler === 'microtask') {
|
|
7
|
+
queueMicrotask(run);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (scheduler === 'raf') {
|
|
11
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
12
|
+
requestAnimationFrame(() => run());
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
queueMicrotask(run);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
run();
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface SubscribeOptions {
|
|
2
|
+
immediate?: boolean;
|
|
3
|
+
}
|
|
4
|
+
export type SignalListener<T> = (value: T) => void;
|
|
5
|
+
export type Cleanup = () => void;
|
|
6
|
+
export type EffectCallback = () => void | Cleanup;
|
|
7
|
+
export type EffectScheduler = 'sync' | 'microtask' | 'raf' | ((run: () => void) => void);
|
|
8
|
+
export interface EffectOptions {
|
|
9
|
+
scheduler?: EffectScheduler;
|
|
10
|
+
}
|
|
11
|
+
export interface ObserverDependency {
|
|
12
|
+
subscribers: Set<ReactiveObserver>;
|
|
13
|
+
}
|
|
14
|
+
export interface ReactiveObserver {
|
|
15
|
+
deps: Set<ObserverDependency>;
|
|
16
|
+
active: boolean;
|
|
17
|
+
schedule(): void;
|
|
18
|
+
notify(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Cleanup, EffectCallback, EffectOptions, SubscribeOptions } from './signal-types.js';
|
|
2
|
+
export interface ReadonlySignal<T> {
|
|
3
|
+
(): T;
|
|
4
|
+
peek(): T;
|
|
5
|
+
subscribe(listener: (value: T) => void, options?: SubscribeOptions): Cleanup;
|
|
6
|
+
}
|
|
7
|
+
export interface Signal<T> extends ReadonlySignal<T> {
|
|
8
|
+
(nextValue: T): T;
|
|
9
|
+
set(nextValue: T): T;
|
|
10
|
+
update(updater: (currentValue: T) => T): T;
|
|
11
|
+
}
|
|
12
|
+
export interface ComputedSignal<T> extends ReadonlySignal<T> {
|
|
13
|
+
stop(): void;
|
|
14
|
+
}
|
|
15
|
+
export declare function signal<T>(initialValue: T): Signal<T>;
|
|
16
|
+
export declare function computed<T>(getter: () => T): ComputedSignal<T>;
|
|
17
|
+
export declare function effect(fn: EffectCallback, options?: EffectOptions): Cleanup;
|
|
18
|
+
export { batch, untrack } from './signal-context.js';
|
|
19
|
+
export declare function isSignal<T = unknown>(value: unknown): value is ReadonlySignal<T>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { batch, READ, untrack } from './signal-context.js';
|
|
2
|
+
import { ComputedNode, EffectNode, SignalNode } from './signal-nodes.js';
|
|
3
|
+
// Create a mutable signal function with helper methods attached to it.
|
|
4
|
+
export function signal(initialValue) {
|
|
5
|
+
const node = new SignalNode(initialValue);
|
|
6
|
+
const readWriteSignal = ((nextValue = READ) => {
|
|
7
|
+
if (nextValue === READ) {
|
|
8
|
+
return node.get();
|
|
9
|
+
}
|
|
10
|
+
return node.set(nextValue);
|
|
11
|
+
});
|
|
12
|
+
readWriteSignal.set = (nextValue) => node.set(nextValue);
|
|
13
|
+
readWriteSignal.update = (updater) => node.update(updater);
|
|
14
|
+
readWriteSignal.peek = () => node.peek();
|
|
15
|
+
readWriteSignal.subscribe = (listener, options) => node.subscribe(listener, options);
|
|
16
|
+
return readWriteSignal;
|
|
17
|
+
}
|
|
18
|
+
// Create a read-only computed signal backed by dependency tracking.
|
|
19
|
+
export function computed(getter) {
|
|
20
|
+
const node = new ComputedNode(getter);
|
|
21
|
+
const readOnlySignal = ((nextValue = READ) => {
|
|
22
|
+
if (nextValue !== READ) {
|
|
23
|
+
throw new Error('Computed signals are read-only');
|
|
24
|
+
}
|
|
25
|
+
return node.get();
|
|
26
|
+
});
|
|
27
|
+
readOnlySignal.peek = () => node.peek();
|
|
28
|
+
readOnlySignal.subscribe = (listener, options) => node.subscribe(listener, options);
|
|
29
|
+
readOnlySignal.stop = () => node.stop();
|
|
30
|
+
return readOnlySignal;
|
|
31
|
+
}
|
|
32
|
+
// Run a reactive effect and return a disposer for it.
|
|
33
|
+
export function effect(fn, options = {}) {
|
|
34
|
+
const node = new EffectNode(fn, options);
|
|
35
|
+
return () => node.stop();
|
|
36
|
+
}
|
|
37
|
+
export { batch, untrack } from './signal-context.js';
|
|
38
|
+
// Detect Qore signal-like values by their callable shape plus peek helper.
|
|
39
|
+
export function isSignal(value) {
|
|
40
|
+
return typeof value === 'function' && typeof value.peek === 'function';
|
|
41
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { BackpressureOptions, Deferred, NormalizedBackpressure } from './stream-types.js';
|
|
2
|
+
export declare function normalizeBackpressure(backpressure: number | BackpressureOptions | null | undefined): NormalizedBackpressure;
|
|
3
|
+
export declare function createDeferred<T>(): Deferred<T>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Normalize backpressure into one consistent shape the runtime can enforce.
|
|
2
|
+
export function normalizeBackpressure(backpressure) {
|
|
3
|
+
if (backpressure == null) {
|
|
4
|
+
return {
|
|
5
|
+
interval: 0,
|
|
6
|
+
buffer: Infinity,
|
|
7
|
+
overflow: 'wait'
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
const options = typeof backpressure === 'number'
|
|
11
|
+
? { interval: backpressure }
|
|
12
|
+
: backpressure;
|
|
13
|
+
const interval = options.interval ?? 0;
|
|
14
|
+
const buffer = options.buffer ?? Infinity;
|
|
15
|
+
const overflow = options.overflow ?? 'wait';
|
|
16
|
+
if (!Number.isFinite(interval) || interval < 0) {
|
|
17
|
+
throw new TypeError('Qore stream backpressure.interval must be a non-negative finite number');
|
|
18
|
+
}
|
|
19
|
+
if (buffer !== Infinity && (!Number.isInteger(buffer) || buffer < 1)) {
|
|
20
|
+
throw new TypeError('Qore stream backpressure.buffer must be a positive integer or Infinity');
|
|
21
|
+
}
|
|
22
|
+
if (!['wait', 'drop-oldest', 'drop-newest', 'error'].includes(overflow)) {
|
|
23
|
+
throw new TypeError('Qore stream backpressure.overflow must be one of "wait", "drop-oldest", "drop-newest", or "error"');
|
|
24
|
+
}
|
|
25
|
+
return { interval, buffer, overflow };
|
|
26
|
+
}
|
|
27
|
+
// Build a deferred promise so queue operations can wait for capacity or completion.
|
|
28
|
+
export function createDeferred() {
|
|
29
|
+
let resolve;
|
|
30
|
+
let reject;
|
|
31
|
+
const promise = new Promise((nextResolve, nextReject) => {
|
|
32
|
+
resolve = nextResolve;
|
|
33
|
+
reject = nextReject;
|
|
34
|
+
});
|
|
35
|
+
return { promise, resolve, reject };
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { NormalizedBackpressure } from './stream-types.js';
|
|
2
|
+
export interface StreamBuffer<TChunk, TValue> {
|
|
3
|
+
push(chunk: TChunk): Promise<TValue>;
|
|
4
|
+
flush(result?: TValue): TValue;
|
|
5
|
+
flushScheduled(): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export declare function createStreamBuffer<TChunk, TValue>({ enqueueChunk, fail, isTerminated, pressure, readCurrent, recordDrop, signal, updateBuffered, writeChunk }: {
|
|
8
|
+
enqueueChunk(chunk: TChunk): void;
|
|
9
|
+
fail(error: unknown): Error | TValue;
|
|
10
|
+
isTerminated(): boolean;
|
|
11
|
+
pressure: NormalizedBackpressure;
|
|
12
|
+
readCurrent(): TValue;
|
|
13
|
+
recordDrop(): void;
|
|
14
|
+
signal: AbortSignal;
|
|
15
|
+
updateBuffered(count: number): void;
|
|
16
|
+
writeChunk(chunk: TChunk): TValue;
|
|
17
|
+
}): StreamBuffer<TChunk, TValue>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { createDeferred } from './stream-backpressure.js';
|
|
2
|
+
import { sleep } from '../shared/utils.js';
|
|
3
|
+
export function createStreamBuffer({ enqueueChunk, fail, isTerminated, pressure, readCurrent, recordDrop, signal, updateBuffered, writeChunk }) {
|
|
4
|
+
const pendingWrites = [];
|
|
5
|
+
const spaceWaiters = [];
|
|
6
|
+
const scheduledPushes = new Set();
|
|
7
|
+
let scheduledPushError = null;
|
|
8
|
+
let drainPromise = null;
|
|
9
|
+
let lastPushAt = 0;
|
|
10
|
+
function releaseSpaceWaiters() {
|
|
11
|
+
if (pressure.buffer === Infinity) {
|
|
12
|
+
while (spaceWaiters.length > 0) {
|
|
13
|
+
spaceWaiters.shift()?.resolve(undefined);
|
|
14
|
+
}
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
while (spaceWaiters.length > 0 && pendingWrites.length < pressure.buffer) {
|
|
18
|
+
spaceWaiters.shift()?.resolve(undefined);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function flush(result = readCurrent()) {
|
|
22
|
+
while (pendingWrites.length > 0) {
|
|
23
|
+
pendingWrites.shift()?.resolve(result);
|
|
24
|
+
}
|
|
25
|
+
updateBuffered(0);
|
|
26
|
+
releaseSpaceWaiters();
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
async function flushScheduled() {
|
|
30
|
+
while (scheduledPushes.size > 0) {
|
|
31
|
+
await Promise.allSettled(Array.from(scheduledPushes));
|
|
32
|
+
}
|
|
33
|
+
if (scheduledPushError !== null) {
|
|
34
|
+
throw scheduledPushError;
|
|
35
|
+
}
|
|
36
|
+
while (drainPromise) {
|
|
37
|
+
await drainPromise;
|
|
38
|
+
}
|
|
39
|
+
if (scheduledPushError !== null) {
|
|
40
|
+
throw scheduledPushError;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function waitForNextWindow() {
|
|
44
|
+
if (pressure.interval <= 0 || lastPushAt === 0) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const remaining = pressure.interval - (Date.now() - lastPushAt);
|
|
48
|
+
if (remaining > 0) {
|
|
49
|
+
try {
|
|
50
|
+
await sleep(remaining, signal);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (!signal.aborted) {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function scheduleDrain() {
|
|
60
|
+
if (drainPromise || isTerminated()) {
|
|
61
|
+
return drainPromise;
|
|
62
|
+
}
|
|
63
|
+
drainPromise = (async () => {
|
|
64
|
+
try {
|
|
65
|
+
while (pendingWrites.length > 0 && !isTerminated() && !signal.aborted) {
|
|
66
|
+
const entry = pendingWrites.shift();
|
|
67
|
+
updateBuffered(pendingWrites.length);
|
|
68
|
+
releaseSpaceWaiters();
|
|
69
|
+
await waitForNextWindow();
|
|
70
|
+
if (isTerminated() || signal.aborted) {
|
|
71
|
+
entry.resolve(readCurrent());
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
enqueueChunk(entry.chunk);
|
|
75
|
+
const nextValue = writeChunk(entry.chunk);
|
|
76
|
+
lastPushAt = Date.now();
|
|
77
|
+
entry.resolve(nextValue);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
drainPromise = null;
|
|
82
|
+
if (pendingWrites.length > 0 && !isTerminated() && !signal.aborted) {
|
|
83
|
+
scheduleDrain();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
})();
|
|
87
|
+
return drainPromise;
|
|
88
|
+
}
|
|
89
|
+
async function waitForBufferSpace() {
|
|
90
|
+
if (pressure.buffer === Infinity) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
while (!isTerminated() && !signal.aborted && pendingWrites.length >= pressure.buffer) {
|
|
94
|
+
const gate = createDeferred();
|
|
95
|
+
spaceWaiters.push(gate);
|
|
96
|
+
await gate.promise;
|
|
97
|
+
}
|
|
98
|
+
return !isTerminated() && !signal.aborted;
|
|
99
|
+
}
|
|
100
|
+
async function push(chunk) {
|
|
101
|
+
if (isTerminated() || signal.aborted) {
|
|
102
|
+
return readCurrent();
|
|
103
|
+
}
|
|
104
|
+
const writeTask = (async () => {
|
|
105
|
+
if (pressure.overflow === 'wait') {
|
|
106
|
+
const hasSpace = await waitForBufferSpace();
|
|
107
|
+
if (!hasSpace) {
|
|
108
|
+
return readCurrent();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (pressure.buffer !== Infinity && pendingWrites.length >= pressure.buffer) {
|
|
112
|
+
if (pressure.overflow === 'drop-oldest' && pendingWrites.length > 0) {
|
|
113
|
+
const droppedWrite = pendingWrites.shift();
|
|
114
|
+
recordDrop();
|
|
115
|
+
droppedWrite.resolve(readCurrent());
|
|
116
|
+
}
|
|
117
|
+
else if (pressure.overflow === 'drop-newest' || pressure.overflow === 'drop-oldest') {
|
|
118
|
+
recordDrop();
|
|
119
|
+
return readCurrent();
|
|
120
|
+
}
|
|
121
|
+
else if (pressure.overflow === 'error') {
|
|
122
|
+
const overflowError = new Error('Qore stream backpressure buffer overflow');
|
|
123
|
+
if (scheduledPushError === null) {
|
|
124
|
+
scheduledPushError = overflowError;
|
|
125
|
+
}
|
|
126
|
+
fail(overflowError);
|
|
127
|
+
return readCurrent();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (isTerminated() || signal.aborted) {
|
|
131
|
+
return readCurrent();
|
|
132
|
+
}
|
|
133
|
+
const entry = createDeferred();
|
|
134
|
+
entry.chunk = chunk;
|
|
135
|
+
pendingWrites.push(entry);
|
|
136
|
+
updateBuffered(pendingWrites.length);
|
|
137
|
+
scheduleDrain();
|
|
138
|
+
return entry.promise;
|
|
139
|
+
})();
|
|
140
|
+
scheduledPushes.add(writeTask);
|
|
141
|
+
try {
|
|
142
|
+
return await writeTask;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (scheduledPushError === null) {
|
|
146
|
+
scheduledPushError = error;
|
|
147
|
+
}
|
|
148
|
+
if (!isTerminated() && !signal.aborted) {
|
|
149
|
+
fail(error);
|
|
150
|
+
}
|
|
151
|
+
return readCurrent();
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
scheduledPushes.delete(writeTask);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
flush,
|
|
159
|
+
flushScheduled,
|
|
160
|
+
push
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AsyncQueue } from './stream-queue.js';
|
|
2
|
+
import type { QoreStream } from './stream-types.js';
|
|
3
|
+
export type IterableStream<TChunk, TValue> = QoreStream<TChunk, TValue> & {
|
|
4
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<TChunk>;
|
|
5
|
+
};
|
|
6
|
+
export declare function attachStreamIterator<TChunk, TValue>(readable: IterableStream<TChunk, TValue>, queue: AsyncQueue<TChunk>, shouldAbortOnReturn: () => boolean, abort: (reason?: unknown) => TValue): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function attachStreamIterator(readable, queue, shouldAbortOnReturn, abort) {
|
|
2
|
+
readable[Symbol.asyncIterator] = async function* () {
|
|
3
|
+
try {
|
|
4
|
+
for await (const chunk of queue) {
|
|
5
|
+
yield chunk;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
finally {
|
|
9
|
+
if (shouldAbortOnReturn()) {
|
|
10
|
+
abort('Stream consumer disposed');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface StreamLifecycle<TValue> {
|
|
2
|
+
isTerminated(): boolean;
|
|
3
|
+
markTerminated(): boolean;
|
|
4
|
+
setCleanup(cleanup: () => TValue | void): void;
|
|
5
|
+
stop(finalizer: () => TValue, cleanup?: () => TValue | void): TValue;
|
|
6
|
+
}
|
|
7
|
+
export declare function createStreamLifecycle<TValue>({ closeQueue, readCurrent }: {
|
|
8
|
+
closeQueue(): void;
|
|
9
|
+
readCurrent(): TValue;
|
|
10
|
+
}): StreamLifecycle<TValue>;
|