@minnowdb/core 0.10.0 → 0.10.2
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/engine/auto-store.d.ts +15 -3
- package/dist/engine/auto-store.js +48 -6
- package/dist/engine/client.js +23 -7
- package/dist/engine/database.js +347 -862
- package/dist/engine/index-terms.js +627 -0
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/index.js +2 -1
- package/dist/engine/live-maintenance.js +220 -0
- package/dist/engine/schema.js +2 -1
- package/dist/engine/sql-functions.js +3 -2
- package/dist/engine/sql-quote.js +6 -0
- package/dist/engine/vector.js +1 -3
- package/dist/engine/worker-host.js +2 -0
- package/dist/engine/worker-server.js +4 -5
- package/dist/engine/worker-store-auto.js +2 -2
- package/dist/engine/write-coordinator.js +21 -1
- package/dist/storage/indexeddb.d.ts +18 -0
- package/dist/storage/indexeddb.js +293 -211
- package/dist/storage/opfs/coordination-helpers.js +54 -0
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +3 -2
- package/dist/storage/opfs/leader.js +44 -5
- package/dist/storage/opfs/rpc.js +3 -1
- package/dist/storage/opfs/store.d.ts +12 -0
- package/dist/storage/opfs/store.js +59 -12
- package/dist/storage/toolkit/wal.js +16 -0
- package/dist/storage/toolkit/wire.js +3 -3
- package/dist/storage/types.d.ts +35 -4
- package/dist/storage/types.js +17 -4
- package/dist/testing/block-store-conformance.js +40 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +9 -0
- package/dist/testing/interaction-simulator.d.ts +319 -0
- package/dist/testing/interaction-simulator.js +1631 -0
- package/dist/transactions/index.d.ts +7 -0
- package/dist/transactions/index.js +17 -3
- package/package.json +4 -1
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { dateMilliseconds } from "../date-value.js";
|
|
2
|
+
import { estimateValuesBytes } from "./byte-estimates.js";
|
|
3
|
+
import { planMemoKey, sameQueryRow as sameLiveRow } from "./query-cache.js";
|
|
4
|
+
import { externalizeQueryResult } from "./query.js";
|
|
5
|
+
import { compareSqlValues } from "./sql-semantics.js";
|
|
6
|
+
const LIVE_HIDDEN_PREFIX = "__minnow_live_";
|
|
7
|
+
const LIVE_KEY_ALIAS = `${LIVE_HIDDEN_PREFIX}key`;
|
|
8
|
+
const LIVE_ORDER_ALIAS = `${LIVE_HIDDEN_PREFIX}order_`;
|
|
9
|
+
const LIVE_MAINTENANCE_MAX_DELTA_ROWS = 2048;
|
|
10
|
+
const LIVE_WINDOW_MARGIN_MIN = 16;
|
|
11
|
+
const LIVE_WINDOW_MARGIN_MAX = 64;
|
|
12
|
+
function liveKeyToken(value) {
|
|
13
|
+
if (typeof value === "number")
|
|
14
|
+
return `n:${String(value)}`;
|
|
15
|
+
if (typeof value === "string")
|
|
16
|
+
return `s:${value}`;
|
|
17
|
+
if (typeof value === "boolean")
|
|
18
|
+
return value ? "b:1" : "b:0";
|
|
19
|
+
if (value instanceof Date)
|
|
20
|
+
return `d:${String(dateMilliseconds(value))}`;
|
|
21
|
+
return "z";
|
|
22
|
+
}
|
|
23
|
+
function splitLiveHiddenColumns(executed, state) {
|
|
24
|
+
const publicCount = state.publicColumns.length;
|
|
25
|
+
const hidden = executed.columns.slice(publicCount);
|
|
26
|
+
const count = executed.rows.length;
|
|
27
|
+
const keys = new Array(count);
|
|
28
|
+
const order = state.orderTerms.map(() => new Array(count));
|
|
29
|
+
for (let index = 0; index < count; index += 1) {
|
|
30
|
+
const row = executed.rows[index] ?? {};
|
|
31
|
+
keys[index] = liveKeyToken(row[LIVE_KEY_ALIAS] ?? null);
|
|
32
|
+
for (const [term, { alias }] of state.orderTerms.entries()) {
|
|
33
|
+
const values = order[term];
|
|
34
|
+
if (values !== void 0)
|
|
35
|
+
values[index] = row[alias] ?? null;
|
|
36
|
+
}
|
|
37
|
+
for (const column of hidden)
|
|
38
|
+
Reflect.deleteProperty(row, column);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
result: externalizeQueryResult({
|
|
42
|
+
columns: executed.columns.slice(0, publicCount),
|
|
43
|
+
columnDomains: executed.columnDomains.slice(0, publicCount),
|
|
44
|
+
rows: executed.rows
|
|
45
|
+
}),
|
|
46
|
+
keys,
|
|
47
|
+
order
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function liveMaintainedOutcome(state, previous, previousIndex) {
|
|
51
|
+
const positions = /* @__PURE__ */ new Map();
|
|
52
|
+
let retainedBytes = state.retainedBytes ?? 128 + planMemoKey(state.fullPlan).length * 2 + planMemoKey(state.deltaPlan).length * 2;
|
|
53
|
+
for (const [index, key] of state.keys.entries()) {
|
|
54
|
+
if (positions.has(key))
|
|
55
|
+
throw new TypeError("Duplicate live input key");
|
|
56
|
+
positions.set(key, index);
|
|
57
|
+
if (state.retainedBytes === void 0)
|
|
58
|
+
retainedBytes += liveRowStateBytes(state, index);
|
|
59
|
+
}
|
|
60
|
+
state = { ...state, positions, retainedBytes };
|
|
61
|
+
const visibleCount = state.limit === void 0 ? state.rows.length : Math.min(state.rows.length, state.limit);
|
|
62
|
+
const visible = state.rows.slice(0, visibleCount);
|
|
63
|
+
if (previous === void 0) {
|
|
64
|
+
return {
|
|
65
|
+
result: {
|
|
66
|
+
columns: [...state.publicColumns],
|
|
67
|
+
columnDomains: [...state.columnDomains],
|
|
68
|
+
rows: visible
|
|
69
|
+
},
|
|
70
|
+
state,
|
|
71
|
+
retainedBytes,
|
|
72
|
+
changed: true
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const previousCount = previous.rows.length;
|
|
76
|
+
const retained = new Int32Array(visibleCount);
|
|
77
|
+
let same = visibleCount === previousCount;
|
|
78
|
+
for (let index = 0; index < visibleCount; index += 1) {
|
|
79
|
+
const was = previousIndex?.[index] ?? -1;
|
|
80
|
+
if (was >= 0 && was < previousCount) {
|
|
81
|
+
retained[index] = was;
|
|
82
|
+
if (was !== index)
|
|
83
|
+
same = false;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
retained[index] = -1;
|
|
87
|
+
const before = previous.rows[index];
|
|
88
|
+
const now = visible[index];
|
|
89
|
+
if (before !== void 0 && now !== void 0 && sameLiveRow(before, now, previous.columns)) {
|
|
90
|
+
visible[index] = before;
|
|
91
|
+
retained[index] = index;
|
|
92
|
+
} else
|
|
93
|
+
same = false;
|
|
94
|
+
}
|
|
95
|
+
if (same)
|
|
96
|
+
return { result: previous, state, retainedBytes, changed: false };
|
|
97
|
+
return {
|
|
98
|
+
result: {
|
|
99
|
+
columns: [...state.publicColumns],
|
|
100
|
+
columnDomains: [...state.columnDomains],
|
|
101
|
+
rows: visible
|
|
102
|
+
},
|
|
103
|
+
state,
|
|
104
|
+
retainedBytes,
|
|
105
|
+
changed: true,
|
|
106
|
+
retained
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function liveRowStateBytes(state, index) {
|
|
110
|
+
let bytes = 112 + (state.keys[index]?.length ?? 0) * 2 + estimateValuesBytes(Object.values(state.rows[index] ?? {})) * 2;
|
|
111
|
+
for (const values of state.order)
|
|
112
|
+
bytes += 8 + estimateValuesBytes([values[index]]) * 2;
|
|
113
|
+
return bytes;
|
|
114
|
+
}
|
|
115
|
+
function filterLiveRows(state, keep) {
|
|
116
|
+
const rows = [];
|
|
117
|
+
const keys = [];
|
|
118
|
+
const order = state.order.map(() => new Array());
|
|
119
|
+
const previous = [];
|
|
120
|
+
for (let index = 0; index < state.rows.length; index += 1) {
|
|
121
|
+
const row = state.rows[index];
|
|
122
|
+
if (row === void 0 || !keep(index))
|
|
123
|
+
continue;
|
|
124
|
+
rows.push(row);
|
|
125
|
+
keys.push(state.keys[index] ?? "z");
|
|
126
|
+
for (const [term, values] of state.order.entries()) {
|
|
127
|
+
order[term]?.push(values[index] ?? null);
|
|
128
|
+
}
|
|
129
|
+
previous.push(index);
|
|
130
|
+
}
|
|
131
|
+
return { rows, keys, order, previousIndex: Int32Array.from(previous) };
|
|
132
|
+
}
|
|
133
|
+
function trimLiveRows(rows, count) {
|
|
134
|
+
return {
|
|
135
|
+
rows: rows.rows.slice(0, count),
|
|
136
|
+
keys: rows.keys.slice(0, count),
|
|
137
|
+
order: rows.order.map((values) => values.slice(0, count)),
|
|
138
|
+
previousIndex: rows.previousIndex.slice(0, count)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function liveOrderComparator(terms) {
|
|
142
|
+
return (leftOrder, leftIndex, rightOrder, rightIndex) => {
|
|
143
|
+
for (const [term, { descending, nulls }] of terms.entries()) {
|
|
144
|
+
const a = leftOrder[term]?.[leftIndex] ?? null;
|
|
145
|
+
const b = rightOrder[term]?.[rightIndex] ?? null;
|
|
146
|
+
if (a === null || b === null) {
|
|
147
|
+
if (a === null && b === null)
|
|
148
|
+
continue;
|
|
149
|
+
const nullsFirst = nulls === "first" || nulls === void 0 && descending;
|
|
150
|
+
return a === null ? nullsFirst ? -1 : 1 : nullsFirst ? 1 : -1;
|
|
151
|
+
}
|
|
152
|
+
let comparison = compareSqlValues(a, b);
|
|
153
|
+
if (descending)
|
|
154
|
+
comparison = -comparison;
|
|
155
|
+
if (comparison !== 0)
|
|
156
|
+
return comparison;
|
|
157
|
+
}
|
|
158
|
+
return 0;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function mergeLiveRows(kept, added, compare, ordered) {
|
|
162
|
+
if (added.rows.length === 0)
|
|
163
|
+
return kept;
|
|
164
|
+
const terms = kept.order.length;
|
|
165
|
+
const total = kept.rows.length + added.rows.length;
|
|
166
|
+
const rows = new Array(total);
|
|
167
|
+
const keys = new Array(total);
|
|
168
|
+
const order = Array.from({ length: terms }, () => new Array(total));
|
|
169
|
+
const previousIndex = new Int32Array(total);
|
|
170
|
+
const take = (source, from, to) => {
|
|
171
|
+
rows[to] = source.rows[from] ?? {};
|
|
172
|
+
keys[to] = source.keys[from] ?? "z";
|
|
173
|
+
for (let term = 0; term < terms; term += 1) {
|
|
174
|
+
const values = order[term];
|
|
175
|
+
if (values !== void 0)
|
|
176
|
+
values[to] = source.order[term]?.[from] ?? null;
|
|
177
|
+
}
|
|
178
|
+
previousIndex[to] = source.previousIndex[from] ?? -1;
|
|
179
|
+
};
|
|
180
|
+
if (!ordered) {
|
|
181
|
+
for (let index = 0; index < kept.rows.length; index += 1)
|
|
182
|
+
take(kept, index, index);
|
|
183
|
+
for (let index = 0; index < added.rows.length; index += 1) {
|
|
184
|
+
take(added, index, kept.rows.length + index);
|
|
185
|
+
}
|
|
186
|
+
return { rows, keys, order, previousIndex };
|
|
187
|
+
}
|
|
188
|
+
const addedIndexes = added.rows.map((_, index) => index);
|
|
189
|
+
addedIndexes.sort((left, right) => compare(added.order, left, added.order, right));
|
|
190
|
+
let keptIndex = 0;
|
|
191
|
+
let addedPosition = 0;
|
|
192
|
+
for (let to = 0; to < total; to += 1) {
|
|
193
|
+
const addedIndex = addedIndexes[addedPosition];
|
|
194
|
+
const takeAdded = addedIndex !== void 0 && (keptIndex >= kept.rows.length || compare(added.order, addedIndex, kept.order, keptIndex) < 0);
|
|
195
|
+
if (takeAdded) {
|
|
196
|
+
take(added, addedIndex, to);
|
|
197
|
+
addedPosition += 1;
|
|
198
|
+
} else {
|
|
199
|
+
take(kept, keptIndex, to);
|
|
200
|
+
keptIndex += 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { rows, keys, order, previousIndex };
|
|
204
|
+
}
|
|
205
|
+
export {
|
|
206
|
+
LIVE_HIDDEN_PREFIX,
|
|
207
|
+
LIVE_KEY_ALIAS,
|
|
208
|
+
LIVE_MAINTENANCE_MAX_DELTA_ROWS,
|
|
209
|
+
LIVE_ORDER_ALIAS,
|
|
210
|
+
LIVE_WINDOW_MARGIN_MAX,
|
|
211
|
+
LIVE_WINDOW_MARGIN_MIN,
|
|
212
|
+
filterLiveRows,
|
|
213
|
+
liveKeyToken,
|
|
214
|
+
liveMaintainedOutcome,
|
|
215
|
+
liveOrderComparator,
|
|
216
|
+
liveRowStateBytes,
|
|
217
|
+
mergeLiveRows,
|
|
218
|
+
splitLiveHiddenColumns,
|
|
219
|
+
trimLiveRows
|
|
220
|
+
};
|
package/dist/engine/schema.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { copyDate, dateMilliseconds } from "../date-value.js";
|
|
2
|
+
import { quoteSqlIdentifier } from "./sql-quote.js";
|
|
2
3
|
import { validateColumnDefault, validateEnumValues, validateSqlDomain } from "../storage/types.js";
|
|
3
4
|
import { childExpressions, compileCheckExpression, expressionColumns, hasAggregate, validateDefaultExpression } from "./query.js";
|
|
4
5
|
import { externalSqlDomainValue, normalizeSqlDomainValue } from "./sql-domains.js";
|
|
@@ -927,7 +928,7 @@ function planMigration(catalog, definition, options = {}) {
|
|
|
927
928
|
}
|
|
928
929
|
function typedTable(database, definition) {
|
|
929
930
|
const columnNames = Object.keys(definition.columns);
|
|
930
|
-
const quote =
|
|
931
|
+
const quote = quoteSqlIdentifier;
|
|
931
932
|
const scalarUniqueKey = Object.entries(definition.columns).find(([, columnDefinition]) => columnDefinition.isUnique)?.[0];
|
|
932
933
|
const keyColumns = definition.primaryKey.length > 0 ? [...definition.primaryKey] : scalarUniqueKey === void 0 ? [] : [scalarUniqueKey];
|
|
933
934
|
const normalizedRows = (rows) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { quoteSqlIdentifier } from "./sql-quote.js";
|
|
1
2
|
import { dateIsoString, dateMilliseconds, dateUtcDate, dateUtcDay, dateUtcFullYear, dateUtcHours, dateUtcMinutes, dateUtcMonth, dateUtcSeconds } from "../date-value.js";
|
|
2
3
|
import { MAX_SQL_SCALAR_RESULT_CHARACTERS } from "./cache-limits.js";
|
|
3
4
|
import { dateDomainValue, exactNumericRounded, exactNumericUnary, externalSqlDomainValue, intervalDomainValue, isDateDomainValue, isExactNumeric, protectedSqlTextValue } from "./sql-domains.js";
|
|
@@ -467,7 +468,7 @@ function formatText(template, values) {
|
|
|
467
468
|
if (value === null || value === void 0)
|
|
468
469
|
throw new TypeError("FORMAT %I does not accept NULL");
|
|
469
470
|
const name = rendered(value);
|
|
470
|
-
return /^[a-z_][a-z0-9_]*$/.test(name) ? name :
|
|
471
|
+
return /^[a-z_][a-z0-9_]*$/.test(name) ? name : quoteSqlIdentifier(name);
|
|
471
472
|
}
|
|
472
473
|
if (value === null || value === void 0)
|
|
473
474
|
return "NULL";
|
|
@@ -902,7 +903,7 @@ const simpleScalarFunctions = /* @__PURE__ */ new Map([
|
|
|
902
903
|
returns: "string",
|
|
903
904
|
evaluate: (values) => {
|
|
904
905
|
const name = text("QUOTE_IDENT", values[0]);
|
|
905
|
-
return /^[a-z_][a-z0-9_]*$/.test(name) ? name : bounded(
|
|
906
|
+
return /^[a-z_][a-z0-9_]*$/.test(name) ? name : bounded(quoteSqlIdentifier(name), "QUOTE_IDENT");
|
|
906
907
|
}
|
|
907
908
|
}
|
|
908
909
|
],
|
package/dist/engine/vector.js
CHANGED
|
@@ -1308,7 +1308,6 @@ async function executeBoundPlanAsync(plan, memory, options) {
|
|
|
1308
1308
|
const rows = plan.grouped ? finishGroups(plan, groups.values(), memory) : output.finish();
|
|
1309
1309
|
return finishResult(plan, rows, memory);
|
|
1310
1310
|
}
|
|
1311
|
-
const SELECTED_ROW_COALESCE_GAP = 32;
|
|
1312
1311
|
async function scanSelectedRows(plan, selection, groups, output, memory, options) {
|
|
1313
1312
|
const scanRows = plan.sourceTables[plan.scanSource]?.rowCount ?? 0;
|
|
1314
1313
|
let index = 0;
|
|
@@ -1329,9 +1328,8 @@ async function scanSelectedRows(plan, selection, groups, output, memory, options
|
|
|
1329
1328
|
index += 1;
|
|
1330
1329
|
while (index < selection.length) {
|
|
1331
1330
|
const next = selection[index] ?? 0;
|
|
1332
|
-
if (next
|
|
1331
|
+
if (next !== end || next >= windowEnd || next >= begin + DEFAULT_BATCH_ROWS)
|
|
1333
1332
|
break;
|
|
1334
|
-
}
|
|
1335
1333
|
end = next + 1;
|
|
1336
1334
|
index += 1;
|
|
1337
1335
|
}
|
|
@@ -14,6 +14,8 @@ async function createStore(descriptor, options) {
|
|
|
14
14
|
...descriptor.opfs?.durability === void 0 ? {} : { durability: descriptor.opfs.durability }
|
|
15
15
|
} : { kind, name: descriptor.name, ...descriptor.indexeddb }, options);
|
|
16
16
|
return opened;
|
|
17
|
+
}, {
|
|
18
|
+
opfsDatabaseExists: async (name) => (await import("../storage/opfs/index.js")).opfsDatabaseExists({ name })
|
|
17
19
|
});
|
|
18
20
|
}
|
|
19
21
|
if (descriptor.kind === "memory") {
|
|
@@ -538,7 +538,7 @@ class DatabaseRpcServer {
|
|
|
538
538
|
if (handle === void 0)
|
|
539
539
|
throw new Error(`Unknown handle: ${handleId}`);
|
|
540
540
|
if (handle.type === "write") {
|
|
541
|
-
this.#beginWriteHandleCall(handle);
|
|
541
|
+
await this.#beginWriteHandleCall(handle);
|
|
542
542
|
try {
|
|
543
543
|
return await this.#callWriteHandle(handleId, handle, method, args, context);
|
|
544
544
|
} finally {
|
|
@@ -847,12 +847,11 @@ class DatabaseRpcServer {
|
|
|
847
847
|
#releaseHandleId(id) {
|
|
848
848
|
this.#reservedHandleIds.delete(id);
|
|
849
849
|
}
|
|
850
|
-
#beginWriteHandleCall(handle) {
|
|
850
|
+
async #beginWriteHandleCall(handle) {
|
|
851
|
+
while (handle.activeCalls !== 0)
|
|
852
|
+
await handle.activeCallDone;
|
|
851
853
|
if (!handle.open)
|
|
852
854
|
throw new Error("Write handle is closed");
|
|
853
|
-
if (handle.activeCalls !== 0) {
|
|
854
|
-
throw new Error("Write handle already has a call in flight");
|
|
855
|
-
}
|
|
856
855
|
handle.activeCalls = 1;
|
|
857
856
|
handle.activeCallDone = new Promise((resolve) => {
|
|
858
857
|
handle.finishActiveCall = resolve;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IndexedDbBlockStore } from "../storage/indexeddb.js";
|
|
2
|
-
import { OpfsBlockStore } from "../storage/opfs/index.js";
|
|
2
|
+
import { OpfsBlockStore, opfsDatabaseExists } from "../storage/opfs/index.js";
|
|
3
3
|
import { openAutoStore } from "./auto-store.js";
|
|
4
4
|
import { unsupportedStoreKindError } from "./worker-server.js";
|
|
5
5
|
const autoWorkerStore = async (descriptor, options) => {
|
|
@@ -26,7 +26,7 @@ const autoWorkerStore = async (descriptor, options) => {
|
|
|
26
26
|
name: descriptor.name,
|
|
27
27
|
...descriptor.indexeddb?.durability === void 0 ? {} : { durability: descriptor.indexeddb.durability },
|
|
28
28
|
...descriptor.indexeddb?.uniqueKeyCacheBytes === void 0 ? {} : { uniqueKeyCacheBytes: descriptor.indexeddb.uniqueKeyCacheBytes }
|
|
29
|
-
}));
|
|
29
|
+
}), { opfsDatabaseExists: (name) => opfsDatabaseExists({ name }) });
|
|
30
30
|
default:
|
|
31
31
|
throw unsupportedStoreKindError("auto", descriptor.kind);
|
|
32
32
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
const anonymous = /* @__PURE__ */ new WeakMap();
|
|
2
2
|
const named = /* @__PURE__ */ new Map();
|
|
3
|
+
const bypassing = /* @__PURE__ */ new Set();
|
|
4
|
+
const NOT_GRANTED = /* @__PURE__ */ Symbol("write admission lock not granted");
|
|
3
5
|
const WRITE_ADMISSION_WAIT_MS = 1e4;
|
|
6
|
+
function _resetWriteAdmissionForTests() {
|
|
7
|
+
bypassing.clear();
|
|
8
|
+
}
|
|
4
9
|
async function coordinateWrite(store, run, signal, options = {}) {
|
|
5
10
|
signal.throwIfAborted();
|
|
6
11
|
const name = store.liveQueryChannelName;
|
|
@@ -25,6 +30,19 @@ async function coordinateWrite(store, run, signal, options = {}) {
|
|
|
25
30
|
signal.throwIfAborted();
|
|
26
31
|
if (name === void 0 || locks === void 0)
|
|
27
32
|
return enter();
|
|
33
|
+
const lockName = `minnowdb-write:${name}`;
|
|
34
|
+
if (bypassing.has(name)) {
|
|
35
|
+
const result = await locks.request(lockName, { ifAvailable: true }, async (lock) => {
|
|
36
|
+
if (lock === null)
|
|
37
|
+
return NOT_GRANTED;
|
|
38
|
+
bypassing.delete(name);
|
|
39
|
+
return enter();
|
|
40
|
+
});
|
|
41
|
+
if (result !== NOT_GRANTED)
|
|
42
|
+
return result;
|
|
43
|
+
signal.throwIfAborted();
|
|
44
|
+
return await enter();
|
|
45
|
+
}
|
|
28
46
|
const startedAt = Date.now();
|
|
29
47
|
const wait = { ranOut: false };
|
|
30
48
|
const waitTimer = setTimeout(() => {
|
|
@@ -33,11 +51,12 @@ async function coordinateWrite(store, run, signal, options = {}) {
|
|
|
33
51
|
}, admissionWaitMs);
|
|
34
52
|
waitTimer.unref?.();
|
|
35
53
|
try {
|
|
36
|
-
return await locks.request(
|
|
54
|
+
return await locks.request(lockName, { signal: lockController.signal }, enter);
|
|
37
55
|
} catch (error) {
|
|
38
56
|
if (!wait.ranOut || admitted)
|
|
39
57
|
throw error;
|
|
40
58
|
signal.throwIfAborted();
|
|
59
|
+
bypassing.add(name);
|
|
41
60
|
options.onAdmissionWaitExceeded?.(Date.now() - startedAt);
|
|
42
61
|
return await enter();
|
|
43
62
|
} finally {
|
|
@@ -72,5 +91,6 @@ async function coordinateWrite(store, run, signal, options = {}) {
|
|
|
72
91
|
}
|
|
73
92
|
export {
|
|
74
93
|
WRITE_ADMISSION_WAIT_MS,
|
|
94
|
+
_resetWriteAdmissionForTests,
|
|
75
95
|
coordinateWrite
|
|
76
96
|
};
|
|
@@ -9,6 +9,12 @@ export interface IndexedDbBlockStoreOptions {
|
|
|
9
9
|
* 8 MiB so a table's row count cannot silently become an unbounded resident-memory cost.
|
|
10
10
|
*/
|
|
11
11
|
uniqueKeyCacheBytes?: number;
|
|
12
|
+
/**
|
|
13
|
+
* How long the connection may answer nothing at all, while it has work outstanding, before
|
|
14
|
+
* every waiting call fails with `StorageUnresponsiveError` and the store refuses new work.
|
|
15
|
+
* Defaults to `INDEXEDDB_UNRESPONSIVE_AFTER_MS`.
|
|
16
|
+
*/
|
|
17
|
+
unresponsiveAfterMs?: number;
|
|
12
18
|
}
|
|
13
19
|
export declare class IndexedDbBlockStore implements BlockStore {
|
|
14
20
|
#private;
|
|
@@ -189,3 +195,15 @@ export declare class IndexedDbBlockStore implements BlockStore {
|
|
|
189
195
|
snapshotPeakRetainedBytes: number;
|
|
190
196
|
};
|
|
191
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* How long a connection may go without a single IndexedDB event, while it has work outstanding,
|
|
200
|
+
* before the adapter calls it unresponsive. Nothing in IndexedDB can be cancelled and nothing
|
|
201
|
+
* reports progress, so this is the only way for a store to tell a wedged browser from a busy
|
|
202
|
+
* one: any event anywhere on the connection resets the deadline, which means a transaction
|
|
203
|
+
* queued behind a genuinely long one is never mistaken for a wedge — only total silence is.
|
|
204
|
+
*
|
|
205
|
+
* See `StorageUnresponsiveError` for the wedge this bounds. Thirty seconds is far longer than
|
|
206
|
+
* any single request takes and comfortably inside the worker client's own request deadline, so
|
|
207
|
+
* the typed error is what a caller sees rather than a lost worker.
|
|
208
|
+
*/
|
|
209
|
+
export declare const INDEXEDDB_UNRESPONSIVE_AFTER_MS = 30000;
|