@qorejs/qore 0.7.3 → 0.9.0

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.
Files changed (44) hide show
  1. package/README.md +275 -5
  2. package/dist/src/core/owner.d.ts +14 -0
  3. package/dist/src/core/owner.js +66 -0
  4. package/dist/src/core/signal-context.js +19 -9
  5. package/dist/src/core/signal-nodes.d.ts +5 -0
  6. package/dist/src/core/signal-nodes.js +51 -21
  7. package/dist/src/core/signal-types.d.ts +2 -0
  8. package/dist/src/core/signal.d.ts +1 -0
  9. package/dist/src/core/signal.js +2 -0
  10. package/dist/src/core/stream-types.d.ts +12 -0
  11. package/dist/src/core/stream.d.ts +1 -1
  12. package/dist/src/core/stream.js +144 -0
  13. package/dist/src/dom/app.js +2 -3
  14. package/dist/src/dom/dom.d.ts +1 -0
  15. package/dist/src/dom/dom.js +96 -9
  16. package/dist/src/dom/scope.d.ts +3 -1
  17. package/dist/src/dom/scope.js +10 -3
  18. package/dist/src/index.d.ts +10 -4
  19. package/dist/src/index.js +7 -2
  20. package/dist/src/providers/anthropic.js +6 -4
  21. package/dist/src/providers/deepseek.d.ts +2 -0
  22. package/dist/src/providers/deepseek.js +90 -0
  23. package/dist/src/providers/line-adapter.d.ts +2 -0
  24. package/dist/src/providers/line-adapter.js +83 -0
  25. package/dist/src/providers/line-parser.d.ts +5 -0
  26. package/dist/src/providers/line-parser.js +103 -0
  27. package/dist/src/providers/metadata.d.ts +8 -0
  28. package/dist/src/providers/metadata.js +193 -0
  29. package/dist/src/providers/ollama.d.ts +2 -0
  30. package/dist/src/providers/ollama.js +82 -0
  31. package/dist/src/providers/openai.js +6 -4
  32. package/dist/src/providers/openrouter.d.ts +2 -0
  33. package/dist/src/providers/openrouter.js +90 -0
  34. package/dist/src/providers/sse-adapter.js +129 -44
  35. package/dist/src/providers/sse-parser.d.ts +1 -1
  36. package/dist/src/providers/sse-parser.js +74 -38
  37. package/dist/src/providers/sse.d.ts +7 -1
  38. package/dist/src/providers/sse.js +6 -0
  39. package/dist/src/providers/types.d.ts +145 -0
  40. package/dist/src/server/sse-response.d.ts +16 -0
  41. package/dist/src/server/sse-response.js +102 -0
  42. package/dist/src/shared/utils.d.ts +1 -0
  43. package/dist/src/shared/utils.js +8 -1
  44. package/package.json +9 -2
@@ -16,4 +16,5 @@ export declare function signal<T>(initialValue: T): Signal<T>;
16
16
  export declare function computed<T>(getter: () => T): ComputedSignal<T>;
17
17
  export declare function effect(fn: EffectCallback, options?: EffectOptions): Cleanup;
18
18
  export { batch, untrack } from './signal-context.js';
19
+ export { createRoot, onCleanup } from './owner.js';
19
20
  export declare function isSignal<T = unknown>(value: unknown): value is ReadonlySignal<T>;
@@ -1,3 +1,4 @@
1
+ import { createRoot, onCleanup } from './owner.js';
1
2
  import { batch, READ, untrack } from './signal-context.js';
2
3
  import { ComputedNode, EffectNode, SignalNode } from './signal-nodes.js';
3
4
  // Create a mutable signal function with helper methods attached to it.
@@ -35,6 +36,7 @@ export function effect(fn, options = {}) {
35
36
  return () => node.stop();
36
37
  }
37
38
  export { batch, untrack } from './signal-context.js';
39
+ export { createRoot, onCleanup } from './owner.js';
38
40
  // Detect Qore signal-like values by their callable shape plus peek helper.
