@abloatai/ablo 0.3.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.
- package/CHANGELOG.md +208 -0
- package/LICENSE +201 -0
- package/NOTICE +12 -0
- package/README.md +230 -0
- package/dist/BaseSyncedStore.d.ts +709 -0
- package/dist/BaseSyncedStore.js +1843 -0
- package/dist/Database.d.ts +344 -0
- package/dist/Database.js +1259 -0
- package/dist/LazyReferenceCollection.d.ts +181 -0
- package/dist/LazyReferenceCollection.js +460 -0
- package/dist/Model.d.ts +339 -0
- package/dist/Model.js +715 -0
- package/dist/ModelRegistry.d.ts +200 -0
- package/dist/ModelRegistry.js +535 -0
- package/dist/NetworkMonitor.d.ts +27 -0
- package/dist/NetworkMonitor.js +73 -0
- package/dist/ObjectPool.d.ts +202 -0
- package/dist/ObjectPool.js +1106 -0
- package/dist/SyncClient.d.ts +489 -0
- package/dist/SyncClient.js +1555 -0
- package/dist/SyncEngineContext.d.ts +46 -0
- package/dist/SyncEngineContext.js +74 -0
- package/dist/adapters/alwaysOnline.d.ts +16 -0
- package/dist/adapters/alwaysOnline.js +19 -0
- package/dist/adapters/inMemoryStorage.d.ts +30 -0
- package/dist/adapters/inMemoryStorage.js +94 -0
- package/dist/agent/Agent.d.ts +358 -0
- package/dist/agent/Agent.js +500 -0
- package/dist/agent/index.d.ts +115 -0
- package/dist/agent/index.js +128 -0
- package/dist/agent/session.d.ts +90 -0
- package/dist/agent/session.js +156 -0
- package/dist/agent/types.d.ts +73 -0
- package/dist/agent/types.js +10 -0
- package/dist/ai-sdk/coordination-context.d.ts +51 -0
- package/dist/ai-sdk/coordination-context.js +107 -0
- package/dist/ai-sdk/index.d.ts +68 -0
- package/dist/ai-sdk/index.js +68 -0
- package/dist/ai-sdk/intent-broadcast.d.ts +77 -0
- package/dist/ai-sdk/intent-broadcast.js +72 -0
- package/dist/ai-sdk/wrap.d.ts +67 -0
- package/dist/ai-sdk/wrap.js +45 -0
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.js +9 -0
- package/dist/auth/index.d.ts +137 -0
- package/dist/auth/index.js +246 -0
- package/dist/client/Ablo.d.ts +835 -0
- package/dist/client/Ablo.js +1440 -0
- package/dist/client/ApiClient.d.ts +200 -0
- package/dist/client/ApiClient.js +659 -0
- package/dist/client/auth.d.ts +79 -0
- package/dist/client/auth.js +81 -0
- package/dist/client/createInternalComponents.d.ts +44 -0
- package/dist/client/createInternalComponents.js +88 -0
- package/dist/client/createModelProxy.d.ts +152 -0
- package/dist/client/createModelProxy.js +199 -0
- package/dist/client/identity.d.ts +63 -0
- package/dist/client/identity.js +156 -0
- package/dist/client/index.d.ts +36 -0
- package/dist/client/index.js +33 -0
- package/dist/client/persistence.d.ts +7 -0
- package/dist/client/persistence.js +11 -0
- package/dist/client/validateAbloOptions.d.ts +42 -0
- package/dist/client/validateAbloOptions.js +43 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.js +12 -0
- package/dist/context.d.ts +27 -0
- package/dist/context.js +58 -0
- package/dist/core/DatabaseManager.d.ts +108 -0
- package/dist/core/DatabaseManager.js +361 -0
- package/dist/core/QueryProcessor.d.ts +77 -0
- package/dist/core/QueryProcessor.js +262 -0
- package/dist/core/QueryView.d.ts +64 -0
- package/dist/core/QueryView.js +219 -0
- package/dist/core/StoreManager.d.ts +131 -0
- package/dist/core/StoreManager.js +334 -0
- package/dist/core/ViewRegistry.d.ts +20 -0
- package/dist/core/ViewRegistry.js +55 -0
- package/dist/core/index.d.ts +34 -0
- package/dist/core/index.js +59 -0
- package/dist/core/openIDBWithTimeout.d.ts +27 -0
- package/dist/core/openIDBWithTimeout.js +63 -0
- package/dist/core/query-utils.d.ts +37 -0
- package/dist/core/query-utils.js +60 -0
- package/dist/errors.d.ts +235 -0
- package/dist/errors.js +243 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +82 -0
- package/dist/interfaces/headless.d.ts +95 -0
- package/dist/interfaces/headless.js +41 -0
- package/dist/interfaces/index.d.ts +321 -0
- package/dist/interfaces/index.js +8 -0
- package/dist/mutators/RecordingTransaction.d.ts +36 -0
- package/dist/mutators/RecordingTransaction.js +216 -0
- package/dist/mutators/Transaction.d.ts +48 -0
- package/dist/mutators/Transaction.js +64 -0
- package/dist/mutators/UndoManager.d.ts +114 -0
- package/dist/mutators/UndoManager.js +143 -0
- package/dist/mutators/defineMutators.d.ts +55 -0
- package/dist/mutators/defineMutators.js +28 -0
- package/dist/policy/index.d.ts +19 -0
- package/dist/policy/index.js +18 -0
- package/dist/policy/types.d.ts +74 -0
- package/dist/policy/types.js +17 -0
- package/dist/principal.d.ts +44 -0
- package/dist/principal.js +49 -0
- package/dist/query/client.d.ts +43 -0
- package/dist/query/client.js +84 -0
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.js +5 -0
- package/dist/query/types.d.ts +143 -0
- package/dist/query/types.js +36 -0
- package/dist/react/AbloProvider.d.ts +205 -0
- package/dist/react/AbloProvider.js +398 -0
- package/dist/react/ClientSideSuspense.d.ts +36 -0
- package/dist/react/ClientSideSuspense.js +17 -0
- package/dist/react/DefaultFallback.d.ts +24 -0
- package/dist/react/DefaultFallback.js +43 -0
- package/dist/react/SyncGroupProvider.d.ts +19 -0
- package/dist/react/SyncGroupProvider.js +44 -0
- package/dist/react/context.d.ts +161 -0
- package/dist/react/context.js +35 -0
- package/dist/react/index.d.ts +64 -0
- package/dist/react/index.js +73 -0
- package/dist/react/internalContext.d.ts +35 -0
- package/dist/react/internalContext.js +3 -0
- package/dist/react/useAblo.d.ts +72 -0
- package/dist/react/useAblo.js +63 -0
- package/dist/react/useCurrentUserId.d.ts +21 -0
- package/dist/react/useCurrentUserId.js +33 -0
- package/dist/react/useErrorListener.d.ts +20 -0
- package/dist/react/useErrorListener.js +39 -0
- package/dist/react/useIntent.d.ts +29 -0
- package/dist/react/useIntent.js +42 -0
- package/dist/react/useMutate.d.ts +83 -0
- package/dist/react/useMutate.js +122 -0
- package/dist/react/useMutationFailureListener.d.ts +26 -0
- package/dist/react/useMutationFailureListener.js +38 -0
- package/dist/react/useMutators.d.ts +56 -0
- package/dist/react/useMutators.js +66 -0
- package/dist/react/usePresence.d.ts +32 -0
- package/dist/react/usePresence.js +41 -0
- package/dist/react/useQuery.d.ts +123 -0
- package/dist/react/useQuery.js +145 -0
- package/dist/react/useReactive.d.ts +35 -0
- package/dist/react/useReactive.js +111 -0
- package/dist/react/useReader.d.ts +69 -0
- package/dist/react/useReader.js +73 -0
- package/dist/react/useSyncStatus.d.ts +61 -0
- package/dist/react/useSyncStatus.js +76 -0
- package/dist/react/useUndoScope.d.ts +36 -0
- package/dist/react/useUndoScope.js +73 -0
- package/dist/realtime/index.d.ts +10 -0
- package/dist/realtime/index.js +9 -0
- package/dist/schema/field.d.ts +134 -0
- package/dist/schema/field.js +264 -0
- package/dist/schema/index.d.ts +29 -0
- package/dist/schema/index.js +38 -0
- package/dist/schema/model.d.ts +326 -0
- package/dist/schema/model.js +89 -0
- package/dist/schema/queries.d.ts +203 -0
- package/dist/schema/queries.js +145 -0
- package/dist/schema/relation.d.ts +172 -0
- package/dist/schema/relation.js +104 -0
- package/dist/schema/schema.d.ts +259 -0
- package/dist/schema/schema.js +188 -0
- package/dist/schema/sugar.d.ts +129 -0
- package/dist/schema/sugar.js +94 -0
- package/dist/source/index.d.ts +423 -0
- package/dist/source/index.js +320 -0
- package/dist/source/pushQueue.d.ts +112 -0
- package/dist/source/pushQueue.js +249 -0
- package/dist/stores/ObjectStore.d.ts +103 -0
- package/dist/stores/ObjectStore.js +371 -0
- package/dist/stores/ObjectStoreContract.d.ts +39 -0
- package/dist/stores/ObjectStoreContract.js +1 -0
- package/dist/stores/SyncActionStore.d.ts +101 -0
- package/dist/stores/SyncActionStore.js +481 -0
- package/dist/sync/BootstrapHelper.d.ts +127 -0
- package/dist/sync/BootstrapHelper.js +434 -0
- package/dist/sync/ConnectionManager.d.ts +136 -0
- package/dist/sync/ConnectionManager.js +465 -0
- package/dist/sync/HydrationCoordinator.d.ts +137 -0
- package/dist/sync/HydrationCoordinator.js +468 -0
- package/dist/sync/NetworkProbe.d.ts +43 -0
- package/dist/sync/NetworkProbe.js +113 -0
- package/dist/sync/OfflineFlush.d.ts +9 -0
- package/dist/sync/OfflineFlush.js +22 -0
- package/dist/sync/OfflineTransactionStore.d.ts +37 -0
- package/dist/sync/OfflineTransactionStore.js +263 -0
- package/dist/sync/SyncWebSocket.d.ts +663 -0
- package/dist/sync/SyncWebSocket.js +1336 -0
- package/dist/sync/createIntentStream.d.ts +33 -0
- package/dist/sync/createIntentStream.js +243 -0
- package/dist/sync/createPresenceStream.d.ts +46 -0
- package/dist/sync/createPresenceStream.js +192 -0
- package/dist/sync/createSnapshot.d.ts +33 -0
- package/dist/sync/createSnapshot.js +124 -0
- package/dist/sync/participants.d.ts +114 -0
- package/dist/sync/participants.js +336 -0
- package/dist/sync/schemas.d.ts +79 -0
- package/dist/sync/schemas.js +78 -0
- package/dist/testing/fixtures/bootstrap.d.ts +45 -0
- package/dist/testing/fixtures/bootstrap.js +53 -0
- package/dist/testing/fixtures/deltas.d.ts +86 -0
- package/dist/testing/fixtures/deltas.js +139 -0
- package/dist/testing/fixtures/models.d.ts +82 -0
- package/dist/testing/fixtures/models.js +270 -0
- package/dist/testing/helpers/react-wrapper.d.ts +66 -0
- package/dist/testing/helpers/react-wrapper.js +64 -0
- package/dist/testing/helpers/sync-engine-harness.d.ts +55 -0
- package/dist/testing/helpers/sync-engine-harness.js +70 -0
- package/dist/testing/helpers/wait.d.ts +25 -0
- package/dist/testing/helpers/wait.js +44 -0
- package/dist/testing/index.d.ts +21 -0
- package/dist/testing/index.js +32 -0
- package/dist/testing/mocks/MockMutationExecutor.d.ts +65 -0
- package/dist/testing/mocks/MockMutationExecutor.js +139 -0
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +20 -0
- package/dist/testing/mocks/MockNetworkMonitor.js +46 -0
- package/dist/testing/mocks/MockSyncContext.d.ts +64 -0
- package/dist/testing/mocks/MockSyncContext.js +100 -0
- package/dist/testing/mocks/MockSyncStore.d.ts +88 -0
- package/dist/testing/mocks/MockSyncStore.js +171 -0
- package/dist/testing/mocks/MockWebSocket.d.ts +66 -0
- package/dist/testing/mocks/MockWebSocket.js +117 -0
- package/dist/transactions/OptimisticEchoTracker.d.ts +82 -0
- package/dist/transactions/OptimisticEchoTracker.js +104 -0
- package/dist/transactions/TransactionQueue.d.ts +499 -0
- package/dist/transactions/TransactionQueue.js +1895 -0
- package/dist/transactions/index.d.ts +16 -0
- package/dist/transactions/index.js +7 -0
- package/dist/transactions/mutation-error-handler.d.ts +5 -0
- package/dist/transactions/mutation-error-handler.js +39 -0
- package/dist/types/global.d.ts +107 -0
- package/dist/types/global.js +38 -0
- package/dist/types/index.d.ts +241 -0
- package/dist/types/index.js +70 -0
- package/dist/types/streams.d.ts +495 -0
- package/dist/types/streams.js +11 -0
- package/dist/utils/asyncIterator.d.ts +41 -0
- package/dist/utils/asyncIterator.js +142 -0
- package/dist/utils/duration.d.ts +28 -0
- package/dist/utils/duration.js +47 -0
- package/dist/utils/mobx-setup.d.ts +42 -0
- package/dist/utils/mobx-setup.js +381 -0
- package/docs/api-keys.md +24 -0
- package/docs/api.md +230 -0
- package/docs/audit.md +81 -0
- package/docs/capabilities.md +163 -0
- package/docs/client-behavior.md +202 -0
- package/docs/data-sources.md +214 -0
- package/docs/examples/agent-human.md +84 -0
- package/docs/examples/ai-sdk-tool.md +92 -0
- package/docs/examples/existing-python-backend.md +249 -0
- package/docs/examples/nextjs.md +88 -0
- package/docs/examples/server-agent.md +86 -0
- package/docs/guarantees.md +148 -0
- package/docs/index.md +97 -0
- package/docs/integration-guide.md +493 -0
- package/docs/interaction-model.md +140 -0
- package/docs/mcp/claude-code.md +43 -0
- package/docs/mcp/cursor.md +53 -0
- package/docs/mcp/windsurf.md +46 -0
- package/docs/mcp.md +59 -0
- package/docs/quickstart.md +152 -0
- package/docs/react.md +115 -0
- package/docs/roadmap.md +45 -0
- package/examples/README.md +54 -0
- package/examples/data-source/README.md +102 -0
- package/examples/data-source/ablo-driver.ts +89 -0
- package/examples/data-source/customer-server.ts +208 -0
- package/examples/data-source/run.ts +101 -0
- package/examples/data-source/schema.ts +25 -0
- package/examples/quickstart.ts +54 -0
- package/examples/tsconfig.json +16 -0
- package/llms.txt +143 -0
- package/package.json +147 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ablo/sync-engine-internal/ai-sdk` — multiplayer-with-AI as language model
|
|
3
|
+
* middleware.
|
|
4
|
+
*
|
|
5
|
+
* Two cross-cutting middlewares for any AI SDK consumer using
|
|
6
|
+
* `streamText` / `generateText`:
|
|
7
|
+
*
|
|
8
|
+
* - `intentBroadcastMiddleware` — agent declares what it's about
|
|
9
|
+
* to mutate via `intent_begin`, abandons the claim at stream end.
|
|
10
|
+
* Peers see the broadcast in their presence stream's
|
|
11
|
+
* `activeIntents` field and can defer / yield / surface
|
|
12
|
+
* "agent is editing this entity right now."
|
|
13
|
+
*
|
|
14
|
+
* - `coordinationContextMiddleware` — reads peer intents from local
|
|
15
|
+
* presence cache before the LLM call, injects a
|
|
16
|
+
* `<multiplayer_context>` system note when peers are editing
|
|
17
|
+
* the same entity. The AI gets coordination awareness without
|
|
18
|
+
* extra round-trips.
|
|
19
|
+
*
|
|
20
|
+
* Compose them with the AI SDK's `wrapLanguageModel`:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { wrapLanguageModel, streamText } from 'ai';
|
|
24
|
+
* import {
|
|
25
|
+
* intentBroadcastMiddleware,
|
|
26
|
+
* coordinationContextMiddleware,
|
|
27
|
+
* } from '@ablo/sync-engine-internal/ai-sdk';
|
|
28
|
+
*
|
|
29
|
+
* const target = { entityType: 'SlideDeck', entityId: 'deck-abc' };
|
|
30
|
+
*
|
|
31
|
+
* const wrappedModel = wrapLanguageModel({
|
|
32
|
+
* model: anthropic('claude-opus-4-7'),
|
|
33
|
+
* middleware: [
|
|
34
|
+
* coordinationContextMiddleware({ agent, target }),
|
|
35
|
+
* intentBroadcastMiddleware({ agent, target }),
|
|
36
|
+
* ],
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* // Consumer keeps full control over messages, tools, system prompt:
|
|
40
|
+
* const result = streamText({
|
|
41
|
+
* model: wrappedModel,
|
|
42
|
+
* messages: [...],
|
|
43
|
+
* tools: { ... },
|
|
44
|
+
* system: '...',
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* Or use the convenience composition for the common case:
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* import { wrapWithMultiplayer } from '@ablo/sync-engine-internal/ai-sdk';
|
|
52
|
+
*
|
|
53
|
+
* const wrappedModel = wrapWithMultiplayer({
|
|
54
|
+
* model: anthropic('claude-opus-4-7'),
|
|
55
|
+
* agent,
|
|
56
|
+
* target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* Order matters: `coordinationContextMiddleware`'s `transformParams`
|
|
61
|
+
* runs at param-transform time (before the model call), reading peer
|
|
62
|
+
* intents *before* this agent's broadcast lands in its own cache.
|
|
63
|
+
* `intentBroadcastMiddleware`'s `wrapStream` runs around the actual
|
|
64
|
+
* call. Self-claim doesn't pollute the peer-intent read.
|
|
65
|
+
*/
|
|
66
|
+
export { intentBroadcastMiddleware, type IntentTarget, type IntentBroadcastMiddlewareOptions, } from './intent-broadcast.js';
|
|
67
|
+
export { coordinationContextMiddleware, type CoordinationContextMiddlewareOptions, } from './coordination-context.js';
|
|
68
|
+
export { wrapWithMultiplayer, type WrapWithMultiplayerOptions } from './wrap.js';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ablo/sync-engine-internal/ai-sdk` — multiplayer-with-AI as language model
|
|
3
|
+
* middleware.
|
|
4
|
+
*
|
|
5
|
+
* Two cross-cutting middlewares for any AI SDK consumer using
|
|
6
|
+
* `streamText` / `generateText`:
|
|
7
|
+
*
|
|
8
|
+
* - `intentBroadcastMiddleware` — agent declares what it's about
|
|
9
|
+
* to mutate via `intent_begin`, abandons the claim at stream end.
|
|
10
|
+
* Peers see the broadcast in their presence stream's
|
|
11
|
+
* `activeIntents` field and can defer / yield / surface
|
|
12
|
+
* "agent is editing this entity right now."
|
|
13
|
+
*
|
|
14
|
+
* - `coordinationContextMiddleware` — reads peer intents from local
|
|
15
|
+
* presence cache before the LLM call, injects a
|
|
16
|
+
* `<multiplayer_context>` system note when peers are editing
|
|
17
|
+
* the same entity. The AI gets coordination awareness without
|
|
18
|
+
* extra round-trips.
|
|
19
|
+
*
|
|
20
|
+
* Compose them with the AI SDK's `wrapLanguageModel`:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { wrapLanguageModel, streamText } from 'ai';
|
|
24
|
+
* import {
|
|
25
|
+
* intentBroadcastMiddleware,
|
|
26
|
+
* coordinationContextMiddleware,
|
|
27
|
+
* } from '@ablo/sync-engine-internal/ai-sdk';
|
|
28
|
+
*
|
|
29
|
+
* const target = { entityType: 'SlideDeck', entityId: 'deck-abc' };
|
|
30
|
+
*
|
|
31
|
+
* const wrappedModel = wrapLanguageModel({
|
|
32
|
+
* model: anthropic('claude-opus-4-7'),
|
|
33
|
+
* middleware: [
|
|
34
|
+
* coordinationContextMiddleware({ agent, target }),
|
|
35
|
+
* intentBroadcastMiddleware({ agent, target }),
|
|
36
|
+
* ],
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* // Consumer keeps full control over messages, tools, system prompt:
|
|
40
|
+
* const result = streamText({
|
|
41
|
+
* model: wrappedModel,
|
|
42
|
+
* messages: [...],
|
|
43
|
+
* tools: { ... },
|
|
44
|
+
* system: '...',
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* Or use the convenience composition for the common case:
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* import { wrapWithMultiplayer } from '@ablo/sync-engine-internal/ai-sdk';
|
|
52
|
+
*
|
|
53
|
+
* const wrappedModel = wrapWithMultiplayer({
|
|
54
|
+
* model: anthropic('claude-opus-4-7'),
|
|
55
|
+
* agent,
|
|
56
|
+
* target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* Order matters: `coordinationContextMiddleware`'s `transformParams`
|
|
61
|
+
* runs at param-transform time (before the model call), reading peer
|
|
62
|
+
* intents *before* this agent's broadcast lands in its own cache.
|
|
63
|
+
* `intentBroadcastMiddleware`'s `wrapStream` runs around the actual
|
|
64
|
+
* call. Self-claim doesn't pollute the peer-intent read.
|
|
65
|
+
*/
|
|
66
|
+
export { intentBroadcastMiddleware, } from './intent-broadcast.js';
|
|
67
|
+
export { coordinationContextMiddleware, } from './coordination-context.js';
|
|
68
|
+
export { wrapWithMultiplayer } from './wrap.js';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intent broadcast middleware — wraps a language model so the agent
|
|
3
|
+
* declares "I'm about to edit entity X" over the sync engine's
|
|
4
|
+
* intent primitive at stream start, and abandons the claim at
|
|
5
|
+
* stream end.
|
|
6
|
+
*
|
|
7
|
+
* Cross-cutting by design — composes via the AI SDK's
|
|
8
|
+
* `wrapLanguageModel`. Same middleware works for every chat
|
|
9
|
+
* surface, and for non-chat agent loops that share the AI SDK's
|
|
10
|
+
* middleware interface (workers, MCP tools, autonomous loops).
|
|
11
|
+
*
|
|
12
|
+
* Open-source-clean: depends only on `@ai-sdk/provider` types and
|
|
13
|
+
* the package's own `SyncAgent`. No app-specific assumptions —
|
|
14
|
+
* Ablo's web app uses this, but so can any consumer of `@ablo/sync-engine`.
|
|
15
|
+
*
|
|
16
|
+
* Cost: one WS frame at stream start (`intent_begin`), one at end
|
|
17
|
+
* (`intent_abandon`). No DB I/O, no extra LLM tokens.
|
|
18
|
+
*/
|
|
19
|
+
import type { LanguageModelV3Middleware } from '@ai-sdk/provider';
|
|
20
|
+
import type { Ablo } from '../client/Ablo.js';
|
|
21
|
+
import type { SchemaRecord } from '../schema/schema.js';
|
|
22
|
+
/**
|
|
23
|
+
* Target entity for the intent broadcast.
|
|
24
|
+
*
|
|
25
|
+
* `entityType` is a free-form string — convention is the schema's
|
|
26
|
+
* typename (e.g. `'SlideDeck'`, `'Task'`, `'Matter'`) so peers can
|
|
27
|
+
* filter consistently. The wire format treats it opaquely.
|
|
28
|
+
*/
|
|
29
|
+
export interface IntentTarget {
|
|
30
|
+
readonly entityType: string;
|
|
31
|
+
readonly entityId: string;
|
|
32
|
+
/** Optional path for file/document-like targets. */
|
|
33
|
+
readonly path?: string;
|
|
34
|
+
/** Optional line/column range for partial-entity coordination. */
|
|
35
|
+
readonly range?: {
|
|
36
|
+
readonly startLine: number;
|
|
37
|
+
readonly endLine: number;
|
|
38
|
+
readonly startColumn?: number;
|
|
39
|
+
readonly endColumn?: number;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Optional sub-field within the entity. Useful when the agent
|
|
43
|
+
* knows it's only editing a specific field — peers can filter
|
|
44
|
+
* on the field too.
|
|
45
|
+
*/
|
|
46
|
+
readonly field?: string;
|
|
47
|
+
/** App-defined structured metadata. Opaque to the core SDK. */
|
|
48
|
+
readonly meta?: Record<string, unknown>;
|
|
49
|
+
/**
|
|
50
|
+
* Hint for the server-side TTL on the claim. Caps at 10 minutes
|
|
51
|
+
* server-side; default 60s — typical chat turn.
|
|
52
|
+
*/
|
|
53
|
+
readonly estimatedMs?: number;
|
|
54
|
+
}
|
|
55
|
+
export interface IntentBroadcastMiddlewareOptions<R extends SchemaRecord = SchemaRecord> {
|
|
56
|
+
/** Connected Ablo. Null disables the middleware (no-op). */
|
|
57
|
+
readonly agent: Ablo<R> | null;
|
|
58
|
+
/** Target entity. Null skips the broadcast (purely conversational). */
|
|
59
|
+
readonly target: IntentTarget | null;
|
|
60
|
+
/**
|
|
61
|
+
* Action verb describing what the agent is doing. Convention:
|
|
62
|
+
* `'edit'`, `'read'`, `'review'`, `'generate'`. Default `'edit'`.
|
|
63
|
+
*/
|
|
64
|
+
readonly action?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Build the middleware. When `agent` or `target` is null, returns a
|
|
68
|
+
* pass-through — keeps call sites unconditional regardless of
|
|
69
|
+
* whether the surface has an entity in scope.
|
|
70
|
+
*
|
|
71
|
+
* Generic over the schema record so callers passing
|
|
72
|
+
* `Ablo<typeof schema>` don't have to widen — `Ablo<S>` and
|
|
73
|
+
* `Ablo<SchemaRecord>` are structurally non-compatible because the
|
|
74
|
+
* widened version collapses model proxies to an index signature
|
|
75
|
+
* that clashes with the named methods (`ready`, `dispose`, etc.).
|
|
76
|
+
*/
|
|
77
|
+
export declare function intentBroadcastMiddleware<R extends SchemaRecord = SchemaRecord>(options: IntentBroadcastMiddlewareOptions<R>): LanguageModelV3Middleware;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intent broadcast middleware — wraps a language model so the agent
|
|
3
|
+
* declares "I'm about to edit entity X" over the sync engine's
|
|
4
|
+
* intent primitive at stream start, and abandons the claim at
|
|
5
|
+
* stream end.
|
|
6
|
+
*
|
|
7
|
+
* Cross-cutting by design — composes via the AI SDK's
|
|
8
|
+
* `wrapLanguageModel`. Same middleware works for every chat
|
|
9
|
+
* surface, and for non-chat agent loops that share the AI SDK's
|
|
10
|
+
* middleware interface (workers, MCP tools, autonomous loops).
|
|
11
|
+
*
|
|
12
|
+
* Open-source-clean: depends only on `@ai-sdk/provider` types and
|
|
13
|
+
* the package's own `SyncAgent`. No app-specific assumptions —
|
|
14
|
+
* Ablo's web app uses this, but so can any consumer of `@ablo/sync-engine`.
|
|
15
|
+
*
|
|
16
|
+
* Cost: one WS frame at stream start (`intent_begin`), one at end
|
|
17
|
+
* (`intent_abandon`). No DB I/O, no extra LLM tokens.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Build the middleware. When `agent` or `target` is null, returns a
|
|
21
|
+
* pass-through — keeps call sites unconditional regardless of
|
|
22
|
+
* whether the surface has an entity in scope.
|
|
23
|
+
*
|
|
24
|
+
* Generic over the schema record so callers passing
|
|
25
|
+
* `Ablo<typeof schema>` don't have to widen — `Ablo<S>` and
|
|
26
|
+
* `Ablo<SchemaRecord>` are structurally non-compatible because the
|
|
27
|
+
* widened version collapses model proxies to an index signature
|
|
28
|
+
* that clashes with the named methods (`ready`, `dispose`, etc.).
|
|
29
|
+
*/
|
|
30
|
+
export function intentBroadcastMiddleware(options) {
|
|
31
|
+
const { agent, target } = options;
|
|
32
|
+
const action = options.action ?? 'edit';
|
|
33
|
+
const openClaim = () => agent && target
|
|
34
|
+
? agent.intents.claim({
|
|
35
|
+
type: target.entityType,
|
|
36
|
+
id: target.entityId,
|
|
37
|
+
path: target.path,
|
|
38
|
+
range: target.range,
|
|
39
|
+
field: target.field,
|
|
40
|
+
meta: target.meta,
|
|
41
|
+
}, { reason: action, ttl: target.estimatedMs ?? 60_000 })
|
|
42
|
+
: null;
|
|
43
|
+
return {
|
|
44
|
+
specificationVersion: 'v3',
|
|
45
|
+
// The AI SDK's middleware contract passes a no-arg `doStream` /
|
|
46
|
+
// `doGenerate` thunk — params have already been transformed by
|
|
47
|
+
// any earlier `transformParams` middleware in the chain. We
|
|
48
|
+
// open the claim, call the inner, abandon when the inner
|
|
49
|
+
// resolves (or rejects).
|
|
50
|
+
async wrapStream({ doStream }) {
|
|
51
|
+
const handle = openClaim();
|
|
52
|
+
try {
|
|
53
|
+
return await doStream();
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
// Always abandon — even on error. The server's TTL would
|
|
57
|
+
// eventually clean up regardless, but explicit release means
|
|
58
|
+
// peers see the claim drop the moment generation completes.
|
|
59
|
+
handle?.revoke();
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
async wrapGenerate({ doGenerate }) {
|
|
63
|
+
const handle = openClaim();
|
|
64
|
+
try {
|
|
65
|
+
return await doGenerate();
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
handle?.revoke();
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convenience composition for the common case — wraps a language
|
|
3
|
+
* model with both multiplayer middlewares (intent broadcast +
|
|
4
|
+
* coordination context) in the right order.
|
|
5
|
+
*
|
|
6
|
+
* Consumers who want full control over middleware composition (add
|
|
7
|
+
* caching / observability / their own custom middleware) should use
|
|
8
|
+
* the factories directly: `intentBroadcastMiddleware`,
|
|
9
|
+
* `coordinationContextMiddleware`. This helper is the one-liner for
|
|
10
|
+
* the 90% case.
|
|
11
|
+
*
|
|
12
|
+
* Stays explicit about its scope — wraps the MODEL only. Consumer
|
|
13
|
+
* keeps full control over their `streamText` / `generateText` call
|
|
14
|
+
* (messages, tools, system prompt, provider options, onFinish, etc.).
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* const wrapped = wrapWithMultiplayer({
|
|
18
|
+
* model: anthropic('claude-opus-4-7'),
|
|
19
|
+
* agent,
|
|
20
|
+
* target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* const result = streamText({
|
|
24
|
+
* model: wrapped,
|
|
25
|
+
* messages: myMessages,
|
|
26
|
+
* tools: myTools,
|
|
27
|
+
* system: mySystem,
|
|
28
|
+
* // ... anything else the consumer's app needs
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
import { wrapLanguageModel } from 'ai';
|
|
33
|
+
import type { LanguageModelV3, LanguageModelV3Middleware } from '@ai-sdk/provider';
|
|
34
|
+
import type { Ablo } from '../client/Ablo.js';
|
|
35
|
+
import type { SchemaRecord } from '../schema/schema.js';
|
|
36
|
+
import { type IntentTarget } from './intent-broadcast.js';
|
|
37
|
+
export interface WrapWithMultiplayerOptions {
|
|
38
|
+
/** The base language model to wrap. Consumer brings their own. */
|
|
39
|
+
readonly model: LanguageModelV3;
|
|
40
|
+
/** Connected SyncAgent. Null = pass-through wrap (no broadcast, no read). */
|
|
41
|
+
readonly agent: Ablo<SchemaRecord> | null;
|
|
42
|
+
/** Target entity. Null = pass-through wrap. */
|
|
43
|
+
readonly target: IntentTarget | null;
|
|
44
|
+
/**
|
|
45
|
+
* Optional action verb for the broadcast. Default `'edit'`.
|
|
46
|
+
* Convention: `'edit'`, `'read'`, `'review'`, `'generate'`.
|
|
47
|
+
*/
|
|
48
|
+
readonly action?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Optional intentIds to exclude from the coordination-context
|
|
51
|
+
* read — typically the caller's own claim if they're composing
|
|
52
|
+
* multiple wrappings. Most consumers leave this empty.
|
|
53
|
+
*/
|
|
54
|
+
readonly excludeIntentIds?: readonly string[];
|
|
55
|
+
/**
|
|
56
|
+
* Optional extra middleware to compose. Runs in the order given,
|
|
57
|
+
* INSIDE the multiplayer middlewares (so the multiplayer wrap is
|
|
58
|
+
* the outer-most). Useful for caching, observability, custom
|
|
59
|
+
* transforms that should not affect the multiplayer signal.
|
|
60
|
+
*
|
|
61
|
+
* For full control over ordering, skip this helper and call
|
|
62
|
+
* `wrapLanguageModel` directly with all middleware in the order
|
|
63
|
+
* you want.
|
|
64
|
+
*/
|
|
65
|
+
readonly extraMiddleware?: readonly LanguageModelV3Middleware[];
|
|
66
|
+
}
|
|
67
|
+
export declare function wrapWithMultiplayer(options: WrapWithMultiplayerOptions): ReturnType<typeof wrapLanguageModel>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convenience composition for the common case — wraps a language
|
|
3
|
+
* model with both multiplayer middlewares (intent broadcast +
|
|
4
|
+
* coordination context) in the right order.
|
|
5
|
+
*
|
|
6
|
+
* Consumers who want full control over middleware composition (add
|
|
7
|
+
* caching / observability / their own custom middleware) should use
|
|
8
|
+
* the factories directly: `intentBroadcastMiddleware`,
|
|
9
|
+
* `coordinationContextMiddleware`. This helper is the one-liner for
|
|
10
|
+
* the 90% case.
|
|
11
|
+
*
|
|
12
|
+
* Stays explicit about its scope — wraps the MODEL only. Consumer
|
|
13
|
+
* keeps full control over their `streamText` / `generateText` call
|
|
14
|
+
* (messages, tools, system prompt, provider options, onFinish, etc.).
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* const wrapped = wrapWithMultiplayer({
|
|
18
|
+
* model: anthropic('claude-opus-4-7'),
|
|
19
|
+
* agent,
|
|
20
|
+
* target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* const result = streamText({
|
|
24
|
+
* model: wrapped,
|
|
25
|
+
* messages: myMessages,
|
|
26
|
+
* tools: myTools,
|
|
27
|
+
* system: mySystem,
|
|
28
|
+
* // ... anything else the consumer's app needs
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
import { wrapLanguageModel } from 'ai';
|
|
33
|
+
import { intentBroadcastMiddleware, } from './intent-broadcast.js';
|
|
34
|
+
import { coordinationContextMiddleware } from './coordination-context.js';
|
|
35
|
+
export function wrapWithMultiplayer(options) {
|
|
36
|
+
const { model, agent, target, action, excludeIntentIds, extraMiddleware } = options;
|
|
37
|
+
return wrapLanguageModel({
|
|
38
|
+
model,
|
|
39
|
+
middleware: [
|
|
40
|
+
coordinationContextMiddleware({ agent, target, excludeIntentIds }),
|
|
41
|
+
intentBroadcastMiddleware({ agent, target, action }),
|
|
42
|
+
...(extraMiddleware ?? []),
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal compatibility entrypoint for the stateless hosted protocol client.
|
|
3
|
+
*
|
|
4
|
+
* Use this build for serverless functions, scripts, and backends that want
|
|
5
|
+
* Resource / Intent / Commit over HTTP without the realtime sync runtime.
|
|
6
|
+
*/
|
|
7
|
+
export { createProtocolClient, createProtocolClient as Ablo, type AbloApi, type AbloApiClientOptions, type AbloApiIntents, type Agent, type AgentIntentInput, type AgentIntentOptions, type AgentOptions, type AgentResourceClient, type AgentResourceReadOptions, type AgentResourceMutationOptions, type AgentRunContext, type AgentRunDone, type AgentRunFailed, type AgentRunCancelled, type AgentRunOptions, type AgentRunResult, type AgentRunStatus, type Capability, type CapabilityCreateOptions, type CapabilityParticipantKind, type CapabilityRecord, type CapabilityResource, type CapabilityRevocation, type CapabilityScope, type Task, type TaskCloseOptions, type TaskCloseResult, type TaskCreateOptions, type TaskResource, } from '../client/ApiClient.js';
|
|
8
|
+
export type { CommitCreateOptions, CommitOperationInput, CommitReceipt, CommitWait, IntentCreateOptions, IntentHandle, IntentWaitOptions, BusyOptions, BusyPolicy, ResourceClient, ResourceIntent, ResourceMutationOptions, ResourceReadOptions, ResourceRead, ResourceTarget, } from '../client/Ablo.js';
|
|
9
|
+
import { createProtocolClient } from '../client/ApiClient.js';
|
|
10
|
+
export default createProtocolClient;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal compatibility entrypoint for the stateless hosted protocol client.
|
|
3
|
+
*
|
|
4
|
+
* Use this build for serverless functions, scripts, and backends that want
|
|
5
|
+
* Resource / Intent / Commit over HTTP without the realtime sync runtime.
|
|
6
|
+
*/
|
|
7
|
+
export { createProtocolClient, createProtocolClient as Ablo, } from '../client/ApiClient.js';
|
|
8
|
+
import { createProtocolClient } from '../client/ApiClient.js';
|
|
9
|
+
export default createProtocolClient;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal apiKey → capability exchange.
|
|
3
|
+
*
|
|
4
|
+
* Called by the `Ablo({...})` factory's `ready()` flow when the
|
|
5
|
+
* consumer passed `apiKey` without an explicit `capabilityToken` /
|
|
6
|
+
* `organizationId` / `user.id`. SDK calls `/auth/capability` once,
|
|
7
|
+
* server returns the scope + userMeta blobs (Phases 1A + 1B),
|
|
8
|
+
* SDK populates internal state from the response.
|
|
9
|
+
*
|
|
10
|
+
* Consumer never sees this happen. Same shape as Stripe / Anthropic
|
|
11
|
+
* SDKs hide their internal auth-handshake — the apiKey is the only
|
|
12
|
+
* credential the consumer touches.
|
|
13
|
+
*/
|
|
14
|
+
/** Server response shape — matches Phase 1A + 1B wire output. */
|
|
15
|
+
export interface CapabilityExchangeResponse {
|
|
16
|
+
readonly capabilityId: string;
|
|
17
|
+
readonly token: string;
|
|
18
|
+
readonly expiresAt: string;
|
|
19
|
+
readonly organizationId: string;
|
|
20
|
+
readonly scope: {
|
|
21
|
+
readonly organizationId: string;
|
|
22
|
+
readonly syncGroups: readonly string[];
|
|
23
|
+
readonly operations: readonly string[];
|
|
24
|
+
readonly participantKind: 'user' | 'agent' | 'system';
|
|
25
|
+
readonly participantId: string;
|
|
26
|
+
};
|
|
27
|
+
readonly userMeta: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export interface ExchangeApiKeyRequest {
|
|
30
|
+
readonly apiKey: string;
|
|
31
|
+
readonly baseUrl: string;
|
|
32
|
+
readonly participantKind: 'user' | 'agent' | 'system';
|
|
33
|
+
readonly participantId?: string;
|
|
34
|
+
readonly syncGroups?: readonly string[];
|
|
35
|
+
readonly operations?: readonly string[];
|
|
36
|
+
readonly wideScope?: boolean;
|
|
37
|
+
readonly ttlSeconds: number;
|
|
38
|
+
readonly label?: string;
|
|
39
|
+
readonly userMeta?: Record<string, unknown>;
|
|
40
|
+
readonly fetch?: typeof fetch;
|
|
41
|
+
readonly timeoutMs?: number;
|
|
42
|
+
}
|
|
43
|
+
export declare function exchangeApiKey(options: ExchangeApiKeyRequest): Promise<CapabilityExchangeResponse>;
|
|
44
|
+
export interface IdentityResolveResponse {
|
|
45
|
+
readonly participantKind: 'user' | 'agent' | 'system';
|
|
46
|
+
readonly participantId: string;
|
|
47
|
+
readonly accountScope: string;
|
|
48
|
+
readonly syncGroups: readonly string[];
|
|
49
|
+
readonly userMeta: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
export interface ResolveIdentityRequest {
|
|
52
|
+
readonly baseUrl: string;
|
|
53
|
+
readonly authToken?: string;
|
|
54
|
+
readonly fetch?: typeof fetch;
|
|
55
|
+
readonly timeoutMs?: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the caller's Ablo identity from the authenticated request
|
|
59
|
+
* context. Used by browser/session/capability flows where the SDK should
|
|
60
|
+
* not require a public `userId` prop just to open local storage.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveIdentity(options: ResolveIdentityRequest): Promise<IdentityResolveResponse>;
|
|
63
|
+
/**
|
|
64
|
+
* Capability-token refresh scheduler.
|
|
65
|
+
*
|
|
66
|
+
* Long-lived `@ablo/sync-engine` clients hold a server-issued capability
|
|
67
|
+
* token whose TTL (1h default) is shorter than typical browser sessions.
|
|
68
|
+
* Without proactive refresh, the WebSocket would either be force-closed
|
|
69
|
+
* by the server at expiry (code 1008) or fail its next reconnect with
|
|
70
|
+
* 401. Either way the user sees a mid-session disconnect.
|
|
71
|
+
*
|
|
72
|
+
* This scheduler keeps the token fresh transparently. Three triggers,
|
|
73
|
+
* one refresh path:
|
|
74
|
+
*
|
|
75
|
+
* 1. Proactive — `setTimeout` for `(expiresAtMs - bufferMs - now)`.
|
|
76
|
+
* 2. Visibility — on `document.visibilitychange→visible`, if the
|
|
77
|
+
* token is within the buffer window, refresh now.
|
|
78
|
+
* Defends against dormant-tab `setTimeout` throttling.
|
|
79
|
+
* 3. Reactive — caller invokes `.refreshNow()` on observed auth
|
|
80
|
+
* failure (WS close 1008/4001 etc).
|
|
81
|
+
*
|
|
82
|
+
* All three resolve through the same `inFlight` promise so concurrent
|
|
83
|
+
* triggers don't double-mint. On any successful refresh the new
|
|
84
|
+
* `expiresAtMs` is captured and trigger 1 is rescheduled.
|
|
85
|
+
*
|
|
86
|
+
* Buffer policy: `max(60s, ttl/10)` — for a 1h TTL that's 360s, which
|
|
87
|
+
* matches the AWS SDK / MSAL.js de-facto 5-minute standard while
|
|
88
|
+
* scaling sensibly for shorter TTLs.
|
|
89
|
+
*/
|
|
90
|
+
export interface RefreshSchedulerOptions {
|
|
91
|
+
/** Initial absolute expiry, ms since epoch (server-supplied). */
|
|
92
|
+
readonly initialExpiresAtMs: number;
|
|
93
|
+
/**
|
|
94
|
+
* Performs the actual exchange. Returns the new expiry. Errors
|
|
95
|
+
* propagate to `onError`; the scheduler stays alive and retries on
|
|
96
|
+
* next trigger (no exponential backoff in v1 — most failures here are
|
|
97
|
+
* the user's apiKey being revoked, in which case retrying is futile).
|
|
98
|
+
*/
|
|
99
|
+
readonly refresh: () => Promise<{
|
|
100
|
+
expiresAtMs: number;
|
|
101
|
+
}>;
|
|
102
|
+
/** Called on every successful refresh. */
|
|
103
|
+
readonly onRefreshed?: (info: {
|
|
104
|
+
expiresAtMs: number;
|
|
105
|
+
}) => void;
|
|
106
|
+
/** Called on every refresh failure. */
|
|
107
|
+
readonly onError?: (error: Error) => void;
|
|
108
|
+
/**
|
|
109
|
+
* Override the buffer (ms ahead of expiry to refresh). Defaults to
|
|
110
|
+
* `max(60_000, ttlMs * 0.1)`. Tests use a tiny value to exercise
|
|
111
|
+
* scheduling without burning real time.
|
|
112
|
+
*/
|
|
113
|
+
readonly bufferMs?: number;
|
|
114
|
+
/**
|
|
115
|
+
* If true, install a `visibilitychange` listener on `document` that
|
|
116
|
+
* triggers a refresh when the tab becomes visible and the token is
|
|
117
|
+
* within the buffer window. No-op if `document` is undefined (Node).
|
|
118
|
+
* Default: true in browser-ish environments.
|
|
119
|
+
*/
|
|
120
|
+
readonly attachVisibilityListener?: boolean;
|
|
121
|
+
/** Time source. Override in tests; defaults to `Date.now`. */
|
|
122
|
+
readonly now?: () => number;
|
|
123
|
+
/** Timer pair. Override in tests. */
|
|
124
|
+
readonly setTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;
|
|
125
|
+
readonly clearTimer?: (handle: ReturnType<typeof setTimeout>) => void;
|
|
126
|
+
}
|
|
127
|
+
export interface RefreshScheduler {
|
|
128
|
+
/** Force a refresh now. Idempotent — concurrent calls share one promise. */
|
|
129
|
+
refreshNow(): Promise<{
|
|
130
|
+
expiresAtMs: number;
|
|
131
|
+
}>;
|
|
132
|
+
/** Stop scheduling. Safe to call multiple times. */
|
|
133
|
+
dispose(): void;
|
|
134
|
+
/** Current absolute expiry. Updated after each successful refresh. */
|
|
135
|
+
readonly expiresAtMs: number;
|
|
136
|
+
}
|
|
137
|
+
export declare function createRefreshScheduler(options: RefreshSchedulerOptions): RefreshScheduler;
|