@rawdash/server 0.14.0 → 0.16.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/README.md +66 -2
- package/dist/index.d.ts +116 -21
- package/dist/index.js +229 -35
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -97,7 +97,38 @@ Transitions:
|
|
|
97
97
|
- `running → failed` (sets `lastError`)
|
|
98
98
|
- Any terminal state can transition back to `queued` / `running` on the next trigger.
|
|
99
99
|
|
|
100
|
-
Clients (`@rawdash/client`) poll `/sync/state` and wait for `!isSyncActive(status)` to settle.
|
|
100
|
+
Clients (`@rawdash/sdk-client`) poll `/sync/state` and wait for `!isSyncActive(status)` to settle.
|
|
101
|
+
|
|
102
|
+
### `CachedWidget.syncState`
|
|
103
|
+
|
|
104
|
+
`listWidgets` and `getWidget` populate `syncState` (and `meta.connectorStatus`) on each `CachedWidget` from the underlying `StorageHandle.getHealth?()`. When storage doesn't implement `getHealth`, `syncState` falls back to `'unsynced'` (no data) or `'fresh'` (data exists).
|
|
105
|
+
|
|
106
|
+
| Value | Meaning |
|
|
107
|
+
| ------------ | ------------------------------------------------------------------------------------ |
|
|
108
|
+
| `'fresh'` | Data exists and the connector's `lastSyncAt` is within `2 × syncIntervalSeconds` |
|
|
109
|
+
| `'stale'` | Data exists but the connector hasn't synced inside its freshness window |
|
|
110
|
+
| `'unsynced'` | No successful sync yet for this connector |
|
|
111
|
+
| `'syncing'` | A sync is actively in progress for the connector backing this widget |
|
|
112
|
+
| `'failing'` | Connector is in `error` / `auth_failed` / `paused` — surface a reauthorize CTA in UI |
|
|
113
|
+
|
|
114
|
+
Storage adapters implement `getHealth?(): Promise<ConnectorHealth | null>` per `StorageHandle` to expose `status`, `lastSyncAt`, `lastError`, and `syncIntervalSeconds`. `InMemoryStorage` provides a minimal implementation (last-write time as `lastSyncAt`, `syncIntervalSeconds: 0`); adapters with first-class per-connector status (e.g. cloud, libSQL) populate it richly.
|
|
115
|
+
|
|
116
|
+
### `triggerSync` modes
|
|
117
|
+
|
|
118
|
+
`triggerSync(ctx, opts?)` accepts an optional `opts.mode`:
|
|
119
|
+
|
|
120
|
+
- **`'in-process'`** (default): the handler records the `queued` transition and then fires `runSync(config, storage)` as a background promise that iterates `config.connectors`. Right for self-hosted, single-process OSS deployments.
|
|
121
|
+
- **`'deferred'`**: the handler only records the `queued` transition. `runSync` is not invoked, and `getConfig` is not called (and may be omitted from `ctx`). The `running → succeeded/failed` transitions are the responsibility of an external runner — typically a queue consumer worker that decrypts credentials, applies retries, and drives storage directly.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// Self-hosted, in-process (default):
|
|
125
|
+
await triggerSync({ getConfig, getStorage });
|
|
126
|
+
|
|
127
|
+
// Queue-backed runner:
|
|
128
|
+
await triggerSync({ getStorage }, { mode: 'deferred' });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
In deferred mode, the wire response is unchanged: `{queued: true}` if `markSyncQueued()` accepted the transition, `{queued: false}` if a sync was already active.
|
|
101
132
|
|
|
102
133
|
## Engine without HTTP
|
|
103
134
|
|
|
@@ -111,6 +142,37 @@ const state = await engine.getSyncState();
|
|
|
111
142
|
|
|
112
143
|
`createEngine` exposes the same shape as the handlers but bypasses HTTP entirely — useful for jobs, CLI tools, or the MCP server.
|
|
113
144
|
|
|
145
|
+
## Widget cache (optional)
|
|
146
|
+
|
|
147
|
+
`listWidgets` and `getWidget` accept an optional `WidgetCache` so deployments can avoid hitting storage for every widget on every request:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import type { WidgetCache } from '@rawdash/server';
|
|
151
|
+
|
|
152
|
+
class LruWidgetCache implements WidgetCache {
|
|
153
|
+
private store = new Map<string, { value: CachedWidget; expiresAt: number }>();
|
|
154
|
+
async get({ dashboardId, widgetId }) {
|
|
155
|
+
const hit = this.store.get(`${dashboardId}/${widgetId}`);
|
|
156
|
+
if (!hit || hit.expiresAt < Date.now()) return undefined;
|
|
157
|
+
return hit.value;
|
|
158
|
+
}
|
|
159
|
+
async set({ dashboardId, widgetId, widget }, value) {
|
|
160
|
+
const ttlMs = ttlForWidget(widget); // e.g. derive from connector syncIntervalSeconds
|
|
161
|
+
this.store.set(`${dashboardId}/${widgetId}`, {
|
|
162
|
+
value,
|
|
163
|
+
expiresAt: Date.now() + ttlMs,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const cache = new LruWidgetCache();
|
|
169
|
+
await listWidgets(ctx, 'engineering', cache);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The cache impl owns TTL, eviction, and the backing store (LRU, KV, Redis…). If `cache` is omitted, behavior is identical to the no-cache path. Errors thrown from `cache.get` fall through to a fresh resolution; errors from `cache.set` are logged via `console.warn` and do not affect the response.
|
|
173
|
+
|
|
174
|
+
`@rawdash/hono`'s `createWidgetsRouter` accepts a `cache: (c: Context) => WidgetCache` factory invoked once per request, so the cache can be scoped to the request's tenant/auth context.
|
|
175
|
+
|
|
114
176
|
## Storage
|
|
115
177
|
|
|
116
178
|
Provide any `ServerStorage` implementation:
|
|
@@ -119,11 +181,13 @@ Provide any `ServerStorage` implementation:
|
|
|
119
181
|
- [`@rawdash/adapter-libsql`](https://www.npmjs.com/package/@rawdash/adapter-libsql) — durable libSQL/Turso/SQLite backend.
|
|
120
182
|
- Roll your own by implementing the [`ServerStorage`](https://github.com/rawdash/rawdash/blob/main/packages/core/src/server-storage.ts) interface.
|
|
121
183
|
|
|
184
|
+
`markSyncRunning` is optional on `ServerStorage`. It's an in-process-only concern: `runSync` calls it to acquire the `queued → running` lock so two concurrent in-process syncs can't trample each other. Deferred-mode storages (where an external runner drives the `running → succeeded/failed` transitions via its own aggregation) may omit `markSyncRunning` entirely — `runSync` skips the call when it's absent.
|
|
185
|
+
|
|
122
186
|
## Links
|
|
123
187
|
|
|
124
188
|
- [rawdash docs](https://rawdash.dev)
|
|
125
189
|
- [`@rawdash/hono`](https://www.npmjs.com/package/@rawdash/hono) — Hono adapter
|
|
126
|
-
- [`@rawdash/client`](https://www.npmjs.com/package/@rawdash/client) — typed HTTP client
|
|
190
|
+
- [`@rawdash/sdk-client`](https://www.npmjs.com/package/@rawdash/sdk-client) — typed HTTP client
|
|
127
191
|
- [GitHub](https://github.com/rawdash/rawdash)
|
|
128
192
|
- [Issues](https://github.com/rawdash/rawdash/issues)
|
|
129
193
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DashboardConfig, ServerStorage,
|
|
2
|
-
export { ACTIVE_SYNC_STATUSES, CachedWidget, ConfiguredConnector, DashboardConfig, HealthResponse, InMemoryStorage, ServerStorage, SyncState, SyncStatus, TriggerSyncResponse, WidgetsListResponse, computeMetric, isSyncActive } from '@rawdash/core';
|
|
1
|
+
import { DashboardConfig, ServerStorage, ConnectorLogger, ConnectorRegistry, SecretsResolver, Widget, CachedWidget, HealthResponse, SyncState, WidgetsListResponse, TriggerSyncResponse, RetentionConfig } from '@rawdash/core';
|
|
2
|
+
export { ACTIVE_SYNC_STATUSES, CachedWidget, ConfiguredConnector, ConnectorClass, ConnectorHealth, ConnectorRegistry, DashboardConfig, HealthResponse, InMemoryStorage, SecretsResolver, ServerStorage, SyncState, SyncStatus, TriggerSyncResponse, Widget, WidgetSyncState, WidgetsListResponse, computeMetric, instantiateConnector, isSyncActive } from '@rawdash/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Per-request lookup functions an HTTP adapter passes to engine handlers.
|
|
@@ -32,7 +32,7 @@ declare function isRawdashError(err: unknown): err is RawdashError;
|
|
|
32
32
|
* Canonical URL path conventions for the rawdash HTTP wire contract.
|
|
33
33
|
*
|
|
34
34
|
* Framework adapters (`@rawdash/hono`, etc.) and clients
|
|
35
|
-
* (`@rawdash/client`) should use these constants instead of hard-coding
|
|
35
|
+
* (`@rawdash/sdk-client`) should use these constants instead of hard-coding
|
|
36
36
|
* paths, so the contract stays in one place.
|
|
37
37
|
*/
|
|
38
38
|
declare const ROUTES: {
|
|
@@ -46,23 +46,21 @@ declare const ROUTES: {
|
|
|
46
46
|
};
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
* Framework-agnostic request handlers for the rawdash wire contract.
|
|
51
|
-
*
|
|
52
|
-
* Each function takes an `EngineContext` (providing per-request access to
|
|
53
|
-
* the config + storage) and returns the response body, or throws a
|
|
54
|
-
* `RawdashError` on a client-visible failure. HTTP adapters
|
|
55
|
-
* (`@rawdash/hono`, etc.) wrap these in their framework's request/response
|
|
56
|
-
* cycle and translate `RawdashError` into a structured error response.
|
|
57
|
-
*/
|
|
58
|
-
declare function getHealth(): HealthResponse;
|
|
59
|
-
declare function getSyncStateHandler(ctx: EngineContext): Promise<SyncState>;
|
|
60
|
-
declare function triggerSync(ctx: EngineContext): Promise<TriggerSyncResponse>;
|
|
61
|
-
declare function listWidgets(ctx: EngineContext, dashboardId: string): Promise<WidgetsListResponse>;
|
|
62
|
-
declare function getWidget(ctx: EngineContext, dashboardId: string, widgetId: string): Promise<CachedWidget>;
|
|
63
|
-
declare function runRetentionOnce(ctx: EngineContext): Promise<void>;
|
|
64
|
-
|
|
65
49
|
declare const FULL_SYNC_TIMEOUT_MS = 300000;
|
|
50
|
+
declare const FULL_SYNC_MAX_CHUNKS = 1000;
|
|
51
|
+
type ConnectorLoggerFactory = (scope: string) => ConnectorLogger;
|
|
52
|
+
interface RunSyncOptions {
|
|
53
|
+
connectorRegistry: ConnectorRegistry;
|
|
54
|
+
secretsResolver?: SecretsResolver;
|
|
55
|
+
/**
|
|
56
|
+
* Build a logger for the runner and for each connector instance. Called
|
|
57
|
+
* with `'runner'` for the start/settled envelopes and with each
|
|
58
|
+
* `connector.name` for the per-connector progress logs. Defaults to
|
|
59
|
+
* {@link createDefaultConnectorLogger}, which writes a single-line
|
|
60
|
+
* `[scope] event key=value …` record to stdout/stderr.
|
|
61
|
+
*/
|
|
62
|
+
loggerFactory?: ConnectorLoggerFactory;
|
|
63
|
+
}
|
|
66
64
|
/**
|
|
67
65
|
* Run a full sync across all connectors in the config in parallel via
|
|
68
66
|
* `Promise.allSettled`, so one failure doesn't abort the others. Each
|
|
@@ -71,6 +69,12 @@ declare const FULL_SYNC_TIMEOUT_MS = 300000;
|
|
|
71
69
|
* ignores `AbortSignal` (or a storage call that hangs) can still not
|
|
72
70
|
* pin sync state in `running` indefinitely.
|
|
73
71
|
*
|
|
72
|
+
* Connectors that return `{ done: false, cursor }` are looped in-process,
|
|
73
|
+
* threading the cursor back into the next `sync` call until `done: true`
|
|
74
|
+
* or the shared timeout / `FULL_SYNC_MAX_CHUNKS` cap fires. Cloud
|
|
75
|
+
* deployments layer cross-restart cursor persistence on top of the same
|
|
76
|
+
* contract; the OSS runner is the trivial in-process case.
|
|
77
|
+
*
|
|
74
78
|
* The per-run storage handle is bound to the same `AbortController`, so
|
|
75
79
|
* once the timeout fires every subsequent write call on that handle
|
|
76
80
|
* becomes a no-op. That makes tail writes from a timed-out connector
|
|
@@ -84,7 +88,95 @@ declare const FULL_SYNC_TIMEOUT_MS = 300000;
|
|
|
84
88
|
*
|
|
85
89
|
* Returns silently if another sync acquired the `running` lock first.
|
|
86
90
|
*/
|
|
87
|
-
declare function runSync(config: DashboardConfig, storage: ServerStorage): Promise<void>;
|
|
91
|
+
declare function runSync(config: DashboardConfig, storage: ServerStorage, options: RunSyncOptions): Promise<void>;
|
|
92
|
+
|
|
93
|
+
interface WidgetCacheKey {
|
|
94
|
+
dashboardId: string;
|
|
95
|
+
widgetId: string;
|
|
96
|
+
widget: Widget;
|
|
97
|
+
}
|
|
98
|
+
interface WidgetCache {
|
|
99
|
+
get(key: WidgetCacheKey): Promise<CachedWidget | undefined>;
|
|
100
|
+
set(key: WidgetCacheKey, value: CachedWidget): Promise<void>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Per-request lookup shape accepted by `triggerSync` in deferred mode.
|
|
105
|
+
* `getConfig` is optional because the trigger handler never calls
|
|
106
|
+
* `runSync` — deployments that delegate the actual sync work to an
|
|
107
|
+
* external runner may not be able to materialize a `DashboardConfig` at
|
|
108
|
+
* request time.
|
|
109
|
+
*/
|
|
110
|
+
interface DeferredTriggerSyncContext {
|
|
111
|
+
getConfig?: () => DashboardConfig | Promise<DashboardConfig>;
|
|
112
|
+
getStorage: () => ServerStorage | Promise<ServerStorage>;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Per-request lookup shape accepted by `triggerSync` in in-process
|
|
116
|
+
* mode. `getConfig` is required because the trigger handler kicks off
|
|
117
|
+
* `runSync(config, storage)` in the background. `connectorRegistry` is
|
|
118
|
+
* required so the background runner can instantiate connector
|
|
119
|
+
* implementations on demand from the declarative `DashboardConfig`.
|
|
120
|
+
*/
|
|
121
|
+
interface InProcessTriggerSyncContext {
|
|
122
|
+
getConfig: () => DashboardConfig | Promise<DashboardConfig>;
|
|
123
|
+
getStorage: () => ServerStorage | Promise<ServerStorage>;
|
|
124
|
+
connectorRegistry: ConnectorRegistry;
|
|
125
|
+
secretsResolver?: SecretsResolver;
|
|
126
|
+
loggerFactory?: ConnectorLoggerFactory;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* @deprecated Prefer `InProcessTriggerSyncContext` /
|
|
130
|
+
* `DeferredTriggerSyncContext`. Retained as the union for callers that
|
|
131
|
+
* need a single type covering both modes.
|
|
132
|
+
*/
|
|
133
|
+
type TriggerSyncContext = DeferredTriggerSyncContext;
|
|
134
|
+
type TriggerSyncMode = 'in-process' | 'deferred';
|
|
135
|
+
interface TriggerSyncOptions {
|
|
136
|
+
/**
|
|
137
|
+
* `'in-process'` (default): the trigger handler also runs the sync in
|
|
138
|
+
* the background by invoking `runSync(config, storage)`. Suitable for
|
|
139
|
+
* self-hosted, single-process deployments.
|
|
140
|
+
*
|
|
141
|
+
* `'deferred'`: the trigger handler only persists the `queued`
|
|
142
|
+
* transition and returns. The `running → succeeded/failed` transitions
|
|
143
|
+
* are the responsibility of an external runner (e.g. a queue consumer
|
|
144
|
+
* worker), which must drive the storage accordingly.
|
|
145
|
+
*/
|
|
146
|
+
mode?: TriggerSyncMode;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Framework-agnostic request handlers for the rawdash wire contract.
|
|
150
|
+
*
|
|
151
|
+
* Each function takes an `EngineContext` (providing per-request access to
|
|
152
|
+
* the config + storage) and returns the response body, or throws a
|
|
153
|
+
* `RawdashError` on a client-visible failure. HTTP adapters
|
|
154
|
+
* (`@rawdash/hono`, etc.) wrap these in their framework's request/response
|
|
155
|
+
* cycle and translate `RawdashError` into a structured error response.
|
|
156
|
+
*/
|
|
157
|
+
declare function getHealth(): HealthResponse;
|
|
158
|
+
declare function getSyncStateHandler(ctx: EngineContext): Promise<SyncState>;
|
|
159
|
+
declare function triggerSync(ctx: InProcessTriggerSyncContext, opts?: {
|
|
160
|
+
mode?: 'in-process';
|
|
161
|
+
}): Promise<TriggerSyncResponse>;
|
|
162
|
+
declare function triggerSync(ctx: DeferredTriggerSyncContext, opts: {
|
|
163
|
+
mode: 'deferred';
|
|
164
|
+
}): Promise<TriggerSyncResponse>;
|
|
165
|
+
declare function listWidgets(ctx: EngineContext, dashboardId: string, cache?: WidgetCache): Promise<WidgetsListResponse>;
|
|
166
|
+
interface GetWidgetOptions {
|
|
167
|
+
cache?: WidgetCache;
|
|
168
|
+
ifNoneMatch?: string;
|
|
169
|
+
}
|
|
170
|
+
type GetWidgetResult = {
|
|
171
|
+
status: 'ok';
|
|
172
|
+
etag: string | undefined;
|
|
173
|
+
widget: CachedWidget;
|
|
174
|
+
} | {
|
|
175
|
+
status: 'not-modified';
|
|
176
|
+
etag: string;
|
|
177
|
+
};
|
|
178
|
+
declare function getWidget(ctx: EngineContext, dashboardId: string, widgetId: string, opts?: GetWidgetOptions): Promise<GetWidgetResult>;
|
|
179
|
+
declare function runRetentionOnce(ctx: EngineContext): Promise<void>;
|
|
88
180
|
|
|
89
181
|
declare const DEFAULT_RETENTION_INTERVAL_MS: number;
|
|
90
182
|
declare function hasPruningPolicy(config: RetentionConfig): boolean;
|
|
@@ -97,6 +189,9 @@ declare function runRetention(config: DashboardConfig, storage: ServerStorage):
|
|
|
97
189
|
|
|
98
190
|
interface EngineOptions {
|
|
99
191
|
storage?: ServerStorage;
|
|
192
|
+
connectorRegistry?: ConnectorRegistry;
|
|
193
|
+
secretsResolver?: SecretsResolver;
|
|
194
|
+
loggerFactory?: ConnectorLoggerFactory;
|
|
100
195
|
}
|
|
101
196
|
interface Engine {
|
|
102
197
|
getWidget(dashboardId: string, widgetId: string): Promise<CachedWidget | undefined>;
|
|
@@ -107,4 +202,4 @@ interface Engine {
|
|
|
107
202
|
}
|
|
108
203
|
declare function createEngine(config: DashboardConfig, options?: EngineOptions): Engine;
|
|
109
204
|
|
|
110
|
-
export { DEFAULT_RETENTION_INTERVAL_MS, type Engine, type EngineContext, type EngineOptions, FULL_SYNC_TIMEOUT_MS, ROUTES, RawdashError, createEngine, getHealth, getSyncStateHandler, getWidget, hasPruningPolicy, isRawdashError, listWidgets, runRetention, runRetentionOnce, runSync, triggerSync };
|
|
205
|
+
export { type ConnectorLoggerFactory, DEFAULT_RETENTION_INTERVAL_MS, type DeferredTriggerSyncContext, type Engine, type EngineContext, type EngineOptions, FULL_SYNC_MAX_CHUNKS, FULL_SYNC_TIMEOUT_MS, type GetWidgetOptions, type GetWidgetResult, type InProcessTriggerSyncContext, ROUTES, RawdashError, type RunSyncOptions, type TriggerSyncContext, type TriggerSyncMode, type TriggerSyncOptions, type WidgetCache, type WidgetCacheKey, createEngine, getHealth, getSyncStateHandler, getWidget, hasPruningPolicy, isRawdashError, listWidgets, runRetention, runRetentionOnce, runSync, triggerSync };
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ var ROUTES = {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
// src/handlers.ts
|
|
29
|
-
import { isSyncActive, resolveWidget } from "@rawdash/core";
|
|
29
|
+
import { computeWidgetEtag, isSyncActive, resolveWidget } from "@rawdash/core";
|
|
30
30
|
|
|
31
31
|
// src/retention.ts
|
|
32
32
|
import { selectForDeletion } from "@rawdash/core";
|
|
@@ -41,8 +41,8 @@ async function runRetention(config, storage) {
|
|
|
41
41
|
}
|
|
42
42
|
const nowMs = Date.now();
|
|
43
43
|
const results = await Promise.allSettled(
|
|
44
|
-
config.connectors.map(async (
|
|
45
|
-
const handle = storage.getStorageHandle(
|
|
44
|
+
config.connectors.map(async (entry) => {
|
|
45
|
+
const handle = storage.getStorageHandle(entry.name);
|
|
46
46
|
const [events, metrics, distributions] = await Promise.all([
|
|
47
47
|
handle.queryEvents({}),
|
|
48
48
|
handle.queryMetrics({}),
|
|
@@ -95,55 +95,152 @@ async function applyRetentionToShape(rows, getTs, config, nowMs, writeSurvivors)
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// src/sync.ts
|
|
98
|
+
import {
|
|
99
|
+
computeConnectorBackfill,
|
|
100
|
+
createDefaultConnectorLogger,
|
|
101
|
+
instantiateConnector
|
|
102
|
+
} from "@rawdash/core";
|
|
98
103
|
var FULL_SYNC_TIMEOUT_MS = 3e5;
|
|
99
|
-
|
|
104
|
+
var FULL_SYNC_MAX_CHUNKS = 1e3;
|
|
105
|
+
var BACKFILL_BUFFER_MS = 864e5;
|
|
106
|
+
async function runSync(config, storage, options) {
|
|
100
107
|
await storage.markSyncQueued();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
if (typeof storage.markSyncRunning === "function") {
|
|
109
|
+
const acquired = await storage.markSyncRunning();
|
|
110
|
+
if (!acquired) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
104
113
|
}
|
|
105
114
|
const errors = [];
|
|
115
|
+
const backfill = computeConnectorBackfill(config);
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
const rawLoggerFactory = options.loggerFactory ?? ((scope) => createDefaultConnectorLogger({ scope }));
|
|
118
|
+
const safeLogger = (scope) => {
|
|
119
|
+
let inner;
|
|
120
|
+
try {
|
|
121
|
+
inner = rawLoggerFactory(scope);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.warn(
|
|
124
|
+
`[runner] loggerFactory threw for scope=${scope}; falling back to default`,
|
|
125
|
+
err
|
|
126
|
+
);
|
|
127
|
+
inner = createDefaultConnectorLogger({ scope });
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
info(event, fields) {
|
|
131
|
+
try {
|
|
132
|
+
inner.info(event, fields);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.warn(
|
|
135
|
+
`[runner] logger.info threw for scope=${scope} event=${event}`,
|
|
136
|
+
err
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
warn(event, fields) {
|
|
141
|
+
try {
|
|
142
|
+
inner.warn(event, fields);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
console.warn(
|
|
145
|
+
`[runner] logger.warn threw for scope=${scope} event=${event}`,
|
|
146
|
+
err
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
const runnerLogger = safeLogger("runner");
|
|
106
153
|
await Promise.allSettled(
|
|
107
|
-
config.connectors.map(async (
|
|
154
|
+
config.connectors.map(async (entry) => {
|
|
155
|
+
if (entry.enabled === false) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const scope = backfill.get(entry.name);
|
|
159
|
+
if (!scope) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
108
162
|
const controller = new AbortController();
|
|
109
|
-
const handle = storage.getStorageHandle(
|
|
163
|
+
const handle = storage.getStorageHandle(entry.name, {
|
|
110
164
|
signal: controller.signal
|
|
111
165
|
});
|
|
166
|
+
const connectorLogger = safeLogger(entry.name);
|
|
167
|
+
const syncStart = Date.now();
|
|
112
168
|
let timer;
|
|
169
|
+
let status = "succeeded";
|
|
170
|
+
let failureReason;
|
|
113
171
|
try {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
172
|
+
const connector = instantiateConnector(
|
|
173
|
+
entry,
|
|
174
|
+
options.connectorRegistry,
|
|
175
|
+
options.secretsResolver,
|
|
176
|
+
connectorLogger
|
|
118
177
|
);
|
|
119
178
|
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
120
179
|
timer = setTimeout(() => {
|
|
121
180
|
controller.abort();
|
|
122
181
|
const err = new Error(
|
|
123
|
-
`${
|
|
182
|
+
`${entry.name} timed out after ${FULL_SYNC_TIMEOUT_MS}ms`
|
|
124
183
|
);
|
|
125
184
|
err.name = "AbortError";
|
|
126
185
|
reject(err);
|
|
127
186
|
}, FULL_SYNC_TIMEOUT_MS);
|
|
128
187
|
});
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
188
|
+
const resources = new Set(scope.keys());
|
|
189
|
+
let maxWindowMs;
|
|
190
|
+
for (const [, { requiredWindowMs }] of scope.entries()) {
|
|
191
|
+
if (requiredWindowMs === void 0) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (maxWindowMs === void 0 || requiredWindowMs > maxWindowMs) {
|
|
195
|
+
maxWindowMs = requiredWindowMs;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const since = maxWindowMs !== void 0 ? new Date(now - maxWindowMs - BACKFILL_BUFFER_MS).toISOString() : void 0;
|
|
199
|
+
runnerLogger.info("sync started", {
|
|
200
|
+
connector: entry.name,
|
|
201
|
+
resources: Array.from(resources),
|
|
202
|
+
mode: "full",
|
|
203
|
+
since
|
|
204
|
+
});
|
|
205
|
+
let cursor = void 0;
|
|
206
|
+
let chunks = 0;
|
|
207
|
+
while (true) {
|
|
208
|
+
chunks += 1;
|
|
209
|
+
if (chunks > FULL_SYNC_MAX_CHUNKS) {
|
|
210
|
+
controller.abort();
|
|
211
|
+
throw new Error(
|
|
212
|
+
`${entry.name} exceeded ${FULL_SYNC_MAX_CHUNKS} sync chunks without completing`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
const syncPromise = connector.sync(
|
|
216
|
+
{ mode: "full", since, cursor, resources },
|
|
217
|
+
handle,
|
|
218
|
+
controller.signal
|
|
133
219
|
);
|
|
220
|
+
const result = await Promise.race([syncPromise, timeoutPromise]);
|
|
221
|
+
if (result.done) {
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
cursor = result.cursor;
|
|
134
225
|
}
|
|
135
226
|
} catch (err) {
|
|
227
|
+
status = "failed";
|
|
136
228
|
if (err instanceof Error && err.name === "AbortError") {
|
|
137
|
-
|
|
138
|
-
`${connector.id} timed out after ${FULL_SYNC_TIMEOUT_MS}ms`
|
|
139
|
-
);
|
|
229
|
+
failureReason = `${entry.name} timed out after ${FULL_SYNC_TIMEOUT_MS}ms`;
|
|
140
230
|
} else {
|
|
141
|
-
|
|
231
|
+
failureReason = err instanceof Error ? err.message : String(err);
|
|
142
232
|
}
|
|
233
|
+
errors.push(failureReason);
|
|
143
234
|
} finally {
|
|
144
235
|
if (timer !== void 0) {
|
|
145
236
|
clearTimeout(timer);
|
|
146
237
|
}
|
|
238
|
+
runnerLogger.info("sync settled", {
|
|
239
|
+
connector: entry.name,
|
|
240
|
+
status,
|
|
241
|
+
duration_ms: Date.now() - syncStart,
|
|
242
|
+
error: failureReason
|
|
243
|
+
});
|
|
147
244
|
}
|
|
148
245
|
})
|
|
149
246
|
);
|
|
@@ -155,6 +252,40 @@ async function runSync(config, storage) {
|
|
|
155
252
|
}
|
|
156
253
|
|
|
157
254
|
// src/handlers.ts
|
|
255
|
+
async function cacheGetSafe(cache, dashboardId, widgetId, widget) {
|
|
256
|
+
try {
|
|
257
|
+
return await cache.get({ dashboardId, widgetId, widget });
|
|
258
|
+
} catch (err) {
|
|
259
|
+
console.warn("Rawdash widget cache get failed", err);
|
|
260
|
+
return void 0;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async function cacheSetSafe(cache, dashboardId, widgetId, widget, value) {
|
|
264
|
+
try {
|
|
265
|
+
await cache.set({ dashboardId, widgetId, widget }, value);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
console.warn("Rawdash widget cache set failed", err);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
async function resolveWithCache(dashboardId, widgetId, widget, connectorNames, storage, cache) {
|
|
271
|
+
if (cache) {
|
|
272
|
+
const hit = await cacheGetSafe(cache, dashboardId, widgetId, widget);
|
|
273
|
+
if (hit) {
|
|
274
|
+
return hit;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const fresh = await resolveWidget(
|
|
278
|
+
dashboardId,
|
|
279
|
+
widgetId,
|
|
280
|
+
widget,
|
|
281
|
+
connectorNames,
|
|
282
|
+
storage
|
|
283
|
+
);
|
|
284
|
+
if (fresh && cache) {
|
|
285
|
+
await cacheSetSafe(cache, dashboardId, widgetId, widget, fresh);
|
|
286
|
+
}
|
|
287
|
+
return fresh;
|
|
288
|
+
}
|
|
158
289
|
function getHealth() {
|
|
159
290
|
return { status: "ok" };
|
|
160
291
|
}
|
|
@@ -162,39 +293,65 @@ async function getSyncStateHandler(ctx) {
|
|
|
162
293
|
const storage = await ctx.getStorage();
|
|
163
294
|
return storage.getSyncState();
|
|
164
295
|
}
|
|
165
|
-
async function triggerSync(ctx) {
|
|
296
|
+
async function triggerSync(ctx, opts = {}) {
|
|
297
|
+
const mode = opts.mode ?? "in-process";
|
|
166
298
|
const storage = await ctx.getStorage();
|
|
167
299
|
const state = await storage.getSyncState();
|
|
168
300
|
if (isSyncActive(state.status)) {
|
|
169
301
|
return { queued: false };
|
|
170
302
|
}
|
|
171
|
-
|
|
303
|
+
let config;
|
|
304
|
+
if (mode === "in-process") {
|
|
305
|
+
if (!ctx.getConfig) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
'triggerSync: getConfig is required when mode is "in-process"'
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
config = await ctx.getConfig();
|
|
311
|
+
}
|
|
172
312
|
const queued = await storage.markSyncQueued();
|
|
173
313
|
if (!queued) {
|
|
174
314
|
return { queued: false };
|
|
175
315
|
}
|
|
176
|
-
|
|
316
|
+
if (mode === "deferred") {
|
|
317
|
+
return { queued: true };
|
|
318
|
+
}
|
|
319
|
+
const inProcessCtx = ctx;
|
|
320
|
+
void runSync(config, storage, {
|
|
321
|
+
connectorRegistry: inProcessCtx.connectorRegistry,
|
|
322
|
+
secretsResolver: inProcessCtx.secretsResolver,
|
|
323
|
+
loggerFactory: inProcessCtx.loggerFactory
|
|
324
|
+
}).catch((err) => {
|
|
177
325
|
console.error("Rawdash sync failed", err);
|
|
178
326
|
});
|
|
179
327
|
return { queued: true };
|
|
180
328
|
}
|
|
181
|
-
async function listWidgets(ctx, dashboardId) {
|
|
329
|
+
async function listWidgets(ctx, dashboardId, cache) {
|
|
182
330
|
const config = await ctx.getConfig();
|
|
183
331
|
const dashboard = config.dashboards[dashboardId];
|
|
184
332
|
if (!dashboard) {
|
|
185
333
|
throw new RawdashError(404, "DASHBOARD_NOT_FOUND", "Dashboard not found");
|
|
186
334
|
}
|
|
187
335
|
const storage = await ctx.getStorage();
|
|
336
|
+
const connectorNames = config.connectors.map((c) => c.name);
|
|
188
337
|
const entries = Object.entries(dashboard.widgets);
|
|
189
338
|
const resolved = await Promise.all(
|
|
190
339
|
entries.map(
|
|
191
|
-
([key, widget]) =>
|
|
340
|
+
([key, widget]) => resolveWithCache(
|
|
341
|
+
dashboardId,
|
|
342
|
+
key,
|
|
343
|
+
widget,
|
|
344
|
+
connectorNames,
|
|
345
|
+
storage,
|
|
346
|
+
cache
|
|
347
|
+
)
|
|
192
348
|
)
|
|
193
349
|
);
|
|
194
350
|
const widgets = resolved.filter((w) => w !== void 0);
|
|
195
351
|
return { widgets };
|
|
196
352
|
}
|
|
197
|
-
async function getWidget(ctx, dashboardId, widgetId) {
|
|
353
|
+
async function getWidget(ctx, dashboardId, widgetId, opts = {}) {
|
|
354
|
+
const { cache, ifNoneMatch } = opts;
|
|
198
355
|
const config = await ctx.getConfig();
|
|
199
356
|
const dashboard = config.dashboards[dashboardId];
|
|
200
357
|
if (!dashboard) {
|
|
@@ -205,16 +362,34 @@ async function getWidget(ctx, dashboardId, widgetId) {
|
|
|
205
362
|
throw new RawdashError(404, "WIDGET_NOT_FOUND", "Widget not found");
|
|
206
363
|
}
|
|
207
364
|
const storage = await ctx.getStorage();
|
|
208
|
-
const
|
|
365
|
+
const connectorNames = config.connectors.map((c) => c.name);
|
|
366
|
+
const connectorId = widget.kind === "status" ? widget.source : widget.metric.connectorId;
|
|
367
|
+
if (!connectorNames.includes(connectorId)) {
|
|
368
|
+
throw new RawdashError(404, "WIDGET_NOT_FOUND", "Widget not found");
|
|
369
|
+
}
|
|
370
|
+
if (ifNoneMatch) {
|
|
371
|
+
const handle = storage.getStorageHandle(connectorId);
|
|
372
|
+
const health = await handle.getHealth?.() ?? null;
|
|
373
|
+
if (health?.lastSyncAt) {
|
|
374
|
+
const probeEtag = computeWidgetEtag(health.lastSyncAt, widget);
|
|
375
|
+
if (probeEtag === ifNoneMatch) {
|
|
376
|
+
return { status: "not-modified", etag: probeEtag };
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const result = await resolveWithCache(
|
|
381
|
+
dashboardId,
|
|
209
382
|
widgetId,
|
|
210
383
|
widget,
|
|
211
|
-
|
|
212
|
-
storage
|
|
384
|
+
connectorNames,
|
|
385
|
+
storage,
|
|
386
|
+
cache
|
|
213
387
|
);
|
|
214
388
|
if (!result) {
|
|
215
389
|
throw new RawdashError(404, "WIDGET_NOT_FOUND", "Widget not found");
|
|
216
390
|
}
|
|
217
|
-
|
|
391
|
+
const etag = result.cachedAt ? computeWidgetEtag(result.cachedAt, widget) : void 0;
|
|
392
|
+
return { status: "ok", etag, widget: result };
|
|
218
393
|
}
|
|
219
394
|
async function runRetentionOnce(ctx) {
|
|
220
395
|
const config = await ctx.getConfig();
|
|
@@ -226,6 +401,7 @@ async function runRetentionOnce(ctx) {
|
|
|
226
401
|
import { InMemoryStorage, isSyncActive as isSyncActive2, resolveWidget as resolveWidget2 } from "@rawdash/core";
|
|
227
402
|
function createEngine(config, options = {}) {
|
|
228
403
|
const storage = options.storage ?? new InMemoryStorage();
|
|
404
|
+
const connectorNames = config.connectors.map((c) => c.name);
|
|
229
405
|
return {
|
|
230
406
|
async getWidget(dashboardId, widgetId) {
|
|
231
407
|
const dashboard = config.dashboards[dashboardId];
|
|
@@ -236,7 +412,13 @@ function createEngine(config, options = {}) {
|
|
|
236
412
|
if (!widget) {
|
|
237
413
|
return void 0;
|
|
238
414
|
}
|
|
239
|
-
return resolveWidget2(
|
|
415
|
+
return resolveWidget2(
|
|
416
|
+
dashboardId,
|
|
417
|
+
widgetId,
|
|
418
|
+
widget,
|
|
419
|
+
connectorNames,
|
|
420
|
+
storage
|
|
421
|
+
);
|
|
240
422
|
},
|
|
241
423
|
async getWidgets(dashboardId) {
|
|
242
424
|
const dashboard = config.dashboards[dashboardId];
|
|
@@ -246,7 +428,7 @@ function createEngine(config, options = {}) {
|
|
|
246
428
|
const entries = Object.entries(dashboard.widgets);
|
|
247
429
|
const resolved = await Promise.all(
|
|
248
430
|
entries.map(
|
|
249
|
-
([key, widget]) => resolveWidget2(key, widget,
|
|
431
|
+
([key, widget]) => resolveWidget2(dashboardId, key, widget, connectorNames, storage)
|
|
250
432
|
)
|
|
251
433
|
);
|
|
252
434
|
return resolved.filter((w) => w !== void 0);
|
|
@@ -258,6 +440,11 @@ function createEngine(config, options = {}) {
|
|
|
258
440
|
return storage.getSyncState();
|
|
259
441
|
},
|
|
260
442
|
async triggerSync() {
|
|
443
|
+
if (!options.connectorRegistry) {
|
|
444
|
+
throw new Error(
|
|
445
|
+
"createEngine: connectorRegistry is required to triggerSync"
|
|
446
|
+
);
|
|
447
|
+
}
|
|
261
448
|
const state = await storage.getSyncState();
|
|
262
449
|
if (isSyncActive2(state.status)) {
|
|
263
450
|
return { queued: false };
|
|
@@ -266,7 +453,11 @@ function createEngine(config, options = {}) {
|
|
|
266
453
|
if (!queued) {
|
|
267
454
|
return { queued: false };
|
|
268
455
|
}
|
|
269
|
-
void runSync(config, storage
|
|
456
|
+
void runSync(config, storage, {
|
|
457
|
+
connectorRegistry: options.connectorRegistry,
|
|
458
|
+
secretsResolver: options.secretsResolver,
|
|
459
|
+
loggerFactory: options.loggerFactory
|
|
460
|
+
}).catch((error) => {
|
|
270
461
|
console.error("Rawdash sync failed", error);
|
|
271
462
|
});
|
|
272
463
|
return { queued: true };
|
|
@@ -282,9 +473,11 @@ import { InMemoryStorage as InMemoryStorage2 } from "@rawdash/core";
|
|
|
282
473
|
|
|
283
474
|
// src/index.ts
|
|
284
475
|
import { isSyncActive as isSyncActive3, ACTIVE_SYNC_STATUSES } from "@rawdash/core";
|
|
476
|
+
import { instantiateConnector as instantiateConnector2 } from "@rawdash/core";
|
|
285
477
|
export {
|
|
286
478
|
ACTIVE_SYNC_STATUSES,
|
|
287
479
|
DEFAULT_RETENTION_INTERVAL_MS,
|
|
480
|
+
FULL_SYNC_MAX_CHUNKS,
|
|
288
481
|
FULL_SYNC_TIMEOUT_MS,
|
|
289
482
|
InMemoryStorage2 as InMemoryStorage,
|
|
290
483
|
ROUTES,
|
|
@@ -295,6 +488,7 @@ export {
|
|
|
295
488
|
getSyncStateHandler,
|
|
296
489
|
getWidget,
|
|
297
490
|
hasPruningPolicy,
|
|
491
|
+
instantiateConnector2 as instantiateConnector,
|
|
298
492
|
isRawdashError,
|
|
299
493
|
isSyncActive3 as isSyncActive,
|
|
300
494
|
listWidgets,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/routes.ts","../src/handlers.ts","../src/retention.ts","../src/sync.ts","../src/engine.ts","../src/compute.ts","../src/storage.ts","../src/index.ts"],"sourcesContent":["/**\n * Thrown by `@rawdash/server` handlers when a request fails in a way the\n * HTTP adapter should translate to a structured response (e.g. 404, 400).\n * Framework adapters (`@rawdash/hono`, etc.) should catch this and map\n * `status` to the appropriate HTTP status code.\n */\nexport class RawdashError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = 'RawdashError';\n }\n}\n\nexport function isRawdashError(err: unknown): err is RawdashError {\n return err instanceof RawdashError;\n}\n","/**\n * Canonical URL path conventions for the rawdash HTTP wire contract.\n *\n * Framework adapters (`@rawdash/hono`, etc.) and clients\n * (`@rawdash/client`) should use these constants instead of hard-coding\n * paths, so the contract stays in one place.\n */\nexport const ROUTES = {\n health: '/health',\n syncState: '/sync/state',\n sync: '/sync',\n retention: '/retention/retain',\n widgets: {\n list: (dashboardId: string): string =>\n `/dashboards/${encodeURIComponent(dashboardId)}/widgets`,\n single: (dashboardId: string, widgetId: string): string =>\n `/dashboards/${encodeURIComponent(dashboardId)}/widgets/${encodeURIComponent(widgetId)}`,\n },\n} as const;\n","import type {\n CachedWidget,\n HealthResponse,\n SyncState,\n TriggerSyncResponse,\n WidgetsListResponse,\n} from '@rawdash/core';\nimport { isSyncActive, resolveWidget } from '@rawdash/core';\n\nimport type { EngineContext } from './context';\nimport { RawdashError } from './errors';\nimport { runRetention } from './retention';\nimport { runSync } from './sync';\n\n/**\n * Framework-agnostic request handlers for the rawdash wire contract.\n *\n * Each function takes an `EngineContext` (providing per-request access to\n * the config + storage) and returns the response body, or throws a\n * `RawdashError` on a client-visible failure. HTTP adapters\n * (`@rawdash/hono`, etc.) wrap these in their framework's request/response\n * cycle and translate `RawdashError` into a structured error response.\n */\n\nexport function getHealth(): HealthResponse {\n return { status: 'ok' };\n}\n\nexport async function getSyncStateHandler(\n ctx: EngineContext,\n): Promise<SyncState> {\n const storage = await ctx.getStorage();\n return storage.getSyncState();\n}\n\nexport async function triggerSync(\n ctx: EngineContext,\n): Promise<TriggerSyncResponse> {\n const storage = await ctx.getStorage();\n const state = await storage.getSyncState();\n if (isSyncActive(state.status)) {\n return { queued: false };\n }\n // Load the config *before* marking queued — if config loading rejects\n // after we've persisted the queued transition, the sync state would be\n // stuck in `queued` with no background run to drain it.\n const config = await ctx.getConfig();\n // Persist the queued transition synchronously so clients polling\n // /sync/state right after the trigger see `queued` (with queuedAt),\n // not a stale terminal state.\n const queued = await storage.markSyncQueued();\n if (!queued) {\n return { queued: false };\n }\n void runSync(config, storage).catch((err) => {\n console.error('Rawdash sync failed', err);\n });\n return { queued: true };\n}\n\nexport async function listWidgets(\n ctx: EngineContext,\n dashboardId: string,\n): Promise<WidgetsListResponse> {\n const config = await ctx.getConfig();\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n throw new RawdashError(404, 'DASHBOARD_NOT_FOUND', 'Dashboard not found');\n }\n const storage = await ctx.getStorage();\n const entries = Object.entries(dashboard.widgets);\n const resolved = await Promise.all(\n entries.map(([key, widget]) =>\n resolveWidget(key, widget, config.connectors, storage),\n ),\n );\n const widgets = resolved.filter((w): w is CachedWidget => w !== undefined);\n return { widgets };\n}\n\nexport async function getWidget(\n ctx: EngineContext,\n dashboardId: string,\n widgetId: string,\n): Promise<CachedWidget> {\n const config = await ctx.getConfig();\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n throw new RawdashError(404, 'DASHBOARD_NOT_FOUND', 'Dashboard not found');\n }\n const widget = dashboard.widgets[widgetId];\n if (!widget) {\n throw new RawdashError(404, 'WIDGET_NOT_FOUND', 'Widget not found');\n }\n const storage = await ctx.getStorage();\n const result = await resolveWidget(\n widgetId,\n widget,\n config.connectors,\n storage,\n );\n if (!result) {\n throw new RawdashError(404, 'WIDGET_NOT_FOUND', 'Widget not found');\n }\n return result;\n}\n\nexport async function runRetentionOnce(ctx: EngineContext): Promise<void> {\n const config = await ctx.getConfig();\n const storage = await ctx.getStorage();\n await runRetention(config, storage);\n}\n","import type {\n DashboardConfig,\n RetentionConfig,\n ServerStorage,\n} from '@rawdash/core';\nimport { selectForDeletion } from '@rawdash/core';\n\nexport const DEFAULT_RETENTION_INTERVAL_MS = 60 * 60 * 1000; // 1 hour\n\nexport function hasPruningPolicy(config: RetentionConfig): boolean {\n return config.maxAge !== undefined || config.maxSize !== undefined;\n}\n\n/**\n * Apply the retention policy in `config` to every connector's stored data.\n * No-op if the config has no pruning policy. Throws an aggregated error if\n * any connector fails.\n */\nexport async function runRetention(\n config: DashboardConfig,\n storage: ServerStorage,\n): Promise<void> {\n const retentionConfig = config.retention;\n if (!retentionConfig || !hasPruningPolicy(retentionConfig)) {\n return;\n }\n\n const nowMs = Date.now();\n\n const results = await Promise.allSettled(\n config.connectors.map(async ({ connector }) => {\n const handle = storage.getStorageHandle(connector.id);\n\n const [events, metrics, distributions] = await Promise.all([\n handle.queryEvents({}),\n handle.queryMetrics({}),\n handle.queryDistributions({}),\n ]);\n\n await applyRetentionToShape(\n events,\n (e) => e.start_ts,\n retentionConfig,\n nowMs,\n (survivors, names) => handle.events(survivors, { names }),\n );\n\n await applyRetentionToShape(\n metrics,\n (m) => m.ts,\n retentionConfig,\n nowMs,\n (survivors, names) => handle.metrics(survivors, { names }),\n );\n\n await applyRetentionToShape(\n distributions,\n (d) => d.ts,\n retentionConfig,\n nowMs,\n (survivors, names) => handle.distributions(survivors, { names }),\n );\n }),\n );\n\n const failures = results.filter(\n (r): r is PromiseRejectedResult => r.status === 'rejected',\n );\n if (failures.length > 0) {\n throw new Error(\n `Retention failed for ${failures.length} connector(s): ${failures.map((f) => String(f.reason)).join('; ')}`,\n );\n }\n}\n\nasync function applyRetentionToShape<T extends { name: string }>(\n rows: T[],\n getTs: (row: T) => number,\n config: RetentionConfig,\n nowMs: number,\n writeSurvivors: (survivors: T[], names: string[]) => Promise<void>,\n): Promise<void> {\n if (rows.length === 0) {\n return;\n }\n\n const sorted = [...rows].sort((a, b) => getTs(b) - getTs(a));\n const toDeleteSet = new Set(selectForDeletion(sorted, getTs, config, nowMs));\n\n if (toDeleteSet.size === 0) {\n return;\n }\n\n const survivors = sorted.filter((r) => !toDeleteSet.has(r));\n const allNames = [...new Set(rows.map((r) => r.name))];\n\n await writeSurvivors(survivors, allNames);\n}\n","import type { DashboardConfig, ServerStorage } from '@rawdash/core';\n\nexport const FULL_SYNC_TIMEOUT_MS = 300_000;\n\n/**\n * Run a full sync across all connectors in the config in parallel via\n * `Promise.allSettled`, so one failure doesn't abort the others. Each\n * connector run is wrapped in a hard timeout (`FULL_SYNC_TIMEOUT_MS`)\n * raced against the connector's own `Promise`, so a connector that\n * ignores `AbortSignal` (or a storage call that hangs) can still not\n * pin sync state in `running` indefinitely.\n *\n * The per-run storage handle is bound to the same `AbortController`, so\n * once the timeout fires every subsequent write call on that handle\n * becomes a no-op. That makes tail writes from a timed-out connector\n * invisible to the next sync even if the connector itself keeps running\n * — see `withAbortSignal` in `@rawdash/core` and the safety-net note in\n * `docs/authoring-a-connector.md`.\n *\n * Transitions storage through `queued` → `running` → `succeeded`/`failed`.\n * The `queued` step is a no-op if the caller (typically `triggerSync`)\n * already marked the run as queued.\n *\n * Returns silently if another sync acquired the `running` lock first.\n */\nexport async function runSync(\n config: DashboardConfig,\n storage: ServerStorage,\n): Promise<void> {\n // Idempotent: if the caller already queued, this returns false and we\n // proceed to markSyncRunning anyway. If nothing queued us, we queue\n // ourselves now so the state machine still goes through `queued`.\n await storage.markSyncQueued();\n const acquired = await storage.markSyncRunning();\n if (!acquired) {\n return;\n }\n const errors: string[] = [];\n await Promise.allSettled(\n config.connectors.map(async ({ connector }) => {\n const controller = new AbortController();\n const handle = storage.getStorageHandle(connector.id, {\n signal: controller.signal,\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const syncPromise = connector.sync(\n { mode: 'full' },\n handle,\n controller.signal,\n );\n const timeoutPromise = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n controller.abort();\n const err = new Error(\n `${connector.id} timed out after ${FULL_SYNC_TIMEOUT_MS}ms`,\n );\n err.name = 'AbortError';\n reject(err);\n }, FULL_SYNC_TIMEOUT_MS);\n });\n const result = await Promise.race([syncPromise, timeoutPromise]);\n if (!result.done) {\n errors.push(\n `${connector.id} did not complete in one chunk (chunked syncs are only supported in cloud)`,\n );\n }\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n errors.push(\n `${connector.id} timed out after ${FULL_SYNC_TIMEOUT_MS}ms`,\n );\n } else {\n errors.push(err instanceof Error ? err.message : String(err));\n }\n } finally {\n if (timer !== undefined) {\n clearTimeout(timer);\n }\n }\n }),\n );\n if (errors.length > 0) {\n await storage.markSyncFailed(errors.join('; '));\n } else {\n await storage.markSyncSucceeded();\n }\n}\n","import type {\n CachedWidget,\n DashboardConfig,\n HealthResponse,\n ServerStorage,\n SyncState,\n TriggerSyncResponse,\n} from '@rawdash/core';\nimport { InMemoryStorage, isSyncActive, resolveWidget } from '@rawdash/core';\n\nimport { runSync } from './sync';\n\nexport interface EngineOptions {\n storage?: ServerStorage;\n}\n\nexport interface Engine {\n getWidget(\n dashboardId: string,\n widgetId: string,\n ): Promise<CachedWidget | undefined>;\n getWidgets(dashboardId: string): Promise<CachedWidget[]>;\n getHealth(): Promise<HealthResponse>;\n getSyncState(): Promise<SyncState>;\n triggerSync(): Promise<TriggerSyncResponse>;\n}\n\nexport function createEngine(\n config: DashboardConfig,\n options: EngineOptions = {},\n): Engine {\n const storage: ServerStorage = options.storage ?? new InMemoryStorage();\n\n return {\n async getWidget(dashboardId, widgetId) {\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n return undefined;\n }\n const widget = dashboard.widgets[widgetId];\n if (!widget) {\n return undefined;\n }\n return resolveWidget(widgetId, widget, config.connectors, storage);\n },\n\n async getWidgets(dashboardId) {\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n return [];\n }\n const entries = Object.entries(dashboard.widgets);\n const resolved = await Promise.all(\n entries.map(([key, widget]) =>\n resolveWidget(key, widget, config.connectors, storage),\n ),\n );\n return resolved.filter((w): w is CachedWidget => w !== undefined);\n },\n\n async getHealth() {\n return { status: 'ok' };\n },\n\n async getSyncState() {\n return storage.getSyncState();\n },\n\n async triggerSync() {\n const state = await storage.getSyncState();\n if (isSyncActive(state.status)) {\n return { queued: false };\n }\n const queued = await storage.markSyncQueued();\n if (!queued) {\n return { queued: false };\n }\n void runSync(config, storage).catch((error) => {\n console.error('Rawdash sync failed', error);\n });\n return { queued: true };\n },\n };\n}\n","export { computeMetric } from '@rawdash/core';\n","export { InMemoryStorage } from '@rawdash/core';\n","export type { EngineContext } from './context';\nexport { RawdashError, isRawdashError } from './errors';\nexport { ROUTES } from './routes';\nexport {\n getHealth,\n getSyncStateHandler,\n getWidget,\n listWidgets,\n runRetentionOnce,\n triggerSync,\n} from './handlers';\nexport { runSync, FULL_SYNC_TIMEOUT_MS } from './sync';\nexport {\n runRetention,\n hasPruningPolicy,\n DEFAULT_RETENTION_INTERVAL_MS,\n} from './retention';\nexport { createEngine } from './engine';\nexport type { Engine, EngineOptions } from './engine';\nexport { computeMetric } from './compute';\nexport { InMemoryStorage } from './storage';\nexport type {\n CachedWidget,\n ConfiguredConnector,\n DashboardConfig,\n HealthResponse,\n ServerStorage,\n SyncState,\n SyncStatus,\n TriggerSyncResponse,\n WidgetsListResponse,\n} from './types';\nexport { isSyncActive, ACTIVE_SYNC_STATUSES } from '@rawdash/core';\n"],"mappings":"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","names":["isSyncActive","resolveWidget","resolveWidget","isSyncActive","InMemoryStorage","isSyncActive"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/routes.ts","../src/handlers.ts","../src/retention.ts","../src/sync.ts","../src/engine.ts","../src/compute.ts","../src/storage.ts","../src/index.ts"],"sourcesContent":["/**\n * Thrown by `@rawdash/server` handlers when a request fails in a way the\n * HTTP adapter should translate to a structured response (e.g. 404, 400).\n * Framework adapters (`@rawdash/hono`, etc.) should catch this and map\n * `status` to the appropriate HTTP status code.\n */\nexport class RawdashError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = 'RawdashError';\n }\n}\n\nexport function isRawdashError(err: unknown): err is RawdashError {\n return err instanceof RawdashError;\n}\n","/**\n * Canonical URL path conventions for the rawdash HTTP wire contract.\n *\n * Framework adapters (`@rawdash/hono`, etc.) and clients\n * (`@rawdash/sdk-client`) should use these constants instead of hard-coding\n * paths, so the contract stays in one place.\n */\nexport const ROUTES = {\n health: '/health',\n syncState: '/sync/state',\n sync: '/sync',\n retention: '/retention/retain',\n widgets: {\n list: (dashboardId: string): string =>\n `/dashboards/${encodeURIComponent(dashboardId)}/widgets`,\n single: (dashboardId: string, widgetId: string): string =>\n `/dashboards/${encodeURIComponent(dashboardId)}/widgets/${encodeURIComponent(widgetId)}`,\n },\n} as const;\n","import type {\n CachedWidget,\n ConnectorRegistry,\n HealthResponse,\n SecretsResolver,\n SyncState,\n TriggerSyncResponse,\n Widget,\n WidgetsListResponse,\n} from '@rawdash/core';\nimport { computeWidgetEtag, isSyncActive, resolveWidget } from '@rawdash/core';\nimport type { DashboardConfig, ServerStorage } from '@rawdash/core';\n\nimport type { EngineContext } from './context';\nimport { RawdashError } from './errors';\nimport { runRetention } from './retention';\nimport { type ConnectorLoggerFactory, runSync } from './sync';\nimport type { WidgetCache } from './widget-cache';\n\nasync function cacheGetSafe(\n cache: WidgetCache,\n dashboardId: string,\n widgetId: string,\n widget: Widget,\n): Promise<CachedWidget | undefined> {\n try {\n return await cache.get({ dashboardId, widgetId, widget });\n } catch (err) {\n console.warn('Rawdash widget cache get failed', err);\n return undefined;\n }\n}\n\nasync function cacheSetSafe(\n cache: WidgetCache,\n dashboardId: string,\n widgetId: string,\n widget: Widget,\n value: CachedWidget,\n): Promise<void> {\n try {\n await cache.set({ dashboardId, widgetId, widget }, value);\n } catch (err) {\n console.warn('Rawdash widget cache set failed', err);\n }\n}\n\nasync function resolveWithCache(\n dashboardId: string,\n widgetId: string,\n widget: Widget,\n connectorNames: readonly string[],\n storage: ServerStorage,\n cache: WidgetCache | undefined,\n): Promise<CachedWidget | undefined> {\n if (cache) {\n const hit = await cacheGetSafe(cache, dashboardId, widgetId, widget);\n if (hit) {\n return hit;\n }\n }\n const fresh = await resolveWidget(\n dashboardId,\n widgetId,\n widget,\n connectorNames,\n storage,\n );\n if (fresh && cache) {\n await cacheSetSafe(cache, dashboardId, widgetId, widget, fresh);\n }\n return fresh;\n}\n\n/**\n * Per-request lookup shape accepted by `triggerSync` in deferred mode.\n * `getConfig` is optional because the trigger handler never calls\n * `runSync` — deployments that delegate the actual sync work to an\n * external runner may not be able to materialize a `DashboardConfig` at\n * request time.\n */\nexport interface DeferredTriggerSyncContext {\n getConfig?: () => DashboardConfig | Promise<DashboardConfig>;\n getStorage: () => ServerStorage | Promise<ServerStorage>;\n}\n\n/**\n * Per-request lookup shape accepted by `triggerSync` in in-process\n * mode. `getConfig` is required because the trigger handler kicks off\n * `runSync(config, storage)` in the background. `connectorRegistry` is\n * required so the background runner can instantiate connector\n * implementations on demand from the declarative `DashboardConfig`.\n */\nexport interface InProcessTriggerSyncContext {\n getConfig: () => DashboardConfig | Promise<DashboardConfig>;\n getStorage: () => ServerStorage | Promise<ServerStorage>;\n connectorRegistry: ConnectorRegistry;\n secretsResolver?: SecretsResolver;\n loggerFactory?: ConnectorLoggerFactory;\n}\n\n/**\n * @deprecated Prefer `InProcessTriggerSyncContext` /\n * `DeferredTriggerSyncContext`. Retained as the union for callers that\n * need a single type covering both modes.\n */\nexport type TriggerSyncContext = DeferredTriggerSyncContext;\n\nexport type TriggerSyncMode = 'in-process' | 'deferred';\n\nexport interface TriggerSyncOptions {\n /**\n * `'in-process'` (default): the trigger handler also runs the sync in\n * the background by invoking `runSync(config, storage)`. Suitable for\n * self-hosted, single-process deployments.\n *\n * `'deferred'`: the trigger handler only persists the `queued`\n * transition and returns. The `running → succeeded/failed` transitions\n * are the responsibility of an external runner (e.g. a queue consumer\n * worker), which must drive the storage accordingly.\n */\n mode?: TriggerSyncMode;\n}\n\n/**\n * Framework-agnostic request handlers for the rawdash wire contract.\n *\n * Each function takes an `EngineContext` (providing per-request access to\n * the config + storage) and returns the response body, or throws a\n * `RawdashError` on a client-visible failure. HTTP adapters\n * (`@rawdash/hono`, etc.) wrap these in their framework's request/response\n * cycle and translate `RawdashError` into a structured error response.\n */\n\nexport function getHealth(): HealthResponse {\n return { status: 'ok' };\n}\n\nexport async function getSyncStateHandler(\n ctx: EngineContext,\n): Promise<SyncState> {\n const storage = await ctx.getStorage();\n return storage.getSyncState();\n}\n\nexport function triggerSync(\n ctx: InProcessTriggerSyncContext,\n opts?: { mode?: 'in-process' },\n): Promise<TriggerSyncResponse>;\nexport function triggerSync(\n ctx: DeferredTriggerSyncContext,\n opts: { mode: 'deferred' },\n): Promise<TriggerSyncResponse>;\nexport async function triggerSync(\n ctx: InProcessTriggerSyncContext | DeferredTriggerSyncContext,\n opts: TriggerSyncOptions = {},\n): Promise<TriggerSyncResponse> {\n const mode: TriggerSyncMode = opts.mode ?? 'in-process';\n const storage = await ctx.getStorage();\n const state = await storage.getSyncState();\n if (isSyncActive(state.status)) {\n return { queued: false };\n }\n let config: DashboardConfig | undefined;\n if (mode === 'in-process') {\n if (!ctx.getConfig) {\n throw new Error(\n 'triggerSync: getConfig is required when mode is \"in-process\"',\n );\n }\n config = await ctx.getConfig();\n }\n const queued = await storage.markSyncQueued();\n if (!queued) {\n return { queued: false };\n }\n if (mode === 'deferred') {\n return { queued: true };\n }\n const inProcessCtx = ctx as InProcessTriggerSyncContext;\n void runSync(config!, storage, {\n connectorRegistry: inProcessCtx.connectorRegistry,\n secretsResolver: inProcessCtx.secretsResolver,\n loggerFactory: inProcessCtx.loggerFactory,\n }).catch((err) => {\n console.error('Rawdash sync failed', err);\n });\n return { queued: true };\n}\n\nexport async function listWidgets(\n ctx: EngineContext,\n dashboardId: string,\n cache?: WidgetCache,\n): Promise<WidgetsListResponse> {\n const config = await ctx.getConfig();\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n throw new RawdashError(404, 'DASHBOARD_NOT_FOUND', 'Dashboard not found');\n }\n const storage = await ctx.getStorage();\n const connectorNames = config.connectors.map((c) => c.name);\n const entries = Object.entries(dashboard.widgets);\n const resolved = await Promise.all(\n entries.map(([key, widget]) =>\n resolveWithCache(\n dashboardId,\n key,\n widget,\n connectorNames,\n storage,\n cache,\n ),\n ),\n );\n const widgets = resolved.filter((w): w is CachedWidget => w !== undefined);\n return { widgets };\n}\n\nexport interface GetWidgetOptions {\n cache?: WidgetCache;\n ifNoneMatch?: string;\n}\n\nexport type GetWidgetResult =\n | { status: 'ok'; etag: string | undefined; widget: CachedWidget }\n | { status: 'not-modified'; etag: string };\n\nexport async function getWidget(\n ctx: EngineContext,\n dashboardId: string,\n widgetId: string,\n opts: GetWidgetOptions = {},\n): Promise<GetWidgetResult> {\n const { cache, ifNoneMatch } = opts;\n const config = await ctx.getConfig();\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n throw new RawdashError(404, 'DASHBOARD_NOT_FOUND', 'Dashboard not found');\n }\n const widget = dashboard.widgets[widgetId];\n if (!widget) {\n throw new RawdashError(404, 'WIDGET_NOT_FOUND', 'Widget not found');\n }\n const storage = await ctx.getStorage();\n const connectorNames = config.connectors.map((c) => c.name);\n const connectorId =\n widget.kind === 'status' ? widget.source : widget.metric.connectorId;\n if (!connectorNames.includes(connectorId)) {\n throw new RawdashError(404, 'WIDGET_NOT_FOUND', 'Widget not found');\n }\n\n if (ifNoneMatch) {\n const handle = storage.getStorageHandle(connectorId);\n const health = (await handle.getHealth?.()) ?? null;\n if (health?.lastSyncAt) {\n const probeEtag = computeWidgetEtag(health.lastSyncAt, widget);\n if (probeEtag === ifNoneMatch) {\n return { status: 'not-modified', etag: probeEtag };\n }\n }\n }\n\n const result = await resolveWithCache(\n dashboardId,\n widgetId,\n widget,\n connectorNames,\n storage,\n cache,\n );\n if (!result) {\n throw new RawdashError(404, 'WIDGET_NOT_FOUND', 'Widget not found');\n }\n const etag = result.cachedAt\n ? computeWidgetEtag(result.cachedAt, widget)\n : undefined;\n return { status: 'ok', etag, widget: result };\n}\n\nexport async function runRetentionOnce(ctx: EngineContext): Promise<void> {\n const config = await ctx.getConfig();\n const storage = await ctx.getStorage();\n await runRetention(config, storage);\n}\n","import type {\n DashboardConfig,\n RetentionConfig,\n ServerStorage,\n} from '@rawdash/core';\nimport { selectForDeletion } from '@rawdash/core';\n\nexport const DEFAULT_RETENTION_INTERVAL_MS = 60 * 60 * 1000; // 1 hour\n\nexport function hasPruningPolicy(config: RetentionConfig): boolean {\n return config.maxAge !== undefined || config.maxSize !== undefined;\n}\n\n/**\n * Apply the retention policy in `config` to every connector's stored data.\n * No-op if the config has no pruning policy. Throws an aggregated error if\n * any connector fails.\n */\nexport async function runRetention(\n config: DashboardConfig,\n storage: ServerStorage,\n): Promise<void> {\n const retentionConfig = config.retention;\n if (!retentionConfig || !hasPruningPolicy(retentionConfig)) {\n return;\n }\n\n const nowMs = Date.now();\n\n const results = await Promise.allSettled(\n config.connectors.map(async (entry) => {\n const handle = storage.getStorageHandle(entry.name);\n\n const [events, metrics, distributions] = await Promise.all([\n handle.queryEvents({}),\n handle.queryMetrics({}),\n handle.queryDistributions({}),\n ]);\n\n await applyRetentionToShape(\n events,\n (e) => e.start_ts,\n retentionConfig,\n nowMs,\n (survivors, names) => handle.events(survivors, { names }),\n );\n\n await applyRetentionToShape(\n metrics,\n (m) => m.ts,\n retentionConfig,\n nowMs,\n (survivors, names) => handle.metrics(survivors, { names }),\n );\n\n await applyRetentionToShape(\n distributions,\n (d) => d.ts,\n retentionConfig,\n nowMs,\n (survivors, names) => handle.distributions(survivors, { names }),\n );\n }),\n );\n\n const failures = results.filter(\n (r): r is PromiseRejectedResult => r.status === 'rejected',\n );\n if (failures.length > 0) {\n throw new Error(\n `Retention failed for ${failures.length} connector(s): ${failures.map((f) => String(f.reason)).join('; ')}`,\n );\n }\n}\n\nasync function applyRetentionToShape<T extends { name: string }>(\n rows: T[],\n getTs: (row: T) => number,\n config: RetentionConfig,\n nowMs: number,\n writeSurvivors: (survivors: T[], names: string[]) => Promise<void>,\n): Promise<void> {\n if (rows.length === 0) {\n return;\n }\n\n const sorted = [...rows].sort((a, b) => getTs(b) - getTs(a));\n const toDeleteSet = new Set(selectForDeletion(sorted, getTs, config, nowMs));\n\n if (toDeleteSet.size === 0) {\n return;\n }\n\n const survivors = sorted.filter((r) => !toDeleteSet.has(r));\n const allNames = [...new Set(rows.map((r) => r.name))];\n\n await writeSurvivors(survivors, allNames);\n}\n","import type {\n ConnectorLogger,\n ConnectorRegistry,\n DashboardConfig,\n SecretsResolver,\n ServerStorage,\n} from '@rawdash/core';\nimport {\n computeConnectorBackfill,\n createDefaultConnectorLogger,\n instantiateConnector,\n} from '@rawdash/core';\n\nexport const FULL_SYNC_TIMEOUT_MS = 300_000;\nexport const FULL_SYNC_MAX_CHUNKS = 1_000;\nexport const BACKFILL_BUFFER_MS = 86_400_000;\n\nexport type ConnectorLoggerFactory = (scope: string) => ConnectorLogger;\n\nexport interface RunSyncOptions {\n connectorRegistry: ConnectorRegistry;\n secretsResolver?: SecretsResolver;\n /**\n * Build a logger for the runner and for each connector instance. Called\n * with `'runner'` for the start/settled envelopes and with each\n * `connector.name` for the per-connector progress logs. Defaults to\n * {@link createDefaultConnectorLogger}, which writes a single-line\n * `[scope] event key=value …` record to stdout/stderr.\n */\n loggerFactory?: ConnectorLoggerFactory;\n}\n\n/**\n * Run a full sync across all connectors in the config in parallel via\n * `Promise.allSettled`, so one failure doesn't abort the others. Each\n * connector run is wrapped in a hard timeout (`FULL_SYNC_TIMEOUT_MS`)\n * raced against the connector's own `Promise`, so a connector that\n * ignores `AbortSignal` (or a storage call that hangs) can still not\n * pin sync state in `running` indefinitely.\n *\n * Connectors that return `{ done: false, cursor }` are looped in-process,\n * threading the cursor back into the next `sync` call until `done: true`\n * or the shared timeout / `FULL_SYNC_MAX_CHUNKS` cap fires. Cloud\n * deployments layer cross-restart cursor persistence on top of the same\n * contract; the OSS runner is the trivial in-process case.\n *\n * The per-run storage handle is bound to the same `AbortController`, so\n * once the timeout fires every subsequent write call on that handle\n * becomes a no-op. That makes tail writes from a timed-out connector\n * invisible to the next sync even if the connector itself keeps running\n * — see `withAbortSignal` in `@rawdash/core` and the safety-net note in\n * `docs/authoring-a-connector.md`.\n *\n * Transitions storage through `queued` → `running` → `succeeded`/`failed`.\n * The `queued` step is a no-op if the caller (typically `triggerSync`)\n * already marked the run as queued.\n *\n * Returns silently if another sync acquired the `running` lock first.\n */\nexport async function runSync(\n config: DashboardConfig,\n storage: ServerStorage,\n options: RunSyncOptions,\n): Promise<void> {\n await storage.markSyncQueued();\n if (typeof storage.markSyncRunning === 'function') {\n const acquired = await storage.markSyncRunning();\n if (!acquired) {\n return;\n }\n }\n const errors: string[] = [];\n const backfill = computeConnectorBackfill(config);\n const now = Date.now();\n const rawLoggerFactory: ConnectorLoggerFactory =\n options.loggerFactory ??\n ((scope) => createDefaultConnectorLogger({ scope }));\n const safeLogger = (scope: string): ConnectorLogger => {\n let inner: ConnectorLogger;\n try {\n inner = rawLoggerFactory(scope);\n } catch (err) {\n console.warn(\n `[runner] loggerFactory threw for scope=${scope}; falling back to default`,\n err,\n );\n inner = createDefaultConnectorLogger({ scope });\n }\n return {\n info(event, fields) {\n try {\n inner.info(event, fields);\n } catch (err) {\n console.warn(\n `[runner] logger.info threw for scope=${scope} event=${event}`,\n err,\n );\n }\n },\n warn(event, fields) {\n try {\n inner.warn(event, fields);\n } catch (err) {\n console.warn(\n `[runner] logger.warn threw for scope=${scope} event=${event}`,\n err,\n );\n }\n },\n };\n };\n const runnerLogger = safeLogger('runner');\n await Promise.allSettled(\n config.connectors.map(async (entry) => {\n if (entry.enabled === false) {\n return;\n }\n const scope = backfill.get(entry.name);\n if (!scope) {\n return;\n }\n const controller = new AbortController();\n const handle = storage.getStorageHandle(entry.name, {\n signal: controller.signal,\n });\n const connectorLogger = safeLogger(entry.name);\n const syncStart = Date.now();\n let timer: ReturnType<typeof setTimeout> | undefined;\n let status: 'succeeded' | 'failed' = 'succeeded';\n let failureReason: string | undefined;\n try {\n const connector = instantiateConnector(\n entry,\n options.connectorRegistry,\n options.secretsResolver,\n connectorLogger,\n );\n const timeoutPromise = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n controller.abort();\n const err = new Error(\n `${entry.name} timed out after ${FULL_SYNC_TIMEOUT_MS}ms`,\n );\n err.name = 'AbortError';\n reject(err);\n }, FULL_SYNC_TIMEOUT_MS);\n });\n\n const resources: ReadonlySet<string> = new Set(scope.keys());\n\n let maxWindowMs: number | undefined;\n for (const [, { requiredWindowMs }] of scope.entries()) {\n if (requiredWindowMs === undefined) {\n continue;\n }\n if (maxWindowMs === undefined || requiredWindowMs > maxWindowMs) {\n maxWindowMs = requiredWindowMs;\n }\n }\n const since =\n maxWindowMs !== undefined\n ? new Date(now - maxWindowMs - BACKFILL_BUFFER_MS).toISOString()\n : undefined;\n\n runnerLogger.info('sync started', {\n connector: entry.name,\n resources: Array.from(resources),\n mode: 'full',\n since,\n });\n\n let cursor: unknown = undefined;\n let chunks = 0;\n while (true) {\n chunks += 1;\n if (chunks > FULL_SYNC_MAX_CHUNKS) {\n controller.abort();\n throw new Error(\n `${entry.name} exceeded ${FULL_SYNC_MAX_CHUNKS} sync chunks without completing`,\n );\n }\n const syncPromise = connector.sync(\n { mode: 'full', since, cursor, resources },\n handle,\n controller.signal,\n );\n const result = await Promise.race([syncPromise, timeoutPromise]);\n if (result.done) {\n break;\n }\n cursor = result.cursor;\n }\n } catch (err) {\n status = 'failed';\n if (err instanceof Error && err.name === 'AbortError') {\n failureReason = `${entry.name} timed out after ${FULL_SYNC_TIMEOUT_MS}ms`;\n } else {\n failureReason = err instanceof Error ? err.message : String(err);\n }\n errors.push(failureReason);\n } finally {\n if (timer !== undefined) {\n clearTimeout(timer);\n }\n runnerLogger.info('sync settled', {\n connector: entry.name,\n status,\n duration_ms: Date.now() - syncStart,\n error: failureReason,\n });\n }\n }),\n );\n if (errors.length > 0) {\n await storage.markSyncFailed(errors.join('; '));\n } else {\n await storage.markSyncSucceeded();\n }\n}\n","import type {\n CachedWidget,\n ConnectorRegistry,\n DashboardConfig,\n HealthResponse,\n SecretsResolver,\n ServerStorage,\n SyncState,\n TriggerSyncResponse,\n} from '@rawdash/core';\nimport { InMemoryStorage, isSyncActive, resolveWidget } from '@rawdash/core';\n\nimport { type ConnectorLoggerFactory, runSync } from './sync';\n\nexport interface EngineOptions {\n storage?: ServerStorage;\n connectorRegistry?: ConnectorRegistry;\n secretsResolver?: SecretsResolver;\n loggerFactory?: ConnectorLoggerFactory;\n}\n\nexport interface Engine {\n getWidget(\n dashboardId: string,\n widgetId: string,\n ): Promise<CachedWidget | undefined>;\n getWidgets(dashboardId: string): Promise<CachedWidget[]>;\n getHealth(): Promise<HealthResponse>;\n getSyncState(): Promise<SyncState>;\n triggerSync(): Promise<TriggerSyncResponse>;\n}\n\nexport function createEngine(\n config: DashboardConfig,\n options: EngineOptions = {},\n): Engine {\n const storage: ServerStorage = options.storage ?? new InMemoryStorage();\n const connectorNames = config.connectors.map((c) => c.name);\n\n return {\n async getWidget(dashboardId, widgetId) {\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n return undefined;\n }\n const widget = dashboard.widgets[widgetId];\n if (!widget) {\n return undefined;\n }\n return resolveWidget(\n dashboardId,\n widgetId,\n widget,\n connectorNames,\n storage,\n );\n },\n\n async getWidgets(dashboardId) {\n const dashboard = config.dashboards[dashboardId];\n if (!dashboard) {\n return [];\n }\n const entries = Object.entries(dashboard.widgets);\n const resolved = await Promise.all(\n entries.map(([key, widget]) =>\n resolveWidget(dashboardId, key, widget, connectorNames, storage),\n ),\n );\n return resolved.filter((w): w is CachedWidget => w !== undefined);\n },\n\n async getHealth() {\n return { status: 'ok' };\n },\n\n async getSyncState() {\n return storage.getSyncState();\n },\n\n async triggerSync() {\n if (!options.connectorRegistry) {\n throw new Error(\n 'createEngine: connectorRegistry is required to triggerSync',\n );\n }\n const state = await storage.getSyncState();\n if (isSyncActive(state.status)) {\n return { queued: false };\n }\n const queued = await storage.markSyncQueued();\n if (!queued) {\n return { queued: false };\n }\n void runSync(config, storage, {\n connectorRegistry: options.connectorRegistry,\n secretsResolver: options.secretsResolver,\n loggerFactory: options.loggerFactory,\n }).catch((error) => {\n console.error('Rawdash sync failed', error);\n });\n return { queued: true };\n },\n };\n}\n","export { computeMetric } from '@rawdash/core';\n","export { InMemoryStorage } from '@rawdash/core';\n","export type { EngineContext } from './context';\nexport { RawdashError, isRawdashError } from './errors';\nexport { ROUTES } from './routes';\nexport {\n getHealth,\n getSyncStateHandler,\n getWidget,\n listWidgets,\n runRetentionOnce,\n triggerSync,\n} from './handlers';\nexport type {\n DeferredTriggerSyncContext,\n GetWidgetOptions,\n GetWidgetResult,\n InProcessTriggerSyncContext,\n TriggerSyncContext,\n TriggerSyncMode,\n TriggerSyncOptions,\n} from './handlers';\nexport type { WidgetCache, WidgetCacheKey } from './widget-cache';\nexport { runSync, FULL_SYNC_TIMEOUT_MS, FULL_SYNC_MAX_CHUNKS } from './sync';\nexport type { ConnectorLoggerFactory, RunSyncOptions } from './sync';\nexport {\n runRetention,\n hasPruningPolicy,\n DEFAULT_RETENTION_INTERVAL_MS,\n} from './retention';\nexport { createEngine } from './engine';\nexport type { Engine, EngineOptions } from './engine';\nexport { computeMetric } from './compute';\nexport { InMemoryStorage } from './storage';\nexport type {\n CachedWidget,\n ConfiguredConnector,\n ConnectorHealth,\n DashboardConfig,\n HealthResponse,\n ServerStorage,\n SyncState,\n SyncStatus,\n TriggerSyncResponse,\n Widget,\n WidgetSyncState,\n WidgetsListResponse,\n} from './types';\nexport { isSyncActive, ACTIVE_SYNC_STATUSES } from '@rawdash/core';\nexport { instantiateConnector } from '@rawdash/core';\nexport type {\n ConnectorClass,\n ConnectorRegistry,\n SecretsResolver,\n} from '@rawdash/core';\n"],"mappings":";AAMO,IAAM,eAAN,cAA2B,MAAM;AAAA,EACtC,YACW,QACA,MACT,SACA;AACA,UAAM,OAAO;AAJJ;AACA;AAIT,SAAK,OAAO;AAAA,EACd;AAAA,EANW;AAAA,EACA;AAMb;AAEO,SAAS,eAAe,KAAmC;AAChE,SAAO,eAAe;AACxB;;;ACZO,IAAM,SAAS;AAAA,EACpB,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AAAA,IACP,MAAM,CAAC,gBACL,eAAe,mBAAmB,WAAW,CAAC;AAAA,IAChD,QAAQ,CAAC,aAAqB,aAC5B,eAAe,mBAAmB,WAAW,CAAC,YAAY,mBAAmB,QAAQ,CAAC;AAAA,EAC1F;AACF;;;ACRA,SAAS,mBAAmB,cAAc,qBAAqB;;;ACL/D,SAAS,yBAAyB;AAE3B,IAAM,gCAAgC,KAAK,KAAK;AAEhD,SAAS,iBAAiB,QAAkC;AACjE,SAAO,OAAO,WAAW,UAAa,OAAO,YAAY;AAC3D;AAOA,eAAsB,aACpB,QACA,SACe;AACf,QAAM,kBAAkB,OAAO;AAC/B,MAAI,CAAC,mBAAmB,CAAC,iBAAiB,eAAe,GAAG;AAC1D;AAAA,EACF;AAEA,QAAM,QAAQ,KAAK,IAAI;AAEvB,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,OAAO,WAAW,IAAI,OAAO,UAAU;AACrC,YAAM,SAAS,QAAQ,iBAAiB,MAAM,IAAI;AAElD,YAAM,CAAC,QAAQ,SAAS,aAAa,IAAI,MAAM,QAAQ,IAAI;AAAA,QACzD,OAAO,YAAY,CAAC,CAAC;AAAA,QACrB,OAAO,aAAa,CAAC,CAAC;AAAA,QACtB,OAAO,mBAAmB,CAAC,CAAC;AAAA,MAC9B,CAAC;AAED,YAAM;AAAA,QACJ;AAAA,QACA,CAAC,MAAM,EAAE;AAAA,QACT;AAAA,QACA;AAAA,QACA,CAAC,WAAW,UAAU,OAAO,OAAO,WAAW,EAAE,MAAM,CAAC;AAAA,MAC1D;AAEA,YAAM;AAAA,QACJ;AAAA,QACA,CAAC,MAAM,EAAE;AAAA,QACT;AAAA,QACA;AAAA,QACA,CAAC,WAAW,UAAU,OAAO,QAAQ,WAAW,EAAE,MAAM,CAAC;AAAA,MAC3D;AAEA,YAAM;AAAA,QACJ;AAAA,QACA,CAAC,MAAM,EAAE;AAAA,QACT;AAAA,QACA;AAAA,QACA,CAAC,WAAW,UAAU,OAAO,cAAc,WAAW,EAAE,MAAM,CAAC;AAAA,MACjE;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,WAAW,QAAQ;AAAA,IACvB,CAAC,MAAkC,EAAE,WAAW;AAAA,EAClD;AACA,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,IAAI;AAAA,MACR,wBAAwB,SAAS,MAAM,kBAAkB,SAAS,IAAI,CAAC,MAAM,OAAO,EAAE,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,IAC3G;AAAA,EACF;AACF;AAEA,eAAe,sBACb,MACA,OACA,QACA,OACA,gBACe;AACf,MAAI,KAAK,WAAW,GAAG;AACrB;AAAA,EACF;AAEA,QAAM,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,MAAM,CAAC,CAAC;AAC3D,QAAM,cAAc,IAAI,IAAI,kBAAkB,QAAQ,OAAO,QAAQ,KAAK,CAAC;AAE3E,MAAI,YAAY,SAAS,GAAG;AAC1B;AAAA,EACF;AAEA,QAAM,YAAY,OAAO,OAAO,CAAC,MAAM,CAAC,YAAY,IAAI,CAAC,CAAC;AAC1D,QAAM,WAAW,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAErD,QAAM,eAAe,WAAW,QAAQ;AAC1C;;;AC1FA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAC7B,IAAM,qBAAqB;AA4ClC,eAAsB,QACpB,QACA,SACA,SACe;AACf,QAAM,QAAQ,eAAe;AAC7B,MAAI,OAAO,QAAQ,oBAAoB,YAAY;AACjD,UAAM,WAAW,MAAM,QAAQ,gBAAgB;AAC/C,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAAA,EACF;AACA,QAAM,SAAmB,CAAC;AAC1B,QAAM,WAAW,yBAAyB,MAAM;AAChD,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,mBACJ,QAAQ,kBACP,CAAC,UAAU,6BAA6B,EAAE,MAAM,CAAC;AACpD,QAAM,aAAa,CAAC,UAAmC;AACrD,QAAI;AACJ,QAAI;AACF,cAAQ,iBAAiB,KAAK;AAAA,IAChC,SAAS,KAAK;AACZ,cAAQ;AAAA,QACN,0CAA0C,KAAK;AAAA,QAC/C;AAAA,MACF;AACA,cAAQ,6BAA6B,EAAE,MAAM,CAAC;AAAA,IAChD;AACA,WAAO;AAAA,MACL,KAAK,OAAO,QAAQ;AAClB,YAAI;AACF,gBAAM,KAAK,OAAO,MAAM;AAAA,QAC1B,SAAS,KAAK;AACZ,kBAAQ;AAAA,YACN,wCAAwC,KAAK,UAAU,KAAK;AAAA,YAC5D;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK,OAAO,QAAQ;AAClB,YAAI;AACF,gBAAM,KAAK,OAAO,MAAM;AAAA,QAC1B,SAAS,KAAK;AACZ,kBAAQ;AAAA,YACN,wCAAwC,KAAK,UAAU,KAAK;AAAA,YAC5D;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,QAAM,eAAe,WAAW,QAAQ;AACxC,QAAM,QAAQ;AAAA,IACZ,OAAO,WAAW,IAAI,OAAO,UAAU;AACrC,UAAI,MAAM,YAAY,OAAO;AAC3B;AAAA,MACF;AACA,YAAM,QAAQ,SAAS,IAAI,MAAM,IAAI;AACrC,UAAI,CAAC,OAAO;AACV;AAAA,MACF;AACA,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,SAAS,QAAQ,iBAAiB,MAAM,MAAM;AAAA,QAClD,QAAQ,WAAW;AAAA,MACrB,CAAC;AACD,YAAM,kBAAkB,WAAW,MAAM,IAAI;AAC7C,YAAM,YAAY,KAAK,IAAI;AAC3B,UAAI;AACJ,UAAI,SAAiC;AACrC,UAAI;AACJ,UAAI;AACF,cAAM,YAAY;AAAA,UAChB;AAAA,UACA,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR;AAAA,QACF;AACA,cAAM,iBAAiB,IAAI,QAAe,CAAC,UAAU,WAAW;AAC9D,kBAAQ,WAAW,MAAM;AACvB,uBAAW,MAAM;AACjB,kBAAM,MAAM,IAAI;AAAA,cACd,GAAG,MAAM,IAAI,oBAAoB,oBAAoB;AAAA,YACvD;AACA,gBAAI,OAAO;AACX,mBAAO,GAAG;AAAA,UACZ,GAAG,oBAAoB;AAAA,QACzB,CAAC;AAED,cAAM,YAAiC,IAAI,IAAI,MAAM,KAAK,CAAC;AAE3D,YAAI;AACJ,mBAAW,CAAC,EAAE,EAAE,iBAAiB,CAAC,KAAK,MAAM,QAAQ,GAAG;AACtD,cAAI,qBAAqB,QAAW;AAClC;AAAA,UACF;AACA,cAAI,gBAAgB,UAAa,mBAAmB,aAAa;AAC/D,0BAAc;AAAA,UAChB;AAAA,QACF;AACA,cAAM,QACJ,gBAAgB,SACZ,IAAI,KAAK,MAAM,cAAc,kBAAkB,EAAE,YAAY,IAC7D;AAEN,qBAAa,KAAK,gBAAgB;AAAA,UAChC,WAAW,MAAM;AAAA,UACjB,WAAW,MAAM,KAAK,SAAS;AAAA,UAC/B,MAAM;AAAA,UACN;AAAA,QACF,CAAC;AAED,YAAI,SAAkB;AACtB,YAAI,SAAS;AACb,eAAO,MAAM;AACX,oBAAU;AACV,cAAI,SAAS,sBAAsB;AACjC,uBAAW,MAAM;AACjB,kBAAM,IAAI;AAAA,cACR,GAAG,MAAM,IAAI,aAAa,oBAAoB;AAAA,YAChD;AAAA,UACF;AACA,gBAAM,cAAc,UAAU;AAAA,YAC5B,EAAE,MAAM,QAAQ,OAAO,QAAQ,UAAU;AAAA,YACzC;AAAA,YACA,WAAW;AAAA,UACb;AACA,gBAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,aAAa,cAAc,CAAC;AAC/D,cAAI,OAAO,MAAM;AACf;AAAA,UACF;AACA,mBAAS,OAAO;AAAA,QAClB;AAAA,MACF,SAAS,KAAK;AACZ,iBAAS;AACT,YAAI,eAAe,SAAS,IAAI,SAAS,cAAc;AACrD,0BAAgB,GAAG,MAAM,IAAI,oBAAoB,oBAAoB;AAAA,QACvE,OAAO;AACL,0BAAgB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACjE;AACA,eAAO,KAAK,aAAa;AAAA,MAC3B,UAAE;AACA,YAAI,UAAU,QAAW;AACvB,uBAAa,KAAK;AAAA,QACpB;AACA,qBAAa,KAAK,gBAAgB;AAAA,UAChC,WAAW,MAAM;AAAA,UACjB;AAAA,UACA,aAAa,KAAK,IAAI,IAAI;AAAA,UAC1B,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AACA,MAAI,OAAO,SAAS,GAAG;AACrB,UAAM,QAAQ,eAAe,OAAO,KAAK,IAAI,CAAC;AAAA,EAChD,OAAO;AACL,UAAM,QAAQ,kBAAkB;AAAA,EAClC;AACF;;;AFvMA,eAAe,aACb,OACA,aACA,UACA,QACmC;AACnC,MAAI;AACF,WAAO,MAAM,MAAM,IAAI,EAAE,aAAa,UAAU,OAAO,CAAC;AAAA,EAC1D,SAAS,KAAK;AACZ,YAAQ,KAAK,mCAAmC,GAAG;AACnD,WAAO;AAAA,EACT;AACF;AAEA,eAAe,aACb,OACA,aACA,UACA,QACA,OACe;AACf,MAAI;AACF,UAAM,MAAM,IAAI,EAAE,aAAa,UAAU,OAAO,GAAG,KAAK;AAAA,EAC1D,SAAS,KAAK;AACZ,YAAQ,KAAK,mCAAmC,GAAG;AAAA,EACrD;AACF;AAEA,eAAe,iBACb,aACA,UACA,QACA,gBACA,SACA,OACmC;AACnC,MAAI,OAAO;AACT,UAAM,MAAM,MAAM,aAAa,OAAO,aAAa,UAAU,MAAM;AACnE,QAAI,KAAK;AACP,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,QAAQ,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,SAAS,OAAO;AAClB,UAAM,aAAa,OAAO,aAAa,UAAU,QAAQ,KAAK;AAAA,EAChE;AACA,SAAO;AACT;AA8DO,SAAS,YAA4B;AAC1C,SAAO,EAAE,QAAQ,KAAK;AACxB;AAEA,eAAsB,oBACpB,KACoB;AACpB,QAAM,UAAU,MAAM,IAAI,WAAW;AACrC,SAAO,QAAQ,aAAa;AAC9B;AAUA,eAAsB,YACpB,KACA,OAA2B,CAAC,GACE;AAC9B,QAAM,OAAwB,KAAK,QAAQ;AAC3C,QAAM,UAAU,MAAM,IAAI,WAAW;AACrC,QAAM,QAAQ,MAAM,QAAQ,aAAa;AACzC,MAAI,aAAa,MAAM,MAAM,GAAG;AAC9B,WAAO,EAAE,QAAQ,MAAM;AAAA,EACzB;AACA,MAAI;AACJ,MAAI,SAAS,cAAc;AACzB,QAAI,CAAC,IAAI,WAAW;AAClB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,aAAS,MAAM,IAAI,UAAU;AAAA,EAC/B;AACA,QAAM,SAAS,MAAM,QAAQ,eAAe;AAC5C,MAAI,CAAC,QAAQ;AACX,WAAO,EAAE,QAAQ,MAAM;AAAA,EACzB;AACA,MAAI,SAAS,YAAY;AACvB,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AACA,QAAM,eAAe;AACrB,OAAK,QAAQ,QAAS,SAAS;AAAA,IAC7B,mBAAmB,aAAa;AAAA,IAChC,iBAAiB,aAAa;AAAA,IAC9B,eAAe,aAAa;AAAA,EAC9B,CAAC,EAAE,MAAM,CAAC,QAAQ;AAChB,YAAQ,MAAM,uBAAuB,GAAG;AAAA,EAC1C,CAAC;AACD,SAAO,EAAE,QAAQ,KAAK;AACxB;AAEA,eAAsB,YACpB,KACA,aACA,OAC8B;AAC9B,QAAM,SAAS,MAAM,IAAI,UAAU;AACnC,QAAM,YAAY,OAAO,WAAW,WAAW;AAC/C,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,aAAa,KAAK,uBAAuB,qBAAqB;AAAA,EAC1E;AACA,QAAM,UAAU,MAAM,IAAI,WAAW;AACrC,QAAM,iBAAiB,OAAO,WAAW,IAAI,CAAC,MAAM,EAAE,IAAI;AAC1D,QAAM,UAAU,OAAO,QAAQ,UAAU,OAAO;AAChD,QAAM,WAAW,MAAM,QAAQ;AAAA,IAC7B,QAAQ;AAAA,MAAI,CAAC,CAAC,KAAK,MAAM,MACvB;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,QAAM,UAAU,SAAS,OAAO,CAAC,MAAyB,MAAM,MAAS;AACzE,SAAO,EAAE,QAAQ;AACnB;AAWA,eAAsB,UACpB,KACA,aACA,UACA,OAAyB,CAAC,GACA;AAC1B,QAAM,EAAE,OAAO,YAAY,IAAI;AAC/B,QAAM,SAAS,MAAM,IAAI,UAAU;AACnC,QAAM,YAAY,OAAO,WAAW,WAAW;AAC/C,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,aAAa,KAAK,uBAAuB,qBAAqB;AAAA,EAC1E;AACA,QAAM,SAAS,UAAU,QAAQ,QAAQ;AACzC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,aAAa,KAAK,oBAAoB,kBAAkB;AAAA,EACpE;AACA,QAAM,UAAU,MAAM,IAAI,WAAW;AACrC,QAAM,iBAAiB,OAAO,WAAW,IAAI,CAAC,MAAM,EAAE,IAAI;AAC1D,QAAM,cACJ,OAAO,SAAS,WAAW,OAAO,SAAS,OAAO,OAAO;AAC3D,MAAI,CAAC,eAAe,SAAS,WAAW,GAAG;AACzC,UAAM,IAAI,aAAa,KAAK,oBAAoB,kBAAkB;AAAA,EACpE;AAEA,MAAI,aAAa;AACf,UAAM,SAAS,QAAQ,iBAAiB,WAAW;AACnD,UAAM,SAAU,MAAM,OAAO,YAAY,KAAM;AAC/C,QAAI,QAAQ,YAAY;AACtB,YAAM,YAAY,kBAAkB,OAAO,YAAY,MAAM;AAC7D,UAAI,cAAc,aAAa;AAC7B,eAAO,EAAE,QAAQ,gBAAgB,MAAM,UAAU;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,aAAa,KAAK,oBAAoB,kBAAkB;AAAA,EACpE;AACA,QAAM,OAAO,OAAO,WAChB,kBAAkB,OAAO,UAAU,MAAM,IACzC;AACJ,SAAO,EAAE,QAAQ,MAAM,MAAM,QAAQ,OAAO;AAC9C;AAEA,eAAsB,iBAAiB,KAAmC;AACxE,QAAM,SAAS,MAAM,IAAI,UAAU;AACnC,QAAM,UAAU,MAAM,IAAI,WAAW;AACrC,QAAM,aAAa,QAAQ,OAAO;AACpC;;;AGlRA,SAAS,iBAAiB,gBAAAA,eAAc,iBAAAC,sBAAqB;AAsBtD,SAAS,aACd,QACA,UAAyB,CAAC,GAClB;AACR,QAAM,UAAyB,QAAQ,WAAW,IAAI,gBAAgB;AACtE,QAAM,iBAAiB,OAAO,WAAW,IAAI,CAAC,MAAM,EAAE,IAAI;AAE1D,SAAO;AAAA,IACL,MAAM,UAAU,aAAa,UAAU;AACrC,YAAM,YAAY,OAAO,WAAW,WAAW;AAC/C,UAAI,CAAC,WAAW;AACd,eAAO;AAAA,MACT;AACA,YAAM,SAAS,UAAU,QAAQ,QAAQ;AACzC,UAAI,CAAC,QAAQ;AACX,eAAO;AAAA,MACT;AACA,aAAOC;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,WAAW,aAAa;AAC5B,YAAM,YAAY,OAAO,WAAW,WAAW;AAC/C,UAAI,CAAC,WAAW;AACd,eAAO,CAAC;AAAA,MACV;AACA,YAAM,UAAU,OAAO,QAAQ,UAAU,OAAO;AAChD,YAAM,WAAW,MAAM,QAAQ;AAAA,QAC7B,QAAQ;AAAA,UAAI,CAAC,CAAC,KAAK,MAAM,MACvBA,eAAc,aAAa,KAAK,QAAQ,gBAAgB,OAAO;AAAA,QACjE;AAAA,MACF;AACA,aAAO,SAAS,OAAO,CAAC,MAAyB,MAAM,MAAS;AAAA,IAClE;AAAA,IAEA,MAAM,YAAY;AAChB,aAAO,EAAE,QAAQ,KAAK;AAAA,IACxB;AAAA,IAEA,MAAM,eAAe;AACnB,aAAO,QAAQ,aAAa;AAAA,IAC9B;AAAA,IAEA,MAAM,cAAc;AAClB,UAAI,CAAC,QAAQ,mBAAmB;AAC9B,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,QAAQ,aAAa;AACzC,UAAIC,cAAa,MAAM,MAAM,GAAG;AAC9B,eAAO,EAAE,QAAQ,MAAM;AAAA,MACzB;AACA,YAAM,SAAS,MAAM,QAAQ,eAAe;AAC5C,UAAI,CAAC,QAAQ;AACX,eAAO,EAAE,QAAQ,MAAM;AAAA,MACzB;AACA,WAAK,QAAQ,QAAQ,SAAS;AAAA,QAC5B,mBAAmB,QAAQ;AAAA,QAC3B,iBAAiB,QAAQ;AAAA,QACzB,eAAe,QAAQ;AAAA,MACzB,CAAC,EAAE,MAAM,CAAC,UAAU;AAClB,gBAAQ,MAAM,uBAAuB,KAAK;AAAA,MAC5C,CAAC;AACD,aAAO,EAAE,QAAQ,KAAK;AAAA,IACxB;AAAA,EACF;AACF;;;ACxGA,SAAS,qBAAqB;;;ACA9B,SAAS,mBAAAC,wBAAuB;;;AC8ChC,SAAS,gBAAAC,eAAc,4BAA4B;AACnD,SAAS,wBAAAC,6BAA4B;","names":["isSyncActive","resolveWidget","resolveWidget","isSyncActive","InMemoryStorage","isSyncActive","instantiateConnector"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rawdash/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Framework-agnostic rawdash request handlers, engine, and wire contract. Wrap with @rawdash/hono (or another adapter) to serve over HTTP.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@rawdash/core": "0.
|
|
25
|
+
"@rawdash/core": "0.16.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"tsup": "^8.0.0",
|