@opendatalabs/personal-server-ts-server 0.0.1-canary.016adf9 → 0.0.1-canary.052d47e
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/dist/api-auth.d.ts +31 -0
- package/dist/api-auth.d.ts.map +1 -0
- package/dist/api-auth.js +148 -0
- package/dist/api-auth.js.map +1 -0
- package/dist/api.d.ts +4 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +3 -0
- package/dist/api.js.map +1 -1
- package/dist/app.d.ts +77 -2
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +165 -9
- package/dist/app.js.map +1 -1
- package/dist/bootstrap.d.ts +36 -3
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +434 -39
- package/dist/bootstrap.js.map +1 -1
- package/dist/client.d.ts +17 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +369 -0
- package/dist/client.js.map +1 -0
- package/dist/config/defaults.d.ts +12 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +14 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +4 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +8 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +78 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/paths.d.ts +9 -0
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +22 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/enclave-main.d.ts +10 -0
- package/dist/enclave-main.d.ts.map +1 -0
- package/dist/enclave-main.js +115 -0
- package/dist/enclave-main.js.map +1 -0
- package/dist/hono-context.d.ts +14 -0
- package/dist/hono-context.d.ts.map +1 -0
- package/dist/hono-context.js +2 -0
- package/dist/hono-context.js.map +1 -0
- package/dist/index.js +48 -12
- package/dist/index.js.map +1 -1
- package/dist/jobs/types.d.ts +17 -0
- package/dist/jobs/types.d.ts.map +1 -0
- package/dist/jobs/types.js +3 -0
- package/dist/jobs/types.js.map +1 -0
- package/dist/jobs/worker.d.ts +30 -0
- package/dist/jobs/worker.d.ts.map +1 -0
- package/dist/jobs/worker.js +217 -0
- package/dist/jobs/worker.js.map +1 -0
- package/dist/keys/server-account.d.ts +28 -0
- package/dist/keys/server-account.d.ts.map +1 -0
- package/dist/keys/server-account.js +48 -0
- package/dist/keys/server-account.js.map +1 -0
- package/dist/listen.d.ts +11 -0
- package/dist/listen.d.ts.map +1 -0
- package/dist/listen.js +23 -0
- package/dist/listen.js.map +1 -0
- package/dist/logger/index.d.ts +5 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +9 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logging/access-log.d.ts +4 -0
- package/dist/logging/access-log.d.ts.map +1 -0
- package/dist/logging/access-log.js +25 -0
- package/dist/logging/access-log.js.map +1 -0
- package/dist/logging/access-reader.d.ts +19 -0
- package/dist/logging/access-reader.d.ts.map +1 -0
- package/dist/logging/access-reader.js +50 -0
- package/dist/logging/access-reader.js.map +1 -0
- package/dist/middleware/access-log.d.ts.map +1 -1
- package/dist/middleware/access-log.js +8 -2
- package/dist/middleware/access-log.js.map +1 -1
- package/dist/middleware/builder-check.d.ts +2 -2
- package/dist/middleware/builder-check.d.ts.map +1 -1
- package/dist/middleware/builder-check.js +7 -2
- package/dist/middleware/builder-check.js.map +1 -1
- package/dist/middleware/cors.d.ts +18 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +51 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/owner-check.js +1 -1
- package/dist/middleware/owner-check.js.map +1 -1
- package/dist/middleware/web3-auth.d.ts +12 -0
- package/dist/middleware/web3-auth.d.ts.map +1 -1
- package/dist/middleware/web3-auth.js +27 -31
- package/dist/middleware/web3-auth.js.map +1 -1
- package/dist/migrations/detect-stuck-version-ledger.d.ts +43 -0
- package/dist/migrations/detect-stuck-version-ledger.d.ts.map +1 -0
- package/dist/migrations/detect-stuck-version-ledger.js +73 -0
- package/dist/migrations/detect-stuck-version-ledger.js.map +1 -0
- package/dist/migrations/local-state.d.ts +35 -0
- package/dist/migrations/local-state.d.ts.map +1 -0
- package/dist/migrations/local-state.js +133 -0
- package/dist/migrations/local-state.js.map +1 -0
- package/dist/migrations/state-migrations.d.ts +78 -0
- package/dist/migrations/state-migrations.d.ts.map +1 -0
- package/dist/migrations/state-migrations.js +72 -0
- package/dist/migrations/state-migrations.js.map +1 -0
- package/dist/pending-blob-deletions.d.ts +10 -0
- package/dist/pending-blob-deletions.d.ts.map +1 -0
- package/dist/pending-blob-deletions.js +38 -0
- package/dist/pending-blob-deletions.js.map +1 -0
- package/dist/routes/access-logs.d.ts +7 -0
- package/dist/routes/access-logs.d.ts.map +1 -1
- package/dist/routes/access-logs.js +11 -17
- package/dist/routes/access-logs.js.map +1 -1
- package/dist/routes/auth-device.d.ts +49 -0
- package/dist/routes/auth-device.d.ts.map +1 -0
- package/dist/routes/auth-device.js +581 -0
- package/dist/routes/auth-device.js.map +1 -0
- package/dist/routes/data.d.ts +67 -2
- package/dist/routes/data.d.ts.map +1 -1
- package/dist/routes/data.js +41 -201
- package/dist/routes/data.js.map +1 -1
- package/dist/routes/derivatives.d.ts +33 -0
- package/dist/routes/derivatives.d.ts.map +1 -0
- package/dist/routes/derivatives.js +33 -0
- package/dist/routes/derivatives.js.map +1 -0
- package/dist/routes/enclave-jobs.d.ts +9 -0
- package/dist/routes/enclave-jobs.d.ts.map +1 -0
- package/dist/routes/enclave-jobs.js +104 -0
- package/dist/routes/enclave-jobs.js.map +1 -0
- package/dist/routes/grants.d.ts +9 -2
- package/dist/routes/grants.d.ts.map +1 -1
- package/dist/routes/grants.js +18 -196
- package/dist/routes/grants.js.map +1 -1
- package/dist/routes/health.d.ts +7 -1
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +39 -2
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/mcp.d.ts +115 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +676 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/oauth-token.d.ts +56 -0
- package/dist/routes/oauth-token.d.ts.map +1 -0
- package/dist/routes/oauth-token.js +49 -0
- package/dist/routes/oauth-token.js.map +1 -0
- package/dist/routes/sync.d.ts +7 -0
- package/dist/routes/sync.d.ts.map +1 -1
- package/dist/routes/sync.js +12 -39
- package/dist/routes/sync.js.map +1 -1
- package/dist/routes/ui-config.d.ts.map +1 -1
- package/dist/routes/ui-config.js +17 -82
- package/dist/routes/ui-config.js.map +1 -1
- package/dist/routes/ui-registration.d.ts +9 -0
- package/dist/routes/ui-registration.d.ts.map +1 -0
- package/dist/routes/ui-registration.js +217 -0
- package/dist/routes/ui-registration.js.map +1 -0
- package/dist/routes/ui.d.ts +1 -0
- package/dist/routes/ui.d.ts.map +1 -1
- package/dist/routes/ui.js +46 -4
- package/dist/routes/ui.js.map +1 -1
- package/dist/routes/write-session.d.ts +37 -0
- package/dist/routes/write-session.d.ts.map +1 -0
- package/dist/routes/write-session.js +34 -0
- package/dist/routes/write-session.js.map +1 -0
- package/dist/storage/hierarchy.d.ts +36 -0
- package/dist/storage/hierarchy.d.ts.map +1 -0
- package/dist/storage/hierarchy.js +282 -0
- package/dist/storage/hierarchy.js.map +1 -0
- package/dist/storage/index-manager.d.ts +60 -0
- package/dist/storage/index-manager.d.ts.map +1 -0
- package/dist/storage/index-manager.js +174 -0
- package/dist/storage/index-manager.js.map +1 -0
- package/dist/storage/index-schema.d.ts +5 -0
- package/dist/storage/index-schema.d.ts.map +1 -0
- package/dist/storage/index-schema.js +69 -0
- package/dist/storage/index-schema.js.map +1 -0
- package/dist/storage/node-data-storage.d.ts +9 -0
- package/dist/storage/node-data-storage.d.ts.map +1 -0
- package/dist/storage/node-data-storage.js +105 -0
- package/dist/storage/node-data-storage.js.map +1 -0
- package/dist/storage/question-store.d.ts +9 -0
- package/dist/storage/question-store.d.ts.map +1 -0
- package/dist/storage/question-store.js +151 -0
- package/dist/storage/question-store.js.map +1 -0
- package/dist/sync-cursor.d.ts +15 -0
- package/dist/sync-cursor.d.ts.map +1 -0
- package/dist/sync-cursor.js +110 -0
- package/dist/sync-cursor.js.map +1 -0
- package/dist/token-store.d.ts +22 -0
- package/dist/token-store.d.ts.map +1 -0
- package/dist/token-store.js +120 -0
- package/dist/token-store.js.map +1 -0
- package/dist/tunnel/auth.d.ts +4 -0
- package/dist/tunnel/auth.d.ts.map +1 -1
- package/dist/tunnel/auth.js +2 -1
- package/dist/tunnel/auth.js.map +1 -1
- package/dist/tunnel/config.d.ts +1 -0
- package/dist/tunnel/config.d.ts.map +1 -1
- package/dist/tunnel/config.js +1 -1
- package/dist/tunnel/config.js.map +1 -1
- package/dist/tunnel/manager.d.ts +21 -1
- package/dist/tunnel/manager.d.ts.map +1 -1
- package/dist/tunnel/manager.js +78 -23
- package/dist/tunnel/manager.js.map +1 -1
- package/dist/ui/browser_tls_rustls_bg.wasm +0 -0
- package/dist/ui/index.html +2192 -0
- package/dist/ui/ps-lite-debug.d.ts +93 -0
- package/dist/ui/ps-lite-debug.d.ts.map +1 -0
- package/dist/ui/ps-lite-debug.js +135347 -0
- package/dist/ui/ps-lite-debug.js.map +1 -0
- package/package.json +20 -6
- package/dist/middleware/grant-check.d.ts +0 -12
- package/dist/middleware/grant-check.d.ts.map +0 -1
- package/dist/middleware/grant-check.js +0 -90
- package/dist/middleware/grant-check.js.map +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BUI-715 follow-up — NON-DESTRUCTIVE detection of the stuck version-ledger
|
|
3
|
+
* fingerprint on the desktop Personal Server.
|
|
4
|
+
*
|
|
5
|
+
* Background. The local `version` in `data_files` is a per-snapshot counter
|
|
6
|
+
* (`findLatestVersionByScope() + 1`, bumped on every connector run). The
|
|
7
|
+
* gateway's `currentVersion` is a per-content-change counter. When the gateway
|
|
8
|
+
* falls behind the local counter (a moksha / DataRegistry reset, or a pre-#755
|
|
9
|
+
* network switch that shared one state folder across mainnet/testnet), repeated
|
|
10
|
+
* re-snapshots pile up several unsynced rows for the same scope that thrash on
|
|
11
|
+
* `409 Gap in version sequence`.
|
|
12
|
+
*
|
|
13
|
+
* Why detect, not repair. The runtime already recovers this non-destructively:
|
|
14
|
+
* the upload worker's 409 handling (BUI-540 adopt / BUI-715 rebase, #158/#202)
|
|
15
|
+
* rebases distinct content onto the gateway's next-valid version and ADOPTS
|
|
16
|
+
* identical content (`record.dataHash === dataHash`) rather than minting a new
|
|
17
|
+
* version — so duplicate pending rows dedupe on the next sync without deleting
|
|
18
|
+
* anything. A boot-time `DELETE` cannot be made safe offline: `data_files`
|
|
19
|
+
* carries no content hash, so "duplicate re-snapshot" (safe to drop) is
|
|
20
|
+
* indistinguishable from "distinct history" (must keep). Worse, the schema
|
|
21
|
+
* migration adds `data_point_id` as a NULL column, so on the first boot after a
|
|
22
|
+
* schema upgrade a legacy user's fully-synced rows momentarily look pending.
|
|
23
|
+
* This migration therefore only OBSERVES and records prevalence; recovery is
|
|
24
|
+
* left to the runtime.
|
|
25
|
+
*
|
|
26
|
+
* Caveat on the count. Because `data_point_id IS NULL` also matches rows freshly
|
|
27
|
+
* backfilled by the schema migration, the reported pending-row count is an upper
|
|
28
|
+
* bound on the first boot after a schema upgrade. It is telemetry, not a gate.
|
|
29
|
+
*/
|
|
30
|
+
export const DETECT_STUCK_VERSION_LEDGER_ID = "2026-07-14-detect-stuck-version-ledger";
|
|
31
|
+
/**
|
|
32
|
+
* Scopes with more than one row still pending upload (`data_point_id IS NULL`) —
|
|
33
|
+
* the accumulation fingerprint. One pending row is ordinary in-flight work.
|
|
34
|
+
*/
|
|
35
|
+
export function findStuckScopes(db) {
|
|
36
|
+
const rows = db
|
|
37
|
+
.prepare(`SELECT scope, COUNT(*) AS pending_rows
|
|
38
|
+
FROM data_files
|
|
39
|
+
WHERE data_point_id IS NULL
|
|
40
|
+
GROUP BY scope
|
|
41
|
+
HAVING COUNT(*) > 1
|
|
42
|
+
ORDER BY scope ASC`)
|
|
43
|
+
.all();
|
|
44
|
+
return rows.map((r) => ({ scope: r.scope, pendingRows: r.pending_rows }));
|
|
45
|
+
}
|
|
46
|
+
export const detectStuckVersionLedger = {
|
|
47
|
+
id: DETECT_STUCK_VERSION_LEDGER_ID,
|
|
48
|
+
description: "Detect (non-destructively) scopes whose upload queue has accumulated multiple pending rows — the version-ledger thrash fingerprint (BUI-715). Records prevalence to state.json; the sync worker's 409 rebase/adopt (#158/#202) performs the actual recovery.",
|
|
49
|
+
// Cheap, mutation-free observation — safe to re-run every boot.
|
|
50
|
+
everyBoot: true,
|
|
51
|
+
check(ctx) {
|
|
52
|
+
return findStuckScopes(ctx.db).length > 0;
|
|
53
|
+
},
|
|
54
|
+
run(ctx) {
|
|
55
|
+
const reports = findStuckScopes(ctx.db);
|
|
56
|
+
if (reports.length === 0)
|
|
57
|
+
return { changed: false };
|
|
58
|
+
const pendingRows = reports.reduce((sum, r) => sum + r.pendingRows, 0);
|
|
59
|
+
ctx.logger?.warn({
|
|
60
|
+
migration: DETECT_STUCK_VERSION_LEDGER_ID,
|
|
61
|
+
scopeCount: reports.length,
|
|
62
|
+
scopes: reports.map((r) => r.scope),
|
|
63
|
+
pendingRows,
|
|
64
|
+
}, "Detected stuck version-ledger scopes (BUI-715); runtime 409 recovery will drain them on the next sync");
|
|
65
|
+
return {
|
|
66
|
+
// Non-destructive: nothing was mutated, so `changed` stays false; the log
|
|
67
|
+
// entry itself is the record of the detection.
|
|
68
|
+
changed: false,
|
|
69
|
+
detail: `detected ${reports.length} scope(s) with ${pendingRows} accumulated pending row(s)`,
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=detect-stuck-version-ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-stuck-version-ledger.js","sourceRoot":"","sources":["../../src/migrations/detect-stuck-version-ledger.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,8BAA8B,GACzC,wCAAwC,CAAC;AAY3C;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,EAAqB;IACnD,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CACN;;;;;2BAKqB,CACtB;SACA,GAAG,EAAqB,CAAC;IAC5B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,CAAC,MAAM,wBAAwB,GAAmB;IACtD,EAAE,EAAE,8BAA8B;IAClC,WAAW,EACT,8PAA8P;IAChQ,gEAAgE;IAChE,SAAS,EAAE,IAAI;IACf,KAAK,CAAC,GAA0B;QAC9B,OAAO,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IAC5C,CAAC;IACD,GAAG,CAAC,GAA0B;QAC5B,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACpD,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QACvE,GAAG,CAAC,MAAM,EAAE,IAAI,CACd;YACE,SAAS,EAAE,8BAA8B;YACzC,UAAU,EAAE,OAAO,CAAC,MAAM;YAC1B,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;YACnC,WAAW;SACZ,EACD,uGAAuG,CACxG,CAAC;QACF,OAAO;YACL,0EAA0E;YAC1E,+CAA+C;YAC/C,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,YAAY,OAAO,CAAC,MAAM,kBAAkB,WAAW,6BAA6B;SAC7F,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
import { type StateMigration, type StateMigrationLogger, type StateMigrationsState } from "./state-migrations.js";
|
|
3
|
+
export declare const LOCAL_STATE_VERSION = 1;
|
|
4
|
+
export declare const INDEX_STATE_VERSION = 1;
|
|
5
|
+
export declare const TOKEN_STORE_VERSION = 1;
|
|
6
|
+
export declare const SYNC_CURSOR_VERSION = 1;
|
|
7
|
+
export declare const DATA_HIERARCHY_VERSION = 1;
|
|
8
|
+
export declare const CONFIG_STATE_VERSION = 1;
|
|
9
|
+
export interface LocalStateMigrationOptions {
|
|
10
|
+
storageRoot: string;
|
|
11
|
+
dataDir: string;
|
|
12
|
+
configPath: string;
|
|
13
|
+
syncCursorPath: string;
|
|
14
|
+
tokensPath: string;
|
|
15
|
+
statePath?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Open `index.db` handle. When supplied, the versioned state-migration
|
|
18
|
+
* registry runs against it and its result is persisted into `state.json`.
|
|
19
|
+
* Omitted in the file-only unit tests that predate the registry.
|
|
20
|
+
*/
|
|
21
|
+
db?: Database.Database;
|
|
22
|
+
/** Overrides the registry (tests). Defaults to STATE_MIGRATIONS. */
|
|
23
|
+
migrations?: StateMigration[];
|
|
24
|
+
logger?: StateMigrationLogger;
|
|
25
|
+
/** Injectable clock for migration log timestamps (tests). */
|
|
26
|
+
now?: () => string;
|
|
27
|
+
}
|
|
28
|
+
export interface LocalStateMigrationResult {
|
|
29
|
+
statePath: string;
|
|
30
|
+
syncCursorCreated: boolean;
|
|
31
|
+
tokensFileVersioned: boolean;
|
|
32
|
+
migrations?: StateMigrationsState;
|
|
33
|
+
}
|
|
34
|
+
export declare function migrateLocalState(options: LocalStateMigrationOptions): Promise<LocalStateMigrationResult>;
|
|
35
|
+
//# sourceMappingURL=local-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-state.d.ts","sourceRoot":"","sources":["../../src/migrations/local-state.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EAC1B,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,EAAE,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC;IACvB,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAC9B,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,oBAAoB,CAAC;CACnC;AA0ID,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,yBAAyB,CAAC,CAiDpC"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { runStateMigrations, } from "./state-migrations.js";
|
|
4
|
+
export const LOCAL_STATE_VERSION = 1;
|
|
5
|
+
export const INDEX_STATE_VERSION = 1;
|
|
6
|
+
export const TOKEN_STORE_VERSION = 1;
|
|
7
|
+
export const SYNC_CURSOR_VERSION = 1;
|
|
8
|
+
export const DATA_HIERARCHY_VERSION = 1;
|
|
9
|
+
export const CONFIG_STATE_VERSION = 1;
|
|
10
|
+
async function fileExists(path) {
|
|
11
|
+
try {
|
|
12
|
+
await readFile(path);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
if (err.code === "ENOENT") {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function writeJsonFile(path, value, mode) {
|
|
23
|
+
await mkdir(dirname(path), { recursive: true });
|
|
24
|
+
const tmpPath = `${path}.tmp`;
|
|
25
|
+
await writeFile(tmpPath, `${JSON.stringify(value, null, 2)}\n`, {
|
|
26
|
+
encoding: "utf-8",
|
|
27
|
+
mode,
|
|
28
|
+
});
|
|
29
|
+
await rename(tmpPath, path);
|
|
30
|
+
}
|
|
31
|
+
async function migrateSyncCursor(configPath, syncCursorPath) {
|
|
32
|
+
if (await fileExists(syncCursorPath)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
let legacyTimestamp = null;
|
|
36
|
+
try {
|
|
37
|
+
const raw = await readFile(configPath, "utf-8");
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
if (typeof parsed.sync?.lastProcessedTimestamp === "string") {
|
|
40
|
+
legacyTimestamp = parsed.sync.lastProcessedTimestamp;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (err.code !== "ENOENT") {
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (legacyTimestamp === null) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const state = {
|
|
52
|
+
version: SYNC_CURSOR_VERSION,
|
|
53
|
+
lastProcessedTimestamp: legacyTimestamp,
|
|
54
|
+
};
|
|
55
|
+
await writeJsonFile(syncCursorPath, state);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
async function migrateTokensFile(tokensPath) {
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
const raw = await readFile(tokensPath, "utf-8");
|
|
62
|
+
parsed = JSON.parse(raw);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err.code === "ENOENT") {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
if (parsed.version === TOKEN_STORE_VERSION) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (!Array.isArray(parsed.tokens)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
await writeJsonFile(tokensPath, {
|
|
77
|
+
version: TOKEN_STORE_VERSION,
|
|
78
|
+
tokens: parsed.tokens,
|
|
79
|
+
}, 0o600);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
async function readPriorState(statePath) {
|
|
83
|
+
try {
|
|
84
|
+
const raw = await readFile(statePath, "utf-8");
|
|
85
|
+
return JSON.parse(raw);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
if (err.code === "ENOENT") {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
// A corrupt/unreadable state.json must not brick boot — start fresh. The
|
|
92
|
+
// migration registry is re-derived from disk state, so nothing is lost
|
|
93
|
+
// beyond the applied-id/log bookkeeping.
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export async function migrateLocalState(options) {
|
|
98
|
+
const statePath = options.statePath ?? join(options.storageRoot, "state.json");
|
|
99
|
+
await mkdir(options.storageRoot, { recursive: true });
|
|
100
|
+
await mkdir(options.dataDir, { recursive: true });
|
|
101
|
+
const priorState = await readPriorState(statePath);
|
|
102
|
+
const syncCursorCreated = await migrateSyncCursor(options.configPath, options.syncCursorPath);
|
|
103
|
+
const tokensFileVersioned = await migrateTokensFile(options.tokensPath);
|
|
104
|
+
// Versioned state-migration registry (needs the open index.db handle).
|
|
105
|
+
const migrations = options.db
|
|
106
|
+
? runStateMigrations({
|
|
107
|
+
db: options.db,
|
|
108
|
+
storageRoot: options.storageRoot,
|
|
109
|
+
logger: options.logger,
|
|
110
|
+
}, priorState?.migrations, { migrations: options.migrations, now: options.now })
|
|
111
|
+
: priorState?.migrations;
|
|
112
|
+
const state = {
|
|
113
|
+
version: LOCAL_STATE_VERSION,
|
|
114
|
+
migratedAt: new Date().toISOString(),
|
|
115
|
+
components: {
|
|
116
|
+
config: CONFIG_STATE_VERSION,
|
|
117
|
+
index: INDEX_STATE_VERSION,
|
|
118
|
+
dataHierarchy: DATA_HIERARCHY_VERSION,
|
|
119
|
+
tokenStore: TOKEN_STORE_VERSION,
|
|
120
|
+
tokensFile: TOKEN_STORE_VERSION,
|
|
121
|
+
syncCursor: SYNC_CURSOR_VERSION,
|
|
122
|
+
},
|
|
123
|
+
...(migrations ? { migrations } : {}),
|
|
124
|
+
};
|
|
125
|
+
await writeJsonFile(statePath, state);
|
|
126
|
+
return {
|
|
127
|
+
statePath,
|
|
128
|
+
syncCursorCreated,
|
|
129
|
+
tokensFileVersioned,
|
|
130
|
+
...(migrations ? { migrations } : {}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=local-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-state.js","sourceRoot":"","sources":["../../src/migrations/local-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
/**
|
|
3
|
+
* Versioned local-state migration framework for the desktop Personal Server.
|
|
4
|
+
*
|
|
5
|
+
* Each release carries a (possibly empty) ordered list of migrations. A
|
|
6
|
+
* migration inspects the on-disk state (`index.db` and friends) and repairs it
|
|
7
|
+
* on boot, so users self-heal on upgrade instead of needing manual surgery.
|
|
8
|
+
*
|
|
9
|
+
* Two shapes:
|
|
10
|
+
* • one-shot (default) — runs once, then its id is recorded in
|
|
11
|
+
* `state.json.migrations.applied` and it is never re-checked. Use for
|
|
12
|
+
* structural, run-once cleanups.
|
|
13
|
+
* • `everyBoot: true` — its `check()`/`run()` are re-evaluated on every boot
|
|
14
|
+
* and it is never recorded as applied. Use for idempotent heals whose
|
|
15
|
+
* triggering condition can recur. The `check()` MUST be cheap and `run()`
|
|
16
|
+
* a no-op when nothing needs repair.
|
|
17
|
+
*
|
|
18
|
+
* Every actual repair is appended to `state.json.migrations.log` for support
|
|
19
|
+
* visibility.
|
|
20
|
+
*/
|
|
21
|
+
export interface StateMigrationLogger {
|
|
22
|
+
info: (obj: unknown, msg?: string) => void;
|
|
23
|
+
warn: (obj: unknown, msg?: string) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface StateMigrationContext {
|
|
26
|
+
db: Database.Database;
|
|
27
|
+
storageRoot: string;
|
|
28
|
+
logger?: StateMigrationLogger;
|
|
29
|
+
}
|
|
30
|
+
export interface StateMigrationResult {
|
|
31
|
+
/** Whether the run actually changed anything (drives the log's `changed`). */
|
|
32
|
+
changed: boolean;
|
|
33
|
+
/** Short human-readable summary of what was repaired. */
|
|
34
|
+
detail?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface StateMigration {
|
|
37
|
+
/** Stable, unique id. Convention: `YYYY-MM-DD-kebab-summary`. */
|
|
38
|
+
id: string;
|
|
39
|
+
description: string;
|
|
40
|
+
/** Re-run every boot instead of once. See module doc. Default false. */
|
|
41
|
+
everyBoot?: boolean;
|
|
42
|
+
/** Cheap guard: is the repair needed right now? */
|
|
43
|
+
check(ctx: StateMigrationContext): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Apply the repair. Should be a no-op when nothing needs fixing.
|
|
46
|
+
*
|
|
47
|
+
* MUST be idempotent even for one-shot (non-`everyBoot`) migrations. The
|
|
48
|
+
* `applied` ledger in state.json is a best-effort skip cache, not a
|
|
49
|
+
* guarantee: a lost or corrupt state.json legitimately resets it, so `run`
|
|
50
|
+
* may execute more than once across boots. Guard destructive work behind
|
|
51
|
+
* `check()` (which is always consulted before `run` when the id is not in the
|
|
52
|
+
* ledger) and make `run` safe to repeat.
|
|
53
|
+
*/
|
|
54
|
+
run(ctx: StateMigrationContext): StateMigrationResult;
|
|
55
|
+
}
|
|
56
|
+
export interface StateMigrationRunLogEntry {
|
|
57
|
+
id: string;
|
|
58
|
+
ranAt: string;
|
|
59
|
+
changed: boolean;
|
|
60
|
+
detail?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface StateMigrationsState {
|
|
63
|
+
/** Ids of one-shot migrations already run (never includes everyBoot ones). */
|
|
64
|
+
applied: string[];
|
|
65
|
+
/** Bounded history of actual repairs, oldest first. */
|
|
66
|
+
log: StateMigrationRunLogEntry[];
|
|
67
|
+
}
|
|
68
|
+
export interface RunStateMigrationsOptions {
|
|
69
|
+
migrations?: StateMigration[];
|
|
70
|
+
/** Injectable clock (tests). Defaults to `new Date().toISOString()`. */
|
|
71
|
+
now?: () => string;
|
|
72
|
+
/** Max retained log entries (oldest dropped). Default 50. */
|
|
73
|
+
maxLog?: number;
|
|
74
|
+
}
|
|
75
|
+
/** The registry applied on every boot. Append new migrations here. */
|
|
76
|
+
export declare const STATE_MIGRATIONS: StateMigration[];
|
|
77
|
+
export declare function runStateMigrations(ctx: StateMigrationContext, prior: StateMigrationsState | undefined, options?: RunStateMigrationsOptions): StateMigrationsState;
|
|
78
|
+
//# sourceMappingURL=state-migrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-migrations.d.ts","sourceRoot":"","sources":["../../src/migrations/state-migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,OAAO,EAAE,OAAO,CAAC;IACjB,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mDAAmD;IACnD,KAAK,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC;IAC3C;;;;;;;;;OASG;IACH,GAAG,CAAC,GAAG,EAAE,qBAAqB,GAAG,oBAAoB,CAAC;CACvD;AAED,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,uDAAuD;IACvD,GAAG,EAAE,yBAAyB,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAC9B,wEAAwE;IACxE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,sEAAsE;AACtE,eAAO,MAAM,gBAAgB,EAAE,cAAc,EAA+B,CAAC;AAI7E,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,qBAAqB,EAC1B,KAAK,EAAE,oBAAoB,GAAG,SAAS,EACvC,OAAO,GAAE,yBAA8B,GACtC,oBAAoB,CA8EtB"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { detectStuckVersionLedger } from "./detect-stuck-version-ledger.js";
|
|
2
|
+
/** The registry applied on every boot. Append new migrations here. */
|
|
3
|
+
export const STATE_MIGRATIONS = [detectStuckVersionLedger];
|
|
4
|
+
const DEFAULT_MAX_LOG = 50;
|
|
5
|
+
export function runStateMigrations(ctx, prior, options = {}) {
|
|
6
|
+
const migrations = options.migrations ?? STATE_MIGRATIONS;
|
|
7
|
+
const now = options.now ?? (() => new Date().toISOString());
|
|
8
|
+
const maxLog = options.maxLog ?? DEFAULT_MAX_LOG;
|
|
9
|
+
// Defensive: state.json is parsed untyped, so a corrupt or hand-edited file
|
|
10
|
+
// can carry non-array `applied`/`log` (e.g. `{}`). Coerce rather than let
|
|
11
|
+
// `new Set(...)` / spread throw and brick boot.
|
|
12
|
+
const applied = new Set(Array.isArray(prior?.applied) ? prior.applied : []);
|
|
13
|
+
const log = Array.isArray(prior?.log)
|
|
14
|
+
? [...prior.log]
|
|
15
|
+
: [];
|
|
16
|
+
for (const migration of migrations) {
|
|
17
|
+
if (!migration.everyBoot && applied.has(migration.id))
|
|
18
|
+
continue;
|
|
19
|
+
let needed;
|
|
20
|
+
try {
|
|
21
|
+
needed = migration.check(ctx);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
// Leave unmarked so a transient failure retries on the next boot.
|
|
25
|
+
ctx.logger?.warn({ migration: migration.id, error: err.message }, "State migration check failed; skipping");
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (needed) {
|
|
29
|
+
try {
|
|
30
|
+
const result = migration.run(ctx);
|
|
31
|
+
// everyBoot migrations re-observe the same condition on every boot
|
|
32
|
+
// until it clears; without dedupe a persistent condition fills the
|
|
33
|
+
// bounded log with identical entries and evicts real history. Keep
|
|
34
|
+
// one entry per observation streak: skip the push when the latest
|
|
35
|
+
// entry for this id already records the same outcome. One-shot
|
|
36
|
+
// migrations always log (a re-run after ledger loss is worth seeing).
|
|
37
|
+
const latestForId = [...log]
|
|
38
|
+
.reverse()
|
|
39
|
+
.find((entry) => entry.id === migration.id);
|
|
40
|
+
const duplicateObservation = migration.everyBoot === true &&
|
|
41
|
+
latestForId !== undefined &&
|
|
42
|
+
latestForId.changed === result.changed &&
|
|
43
|
+
latestForId.detail === result.detail;
|
|
44
|
+
if (!duplicateObservation) {
|
|
45
|
+
log.push({
|
|
46
|
+
id: migration.id,
|
|
47
|
+
ranAt: now(),
|
|
48
|
+
changed: result.changed,
|
|
49
|
+
detail: result.detail,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
ctx.logger?.info({
|
|
53
|
+
migration: migration.id,
|
|
54
|
+
changed: result.changed,
|
|
55
|
+
detail: result.detail,
|
|
56
|
+
}, "Applied state migration");
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
// A failed repair must not be recorded as applied — retry next boot.
|
|
60
|
+
ctx.logger?.warn({ migration: migration.id, error: err.message }, "State migration failed; leaving state unchanged");
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!migration.everyBoot)
|
|
65
|
+
applied.add(migration.id);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
applied: [...applied],
|
|
69
|
+
log: log.slice(-maxLog),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=state-migrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-migrations.js","sourceRoot":"","sources":["../../src/migrations/state-migrations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PendingBlobDeletionStore } from "@opendatalabs/personal-server-ts-core/ports";
|
|
2
|
+
/**
|
|
3
|
+
* File-backed retry markers for blob deletions whose gateway tombstone landed
|
|
4
|
+
* but whose storage DELETE did not finish: one exact (scope, version) key
|
|
5
|
+
* each. Lives next to `sync-cursor.json` under the server root; written
|
|
6
|
+
* atomically (tmp + rename) like the cursor. A version-1 file (whole scopes)
|
|
7
|
+
* is read as version-less markers the retry expands into exact keys.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createFilePendingBlobDeletionStore(filePath: string): PendingBlobDeletionStore;
|
|
10
|
+
//# sourceMappingURL=pending-blob-deletions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-blob-deletions.d.ts","sourceRoot":"","sources":["../src/pending-blob-deletions.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAEV,wBAAwB,EACzB,MAAM,6CAA6C,CAAC;AAOrD;;;;;;GAMG;AACH,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,MAAM,GACf,wBAAwB,CA+B1B"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { createPendingBlobDeletionStore, normalizePendingBlobDeletions, } from "@opendatalabs/personal-server-ts-core/sync";
|
|
4
|
+
/**
|
|
5
|
+
* File-backed retry markers for blob deletions whose gateway tombstone landed
|
|
6
|
+
* but whose storage DELETE did not finish: one exact (scope, version) key
|
|
7
|
+
* each. Lives next to `sync-cursor.json` under the server root; written
|
|
8
|
+
* atomically (tmp + rename) like the cursor. A version-1 file (whole scopes)
|
|
9
|
+
* is read as version-less markers the retry expands into exact keys.
|
|
10
|
+
*/
|
|
11
|
+
export function createFilePendingBlobDeletionStore(filePath) {
|
|
12
|
+
const kv = {
|
|
13
|
+
async read() {
|
|
14
|
+
try {
|
|
15
|
+
const raw = await readFile(filePath, "utf8");
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
return normalizePendingBlobDeletions(parsed.keys ?? parsed.scopes);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (err instanceof Error &&
|
|
21
|
+
"code" in err &&
|
|
22
|
+
err.code === "ENOENT") {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
async write(keys) {
|
|
29
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
30
|
+
const tmpPath = `${filePath}.tmp`;
|
|
31
|
+
const state = { version: 2, keys };
|
|
32
|
+
await writeFile(tmpPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
33
|
+
await rename(tmpPath, filePath);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
return createPendingBlobDeletionStore(kv);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=pending-blob-deletions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-blob-deletions.js","sourceRoot":"","sources":["../src/pending-blob-deletions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,8BAA8B,EAC9B,6BAA6B,GAE9B,MAAM,4CAA4C,CAAC;AAWpD;;;;;;GAMG;AACH,MAAM,UAAU,kCAAkC,CAChD,QAAgB;IAEhB,MAAM,EAAE,GAA0B;QAChC,KAAK,CAAC,IAAI;YACR,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;gBAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAI5B,CAAC;gBACF,OAAO,6BAA6B,CAAC,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC;YACrE,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IACE,GAAG,YAAY,KAAK;oBACpB,MAAM,IAAI,GAAG;oBACZ,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAChD,CAAC;oBACD,OAAO,IAAI,CAAC;gBACd,CAAC;gBACD,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,IAAI;YACd,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACpD,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,CAAC;YAClC,MAAM,KAAK,GAA6B,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;YAC7D,MAAM,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YAChE,MAAM,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAClC,CAAC;KACF,CAAC;IACF,OAAO,8BAA8B,CAAC,EAAE,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -5,12 +5,19 @@
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import type { Logger } from "pino";
|
|
7
7
|
import type { AccessLogReader } from "@opendatalabs/personal-server-ts-core/logging/access-reader";
|
|
8
|
+
import { type PersonalServerApiDispatchOptions } from "@opendatalabs/personal-server-ts-core/api";
|
|
9
|
+
import type { TokenStore } from "../token-store.js";
|
|
10
|
+
import type { GatewayClient } from "@opendatalabs/vana-sdk/node";
|
|
8
11
|
export interface AccessLogsRouteDeps {
|
|
9
12
|
logger: Logger;
|
|
10
13
|
accessLogReader: AccessLogReader;
|
|
11
14
|
serverOrigin: string | (() => string);
|
|
12
15
|
serverOwner?: `0x${string}`;
|
|
16
|
+
gateway: GatewayClient;
|
|
13
17
|
devToken?: string;
|
|
18
|
+
accessToken?: string;
|
|
19
|
+
tokenStore?: TokenStore;
|
|
20
|
+
mountPath?: PersonalServerApiDispatchOptions["basePath"];
|
|
14
21
|
}
|
|
15
22
|
export declare function accessLogsRoutes(deps: AccessLogsRouteDeps): Hono;
|
|
16
23
|
//# sourceMappingURL=access-logs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-logs.d.ts","sourceRoot":"","sources":["../../src/routes/access-logs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6DAA6D,CAAC;
|
|
1
|
+
{"version":3,"file":"access-logs.d.ts","sourceRoot":"","sources":["../../src/routes/access-logs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6DAA6D,CAAC;AACnG,OAAO,EAEL,KAAK,gCAAgC,EACtC,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,eAAe,CAAC;IACjC,YAAY,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC;IACtC,WAAW,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;IAC5B,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,gCAAgC,CAAC,UAAU,CAAC,CAAC;CAC1D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAwBhE"}
|
|
@@ -3,28 +3,22 @@
|
|
|
3
3
|
* Owner auth is wired in Task 4.1.
|
|
4
4
|
*/
|
|
5
5
|
import { Hono } from "hono";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { handlePersonalServerAccessLogsRequest, } from "@opendatalabs/personal-server-ts-core/api";
|
|
7
|
+
import { createServerApiAuth } from "../api-auth.js";
|
|
8
8
|
export function accessLogsRoutes(deps) {
|
|
9
9
|
const app = new Hono();
|
|
10
|
-
const
|
|
10
|
+
const auth = createServerApiAuth({
|
|
11
11
|
serverOrigin: deps.serverOrigin,
|
|
12
|
-
devToken: deps.devToken,
|
|
13
12
|
serverOwner: deps.serverOwner,
|
|
13
|
+
gateway: deps.gateway,
|
|
14
|
+
devToken: deps.devToken,
|
|
15
|
+
accessToken: deps.accessToken,
|
|
16
|
+
tokenStore: deps.tokenStore,
|
|
14
17
|
});
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const offsetParam = c.req.query("offset");
|
|
20
|
-
const limit = limitParam !== undefined ? parseInt(limitParam, 10) : 50;
|
|
21
|
-
const offset = offsetParam !== undefined ? parseInt(offsetParam, 10) : 0;
|
|
22
|
-
const result = await deps.accessLogReader.read({
|
|
23
|
-
limit: Number.isNaN(limit) ? 50 : limit,
|
|
24
|
-
offset: Number.isNaN(offset) ? 0 : offset,
|
|
25
|
-
});
|
|
26
|
-
return c.json(result);
|
|
27
|
-
});
|
|
18
|
+
app.all("*", (c) => handlePersonalServerAccessLogsRequest(c.req.raw, {
|
|
19
|
+
auth,
|
|
20
|
+
accessLogReader: deps.accessLogReader,
|
|
21
|
+
}, { basePath: deps.mountPath }));
|
|
28
22
|
return app;
|
|
29
23
|
}
|
|
30
24
|
//# sourceMappingURL=access-logs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-logs.js","sourceRoot":"","sources":["../../src/routes/access-logs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,
|
|
1
|
+
{"version":3,"file":"access-logs.js","sourceRoot":"","sources":["../../src/routes/access-logs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EACL,qCAAqC,GAEtC,MAAM,2CAA2C,CAAC;AAEnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAerD,MAAM,UAAU,gBAAgB,CAAC,IAAyB;IACxD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAEvB,MAAM,IAAI,GAAG,mBAAmB,CAAC;QAC/B,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CACjB,qCAAqC,CACnC,CAAC,CAAC,GAAG,CAAC,GAAG,EACT;QACE,IAAI;QACJ,eAAe,EAAE,IAAI,CAAC,eAAe;KACtC,EACD,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,CAC7B,CACF,CAAC;IAEF,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login Flow v2 — Nextcloud-style device login for self-hosted CLI authentication.
|
|
3
|
+
*
|
|
4
|
+
* POST /auth/device — Initiate a login session
|
|
5
|
+
* GET /auth/device/poll — Poll for completion (token= query param)
|
|
6
|
+
* GET /auth/device/approve — HTML approval page (session= query param)
|
|
7
|
+
* POST /auth/device/approve — Approve the session (session= query param)
|
|
8
|
+
*/
|
|
9
|
+
import "../hono-context.js";
|
|
10
|
+
import { Hono, type Context } from "hono";
|
|
11
|
+
import type { Logger } from "pino";
|
|
12
|
+
import { DEVICE_POLL_INTERVAL_MS, DEVICE_SESSION_TTL_MS, type DeviceSessionStore } from "@opendatalabs/personal-server-ts-core/contracts";
|
|
13
|
+
import type { TokenStore } from "../token-store.js";
|
|
14
|
+
export interface LoginV2Deps {
|
|
15
|
+
logger: Logger;
|
|
16
|
+
serverOrigin: string | (() => string);
|
|
17
|
+
localApprovalOrigin?: string | (() => string | undefined);
|
|
18
|
+
serverOwner?: `0x${string}`;
|
|
19
|
+
tokenStore: TokenStore;
|
|
20
|
+
devToken?: string;
|
|
21
|
+
accessToken?: string;
|
|
22
|
+
allowInteractiveLogin?: boolean;
|
|
23
|
+
getRemoteAddress?: (c: Context) => string | undefined;
|
|
24
|
+
}
|
|
25
|
+
type InspectableDeviceSessionStore = DeviceSessionStore & {
|
|
26
|
+
clear(): void;
|
|
27
|
+
readonly size: number;
|
|
28
|
+
has(sessionId: string): boolean;
|
|
29
|
+
};
|
|
30
|
+
/** In-memory session store (short-lived device authorization state). */
|
|
31
|
+
declare const sessions: InspectableDeviceSessionStore;
|
|
32
|
+
export declare function authDeviceRoutes(deps: LoginV2Deps): Hono;
|
|
33
|
+
export { sessions, DEVICE_SESSION_TTL_MS as SESSION_TTL_MS, DEVICE_POLL_INTERVAL_MS as POLL_INTERVAL_MS, };
|
|
34
|
+
/**
|
|
35
|
+
* Read-only adapter exposing the device-flow session map to the standard
|
|
36
|
+
* RFC 8628 token endpoint (`POST /oauth/token` with
|
|
37
|
+
* `grant_type=urn:ietf:params:oauth:grant-type:device_code`). The
|
|
38
|
+
* `pollToken` issued at `POST /auth/device` is the OAuth2 `device_code`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createDeviceSessionLookup(): {
|
|
41
|
+
findByDeviceCode(deviceCode: string): {
|
|
42
|
+
status: "pending" | "approved";
|
|
43
|
+
accessToken: string | undefined;
|
|
44
|
+
accessTokenExpiresAt: string | undefined;
|
|
45
|
+
sessionId: string;
|
|
46
|
+
} | null;
|
|
47
|
+
consume(sessionId: string): void;
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=auth-device.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-device.d.ts","sourceRoot":"","sources":["../../src/routes/auth-device.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,oBAAoB,CAAC;AAE5B,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAKnC,OAAO,EAGL,uBAAuB,EACvB,qBAAqB,EAKrB,KAAK,kBAAkB,EACxB,MAAM,iDAAiD,CAAC;AAKzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAOpD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC;IACtC,mBAAmB,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC;IAC1D,WAAW,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;IAC5B,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,gBAAgB,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;CACvD;AAED,KAAK,6BAA6B,GAAG,kBAAkB,GAAG;IACxD,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AA0CF,wEAAwE;AACxE,QAAA,MAAM,QAAQ,+BAAwC,CAAC;AAsIvD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CA2MxD;AAkQD,OAAO,EACL,QAAQ,EACR,qBAAqB,IAAI,cAAc,EACvC,uBAAuB,IAAI,gBAAgB,GAC5C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,yBAAyB;iCAER,MAAM;;;;;;uBAWhB,MAAM;EAI5B"}
|