hikoutei 0.10.1-dev → 0.10.4-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.
Files changed (177) hide show
  1. package/dist/composition/localRuntime.js +2 -2
  2. package/dist/composition/localRuntime.js.map +1 -1
  3. package/dist/contracts/domain/model/constants.d.ts +15 -0
  4. package/dist/contracts/domain/model/constants.d.ts.map +1 -1
  5. package/dist/contracts/domain/model/constants.js +13 -0
  6. package/dist/contracts/domain/model/constants.js.map +1 -1
  7. package/dist/contracts/shared/observability/internalLog.d.ts.map +1 -0
  8. package/dist/contracts/shared/observability/internalLog.js.map +1 -0
  9. package/dist/contracts/shared/observability/logEvents.d.ts.map +1 -0
  10. package/dist/contracts/shared/observability/logEvents.js.map +1 -0
  11. package/dist/contracts/sheets/mutationCoordinator/CoordinatedSheetsProvider.js +1 -1
  12. package/dist/contracts/sheets/mutationCoordinator/CoordinatedSheetsProvider.js.map +1 -1
  13. package/dist/contracts/sheets/mutationCoordinator/laneTelemetry.d.ts +1 -1
  14. package/dist/contracts/sheets/mutationCoordinator/laneTelemetry.d.ts.map +1 -1
  15. package/dist/contracts/sheets/mutationCoordinator/laneTelemetry.js +1 -1
  16. package/dist/contracts/sheets/mutationCoordinator/laneTelemetry.js.map +1 -1
  17. package/dist/contracts/sheets/timing.d.ts +4 -37
  18. package/dist/contracts/sheets/timing.d.ts.map +1 -1
  19. package/dist/contracts/sheets/timing.js +4 -18
  20. package/dist/contracts/sheets/timing.js.map +1 -1
  21. package/dist/internal/syncStatus.d.ts.map +1 -1
  22. package/dist/internal/syncStatus.js +1 -2
  23. package/dist/internal/syncStatus.js.map +1 -1
  24. package/dist/orm/persistence/projection/projectionEffects.d.ts.map +1 -1
  25. package/dist/orm/persistence/projection/projectionEffects.js +2 -2
  26. package/dist/orm/persistence/projection/projectionEffects.js.map +1 -1
  27. package/dist/sheets/providers/google-sheets-api/GoogleSheetsApiSyncProvider.d.ts +2 -0
  28. package/dist/sheets/providers/google-sheets-api/GoogleSheetsApiSyncProvider.d.ts.map +1 -1
  29. package/dist/sheets/providers/google-sheets-api/GoogleSheetsApiSyncProvider.js +21 -4
  30. package/dist/sheets/providers/google-sheets-api/GoogleSheetsApiSyncProvider.js.map +1 -1
  31. package/dist/sheets/providers/google-sheets-api/constants.d.ts +13 -2
  32. package/dist/sheets/providers/google-sheets-api/constants.d.ts.map +1 -1
  33. package/dist/sheets/providers/google-sheets-api/constants.js +17 -2
  34. package/dist/sheets/providers/google-sheets-api/constants.js.map +1 -1
  35. package/dist/sheets/providers/google-sheets-api/errors.js +2 -2
  36. package/dist/sheets/providers/google-sheets-api/errors.js.map +1 -1
  37. package/dist/sheets/providers/google-sheets-api/index.d.ts +1 -1
  38. package/dist/sheets/providers/google-sheets-api/index.d.ts.map +1 -1
  39. package/dist/sheets/providers/google-sheets-api/index.js +1 -1
  40. package/dist/sheets/providers/google-sheets-api/index.js.map +1 -1
  41. package/dist/sheets/providers/google-sheets-api/model/batchBuilder.d.ts.map +1 -1
  42. package/dist/sheets/providers/google-sheets-api/model/batchBuilder.js +7 -7
  43. package/dist/sheets/providers/google-sheets-api/model/batchBuilder.js.map +1 -1
  44. package/dist/sheets/providers/google-sheets-api/model/observation.d.ts +3 -3
  45. package/dist/sheets/providers/google-sheets-api/model/observation.d.ts.map +1 -1
  46. package/dist/sheets/providers/google-sheets-api/model/observation.js +11 -8
  47. package/dist/sheets/providers/google-sheets-api/model/observation.js.map +1 -1
  48. package/dist/sheets/providers/google-sheets-api/model/plannerReceipt.d.ts +6 -5
  49. package/dist/sheets/providers/google-sheets-api/model/plannerReceipt.d.ts.map +1 -1
  50. package/dist/sheets/providers/google-sheets-api/model/plannerReceipt.js +97 -43
  51. package/dist/sheets/providers/google-sheets-api/model/plannerReceipt.js.map +1 -1
  52. package/dist/sheets/providers/google-sheets-api/model/postcondition.d.ts +8 -5
  53. package/dist/sheets/providers/google-sheets-api/model/postcondition.d.ts.map +1 -1
  54. package/dist/sheets/providers/google-sheets-api/model/postcondition.js +50 -60
  55. package/dist/sheets/providers/google-sheets-api/model/postcondition.js.map +1 -1
  56. package/dist/sheets/providers/google-sheets-api/model/preflightContext.d.ts +5 -6
  57. package/dist/sheets/providers/google-sheets-api/model/preflightContext.d.ts.map +1 -1
  58. package/dist/sheets/providers/google-sheets-api/model/preflightContext.js +22 -15
  59. package/dist/sheets/providers/google-sheets-api/model/preflightContext.js.map +1 -1
  60. package/dist/sheets/providers/google-sheets-api/model/preflightVerify.d.ts +2 -2
  61. package/dist/sheets/providers/google-sheets-api/model/preflightVerify.d.ts.map +1 -1
  62. package/dist/sheets/providers/google-sheets-api/model/preflightVerify.js +10 -7
  63. package/dist/sheets/providers/google-sheets-api/model/preflightVerify.js.map +1 -1
  64. package/dist/sheets/providers/google-sheets-api/operations/applyEffects.d.ts.map +1 -1
  65. package/dist/sheets/providers/google-sheets-api/operations/applyEffects.js +2 -3
  66. package/dist/sheets/providers/google-sheets-api/operations/applyEffects.js.map +1 -1
  67. package/dist/sheets/providers/google-sheets-api/operations/fastAppend.d.ts.map +1 -1
  68. package/dist/sheets/providers/google-sheets-api/operations/fastAppend.js +2 -3
  69. package/dist/sheets/providers/google-sheets-api/operations/fastAppend.js.map +1 -1
  70. package/dist/sheets/providers/google-sheets-api/operations/preflightOp.d.ts.map +1 -1
  71. package/dist/sheets/providers/google-sheets-api/operations/preflightOp.js +2 -2
  72. package/dist/sheets/providers/google-sheets-api/operations/preflightOp.js.map +1 -1
  73. package/dist/sheets/providers/google-sheets-api/operations/provisioning.d.ts.map +1 -1
  74. package/dist/sheets/providers/google-sheets-api/operations/provisioning.js +2 -2
  75. package/dist/sheets/providers/google-sheets-api/operations/provisioning.js.map +1 -1
  76. package/dist/sheets/providers/google-sheets-api/operations/readRows.js +1 -1
  77. package/dist/sheets/providers/google-sheets-api/operations/readRows.js.map +1 -1
  78. package/dist/sheets/providers/google-sheets-api/operations/rowChecks.d.ts.map +1 -1
  79. package/dist/sheets/providers/google-sheets-api/operations/rowChecks.js +9 -7
  80. package/dist/sheets/providers/google-sheets-api/operations/rowChecks.js.map +1 -1
  81. package/dist/sheets/providers/google-sheets-api/operations/shared.d.ts +119 -58
  82. package/dist/sheets/providers/google-sheets-api/operations/shared.d.ts.map +1 -1
  83. package/dist/sheets/providers/google-sheets-api/operations/shared.js +168 -153
  84. package/dist/sheets/providers/google-sheets-api/operations/shared.js.map +1 -1
  85. package/dist/sheets/providers/google-sheets-api/transport/googleSheetsApiTransport.d.ts +1 -1
  86. package/dist/sheets/providers/google-sheets-api/transport/googleSheetsApiTransport.js +4 -4
  87. package/dist/sheets/providers/google-sheets-api/transport/googleSheetsApiTransport.js.map +1 -1
  88. package/dist/storage/sqlite/migrateSchema.d.ts.map +1 -1
  89. package/dist/storage/sqlite/migrateSchema.js +36 -3
  90. package/dist/storage/sqlite/migrateSchema.js.map +1 -1
  91. package/dist/storage/sqlite/schema.d.ts +3 -1
  92. package/dist/storage/sqlite/schema.d.ts.map +1 -1
  93. package/dist/storage/sqlite/schema.js +6 -2
  94. package/dist/storage/sqlite/schema.js.map +1 -1
  95. package/dist/storage/sqlite/schemaTypes.d.ts +1 -1
  96. package/dist/storage/sqlite/schemaTypes.d.ts.map +1 -1
  97. package/dist/sync/outbound/projection/ProjectionEffectFactory.d.ts +19 -0
  98. package/dist/sync/outbound/projection/ProjectionEffectFactory.d.ts.map +1 -1
  99. package/dist/sync/outbound/projection/ProjectionEffectFactory.js +35 -0
  100. package/dist/sync/outbound/projection/ProjectionEffectFactory.js.map +1 -1
  101. package/dist/sync-engine/api/hikouteiCore.js +2 -2
  102. package/dist/sync-engine/api/hikouteiCore.js.map +1 -1
  103. package/dist/sync-engine/api/internalEntityManager.js +2 -2
  104. package/dist/sync-engine/api/internalEntityManager.js.map +1 -1
  105. package/dist/sync-engine/sync/outbound/SheetsEffectDispatcher.d.ts +13 -2
  106. package/dist/sync-engine/sync/outbound/SheetsEffectDispatcher.d.ts.map +1 -1
  107. package/dist/sync-engine/sync/outbound/SheetsEffectDispatcher.js +37 -5
  108. package/dist/sync-engine/sync/outbound/SheetsEffectDispatcher.js.map +1 -1
  109. package/dist/sync-engine/sync/outbound/dispatcherSupport.d.ts +4 -7
  110. package/dist/sync-engine/sync/outbound/dispatcherSupport.d.ts.map +1 -1
  111. package/dist/sync-engine/sync/outbound/dispatcherSupport.js +4 -14
  112. package/dist/sync-engine/sync/outbound/dispatcherSupport.js.map +1 -1
  113. package/dist/sync-engine/sync/service/SyncServiceBootstrap.js +2 -2
  114. package/dist/sync-engine/sync/service/SyncServiceBootstrap.js.map +1 -1
  115. package/dist/sync-engine/sync/service/adopt/adoptionSeeding.js +1 -1
  116. package/dist/sync-engine/sync/service/adopt/adoptionSeeding.js.map +1 -1
  117. package/dist/sync-engine/sync/service/effectSupervisor.js +2 -2
  118. package/dist/sync-engine/sync/service/effectSupervisor.js.map +1 -1
  119. package/dist/sync-engine/sync/service/pollingSupervisor.js +2 -2
  120. package/dist/sync-engine/sync/service/pollingSupervisor.js.map +1 -1
  121. package/dist/sync-engine/sync/service/requestTelemetry.js +2 -2
  122. package/dist/sync-engine/sync/service/requestTelemetry.js.map +1 -1
  123. package/dist/sync-engine/sync/service/shutdown.js +1 -1
  124. package/dist/sync-engine/sync/service/shutdown.js.map +1 -1
  125. package/dist/sync-engine/sync/service/syncAutoStart.js +2 -2
  126. package/dist/sync-engine/sync/service/syncAutoStart.js.map +1 -1
  127. package/dist/sync-engine/sync/sheetsContract/conflictProjectionRegistration.js +1 -1
  128. package/dist/sync-engine/sync/sheetsContract/conflictProjectionRegistration.js.map +1 -1
  129. package/package.json +5 -4
  130. package/dist/contracts/sheets/transportOutcome.d.ts +0 -90
  131. package/dist/contracts/sheets/transportOutcome.d.ts.map +0 -1
  132. package/dist/contracts/sheets/transportOutcome.js +0 -191
  133. package/dist/contracts/sheets/transportOutcome.js.map +0 -1
  134. package/dist/sheets/providers/google-sheets-api/model/readPlan.d.ts +0 -168
  135. package/dist/sheets/providers/google-sheets-api/model/readPlan.d.ts.map +0 -1
  136. package/dist/sheets/providers/google-sheets-api/model/readPlan.js +0 -184
  137. package/dist/sheets/providers/google-sheets-api/model/readPlan.js.map +0 -1
  138. package/dist/sheets/providers/google-sheets-api/model/receiptCursor.d.ts +0 -102
  139. package/dist/sheets/providers/google-sheets-api/model/receiptCursor.d.ts.map +0 -1
  140. package/dist/sheets/providers/google-sheets-api/model/receiptCursor.js +0 -132
  141. package/dist/sheets/providers/google-sheets-api/model/receiptCursor.js.map +0 -1
  142. package/dist/sheets/providers/google-sheets-api/model/rowCheckFormula.d.ts +0 -35
  143. package/dist/sheets/providers/google-sheets-api/model/rowCheckFormula.d.ts.map +0 -1
  144. package/dist/sheets/providers/google-sheets-api/model/rowCheckFormula.js +0 -57
  145. package/dist/sheets/providers/google-sheets-api/model/rowCheckFormula.js.map +0 -1
  146. package/dist/sheets/providers/google-sheets-api/model/sheetIdAllocator.d.ts +0 -16
  147. package/dist/sheets/providers/google-sheets-api/model/sheetIdAllocator.d.ts.map +0 -1
  148. package/dist/sheets/providers/google-sheets-api/model/sheetIdAllocator.js +0 -29
  149. package/dist/sheets/providers/google-sheets-api/model/sheetIdAllocator.js.map +0 -1
  150. package/dist/sheets/providers/google-sheets-api/operations/readEngine.d.ts +0 -50
  151. package/dist/sheets/providers/google-sheets-api/operations/readEngine.d.ts.map +0 -1
  152. package/dist/sheets/providers/google-sheets-api/operations/readEngine.js +0 -124
  153. package/dist/sheets/providers/google-sheets-api/operations/readEngine.js.map +0 -1
  154. package/dist/sheets/providers/google-sheets-api/operations/writeEngine.d.ts +0 -90
  155. package/dist/sheets/providers/google-sheets-api/operations/writeEngine.d.ts.map +0 -1
  156. package/dist/sheets/providers/google-sheets-api/operations/writeEngine.js +0 -112
  157. package/dist/sheets/providers/google-sheets-api/operations/writeEngine.js.map +0 -1
  158. package/dist/sheets/providers/google-sheets-api/transport/quotaGovernor.d.ts +0 -229
  159. package/dist/sheets/providers/google-sheets-api/transport/quotaGovernor.d.ts.map +0 -1
  160. package/dist/sheets/providers/google-sheets-api/transport/quotaGovernor.js +0 -299
  161. package/dist/sheets/providers/google-sheets-api/transport/quotaGovernor.js.map +0 -1
  162. package/dist/sheets/providers/google-sheets-api/transport/rateLimiter.d.ts +0 -185
  163. package/dist/sheets/providers/google-sheets-api/transport/rateLimiter.d.ts.map +0 -1
  164. package/dist/sheets/providers/google-sheets-api/transport/rateLimiter.js +0 -248
  165. package/dist/sheets/providers/google-sheets-api/transport/rateLimiter.js.map +0 -1
  166. package/dist/sheets/providers/google-sheets-api/transport/rawErrorSchemas.d.ts +0 -7
  167. package/dist/sheets/providers/google-sheets-api/transport/rawErrorSchemas.d.ts.map +0 -1
  168. package/dist/sheets/providers/google-sheets-api/transport/rawErrorSchemas.js +0 -26
  169. package/dist/sheets/providers/google-sheets-api/transport/rawErrorSchemas.js.map +0 -1
  170. package/dist/sync-engine/shared/observability/internalLog.d.ts.map +0 -1
  171. package/dist/sync-engine/shared/observability/internalLog.js.map +0 -1
  172. package/dist/sync-engine/shared/observability/logEvents.d.ts.map +0 -1
  173. package/dist/sync-engine/shared/observability/logEvents.js.map +0 -1
  174. /package/dist/{sync-engine → contracts}/shared/observability/internalLog.d.ts +0 -0
  175. /package/dist/{sync-engine → contracts}/shared/observability/internalLog.js +0 -0
  176. /package/dist/{sync-engine → contracts}/shared/observability/logEvents.d.ts +0 -0
  177. /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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"}