39
41
  export function isSignal(value) {
40
42
  return typeof value === 'function' && typeof value.peek === 'function';
@@ -6,6 +6,7 @@ export interface BackpressureOptions {
6
6
  buffer?: number;
7
7
  overflow?: OverflowStrategy;
8
8
  }
9
+ export type RetryBackoff = number | number[] | 'exponential' | ((retry: number, error: unknown) => MaybePromise<number>);
9
10
  export interface NormalizedBackpressure {
10
11
  interval: number;
11
12
  buffer: number;
@@ -21,6 +22,11 @@ export interface StreamOptions<TChunk = unknown, TValue = string> {
21
22
  reduce?: (currentValue: TValue, chunk: TChunk, index: number) => TValue;
22
23
  backpressure?: number | BackpressureOptions | null;
23
24
  }
25
+ export interface RetryableStreamOptions<TChunk = unknown, TValue = string> extends StreamOptions<TChunk, TValue> {
26
+ maxRetries?: number;
27
+ backoff?: RetryBackoff;
28
+ }
29
+ export type StreamPipeStage<TChunk = unknown, TValue = string> = (value: TValue, index: number) => MaybePromise<StreamInput<TChunk, TValue>>;
24
30
  export interface StreamController<TChunk = unknown, TValue = string> {
25
31
  readonly signal: GlobalAbortSignal;
26
32
  push(chunk: TChunk): Promise<TValue>;
@@ -57,5 +63,11 @@ export interface StreamFactory {
57
63
  latest<TChunk>(sourceOrSetup: StreamInput<TChunk, TChunk | null>, options?: StreamOptions<TChunk, TChunk | null>): QoreStream<TChunk, TChunk | null>;
58
64
  withBackpressure<TChunk = unknown, TValue = string>(sourceOrSetup: StreamInput<TChunk, TValue>, backpressure: number | BackpressureOptions, options?: StreamOptions<TChunk, TValue>): QoreStream<TChunk, TValue>;
59
65
  paced<TChunk = unknown, TValue = string>(sourceOrSetup: StreamInput<TChunk, TValue>, interval: number, options?: StreamOptions<TChunk, TValue>): QoreStream<TChunk, TValue>;
66
+ merge<TChunk = unknown, TValue = string>(sources: Array<SourceLike<TChunk>>, options?: StreamOptions<TChunk, TValue>): QoreStream<TChunk, TValue>;
67
+ concat<TChunk = unknown, TValue = string>(sources: Array<SourceLike<TChunk>>, options?: StreamOptions<TChunk, TValue>): QoreStream<TChunk, TValue>;
68
+ pipe<TChunk = unknown, TValue = string>(sourceOrSetup: StreamInput<TChunk, TValue>, stages: Array<StreamPipeStage<TChunk, TValue>>, options?: StreamOptions<TChunk, TValue>): QoreStream<TChunk, TValue>;
69
+ race<TChunk = unknown, TValue = string>(sources: Array<SourceLike<TChunk>>, options?: StreamOptions<TChunk, TValue>): QoreStream<TChunk, TValue>;
70
+ retryable<TChunk = unknown, TValue = string>(sourceFactory: (attempt: number) => StreamInput<TChunk, TValue>, options?: RetryableStreamOptions<TChunk, TValue>): QoreStream<TChunk, TValue>;
71
+ switchMap<TInput, TChunk = unknown, TValue = string>(source: SourceLike<TInput>, mapper: (value: TInput, index: number) => MaybePromise<SourceLike<TChunk>>, options?: StreamOptions<TChunk, TValue>): QoreStream<TChunk, TValue>;
60
72
  }
61
73
  export type StreamResponseState<TChunk, TValue> = ResponseState<TChunk, TValue>;
@@ -7,5 +7,5 @@ export declare function from<TChunk, TValue = string>(source: SourceLike<TChunk>
7
7
  export declare function mapStream<TInput, TOutput, TValue = string>(source: SourceLike<TInput>, mapper: (chunk: TInput, index: number) => MaybePromise<TOutput>, options?: StreamOptions<TOutput, TValue>): QoreStream<TOutput, TValue>;
8
8
  export declare function scanStream<TInput, TOutput>(source: SourceLike<TInput>, reducer: (currentValue: TOutput, chunk: TInput, index: number) => MaybePromise<TOutput>, seed: TOutput, options?: Omit<StreamOptions<TOutput, TOutput>, 'seed' | 'reduce'>): QoreStream<TOutput, TOutput>;
9
9
  export { createStream } from './stream-runtime.js';
10
- export type { BackpressureOptions, QoreStream, StreamController, StreamFactory, StreamInput, StreamOptions, StreamSetup } from './stream-types.js';
10
+ export type { BackpressureOptions, QoreStream, RetryBackoff, RetryableStreamOptions, StreamController, StreamFactory, StreamInput, StreamOptions, StreamSetup } from './stream-types.js';
11
11
  export { toAsyncIterable } from './iterable.js';
@@ -1,5 +1,6 @@
1
1
  import { toAsyncIterable } from './iterable.js';
2
2
  import { createStream } from './stream-runtime.js';
3
+ import { startSource } from './stream-source.js';
3
4
  import { reduceText } from './stream-state.js';
4
5
  import { sleep } from '../shared/utils.js';
5
6
  const streamFactory = ((sourceOrSetup, options = {}) => createStream(sourceOrSetup, {
@@ -36,8 +37,151 @@ streamFactory.withBackpressure = (sourceOrSetup, backpressure, options = {}) =>
36
37
  });
37
38
  // Shorthand for the first backpressure primitive: minimum interval between chunks.
38
39
  streamFactory.paced = (sourceOrSetup, interval, options = {}) => streamFactory.withBackpressure(sourceOrSetup, { interval }, options);
40
+ streamFactory.merge = (sources, options = {}) => createStream(async (controller) => {
41
+ await Promise.all(sources.map(async (source) => {
42
+ await startSource(source, controller);
43
+ }));
44
+ }, options);
45
+ streamFactory.concat = (sources, options = {}) => createStream(async (controller) => {
46
+ for (const source of sources) {
47
+ if (controller.signal.aborted) {
48
+ break;
49
+ }
50
+ await startSource(source, controller);
51
+ }
52
+ }, options);
53
+ streamFactory.pipe = (sourceOrSetup, stages, options = {}) => createStream(async (controller) => {
54
+ const consumeSource = async (currentSource) => {
55
+ const currentStream = createStream(currentSource, options);
56
+ for await (const chunk of currentStream) {
57
+ if (controller.signal.aborted) {
58
+ break;
59
+ }
60
+ await controller.push(chunk);
61
+ }
62
+ return currentStream.ready;
63
+ };
64
+ let currentValue = await consumeSource(sourceOrSetup);
65
+ for (const [index, stage] of stages.entries()) {
66
+ if (controller.signal.aborted) {
67
+ break;
68
+ }
69
+ const nextSource = await stage(currentValue, index);
70
+ currentValue = await consumeSource(nextSource);
71
+ }
72
+ }, options);
73
+ streamFactory.race = (sources, options = {}) => createStream(async (controller) => {
74
+ const activeIterators = sources.map((source, index) => ({
75
+ index,
76
+ iterator: toAsyncIterable(source)[Symbol.asyncIterator]()
77
+ }));
78
+ let winner = null;
79
+ try {
80
+ while (!controller.signal.aborted && winner === null && activeIterators.length > 0) {
81
+ const nextResult = await Promise.race(activeIterators.map(async ({ index, iterator }) => ({
82
+ index,
83
+ result: await iterator.next()
84
+ })));
85
+ if (nextResult.result.done) {
86
+ const exhaustedIndex = activeIterators.findIndex(({ index }) => index === nextResult.index);
87
+ if (exhaustedIndex >= 0) {
88
+ activeIterators.splice(exhaustedIndex, 1);
89
+ }
90
+ continue;
91
+ }
92
+ winner = activeIterators.find(({ index }) => index === nextResult.index)?.iterator ?? null;
93
+ await controller.push(nextResult.result.value);
94
+ }
95
+ if (!winner || controller.signal.aborted) {
96
+ return;
97
+ }
98
+ for (const candidate of activeIterators) {
99
+ if (candidate.iterator !== winner) {
100
+ await candidate.iterator.return?.();
101
+ }
102
+ }
103
+ while (!controller.signal.aborted) {
104
+ const nextChunk = await winner.next();
105
+ if (nextChunk.done) {
106
+ break;
107
+ }
108
+ await controller.push(nextChunk.value);
109
+ }
110
+ }
111
+ finally {
112
+ for (const { iterator } of activeIterators) {
113
+ if (iterator !== winner) {
114
+ await iterator.return?.();
115
+ }
116
+ }
117
+ }
118
+ }, options);
119
+ streamFactory.retryable = (sourceFactory, options = {}) => {
120
+ const { maxRetries = 0, backoff = 'exponential', ...streamOptions } = options;
121
+ return createStream(async (controller) => {
122
+ let retries = 0;
123
+ while (!controller.signal.aborted) {
124
+ try {
125
+ await startSource(sourceFactory(retries), controller);
126
+ return;
127
+ }
128
+ catch (error) {
129
+ if (retries >= maxRetries || controller.signal.aborted) {
130
+ throw error;
131
+ }
132
+ retries += 1;
133
+ const delay = await resolveRetryDelay(backoff, retries, error);
134
+ if (delay > 0) {
135
+ await sleep(delay, controller.signal);
136
+ }
137
+ }
138
+ }
139
+ }, streamOptions);
140
+ };
141
+ streamFactory.switchMap = (source, mapper, options = {}) => createStream(async (controller) => {
142
+ let index = 0;
143
+ let activeToken = 0;
144
+ let activeTask = null;
145
+ const startInner = (token, innerSource) => (async () => {
146
+ for await (const chunk of toAsyncIterable(innerSource)) {
147
+ if (controller.signal.aborted || token !== activeToken) {
148
+ break;
149
+ }
150
+ await controller.push(chunk);
151
+ }
152
+ })().catch((error) => {
153
+ if (token !== activeToken || controller.signal.aborted) {
154
+ return;
155
+ }
156
+ throw error;
157
+ });
158
+ for await (const value of toAsyncIterable(source)) {
159
+ if (controller.signal.aborted) {
160
+ break;
161
+ }
162
+ activeToken += 1;
163
+ const token = activeToken;
164
+ const innerSource = await mapper(value, index);
165
+ activeTask = startInner(token, innerSource);
166
+ index += 1;
167
+ }
168
+ await activeTask;
169
+ }, options);
39
170
  // Create a text-accumulating stream by default.
40
171
  export const stream = streamFactory;
172
+ async function resolveRetryDelay(backoff, retry, error) {
173
+ if (typeof backoff === 'function') {
174
+ return Math.max(0, await backoff(retry, error));
175
+ }
176
+ if (Array.isArray(backoff)) {
177
+ const nextDelay = backoff[Math.min(retry - 1, backoff.length - 1)] ?? 0;
178
+ return Math.max(0, nextDelay);
179
+ }
180
+ if (backoff === 'exponential') {
181
+ return 250 * 2 ** (retry - 1);
182
+ }
183
+ return Math.max(0, backoff);
184
+ }
41
185
  // Turn any iterable or async iterable into a stream, optionally adding source delay.
42
186
  export function from(source, options = {}) {
43
187
  const { delay = 0, ...streamOptions } = options;
@@ -2,11 +2,10 @@ import { dynamic, fragment, h, list, mount, renderResponse, show, text } from '.
2
2
  import { batch, computed, effect, signal, untrack } from '../core/signal.js';
3
3
  import { response } from '../core/response.js';
4
4
  import { from, mapStream, scanStream, stream } from '../core/stream.js';
5
+ import { assertDocument } from './scope.js';
5
6
  // Resolve a CSS selector or direct node into the root mount target.
6
7
  function resolveTarget(target) {
7
- if (typeof document === 'undefined') {
8
- throw new Error('Qore app mounting requires a browser-like environment');
9
- }
8
+ assertDocument('createApp(...).mount(...)');
10
9
  if (typeof target === 'string') {
11
10
  const element = document.querySelector(target);
12
11
  if (!element) {
@@ -5,6 +5,7 @@ export declare function dynamic<T>(source: ReactiveValue<T>, render?: (value: T)
5
5
  export declare function show<T>(source: ReactiveValue<T>, render?: (value: T) => QoreChild, fallback?: QoreChild | ((value: T) => QoreChild)): QoreDocumentFragment;
6
6
  export declare function list<T>(source: ReactiveValue<Iterable<T> | ArrayLike<T> | null | undefined>, render: (item: T, index: number) => QoreChild, options?: {
7
7
  fallback?: QoreChild | ((items: T[]) => QoreChild);
8
+ key?: (item: T, index: number) => unknown;
8
9
  }): QoreDocumentFragment;
9
10
  export declare function renderResponse<TChunk, TValue>(responseState: ResponseState<TChunk, TValue>, views?: ResponseViews<TChunk, TValue>): QoreDocumentFragment;
10
11
  export declare function h(tag: string, props?: Record<string, unknown> | null, ...children: QoreChild[]): QoreElement;
@@ -63,6 +63,27 @@ function replaceRange(start, end, nextValue) {
63
63
  parent.insertBefore(node, end);
64
64
  }
65
65
  }
66
+ function insertBefore(parent, reference, value) {
67
+ for (const node of materialize(value)) {
68
+ parent.insertBefore(node, reference);
69
+ }
70
+ }
71
+ function destroyKeyedEntry(entry) {
72
+ disposeScope(entry.scope);
73
+ clearRange(entry.start, entry.end);
74
+ entry.start.remove();
75
+ entry.end.remove();
76
+ }
77
+ function renderKeyedEntry(parent, reference, item, index, render) {
78
+ const start = document.createComment('qore-list-item-start');
79
+ const end = document.createComment('qore-list-item-end');
80
+ const scope = createScope();
81
+ parent.insertBefore(start, reference);
82
+ const content = withScope(scope, () => render(item, index));
83
+ insertBefore(parent, reference, content);
84
+ parent.insertBefore(end, reference);
85
+ return { key: null, value: item, index, start, end, scope };
86
+ }
66
87
  // Allow mount targets to be passed as selectors or direct nodes.
67
88
  function resolveRoot(root) {
68
89
  if (typeof root === 'string') {
@@ -76,7 +97,7 @@ function resolveRoot(root) {
76
97
  }
77
98
  // Build a fragment from a variadic list of children.
78
99
  export function fragment(...children) {
79
- assertDocument();
100
+ assertDocument('fragment()');
80
101
  const node = document.createDocumentFragment();
81
102
  for (const child of children) {
82
103
  appendChild(node, child);
@@ -85,7 +106,7 @@ export function fragment(...children) {
85
106
  }
86
107
  // Render a live region between comment markers and refresh it when the source changes.
87
108
  export function dynamic(source, render = (value) => value) {
88
- assertDocument();
109
+ assertDocument('dynamic()');
89
110
  const start = document.createComment('qore-dynamic-start');
90
111
  const end = document.createComment('qore-dynamic-end');
91
112
  const node = document.createDocumentFragment();
@@ -115,18 +136,84 @@ export function show(source, render, fallback = null) {
115
136
  }
116
137
  // Render a list reactively, or a fallback when the collection is empty.
117
138
  export function list(source, render, options = {}) {
118
- const { fallback = null } = options;
119
- return dynamic(source, (value) => {
139
+ const { fallback = null, key } = options;
140
+ if (!key) {
141
+ return dynamic(source, (value) => {
142
+ const items = value == null
143
+ ? []
144
+ : Array.isArray(value)
145
+ ? value
146
+ : Array.from(value);
147
+ if (items.length === 0) {
148
+ return resolveTemplate(fallback, items);
149
+ }
150
+ return items.map((item, index) => render(item, index));
151
+ });
152
+ }
153
+ assertDocument('list()');
154
+ const start = document.createComment('qore-list-start');
155
+ const end = document.createComment('qore-list-end');
156
+ const node = document.createDocumentFragment();
157
+ node.append(start, end);
158
+ let entries = [];
159
+ let fallbackScope = null;
160
+ const stop = effect(() => {
161
+ const value = resolveAccessor(source);
120
162
  const items = value == null
121
163
  ? []
122
164
  : Array.isArray(value)
123
165
  ? value
124
166
  : Array.from(value);
167
+ const parent = end.parentNode;
168
+ if (!parent) {
169
+ return;
170
+ }
125
171
  if (items.length === 0) {
126
- return resolveTemplate(fallback, items);
172
+ for (const entry of entries) {
173
+ destroyKeyedEntry(entry);
174
+ }
175
+ entries = [];
176
+ disposeScope(fallbackScope);
177
+ fallbackScope = createScope();
178
+ const renderedFallback = withScope(fallbackScope, () => resolveTemplate(fallback, items));
179
+ replaceRange(start, end, renderedFallback);
180
+ return;
181
+ }
182
+ disposeScope(fallbackScope);
183
+ fallbackScope = null;
184
+ const nextKeys = items.map((item, index) => key(item, index));
185
+ const canAppend = entries.length <= items.length
186
+ && entries.every((entry, index) => Object.is(entry.key, nextKeys[index]));
187
+ if (!canAppend) {
188
+ for (const entry of entries) {
189
+ destroyKeyedEntry(entry);
190
+ }
191
+ clearRange(start, end);
192
+ entries = [];
193
+ }
194
+ if (!canAppend) {
195
+ for (let index = 0; index < items.length; index += 1) {
196
+ const entry = renderKeyedEntry(parent, end, items[index], index, render);
197
+ entry.key = nextKeys[index];
198
+ entries.push(entry);
199
+ }
200
+ return;
201
+ }
202
+ for (let index = entries.length; index < items.length; index += 1) {
203
+ const entry = renderKeyedEntry(parent, end, items[index], index, render);
204
+ entry.key = nextKeys[index];
205
+ entries.push(entry);
127
206
  }
128
- return items.map((item, index) => render(item, index));
129
207
  });
208
+ registerCleanup(() => {
209
+ stop();
210
+ disposeScope(fallbackScope);
211
+ for (const entry of entries) {
212
+ destroyKeyedEntry(entry);
213
+ }
214
+ entries = [];
215
+ });
216
+ return node;
130
217
  }
131
218
  // Render response state through status-aware template overrides.
132
219
  export function renderResponse(responseState, views = {}) {
@@ -142,7 +229,7 @@ export function renderResponse(responseState, views = {}) {
142
229
  });
143
230
  }
144
231
  export function h(tag, props = null, ...children) {
145
- assertDocument();
232
+ assertDocument('h()');
146
233
  if (typeof tag === 'function') {
147
234
  return tag({
148
235
  ...(props ?? {}),
@@ -162,7 +249,7 @@ export function h(tag, props = null, ...children) {
162
249
  }
163
250
  // Create a text node and keep it in sync with a reactive getter when necessary.
164
251
  export function text(valueOrGetter) {
165
- assertDocument();
252
+ assertDocument('text()');
166
253
  const node = document.createTextNode('');
167
254
  if (isReactiveValue(valueOrGetter)) {
168
255
  const stop = effect(() => {
@@ -177,7 +264,7 @@ export function text(valueOrGetter) {
177
264
  }
178
265
  // Mount a view into a root element and return a disposer for its reactive scope.
179
266
  export function mount(root, view) {
180
- assertDocument();
267
+ assertDocument('mount()');
181
268
  const target = resolveRoot(root);
182
269
  target[ROOT_CLEANUP]?.();
183
270
  const scope = createScope();
@@ -3,7 +3,9 @@ export declare const ROOT_CLEANUP: unique symbol;
3
3
  export interface Scope {
4
4
  cleanups: Cleanup[];
5
5
  }
6
- export declare function assertDocument(): void;
6
+ export declare function canUseDOM(): boolean;
7
+ export declare function assertCanUseDOM(apiName?: string): void;
8
+ export declare function assertDocument(apiName?: string): void;
7
9
  export declare function createScope(): Scope;
8
10
  export declare function withScope<T>(scope: Scope, fn: () => T): T;
9
11
  export declare function registerCleanup<T extends Cleanup | null | undefined>(cleanup: T): T;
@@ -1,12 +1,19 @@
1
1
  // Store mount cleanup directly on the root node so remounts can tear down old scopes.
2
2
  export const ROOT_CLEANUP = Symbol('qore.dom.cleanup');
3
3
  let activeScope = null;
4
+ // Detect whether the current runtime can create and mutate real DOM nodes.
5
+ export function canUseDOM() {
6
+ return typeof document !== 'undefined';
7
+ }
4
8
  // Guard DOM helpers so they only run in browser-like environments.
5
- export function assertDocument() {
6
- if (typeof document === 'undefined') {
7
- throw new Error('Qore DOM APIs require a browser-like environment');
9
+ export function assertCanUseDOM(apiName = 'Qore DOM APIs') {
10
+ if (!canUseDOM()) {
11
+ throw new Error(`${apiName} requires a browser-like environment`);
8
12
  }
9
13
  }
14
+ export function assertDocument(apiName = 'Qore DOM APIs') {
15
+ assertCanUseDOM(apiName);
16
+ }
10
17
  // A scope collects effect disposers created while rendering a subtree.
11
18
  export function createScope() {
12
19
  return { cleanups: [] };
@@ -1,16 +1,22 @@
1
- export { signal, computed, effect, batch, untrack, isSignal } from './core/signal.js';
1
+ export { signal, computed, createRoot, effect, onCleanup, batch, untrack, isSignal } from './core/signal.js';
2
2
  export type { ComputedSignal, ReadonlySignal, Signal } from './core/signal.js';
3
3
  export type { EffectOptions, EffectScheduler, SubscribeOptions } from './core/signal-types.js';
4
4
  export { stream, createStream, from, mapStream, scanStream, toAsyncIterable } from './core/stream.js';
5
- export type { BackpressureOptions, QoreStream, StreamController, StreamFactory, StreamInput, StreamOptions, StreamSetup } from './core/stream.js';
5
+ export type { BackpressureOptions, QoreStream, RetryBackoff, RetryableStreamOptions, StreamController, StreamFactory, StreamInput, StreamOptions, StreamSetup } from './core/stream.js';
6
6
  export { createResponse, response } from './core/response.js';
7
7
  export type { CreateResponseOptions, GlobalAbortSignal, MaybePromise, ResponseConsumeContext, ResponseExecutorContext, ResponseFactory, ResponseReactiveState, ResponseRunOptions, ResponseSnapshot, ResponseSource, ResponseSourceFactory, ResponseState, ResponseStatus, SourceLike } from './core/response.js';
8
8
  export { createApp } from './dom/app.js';
9
9
  export type { AppContext, AppSetupResult, QoreApp } from './dom/app.js';
10
10
  export { dynamic, fragment, h, list, mount, renderResponse, show, text } from './dom/dom.js';
11
+ export { assertCanUseDOM, canUseDOM } from './dom/scope.js';
12
+ export { createSSEResponse } from './server/sse-response.js';
13
+ export type { CreateSSEResponseOptions, SSEFrame } from './server/sse-response.js';
11
14
  export type { GlobalDocumentFragment, GlobalElement, GlobalNode, GlobalText, MountTarget, MountView, QoreChild, QoreComponent, QoreDocumentFragment, QoreElement, QoreNode, QoreTemplate, QoreText, ReactiveValue, ResponseRenderState, ResponseViews } from './dom/types.js';
12
15
  export { createAnthropic } from './providers/anthropic.js';
16
+ export { createDeepSeek } from './providers/deepseek.js';
17
+ export { createOllama } from './providers/ollama.js';
13
18
  export { createOpenAI } from './providers/openai.js';
14
- export { createSSEAdapter } from './providers/sse.js';
15
- export type { AnthropicAdapter, AnthropicChatInput, AnthropicEvent, AnthropicMessage, AnthropicOptions, AnthropicRequest, FetchLike, OpenAIAdapter, OpenAIChatInput, OpenAIEvent, OpenAIMessage, OpenAIOptions, OpenAIRequest, ProviderHeaders, ProviderRequestOptions, SSEAdapter, SSEAdapterOptions, SSEEvent, SSERequestConfig } from './providers/sse.js';
19
+ export { createOpenRouter } from './providers/openrouter.js';
20
+ export { collectProviderMetadata, createLineAdapter, createSSEAdapter, extractAnthropicMetadata, extractDeepSeekMetadata, extractOllamaMetadata, extractOpenAIMetadata, extractOpenRouterMetadata, mergeProviderMetadata } from './providers/sse.js';
21
+ export type { AnthropicAdapter, AnthropicChatInput, AnthropicEvent, AnthropicMessage, AnthropicOptions, AnthropicRequest, DeepSeekAdapter, DeepSeekChatInput, DeepSeekEvent, DeepSeekMessage, DeepSeekOptions, DeepSeekRequest, FetchLike, LineAdapter, LineAdapterOptions, LineEvent, LineRequestConfig, OpenAIAdapter, OpenAIChatInput, OpenAIEvent, OpenAIMessage, OpenAIOptions, OpenAIRequest, OllamaAdapter, OllamaChatInput, OllamaEvent, OllamaMessage, OllamaOptions, OllamaRequest, OpenRouterAdapter, OpenRouterChatInput, OpenRouterEvent, OpenRouterMessage, OpenRouterOptions, OpenRouterRequest, ProviderHeaders, ProviderMetadataUpdate, ProviderRetryBackoff, ProviderRetryOptions, ProviderRequestOptions, ProviderStreamMetadata, ProviderUsage, SSEAdapter, SSEAdapterOptions, SSEEvent, SSERequestConfig } from './providers/sse.js';
16
22
  export { normalizeError, sleep } from './shared/utils.js';
package/dist/src/index.js CHANGED
@@ -1,10 +1,15 @@
1
1
  // Re-export the public runtime surface from a single module entrypoint.
2
- export { signal, computed, effect, batch, untrack, isSignal } from './core/signal.js';
2
+ export { signal, computed, createRoot, effect, onCleanup, batch, untrack, isSignal } from './core/signal.js';
3
3
  export { stream, createStream, from, mapStream, scanStream, toAsyncIterable } from './core/stream.js';
4
4
  export { createResponse, response } from './core/response.js';
5
5
  export { createApp } from './dom/app.js';
6
6
  export { dynamic, fragment, h, list, mount, renderResponse, show, text } from './dom/dom.js';
7
+ export { assertCanUseDOM, canUseDOM } from './dom/scope.js';
8
+ export { createSSEResponse } from './server/sse-response.js';
7
9
  export { createAnthropic } from './providers/anthropic.js';
10
+ export { createDeepSeek } from './providers/deepseek.js';
11
+ export { createOllama } from './providers/ollama.js';
8
12
  export { createOpenAI } from './providers/openai.js';
9
- export { createSSEAdapter } from './providers/sse.js';
13
+ export { createOpenRouter } from './providers/openrouter.js';
14
+ export { collectProviderMetadata, createLineAdapter, createSSEAdapter, extractAnthropicMetadata, extractDeepSeekMetadata, extractOllamaMetadata, extractOpenAIMetadata, extractOpenRouterMetadata, mergeProviderMetadata } from './providers/sse.js';
10
15
  export { normalizeError, sleep } from './shared/utils.js';
@@ -18,7 +18,7 @@ function normalizeMessages(input) {
18
18
  }
19
19
  // Keep provider setup explicit because real API keys should stay off the client.
20
20
  export function createAnthropic(options = {}) {
21
- const { apiKey, baseURL = DEFAULT_BASE_URL, model = DEFAULT_MODEL, version = DEFAULT_VERSION, maxTokens = DEFAULT_MAX_TOKENS, headers: defaultHeaders = {}, fetch: fetchImpl = globalThis.fetch } = options;
21
+ const { apiKey, baseURL = DEFAULT_BASE_URL, model = DEFAULT_MODEL, version = DEFAULT_VERSION, maxTokens = DEFAULT_MAX_TOKENS, headers: defaultHeaders = {}, fetch: fetchImpl = globalThis.fetch, retry } = options;
22
22
  const resolvedApiKey = apiKey ?? readEnv('ANTHROPIC_API_KEY');
23
23
  if (!resolvedApiKey) {
24
24
  throw new Error('Qore Anthropic adapter requires an API key. Pass apiKey or set ANTHROPIC_API_KEY.');
@@ -33,8 +33,9 @@ export function createAnthropic(options = {}) {
33
33
  ...defaultHeaders
34
34
  },
35
35
  fetch: fetchImpl,
36
+ ...(retry ? { retry } : {}),
36
37
  buildRequest(request, requestOptions = {}) {
37
- const { signal, headers = {}, ...overrides } = requestOptions;
38
+ const { signal, headers = {}, retry: _retry, ...overrides } = requestOptions;
38
39
  const config = {
39
40
  method: 'POST',
40
41
  headers,
@@ -88,14 +89,15 @@ export function createAnthropic(options = {}) {
88
89
  },
89
90
  // Match the Qore narrative directly: stream(anthropic.chat(prompt)).
90
91
  chat(input, requestOptions = {}) {
91
- const { signal, headers, ...rest } = requestOptions;
92
+ const { signal, headers, retry, ...rest } = requestOptions;
92
93
  const request = { ...rest };
93
94
  if (!('messages' in request)) {
94
95
  request.messages = normalizeMessages(input);
95
96
  }
96
97
  return streamText(request, {
97
98
  ...(signal ? { signal } : {}),
98
- ...(headers ? { headers } : {})
99
+ ...(headers ? { headers } : {}),
100
+ ...(retry ? { retry } : {})
99
101
  });
100
102
  }
101
103
  };
@@ -0,0 +1,2 @@
1
+ import type { DeepSeekAdapter, DeepSeekOptions } from './types.js';
2
+ export declare function createDeepSeek(options?: DeepSeekOptions): DeepSeekAdapter;
@@ -0,0 +1,90 @@
1
+ import { createSSEAdapter, readEnv } from './sse.js';
2
+ const DEFAULT_BASE_URL = 'https://api.deepseek.com';
3
+ const DEFAULT_MODEL = 'deepseek-chat';
4
+ function normalizeChatInput(input) {
5
+ if (typeof input === 'string') {
6
+ return [{ role: 'user', content: input }];
7
+ }
8
+ if (Array.isArray(input)) {
9
+ return input;
10
+ }
11
+ if (input && typeof input === 'object' && 'role' in input) {
12
+ return [input];
13
+ }
14
+ return input;
15
+ }
16
+ export function createDeepSeek(options = {}) {
17
+ const { apiKey, baseURL = DEFAULT_BASE_URL, model = DEFAULT_MODEL, headers: defaultHeaders = {}, fetch: fetchImpl = globalThis.fetch, retry } = options;
18
+ const resolvedApiKey = apiKey ?? readEnv('DEEPSEEK_API_KEY');
19
+ if (!resolvedApiKey) {
20
+ throw new Error('Qore DeepSeek adapter requires an API key. Pass apiKey or set DEEPSEEK_API_KEY.');
21
+ }
22
+ const transport = createSSEAdapter({
23
+ name: 'DeepSeek',
24
+ url: `${baseURL}/chat/completions`,
25
+ headers: {
26
+ Authorization: `Bearer ${resolvedApiKey}`,
27
+ 'Content-Type': 'application/json',
28
+ ...defaultHeaders
29
+ },
30
+ fetch: fetchImpl,
31
+ ...(retry ? { retry } : {}),
32
+ buildRequest(request, requestOptions = {}) {
33
+ const { signal, headers = {}, retry: _retry, ...overrides } = requestOptions;
34
+ const config = {
35
+ method: 'POST',
36
+ headers,
37
+ body: JSON.stringify({
38
+ model,
39
+ stream: true,
40
+ ...request,
41
+ ...overrides
42
+ })
43
+ };
44
+ if (signal) {
45
+ config.signal = signal;
46
+ }
47
+ return config;
48
+ },
49
+ parse: (data) => JSON.parse(data),
50
+ eventToText: (event) => {
51
+ const nextChoice = event.data?.choices?.[0];
52
+ return typeof nextChoice?.delta?.content === 'string'
53
+ ? nextChoice.delta.content
54
+ : undefined;
55
+ }
56
+ });
57
+ async function* streamEvents(request, requestOptions = {}) {
58
+ for await (const event of transport.stream(request, requestOptions)) {
59
+ yield event.data;
60
+ }
61
+ }
62
+ async function* streamText(input, requestOptions = {}) {
63
+ const request = input && typeof input === 'object' && 'messages' in input
64
+ ? input
65
+ : { messages: input };
66
+ for await (const chunk of transport.streamText(request, requestOptions)) {
67
+ yield chunk;
68
+ }
69
+ }
70
+ return {
71
+ chatCompletions: {
72
+ stream: streamEvents
73
+ },
74
+ streamText(input, requestOptions = {}) {
75
+ return streamText(input, requestOptions);
76
+ },
77
+ chat(input, requestOptions = {}) {
78
+ const { signal, headers, retry, ...rest } = requestOptions;
79
+ const request = { ...rest };
80
+ if (!('messages' in request)) {
81
+ request.messages = normalizeChatInput(input);
82
+ }
83
+ return streamText(request, {
84
+ ...(signal ? { signal } : {}),
85
+ ...(headers ? { headers } : {}),
86
+ ...(retry ? { retry } : {})
87
+ });
88
+ }
89
+ };
90
+ }
@@ -0,0 +1,2 @@
1
+ import type { LineAdapter, LineAdapterOptions } from './types.js';
2
+ export declare function createLineAdapter<TRequest = Record<string, unknown>, TChatInput = unknown, TData = unknown>(options?: LineAdapterOptions<TRequest, TChatInput, TData>): LineAdapter<TRequest, TChatInput, TData>;