@cortexkit/antigravity-auth-core 2.2.1 → 3.0.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 +24 -2
- package/dist/abort-forwarding.d.ts +3 -0
- package/dist/abort-forwarding.d.ts.map +1 -0
- package/dist/abort-forwarding.js +19 -0
- package/dist/abort-forwarding.js.map +1 -0
- package/dist/account-identity.d.ts +52 -0
- package/dist/account-identity.d.ts.map +1 -0
- package/dist/account-identity.js +66 -0
- package/dist/account-identity.js.map +1 -0
- package/dist/account-manager.d.ts +228 -43
- package/dist/account-manager.d.ts.map +1 -1
- package/dist/account-manager.js +942 -903
- package/dist/account-manager.js.map +1 -1
- package/dist/account-migration.d.ts +309 -0
- package/dist/account-migration.d.ts.map +1 -0
- package/dist/account-migration.js +2282 -0
- package/dist/account-migration.js.map +1 -0
- package/dist/account-repository-codecs.d.ts +82 -0
- package/dist/account-repository-codecs.d.ts.map +1 -0
- package/dist/account-repository-codecs.js +609 -0
- package/dist/account-repository-codecs.js.map +1 -0
- package/dist/account-repository-types.d.ts +831 -0
- package/dist/account-repository-types.d.ts.map +1 -0
- package/dist/account-repository-types.js +137 -0
- package/dist/account-repository-types.js.map +1 -0
- package/dist/account-repository.d.ts +442 -0
- package/dist/account-repository.d.ts.map +1 -0
- package/dist/account-repository.js +2253 -0
- package/dist/account-repository.js.map +1 -0
- package/dist/account-selector.d.ts +470 -0
- package/dist/account-selector.d.ts.map +1 -0
- package/dist/account-selector.js +1383 -0
- package/dist/account-selector.js.map +1 -0
- package/dist/account-storage.d.ts.map +1 -1
- package/dist/account-storage.js +5 -0
- package/dist/account-storage.js.map +1 -1
- package/dist/agy-transport.d.ts.map +1 -1
- package/dist/agy-transport.js +21 -35
- package/dist/agy-transport.js.map +1 -1
- package/dist/antigravity-command-menu.d.ts +171 -0
- package/dist/antigravity-command-menu.d.ts.map +1 -0
- package/dist/antigravity-command-menu.js +523 -0
- package/dist/antigravity-command-menu.js.map +1 -0
- package/dist/atomic-write.d.ts +18 -1
- package/dist/atomic-write.d.ts.map +1 -1
- package/dist/atomic-write.js +15 -3
- package/dist/atomic-write.js.map +1 -1
- package/dist/common-auth-embedded/NOTICE.txt +21 -0
- package/dist/common-auth-embedded/auth-menu/accounts.d.ts +97 -0
- package/dist/common-auth-embedded/auth-menu/accounts.js +387 -0
- package/dist/common-auth-embedded/auth-menu/ansi.d.ts +26 -0
- package/dist/common-auth-embedded/auth-menu/ansi.js +98 -0
- package/dist/common-auth-embedded/auth-menu/confirm.d.ts +7 -0
- package/dist/common-auth-embedded/auth-menu/confirm.js +21 -0
- package/dist/common-auth-embedded/auth-menu/doctor.d.ts +52 -0
- package/dist/common-auth-embedded/auth-menu/doctor.js +81 -0
- package/dist/common-auth-embedded/auth-menu/index.d.ts +17 -0
- package/dist/common-auth-embedded/auth-menu/index.js +12 -0
- package/dist/common-auth-embedded/auth-menu/login.d.ts +55 -0
- package/dist/common-auth-embedded/auth-menu/login.js +83 -0
- package/dist/common-auth-embedded/auth-menu/menu.d.ts +59 -0
- package/dist/common-auth-embedded/auth-menu/menu.js +73 -0
- package/dist/common-auth-embedded/auth-menu/opencode-v1.d.ts +49 -0
- package/dist/common-auth-embedded/auth-menu/opencode-v1.js +47 -0
- package/dist/common-auth-embedded/auth-menu/select.d.ts +21 -0
- package/dist/common-auth-embedded/auth-menu/select.js +159 -0
- package/dist/common-auth-embedded/auth-menu/terminal.d.ts +43 -0
- package/dist/common-auth-embedded/auth-menu/terminal.js +19 -0
- package/dist/common-auth-embedded/claustrum/consumer.d.ts +102 -0
- package/dist/common-auth-embedded/claustrum/consumer.js +298 -0
- package/dist/common-auth-embedded/claustrum/custody.d.ts +175 -0
- package/dist/common-auth-embedded/claustrum/custody.js +354 -0
- package/dist/common-auth-embedded/claustrum/enrollment.d.ts +158 -0
- package/dist/common-auth-embedded/claustrum/enrollment.js +759 -0
- package/dist/common-auth-embedded/claustrum/errors.d.ts +16 -0
- package/dist/common-auth-embedded/claustrum/errors.js +8 -0
- package/dist/common-auth-embedded/claustrum/host-slot.d.ts +39 -0
- package/dist/common-auth-embedded/claustrum/host-slot.js +72 -0
- package/dist/common-auth-embedded/claustrum/index.d.ts +18 -0
- package/dist/common-auth-embedded/claustrum/index.js +22 -0
- package/dist/common-auth-embedded/claustrum/interlock.d.ts +27 -0
- package/dist/common-auth-embedded/claustrum/interlock.js +29 -0
- package/dist/common-auth-embedded/claustrum/roster.d.ts +208 -0
- package/dist/common-auth-embedded/claustrum/roster.js +536 -0
- package/dist/common-auth-embedded/commands/builtins.d.ts +71 -0
- package/dist/common-auth-embedded/commands/builtins.js +534 -0
- package/dist/common-auth-embedded/commands/index.d.ts +10 -0
- package/dist/common-auth-embedded/commands/index.js +5 -0
- package/dist/common-auth-embedded/commands/menu.d.ts +61 -0
- package/dist/common-auth-embedded/commands/menu.js +300 -0
- package/dist/common-auth-embedded/commands/model.d.ts +238 -0
- package/dist/common-auth-embedded/commands/model.js +16 -0
- package/dist/common-auth-embedded/commands/pi.d.ts +23 -0
- package/dist/common-auth-embedded/commands/pi.js +206 -0
- package/dist/common-auth-embedded/commands/seam.d.ts +72 -0
- package/dist/common-auth-embedded/commands/seam.js +301 -0
- package/dist/common-auth-embedded/fs/atomic-write.d.ts +20 -0
- package/dist/common-auth-embedded/fs/atomic-write.js +80 -0
- package/dist/common-auth-embedded/fs/index.d.ts +7 -0
- package/dist/common-auth-embedded/fs/index.js +6 -0
- package/dist/common-auth-embedded/fs/lock-constants.d.ts +14 -0
- package/dist/common-auth-embedded/fs/lock-constants.js +14 -0
- package/dist/common-auth-embedded/fs/refresh-file-lock.d.ts +33 -0
- package/dist/common-auth-embedded/fs/refresh-file-lock.js +501 -0
- package/dist/common-auth-embedded/fs/with-lock.d.ts +33 -0
- package/dist/common-auth-embedded/fs/with-lock.js +48 -0
- package/dist/common-auth-embedded/logger/capture-sink.d.ts +14 -0
- package/dist/common-auth-embedded/logger/capture-sink.js +13 -0
- package/dist/common-auth-embedded/logger/engine.d.ts +100 -0
- package/dist/common-auth-embedded/logger/engine.js +246 -0
- package/dist/common-auth-embedded/logger/index.d.ts +6 -0
- package/dist/common-auth-embedded/logger/index.js +3 -0
- package/dist/common-auth-embedded/logger/redact.d.ts +11 -0
- package/dist/common-auth-embedded/logger/redact.js +83 -0
- package/dist/common-auth-embedded/pi-slot/index.d.ts +3 -0
- package/dist/common-auth-embedded/pi-slot/index.js +2 -0
- package/dist/common-auth-embedded/pi-slot/lock.js +212 -0
- package/dist/common-auth-embedded/pi-slot/slot.d.ts +49 -0
- package/dist/common-auth-embedded/pi-slot/slot.js +383 -0
- package/dist/common-auth-embedded/quota/codec.d.ts +11 -0
- package/dist/common-auth-embedded/quota/codec.js +12 -0
- package/dist/common-auth-embedded/quota/format.d.ts +46 -0
- package/dist/common-auth-embedded/quota/format.js +138 -0
- package/dist/common-auth-embedded/quota/index.d.ts +10 -0
- package/dist/common-auth-embedded/quota/index.js +5 -0
- package/dist/common-auth-embedded/quota/map.d.ts +63 -0
- package/dist/common-auth-embedded/quota/map.js +105 -0
- package/dist/common-auth-embedded/quota/merge.d.ts +48 -0
- package/dist/common-auth-embedded/quota/merge.js +162 -0
- package/dist/common-auth-embedded/quota/projection.d.ts +64 -0
- package/dist/common-auth-embedded/quota/projection.js +134 -0
- package/dist/common-auth-embedded/routing/admission.d.ts +93 -0
- package/dist/common-auth-embedded/routing/admission.js +142 -0
- package/dist/common-auth-embedded/routing/index.d.ts +8 -0
- package/dist/common-auth-embedded/routing/index.js +4 -0
- package/dist/common-auth-embedded/routing/ordered.d.ts +47 -0
- package/dist/common-auth-embedded/routing/ordered.js +58 -0
- package/dist/common-auth-embedded/routing/pins.d.ts +22 -0
- package/dist/common-auth-embedded/routing/pins.js +34 -0
- package/dist/common-auth-embedded/routing/sticky.d.ts +158 -0
- package/dist/common-auth-embedded/routing/sticky.js +339 -0
- package/dist/common-auth-embedded/source-output.json +1090 -0
- package/dist/common-auth-embedded/store/attribution.d.ts +28 -0
- package/dist/common-auth-embedded/store/attribution.js +60 -0
- package/dist/common-auth-embedded/store/errors.d.ts +55 -0
- package/dist/common-auth-embedded/store/errors.js +38 -0
- package/dist/common-auth-embedded/store/hooks.d.ts +12 -0
- package/dist/common-auth-embedded/store/hooks.js +34 -0
- package/dist/common-auth-embedded/store/identity.d.ts +39 -0
- package/dist/common-auth-embedded/store/identity.js +79 -0
- package/dist/common-auth-embedded/store/index.d.ts +21 -0
- package/dist/common-auth-embedded/store/index.js +7 -0
- package/dist/common-auth-embedded/store/mutate.d.ts +189 -0
- package/dist/common-auth-embedded/store/mutate.js +500 -0
- package/dist/common-auth-embedded/store/pool.d.ts +196 -0
- package/dist/common-auth-embedded/store/pool.js +120 -0
- package/dist/common-auth-embedded/store/provider-state.d.ts +131 -0
- package/dist/common-auth-embedded/store/provider-state.js +226 -0
- package/dist/common-auth-embedded/store/publication.d.ts +47 -0
- package/dist/common-auth-embedded/store/publication.js +304 -0
- package/dist/common-auth-embedded/store/pull.d.ts +36 -0
- package/dist/common-auth-embedded/store/pull.js +143 -0
- package/dist/common-auth-embedded/store/refresh-lock.d.ts +63 -0
- package/dist/common-auth-embedded/store/refresh-lock.js +136 -0
- package/dist/common-auth-embedded/store/refresh.d.ts +72 -0
- package/dist/common-auth-embedded/store/refresh.js +225 -0
- package/dist/common-auth-embedded/store/reserved.js +13 -0
- package/dist/common-auth-embedded/store/rows.d.ts +313 -0
- package/dist/common-auth-embedded/store/rows.js +1258 -0
- package/dist/common-auth-embedded/store/runtime.d.ts +36 -0
- package/dist/common-auth-embedded/store/runtime.js +68 -0
- package/dist/common-auth-embedded/store/schema.d.ts +443 -0
- package/dist/common-auth-embedded/store/schema.js +754 -0
- package/dist/common-auth-embedded/store/settings.d.ts +63 -0
- package/dist/common-auth-embedded/store/settings.js +120 -0
- package/dist/common-auth-embedded/store/torn.d.ts +78 -0
- package/dist/common-auth-embedded/store/torn.js +294 -0
- package/dist/common-auth-embedded/util/any-signal.js +42 -0
- package/dist/common-auth-runtime.d.ts +73 -0
- package/dist/common-auth-runtime.d.ts.map +1 -0
- package/dist/common-auth-runtime.js +65 -0
- package/dist/common-auth-runtime.js.map +1 -0
- package/dist/credential-redaction.d.ts +13 -0
- package/dist/credential-redaction.d.ts.map +1 -0
- package/dist/credential-redaction.js +230 -0
- package/dist/credential-redaction.js.map +1 -0
- package/dist/fetch-timeout.d.ts +4 -2
- package/dist/fetch-timeout.d.ts.map +1 -1
- package/dist/fetch-timeout.js +121 -14
- package/dist/fetch-timeout.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/quota-manager.d.ts.map +1 -1
- package/dist/quota-manager.js +35 -10
- package/dist/quota-manager.js.map +1 -1
- package/dist/quota-presentation.d.ts +10 -0
- package/dist/quota-presentation.d.ts.map +1 -0
- package/dist/quota-presentation.js +60 -0
- package/dist/quota-presentation.js.map +1 -0
- package/dist/quota-types.d.ts +2 -0
- package/dist/quota-types.d.ts.map +1 -1
- package/dist/rotation.d.ts +8 -0
- package/dist/rotation.d.ts.map +1 -1
- package/dist/rotation.js +24 -0
- package/dist/rotation.js.map +1 -1
- package/dist/transform/claude.d.ts +7 -3
- package/dist/transform/claude.d.ts.map +1 -1
- package/dist/transform/claude.js +6 -2
- package/dist/transform/claude.js.map +1 -1
- package/dist/transform/types.d.ts +1 -1
- package/dist/transform/types.d.ts.map +1 -1
- package/dist/vault-account-source.d.ts +183 -0
- package/dist/vault-account-source.d.ts.map +1 -0
- package/dist/vault-account-source.js +296 -0
- package/dist/vault-account-source.js.map +1 -0
- package/dist/vault-enrollment-authority.d.ts +16 -0
- package/dist/vault-enrollment-authority.d.ts.map +1 -0
- package/dist/vault-enrollment-authority.js +19 -0
- package/dist/vault-enrollment-authority.js.map +1 -0
- package/dist/vault-family.d.ts +11 -0
- package/dist/vault-family.d.ts.map +1 -0
- package/dist/vault-family.js +24 -0
- package/dist/vault-family.js.map +1 -0
- package/dist/vault-provider-state.d.ts +162 -0
- package/dist/vault-provider-state.d.ts.map +1 -0
- package/dist/vault-provider-state.js +422 -0
- package/dist/vault-provider-state.js.map +1 -0
- package/package.json +39 -2
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Attribution } from './attribution.js';
|
|
2
|
+
import type { PoolLockSpec } from './refresh-lock.js';
|
|
3
|
+
import { type FailureHook, type ProtectFn } from './rows.js';
|
|
4
|
+
import { type StoreRuntime } from './runtime.js';
|
|
5
|
+
export interface PublishPlan {
|
|
6
|
+
operationId: string;
|
|
7
|
+
remove: Array<{
|
|
8
|
+
id: string;
|
|
9
|
+
attribution: Attribution;
|
|
10
|
+
fingerprint?: string;
|
|
11
|
+
}>;
|
|
12
|
+
finalize: Array<{
|
|
13
|
+
id: string;
|
|
14
|
+
attribution: Attribution;
|
|
15
|
+
reservation: string;
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
disabledReason?: string;
|
|
18
|
+
}>;
|
|
19
|
+
order: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface PublicationReceipt {
|
|
22
|
+
operationId: string;
|
|
23
|
+
planDigest: string;
|
|
24
|
+
phase: 'committed' | 'cleaned';
|
|
25
|
+
removed: Array<{
|
|
26
|
+
id: string;
|
|
27
|
+
credentialEpoch: number;
|
|
28
|
+
}>;
|
|
29
|
+
finalized: Array<{
|
|
30
|
+
id: string;
|
|
31
|
+
credentialEpoch: number;
|
|
32
|
+
reservation: string;
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
export interface PublishOptions {
|
|
37
|
+
protect?: ProtectFn;
|
|
38
|
+
extraLocks?: PoolLockSpec[];
|
|
39
|
+
providerLock?: PoolLockSpec;
|
|
40
|
+
onFailure?: FailureHook;
|
|
41
|
+
}
|
|
42
|
+
export interface PublishResult {
|
|
43
|
+
outcome: 'published' | 'cleaned' | 'already-cleaned';
|
|
44
|
+
receipt: PublicationReceipt;
|
|
45
|
+
}
|
|
46
|
+
export declare function publication(rt: StoreRuntime, operationId: string): Promise<PublicationReceipt | undefined>;
|
|
47
|
+
export declare function publishRoster(rt: StoreRuntime, plan: PublishPlan, options?: PublishOptions): Promise<PublishResult>;
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { assertNotInsideHook } from './hooks.js';
|
|
2
|
+
import { notReadyError, readPool, runOperation, withTransaction, } from './mutate.js';
|
|
3
|
+
import { assertBoundRowAttribution, protectIn, validateAttribution, } from './rows.js';
|
|
4
|
+
import { refusal, rowLockSpec } from './runtime.js';
|
|
5
|
+
import { CREDENTIAL_STAMP_KEY, canonicalDigest, fingerprintOf, idProblem, isCredentialEpoch, isRecord, POOL_KEY, PROVIDER_STATE_KEY, parseStamp, rosterOf, rowLockKey, } from './schema.js';
|
|
6
|
+
function receiptsIn(config) {
|
|
7
|
+
const pool = config[POOL_KEY];
|
|
8
|
+
return isRecord(pool) && isRecord(pool.publications) ? pool.publications : {};
|
|
9
|
+
}
|
|
10
|
+
function receiptIn(config, operationId) {
|
|
11
|
+
const receipts = receiptsIn(config);
|
|
12
|
+
if (!Object.hasOwn(receipts, operationId))
|
|
13
|
+
return undefined;
|
|
14
|
+
const raw = receipts[operationId];
|
|
15
|
+
if (!isRecord(raw) ||
|
|
16
|
+
raw.operationId !== operationId ||
|
|
17
|
+
typeof raw.planDigest !== 'string' ||
|
|
18
|
+
!/^[a-f0-9]{64}$/.test(raw.planDigest) ||
|
|
19
|
+
!['committed', 'cleaned'].includes(String(raw.phase)) ||
|
|
20
|
+
!Array.isArray(raw.removed) ||
|
|
21
|
+
!Array.isArray(raw.finalized) ||
|
|
22
|
+
raw.removed.some((ref) => !isRecord(ref) ||
|
|
23
|
+
typeof ref.id !== 'string' ||
|
|
24
|
+
!isCredentialEpoch(ref.credentialEpoch)) ||
|
|
25
|
+
raw.finalized.some((ref) => !isRecord(ref) ||
|
|
26
|
+
typeof ref.id !== 'string' ||
|
|
27
|
+
!isCredentialEpoch(ref.credentialEpoch) ||
|
|
28
|
+
typeof ref.reservation !== 'string' ||
|
|
29
|
+
!ref.reservation ||
|
|
30
|
+
typeof ref.enabled !== 'boolean'))
|
|
31
|
+
throw refusal('publishRoster', operationId, 'invalid-input', 'the stored publication receipt is malformed');
|
|
32
|
+
return structuredClone(raw);
|
|
33
|
+
}
|
|
34
|
+
export async function publication(rt, operationId) {
|
|
35
|
+
const result = await readPool(rt.ctx);
|
|
36
|
+
if (result.status !== 'ready')
|
|
37
|
+
throw notReadyError(result, 'publishRoster', operationId);
|
|
38
|
+
return receiptIn(result.config, operationId);
|
|
39
|
+
}
|
|
40
|
+
function validatePlan(rt, plan) {
|
|
41
|
+
const fail = () => {
|
|
42
|
+
throw refusal('publishRoster', plan?.operationId ?? '', 'invalid-input', 'the publication plan is invalid');
|
|
43
|
+
};
|
|
44
|
+
if (!isRecord(plan) ||
|
|
45
|
+
typeof plan.operationId !== 'string' ||
|
|
46
|
+
!plan.operationId ||
|
|
47
|
+
!Array.isArray(plan.remove) ||
|
|
48
|
+
!Array.isArray(plan.finalize) ||
|
|
49
|
+
!Array.isArray(plan.order))
|
|
50
|
+
fail();
|
|
51
|
+
const ids = new Set();
|
|
52
|
+
for (const ref of [...plan.remove, ...plan.finalize]) {
|
|
53
|
+
if (!isRecord(ref) ||
|
|
54
|
+
typeof ref.id !== 'string' ||
|
|
55
|
+
idProblem(ref.id) ||
|
|
56
|
+
ids.has(ref.id) ||
|
|
57
|
+
!ref.attribution)
|
|
58
|
+
fail();
|
|
59
|
+
ids.add(ref.id);
|
|
60
|
+
validateAttribution('publishRoster', ref.id, ref.attribution);
|
|
61
|
+
if (ref.attribution.identity !== undefined &&
|
|
62
|
+
(typeof ref.attribution.identity !== 'string' ||
|
|
63
|
+
!ref.attribution.identity))
|
|
64
|
+
fail();
|
|
65
|
+
}
|
|
66
|
+
for (const ref of plan.remove)
|
|
67
|
+
if ((rt.ctx.requireRemovedFingerprint && !ref.fingerprint) ||
|
|
68
|
+
(ref.fingerprint !== undefined && typeof ref.fingerprint !== 'string'))
|
|
69
|
+
fail();
|
|
70
|
+
for (const ref of plan.finalize)
|
|
71
|
+
if (typeof ref.reservation !== 'string' ||
|
|
72
|
+
!ref.reservation ||
|
|
73
|
+
typeof ref.enabled !== 'boolean' ||
|
|
74
|
+
(ref.enabled
|
|
75
|
+
? ref.disabledReason !== undefined
|
|
76
|
+
: typeof ref.disabledReason !== 'string'))
|
|
77
|
+
fail();
|
|
78
|
+
if (plan.order.some((id) => typeof id !== 'string' || idProblem(id)) ||
|
|
79
|
+
new Set(plan.order).size !== plan.order.length)
|
|
80
|
+
fail();
|
|
81
|
+
}
|
|
82
|
+
function saveReceipt(tx, receipt) {
|
|
83
|
+
tx.entries();
|
|
84
|
+
const pool = tx.config[POOL_KEY];
|
|
85
|
+
const receipts = { ...receiptsIn(tx.config) };
|
|
86
|
+
Object.defineProperty(receipts, receipt.operationId, {
|
|
87
|
+
value: receipt,
|
|
88
|
+
enumerable: true,
|
|
89
|
+
writable: true,
|
|
90
|
+
configurable: true,
|
|
91
|
+
});
|
|
92
|
+
pool.publications = receipts;
|
|
93
|
+
// Never forget which plan used an operation id or the actual row epochs it
|
|
94
|
+
// changed: otherwise an old replay could publish again against another pool.
|
|
95
|
+
delete pool.publicationOrder;
|
|
96
|
+
}
|
|
97
|
+
function publicationSurvivors(tx, plan, rows) {
|
|
98
|
+
const rosterIds = new Set();
|
|
99
|
+
for (const raw of rosterOf(tx.config)) {
|
|
100
|
+
if (!isRecord(raw) || typeof raw.id !== 'string')
|
|
101
|
+
continue;
|
|
102
|
+
if (rosterIds.has(raw.id))
|
|
103
|
+
throw refusal('publishRoster', plan.operationId, 'invalid-input', 'publication needs a roster of unique named rows');
|
|
104
|
+
rosterIds.add(raw.id);
|
|
105
|
+
}
|
|
106
|
+
const removedIds = new Set(plan.remove.map((ref) => ref.id));
|
|
107
|
+
const survivors = rows.filter((row) => row.invalid !== 'roster' && !removedIds.has(row.id));
|
|
108
|
+
if (survivors.length !== plan.order.length ||
|
|
109
|
+
plan.order.some((id) => !survivors.some((row) => row.id === id)) ||
|
|
110
|
+
survivors.some((row) => !plan.order.includes(row.id)))
|
|
111
|
+
throw refusal('publishRoster', plan.operationId, 'invalid-input', 'order must name exactly the surviving roster');
|
|
112
|
+
return survivors;
|
|
113
|
+
}
|
|
114
|
+
async function cleanup(tx, receipt) {
|
|
115
|
+
await tx.assertAll();
|
|
116
|
+
for (const ref of receipt.removed) {
|
|
117
|
+
const stamp = parseStamp(tx.stateAccount(ref.id)?.[CREDENTIAL_STAMP_KEY]);
|
|
118
|
+
// A later add uses a later epoch. Cleanup never deletes that successor.
|
|
119
|
+
if (stamp?.credentialEpoch === ref.credentialEpoch && !tx.rosterRow(ref.id))
|
|
120
|
+
tx.dropStateAccount(ref.id);
|
|
121
|
+
}
|
|
122
|
+
for (const ref of receipt.finalized) {
|
|
123
|
+
const account = tx.stateAccount(ref.id);
|
|
124
|
+
const stamp = parseStamp(account?.[CREDENTIAL_STAMP_KEY]);
|
|
125
|
+
if (account &&
|
|
126
|
+
stamp?.credentialEpoch === ref.credentialEpoch &&
|
|
127
|
+
stamp.staged?.reservation === ref.reservation) {
|
|
128
|
+
const next = {
|
|
129
|
+
...account[CREDENTIAL_STAMP_KEY],
|
|
130
|
+
};
|
|
131
|
+
delete next.staged;
|
|
132
|
+
tx.setStateAccount(ref.id, { ...account, [CREDENTIAL_STAMP_KEY]: next });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
await tx.commitState(undefined, { durable: true });
|
|
136
|
+
const cleaned = { ...receipt, phase: 'cleaned' };
|
|
137
|
+
saveReceipt(tx, cleaned);
|
|
138
|
+
await tx.commitConfig({ durable: true });
|
|
139
|
+
return cleaned;
|
|
140
|
+
}
|
|
141
|
+
export async function publishRoster(rt, plan, options = {}) {
|
|
142
|
+
assertNotInsideHook('publishRoster');
|
|
143
|
+
return runOperation(rt.ctx, 'publishRoster', plan?.operationId ?? '', options.onFailure, async (locks, progress) => {
|
|
144
|
+
if (!isRecord(plan) ||
|
|
145
|
+
typeof plan.operationId !== 'string' ||
|
|
146
|
+
!plan.operationId)
|
|
147
|
+
throw refusal('publishRoster', plan?.operationId ?? '', 'invalid-input', 'the publication needs an operation id');
|
|
148
|
+
plan = structuredClone(plan);
|
|
149
|
+
const planDigest = canonicalDigest(plan);
|
|
150
|
+
const named = [
|
|
151
|
+
...(Array.isArray(plan.remove) ? plan.remove : []),
|
|
152
|
+
...(Array.isArray(plan.finalize) ? plan.finalize : []),
|
|
153
|
+
].filter((ref) => isRecord(ref) && typeof ref.id === 'string');
|
|
154
|
+
for (let attempt = 0;; attempt++) {
|
|
155
|
+
const seen = await readPool(rt.ctx);
|
|
156
|
+
if (seen.status !== 'ready')
|
|
157
|
+
throw notReadyError(seen, 'publishRoster', plan.operationId);
|
|
158
|
+
const keys = new Map(named.map(({ id }) => [
|
|
159
|
+
id,
|
|
160
|
+
rowLockKey(seen.rows.find((row) => row.id === id) ?? { id }),
|
|
161
|
+
]));
|
|
162
|
+
// All row keys precede the provider lock. A refresh owns just one row
|
|
163
|
+
// before that lock, so it cannot wait for a second row this call holds.
|
|
164
|
+
for (const key of [...new Set(keys.values())].sort())
|
|
165
|
+
await locks.acquire(rowLockSpec(rt, { id: key }));
|
|
166
|
+
await locks.acquire(options.providerLock ?? rt.providerLock);
|
|
167
|
+
for (const extra of options.extraLocks ?? [])
|
|
168
|
+
await locks.acquire(extra);
|
|
169
|
+
const result = await withTransaction(rt.ctx, locks, progress, { operation: 'publishRoster', rowId: plan.operationId }, async (tx) => {
|
|
170
|
+
const rows = tx.rows();
|
|
171
|
+
if (named.some(({ id }) => rowLockKey(rows.find((row) => row.id === id) ?? { id }) !==
|
|
172
|
+
keys.get(id)))
|
|
173
|
+
return undefined;
|
|
174
|
+
const prior = receiptIn(tx.config, plan.operationId);
|
|
175
|
+
if (prior) {
|
|
176
|
+
if (prior.planDigest !== planDigest)
|
|
177
|
+
throw refusal('publishRoster', plan.operationId, 'publication-mismatch', 'the operation id belongs to a different publication plan');
|
|
178
|
+
if (prior.phase === 'cleaned')
|
|
179
|
+
return { outcome: 'already-cleaned', receipt: prior };
|
|
180
|
+
tx.markPublicationDecision();
|
|
181
|
+
await tx.syncConfig();
|
|
182
|
+
return { outcome: 'cleaned', receipt: await cleanup(tx, prior) };
|
|
183
|
+
}
|
|
184
|
+
validatePlan(rt, plan);
|
|
185
|
+
const survivors = publicationSurvivors(tx, plan, rows);
|
|
186
|
+
const removed = [];
|
|
187
|
+
for (const ref of plan.remove) {
|
|
188
|
+
tx.assertNotStaged(ref.id);
|
|
189
|
+
const row = rows.find((row) => row.id === ref.id);
|
|
190
|
+
if (!row)
|
|
191
|
+
throw refusal('publishRoster', ref.id, 'attribution', 'the removed row no longer exists');
|
|
192
|
+
assertBoundRowAttribution(ref.id, row, ref.attribution, 'publishRoster');
|
|
193
|
+
if (!row.credential ||
|
|
194
|
+
(ref.fingerprint !== undefined &&
|
|
195
|
+
fingerprintOf(row.credential) !== ref.fingerprint))
|
|
196
|
+
throw refusal('publishRoster', ref.id, 'attribution', 'the removed secret no longer matches');
|
|
197
|
+
removed.push(row);
|
|
198
|
+
}
|
|
199
|
+
const finalized = [];
|
|
200
|
+
for (const ref of plan.finalize) {
|
|
201
|
+
const row = rows.find((row) => row.id === ref.id);
|
|
202
|
+
const stamp = parseStamp(tx.stateAccount(ref.id)?.[CREDENTIAL_STAMP_KEY]);
|
|
203
|
+
if (!row ||
|
|
204
|
+
row.staged?.reservation !== ref.reservation ||
|
|
205
|
+
stamp?.staged?.reservation !== ref.reservation ||
|
|
206
|
+
row.enabled)
|
|
207
|
+
throw refusal('publishRoster', ref.id, 'row-staged', 'the finalized row is not disabled under this reservation');
|
|
208
|
+
const account = tx.stateAccount(ref.id);
|
|
209
|
+
const raw = tx.rosterRow(ref.id);
|
|
210
|
+
const storedProviderState = account?.[PROVIDER_STATE_KEY];
|
|
211
|
+
const fullProviderDigest = storedProviderState === undefined
|
|
212
|
+
? undefined
|
|
213
|
+
: canonicalDigest(storedProviderState);
|
|
214
|
+
if (!stamp?.staged ||
|
|
215
|
+
stamp.staged.label !== raw?.label ||
|
|
216
|
+
stamp.staged.disabledReason !== row.disabledReason ||
|
|
217
|
+
stamp.staged.providerState !== fullProviderDigest)
|
|
218
|
+
throw refusal('publishRoster', ref.id, 'row-staged', 'the staged label, disabled reason or full provider state no longer matches its stamp');
|
|
219
|
+
assertBoundRowAttribution(ref.id, row, ref.attribution, 'publishRoster');
|
|
220
|
+
finalized.push(row);
|
|
221
|
+
}
|
|
222
|
+
const tornSurvivor = survivors.find((row) => row.torn);
|
|
223
|
+
if (tornSurvivor)
|
|
224
|
+
throw refusal('publishRoster', plan.operationId, 'attribution', `Row ${tornSurvivor.id} has an interrupted write; complete it with a normal store operation before publishing.`, false);
|
|
225
|
+
const identities = new Set();
|
|
226
|
+
for (const row of survivors) {
|
|
227
|
+
const enabled = plan.finalize.find((ref) => ref.id === row.id)?.enabled ??
|
|
228
|
+
row.enabled;
|
|
229
|
+
if (!enabled ||
|
|
230
|
+
row.type !== 'oauth' ||
|
|
231
|
+
row.identity === undefined)
|
|
232
|
+
continue;
|
|
233
|
+
if (identities.has(row.identity))
|
|
234
|
+
throw refusal('publishRoster', row.id, 'invalid-input', 'the final enabled roster repeats a known identity');
|
|
235
|
+
identities.add(row.identity);
|
|
236
|
+
}
|
|
237
|
+
for (const row of survivors) {
|
|
238
|
+
if (row.identity !== undefined &&
|
|
239
|
+
row.fingerprint !== undefined &&
|
|
240
|
+
survivors.some((other) => other.id !== row.id &&
|
|
241
|
+
other.identity !== undefined &&
|
|
242
|
+
other.identity !== row.identity &&
|
|
243
|
+
other.fingerprint === row.fingerprint))
|
|
244
|
+
throw refusal('publishRoster', row.id, 'attribution', 'the resulting pool gives the same secret different known identities');
|
|
245
|
+
}
|
|
246
|
+
for (const row of finalized)
|
|
247
|
+
if (row.identity !== undefined &&
|
|
248
|
+
removed.some((old) => old.identity !== undefined &&
|
|
249
|
+
old.identity !== row.identity &&
|
|
250
|
+
old.fingerprint === row.fingerprint))
|
|
251
|
+
throw refusal('publishRoster', row.id, 'attribution', 'the staged secret belonged to a different known identity');
|
|
252
|
+
await protectIn(tx, plan.operationId, options.protect);
|
|
253
|
+
const receipt = {
|
|
254
|
+
operationId: plan.operationId,
|
|
255
|
+
planDigest,
|
|
256
|
+
phase: 'committed',
|
|
257
|
+
removed: removed.map((row) => ({
|
|
258
|
+
id: row.id,
|
|
259
|
+
credentialEpoch: row.credentialEpoch ?? 1,
|
|
260
|
+
})),
|
|
261
|
+
finalized: plan.finalize.map((ref, index) => ({
|
|
262
|
+
id: ref.id,
|
|
263
|
+
credentialEpoch: finalized[index]?.credentialEpoch ?? 1,
|
|
264
|
+
reservation: ref.reservation,
|
|
265
|
+
enabled: ref.enabled,
|
|
266
|
+
})),
|
|
267
|
+
};
|
|
268
|
+
for (const ref of plan.remove)
|
|
269
|
+
tx.dropRosterRows(ref.id);
|
|
270
|
+
for (const ref of plan.finalize) {
|
|
271
|
+
const raw = tx.rosterRow(ref.id);
|
|
272
|
+
raw.enabled = ref.enabled;
|
|
273
|
+
const entry = { ...tx.entry(ref.id) };
|
|
274
|
+
delete entry.staged;
|
|
275
|
+
if (ref.enabled)
|
|
276
|
+
delete entry.disabledReason;
|
|
277
|
+
else
|
|
278
|
+
entry.disabledReason = ref.disabledReason;
|
|
279
|
+
tx.setEntry(ref.id, entry);
|
|
280
|
+
}
|
|
281
|
+
const preserved = tx
|
|
282
|
+
.roster()
|
|
283
|
+
.filter((raw) => !isRecord(raw) ||
|
|
284
|
+
typeof raw.id !== 'string' ||
|
|
285
|
+
rows.find((row) => row.id === raw.id)?.invalid === 'roster');
|
|
286
|
+
tx.config.accounts = [
|
|
287
|
+
...plan.order.map((id) => tx.rosterRow(id)),
|
|
288
|
+
...preserved,
|
|
289
|
+
];
|
|
290
|
+
saveReceipt(tx, receipt);
|
|
291
|
+
// This durable rename is the irreversible roster decision. Everything
|
|
292
|
+
// after it follows only the receipt, including after a process restart.
|
|
293
|
+
await tx.syncState();
|
|
294
|
+
await tx.commitConfig({ durable: true, publicationDecision: true });
|
|
295
|
+
return { outcome: 'published', receipt: await cleanup(tx, receipt) };
|
|
296
|
+
}, { completeTorn: false });
|
|
297
|
+
if (result)
|
|
298
|
+
return result;
|
|
299
|
+
await locks.releaseAll();
|
|
300
|
+
if (attempt >= 1)
|
|
301
|
+
throw refusal('publishRoster', plan.operationId, 'row-key-changed', 'a named row changed lock key twice during publication', true);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { PoolOperationError } from './errors.js';
|
|
2
|
+
import type { PoolLogger } from './hooks.js';
|
|
3
|
+
import { type PullReason, type StoreRuntime } from './runtime.js';
|
|
4
|
+
import type { StoredCredential } from './schema.js';
|
|
5
|
+
/** What a pull is issued with: the credential and its attribution tuple. */
|
|
6
|
+
export interface PullRequest {
|
|
7
|
+
id: string;
|
|
8
|
+
credential: StoredCredential;
|
|
9
|
+
credentialEpoch: number;
|
|
10
|
+
identity?: string;
|
|
11
|
+
reason: PullReason;
|
|
12
|
+
}
|
|
13
|
+
export type PullHook = (request: PullRequest) => Promise<unknown>;
|
|
14
|
+
/**
|
|
15
|
+
* Fires quota pulls without ever making a caller wait for one. A pull first
|
|
16
|
+
* gives a row without a per-row entry its entry at epoch 1 (its own locked
|
|
17
|
+
* config write), then captures the credential and the attribution tuple in
|
|
18
|
+
* one locked read, requests the observation, and records it only if
|
|
19
|
+
* attribution still holds. Failures go to the store's pull failure hook.
|
|
20
|
+
*/
|
|
21
|
+
export declare class PullScheduler {
|
|
22
|
+
private readonly rt;
|
|
23
|
+
private readonly hook;
|
|
24
|
+
private readonly onFailure;
|
|
25
|
+
/** Rows a load-time pull has fired for in this process. */
|
|
26
|
+
private readonly firedAtLoad;
|
|
27
|
+
private readonly logger;
|
|
28
|
+
private readonly inflight;
|
|
29
|
+
constructor(rt: () => StoreRuntime, hook: PullHook | undefined, onFailure: ((rowId: string, error: PoolOperationError) => void | Promise<void>) | undefined,
|
|
30
|
+
/** Rows a load-time pull has fired for in this process. */
|
|
31
|
+
firedAtLoad: Set<string>, logger: PoolLogger | undefined);
|
|
32
|
+
fire(id: string, reason: PullReason): void;
|
|
33
|
+
/** Resolves once every pull fired so far has settled. */
|
|
34
|
+
settled(): Promise<void>;
|
|
35
|
+
private run;
|
|
36
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { recordQuota } from './attribution.js';
|
|
2
|
+
import { PoolOperationError } from './errors.js';
|
|
3
|
+
import { toFailure, withTransaction } from './mutate.js';
|
|
4
|
+
import { LockStack } from './refresh-lock.js';
|
|
5
|
+
import { requireBound } from './runtime.js';
|
|
6
|
+
/**
|
|
7
|
+
* Fires quota pulls without ever making a caller wait for one. A pull first
|
|
8
|
+
* gives a row without a per-row entry its entry at epoch 1 (its own locked
|
|
9
|
+
* config write), then captures the credential and the attribution tuple in
|
|
10
|
+
* one locked read, requests the observation, and records it only if
|
|
11
|
+
* attribution still holds. Failures go to the store's pull failure hook.
|
|
12
|
+
*/
|
|
13
|
+
export class PullScheduler {
|
|
14
|
+
rt;
|
|
15
|
+
hook;
|
|
16
|
+
onFailure;
|
|
17
|
+
firedAtLoad;
|
|
18
|
+
logger;
|
|
19
|
+
inflight = new Set();
|
|
20
|
+
constructor(rt, hook, onFailure,
|
|
21
|
+
/** Rows a load-time pull has fired for in this process. */
|
|
22
|
+
firedAtLoad, logger) {
|
|
23
|
+
this.rt = rt;
|
|
24
|
+
this.hook = hook;
|
|
25
|
+
this.onFailure = onFailure;
|
|
26
|
+
this.firedAtLoad = firedAtLoad;
|
|
27
|
+
this.logger = logger;
|
|
28
|
+
}
|
|
29
|
+
fire(id, reason) {
|
|
30
|
+
if (!this.hook)
|
|
31
|
+
return;
|
|
32
|
+
if (reason === 'load') {
|
|
33
|
+
if (this.firedAtLoad.has(id))
|
|
34
|
+
return;
|
|
35
|
+
this.firedAtLoad.add(id);
|
|
36
|
+
}
|
|
37
|
+
const run = this.run(id, reason);
|
|
38
|
+
this.inflight.add(run);
|
|
39
|
+
void run.finally(() => this.inflight.delete(run));
|
|
40
|
+
}
|
|
41
|
+
/** Resolves once every pull fired so far has settled. */
|
|
42
|
+
async settled() {
|
|
43
|
+
while (this.inflight.size > 0)
|
|
44
|
+
await Promise.allSettled([...this.inflight]);
|
|
45
|
+
}
|
|
46
|
+
async run(id, reason) {
|
|
47
|
+
const rt = this.rt();
|
|
48
|
+
const { ctx } = rt;
|
|
49
|
+
const locks = new LockStack(ctx.lockDefaults, ctx.lockEnv);
|
|
50
|
+
const progress = { writes: 0 };
|
|
51
|
+
try {
|
|
52
|
+
const request = await withTransaction(ctx, locks, progress, { operation: 'pull', rowId: id }, async (tx) => {
|
|
53
|
+
// Even a skipped capture must not repair another row on a reserved
|
|
54
|
+
// row's behalf. Decide reservation before pool-wide torn repair.
|
|
55
|
+
if (tx.reservation(id) !== undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
await tx.completeTorn();
|
|
58
|
+
const row = tx.row(id);
|
|
59
|
+
// A row that would pull but for its unbound credential refuses, so
|
|
60
|
+
// the failure hook hears of it instead of the pull vanishing.
|
|
61
|
+
if (row?.unbound &&
|
|
62
|
+
row.enabled &&
|
|
63
|
+
row.type === 'oauth' &&
|
|
64
|
+
row.credential &&
|
|
65
|
+
!row.invalid)
|
|
66
|
+
requireBound('pull', row);
|
|
67
|
+
// Disabled, API-key and credential-less rows never pull.
|
|
68
|
+
if (!row?.candidate || row.type !== 'oauth')
|
|
69
|
+
return undefined;
|
|
70
|
+
if (!row.hasEntry) {
|
|
71
|
+
tx.setEntry(id, { credentialEpoch: 1, needsFirstReading: true });
|
|
72
|
+
await tx.commitConfig();
|
|
73
|
+
}
|
|
74
|
+
const current = tx.row(id);
|
|
75
|
+
if (!current?.credential || current.credentialEpoch === undefined)
|
|
76
|
+
return undefined;
|
|
77
|
+
return {
|
|
78
|
+
id,
|
|
79
|
+
credential: current.credential,
|
|
80
|
+
credentialEpoch: current.credentialEpoch,
|
|
81
|
+
...(current.identity !== undefined
|
|
82
|
+
? { identity: current.identity }
|
|
83
|
+
: {}),
|
|
84
|
+
reason,
|
|
85
|
+
};
|
|
86
|
+
}, { completeTorn: false });
|
|
87
|
+
await locks.releaseAll();
|
|
88
|
+
if (!request) {
|
|
89
|
+
this.firedAtLoad.delete(id);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
await ctx.hold?.('pull-before-request', id);
|
|
93
|
+
let observation;
|
|
94
|
+
try {
|
|
95
|
+
observation = await this.hook?.(request);
|
|
96
|
+
}
|
|
97
|
+
catch (cause) {
|
|
98
|
+
throw new PoolOperationError({
|
|
99
|
+
operation: 'pull',
|
|
100
|
+
rowId: id,
|
|
101
|
+
phase: 'pull',
|
|
102
|
+
retryable: true,
|
|
103
|
+
kind: 'pull',
|
|
104
|
+
message: 'the quota pull rejected',
|
|
105
|
+
cause,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
await recordQuota(rt, id, {
|
|
109
|
+
credentialEpoch: request.credentialEpoch,
|
|
110
|
+
...(request.identity !== undefined
|
|
111
|
+
? { identity: request.identity }
|
|
112
|
+
: {}),
|
|
113
|
+
}, observation);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
// Needs-first-reading stays set; a later load may fire again.
|
|
117
|
+
this.firedAtLoad.delete(id);
|
|
118
|
+
const failure = toFailure(error, 'pull', id, progress);
|
|
119
|
+
if (this.onFailure) {
|
|
120
|
+
try {
|
|
121
|
+
await this.onFailure(id, failure);
|
|
122
|
+
}
|
|
123
|
+
catch (hookError) {
|
|
124
|
+
this.logger?.warn('pull failure hook threw', {
|
|
125
|
+
rowId: id,
|
|
126
|
+
error: hookError instanceof Error
|
|
127
|
+
? hookError.message
|
|
128
|
+
: String(hookError),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
this.logger?.warn('quota pull failed', {
|
|
134
|
+
rowId: id,
|
|
135
|
+
kind: failure.kind,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
await locks.releaseAll();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { acquireRefreshFileLock } from '../fs/refresh-file-lock.js';
|
|
2
|
+
type LockStep = NonNullable<Parameters<typeof acquireRefreshFileLock>[0]['onStep']> extends (step: infer S) => unknown ? S : never;
|
|
3
|
+
/** Tuning shared by every lock the store takes; each field is overridable. */
|
|
4
|
+
export interface PoolLockOptions {
|
|
5
|
+
ttlMs: number;
|
|
6
|
+
timeoutMs: number;
|
|
7
|
+
retryMs: number;
|
|
8
|
+
renew: boolean;
|
|
9
|
+
renewIntervalMs?: number;
|
|
10
|
+
}
|
|
11
|
+
/** A lock: the (name, path) pair naming its file plus optional tuning. */
|
|
12
|
+
export interface PoolLockSpec extends Partial<PoolLockOptions> {
|
|
13
|
+
name: string;
|
|
14
|
+
path: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Store lock defaults: a 15 000 ms bounded wait retried every 50 ms plus
|
|
18
|
+
* jitter, and a renewed 10 000 ms lease.
|
|
19
|
+
*/
|
|
20
|
+
export declare const POOL_LOCK_DEFAULTS: Readonly<PoolLockOptions>;
|
|
21
|
+
export type LockEvent = {
|
|
22
|
+
type: 'acquired' | 'released' | 'contended';
|
|
23
|
+
name: string;
|
|
24
|
+
path: string;
|
|
25
|
+
};
|
|
26
|
+
export interface LockEnvironment {
|
|
27
|
+
now: () => number;
|
|
28
|
+
onLockEvent?: (event: LockEvent) => void;
|
|
29
|
+
onLockStep?: (lock: {
|
|
30
|
+
name: string;
|
|
31
|
+
path: string;
|
|
32
|
+
}, step: LockStep) => void | Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export interface HeldLock {
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly path: string;
|
|
37
|
+
assertOwned(): Promise<void>;
|
|
38
|
+
release(): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Takes one lock, retrying while a live holder has it until `timeoutMs` has
|
|
42
|
+
* passed on the real clock, then throws `LockContentionError`. The lease is
|
|
43
|
+
* asserted as owned once acquired, so a caller never proceeds on a lease that
|
|
44
|
+
* expired during the wait.
|
|
45
|
+
*/
|
|
46
|
+
export declare function acquirePoolLock(spec: PoolLockSpec, defaults: PoolLockOptions, env: LockEnvironment): Promise<HeldLock>;
|
|
47
|
+
/**
|
|
48
|
+
* Locks taken in order and released in reverse. Every acquisition re-asserts
|
|
49
|
+
* the leases already held, because a wait is exactly when an earlier lease
|
|
50
|
+
* can expire unnoticed.
|
|
51
|
+
*/
|
|
52
|
+
export declare class LockStack {
|
|
53
|
+
private readonly defaults;
|
|
54
|
+
private readonly env;
|
|
55
|
+
readonly held: HeldLock[];
|
|
56
|
+
constructor(defaults: PoolLockOptions, env: LockEnvironment);
|
|
57
|
+
acquire(spec: PoolLockSpec): Promise<HeldLock>;
|
|
58
|
+
assertAll(): Promise<void>;
|
|
59
|
+
/** Releases every lock taken after `mark` (a length of `held`), newest first. */
|
|
60
|
+
releaseTo(mark: number): Promise<void>;
|
|
61
|
+
releaseAll(): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
export {};
|