@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 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, HealthResponse, SyncState, CachedWidget, WidgetsListResponse, TriggerSyncResponse, RetentionConfig } from '@rawdash/core';
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 ({ connector }) => {
45
- const handle = storage.getStorageHandle(connector.id);
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
- async function runSync(config, storage) {
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
- const acquired = await storage.markSyncRunning();
102
- if (!acquired) {
103
- return;
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 ({ connector }) => {
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(connector.id, {
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 syncPromise = connector.sync(
115
- { mode: "full" },
116
- handle,
117
- controller.signal
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
- `${connector.id} timed out after ${FULL_SYNC_TIMEOUT_MS}ms`
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 result = await Promise.race([syncPromise, timeoutPromise]);
130
- if (!result.done) {
131
- errors.push(
132
- `${connector.id} did not complete in one chunk (chunked syncs are only supported in cloud)`
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
- errors.push(
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
- errors.push(err instanceof Error ? err.message : String(err));
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
- const config = await ctx.getConfig();
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
- void runSync(config, storage).catch((err) => {
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]) => resolveWidget(key, widget, config.connectors, storage)
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 result = await resolveWidget(
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
- config.connectors,
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
- return result;
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(widgetId, widget, config.connectors, storage);
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, config.connectors, storage)
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).catch((error) => {
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":"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","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.14.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.14.0"
25
+ "@rawdash/core": "0.16.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "tsup": "^8.0.0",