hikoutei 0.10.1-dev → 0.10.3-dev
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/composition/localRuntime.js +2 -2
- package/dist/composition/localRuntime.js.map +1 -1
- package/dist/contracts/domain/model/constants.d.ts +15 -0
- package/dist/contracts/domain/model/constants.d.ts.map +1 -1
- package/dist/contracts/domain/model/constants.js +13 -0
- package/dist/contracts/domain/model/constants.js.map +1 -1
- package/dist/contracts/shared/observability/internalLog.d.ts.map +1 -0
- package/dist/contracts/shared/observability/internalLog.js.map +1 -0
- package/dist/contracts/shared/observability/logEvents.d.ts.map +1 -0
- package/dist/contracts/shared/observability/logEvents.js.map +1 -0
- package/dist/contracts/sheets/mutationCoordinator/CoordinatedSheetsProvider.js +1 -1
- package/dist/contracts/sheets/mutationCoordinator/CoordinatedSheetsProvider.js.map +1 -1
- package/dist/contracts/sheets/mutationCoordinator/laneTelemetry.d.ts +1 -1
- package/dist/contracts/sheets/mutationCoordinator/laneTelemetry.d.ts.map +1 -1
- package/dist/contracts/sheets/mutationCoordinator/laneTelemetry.js +1 -1
- package/dist/contracts/sheets/mutationCoordinator/laneTelemetry.js.map +1 -1
- package/dist/contracts/sheets/timing.d.ts +4 -37
- package/dist/contracts/sheets/timing.d.ts.map +1 -1
- package/dist/contracts/sheets/timing.js +4 -18
- package/dist/contracts/sheets/timing.js.map +1 -1
- package/dist/internal/syncStatus.d.ts.map +1 -1
- package/dist/internal/syncStatus.js +1 -2
- package/dist/internal/syncStatus.js.map +1 -1
- package/dist/orm/persistence/projection/projectionEffects.d.ts.map +1 -1
- package/dist/orm/persistence/projection/projectionEffects.js +2 -2
- package/dist/orm/persistence/projection/projectionEffects.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/GoogleSheetsApiSyncProvider.d.ts +2 -0
- package/dist/sheets/providers/google-sheets-api/GoogleSheetsApiSyncProvider.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/GoogleSheetsApiSyncProvider.js +21 -4
- package/dist/sheets/providers/google-sheets-api/GoogleSheetsApiSyncProvider.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/constants.d.ts +13 -2
- package/dist/sheets/providers/google-sheets-api/constants.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/constants.js +17 -2
- package/dist/sheets/providers/google-sheets-api/constants.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/errors.js +2 -2
- package/dist/sheets/providers/google-sheets-api/errors.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/index.d.ts +1 -1
- package/dist/sheets/providers/google-sheets-api/index.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/index.js +1 -1
- package/dist/sheets/providers/google-sheets-api/index.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/batchBuilder.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/batchBuilder.js +7 -7
- package/dist/sheets/providers/google-sheets-api/model/batchBuilder.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/observation.d.ts +3 -3
- package/dist/sheets/providers/google-sheets-api/model/observation.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/observation.js +11 -8
- package/dist/sheets/providers/google-sheets-api/model/observation.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/plannerReceipt.d.ts +6 -5
- package/dist/sheets/providers/google-sheets-api/model/plannerReceipt.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/plannerReceipt.js +97 -43
- package/dist/sheets/providers/google-sheets-api/model/plannerReceipt.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/postcondition.d.ts +8 -5
- package/dist/sheets/providers/google-sheets-api/model/postcondition.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/postcondition.js +50 -60
- package/dist/sheets/providers/google-sheets-api/model/postcondition.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/preflightContext.d.ts +5 -6
- package/dist/sheets/providers/google-sheets-api/model/preflightContext.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/preflightContext.js +22 -15
- package/dist/sheets/providers/google-sheets-api/model/preflightContext.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/preflightVerify.d.ts +2 -2
- package/dist/sheets/providers/google-sheets-api/model/preflightVerify.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/model/preflightVerify.js +10 -7
- package/dist/sheets/providers/google-sheets-api/model/preflightVerify.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/applyEffects.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/applyEffects.js +2 -3
- package/dist/sheets/providers/google-sheets-api/operations/applyEffects.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/fastAppend.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/fastAppend.js +2 -3
- package/dist/sheets/providers/google-sheets-api/operations/fastAppend.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/preflightOp.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/preflightOp.js +2 -2
- package/dist/sheets/providers/google-sheets-api/operations/preflightOp.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/provisioning.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/provisioning.js +2 -2
- package/dist/sheets/providers/google-sheets-api/operations/provisioning.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/readRows.js +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/readRows.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/rowChecks.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/rowChecks.js +9 -7
- package/dist/sheets/providers/google-sheets-api/operations/rowChecks.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/shared.d.ts +119 -58
- package/dist/sheets/providers/google-sheets-api/operations/shared.d.ts.map +1 -1
- package/dist/sheets/providers/google-sheets-api/operations/shared.js +168 -153
- package/dist/sheets/providers/google-sheets-api/operations/shared.js.map +1 -1
- package/dist/sheets/providers/google-sheets-api/transport/googleSheetsApiTransport.d.ts +1 -1
- package/dist/sheets/providers/google-sheets-api/transport/googleSheetsApiTransport.js +4 -4
- package/dist/sheets/providers/google-sheets-api/transport/googleSheetsApiTransport.js.map +1 -1
- package/dist/storage/sqlite/migrateSchema.d.ts.map +1 -1
- package/dist/storage/sqlite/migrateSchema.js +36 -3
- package/dist/storage/sqlite/migrateSchema.js.map +1 -1
- package/dist/storage/sqlite/schema.d.ts +3 -1
- package/dist/storage/sqlite/schema.d.ts.map +1 -1
- package/dist/storage/sqlite/schema.js +6 -2
- package/dist/storage/sqlite/schema.js.map +1 -1
- package/dist/storage/sqlite/schemaTypes.d.ts +1 -1
- package/dist/storage/sqlite/schemaTypes.d.ts.map +1 -1
- package/dist/sync/outbound/projection/ProjectionEffectFactory.d.ts +19 -0
- package/dist/sync/outbound/projection/ProjectionEffectFactory.d.ts.map +1 -1
- package/dist/sync/outbound/projection/ProjectionEffectFactory.js +35 -0
- package/dist/sync/outbound/projection/ProjectionEffectFactory.js.map +1 -1
- package/dist/sync-engine/api/hikouteiCore.js +2 -2
- package/dist/sync-engine/api/hikouteiCore.js.map +1 -1
- package/dist/sync-engine/api/internalEntityManager.js +2 -2
- package/dist/sync-engine/api/internalEntityManager.js.map +1 -1
- package/dist/sync-engine/sync/outbound/SheetsEffectDispatcher.d.ts +13 -2
- package/dist/sync-engine/sync/outbound/SheetsEffectDispatcher.d.ts.map +1 -1
- package/dist/sync-engine/sync/outbound/SheetsEffectDispatcher.js +37 -5
- package/dist/sync-engine/sync/outbound/SheetsEffectDispatcher.js.map +1 -1
- package/dist/sync-engine/sync/outbound/dispatcherSupport.d.ts +4 -7
- package/dist/sync-engine/sync/outbound/dispatcherSupport.d.ts.map +1 -1
- package/dist/sync-engine/sync/outbound/dispatcherSupport.js +4 -14
- package/dist/sync-engine/sync/outbound/dispatcherSupport.js.map +1 -1
- package/dist/sync-engine/sync/service/SyncServiceBootstrap.js +2 -2
- package/dist/sync-engine/sync/service/SyncServiceBootstrap.js.map +1 -1
- package/dist/sync-engine/sync/service/adopt/adoptionSeeding.js +1 -1
- package/dist/sync-engine/sync/service/adopt/adoptionSeeding.js.map +1 -1
- package/dist/sync-engine/sync/service/effectSupervisor.js +2 -2
- package/dist/sync-engine/sync/service/effectSupervisor.js.map +1 -1
- package/dist/sync-engine/sync/service/pollingSupervisor.js +2 -2
- package/dist/sync-engine/sync/service/pollingSupervisor.js.map +1 -1
- package/dist/sync-engine/sync/service/requestTelemetry.js +2 -2
- package/dist/sync-engine/sync/service/requestTelemetry.js.map +1 -1
- package/dist/sync-engine/sync/service/shutdown.js +1 -1
- package/dist/sync-engine/sync/service/shutdown.js.map +1 -1
- package/dist/sync-engine/sync/service/syncAutoStart.js +2 -2
- package/dist/sync-engine/sync/service/syncAutoStart.js.map +1 -1
- package/dist/sync-engine/sync/sheetsContract/conflictProjectionRegistration.js +1 -1
- package/dist/sync-engine/sync/sheetsContract/conflictProjectionRegistration.js.map +1 -1
- package/package.json +2 -2
- package/dist/contracts/sheets/transportOutcome.d.ts +0 -90
- package/dist/contracts/sheets/transportOutcome.d.ts.map +0 -1
- package/dist/contracts/sheets/transportOutcome.js +0 -191
- package/dist/contracts/sheets/transportOutcome.js.map +0 -1
- package/dist/sheets/providers/google-sheets-api/model/readPlan.d.ts +0 -168
- package/dist/sheets/providers/google-sheets-api/model/readPlan.d.ts.map +0 -1
- package/dist/sheets/providers/google-sheets-api/model/readPlan.js +0 -184
- package/dist/sheets/providers/google-sheets-api/model/readPlan.js.map +0 -1
- package/dist/sheets/providers/google-sheets-api/model/receiptCursor.d.ts +0 -102
- package/dist/sheets/providers/google-sheets-api/model/receiptCursor.d.ts.map +0 -1
- package/dist/sheets/providers/google-sheets-api/model/receiptCursor.js +0 -132
- package/dist/sheets/providers/google-sheets-api/model/receiptCursor.js.map +0 -1
- package/dist/sheets/providers/google-sheets-api/model/rowCheckFormula.d.ts +0 -35
- package/dist/sheets/providers/google-sheets-api/model/rowCheckFormula.d.ts.map +0 -1
- package/dist/sheets/providers/google-sheets-api/model/rowCheckFormula.js +0 -57
- package/dist/sheets/providers/google-sheets-api/model/rowCheckFormula.js.map +0 -1
- package/dist/sheets/providers/google-sheets-api/model/sheetIdAllocator.d.ts +0 -16
- package/dist/sheets/providers/google-sheets-api/model/sheetIdAllocator.d.ts.map +0 -1
- package/dist/sheets/providers/google-sheets-api/model/sheetIdAllocator.js +0 -29
- package/dist/sheets/providers/google-sheets-api/model/sheetIdAllocator.js.map +0 -1
- package/dist/sheets/providers/google-sheets-api/operations/readEngine.d.ts +0 -50
- package/dist/sheets/providers/google-sheets-api/operations/readEngine.d.ts.map +0 -1
- package/dist/sheets/providers/google-sheets-api/operations/readEngine.js +0 -124
- package/dist/sheets/providers/google-sheets-api/operations/readEngine.js.map +0 -1
- package/dist/sheets/providers/google-sheets-api/operations/writeEngine.d.ts +0 -90
- package/dist/sheets/providers/google-sheets-api/operations/writeEngine.d.ts.map +0 -1
- package/dist/sheets/providers/google-sheets-api/operations/writeEngine.js +0 -112
- package/dist/sheets/providers/google-sheets-api/operations/writeEngine.js.map +0 -1
- package/dist/sheets/providers/google-sheets-api/transport/quotaGovernor.d.ts +0 -229
- package/dist/sheets/providers/google-sheets-api/transport/quotaGovernor.d.ts.map +0 -1
- package/dist/sheets/providers/google-sheets-api/transport/quotaGovernor.js +0 -299
- package/dist/sheets/providers/google-sheets-api/transport/quotaGovernor.js.map +0 -1
- package/dist/sheets/providers/google-sheets-api/transport/rateLimiter.d.ts +0 -185
- package/dist/sheets/providers/google-sheets-api/transport/rateLimiter.d.ts.map +0 -1
- package/dist/sheets/providers/google-sheets-api/transport/rateLimiter.js +0 -248
- package/dist/sheets/providers/google-sheets-api/transport/rateLimiter.js.map +0 -1
- package/dist/sheets/providers/google-sheets-api/transport/rawErrorSchemas.d.ts +0 -7
- package/dist/sheets/providers/google-sheets-api/transport/rawErrorSchemas.d.ts.map +0 -1
- package/dist/sheets/providers/google-sheets-api/transport/rawErrorSchemas.js +0 -26
- package/dist/sheets/providers/google-sheets-api/transport/rawErrorSchemas.js.map +0 -1
- package/dist/sync-engine/shared/observability/internalLog.d.ts.map +0 -1
- package/dist/sync-engine/shared/observability/internalLog.js.map +0 -1
- package/dist/sync-engine/shared/observability/logEvents.d.ts.map +0 -1
- package/dist/sync-engine/shared/observability/logEvents.js.map +0 -1
- /package/dist/{sync-engine → contracts}/shared/observability/internalLog.d.ts +0 -0
- /package/dist/{sync-engine → contracts}/shared/observability/internalLog.js +0 -0
- /package/dist/{sync-engine → contracts}/shared/observability/logEvents.d.ts +0 -0
- /package/dist/{sync-engine → contracts}/shared/observability/logEvents.js +0 -0
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Adaptive quota governor layered ON TOP of the request-start limiters.
|
|
3
|
-
*
|
|
4
|
-
* The interval limiters (`RequestStartLimiter`, `ReadQoSScheduler`) space
|
|
5
|
-
* request STARTS but have no notion of the per-minute quota budget, and a
|
|
6
|
-
* remote 429 never feeds back into their pacing. This module adds the two
|
|
7
|
-
* missing pieces, both of which gate request STARTS only and never touch
|
|
8
|
-
* CAS/fencing, prepared-state, batch building, or result encoding:
|
|
9
|
-
*
|
|
10
|
-
* 1. `RollingQuotaBudget` — a sliding-window per-minute TOTAL request-start
|
|
11
|
-
* budget per lane (reads vs writes). It composes with (does not replace)
|
|
12
|
-
* the interval limiters: a start must pass BOTH the budget and its lane's
|
|
13
|
-
* interval pacing. Admission is BOUNDED with the same semantics as the
|
|
14
|
-
* interval limiters: a refused caller reserves nothing, so a refusal can
|
|
15
|
-
* never advance the budget window or the pacing horizon.
|
|
16
|
-
*
|
|
17
|
-
* 2. `QuotaPacingGovernor` — AIMD pacing feedback. When a transport response
|
|
18
|
-
* is classified as quota-limited (HTTP 429 / `RESOURCE_EXHAUSTED`), the
|
|
19
|
-
* offending lane's pacing interval doubles (multiplicative decrease,
|
|
20
|
-
* capped at a multiple of the base). After a sustained quiet period
|
|
21
|
-
* (enough successful request starts, or enough elapsed time since the
|
|
22
|
-
* last quota event), the interval steps back down ~10% at a time
|
|
23
|
-
* (additive increase) and never falls below the base. With no 429 ever
|
|
24
|
-
* observed the governor stays `nominal` at 1x and pacing is byte-for-byte
|
|
25
|
-
* the pre-governor behavior.
|
|
26
|
-
*
|
|
27
|
-
* Why a per-minute budget at all: the binding Google quota for our
|
|
28
|
-
* deployment is the per-user READ rate (60/min); a measured 30k-effect
|
|
29
|
-
* burst peaked near 69/min of reads, producing 429s and a delivery-uncertain
|
|
30
|
-
* requeue spiral. The interval pacing alone (75/min at the 800 ms default)
|
|
31
|
-
* cannot hold a per-minute ceiling because concurrent lock-free readers each
|
|
32
|
-
* reserve successive interval slots without any rate cap per window.
|
|
33
|
-
*/
|
|
34
|
-
import { GOOGLE_SHEETS_API_DEFAULTS } from "../../../../contracts/sheets/googleSheetsApi.js";
|
|
35
|
-
import { PRESENCE_KINDS } from "../../../../contracts/state/index.js";
|
|
36
|
-
import { RATE_LIMIT_OPTIONS_ERROR_CODES, RateLimitOptionsError, } from "./rateLimiter.js";
|
|
37
|
-
/** Lanes the governor paces: the shared read timeline and the write lane. */
|
|
38
|
-
export const QUOTA_GOVERNOR_LANES = {
|
|
39
|
-
READ: "read",
|
|
40
|
-
WRITE: "write",
|
|
41
|
-
};
|
|
42
|
-
/** Lifecycle states of one lane's AIMD pacing. */
|
|
43
|
-
export const QUOTA_PACING_STATES = {
|
|
44
|
-
NOMINAL: "nominal",
|
|
45
|
-
BACKOFF: "backoff",
|
|
46
|
-
RECOVERY: "recovery",
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* AIMD pacing feedback for the two request-start lanes.
|
|
50
|
-
*
|
|
51
|
-
* `intervalMsFor(lane)` is polled by the lane's limiter at each reservation,
|
|
52
|
-
* so a doubled multiplier doubles the effective spacing on the NEXT start
|
|
53
|
-
* without reconstructing the limiter. `recordRequestStart` and
|
|
54
|
-
* `recordQuotaLimited` are the only state inputs; both are fire-and-forget
|
|
55
|
-
* bookkeeping that can never throw out of the admission path.
|
|
56
|
-
*/
|
|
57
|
-
export class QuotaPacingGovernor {
|
|
58
|
-
baseIntervalMs;
|
|
59
|
-
now;
|
|
60
|
-
lanes;
|
|
61
|
-
constructor(options) {
|
|
62
|
-
this.baseIntervalMs = options.baseIntervalMs;
|
|
63
|
-
this.now = options.now ?? Date.now;
|
|
64
|
-
this.lanes = {
|
|
65
|
-
[QUOTA_GOVERNOR_LANES.READ]: {
|
|
66
|
-
multiplier: 1,
|
|
67
|
-
lastQuotaEventAt: 0,
|
|
68
|
-
quietStarts: 0,
|
|
69
|
-
steppedDown: false,
|
|
70
|
-
},
|
|
71
|
-
[QUOTA_GOVERNOR_LANES.WRITE]: {
|
|
72
|
-
multiplier: 1,
|
|
73
|
-
lastQuotaEventAt: 0,
|
|
74
|
-
quietStarts: 0,
|
|
75
|
-
steppedDown: false,
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
/** Effective pacing interval for one lane: base x AIMD multiplier. */
|
|
80
|
-
intervalMsFor(lane) {
|
|
81
|
-
// Lazy time-based recovery: a lane with zero traffic never reaches
|
|
82
|
-
// recordRequestStart, so the quiet window is also advanced wherever the
|
|
83
|
-
// interval is OBSERVED (the lane reservation path polls this).
|
|
84
|
-
this.recoverByElapsed(lane);
|
|
85
|
-
const multiplier = this.lanes[lane].multiplier;
|
|
86
|
-
return multiplier === 1
|
|
87
|
-
? this.baseIntervalMs
|
|
88
|
-
: Math.round(this.baseIntervalMs * multiplier);
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Multiplicative decrease: one quota-limited (429) response doubles this
|
|
92
|
-
* lane's multiplier, capped at `QUOTA_BACKOFF_MAX_MULTIPLIER` of base.
|
|
93
|
-
*/
|
|
94
|
-
recordQuotaLimited(lane) {
|
|
95
|
-
const state = this.lanes[lane];
|
|
96
|
-
state.multiplier = Math.min(state.multiplier * GOOGLE_SHEETS_API_DEFAULTS.QUOTA_BACKOFF_GROWTH_FACTOR, GOOGLE_SHEETS_API_DEFAULTS.QUOTA_BACKOFF_MAX_MULTIPLIER);
|
|
97
|
-
state.lastQuotaEventAt = this.now();
|
|
98
|
-
state.quietStarts = 0;
|
|
99
|
-
state.steppedDown = false;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Additive increase: one successful request start on this lane. After a
|
|
103
|
-
* sustained quiet period (RECOVERY_SUCCESS_THRESHOLD starts, or
|
|
104
|
-
* RECOVERY_QUIET_MS elapsed since the last quota event) the multiplier
|
|
105
|
-
* steps down by ~10% and needs a FRESH quiet period before the next step,
|
|
106
|
-
* so recovery is gradual and a single 429 cannot be answered with a
|
|
107
|
-
* immediate undo. Landing on exactly 1x returns the lane to `nominal`.
|
|
108
|
-
*/
|
|
109
|
-
recordRequestStart(lane) {
|
|
110
|
-
const state = this.lanes[lane];
|
|
111
|
-
if (state.multiplier === 1)
|
|
112
|
-
return;
|
|
113
|
-
state.quietStarts += 1;
|
|
114
|
-
const quietElapsed = this.now() - state.lastQuotaEventAt;
|
|
115
|
-
if (state.quietStarts < GOOGLE_SHEETS_API_DEFAULTS.QUOTA_RECOVERY_SUCCESS_THRESHOLD &&
|
|
116
|
-
quietElapsed < GOOGLE_SHEETS_API_DEFAULTS.QUOTA_RECOVERY_QUIET_MS) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
this.stepDown(state);
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Elapsed-time-only quiet recovery, applied lazily at every state
|
|
123
|
-
* observation: with no traffic on the lane, `recordRequestStart` never
|
|
124
|
-
* runs, so a backed-off lane would otherwise stay in backoff forever.
|
|
125
|
-
*/
|
|
126
|
-
recoverByElapsed(lane) {
|
|
127
|
-
const state = this.lanes[lane];
|
|
128
|
-
if (state.multiplier === 1)
|
|
129
|
-
return;
|
|
130
|
-
if (this.now() - state.lastQuotaEventAt >=
|
|
131
|
-
GOOGLE_SHEETS_API_DEFAULTS.QUOTA_RECOVERY_QUIET_MS) {
|
|
132
|
-
this.stepDown(state);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
/** One additive-increase step: ~10% down, clamped at nominal (1x). */
|
|
136
|
-
stepDown(state) {
|
|
137
|
-
state.multiplier = Math.max(state.multiplier / GOOGLE_SHEETS_API_DEFAULTS.QUOTA_RECOVERY_STEP_FACTOR, 1);
|
|
138
|
-
state.lastQuotaEventAt = this.now();
|
|
139
|
-
state.quietStarts = 0;
|
|
140
|
-
state.steppedDown = state.multiplier > 1;
|
|
141
|
-
}
|
|
142
|
-
/** Read-only snapshot of one lane's pacing state (for telemetry/tests). */
|
|
143
|
-
stateFor(lane) {
|
|
144
|
-
this.recoverByElapsed(lane);
|
|
145
|
-
const state = this.lanes[lane];
|
|
146
|
-
if (state.multiplier <= 1 && !state.steppedDown) {
|
|
147
|
-
return { status: QUOTA_PACING_STATES.NOMINAL, multiplier: 1 };
|
|
148
|
-
}
|
|
149
|
-
return {
|
|
150
|
-
status: state.steppedDown
|
|
151
|
-
? QUOTA_PACING_STATES.RECOVERY
|
|
152
|
-
: QUOTA_PACING_STATES.BACKOFF,
|
|
153
|
-
multiplier: state.multiplier,
|
|
154
|
-
lastQuotaEventAt: state.lastQuotaEventAt,
|
|
155
|
-
quietStarts: state.quietStarts,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Returns true when a classified transport outcome proves the request was
|
|
161
|
-
* quota-limited remotely (HTTP 429 or the allowlisted `RESOURCE_EXHAUSTED`
|
|
162
|
-
* google.rpc code; either marker alone is sufficient because a proxy-emitted
|
|
163
|
-
* 429 can carry no body and a body can omit the status line evidence).
|
|
164
|
-
*/
|
|
165
|
-
export function isQuotaLimitedOutcome(outcome) {
|
|
166
|
-
const byStatus = outcome.httpStatus.kind === PRESENCE_KINDS.PRESENT &&
|
|
167
|
-
outcome.httpStatus.value === GOOGLE_SHEETS_API_DEFAULTS.QUOTA_LIMIT_HTTP_STATUS;
|
|
168
|
-
const byCode = outcome.code.kind === PRESENCE_KINDS.PRESENT &&
|
|
169
|
-
outcome.code.value === GOOGLE_SHEETS_API_DEFAULTS.QUOTA_LIMIT_REMOTE_CODE;
|
|
170
|
-
return byStatus || byCode;
|
|
171
|
-
}
|
|
172
|
-
const DEFAULT_SLEEP = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
173
|
-
/**
|
|
174
|
-
* Sliding-window per-minute request-start budget for ONE lane.
|
|
175
|
-
*
|
|
176
|
-
* Reservations, not completed requests, occupy the window: an admitted
|
|
177
|
-
* caller records the slot it will start at, so a burst of concurrent
|
|
178
|
-
* callers arriving in the same tick each consumes one of the remaining
|
|
179
|
-
* budget slots and the deep ones are refused by the bounded wait, exactly
|
|
180
|
-
* like the interval limiters. A budget refusal records NOTHING, so the
|
|
181
|
-
* window can never be poisoned forward by refused callers.
|
|
182
|
-
*
|
|
183
|
-
* A budget ADMISSION reserves provisionally: a caller that later has to
|
|
184
|
-
* refuse on a downstream gate (e.g. the pacing lane) hands the reservation
|
|
185
|
-
* token to `rollback`, which removes exactly that entry. Rollback never
|
|
186
|
-
* advances (or otherwise touches) the window and can never free a live
|
|
187
|
-
* reservation belonging to another caller.
|
|
188
|
-
*
|
|
189
|
-
* The earliest legal start when the window already holds `max` reservations
|
|
190
|
-
* is `starts[n - max] + windowMs`: at that instant all but the `max - 1`
|
|
191
|
-
* newest reservations have aged out, so admitting keeps the count at `max`.
|
|
192
|
-
*/
|
|
193
|
-
export class RollingQuotaBudget {
|
|
194
|
-
maxStartsPerWindow;
|
|
195
|
-
windowMs;
|
|
196
|
-
now;
|
|
197
|
-
sleep;
|
|
198
|
-
/**
|
|
199
|
-
* Reserved start times, kept in chronological (non-decreasing `reservedAt`)
|
|
200
|
-
* order at all times. Rollback splices its own entry out (order survives)
|
|
201
|
-
* and every new reservation is inserted at its sorted position rather than
|
|
202
|
-
* blindly appended: a rolled-back older slot must never let a later caller
|
|
203
|
-
* land BEHIND a queued future reservation, or the prefix expiry scan would
|
|
204
|
-
* stop at the future entry and retain the trailing expired one forever,
|
|
205
|
-
* leaking budget capacity.
|
|
206
|
-
*/
|
|
207
|
-
starts = [];
|
|
208
|
-
constructor(options) {
|
|
209
|
-
const validRate = options.maxStartsPerWindow === Number.POSITIVE_INFINITY ||
|
|
210
|
-
(Number.isSafeInteger(options.maxStartsPerWindow) && options.maxStartsPerWindow > 0);
|
|
211
|
-
if (!validRate) {
|
|
212
|
-
throw new RateLimitOptionsError(RATE_LIMIT_OPTIONS_ERROR_CODES.BUDGET_POSITIVE_INTEGER_REQUIRED);
|
|
213
|
-
}
|
|
214
|
-
if (!Number.isSafeInteger(options.windowMs) || options.windowMs <= 0) {
|
|
215
|
-
throw new RateLimitOptionsError(RATE_LIMIT_OPTIONS_ERROR_CODES.BUDGET_POSITIVE_INTEGER_REQUIRED);
|
|
216
|
-
}
|
|
217
|
-
this.maxStartsPerWindow = options.maxStartsPerWindow;
|
|
218
|
-
this.windowMs = options.windowMs;
|
|
219
|
-
this.now = options.now ?? Date.now;
|
|
220
|
-
this.sleep = options.sleep ?? DEFAULT_SLEEP;
|
|
221
|
-
}
|
|
222
|
-
/** Number of reservations (admitted, possibly still sleeping) in window. */
|
|
223
|
-
reservedCount() {
|
|
224
|
-
return this.starts.length;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Bounded admission to one budget slot: waits only when the predicted
|
|
228
|
-
* slot is at most `maxWaitMs` out, otherwise returns `refused` having
|
|
229
|
-
* reserved nothing. The decision and the reservation happen synchronously
|
|
230
|
-
* before any await, so concurrent callers can never double-book one slot.
|
|
231
|
-
*/
|
|
232
|
-
async waitForSlot(maxWaitMs) {
|
|
233
|
-
if (!Number.isSafeInteger(maxWaitMs) || maxWaitMs < 0) {
|
|
234
|
-
throw new RateLimitOptionsError(RATE_LIMIT_OPTIONS_ERROR_CODES.MAX_WAIT_NON_NEGATIVE_REQUIRED);
|
|
235
|
-
}
|
|
236
|
-
const now = this.now();
|
|
237
|
-
// Age out every reservation that has left the trailing window.
|
|
238
|
-
const cutoff = now - this.windowMs;
|
|
239
|
-
let firstLive = 0;
|
|
240
|
-
for (;;) {
|
|
241
|
-
const candidate = this.starts[firstLive];
|
|
242
|
-
if (candidate === undefined || candidate.reservedAt > cutoff)
|
|
243
|
-
break;
|
|
244
|
-
firstLive += 1;
|
|
245
|
-
}
|
|
246
|
-
if (firstLive > 0) {
|
|
247
|
-
this.starts = this.starts.slice(firstLive);
|
|
248
|
-
}
|
|
249
|
-
let nextStart = now;
|
|
250
|
-
if (this.starts.length >= this.maxStartsPerWindow) {
|
|
251
|
-
// Window holds its full budget: the earliest legal start is the moment
|
|
252
|
-
// the OLDEST still-counted reservation ages out of the trailing window.
|
|
253
|
-
// The index is in range (length >= max >= 1); the fallback arm only
|
|
254
|
-
// answers `noUncheckedIndexedAccess` and is unreachable.
|
|
255
|
-
const anchor = this.starts[this.starts.length - this.maxStartsPerWindow];
|
|
256
|
-
nextStart = Math.max(now, (anchor?.reservedAt ?? now) + this.windowMs);
|
|
257
|
-
}
|
|
258
|
-
const waited = Math.max(0, nextStart - now);
|
|
259
|
-
if (waited > maxWaitMs) {
|
|
260
|
-
// Refused WITHOUT reserving: the window keeps only admitted slots.
|
|
261
|
-
return { status: "refused", waitedMs: waited, nextStartAt: nextStart };
|
|
262
|
-
}
|
|
263
|
-
const reservation = { reservedAt: nextStart };
|
|
264
|
-
this.insertReservation(reservation);
|
|
265
|
-
if (waited > 0) {
|
|
266
|
-
await this.sleep(waited);
|
|
267
|
-
}
|
|
268
|
-
return { status: "admitted", waitedMs: waited, reservation };
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Inserts one reservation keeping {@link starts} chronologically ordered.
|
|
272
|
-
* The common case (slot at or after every existing entry) is a plain push;
|
|
273
|
-
* only an out-of-order arrival after a rollback walks back from the tail,
|
|
274
|
-
* which is bounded by the window budget (a few hundred entries per minute).
|
|
275
|
-
*/
|
|
276
|
-
insertReservation(reservation) {
|
|
277
|
-
let index = this.starts.length;
|
|
278
|
-
while (index > 0 && this.starts[index - 1].reservedAt > reservation.reservedAt) {
|
|
279
|
-
index -= 1;
|
|
280
|
-
}
|
|
281
|
-
this.starts.splice(index, 0, reservation);
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Rolls back one PROVISIONAL budget reservation returned by `waitForSlot`
|
|
285
|
-
* whose caller never reached request start (e.g. a downstream pacing-lane
|
|
286
|
-
* refusal). Invariants: the removal is matched by object identity, so it
|
|
287
|
-
* can never release a different live reservation; a token that was already
|
|
288
|
-
* rolled back or aged out of the window is a no-op (no double release);
|
|
289
|
-
* rollback never advances the window (remaining reservations and their
|
|
290
|
-
* timestamps are untouched).
|
|
291
|
-
*/
|
|
292
|
-
rollback(reservation) {
|
|
293
|
-
const index = this.starts.indexOf(reservation);
|
|
294
|
-
if (index !== -1) {
|
|
295
|
-
this.starts.splice(index, 1);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
//# sourceMappingURL=quotaGovernor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"quotaGovernor.js","sourceRoot":"","sources":["../../../../../../../../src/sheets/providers/google-sheets-api/transport/quotaGovernor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Request-start interval limiter used by the direct Sheets provider.
|
|
3
|
-
*
|
|
4
|
-
* Google Sheets quota windows are per 100 seconds, so bursty request starts
|
|
5
|
-
* can exhaust the budget before the worker's own latency/backoff reacts.
|
|
6
|
-
* The provider keeps TWO independent limiters — one for reads and one for
|
|
7
|
-
* writes — so reads serialize only against reads and writes only against
|
|
8
|
-
* writes: at most one read (getSpreadsheet) or one write (batchUpdate) can
|
|
9
|
-
* start per interval of its own class, while a read and a write may start
|
|
10
|
-
* concurrently. An idle provider is never throttled; the limiter only
|
|
11
|
-
* waits the time remaining since the previous request START. Admission is
|
|
12
|
-
* BOUNDED: a caller whose slot lies more than `maxWaitMs` out is refused
|
|
13
|
-
* without reserving anything, so a burst of concurrent callers can never
|
|
14
|
-
* queue an arbitrarily long line of future reservations.
|
|
15
|
-
*/
|
|
16
|
-
/** Persistent error codes for rate-limiter option validation. */
|
|
17
|
-
export declare const RATE_LIMIT_OPTIONS_ERROR_CODES: {
|
|
18
|
-
readonly INTERVAL_NON_NEGATIVE_REQUIRED: "rate_limiter_interval_non_negative_required";
|
|
19
|
-
readonly MAX_WAIT_NON_NEGATIVE_REQUIRED: "rate_limiter_max_wait_non_negative_required";
|
|
20
|
-
readonly BUDGET_POSITIVE_INTEGER_REQUIRED: "rate_limiter_budget_positive_integer_required";
|
|
21
|
-
};
|
|
22
|
-
/** Union type derived from the rate-limit error code table. */
|
|
23
|
-
export type RateLimitOptionErrorCode = (typeof RATE_LIMIT_OPTIONS_ERROR_CODES)[keyof typeof RATE_LIMIT_OPTIONS_ERROR_CODES];
|
|
24
|
-
/** Error thrown when rate-limiter options violate non-negative integer constraints. */
|
|
25
|
-
export declare class RateLimitOptionsError extends RangeError {
|
|
26
|
-
readonly code: RateLimitOptionErrorCode;
|
|
27
|
-
constructor(code: RateLimitOptionErrorCode);
|
|
28
|
-
}
|
|
29
|
-
export interface RequestStartLimiterOptions {
|
|
30
|
-
/** Minimum interval between two request starts of this limiter class. */
|
|
31
|
-
readonly intervalMs: number;
|
|
32
|
-
/**
|
|
33
|
-
* Optional live interval source consulted at EVERY reservation (the
|
|
34
|
-
* quota-pacing governor). When present, its value paces this limiter
|
|
35
|
-
* instead of the fixed `intervalMs`, so an AIMD multiplier takes effect on
|
|
36
|
-
* the next start without reconstructing the limiter. Defaults to the fixed
|
|
37
|
-
* `intervalMs` (governor disabled or at 1x: zero behavior change).
|
|
38
|
-
*/
|
|
39
|
-
readonly getIntervalMs?: () => number;
|
|
40
|
-
/** Injectable clock for deterministic tests; defaults to Date.now. */
|
|
41
|
-
readonly now?: () => number;
|
|
42
|
-
/** Injectable sleep used while waiting for the next slot. */
|
|
43
|
-
readonly sleep?: (ms: number) => Promise<void>;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Result of one BOUNDED `waitForSlot(maxWaitMs)` admission attempt.
|
|
47
|
-
*
|
|
48
|
-
* An admitted caller owns the reserved slot and waits `waitedMs`; a refused
|
|
49
|
-
* caller reserved NOTHING (the future slot stays available to later callers)
|
|
50
|
-
* and the limiter's `lastStartAt` is left untouched, so a refusal can never
|
|
51
|
-
* push the pacing horizon forward.
|
|
52
|
-
*/
|
|
53
|
-
export type RequestStartAdmission = {
|
|
54
|
-
readonly status: "admitted";
|
|
55
|
-
readonly waitedMs: number;
|
|
56
|
-
} | {
|
|
57
|
-
readonly status: "refused";
|
|
58
|
-
/** The wait the refused slot WOULD have required. */
|
|
59
|
-
readonly waitedMs: number;
|
|
60
|
-
/** The slot that was NOT reserved; still open to later callers. */
|
|
61
|
-
readonly nextStartAt: number;
|
|
62
|
-
};
|
|
63
|
-
/**
|
|
64
|
-
* Waits only the remaining interval since the previous request start.
|
|
65
|
-
*
|
|
66
|
-
* Returns the number of milliseconds actually waited. A limiter with a zero
|
|
67
|
-
* interval never waits and records every start.
|
|
68
|
-
*/
|
|
69
|
-
export declare class RequestStartLimiter {
|
|
70
|
-
private readonly intervalMs;
|
|
71
|
-
private readonly getIntervalMs;
|
|
72
|
-
private readonly now;
|
|
73
|
-
private readonly sleep;
|
|
74
|
-
private lastStartAt;
|
|
75
|
-
constructor(options: RequestStartLimiterOptions);
|
|
76
|
-
/** Effective pacing interval NOW: governor-multiplied when wired. */
|
|
77
|
-
private currentIntervalMs;
|
|
78
|
-
/** Returns the last recorded request start, when one exists. */
|
|
79
|
-
lastStart(): number | undefined;
|
|
80
|
-
/**
|
|
81
|
-
* Waits for the next request-start slot and records the start time.
|
|
82
|
-
*
|
|
83
|
-
* Returns the number of milliseconds actually waited. A limiter with a zero
|
|
84
|
-
* interval never waits and records every start.
|
|
85
|
-
*
|
|
86
|
-
* The slot reservation happens SYNCHRONOUSLY before any await: each caller
|
|
87
|
-
* reserves the NEXT slot (`nextStart = max(now, lastStartAt + intervalMs)`)
|
|
88
|
-
* and records it immediately, so two concurrent waiters can never compute
|
|
89
|
-
* the same remaining time from the same `lastStartAt`, sleep together, and
|
|
90
|
-
* start at the same instant. A caller that arrives after its reserved slot
|
|
91
|
-
* has already passed waits zero (its reservation collapses to now).
|
|
92
|
-
*/
|
|
93
|
-
waitForSlot(): Promise<number>;
|
|
94
|
-
/**
|
|
95
|
-
* Bounded admission: waits for the next slot only when the predicted wait
|
|
96
|
-
* is at most `maxWaitMs`, otherwise returns a REFUSED result without
|
|
97
|
-
* reserving or advancing the future slot.
|
|
98
|
-
*
|
|
99
|
-
* The bound keeps an unbounded queue of concurrent callers from each
|
|
100
|
-
* reserving a distant future slot: with `maxWaitMs` equal to `intervalMs`
|
|
101
|
-
* the immediate caller and the one exactly one interval out are admitted
|
|
102
|
-
* and every later reservation is refused up front. A refusal never records
|
|
103
|
-
* a start, so the pacing horizon is unchanged and a later caller can still
|
|
104
|
-
* be admitted once time advances past the reserved slot.
|
|
105
|
-
*/
|
|
106
|
-
waitForSlot(maxWaitMs: number): Promise<RequestStartAdmission>;
|
|
107
|
-
}
|
|
108
|
-
/** Read QoS classes sharing ONE request-start timeline inside the scheduler. */
|
|
109
|
-
export type ReadQoSClass = "polling" | "preflight";
|
|
110
|
-
/**
|
|
111
|
-
* Internal read request-start scheduler with weighted read-class fairness.
|
|
112
|
-
*
|
|
113
|
-
* All read starts (polling values/observation/safety reads AND outbound
|
|
114
|
-
* preflight reads) share ONE pacing timeline and interval. When BOTH classes
|
|
115
|
-
* have queued work the scheduler applies the weighted fairness policy
|
|
116
|
-
* `polling 2:1 preflight` — two polling starts, then one preflight start —
|
|
117
|
-
* with FIFO inside each class and no preflight starvation (a preflight is
|
|
118
|
-
* served at least every three picks while both queues are non-empty, and an
|
|
119
|
-
* idle class never blocks the other). The separate WRITE lane stays on the
|
|
120
|
-
* plain `RequestStartLimiter`, so reads and writes still pace independently.
|
|
121
|
-
*
|
|
122
|
-
* Bounded admission matches the single-class limiter: a caller whose
|
|
123
|
-
* predicted slot lies more than `maxWaitMs` out is refused WITHOUT reserving
|
|
124
|
-
* anything, and a refusal never advances the shared horizon (so a later
|
|
125
|
-
* caller is still admitted once time passes the open slot). Queued waiters
|
|
126
|
-
* are served in weighted-FIFO batches and every waiter is either admitted or
|
|
127
|
-
* refused before transport, so a burst of concurrent callers can never pile
|
|
128
|
-
* up an arbitrarily long reservation line.
|
|
129
|
-
*/
|
|
130
|
-
export declare class ReadQoSScheduler {
|
|
131
|
-
private readonly intervalMs;
|
|
132
|
-
private readonly getIntervalMs;
|
|
133
|
-
private readonly now;
|
|
134
|
-
private readonly sleep;
|
|
135
|
-
/** Shared request-start horizon for BOTH read classes. */
|
|
136
|
-
private lastStartAt;
|
|
137
|
-
/** Consecutive polling picks since the last preflight pick (fairness state). */
|
|
138
|
-
private pollingSincePreflight;
|
|
139
|
-
/** True while the single admission-loop coroutine is running. */
|
|
140
|
-
private draining;
|
|
141
|
-
private readonly pollingQueue;
|
|
142
|
-
private readonly preflightQueue;
|
|
143
|
-
constructor(options: RequestStartLimiterOptions);
|
|
144
|
-
/** Effective shared-read pacing interval NOW (governor-aware). */
|
|
145
|
-
private currentIntervalMs;
|
|
146
|
-
/** Returns the last recorded shared request start, when one exists. */
|
|
147
|
-
lastStart(): number | undefined;
|
|
148
|
-
/**
|
|
149
|
-
* Bounded admission on one read class, sharing ONE timeline with the other
|
|
150
|
-
* class. Returns `admitted` after sleeping to the reserved slot or `refused`
|
|
151
|
-
* (reserving nothing and never advancing the horizon) when the predicted
|
|
152
|
-
* wait exceeds `maxWaitMs`. Decisions are made synchronously per enqueue, so
|
|
153
|
-
* concurrent callers can never double-reserve one slot.
|
|
154
|
-
*/
|
|
155
|
-
waitForSlot(pacing: ReadQoSClass, maxWaitMs: number): Promise<RequestStartAdmission>;
|
|
156
|
-
/**
|
|
157
|
-
* Single serialized admission-loop coroutine.
|
|
158
|
-
*
|
|
159
|
-
* Every concurrent caller enqueues its waiter and, when a drain is already
|
|
160
|
-
* running, returns immediately; the running loop picks the waiter up on its
|
|
161
|
-
* next `serveBatch` pass. Each pass reserves the shared slots for ALL
|
|
162
|
-
* currently-queued waiters in weighted-FIFO order with the clock frozen for
|
|
163
|
-
* that pass, so a burst of callers arriving in one synchronous tick is
|
|
164
|
-
* scheduled against the SAME horizon — deep reservations beyond `maxWaitMs`
|
|
165
|
-
* are refused without advancing it, exactly like the single-class limiter —
|
|
166
|
-
* while the 2:1 weighted fairness is applied whenever both read classes are
|
|
167
|
-
* queued together.
|
|
168
|
-
*/
|
|
169
|
-
private drain;
|
|
170
|
-
/**
|
|
171
|
-
* Reserves the next shared slots for every currently-queued waiter, in
|
|
172
|
-
* weighted-FIFO order, synchronously (the clock is frozen for the pass). An
|
|
173
|
-
* admitted waiter sleeps before it is released; a refused waiter is released
|
|
174
|
-
* immediately WITHOUT reserving or advancing the shared horizon. Returns
|
|
175
|
-
* whether any waiter was served (admitted or refused).
|
|
176
|
-
*/
|
|
177
|
-
private serveBatch;
|
|
178
|
-
/**
|
|
179
|
-
* Picks the next class to serve: FIFO within a class, and the weighted 2:1
|
|
180
|
-
* polling:preflight policy only when BOTH classes have queued work so an
|
|
181
|
-
* idle class never blocks or starves the other.
|
|
182
|
-
*/
|
|
183
|
-
private pickClass;
|
|
184
|
-
}
|
|
185
|
-
//# sourceMappingURL=rateLimiter.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rateLimiter.d.ts","sourceRoot":"","sources":["../../../../../../../../src/sheets/providers/google-sheets-api/transport/rateLimiter.ts"],"names":[],"mappings":"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"}
|