@syncular/server 0.0.1-60
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/blobs/adapters/database.d.ts +83 -0
- package/dist/blobs/adapters/database.d.ts.map +1 -0
- package/dist/blobs/adapters/database.js +180 -0
- package/dist/blobs/adapters/database.js.map +1 -0
- package/dist/blobs/adapters/s3.d.ts +82 -0
- package/dist/blobs/adapters/s3.d.ts.map +1 -0
- package/dist/blobs/adapters/s3.js +170 -0
- package/dist/blobs/adapters/s3.js.map +1 -0
- package/dist/blobs/index.d.ts +9 -0
- package/dist/blobs/index.d.ts.map +1 -0
- package/dist/blobs/index.js +9 -0
- package/dist/blobs/index.js.map +1 -0
- package/dist/blobs/manager.d.ts +195 -0
- package/dist/blobs/manager.d.ts.map +1 -0
- package/dist/blobs/manager.js +440 -0
- package/dist/blobs/manager.js.map +1 -0
- package/dist/blobs/migrate.d.ts +27 -0
- package/dist/blobs/migrate.d.ts.map +1 -0
- package/dist/blobs/migrate.js +119 -0
- package/dist/blobs/migrate.js.map +1 -0
- package/dist/blobs/types.d.ts +54 -0
- package/dist/blobs/types.d.ts.map +1 -0
- package/dist/blobs/types.js +5 -0
- package/dist/blobs/types.js.map +1 -0
- package/dist/clients.d.ts +14 -0
- package/dist/clients.d.ts.map +1 -0
- package/dist/clients.js +7 -0
- package/dist/clients.js.map +1 -0
- package/dist/compaction.d.ts +27 -0
- package/dist/compaction.d.ts.map +1 -0
- package/dist/compaction.js +49 -0
- package/dist/compaction.js.map +1 -0
- package/dist/dialect/index.d.ts +5 -0
- package/dist/dialect/index.d.ts.map +1 -0
- package/dist/dialect/index.js +5 -0
- package/dist/dialect/index.js.map +1 -0
- package/dist/dialect/types.d.ts +170 -0
- package/dist/dialect/types.d.ts.map +1 -0
- package/dist/dialect/types.js +8 -0
- package/dist/dialect/types.js.map +1 -0
- package/dist/helpers/conflict.d.ts +52 -0
- package/dist/helpers/conflict.d.ts.map +1 -0
- package/dist/helpers/conflict.js +49 -0
- package/dist/helpers/conflict.js.map +1 -0
- package/dist/helpers/emitted-change.d.ts +56 -0
- package/dist/helpers/emitted-change.d.ts.map +1 -0
- package/dist/helpers/emitted-change.js +46 -0
- package/dist/helpers/emitted-change.js.map +1 -0
- package/dist/helpers/index.d.ts +10 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +10 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/paginate.d.ts +49 -0
- package/dist/helpers/paginate.d.ts.map +1 -0
- package/dist/helpers/paginate.js +54 -0
- package/dist/helpers/paginate.js.map +1 -0
- package/dist/helpers/scope-strings.d.ts +74 -0
- package/dist/helpers/scope-strings.d.ts.map +1 -0
- package/dist/helpers/scope-strings.js +82 -0
- package/dist/helpers/scope-strings.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/migrate.d.ts +14 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +13 -0
- package/dist/migrate.js.map +1 -0
- package/dist/proxy/handler.d.ts +42 -0
- package/dist/proxy/handler.d.ts.map +1 -0
- package/dist/proxy/handler.js +99 -0
- package/dist/proxy/handler.js.map +1 -0
- package/dist/proxy/index.d.ts +9 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +14 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/proxy/mutation-detector.d.ts +31 -0
- package/dist/proxy/mutation-detector.d.ts.map +1 -0
- package/dist/proxy/mutation-detector.js +61 -0
- package/dist/proxy/mutation-detector.js.map +1 -0
- package/dist/proxy/oplog.d.ts +30 -0
- package/dist/proxy/oplog.d.ts.map +1 -0
- package/dist/proxy/oplog.js +110 -0
- package/dist/proxy/oplog.js.map +1 -0
- package/dist/proxy/registry.d.ts +35 -0
- package/dist/proxy/registry.d.ts.map +1 -0
- package/dist/proxy/registry.js +49 -0
- package/dist/proxy/registry.js.map +1 -0
- package/dist/proxy/types.d.ts +44 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/types.js +7 -0
- package/dist/proxy/types.js.map +1 -0
- package/dist/prune.d.ts +37 -0
- package/dist/prune.d.ts.map +1 -0
- package/dist/prune.js +112 -0
- package/dist/prune.js.map +1 -0
- package/dist/pull.d.ts +31 -0
- package/dist/pull.d.ts.map +1 -0
- package/dist/pull.js +414 -0
- package/dist/pull.js.map +1 -0
- package/dist/push.d.ts +33 -0
- package/dist/push.d.ts.map +1 -0
- package/dist/push.js +329 -0
- package/dist/push.js.map +1 -0
- package/dist/realtime/in-memory.d.ts +13 -0
- package/dist/realtime/in-memory.d.ts.map +1 -0
- package/dist/realtime/in-memory.js +28 -0
- package/dist/realtime/in-memory.js.map +1 -0
- package/dist/realtime/index.d.ts +3 -0
- package/dist/realtime/index.d.ts.map +1 -0
- package/dist/realtime/index.js +2 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/realtime/types.d.ts +50 -0
- package/dist/realtime/types.d.ts.map +1 -0
- package/dist/realtime/types.js +7 -0
- package/dist/realtime/types.js.map +1 -0
- package/dist/schema.d.ts +164 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +10 -0
- package/dist/schema.js.map +1 -0
- package/dist/shapes/create-handler.d.ts +119 -0
- package/dist/shapes/create-handler.d.ts.map +1 -0
- package/dist/shapes/create-handler.js +327 -0
- package/dist/shapes/create-handler.js.map +1 -0
- package/dist/shapes/index.d.ts +4 -0
- package/dist/shapes/index.d.ts.map +1 -0
- package/dist/shapes/index.js +4 -0
- package/dist/shapes/index.js.map +1 -0
- package/dist/shapes/registry.d.ts +20 -0
- package/dist/shapes/registry.d.ts.map +1 -0
- package/dist/shapes/registry.js +88 -0
- package/dist/shapes/registry.js.map +1 -0
- package/dist/shapes/types.d.ts +204 -0
- package/dist/shapes/types.d.ts.map +1 -0
- package/dist/shapes/types.js +2 -0
- package/dist/shapes/types.js.map +1 -0
- package/dist/snapshot-chunks/adapters/s3.d.ts +63 -0
- package/dist/snapshot-chunks/adapters/s3.d.ts.map +1 -0
- package/dist/snapshot-chunks/adapters/s3.js +50 -0
- package/dist/snapshot-chunks/adapters/s3.js.map +1 -0
- package/dist/snapshot-chunks/db-metadata.d.ts +33 -0
- package/dist/snapshot-chunks/db-metadata.d.ts.map +1 -0
- package/dist/snapshot-chunks/db-metadata.js +169 -0
- package/dist/snapshot-chunks/db-metadata.js.map +1 -0
- package/dist/snapshot-chunks/index.d.ts +9 -0
- package/dist/snapshot-chunks/index.d.ts.map +1 -0
- package/dist/snapshot-chunks/index.js +9 -0
- package/dist/snapshot-chunks/index.js.map +1 -0
- package/dist/snapshot-chunks/types.d.ts +65 -0
- package/dist/snapshot-chunks/types.d.ts.map +1 -0
- package/dist/snapshot-chunks/types.js +8 -0
- package/dist/snapshot-chunks/types.js.map +1 -0
- package/dist/snapshot-chunks.d.ts +59 -0
- package/dist/snapshot-chunks.d.ts.map +1 -0
- package/dist/snapshot-chunks.js +202 -0
- package/dist/snapshot-chunks.js.map +1 -0
- package/dist/stats.d.ts +19 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +57 -0
- package/dist/stats.js.map +1 -0
- package/dist/subscriptions/index.d.ts +2 -0
- package/dist/subscriptions/index.d.ts.map +1 -0
- package/dist/subscriptions/index.js +2 -0
- package/dist/subscriptions/index.js.map +1 -0
- package/dist/subscriptions/resolve.d.ts +35 -0
- package/dist/subscriptions/resolve.d.ts.map +1 -0
- package/dist/subscriptions/resolve.js +134 -0
- package/dist/subscriptions/resolve.js.map +1 -0
- package/package.json +80 -0
- package/src/blobs/adapters/database.ts +290 -0
- package/src/blobs/adapters/s3.ts +271 -0
- package/src/blobs/index.ts +9 -0
- package/src/blobs/manager.ts +600 -0
- package/src/blobs/migrate.ts +150 -0
- package/src/blobs/types.ts +70 -0
- package/src/clients.ts +21 -0
- package/src/compaction.ts +77 -0
- package/src/dialect/index.ts +5 -0
- package/src/dialect/types.ts +222 -0
- package/src/helpers/conflict.ts +64 -0
- package/src/helpers/emitted-change.ts +69 -0
- package/src/helpers/index.ts +10 -0
- package/src/helpers/paginate.ts +82 -0
- package/src/helpers/scope-strings.ts +101 -0
- package/src/index.ts +28 -0
- package/src/migrate.ts +20 -0
- package/src/proxy/handler.ts +152 -0
- package/src/proxy/index.ts +18 -0
- package/src/proxy/mutation-detector.ts +83 -0
- package/src/proxy/oplog.ts +144 -0
- package/src/proxy/registry.ts +56 -0
- package/src/proxy/types.ts +46 -0
- package/src/prune.ts +200 -0
- package/src/pull.ts +551 -0
- package/src/push.ts +457 -0
- package/src/realtime/in-memory.ts +33 -0
- package/src/realtime/index.ts +5 -0
- package/src/realtime/types.ts +55 -0
- package/src/schema.ts +172 -0
- package/src/shapes/create-handler.ts +590 -0
- package/src/shapes/index.ts +3 -0
- package/src/shapes/registry.ts +109 -0
- package/src/shapes/types.ts +267 -0
- package/src/snapshot-chunks/adapters/s3.ts +68 -0
- package/src/snapshot-chunks/db-metadata.ts +238 -0
- package/src/snapshot-chunks/index.ts +9 -0
- package/src/snapshot-chunks/types.ts +79 -0
- package/src/snapshot-chunks.ts +301 -0
- package/src/stats.ts +104 -0
- package/src/subscriptions/index.ts +1 -0
- package/src/subscriptions/resolve.ts +185 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/server - Proxy Types
|
|
3
|
+
*
|
|
4
|
+
* Types for database proxy with automatic oplog generation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { StoredScopes } from '@syncular/core';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Proxy table handler for mutations.
|
|
11
|
+
*
|
|
12
|
+
* Defines how to compute scopes for rows affected by proxy operations.
|
|
13
|
+
*/
|
|
14
|
+
export interface ProxyTableHandler {
|
|
15
|
+
/** Database table name */
|
|
16
|
+
table: string;
|
|
17
|
+
/** Primary key column name (default: 'id') */
|
|
18
|
+
primaryKey?: string;
|
|
19
|
+
/** Version column name (default: 'server_version') */
|
|
20
|
+
versionColumn?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Compute scope values for a row.
|
|
23
|
+
*
|
|
24
|
+
* This determines which sync subscriptions will see changes to this row.
|
|
25
|
+
* Returns a JSONB-compatible object of scope key-value pairs.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* computeScopes: (row) => ({
|
|
29
|
+
* user_id: String(row.user_id),
|
|
30
|
+
* project_id: String(row.project_id),
|
|
31
|
+
* })
|
|
32
|
+
*/
|
|
33
|
+
computeScopes(row: Record<string, unknown>): StoredScopes;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Context for executing a proxied query.
|
|
38
|
+
*/
|
|
39
|
+
export interface ProxyQueryContext {
|
|
40
|
+
/** Actor ID for oplog tracking */
|
|
41
|
+
actorId: string;
|
|
42
|
+
/** Client ID for oplog tracking */
|
|
43
|
+
clientId: string;
|
|
44
|
+
/** Logical partition key (default: 'default') */
|
|
45
|
+
partitionId?: string;
|
|
46
|
+
}
|
package/src/prune.ts
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/server - Pruning utilities
|
|
3
|
+
*
|
|
4
|
+
* Pruning strategy (initial):
|
|
5
|
+
* - Track per-client cursors in `sync_client_cursors`
|
|
6
|
+
* - Consider a client "active" if it has pulled within `activeWindowMs`
|
|
7
|
+
* - Compute watermark = min(cursor) across active clients (ignoring cursor < 0)
|
|
8
|
+
* - Delete commits with commit_seq <= watermark (cascade deletes changes)
|
|
9
|
+
*
|
|
10
|
+
* Clients behind pruned history will be forced to bootstrap.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
DeleteQueryBuilder,
|
|
15
|
+
DeleteResult,
|
|
16
|
+
Kysely,
|
|
17
|
+
SelectQueryBuilder,
|
|
18
|
+
SqlBool,
|
|
19
|
+
} from 'kysely';
|
|
20
|
+
import { sql } from 'kysely';
|
|
21
|
+
import type { SyncCoreDb } from './schema';
|
|
22
|
+
|
|
23
|
+
type EmptySelection = Record<string, never>;
|
|
24
|
+
|
|
25
|
+
function coerceNumber(value: unknown): number | null {
|
|
26
|
+
if (value === null || value === undefined) return null;
|
|
27
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : null;
|
|
28
|
+
if (typeof value === 'bigint')
|
|
29
|
+
return Number.isFinite(Number(value)) ? Number(value) : null;
|
|
30
|
+
if (typeof value === 'string') {
|
|
31
|
+
const n = Number(value);
|
|
32
|
+
return Number.isFinite(n) ? n : null;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface PruneOptions {
|
|
38
|
+
/** Clients with updated_at older than this are ignored for watermark. Default: 14 days. */
|
|
39
|
+
activeWindowMs?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Time-based retention safety cap.
|
|
42
|
+
*
|
|
43
|
+
* The server prunes commits older than this age even if watermark pruning
|
|
44
|
+
* is stuck (e.g. a client never advances).
|
|
45
|
+
* Default: 30 days.
|
|
46
|
+
*/
|
|
47
|
+
fallbackMaxAgeMs?: number;
|
|
48
|
+
/** Soft cap: keep at least this many newest commits even if watermark is high. Default: 1000. */
|
|
49
|
+
keepNewestCommits?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function computePruneWatermarkCommitSeq<DB extends SyncCoreDb>(
|
|
53
|
+
db: Kysely<DB>,
|
|
54
|
+
options: PruneOptions = {}
|
|
55
|
+
): Promise<number> {
|
|
56
|
+
type SyncDb = Pick<Kysely<SyncCoreDb>, 'selectFrom'>;
|
|
57
|
+
const syncDb = db as SyncDb;
|
|
58
|
+
|
|
59
|
+
const activeWindowMs = options.activeWindowMs ?? 14 * 24 * 60 * 60 * 1000;
|
|
60
|
+
const cutoffIso = new Date(Date.now() - activeWindowMs).toISOString();
|
|
61
|
+
|
|
62
|
+
const cursorsQ = syncDb.selectFrom(
|
|
63
|
+
'sync_client_cursors'
|
|
64
|
+
) as SelectQueryBuilder<SyncCoreDb, 'sync_client_cursors', EmptySelection>;
|
|
65
|
+
|
|
66
|
+
const row = await cursorsQ
|
|
67
|
+
.select(({ fn }) => fn.min('cursor').as('minCursor'))
|
|
68
|
+
.where(sql<SqlBool>`updated_at >= ${cutoffIso}`)
|
|
69
|
+
.where(sql<SqlBool>`cursor >= ${0}`)
|
|
70
|
+
.executeTakeFirst();
|
|
71
|
+
|
|
72
|
+
const minCursor = coerceNumber(row?.minCursor) ?? 0;
|
|
73
|
+
|
|
74
|
+
const fallbackMaxAgeMs = options.fallbackMaxAgeMs ?? 30 * 24 * 60 * 60 * 1000;
|
|
75
|
+
if (fallbackMaxAgeMs <= 0) return minCursor;
|
|
76
|
+
|
|
77
|
+
const ageCutoffIso = new Date(Date.now() - fallbackMaxAgeMs).toISOString();
|
|
78
|
+
const commitsQ = syncDb.selectFrom('sync_commits') as SelectQueryBuilder<
|
|
79
|
+
SyncCoreDb,
|
|
80
|
+
'sync_commits',
|
|
81
|
+
EmptySelection
|
|
82
|
+
>;
|
|
83
|
+
|
|
84
|
+
const ageRow = await commitsQ
|
|
85
|
+
.select(({ fn }) => fn.max('commit_seq').as('maxSeq'))
|
|
86
|
+
.where(sql<SqlBool>`created_at < ${ageCutoffIso}`)
|
|
87
|
+
.executeTakeFirst();
|
|
88
|
+
|
|
89
|
+
const ageSeq = coerceNumber(ageRow?.maxSeq) ?? 0;
|
|
90
|
+
return Math.max(minCursor, ageSeq);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function pruneSync<DB extends SyncCoreDb>(
|
|
94
|
+
db: Kysely<DB>,
|
|
95
|
+
args: { watermarkCommitSeq: number; keepNewestCommits?: number }
|
|
96
|
+
): Promise<number> {
|
|
97
|
+
if (args.watermarkCommitSeq <= 0) return 0;
|
|
98
|
+
|
|
99
|
+
type SyncDb = Pick<Kysely<SyncCoreDb>, 'deleteFrom' | 'selectFrom'>;
|
|
100
|
+
const syncDb = db as SyncDb;
|
|
101
|
+
|
|
102
|
+
const keepNewestCommits = args.keepNewestCommits ?? 1000;
|
|
103
|
+
|
|
104
|
+
// Don't delete the newest N commits (even if watermark is higher)
|
|
105
|
+
const commitsQ = syncDb.selectFrom('sync_commits') as SelectQueryBuilder<
|
|
106
|
+
SyncCoreDb,
|
|
107
|
+
'sync_commits',
|
|
108
|
+
EmptySelection
|
|
109
|
+
>;
|
|
110
|
+
|
|
111
|
+
const maxRow = await commitsQ
|
|
112
|
+
.select(({ fn }) => fn.max('commit_seq').as('maxSeq'))
|
|
113
|
+
.executeTakeFirst();
|
|
114
|
+
|
|
115
|
+
const maxSeq = coerceNumber(maxRow?.maxSeq) ?? 0;
|
|
116
|
+
const minKept = Math.max(0, maxSeq - keepNewestCommits);
|
|
117
|
+
const pruneUpTo = Math.min(args.watermarkCommitSeq, minKept);
|
|
118
|
+
|
|
119
|
+
if (pruneUpTo <= 0) return 0;
|
|
120
|
+
|
|
121
|
+
// Delete dependent rows explicitly to be robust across dialects and older
|
|
122
|
+
// schemas that may not have FK cascade enabled.
|
|
123
|
+
await (
|
|
124
|
+
syncDb.deleteFrom('sync_table_commits') as DeleteQueryBuilder<
|
|
125
|
+
SyncCoreDb,
|
|
126
|
+
'sync_table_commits',
|
|
127
|
+
DeleteResult
|
|
128
|
+
>
|
|
129
|
+
)
|
|
130
|
+
.where(sql<SqlBool>`commit_seq <= ${pruneUpTo}`)
|
|
131
|
+
.executeTakeFirst();
|
|
132
|
+
|
|
133
|
+
await (
|
|
134
|
+
syncDb.deleteFrom('sync_changes') as DeleteQueryBuilder<
|
|
135
|
+
SyncCoreDb,
|
|
136
|
+
'sync_changes',
|
|
137
|
+
DeleteResult
|
|
138
|
+
>
|
|
139
|
+
)
|
|
140
|
+
.where(sql<SqlBool>`commit_seq <= ${pruneUpTo}`)
|
|
141
|
+
.executeTakeFirst();
|
|
142
|
+
|
|
143
|
+
const res = await (
|
|
144
|
+
syncDb.deleteFrom('sync_commits') as DeleteQueryBuilder<
|
|
145
|
+
SyncCoreDb,
|
|
146
|
+
'sync_commits',
|
|
147
|
+
DeleteResult
|
|
148
|
+
>
|
|
149
|
+
)
|
|
150
|
+
.where(sql<SqlBool>`commit_seq <= ${pruneUpTo}`)
|
|
151
|
+
.executeTakeFirst();
|
|
152
|
+
|
|
153
|
+
return Number(res?.numDeletedRows ?? 0);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
interface PruneState {
|
|
157
|
+
lastPruneAtMs: number;
|
|
158
|
+
pruneInFlight: Promise<number> | null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const pruneStateByDb = new WeakMap<object, PruneState>();
|
|
162
|
+
|
|
163
|
+
function getPruneState(db: object): PruneState {
|
|
164
|
+
const existing = pruneStateByDb.get(db);
|
|
165
|
+
if (existing) return existing;
|
|
166
|
+
|
|
167
|
+
const created: PruneState = {
|
|
168
|
+
lastPruneAtMs: 0,
|
|
169
|
+
pruneInFlight: null,
|
|
170
|
+
};
|
|
171
|
+
pruneStateByDb.set(db, created);
|
|
172
|
+
return created;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function maybePruneSync<DB extends SyncCoreDb>(
|
|
176
|
+
db: Kysely<DB>,
|
|
177
|
+
args: { minIntervalMs: number; options?: PruneOptions }
|
|
178
|
+
): Promise<number> {
|
|
179
|
+
const state = getPruneState(db);
|
|
180
|
+
const now = Date.now();
|
|
181
|
+
if (now - state.lastPruneAtMs < args.minIntervalMs) return 0;
|
|
182
|
+
|
|
183
|
+
if (state.pruneInFlight) return state.pruneInFlight;
|
|
184
|
+
|
|
185
|
+
state.pruneInFlight = (async () => {
|
|
186
|
+
try {
|
|
187
|
+
const watermark = await computePruneWatermarkCommitSeq(db, args.options);
|
|
188
|
+
const deleted = await pruneSync(db, {
|
|
189
|
+
watermarkCommitSeq: watermark,
|
|
190
|
+
keepNewestCommits: args.options?.keepNewestCommits,
|
|
191
|
+
});
|
|
192
|
+
state.lastPruneAtMs = Date.now();
|
|
193
|
+
return deleted;
|
|
194
|
+
} finally {
|
|
195
|
+
state.pruneInFlight = null;
|
|
196
|
+
}
|
|
197
|
+
})();
|
|
198
|
+
|
|
199
|
+
return state.pruneInFlight;
|
|
200
|
+
}
|