hikoutei 0.2.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.
Files changed (535) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +135 -0
  3. package/README.ko.md +138 -0
  4. package/README.md +143 -0
  5. package/apps-script/gateway/Code.gs +412 -0
  6. package/dist/adapter/persistence/contracts/index.d.ts +3 -0
  7. package/dist/adapter/persistence/contracts/index.d.ts.map +1 -0
  8. package/dist/adapter/persistence/contracts/index.js +3 -0
  9. package/dist/adapter/persistence/contracts/index.js.map +1 -0
  10. package/dist/adapter/persistence/contracts/sql.d.ts +50 -0
  11. package/dist/adapter/persistence/contracts/sql.d.ts.map +1 -0
  12. package/dist/adapter/persistence/contracts/sql.js +9 -0
  13. package/dist/adapter/persistence/contracts/sql.js.map +1 -0
  14. package/dist/adapter/persistence/index.d.ts +2 -0
  15. package/dist/adapter/persistence/index.d.ts.map +1 -0
  16. package/dist/adapter/persistence/index.js +2 -0
  17. package/dist/adapter/persistence/index.js.map +1 -0
  18. package/dist/adapter/persistence/providers/mikro-orm/api/MikroOrmTypedSheetsEntityManager.d.ts +53 -0
  19. package/dist/adapter/persistence/providers/mikro-orm/api/MikroOrmTypedSheetsEntityManager.d.ts.map +1 -0
  20. package/dist/adapter/persistence/providers/mikro-orm/api/MikroOrmTypedSheetsEntityManager.js +105 -0
  21. package/dist/adapter/persistence/providers/mikro-orm/api/MikroOrmTypedSheetsEntityManager.js.map +1 -0
  22. package/dist/adapter/persistence/providers/mikro-orm/engine/MikroOrmMappedTypedSheets.d.ts +39 -0
  23. package/dist/adapter/persistence/providers/mikro-orm/engine/MikroOrmMappedTypedSheets.d.ts.map +1 -0
  24. package/dist/adapter/persistence/providers/mikro-orm/engine/MikroOrmMappedTypedSheets.js +54 -0
  25. package/dist/adapter/persistence/providers/mikro-orm/engine/MikroOrmMappedTypedSheets.js.map +1 -0
  26. package/dist/adapter/persistence/providers/mikro-orm/engine/MikroOrmTypedSheetsEngine.d.ts +38 -0
  27. package/dist/adapter/persistence/providers/mikro-orm/engine/MikroOrmTypedSheetsEngine.d.ts.map +1 -0
  28. package/dist/adapter/persistence/providers/mikro-orm/engine/MikroOrmTypedSheetsEngine.js +55 -0
  29. package/dist/adapter/persistence/providers/mikro-orm/engine/MikroOrmTypedSheetsEngine.js.map +1 -0
  30. package/dist/adapter/persistence/providers/mikro-orm/index.d.ts +16 -0
  31. package/dist/adapter/persistence/providers/mikro-orm/index.d.ts.map +1 -0
  32. package/dist/adapter/persistence/providers/mikro-orm/index.js +9 -0
  33. package/dist/adapter/persistence/providers/mikro-orm/index.js.map +1 -0
  34. package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmMappedObservation.d.ts +27 -0
  35. package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmMappedObservation.d.ts.map +1 -0
  36. package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmMappedObservation.js +67 -0
  37. package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmMappedObservation.js.map +1 -0
  38. package/dist/adapter/persistence/providers/mikro-orm/storage/MikroOrmFlushChanges.d.ts +19 -0
  39. package/dist/adapter/persistence/providers/mikro-orm/storage/MikroOrmFlushChanges.d.ts.map +1 -0
  40. package/dist/adapter/persistence/providers/mikro-orm/storage/MikroOrmFlushChanges.js +67 -0
  41. package/dist/adapter/persistence/providers/mikro-orm/storage/MikroOrmFlushChanges.js.map +1 -0
  42. package/dist/adapter/persistence/providers/mikro-orm/storage/MikroOrmSqliteAdapter.d.ts +78 -0
  43. package/dist/adapter/persistence/providers/mikro-orm/storage/MikroOrmSqliteAdapter.d.ts.map +1 -0
  44. package/dist/adapter/persistence/providers/mikro-orm/storage/MikroOrmSqliteAdapter.js +121 -0
  45. package/dist/adapter/persistence/providers/mikro-orm/storage/MikroOrmSqliteAdapter.js.map +1 -0
  46. package/dist/adapter/persistence/providers/mikro-orm/storage/MikroOrmSqliteSchema.d.ts +20 -0
  47. package/dist/adapter/persistence/providers/mikro-orm/storage/MikroOrmSqliteSchema.d.ts.map +1 -0
  48. package/dist/adapter/persistence/providers/mikro-orm/storage/MikroOrmSqliteSchema.js +125 -0
  49. package/dist/adapter/persistence/providers/mikro-orm/storage/MikroOrmSqliteSchema.js.map +1 -0
  50. package/dist/adapter/sheets/providers/apps-script-gateway/errors.d.ts +42 -0
  51. package/dist/adapter/sheets/providers/apps-script-gateway/errors.d.ts.map +1 -0
  52. package/dist/adapter/sheets/providers/apps-script-gateway/errors.js +51 -0
  53. package/dist/adapter/sheets/providers/apps-script-gateway/errors.js.map +1 -0
  54. package/dist/adapter/sheets/providers/apps-script-gateway/index.d.ts +22 -0
  55. package/dist/adapter/sheets/providers/apps-script-gateway/index.d.ts.map +1 -0
  56. package/dist/adapter/sheets/providers/apps-script-gateway/index.js +12 -0
  57. package/dist/adapter/sheets/providers/apps-script-gateway/index.js.map +1 -0
  58. package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperation.d.ts +42 -0
  59. package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperation.d.ts.map +1 -0
  60. package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperation.js +161 -0
  61. package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperation.js.map +1 -0
  62. package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperationScript.d.ts +3 -0
  63. package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperationScript.d.ts.map +1 -0
  64. package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperationScript.js +692 -0
  65. package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperationScript.js.map +1 -0
  66. package/dist/adapter/sheets/providers/apps-script-gateway/operations/observation/observationOperation.d.ts +29 -0
  67. package/dist/adapter/sheets/providers/apps-script-gateway/operations/observation/observationOperation.d.ts.map +1 -0
  68. package/dist/adapter/sheets/providers/apps-script-gateway/operations/observation/observationOperation.js +538 -0
  69. package/dist/adapter/sheets/providers/apps-script-gateway/operations/observation/observationOperation.js.map +1 -0
  70. package/dist/adapter/sheets/providers/apps-script-gateway/operations/read/tableReadOperation.d.ts +12 -0
  71. package/dist/adapter/sheets/providers/apps-script-gateway/operations/read/tableReadOperation.d.ts.map +1 -0
  72. package/dist/adapter/sheets/providers/apps-script-gateway/operations/read/tableReadOperation.js +190 -0
  73. package/dist/adapter/sheets/providers/apps-script-gateway/operations/read/tableReadOperation.js.map +1 -0
  74. package/dist/adapter/sheets/providers/apps-script-gateway/operations/write/fastAppendOperation.d.ts +23 -0
  75. package/dist/adapter/sheets/providers/apps-script-gateway/operations/write/fastAppendOperation.d.ts.map +1 -0
  76. package/dist/adapter/sheets/providers/apps-script-gateway/operations/write/fastAppendOperation.js +166 -0
  77. package/dist/adapter/sheets/providers/apps-script-gateway/operations/write/fastAppendOperation.js.map +1 -0
  78. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/codeGsProtocol.d.ts +50 -0
  79. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/codeGsProtocol.d.ts.map +1 -0
  80. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/codeGsProtocol.js +60 -0
  81. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/codeGsProtocol.js.map +1 -0
  82. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/constants.d.ts +44 -0
  83. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/constants.d.ts.map +1 -0
  84. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/constants.js +40 -0
  85. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/constants.js.map +1 -0
  86. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/syncProtocol.d.ts +7 -0
  87. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/syncProtocol.d.ts.map +1 -0
  88. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/syncProtocol.js +47 -0
  89. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/syncProtocol.js.map +1 -0
  90. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/timing.d.ts +5 -0
  91. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/timing.d.ts.map +1 -0
  92. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/timing.js +54 -0
  93. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/timing.js.map +1 -0
  94. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/types.d.ts +5 -0
  95. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/types.d.ts.map +1 -0
  96. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/types.js +2 -0
  97. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/types.js.map +1 -0
  98. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/validation.d.ts +10 -0
  99. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/validation.d.ts.map +1 -0
  100. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/validation.js +35 -0
  101. package/dist/adapter/sheets/providers/apps-script-gateway/protocol/validation.js.map +1 -0
  102. package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationClient.d.ts +67 -0
  103. package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationClient.d.ts.map +1 -0
  104. package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationClient.js +204 -0
  105. package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationClient.js.map +1 -0
  106. package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationSyncGateway.d.ts +78 -0
  107. package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationSyncGateway.d.ts.map +1 -0
  108. package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationSyncGateway.js +343 -0
  109. package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationSyncGateway.js.map +1 -0
  110. package/dist/application/index.d.ts +4 -0
  111. package/dist/application/index.d.ts.map +1 -0
  112. package/dist/application/index.js +4 -0
  113. package/dist/application/index.js.map +1 -0
  114. package/dist/application/orm/api/TypedSheetsOrm.d.ts +71 -0
  115. package/dist/application/orm/api/TypedSheetsOrm.d.ts.map +1 -0
  116. package/dist/application/orm/api/TypedSheetsOrm.js +116 -0
  117. package/dist/application/orm/api/TypedSheetsOrm.js.map +1 -0
  118. package/dist/application/orm/api/contracts.d.ts +118 -0
  119. package/dist/application/orm/api/contracts.d.ts.map +1 -0
  120. package/dist/application/orm/api/contracts.js +21 -0
  121. package/dist/application/orm/api/contracts.js.map +1 -0
  122. package/dist/application/orm/errors.d.ts +35 -0
  123. package/dist/application/orm/errors.d.ts.map +1 -0
  124. package/dist/application/orm/errors.js +35 -0
  125. package/dist/application/orm/errors.js.map +1 -0
  126. package/dist/application/orm/index.d.ts +13 -0
  127. package/dist/application/orm/index.d.ts.map +1 -0
  128. package/dist/application/orm/index.js +7 -0
  129. package/dist/application/orm/index.js.map +1 -0
  130. package/dist/application/orm/mapping/contracts.d.ts +113 -0
  131. package/dist/application/orm/mapping/contracts.d.ts.map +1 -0
  132. package/dist/application/orm/mapping/contracts.js +8 -0
  133. package/dist/application/orm/mapping/contracts.js.map +1 -0
  134. package/dist/application/orm/mapping/definition.d.ts +16 -0
  135. package/dist/application/orm/mapping/definition.d.ts.map +1 -0
  136. package/dist/application/orm/mapping/definition.js +145 -0
  137. package/dist/application/orm/mapping/definition.js.map +1 -0
  138. package/dist/application/orm/mapping/entityMapping.d.ts +13 -0
  139. package/dist/application/orm/mapping/entityMapping.d.ts.map +1 -0
  140. package/dist/application/orm/mapping/entityMapping.js +13 -0
  141. package/dist/application/orm/mapping/entityMapping.js.map +1 -0
  142. package/dist/application/orm/mapping/identity.d.ts +14 -0
  143. package/dist/application/orm/mapping/identity.d.ts.map +1 -0
  144. package/dist/application/orm/mapping/identity.js +37 -0
  145. package/dist/application/orm/mapping/identity.js.map +1 -0
  146. package/dist/application/orm/mapping/observationMapping.d.ts +42 -0
  147. package/dist/application/orm/mapping/observationMapping.d.ts.map +1 -0
  148. package/dist/application/orm/mapping/observationMapping.js +65 -0
  149. package/dist/application/orm/mapping/observationMapping.js.map +1 -0
  150. package/dist/application/orm/mapping/projection.d.ts +22 -0
  151. package/dist/application/orm/mapping/projection.d.ts.map +1 -0
  152. package/dist/application/orm/mapping/projection.js +74 -0
  153. package/dist/application/orm/mapping/projection.js.map +1 -0
  154. package/dist/application/orm/mapping/registry.d.ts +15 -0
  155. package/dist/application/orm/mapping/registry.d.ts.map +1 -0
  156. package/dist/application/orm/mapping/registry.js +38 -0
  157. package/dist/application/orm/mapping/registry.js.map +1 -0
  158. package/dist/application/orm/mapping/values.d.ts +17 -0
  159. package/dist/application/orm/mapping/values.d.ts.map +1 -0
  160. package/dist/application/orm/mapping/values.js +141 -0
  161. package/dist/application/orm/mapping/values.js.map +1 -0
  162. package/dist/application/orm/persistence/flush/flushCoordinator.d.ts +28 -0
  163. package/dist/application/orm/persistence/flush/flushCoordinator.d.ts.map +1 -0
  164. package/dist/application/orm/persistence/flush/flushCoordinator.js +170 -0
  165. package/dist/application/orm/persistence/flush/flushCoordinator.js.map +1 -0
  166. package/dist/application/orm/persistence/flush/mappedFlushCoordinator.d.ts +9 -0
  167. package/dist/application/orm/persistence/flush/mappedFlushCoordinator.d.ts.map +1 -0
  168. package/dist/application/orm/persistence/flush/mappedFlushCoordinator.js +8 -0
  169. package/dist/application/orm/persistence/flush/mappedFlushCoordinator.js.map +1 -0
  170. package/dist/application/orm/persistence/lifecycle/entityLifecycle.d.ts +12 -0
  171. package/dist/application/orm/persistence/lifecycle/entityLifecycle.d.ts.map +1 -0
  172. package/dist/application/orm/persistence/lifecycle/entityLifecycle.js +134 -0
  173. package/dist/application/orm/persistence/lifecycle/entityLifecycle.js.map +1 -0
  174. package/dist/application/orm/persistence/projection/projectionEffects.d.ts +21 -0
  175. package/dist/application/orm/persistence/projection/projectionEffects.d.ts.map +1 -0
  176. package/dist/application/orm/persistence/projection/projectionEffects.js +203 -0
  177. package/dist/application/orm/persistence/projection/projectionEffects.js.map +1 -0
  178. package/dist/application/orm/persistence/support/businessKeys.d.ts +17 -0
  179. package/dist/application/orm/persistence/support/businessKeys.d.ts.map +1 -0
  180. package/dist/application/orm/persistence/support/businessKeys.js +72 -0
  181. package/dist/application/orm/persistence/support/businessKeys.js.map +1 -0
  182. package/dist/application/orm/persistence/support/canonicalState.d.ts +22 -0
  183. package/dist/application/orm/persistence/support/canonicalState.d.ts.map +1 -0
  184. package/dist/application/orm/persistence/support/canonicalState.js +88 -0
  185. package/dist/application/orm/persistence/support/canonicalState.js.map +1 -0
  186. package/dist/application/orm/persistence/support/contracts.d.ts +139 -0
  187. package/dist/application/orm/persistence/support/contracts.d.ts.map +1 -0
  188. package/dist/application/orm/persistence/support/contracts.js +93 -0
  189. package/dist/application/orm/persistence/support/contracts.js.map +1 -0
  190. package/dist/application/orm/persistence/support/helpers.d.ts +32 -0
  191. package/dist/application/orm/persistence/support/helpers.d.ts.map +1 -0
  192. package/dist/application/orm/persistence/support/helpers.js +66 -0
  193. package/dist/application/orm/persistence/support/helpers.js.map +1 -0
  194. package/dist/application/orm/persistence/support/timing.d.ts +20 -0
  195. package/dist/application/orm/persistence/support/timing.d.ts.map +1 -0
  196. package/dist/application/orm/persistence/support/timing.js +55 -0
  197. package/dist/application/orm/persistence/support/timing.js.map +1 -0
  198. package/dist/application/sync/gateway/SyncGatewayBootstrap.d.ts +46 -0
  199. package/dist/application/sync/gateway/SyncGatewayBootstrap.d.ts.map +1 -0
  200. package/dist/application/sync/gateway/SyncGatewayBootstrap.js +93 -0
  201. package/dist/application/sync/gateway/SyncGatewayBootstrap.js.map +1 -0
  202. package/dist/application/sync/gateway/constants.d.ts +65 -0
  203. package/dist/application/sync/gateway/constants.d.ts.map +1 -0
  204. package/dist/application/sync/gateway/constants.js +57 -0
  205. package/dist/application/sync/gateway/constants.js.map +1 -0
  206. package/dist/application/sync/gateway/errors.d.ts +15 -0
  207. package/dist/application/sync/gateway/errors.d.ts.map +1 -0
  208. package/dist/application/sync/gateway/errors.js +16 -0
  209. package/dist/application/sync/gateway/errors.js.map +1 -0
  210. package/dist/application/sync/gateway/syncGateway.d.ts +313 -0
  211. package/dist/application/sync/gateway/syncGateway.d.ts.map +1 -0
  212. package/dist/application/sync/gateway/syncGateway.js +202 -0
  213. package/dist/application/sync/gateway/syncGateway.js.map +1 -0
  214. package/dist/application/sync/gateway/validation.d.ts +17 -0
  215. package/dist/application/sync/gateway/validation.d.ts.map +1 -0
  216. package/dist/application/sync/gateway/validation.js +60 -0
  217. package/dist/application/sync/gateway/validation.js.map +1 -0
  218. package/dist/application/sync/inbound/polling/SimpleSheetPolling.d.ts +72 -0
  219. package/dist/application/sync/inbound/polling/SimpleSheetPolling.d.ts.map +1 -0
  220. package/dist/application/sync/inbound/polling/SimpleSheetPolling.js +272 -0
  221. package/dist/application/sync/inbound/polling/SimpleSheetPolling.js.map +1 -0
  222. package/dist/application/sync/index.d.ts +16 -0
  223. package/dist/application/sync/index.d.ts.map +1 -0
  224. package/dist/application/sync/index.js +9 -0
  225. package/dist/application/sync/index.js.map +1 -0
  226. package/dist/application/sync/outbound/effects/SyncEffectSupervisor.d.ts +106 -0
  227. package/dist/application/sync/outbound/effects/SyncEffectSupervisor.d.ts.map +1 -0
  228. package/dist/application/sync/outbound/effects/SyncEffectSupervisor.js +297 -0
  229. package/dist/application/sync/outbound/effects/SyncEffectSupervisor.js.map +1 -0
  230. package/dist/application/sync/outbound/effects/SyncEffectWorker.d.ts +100 -0
  231. package/dist/application/sync/outbound/effects/SyncEffectWorker.d.ts.map +1 -0
  232. package/dist/application/sync/outbound/effects/SyncEffectWorker.js +345 -0
  233. package/dist/application/sync/outbound/effects/SyncEffectWorker.js.map +1 -0
  234. package/dist/application/sync/outbound/effects/SyncEffectWorkerConstants.d.ts +46 -0
  235. package/dist/application/sync/outbound/effects/SyncEffectWorkerConstants.d.ts.map +1 -0
  236. package/dist/application/sync/outbound/effects/SyncEffectWorkerConstants.js +63 -0
  237. package/dist/application/sync/outbound/effects/SyncEffectWorkerConstants.js.map +1 -0
  238. package/dist/application/sync/outbound/effects/SyncEffectWorkerDispatch.d.ts +20 -0
  239. package/dist/application/sync/outbound/effects/SyncEffectWorkerDispatch.d.ts.map +1 -0
  240. package/dist/application/sync/outbound/effects/SyncEffectWorkerDispatch.js +102 -0
  241. package/dist/application/sync/outbound/effects/SyncEffectWorkerDispatch.js.map +1 -0
  242. package/dist/application/sync/outbound/effects/SyncEffectWorkerHelpers.d.ts +19 -0
  243. package/dist/application/sync/outbound/effects/SyncEffectWorkerHelpers.d.ts.map +1 -0
  244. package/dist/application/sync/outbound/effects/SyncEffectWorkerHelpers.js +32 -0
  245. package/dist/application/sync/outbound/effects/SyncEffectWorkerHelpers.js.map +1 -0
  246. package/dist/application/sync/outbound/effects/SyncEffectWorkerRouting.d.ts +30 -0
  247. package/dist/application/sync/outbound/effects/SyncEffectWorkerRouting.d.ts.map +1 -0
  248. package/dist/application/sync/outbound/effects/SyncEffectWorkerRouting.js +202 -0
  249. package/dist/application/sync/outbound/effects/SyncEffectWorkerRouting.js.map +1 -0
  250. package/dist/application/sync/outbound/effects/SyncEffectWorkerTiming.d.ts +17 -0
  251. package/dist/application/sync/outbound/effects/SyncEffectWorkerTiming.d.ts.map +1 -0
  252. package/dist/application/sync/outbound/effects/SyncEffectWorkerTiming.js +80 -0
  253. package/dist/application/sync/outbound/effects/SyncEffectWorkerTiming.js.map +1 -0
  254. package/dist/application/sync/outbound/effects/SyncEffectWorkerTransitions.d.ts +13 -0
  255. package/dist/application/sync/outbound/effects/SyncEffectWorkerTransitions.d.ts.map +1 -0
  256. package/dist/application/sync/outbound/effects/SyncEffectWorkerTransitions.js +205 -0
  257. package/dist/application/sync/outbound/effects/SyncEffectWorkerTransitions.js.map +1 -0
  258. package/dist/application/sync/outbound/projection/ProjectionEffectFactory.d.ts +81 -0
  259. package/dist/application/sync/outbound/projection/ProjectionEffectFactory.d.ts.map +1 -0
  260. package/dist/application/sync/outbound/projection/ProjectionEffectFactory.js +245 -0
  261. package/dist/application/sync/outbound/projection/ProjectionEffectFactory.js.map +1 -0
  262. package/dist/application/sync/outbound/reconciliation/ReconciliationScanner.d.ts +77 -0
  263. package/dist/application/sync/outbound/reconciliation/ReconciliationScanner.d.ts.map +1 -0
  264. package/dist/application/sync/outbound/reconciliation/ReconciliationScanner.js +518 -0
  265. package/dist/application/sync/outbound/reconciliation/ReconciliationScanner.js.map +1 -0
  266. package/dist/application/sync/telemetry/syncTiming.d.ts +50 -0
  267. package/dist/application/sync/telemetry/syncTiming.d.ts.map +1 -0
  268. package/dist/application/sync/telemetry/syncTiming.js +22 -0
  269. package/dist/application/sync/telemetry/syncTiming.js.map +1 -0
  270. package/dist/domain/conflict/errors.d.ts +42 -0
  271. package/dist/domain/conflict/errors.d.ts.map +1 -0
  272. package/dist/domain/conflict/errors.js +36 -0
  273. package/dist/domain/conflict/errors.js.map +1 -0
  274. package/dist/domain/conflict/index.d.ts +5 -0
  275. package/dist/domain/conflict/index.d.ts.map +1 -0
  276. package/dist/domain/conflict/index.js +3 -0
  277. package/dist/domain/conflict/index.js.map +1 -0
  278. package/dist/domain/conflict/transitions.d.ts +52 -0
  279. package/dist/domain/conflict/transitions.d.ts.map +1 -0
  280. package/dist/domain/conflict/transitions.js +115 -0
  281. package/dist/domain/conflict/transitions.js.map +1 -0
  282. package/dist/domain/errors/evaluation.d.ts +13 -0
  283. package/dist/domain/errors/evaluation.d.ts.map +1 -0
  284. package/dist/domain/errors/evaluation.js +14 -0
  285. package/dist/domain/errors/evaluation.js.map +1 -0
  286. package/dist/domain/errors/identity.d.ts +12 -0
  287. package/dist/domain/errors/identity.d.ts.map +1 -0
  288. package/dist/domain/errors/identity.js +15 -0
  289. package/dist/domain/errors/identity.js.map +1 -0
  290. package/dist/domain/errors/index.d.ts +7 -0
  291. package/dist/domain/errors/index.d.ts.map +1 -0
  292. package/dist/domain/errors/index.js +5 -0
  293. package/dist/domain/errors/index.js.map +1 -0
  294. package/dist/domain/errors/stableEncoding.d.ts +7 -0
  295. package/dist/domain/errors/stableEncoding.d.ts.map +1 -0
  296. package/dist/domain/errors/stableEncoding.js +8 -0
  297. package/dist/domain/errors/stableEncoding.js.map +1 -0
  298. package/dist/domain/errors/types.d.ts +20 -0
  299. package/dist/domain/errors/types.d.ts.map +1 -0
  300. package/dist/domain/errors/types.js +18 -0
  301. package/dist/domain/errors/types.js.map +1 -0
  302. package/dist/domain/evaluate/constants.d.ts +55 -0
  303. package/dist/domain/evaluate/constants.d.ts.map +1 -0
  304. package/dist/domain/evaluate/constants.js +41 -0
  305. package/dist/domain/evaluate/constants.js.map +1 -0
  306. package/dist/domain/evaluate/contracts.d.ts +125 -0
  307. package/dist/domain/evaluate/contracts.d.ts.map +1 -0
  308. package/dist/domain/evaluate/contracts.js +8 -0
  309. package/dist/domain/evaluate/contracts.js.map +1 -0
  310. package/dist/domain/evaluate/evaluateBatch.d.ts +11 -0
  311. package/dist/domain/evaluate/evaluateBatch.d.ts.map +1 -0
  312. package/dist/domain/evaluate/evaluateBatch.js +91 -0
  313. package/dist/domain/evaluate/evaluateBatch.js.map +1 -0
  314. package/dist/domain/evaluate/fieldEvaluation.d.ts +14 -0
  315. package/dist/domain/evaluate/fieldEvaluation.d.ts.map +1 -0
  316. package/dist/domain/evaluate/fieldEvaluation.js +133 -0
  317. package/dist/domain/evaluate/fieldEvaluation.js.map +1 -0
  318. package/dist/domain/evaluate/identity.d.ts +21 -0
  319. package/dist/domain/evaluate/identity.d.ts.map +1 -0
  320. package/dist/domain/evaluate/identity.js +72 -0
  321. package/dist/domain/evaluate/identity.js.map +1 -0
  322. package/dist/domain/evaluate/index.d.ts +4 -0
  323. package/dist/domain/evaluate/index.d.ts.map +1 -0
  324. package/dist/domain/evaluate/index.js +3 -0
  325. package/dist/domain/evaluate/index.js.map +1 -0
  326. package/dist/domain/evaluate/preconditions.d.ts +30 -0
  327. package/dist/domain/evaluate/preconditions.d.ts.map +1 -0
  328. package/dist/domain/evaluate/preconditions.js +389 -0
  329. package/dist/domain/evaluate/preconditions.js.map +1 -0
  330. package/dist/domain/evaluate/quarantine.d.ts +15 -0
  331. package/dist/domain/evaluate/quarantine.d.ts.map +1 -0
  332. package/dist/domain/evaluate/quarantine.js +185 -0
  333. package/dist/domain/evaluate/quarantine.js.map +1 -0
  334. package/dist/domain/index.d.ts +7 -0
  335. package/dist/domain/index.d.ts.map +1 -0
  336. package/dist/domain/index.js +7 -0
  337. package/dist/domain/index.js.map +1 -0
  338. package/dist/domain/model/constants.d.ts +73 -0
  339. package/dist/domain/model/constants.d.ts.map +1 -0
  340. package/dist/domain/model/constants.js +59 -0
  341. package/dist/domain/model/constants.js.map +1 -0
  342. package/dist/domain/model/index.d.ts +3 -0
  343. package/dist/domain/model/index.d.ts.map +1 -0
  344. package/dist/domain/model/index.js +2 -0
  345. package/dist/domain/model/index.js.map +1 -0
  346. package/dist/domain/model/types.d.ts +330 -0
  347. package/dist/domain/model/types.d.ts.map +1 -0
  348. package/dist/domain/model/types.js +8 -0
  349. package/dist/domain/model/types.js.map +1 -0
  350. package/dist/index.d.ts +17 -0
  351. package/dist/index.d.ts.map +1 -0
  352. package/dist/index.js +11 -0
  353. package/dist/index.js.map +1 -0
  354. package/dist/infrastructure/index.d.ts +3 -0
  355. package/dist/infrastructure/index.d.ts.map +1 -0
  356. package/dist/infrastructure/index.js +3 -0
  357. package/dist/infrastructure/index.js.map +1 -0
  358. package/dist/infrastructure/storage/constants.d.ts +4 -0
  359. package/dist/infrastructure/storage/constants.d.ts.map +1 -0
  360. package/dist/infrastructure/storage/constants.js +4 -0
  361. package/dist/infrastructure/storage/constants.js.map +1 -0
  362. package/dist/infrastructure/storage/errors.d.ts +55 -0
  363. package/dist/infrastructure/storage/errors.d.ts.map +1 -0
  364. package/dist/infrastructure/storage/errors.js +58 -0
  365. package/dist/infrastructure/storage/errors.js.map +1 -0
  366. package/dist/infrastructure/storage/index.d.ts +23 -0
  367. package/dist/infrastructure/storage/index.d.ts.map +1 -0
  368. package/dist/infrastructure/storage/index.js +13 -0
  369. package/dist/infrastructure/storage/index.js.map +1 -0
  370. package/dist/infrastructure/storage/recovery/restoreRecovery.d.ts +64 -0
  371. package/dist/infrastructure/storage/recovery/restoreRecovery.d.ts.map +1 -0
  372. package/dist/infrastructure/storage/recovery/restoreRecovery.js +148 -0
  373. package/dist/infrastructure/storage/recovery/restoreRecovery.js.map +1 -0
  374. package/dist/infrastructure/storage/sqlite/schema.d.ts +49 -0
  375. package/dist/infrastructure/storage/sqlite/schema.d.ts.map +1 -0
  376. package/dist/infrastructure/storage/sqlite/schema.js +529 -0
  377. package/dist/infrastructure/storage/sqlite/schema.js.map +1 -0
  378. package/dist/infrastructure/storage/sqlite/schemaTypes.d.ts +5 -0
  379. package/dist/infrastructure/storage/sqlite/schemaTypes.d.ts.map +1 -0
  380. package/dist/infrastructure/storage/sqlite/schemaTypes.js +2 -0
  381. package/dist/infrastructure/storage/sqlite/schemaTypes.js.map +1 -0
  382. package/dist/infrastructure/storage/sqlite/sqlScript.d.ts +9 -0
  383. package/dist/infrastructure/storage/sqlite/sqlScript.d.ts.map +1 -0
  384. package/dist/infrastructure/storage/sqlite/sqlScript.js +122 -0
  385. package/dist/infrastructure/storage/sqlite/sqlScript.js.map +1 -0
  386. package/dist/infrastructure/storage/sqlite/sqlState.d.ts +6 -0
  387. package/dist/infrastructure/storage/sqlite/sqlState.d.ts.map +1 -0
  388. package/dist/infrastructure/storage/sqlite/sqlState.js +14 -0
  389. package/dist/infrastructure/storage/sqlite/sqlState.js.map +1 -0
  390. package/dist/infrastructure/storage/sqlite/sqlTransaction.d.ts +6 -0
  391. package/dist/infrastructure/storage/sqlite/sqlTransaction.d.ts.map +1 -0
  392. package/dist/infrastructure/storage/sqlite/sqlTransaction.js +24 -0
  393. package/dist/infrastructure/storage/sqlite/sqlTransaction.js.map +1 -0
  394. package/dist/infrastructure/storage/sqlite/sqliteBridge.d.ts +44 -0
  395. package/dist/infrastructure/storage/sqlite/sqliteBridge.d.ts.map +1 -0
  396. package/dist/infrastructure/storage/sqlite/sqliteBridge.js +66 -0
  397. package/dist/infrastructure/storage/sqlite/sqliteBridge.js.map +1 -0
  398. package/dist/infrastructure/storage/state/canonical/canonicalCommit.d.ts +101 -0
  399. package/dist/infrastructure/storage/state/canonical/canonicalCommit.d.ts.map +1 -0
  400. package/dist/infrastructure/storage/state/canonical/canonicalCommit.js +417 -0
  401. package/dist/infrastructure/storage/state/canonical/canonicalCommit.js.map +1 -0
  402. package/dist/infrastructure/storage/state/observation/observationAudit.d.ts +16 -0
  403. package/dist/infrastructure/storage/state/observation/observationAudit.d.ts.map +1 -0
  404. package/dist/infrastructure/storage/state/observation/observationAudit.js +85 -0
  405. package/dist/infrastructure/storage/state/observation/observationAudit.js.map +1 -0
  406. package/dist/infrastructure/storage/state/observation/observationCanonical.d.ts +31 -0
  407. package/dist/infrastructure/storage/state/observation/observationCanonical.d.ts.map +1 -0
  408. package/dist/infrastructure/storage/state/observation/observationCanonical.js +439 -0
  409. package/dist/infrastructure/storage/state/observation/observationCanonical.js.map +1 -0
  410. package/dist/infrastructure/storage/state/observation/observationConstants.d.ts +53 -0
  411. package/dist/infrastructure/storage/state/observation/observationConstants.d.ts.map +1 -0
  412. package/dist/infrastructure/storage/state/observation/observationConstants.js +41 -0
  413. package/dist/infrastructure/storage/state/observation/observationConstants.js.map +1 -0
  414. package/dist/infrastructure/storage/state/observation/observationLedger.d.ts +49 -0
  415. package/dist/infrastructure/storage/state/observation/observationLedger.d.ts.map +1 -0
  416. package/dist/infrastructure/storage/state/observation/observationLedger.js +537 -0
  417. package/dist/infrastructure/storage/state/observation/observationLedger.js.map +1 -0
  418. package/dist/infrastructure/storage/state/observation/observationQuarantine.d.ts +15 -0
  419. package/dist/infrastructure/storage/state/observation/observationQuarantine.d.ts.map +1 -0
  420. package/dist/infrastructure/storage/state/observation/observationQuarantine.js +100 -0
  421. package/dist/infrastructure/storage/state/observation/observationQuarantine.js.map +1 -0
  422. package/dist/infrastructure/storage/state/observation/observationTypes.d.ts +124 -0
  423. package/dist/infrastructure/storage/state/observation/observationTypes.d.ts.map +1 -0
  424. package/dist/infrastructure/storage/state/observation/observationTypes.js +13 -0
  425. package/dist/infrastructure/storage/state/observation/observationTypes.js.map +1 -0
  426. package/dist/infrastructure/storage/state/observation/observationValidation.d.ts +28 -0
  427. package/dist/infrastructure/storage/state/observation/observationValidation.d.ts.map +1 -0
  428. package/dist/infrastructure/storage/state/observation/observationValidation.js +223 -0
  429. package/dist/infrastructure/storage/state/observation/observationValidation.js.map +1 -0
  430. package/dist/infrastructure/storage/state/observation/observationWriter.d.ts +28 -0
  431. package/dist/infrastructure/storage/state/observation/observationWriter.d.ts.map +1 -0
  432. package/dist/infrastructure/storage/state/observation/observationWriter.js +293 -0
  433. package/dist/infrastructure/storage/state/observation/observationWriter.js.map +1 -0
  434. package/dist/infrastructure/storage/state/readonly/readOnlyObservation.d.ts +58 -0
  435. package/dist/infrastructure/storage/state/readonly/readOnlyObservation.d.ts.map +1 -0
  436. package/dist/infrastructure/storage/state/readonly/readOnlyObservation.js +195 -0
  437. package/dist/infrastructure/storage/state/readonly/readOnlyObservation.js.map +1 -0
  438. package/dist/infrastructure/storage/state/resolution/resolutionWriter.d.ts +31 -0
  439. package/dist/infrastructure/storage/state/resolution/resolutionWriter.d.ts.map +1 -0
  440. package/dist/infrastructure/storage/state/resolution/resolutionWriter.js +563 -0
  441. package/dist/infrastructure/storage/state/resolution/resolutionWriter.js.map +1 -0
  442. package/dist/infrastructure/storage/state/resolution/resolutionWriterContracts.d.ts +118 -0
  443. package/dist/infrastructure/storage/state/resolution/resolutionWriterContracts.d.ts.map +1 -0
  444. package/dist/infrastructure/storage/state/resolution/resolutionWriterContracts.js +18 -0
  445. package/dist/infrastructure/storage/state/resolution/resolutionWriterContracts.js.map +1 -0
  446. package/dist/infrastructure/storage/state/resolution/resolutionWriterHelpers.d.ts +23 -0
  447. package/dist/infrastructure/storage/state/resolution/resolutionWriterHelpers.d.ts.map +1 -0
  448. package/dist/infrastructure/storage/state/resolution/resolutionWriterHelpers.js +84 -0
  449. package/dist/infrastructure/storage/state/resolution/resolutionWriterHelpers.js.map +1 -0
  450. package/dist/infrastructure/storage/state/resolution/resolutionWriterSql.d.ts +16 -0
  451. package/dist/infrastructure/storage/state/resolution/resolutionWriterSql.d.ts.map +1 -0
  452. package/dist/infrastructure/storage/state/resolution/resolutionWriterSql.js +97 -0
  453. package/dist/infrastructure/storage/state/resolution/resolutionWriterSql.js.map +1 -0
  454. package/dist/infrastructure/storage/sync/outbound/effectOutbox.d.ts +137 -0
  455. package/dist/infrastructure/storage/sync/outbound/effectOutbox.d.ts.map +1 -0
  456. package/dist/infrastructure/storage/sync/outbound/effectOutbox.js +396 -0
  457. package/dist/infrastructure/storage/sync/outbound/effectOutbox.js.map +1 -0
  458. package/dist/infrastructure/storage/sync/outbound/effectOutboxContracts.d.ts +101 -0
  459. package/dist/infrastructure/storage/sync/outbound/effectOutboxContracts.d.ts.map +1 -0
  460. package/dist/infrastructure/storage/sync/outbound/effectOutboxContracts.js +9 -0
  461. package/dist/infrastructure/storage/sync/outbound/effectOutboxContracts.js.map +1 -0
  462. package/dist/infrastructure/storage/sync/outbound/effectOutboxSql.d.ts +21 -0
  463. package/dist/infrastructure/storage/sync/outbound/effectOutboxSql.d.ts.map +1 -0
  464. package/dist/infrastructure/storage/sync/outbound/effectOutboxSql.js +184 -0
  465. package/dist/infrastructure/storage/sync/outbound/effectOutboxSql.js.map +1 -0
  466. package/dist/infrastructure/storage/sync/outbound/effectOutboxSupport.d.ts +25 -0
  467. package/dist/infrastructure/storage/sync/outbound/effectOutboxSupport.d.ts.map +1 -0
  468. package/dist/infrastructure/storage/sync/outbound/effectOutboxSupport.js +158 -0
  469. package/dist/infrastructure/storage/sync/outbound/effectOutboxSupport.js.map +1 -0
  470. package/dist/infrastructure/storage/sync/shared/syncRegistry.d.ts +69 -0
  471. package/dist/infrastructure/storage/sync/shared/syncRegistry.d.ts.map +1 -0
  472. package/dist/infrastructure/storage/sync/shared/syncRegistry.js +272 -0
  473. package/dist/infrastructure/storage/sync/shared/syncRegistry.js.map +1 -0
  474. package/dist/infrastructure/storage/sync/shared/writerLease.d.ts +90 -0
  475. package/dist/infrastructure/storage/sync/shared/writerLease.d.ts.map +1 -0
  476. package/dist/infrastructure/storage/sync/shared/writerLease.js +282 -0
  477. package/dist/infrastructure/storage/sync/shared/writerLease.js.map +1 -0
  478. package/dist/shared/constants.d.ts +8 -0
  479. package/dist/shared/constants.d.ts.map +1 -0
  480. package/dist/shared/constants.js +8 -0
  481. package/dist/shared/constants.js.map +1 -0
  482. package/dist/shared/encoding/constants.d.ts +45 -0
  483. package/dist/shared/encoding/constants.d.ts.map +1 -0
  484. package/dist/shared/encoding/constants.js +37 -0
  485. package/dist/shared/encoding/constants.js.map +1 -0
  486. package/dist/shared/encoding/index.d.ts +6 -0
  487. package/dist/shared/encoding/index.d.ts.map +1 -0
  488. package/dist/shared/encoding/index.js +4 -0
  489. package/dist/shared/encoding/index.js.map +1 -0
  490. package/dist/shared/encoding/stableEncode.d.ts +34 -0
  491. package/dist/shared/encoding/stableEncode.d.ts.map +1 -0
  492. package/dist/shared/encoding/stableEncode.js +210 -0
  493. package/dist/shared/encoding/stableEncode.js.map +1 -0
  494. package/dist/shared/encoding/typeGuards.d.ts +15 -0
  495. package/dist/shared/encoding/typeGuards.d.ts.map +1 -0
  496. package/dist/shared/encoding/typeGuards.js +11 -0
  497. package/dist/shared/encoding/typeGuards.js.map +1 -0
  498. package/dist/shared/encoding/types.d.ts +52 -0
  499. package/dist/shared/encoding/types.d.ts.map +1 -0
  500. package/dist/shared/encoding/types.js +8 -0
  501. package/dist/shared/encoding/types.js.map +1 -0
  502. package/dist/shared/index.d.ts +6 -0
  503. package/dist/shared/index.d.ts.map +1 -0
  504. package/dist/shared/index.js +6 -0
  505. package/dist/shared/index.js.map +1 -0
  506. package/dist/shared/state/constants.d.ts +16 -0
  507. package/dist/shared/state/constants.d.ts.map +1 -0
  508. package/dist/shared/state/constants.js +16 -0
  509. package/dist/shared/state/constants.js.map +1 -0
  510. package/dist/shared/state/index.d.ts +3 -0
  511. package/dist/shared/state/index.d.ts.map +1 -0
  512. package/dist/shared/state/index.js +2 -0
  513. package/dist/shared/state/index.js.map +1 -0
  514. package/dist/shared/state/types.d.ts +23 -0
  515. package/dist/shared/state/types.d.ts.map +1 -0
  516. package/dist/shared/state/types.js +2 -0
  517. package/dist/shared/state/types.js.map +1 -0
  518. package/dist/shared/validation.d.ts +9 -0
  519. package/dist/shared/validation.d.ts.map +1 -0
  520. package/dist/shared/validation.js +25 -0
  521. package/dist/shared/validation.js.map +1 -0
  522. package/docs/architecture.md +72 -0
  523. package/docs/code-guidelines.md +256 -0
  524. package/docs/development.md +48 -0
  525. package/docs/git-workflow.md +224 -0
  526. package/docs/mikro-orm-adapter-spike.md +248 -0
  527. package/docs/quick-start.md +102 -0
  528. package/docs/sql-layer-plan.md +59 -0
  529. package/docs/sync-bulk-write-benchmark.md +1036 -0
  530. package/docs/sync-observability.md +71 -0
  531. package/docs/task-queue-write-model.md +640 -0
  532. package/docs/typed-sheets-mvp-scope-2026-06-29.md +490 -0
  533. package/docs/typed-sheets-plan.md +417 -0
  534. package/docs/write-and-synchronization-flow.md +71 -0
  535. package/package.json +68 -0
@@ -0,0 +1,1036 @@
1
+ # Sync bulk-write benchmark
2
+
3
+ ## 2026-07-24 — raw Apps Script write
4
+
5
+ - Branch: `benchmark/apps-script-bulk-write`
6
+ - Script: `apps-script/gateway/BulkWriteBenchmark.gs`
7
+ - Entry point: `runBulkWriteBenchmark100()`
8
+ - Backend: Apps Script `SpreadsheetApp`
9
+ - Target: `__typed_sheets_bulk_write_benchmark`
10
+ - Dataset: 100 rows × 6 columns = 600 cells
11
+ - Write mode: one contiguous `setValues()` followed by one `flush()`
12
+ - Setup: the benchmark appended at row 22; one-time sheet setup was excluded
13
+ from the measured write interval
14
+
15
+ | Measurement | Setup | `setValues()` | `flush()` | Steady-state total |
16
+ | --- | ---: | ---: | ---: | ---: |
17
+ | 100 rows / 600 cells | 713 ms | 96 ms | 278 ms | 374 ms |
18
+
19
+ Throughput was 267.38 rows/second and 1,604.28 cells/second.
20
+
21
+ ## Comparison: full sync request
22
+
23
+ The previous clean-DB test sent 20 customer effects through the complete
24
+ Gateway path with `POST /sync/once?maxEffects=20`. It took 75,409 ms and all 20
25
+ effects were applied. That path included snapshot reads, row metadata, CAS
26
+ checks, postcondition reads, receipts, and request tracing.
27
+
28
+ The raw write benchmark is therefore roughly 1,000 times faster per row than
29
+ the complete 20-effect sync request. This is not a correctness comparison: the
30
+ benchmark intentionally bypasses concurrency checks, user-edit detection,
31
+ receipts, and postcondition verification.
32
+
33
+ ## 2026-07-24 — isolated stage benchmark
34
+
35
+ - Branch: `benchmark/apps-script-bulk-write`
36
+ - Script: `apps-script/gateway/BulkWriteBenchmark.gs`
37
+ - Entry point: `runBulkWriteStageBenchmark20()`
38
+ - Backend: Apps Script `SpreadsheetApp`
39
+ - Dataset: 20 rows × 6 columns
40
+ - Setup time is excluded from each stage measurement.
41
+
42
+ | Stage | Setup | Operation | Flush | Measured total |
43
+ | --- | ---: | ---: | ---: | ---: |
44
+ | `metadata_read` | 35,117 ms | 3,609 ms | — | 3,609 ms |
45
+ | `metadata_write` | 33,273 ms | 31,637 ms | 59 ms | 31,696 ms |
46
+ | `snapshot_read` | 19,540 ms | 4,303 ms | — | 4,303 ms |
47
+ | `cas_compare` | 0 ms | 2 ms | — | 2 ms |
48
+ | `postcondition_read` | 18,280 ms | 2,646 ms | — | 2,646 ms |
49
+ | `receipt_read` | 1,200 ms | 119 ms | — | 119 ms |
50
+ | `receipt_write` | 1,597 ms | 430 ms | 352 ms | 782 ms |
51
+
52
+ The dominant measured stage is `metadata_write`: rewriting three row metadata
53
+ entries for 20 rows took about 31.7 seconds, while the `flush()` itself took
54
+ only 59 ms. This identifies the per-row Developer Metadata API calls as the
55
+ primary bottleneck. The in-memory CAS comparison and receipt operations are
56
+ not significant for this test size.
57
+
58
+ ## 2026-07-24 — visible-state metadata migration
59
+
60
+ The Gateway batch path now treats SQLite as the authority for
61
+ `visibleRevision` (가시적 버전) and `visibleHash` (가시적 상태 해시):
62
+
63
+ - Sheet Developer Metadata keeps only the projection-local row anchor.
64
+ - Batch CAS compares the bulk-read cell hash with the effect's
65
+ `expectedVisibleHash` from SQLite; it no longer reads or rewrites a
66
+ revision/hash metadata pair for every changed row.
67
+ - Successful results and receipts derive the next revision as
68
+ `expectedVisibleRevision + 1`.
69
+ - Batch postconditions verify the raw cell hash in one range read.
70
+ - The receipt sheet remains because it is durable response-loss evidence, not
71
+ per-row Developer Metadata.
72
+ - Existing visible revision/hash metadata is not deleted; it is left inert so
73
+ this migration does not perform a destructive cleanup of user sheets.
74
+
75
+ This change is source-level and requires deploying the updated
76
+ `apps-script/gateway/Code.gs` before a live benchmark. The next comparison
77
+ should rerun `runBulkWriteStageBenchmark20()` and a 20-effect sync against a
78
+ fresh test sheet, then verify that `metadata_write` no longer appears in the
79
+ production batch path and that SQLite confirmations still advance normally.
80
+
81
+ ## 2026-07-24 — new Gateway progressive load test
82
+
83
+ - Branch: `benchmark/apps-script-bulk-write`
84
+ - Harness: `.local/lib-test-beta-0.2.0-beta1`
85
+ - Backend: MikroORM + SQLite locally, deployed Apps Script Gateway remotely
86
+ - Database: fresh `typed-sheets-load-test-new-gateway.sqlite`
87
+ - Setup: provisioned five projection tabs and seeded 20 customers plus 20
88
+ products; the 40-effect seed sync took 46.7 seconds and applied all effects
89
+ with no failures. Setup is excluded from the stage comparison below.
90
+ - Gateway behavior: visible revision/hash row metadata disabled; anchor
91
+ metadata and receipt sheet retained.
92
+ - Stage runner: the local Node fetch runner created orders in groups of
93
+ `1/5/10/20` and called `/sync/once?maxEffects=1/5/10/20` once after each
94
+ group. Each order produced four effects in this scenario.
95
+
96
+ | Orders added | `maxEffects` | Order writes | Sync request | Applied | Failed | Pending after stage |
97
+ | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
98
+ | 1 | 1 | 6.03 s | 5.97 s | 1 | 0 | 3 |
99
+ | 5 | 5 | 11.07 s | 11.03 s | 5 | 0 | 18 |
100
+ | 10 | 10 | 16.22 s | 16.15 s | 10 | 0 | 48 |
101
+ | 20 | 20 | 23.24 s | 23.10 s | 20 | 0 | 108 |
102
+
103
+ The stage sync cost was approximately 1.15 seconds per effect at the 20-effect
104
+ batch, compared with 5.97 seconds per effect at the single-effect batch. The
105
+ cost is still substantial, but the 20-effect request is about 3.3× faster than
106
+ the earlier 75.4-second 20-effect run that rewrote visible row metadata.
107
+
108
+ The accumulated 108-effect backlog was then drained with
109
+ `/sync/once?maxEffects=100`:
110
+
111
+ | Drain pass | Duration | Selected | Applied | Deferred | Failed | Pending after pass |
112
+ | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
113
+ | 1 | 59.85 s | 92 | 60 | 32 | 0 | 48 |
114
+ | 2 | 69.49 s | 48 | 48 | 0 | 0 | 0 |
115
+
116
+ Final SQLite counts were 36 orders, 36 order items, and 36 payments. The final
117
+ outbox contained 184 applied effects and no failed effects. This confirms that
118
+ the new Gateway path can drain this clean backlog, but it does not yet prove
119
+ that arbitrary response loss or a manually edited Sheet will always converge.
120
+
121
+ ## 2026-07-24 — write-batch read reduction
122
+
123
+ - Branch: `benchmark/apps-script-bulk-write`
124
+ - Source: `apps-script/gateway/Code.gs`
125
+ - Scope: the `applyEffects` write path only; the standalone `readSnapshot` path
126
+ remains unchanged
127
+
128
+ The write path no longer uses the reader-oriented full snapshot at both sides
129
+ of a batch:
130
+
131
+ - `createSyncBatchContext_` now reads one raw-value range and row anchors using
132
+ `readSyncBatchState_`; it does not read formulas, display values, or merged
133
+ ranges, and it does not repeat the raw-value range read.
134
+ - The postcondition check now reads only the changed rows, grouped into
135
+ contiguous ranges, instead of reading every registered row from row 2 to the
136
+ sheet's last row.
137
+ - The final full snapshot read was removed from the write response. SQLite is
138
+ authoritative for visible revision/hash state, while each changed effect is
139
+ still verified from raw Sheet cells before its receipt is written. The
140
+ optional `snapshotHash` in an apply response is therefore `null`.
141
+ - Stable row anchors and the receipt sheet remain unchanged because they are
142
+ still required for row identity and response-loss recovery.
143
+
144
+ Static verification passed with `node --check`, the focused Apps Script source
145
+ and sync-client tests (9 tests), `npm run typecheck`, and `npm run build`. A live
146
+ comparison requires deploying this `Code.gs` first. The next benchmark should
147
+ compare the `prepare_batch_context`, `batch_flush`, and postcondition phase
148
+ durations against the progressive run above, especially on a sheet with many
149
+ existing rows.
150
+
151
+ ## 2026-07-24 — fresh-sheet progressive load test after a new deployment
152
+
153
+ - Branch: `benchmark/apps-script-bulk-write`
154
+ - Harness: `.local/lib-test-beta-0.2.0-beta1`
155
+ - Backend: MikroORM + SQLite locally, newly deployed Apps Script Gateway
156
+ - Database: fresh `data/typed-sheets-load-test-progressive-20260724.sqlite`
157
+ - Dataset: 20 customers and 20 products seeded into a new spreadsheet; seed
158
+ synchronization was 40 effects and is excluded from the progressive stage
159
+ comparison
160
+ - Setup commands: `POST /sync/provision`, `POST /load-test/seed` with
161
+ `customers=20`, `products=20`, then `POST /sync/once?maxEffects=100`
162
+ - Progressive script: create order groups of `1/5/10/20`; after each group,
163
+ call `POST /sync/once?maxEffects=<group size>` and record `/metrics` before
164
+ and after the call
165
+ - Background worker: disabled; synchronization was explicitly invoked by the
166
+ test runner
167
+
168
+ The 40-effect seed took 49.87 seconds and applied all effects without failure.
169
+
170
+ | Orders added | `maxEffects` | Order writes | Sync request | Applied | Failed | Pending after stage |
171
+ | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
172
+ | 1 | 1 | 21.98 ms | 6.40 s | 1 | 0 | 3 |
173
+ | 5 | 5 | 42.29 ms | 9.13 s | 5 | 0 | 18 |
174
+ | 10 | 10 | 77.42 ms | 14.72 s | 10 | 0 | 48 |
175
+ | 20 | 20 | 139.47 ms | 27.39 s | 20 | 0 | 108 |
176
+
177
+ The first drain pass selected 92 effects, applied 60, deferred 32 because of
178
+ the Gateway prefix limit, and took 56.62 seconds. The following 16-effect
179
+ `load-payments-state` request exceeded the 120-second client timeout. Its
180
+ response-loss recovery then issued one full `readSnapshot` request per unknown
181
+ effect, with individual reads taking roughly 7–16 seconds. The observed state
182
+ after that recovery was 162 applied, 16 failed, and 6 pending; every failed
183
+ effect had `postcondition_unavailable`.
184
+
185
+ A follow-up drain selected 21 effects (the 16 failed effects were eligible for
186
+ retry plus 5 pending effects), took 184.39 seconds, applied only the 5 pending
187
+ effects, and left the same 16 failures. Final metrics were:
188
+
189
+ | SQLite table | Count |
190
+ | --- | ---: |
191
+ | customers | 20 |
192
+ | products | 20 |
193
+ | orders | 36 |
194
+ | orderItems | 36 |
195
+ | payments | 36 |
196
+
197
+ The final outbox contained 167 applied, 16 failed, and 1 pending effect. This
198
+ test confirms the dominant failure path is not raw `setValues()` throughput:
199
+ an oversized or slow payment batch reaches the 120-second transport timeout,
200
+ then failed-effect retry multiplies the cost by performing full snapshot reads
201
+ per effect. The next fix should make postcondition recovery batch-aware and
202
+ avoid retrying all failed effects in the normal drain pass without a bounded
203
+ backoff or explicit recovery mode.
204
+
205
+ ## 2026-07-24 — fresh-sheet progressive load test with batch postcondition recovery
206
+
207
+ - Branch: `benchmark/apps-script-bulk-write`
208
+ - Harness: `.local/lib-test-beta-0.2.0-beta1`
209
+ - Backend: MikroORM + SQLite locally, newly deployed Apps Script Gateway
210
+ - Database: fresh `data/typed-sheets-load-test-progressive-20260724-new-sheet.sqlite`
211
+ - Dataset: 20 customers and 20 products seeded into a fresh spreadsheet; seed
212
+ synchronization applied 40 effects without failure and is excluded from the
213
+ progressive stage comparison
214
+ - Background worker: disabled; synchronization was explicitly invoked with
215
+ `POST /sync/once`
216
+ - Recovery path: `readEffectPostconditions` batch API; no per-effect full
217
+ `readSnapshot` recovery call
218
+
219
+ | Orders added | `maxEffects` | Order writes | Sync request | Selected | Applied | Requeued | Failed | Pending after stage |
220
+ | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
221
+ | 1 | 1 | 57.16 ms | 5.96 s | 1 | 1 | 0 | 0 | 3 |
222
+ | 5 | 5 | 44.42 ms | 125.51 s | 5 | 0 | 5 | 0 | 23 |
223
+ | 10 | 10 | 77.86 ms | 15.38 s | 10 | 10 | 0 | 0 | 53 |
224
+ | 20 | 20 | 558.76 ms | 18.54 s | 20 | 20 | 0 | 0 | 113 |
225
+
226
+ The five-effect stage exceeded the 120-second client timeout, but the new
227
+ batch postcondition recovery classified all five effects as `unapplied` and
228
+ returned them to `pending`; it did not create `postcondition_unavailable`
229
+ failures or issue one full snapshot read per effect. The other stages applied
230
+ normally.
231
+
232
+ The backlog was drained in multiple passes because the Apps Script Gateway
233
+ still limits each apply prefix to 20 effects, and product stock effects for the
234
+ same target must respect predecessor ordering:
235
+
236
+ | Drain pass | Selected | Applied | Deferred | Pending after pass |
237
+ | ---: | ---: | ---: | ---: | ---: |
238
+ | 1 | 78 | 46 | 32 | 67 |
239
+ | 2 | 33 | 33 | 0 | 34 |
240
+ | 3 | 1 | 1 | 0 | 33 |
241
+ | Ordered-chain passes | 25 | 25 | 0 | 0 |
242
+
243
+ Final metrics were 184 applied effects, zero failed effects, and zero pending
244
+ effects. SQLite contained 36 orders, 36 order items, and 36 payments. This
245
+ reproduces the variable Gateway latency, but confirms that batch postcondition
246
+ recovery prevents the previous per-effect snapshot amplification and allows
247
+ the backlog to converge to zero.
248
+
249
+ ## 2026-07-25 — published beta progressive API load test
250
+
251
+ - Branch: `refactor/thin-sync-gateway`
252
+ - Harness: `.local/lib-test-beta-0.2.0-beta1`
253
+ - Package: published `typed-sheets@0.2.0-beta-1` (not a local workspace link)
254
+ - Backend: MikroORM + SQLite locally, deployed Apps Script Gateway remotely
255
+ - Gateway configuration: the new untracked `.env` values supplied for this
256
+ test; the secret is intentionally not recorded here
257
+ - Background worker: disabled; the load runner invoked `/sync/once` itself
258
+ - Existing state: the test DB already contained one customer, one product, and
259
+ two applied seed effects; the spreadsheet was also reused, so this is not a
260
+ clean-sheet comparison
261
+ - Commands used:
262
+
263
+ ```sh
264
+ LOAD_TEST_DURATION_MS=5000 LOAD_TEST_CONCURRENCY=1 \
265
+ LOAD_TEST_WRITE_RATIO=0.7 LOAD_TEST_SEED_CUSTOMERS=5 \
266
+ LOAD_TEST_SEED_PRODUCTS=5 LOAD_TEST_SYNC=1 LOAD_TEST_DRAIN=0 \
267
+ node --env-file-if-exists=.env load-test.mjs
268
+
269
+ LOAD_TEST_DURATION_MS=10000 LOAD_TEST_CONCURRENCY=2 \
270
+ LOAD_TEST_WRITE_RATIO=0.7 LOAD_TEST_SEED_CUSTOMERS=5 \
271
+ LOAD_TEST_SEED_PRODUCTS=5 LOAD_TEST_SYNC=1 LOAD_TEST_DRAIN=0 \
272
+ node --env-file-if-exists=.env load-test.mjs
273
+ ```
274
+
275
+ | Stage | Duration | Concurrency | Generated orders | Total requests | Requests/s | Sync calls recorded | Applied | Pending | Processing | Failed |
276
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
277
+ | Low | 5 s | 1 | 643 | 918 | 181.30 | 0 | 2 | 3,558 | 10 | 0 |
278
+ | Medium | 10 s | 2 | 876 | 1,283 | 127.24 | 0 | 18 | 8,429 | 45 | 0 |
279
+
280
+ The `/orders` route combines order writes and order-list reads, so its latency
281
+ is not a write-only measurement. The low-stage mixed route latency was p50
282
+ 6.18 ms / p95 9.74 ms / p99 13.93 ms; the medium-stage latency was p50
283
+ 14.67 ms / p95 28.77 ms / p99 39.45 ms. All HTTP requests succeeded.
284
+
285
+ The load runner did not receive a completed `maxEffects=50` sync response before
286
+ each short stage ended, so those calls were aborted from the runner's point of
287
+ view. A standalone `maxEffects=1` request after the low stage selected and
288
+ applied one effect successfully in roughly 3.3 seconds. This gives a direct
289
+ steady-state reference for the consumer path, excluding the load runner's
290
+ health and seed setup.
291
+
292
+ The medium stage already saturated the projection path: local SQLite produced
293
+ orders much faster than the Gateway consumed effects, growing the backlog from
294
+ 3,558 to 8,429 pending effects while failed effects remained at zero. The high
295
+ stage was intentionally not run because it would add backlog and external
296
+ Gateway traffic without changing this conclusion. A clean-sheet drain test is
297
+ still required separately.
298
+
299
+ ## 2026-07-25 — new Gateway fast-append and management-loop smoke test
300
+
301
+ - Branch: `refactor/thin-sync-gateway`
302
+ - Harness: `.local/lib-test-beta-0.2.0-beta1`
303
+ - Backend: MikroORM + SQLite locally, newly deployed Apps Script Gateway
304
+ - Database: fresh `data/typed-sheets-load-test-20260725-new-gateway.sqlite`
305
+ - Gateway configuration: new untracked `.env` values supplied for this test;
306
+ the secret is intentionally not recorded here
307
+ - Background worker: enabled; reconciliation(불일치 보정) management loop was
308
+ enabled with its default 60-second interval
309
+ - Command:
310
+
311
+ ```sh
312
+ env LOAD_TEST_PROVISION=1 LOAD_TEST_DURATION_MS=3000 \
313
+ LOAD_TEST_CONCURRENCY=1 LOAD_TEST_WRITE_RATIO=1 \
314
+ LOAD_TEST_SEED_CUSTOMERS=20 LOAD_TEST_SEED_PRODUCTS=20 \
315
+ LOAD_TEST_DRAIN=1 LOAD_TEST_SYNC_DRAIN_TIMEOUT_MS=120000 \
316
+ npm run load-test
317
+ ```
318
+
319
+ | Stage | Setup / provision | Traffic duration | Generated orders | HTTP errors | Applied | Pending | Processing | Failed |
320
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
321
+ | 20/20 seed | 13.24 s | 3 s | 435 | 0 | 300 | 2,178 | 100 | 0 |
322
+
323
+ The local API successfully created 435 orders, 834 order items, and 435
324
+ payments; all 562 HTTP requests succeeded. The Gateway accepted
325
+ `fastAppendRows` batches of 20, 80, and 100 rows, and each returned `applied`,
326
+ but the remote requests still took approximately 16–37 seconds each. The
327
+ SQLite producer therefore outpaced the Gateway consumer and the drain did not
328
+ reach zero within 120 seconds.
329
+
330
+ The management loop exposed a compatibility defect after provisioning:
331
+ `readSnapshot` returned a protocol version that the Node client rejected as
332
+ unsupported. Consequently reconciliation did not run, so this result validated
333
+ fast append only and did not validate automatic drift repair. The protocol was
334
+ aligned in the follow-up run below.
335
+
336
+ ## 2026-07-26 — protocol-aligned Gateway load test
337
+
338
+ - Branch: `refactor/thin-sync-gateway`
339
+ - Harness: `.local/lib-test-beta-0.2.0-beta1`
340
+ - Backend: MikroORM + SQLite locally, deployed Apps Script Gateway remotely
341
+ - Database: fresh `/private/tmp/typed-sheets-protocol-fixed-20260726.sqlite`
342
+ - Gateway configuration: current untracked `.env` values supplied for this
343
+ test; the secret is intentionally not recorded here
344
+ - Background worker: enabled; reconciliation(불일치 보정) management loop was
345
+ enabled
346
+ - The SQLite database was fresh, but the remote spreadsheet was reused and was
347
+ not an empty-sheet comparison. The first reconciliation scan observed
348
+ existing remote rows.
349
+ - Server command:
350
+
351
+ ```sh
352
+ TYPED_SHEETS_DB_PATH=/private/tmp/typed-sheets-protocol-fixed-20260726.sqlite \
353
+ TYPED_SHEETS_API_PORT=3200 TYPED_SHEETS_BACKGROUND_WORKER=1 npm start
354
+ ```
355
+
356
+ - Load command:
357
+
358
+ ```sh
359
+ LOAD_TEST_PROVISION=1 LOAD_TEST_DURATION_MS=3000 \
360
+ LOAD_TEST_CONCURRENCY=1 LOAD_TEST_WRITE_RATIO=1 \
361
+ LOAD_TEST_SEED_CUSTOMERS=20 LOAD_TEST_SEED_PRODUCTS=20 \
362
+ LOAD_TEST_DRAIN=1 LOAD_TEST_SYNC_DRAIN_TIMEOUT_MS=120000 \
363
+ npm run load-test
364
+ ```
365
+
366
+ | Metric | Result |
367
+ | --- | ---: |
368
+ | Provision setup | 7.49 s |
369
+ | Traffic duration | 3 s |
370
+ | Generated orders | 465 |
371
+ | Total HTTP requests | 591 |
372
+ | HTTP errors | 0 |
373
+ | Steady-state `/orders` requests | 465 successful; p50 6.21 ms / p95 8.93 ms / p99 12.53 ms |
374
+ | Total elapsed including drain wait | 130.99 s |
375
+ | Final applied effects | 20 |
376
+ | Final pending effects | 3,605 |
377
+ | Final failed effects | 0 |
378
+
379
+ The protocol error disappeared: every observed `readSnapshot` request returned
380
+ `ok: true`, including reconciliation reads. However, the first reconciliation
381
+ scan on the reused sheet found 1,350 desired rows and 500 scanned remote rows,
382
+ then enqueued 885 correction effects. Those corrections competed with the
383
+ load-test writes. The Gateway applied the first 20-row `fastAppendRows` batch,
384
+ but returned `operation_failed` for later 80-row and 100-row batches; the
385
+ worker requeued those effects and no final `failed` rows remained. This run
386
+ therefore confirms protocol compatibility, but not successful convergence.
387
+
388
+ The result also shows that starting reconciliation immediately against a
389
+ non-empty reused sheet contaminates a producer-throughput test. The next
390
+ benchmark should use a clean spreadsheet, disable reconciliation for the raw
391
+ fast-append measurement, then run reconciliation separately with a controlled
392
+ drift case.
393
+
394
+ ## 2026-07-26 — clean Sheet protocol-aligned load test
395
+
396
+ - Branch: `refactor/thin-sync-gateway`
397
+ - Harness: `.local/lib-test-beta-0.2.0-beta1`
398
+ - Backend: MikroORM + SQLite locally, newly deployed Apps Script Gateway
399
+ - Database: fresh `/private/tmp/typed-sheets-new-sheet-20260726.sqlite`
400
+ - Gateway configuration: new untracked `.env` values supplied for this test;
401
+ the secret is intentionally not recorded here
402
+ - Background worker: enabled; reconciliation(불일치 보정) management loop was
403
+ enabled
404
+ - The remote Sheet was new and passed provisioning successfully.
405
+ - Command:
406
+
407
+ ```sh
408
+ LOAD_TEST_PROVISION=1 LOAD_TEST_DURATION_MS=3000 \
409
+ LOAD_TEST_CONCURRENCY=1 LOAD_TEST_WRITE_RATIO=1 \
410
+ LOAD_TEST_SEED_CUSTOMERS=20 LOAD_TEST_SEED_PRODUCTS=20 \
411
+ LOAD_TEST_DRAIN=1 LOAD_TEST_SYNC_DRAIN_TIMEOUT_MS=120000 \
412
+ npm run load-test
413
+ ```
414
+
415
+ | Metric | Result |
416
+ | --- | ---: |
417
+ | Provision setup | 16.89 s |
418
+ | Traffic duration | 3 s |
419
+ | Generated orders | 393 |
420
+ | Generated order items | 759 |
421
+ | Generated payments | 393 |
422
+ | Total HTTP requests | 519 |
423
+ | HTTP errors | 0 |
424
+ | Steady-state `/orders` requests | 393 successful; p50 7.29 ms / p95 11.26 ms / p99 15.95 ms |
425
+ | Total elapsed including drain wait | 140.32 s |
426
+ | Final applied effects | 100 |
427
+ | Final pending effects | 2,164 |
428
+ | Final processing effects | 100 |
429
+ | Final failed effects | 0 |
430
+
431
+ The clean Sheet removed the previous reused-data problem for the initial
432
+ fast-append calls: 20-row and 80-row `fastAppendRows` batches were both
433
+ applied successfully, and no protocol or `operation_failed` error was observed
434
+ during those batches. However, the management loop later saw the canonical
435
+ SQLite state growing faster than the Sheet snapshot and enqueued additional
436
+ reconciliation work. The worker could not drain the resulting queue within
437
+ 120 seconds; 2,164 effects remained pending and 100 remained processing when
438
+ the load runner stopped. A separate 20-effect correction request observed an
439
+ `operation_failed` response while the server was being shut down, so that
440
+ late response should not be treated as a clean fast-append result.
441
+
442
+ This isolates the current conclusion more clearly: a clean Sheet can accept
443
+ the tested 20- and 80-row fast-append batches, but the combined producer plus
444
+ reconciliation workload still exceeds the worker/Gateway drain rate. A raw
445
+ fast-append benchmark with reconciliation disabled is still needed to measure
446
+ the Gateway ceiling without management-loop work competing for the same worker.
447
+
448
+ ## 2026-07-26 — new Gateway progressive load test
449
+
450
+ - Branch: `refactor/thin-sync-gateway`
451
+ - Harness: `.local/lib-test-beta-0.2.0-beta1`
452
+ - Backend: MikroORM + SQLite locally, newly deployed Apps Script Gateway
453
+ - Database: fresh `/private/tmp/typed-sheets-progressive-20260726-01.sqlite`
454
+ - Gateway configuration: new untracked values supplied for this test; the
455
+ secret is intentionally not recorded here
456
+ - Provisioning: five projection sheets created and initialized successfully
457
+ - Background worker: enabled; reconciliation management loop enabled
458
+ - Traffic: write-only order workload (`LOAD_TEST_WRITE_RATIO=1`), 5 seconds per
459
+ stage, 20 customers and 20 products seeded at each stage, no drain wait
460
+
461
+ The server was started with the Gateway environment variables supplied for this
462
+ run and `TYPED_SHEETS_WORKER_MAX_EFFECTS=100`. Each stage reused the same local
463
+ database and newly provisioned spreadsheet so the table and outbox totals show
464
+ the cumulative effect of the stages.
465
+
466
+ | Stage | Concurrency | Orders | `/orders` req/s | p50 / p95 / p99 (ms) | Final outbox |
467
+ | --- | ---: | ---: | ---: | ---: | --- |
468
+ | 1 | 1 | 683 | 135.11 | 6.97 / 11.33 / 15.80 | applied 340, pending 4,084 |
469
+ | 2 | 2 | 582 | 112.71 | 15.53 / 27.20 / 36.95 | applied 340, failed 100, pending 8,431 |
470
+ | 3 | 4 | 481 | 93.66 | 42.46 / 67.13 / 141.94 | applied 340, failed 66, pending 12,462, processing 80 |
471
+
472
+ All API requests succeeded with zero HTTP errors. The SQLite totals after the
473
+ third stage were 1,746 orders, 3,385 order items, and 1,746 payments. The
474
+ write path therefore accepted traffic, but the projection path did not keep up:
475
+ the backlog increased by roughly 4,000 effects per stage while the applied
476
+ count remained at 340.
477
+
478
+ The server logs identified two concrete causes:
479
+
480
+ 1. The reconciliation scan observed 340 Sheet rows but 4,434 desired SQLite
481
+ rows and enqueued 1,197 correction effects while the original outbox was
482
+ still being drained. Reconciliation is therefore amplifying the backlog
483
+ when it runs before the initial append queue has caught up.
484
+ 2. The correction effects include the `_deleted` tombstone field, but the
485
+ registered ranges for the test sheets do not declare `_deleted`. Gateway
486
+ postcondition recovery repeatedly failed with:
487
+
488
+ ```text
489
+ postcondition_read_failed: Effect field is not a registered header: _deleted
490
+ ```
491
+
492
+ There was also a direct Gateway cost signal: a `readSnapshot` for a 320-row
493
+ sheet took 65.553 seconds. This is outside the fast append write itself, but it
494
+ shows that running full reconciliation snapshots during a high-write backlog
495
+ can monopolize the same worker and make convergence worse.
496
+
497
+ This run is not a successful convergence test. It demonstrates that the raw
498
+ SQLite API path remains responsive, while the current combination of pending
499
+ effect drain, immediate reconciliation, and an invalid tombstone field cannot
500
+ reach outbox zero.
501
+
502
+ ## 2026-07-26 — pure fast-append Gateway throughput
503
+
504
+ - Branch: `refactor/thin-sync-gateway`
505
+ - Harness: temporary `.local/fast-append-load-test.mjs`
506
+ - Backend: direct `AppsScriptOperationClient` + `createFastAppendRowsOperation`
507
+ calls; no SQLite
508
+ outbox, effect worker, reconciliation, snapshot, postcondition, receipt, or
509
+ delete operation was involved
510
+ - Database: `/private/tmp/typed-sheets-fast-append-20260726-01.sqlite` was used
511
+ only to provision the five projection registry entries; it was not part of
512
+ the measured write path
513
+ - Spreadsheet: newly provisioned `LoadTest_Customers` projection
514
+ - Gateway mode: the newly deployed fast-append-only Code.gs; a verification
515
+ `readSnapshot` request was rejected with `Fast append benchmark accepts only
516
+ fastAppendRows.`
517
+ - Request limit: 100 rows per Gateway call; the 200-row stage used two calls
518
+
519
+ | Batch rows | Gateway calls | Applied rows | Elapsed | Rows/s | Cells/s |
520
+ | ---: | ---: | ---: | ---: | ---: | ---: |
521
+ | 20 | 1 | 20 | 3,309.04 ms | 6.04 | 36.26 |
522
+ | 50 | 1 | 50 | 2,766.91 ms | 18.07 | 108.42 |
523
+ | 100 | 1 | 100 | 2,506.87 ms | 39.89 | 239.34 |
524
+ | 200 | 2 | 200 | 5,459.14 ms | 36.64 | 219.82 |
525
+
526
+ All 370 requested rows were acknowledged as `applied`; no row was left for a
527
+ worker or reconciliation pass. This is the first clean measurement of the
528
+ Gateway's current fast-append path. It confirms that the pure path can process
529
+ the complete tested load, but its steady-state ceiling is approximately 36–40
530
+ rows/s for six-column rows, with roughly 2.5–2.8 seconds of latency per 50–100
531
+ row request. The earlier backlog and `_deleted` failures were therefore not
532
+ part of this measurement.
533
+
534
+ ## 2026-07-26 — continuous drain follow-up
535
+
536
+ - Branch: `refactor/thin-sync-gateway`
537
+ - Harness: `.local/lib-test-beta-0.2.0-beta1`
538
+ - Backend: the same local SQLite database and new Sheet from the preceding
539
+ clean-Sheet test
540
+ - Server command:
541
+
542
+ ```sh
543
+ TYPED_SHEETS_DB_PATH=/private/tmp/typed-sheets-new-sheet-20260726.sqlite \
544
+ TYPED_SHEETS_API_PORT=3200 TYPED_SHEETS_BACKGROUND_WORKER=1 npm start
545
+ ```
546
+
547
+ - The server was restarted with the previous outbox intact. A local metrics
548
+ poll ran every 15 seconds for 180 seconds; no new application traffic was
549
+ generated.
550
+
551
+ | Metric | Result |
552
+ | --- | ---: |
553
+ | Initial recovered state | applied 180 / pending 2,084 / processing 80 / failed 20 |
554
+ | Applied during observation | +240 |
555
+ | Final applied effects | 420 |
556
+ | Final pending effects | 2,337 |
557
+ | Final processing effects | 0 |
558
+ | Final failed effects | 20 |
559
+ | Observation duration | 180 s |
560
+
561
+ The worker did continue consuming effects after the load runner had stopped;
562
+ this disproves the idea that the 120-second test timeout closes the Gateway or
563
+ stops synchronization. However, the queue did not converge. Reconciliation
564
+ continued to enqueue correction effects while the worker was consuming the
565
+ existing queue, so pending temporarily increased from 2,104 to 2,417 before
566
+ ending at 2,337.
567
+
568
+ The 20 failed effects were repeatedly rejected during batched postcondition
569
+ recovery with:
570
+
571
+ ```text
572
+ postcondition_read_failed: Effect field is not a registered header: _deleted
573
+ ```
574
+
575
+ Those effects reached four attempts and remained failed. This is a concrete
576
+ blocking defect independent of the 120-second drain window: the worker can
577
+ consume some work indefinitely, but these effects cannot complete until the
578
+ registered Sheet headers and the postcondition payload agree. The continuous
579
+ test therefore confirms ongoing consumption, but not eventual convergence.
580
+
581
+ ## 2026-07-27 — lib-test-beta thin-interface fast-append benchmark
582
+
583
+ - Branch: `refactor/thin-sync-gateway`
584
+ - Historical harness: `.local/lib-test-beta-0.2.0-beta1/fast-append-load-test.mjs`
585
+ (removed after this benchmark)
586
+ - Server: `.local/lib-test-beta-0.2.0-beta1/server.mjs`, using the current
587
+ `AppsScriptOperationClient` and `createFastAppendRowsOperation` library APIs
588
+ - Database: fresh `/private/tmp/typed-sheets-lib-test-fast-append-20260727.sqlite`
589
+ used only to initialize the lib-test runtime; SQLite outbox/effect processing
590
+ was not part of the measured path
591
+ - Gateway: newly supplied deployment; URL, shared secret, and spreadsheet ID
592
+ are intentionally not recorded
593
+ - Background effect worker: disabled
594
+ - Reconciliation: disabled
595
+ - Setup: one unmeasured operation created/initialized `LoadTest_Customers` and
596
+ wrote its six-column header
597
+ - Measured scenario: three sequential requests, one contiguous `setValues()`
598
+ operation per stage; no metadata, snapshot, CAS, receipt, postcondition, or
599
+ delete work
600
+
601
+ Command used for the measured runner:
602
+
603
+ ```sh
604
+ LOAD_TEST_BASE_URL=http://127.0.0.1:3205 \
605
+ node .local/lib-test-beta-0.2.0-beta1/fast-append-load-test.mjs
606
+ ```
607
+
608
+ | Batch rows | Gateway calls | Applied rows | Elapsed | Rows/s | Cells/s |
609
+ | ---: | ---: | ---: | ---: | ---: | ---: |
610
+ | 20 | 1 | 20 | 2,275 ms | 8.79 | 52.75 |
611
+ | 100 | 1 | 100 | 2,729 ms | 36.64 | 219.86 |
612
+ | 370 | 1 | 370 | 3,792 ms | 97.57 | 585.44 |
613
+
614
+ All 490 rows across the three stages returned `applied`, with HTTP 200 and no
615
+ client or remote error. The 20-row stage includes the fixed HTTP/Apps Script
616
+ startup cost; larger contiguous writes amortized that cost substantially. The
617
+ 370-row one-request result is materially faster than the earlier 200-row
618
+ two-request measurement (36.64 rows/s), but it is not a complete production
619
+ sync test because it bypasses SQLite outbox consumption and reconciliation.
620
+
621
+ ## 2026-07-27 — operational User/Order/OrderItem end-to-end test
622
+
623
+ - Branch: `refactor/thin-sync-gateway`
624
+ - Harness: `.local/typed-sheets-e2e.mjs`
625
+ - Command: `npm run build`, then `node .local/typed-sheets-e2e.mjs` with the
626
+ supplied Gateway environment variables; secret and Sheet ID are intentionally
627
+ not recorded
628
+ - Package boundary: the server imported the built package entry
629
+ (`dist/index.js`), `typed-sheets/orm`, and `typed-sheets/mikro-orm`; the test
630
+ runner rejected a `src` import and did not call the Gateway directly
631
+ - Domain tables: `User`, `Order`, and `OrderItem`; the library's internal
632
+ registry and outbox tables were also created by the MikroORM SQLite adapter
633
+ - Gateway: the supplied thin `Code.gs` deployment; provisioning and all remote
634
+ writes went through `AppsScriptOperationClient` and
635
+ `AppsScriptOperationSyncGateway`
636
+ - Spreadsheet setup: each run used three unique tabs derived from its run ID,
637
+ so existing Sheet data was neither deleted nor reused
638
+ - Worker: started automatically by the server after successful provisioning;
639
+ maximum 100 effects per pass
640
+ - Reconciliation: disabled for this isolated SQLite-to-Sheets create path
641
+ - API concurrency: 4 HTTP clients
642
+
643
+ The functional smoke flow created one User, one Order, and two OrderItems, then
644
+ read the User-Order-OrderItem graph through the HTTP API. Each load stage used
645
+ the same API and EntityManager path, generated durable SQLite outbox effects,
646
+ and waited for the server-owned worker to drain them. The stage measurements
647
+ exclude one-time package build, SQLite migration, Sheet provisioning, and the
648
+ functional smoke setup; they are the no-setup/steady-state measurements.
649
+
650
+ | Stage | New Orders | New OrderItems | Setup excluded | Stage time | SQLite rows = Sheet rows | Failed effects |
651
+ | ---: | ---: | ---: | :---: | ---: | --- | ---: |
652
+ | Smoke | 1 | 2 | — | functional check | 1 User / 1 Order / 2 Items | 0 |
653
+ | 20 | 20 | 40 | yes | 12,673 ms | 1 / 21 / 42 | 0 |
654
+ | 100 | 100 | 200 | yes | 16,918 ms | 1 / 121 / 242 | 0 |
655
+ | 370 | 370 | 740 | yes | 40,196 ms | 1 / 491 / 982 | 0 |
656
+
657
+ The final cumulative worker count was 1,474 applied effects, with zero failed
658
+ effects and zero ready outbox effects. Every dedicated Sheet tab matched the
659
+ SQLite domain count. The server was stopped and restarted against the same
660
+ SQLite file; the original Order and its two OrderItems were read successfully
661
+ after restart.
662
+
663
+ ## 2026-07-27 — operational test with idle-gated reconciliation
664
+
665
+ - Branch: `refactor/thin-sync-gateway`
666
+ - Harness: `.local/typed-sheets-e2e.mjs`
667
+ - Backend: built `typed-sheets` package, MikroORM, and local SQLite
668
+ - Gateway: the previously supplied Apps Script deployment; credentials and
669
+ spreadsheet ID are intentionally not recorded
670
+ - Scenario command (Gateway environment variables were supplied separately):
671
+
672
+ ```sh
673
+ TYPED_SHEETS_OPERATIONAL_LOAD_STAGES=1 \
674
+ TYPED_SHEETS_OPERATIONAL_RECONCILIATION_INTERVAL_MS=1000 \
675
+ TYPED_SHEETS_OPERATIONAL_DRAIN_TIMEOUT_MS=120000 \
676
+ node .local/typed-sheets-e2e.mjs
677
+ ```
678
+
679
+ - Reconciliation policy: run only after the worker pass is idle and SQLite has
680
+ no `pending` or `processing` outbox effects
681
+ - Dataset: one functional smoke flow, followed by one additional Order and two
682
+ OrderItems through the HTTP API
683
+ - Setup: package build, SQLite migration, Sheet provisioning, and functional
684
+ smoke setup are excluded from the stage timing
685
+
686
+ | Stage | New Orders | New OrderItems | Stage time | SQLite rows = Sheet rows | Failed effects |
687
+ | ---: | ---: | ---: | ---: | --- | ---: |
688
+ | 1 | 1 | 2 | 38,724 ms | 1 User / 2 Orders / 4 Items | 0 |
689
+
690
+ The operational E2E completed successfully. It imported the built package,
691
+ created all three domain tables through the library, wrote through the SQLite
692
+ outbox and server-owned worker, read the entity graph through the HTTP API, and
693
+ verified the same row counts in Sheets. The server restart check also passed.
694
+ The final outbox contained 21 applied effects and zero pending or processing
695
+ effects.
696
+
697
+ The test also exposed a remaining system-level issue. Even after the worker
698
+ became idle, reconciliation observed a stale or incomplete Sheet snapshot and
699
+ created correction effects:
700
+
701
+ | Reconciliation observation | Desired rows | Drifted rows | Missing rows | Correction effects |
702
+ | --- | ---: | ---: | ---: | ---: |
703
+ | First scan | 4 | 0 | 4 | 4 |
704
+ | Later scan | 7 | 4 | 3 | 7 |
705
+
706
+ Fourteen reconciliation effects were eventually applied, which is why the
707
+ final row counts converged. This means the idle gate prevents reconciliation
708
+ from competing with an actively pending outbox, but it does not yet prove that
709
+ an `applied` worker result and the subsequent Sheet snapshot are immediately
710
+ consistent. The normal write result, Gateway snapshot behavior, and
711
+ reconciliation identity matching need separate investigation before claiming
712
+ that the correction path is only a rare safety net.
713
+
714
+ Compared with the earlier pure fast-append benchmark (370 synthetic rows in
715
+ 3,792 ms), this run is not a raw `setValues()` comparison: it includes 1,110
716
+ entity effects, SQLite flush/outbox work, worker leasing, multiple Gateway
717
+ requests, and Sheet row-count verification. It confirms that the current
718
+ library-to-worker-to-thin-Gateway create path converges for this workload, not
719
+ that update/delete or reconciliation paths are complete.
720
+
721
+ ## 2026-07-27 — batched anchor lookup polling benchmark
722
+
723
+ - Branch: `refactor/thin-sync-gateway`
724
+ - Harness: `.local/typed-sheets-e2e.mjs`
725
+ - Backend: built `typed-sheets` package, MikroORM, and local SQLite
726
+ - Gateway: an operational `Code.gs` deployment; URL, secret, and spreadsheet ID
727
+ are intentionally not recorded
728
+ - Reconciliation: disabled so it could not add correction effects during the
729
+ polling measurement
730
+ - Scenario: create one smoke User plus 20 Orders and 40 OrderItems through the
731
+ HTTP API, wait for the server-owned effect worker to drain, then run the
732
+ polling path twice
733
+ - Command shape (Gateway environment variables were supplied separately):
734
+
735
+ ```sh
736
+ npm run build
737
+ TYPED_SHEETS_OPERATIONAL_RECONCILIATION=0 \
738
+ TYPED_SHEETS_OPERATIONAL_POLLING_BENCHMARK=1 \
739
+ TYPED_SHEETS_OPERATIONAL_POLLING_RUNS=2 \
740
+ TYPED_SHEETS_OPERATIONAL_LOAD_STAGES=20 \
741
+ node .local/typed-sheets-e2e.mjs
742
+ ```
743
+
744
+ The observation operation was changed from one `getDeveloperMetadata()` call
745
+ per row to one Sheet-scoped `DeveloperMetadataFinder` search per operation.
746
+ The returned row locations are indexed in memory and reused by both anchor
747
+ assignment and snapshot construction. Existing duplicate-anchor and
748
+ unanchored-row behavior remains unchanged.
749
+
750
+ | Poll run | Rows scanned | Anchors assigned | Users | Orders | OrderItems | Poll time |
751
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: |
752
+ | First (includes anchor setup) | 64 | 64 | 1 | 21 | 42 | 56,159 ms |
753
+ | Second (steady state) | 64 | 0 | 1 | 21 | 42 | 55,401 ms |
754
+
755
+ The load stage itself took 12,483 ms and produced matching SQLite/Sheet row
756
+ counts with zero failed effects. The first poll assigned one anchor per row;
757
+ this write remains intentionally per-row because `fastAppend` does not create
758
+ Developer Metadata. The second poll had no anchor writes, but its duration was
759
+ almost unchanged. Therefore the batch lookup removes the explicit row-by-row
760
+ metadata-read pattern, but it is not yet a material end-to-end polling speedup.
761
+
762
+ The remaining polling cost is now likely distributed across the full range
763
+ reads (`values`, formulas, display values, and merged ranges), per-cell
764
+ normalization and SHA-256 hashing, and conversion of the returned metadata
765
+ locations. A phase-level benchmark is required before claiming that any one of
766
+ those is the next dominant stage. This result also means the earlier estimate
767
+ of a few minutes for 10,000 rows cannot be accepted as a measured expectation;
768
+ the current steady-state result is still too slow to extrapolate safely.
769
+
770
+ ## 2026-07-27 — operational timing run on the newly deployed Gateway
771
+
772
+ - Branch: `refactor/thin-sync-gateway`
773
+ - Harness: `.local/typed-sheets-e2e.mjs`
774
+ - Backend: built `typed-sheets` package, MikroORM, and local SQLite
775
+ - Package boundary: the server imported `dist/index.js`; the runner did not
776
+ import `src/**` or call SQLite/Gateway operations directly
777
+ - Gateway: the newly supplied Apps Script deployment; URL, secret, and
778
+ spreadsheet ID are intentionally not recorded
779
+ - Reconciliation: disabled for this isolated write-path measurement
780
+ - API concurrency: 4; load stage: 20 new Orders with 40 new OrderItems
781
+ - Sheet setup: three unique projection tabs were provisioned for this run;
782
+ setup and provisioning are excluded from the stage timer
783
+ - Command shape (Gateway environment variables were supplied separately):
784
+
785
+ ```sh
786
+ TYPED_SHEETS_OPERATIONAL_RECONCILIATION=0 \
787
+ TYPED_SHEETS_OPERATIONAL_LOAD_STAGES=20 \
788
+ TYPED_SHEETS_OPERATIONAL_DRAIN_TIMEOUT_MS=240000 \
789
+ node .local/typed-sheets-e2e.mjs
790
+ ```
791
+
792
+ The functional smoke path created a User, an Order, and two OrderItems,
793
+ read the entity graph through the HTTP API, updated an Order through the
794
+ EntityManager, and removed the Order and its two related test items. Delete is
795
+ represented in the System_State projection by the library's
796
+ `__typed_sheets_deleted` tombstone, so the verification compares active rows,
797
+ not physical Sheet row count.
798
+
799
+ | Stage | New Orders | New OrderItems | Stage time | SQLite active rows = Sheet active rows | Failed effects |
800
+ | ---: | ---: | ---: | ---: | --- | ---: |
801
+ | Smoke | 1 | 2 | functional check | 1 User / 1 Order / 2 Items | 0 |
802
+ | 20 | 20 | 40 | 28,182 ms | 1 / 21 / 42 | 0 |
803
+
804
+ The stage finished with 69 applied effects, zero failed effects, and no ready
805
+ or active effects. The server was stopped and restarted against the same
806
+ SQLite file; the original Order and its two OrderItems were readable after
807
+ restart. All remote requests returned HTTP 200.
808
+
809
+ The timing sink separated local ORM work, worker orchestration, and the remote
810
+ Gateway. The largest steady-state totals were:
811
+
812
+ | Scope | Operation | Phase | Calls | Total | Max |
813
+ | --- | --- | --- | ---: | ---: | ---: |
814
+ | Worker | append | `append_gateway_dispatch` | 9 | 21,698 ms | 4,301 ms |
815
+ | Worker | update-like | `regular_gateway_dispatch` | 3 | 12,512 ms | 5,217 ms |
816
+ | Gateway | update-like | `dispatcher_eval` | 3 | 6,553 ms | 2,920 ms |
817
+ | Gateway | update-like | `script_lock` | 3 | 6,318 ms | 2,830 ms |
818
+ | Gateway | append | `dispatcher_flush` | 9 | 2,091 ms | 293 ms |
819
+ | Gateway | append | `append_range_lookup` | 9 | 896 ms | 248 ms |
820
+ | Gateway | append | `set_values` | 9 | 92 ms | 17 ms |
821
+ | ORM flush | append | `flush_total` | 23 | 55 ms | 4 ms |
822
+ | ORM flush | delete | `flush_total` | 1 | 2 ms | 2 ms |
823
+
824
+ This run confirms that local SQLite persistence and outbox creation are not the
825
+ current bottleneck: ORM flushes were measured in milliseconds. The dominant
826
+ cost is the Gateway round trip and Apps Script dispatcher/lock overhead. The
827
+ raw append `setValues()` work itself was only 92 ms across nine requests; the
828
+ remaining append time is request dispatch, range lookup, and remote execution
829
+ overhead. At the ORM layer `em.remove()` is correctly classified as `delete`,
830
+ but its System_State materialization is a tombstone write and therefore appears
831
+ in the Gateway's regular update-like timing path rather than as a physical row
832
+ delete.
833
+
834
+ This is a successful functional and timing run for the 20-order operational
835
+ scenario. It does not establish the sustained 100/370-order drain ceiling, nor
836
+ does it test reconciliation or user-edit conflict handling; those require
837
+ separate runs so their reads and corrective effects do not contaminate this
838
+ write-path measurement.
839
+
840
+ ## 2026-07-27 — progressive operational throughput run
841
+
842
+ - Branch: `refactor/thin-sync-gateway`
843
+ - Harness: `.local/typed-sheets-e2e.mjs`
844
+ - Backend: built `typed-sheets` package, MikroORM, and local SQLite
845
+ - Package boundary: the server imported `dist/index.js`; the runner did not
846
+ import `src/**` or call SQLite/Gateway operations directly
847
+ - Gateway: the newly supplied Apps Script deployment; credentials are not
848
+ recorded
849
+ - Reconciliation: disabled for this isolated throughput measurement
850
+ - Stage Sheet verification: disabled so the result measures API ingestion plus
851
+ worker drain, without an additional full snapshot read after every stage
852
+ - Load stages: 20, 100, and 370 new Orders; each Order created two OrderItems
853
+ - API concurrency: 4; Gateway timeout: 120 seconds; drain timeout: 240 seconds
854
+ - Command shape (Gateway environment variables were supplied separately):
855
+
856
+ ```sh
857
+ TYPED_SHEETS_OPERATIONAL_RECONCILIATION=0 \
858
+ TYPED_SHEETS_OPERATIONAL_LOAD_STAGES='20,100,370' \
859
+ TYPED_SHEETS_OPERATIONAL_VERIFY_STAGE_SHEETS=0 \
860
+ TYPED_SHEETS_OPERATIONAL_DRAIN_TIMEOUT_MS=240000 \
861
+ node .local/typed-sheets-e2e.mjs
862
+ ```
863
+
864
+ | Stage | New Orders | New OrderItems | Materialized rows | Stage time | Approx. rows/s | Cumulative applied | Failed |
865
+ | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
866
+ | 20 | 20 | 40 | 60 | 11,987 ms | 5.0 | 69 | 0 |
867
+ | 100 | 100 | 200 | 300 | 14,960 ms | 20.1 | 369 | 0 |
868
+ | 370 | 370 | 740 | 1,110 | 36,865 ms | 30.1 | 1,479 | 0 |
869
+
870
+ All three stages completed through the real HTTP server and the deployed
871
+ Gateway. The worker reached the expected applied count at every stage, no
872
+ effect failed, the server restarted successfully against the same SQLite file,
873
+ and the persisted Order/OrderItem graph remained readable after restart.
874
+
875
+ The dominant measured cost was still the remote worker-to-Gateway path:
876
+
877
+ | Scope | Phase | Calls | Total | Max |
878
+ | --- | --- | ---: | ---: | ---: |
879
+ | Worker | `append_gateway_dispatch` | 27 | 67,825 ms | 3,964 ms |
880
+ | Worker | `worker_total` (append) | 20 | 71,592 ms | 6,068 ms |
881
+ | Gateway | `append_range_lookup` | 27 | 3,941 ms | 938 ms |
882
+ | Gateway | `set_values` | 27 | 370 ms | 52 ms |
883
+ | ORM flush | `flush_total` (append) | 493 | 1,118 ms | 7 ms |
884
+
885
+ This separates the bottleneck more clearly: local SQLite/ORM flush work is
886
+ roughly millisecond-scale, and the raw Gateway `setValues()` work is small.
887
+ Most elapsed time is HTTP/Apps Script dispatch and Gateway range lookup. The
888
+ stage timer includes API entity creation and worker drain, so it is an
889
+ end-to-end operational throughput result, not a raw `setValues()` benchmark.
890
+
891
+ The polling probe returned zero rows and zero tables on both steady-state runs.
892
+ That is **not** a measured user-input polling speed: this operational fixture
893
+ registers only `system_state` projections, so there is no `user_input`
894
+ projection for the optimized polling path to scan. User-input polling needs a
895
+ separate fixture with a valid user-input registration and data.
896
+
897
+ ## 2026-07-27 — corrected full-table polling run
898
+
899
+ The previous section's polling probe was invalid for this operational scenario:
900
+ it filtered by the projection label `user_input`, even though the test's User,
901
+ Order, and OrderItem tabs are all domain tables represented by `system_state`.
902
+ The polling helper now batches all registered projections; `user_input` remains
903
+ an ownership/edit-mode classification, not a domain table name.
904
+
905
+ - Branch: `refactor/thin-sync-gateway`
906
+ - Harness: `.local/typed-sheets-e2e.mjs`
907
+ - Backend and package boundary: same as the progressive throughput run above
908
+ - Reconciliation: disabled
909
+ - Load stage: 20 Orders and 40 OrderItems, plus the functional smoke rows
910
+ - Polling: one combined Gateway observation request for User, Order, and
911
+ OrderItem, followed by two polling passes
912
+ - Command shape (Gateway environment variables were supplied separately):
913
+
914
+ ```sh
915
+ TYPED_SHEETS_OPERATIONAL_RECONCILIATION=0 \
916
+ TYPED_SHEETS_OPERATIONAL_LOAD_STAGES=20 \
917
+ TYPED_SHEETS_OPERATIONAL_POLLING_BENCHMARK=1 \
918
+ TYPED_SHEETS_OPERATIONAL_POLLING_RUNS=2 \
919
+ TYPED_SHEETS_OPERATIONAL_VERIFY_STAGE_SHEETS=0 \
920
+ node .local/typed-sheets-e2e.mjs
921
+ ```
922
+
923
+ | Poll pass | Rows scanned | Anchors assigned | User rows | Order rows | OrderItem rows | Elapsed |
924
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: |
925
+ | First (anchor setup) | 66 | 60 | 1 | 22 | 43 | 34,919 ms |
926
+ | Second (steady state) | 66 | 0 | 1 | 22 | 43 | 75,150 ms |
927
+
928
+ Both passes completed without failed effects. The first pass assigned anchors
929
+ to rows created by fast-append; the second pass reused all 66 anchors. The
930
+ steady-state pass was slower than the first despite doing no metadata writes,
931
+ so the dominant polling cost is not only anchor assignment. The full snapshot
932
+ read, Apps Script lock/dispatch latency, range reads, normalization, hashing,
933
+ and SQLite observation persistence all remain in the path.
934
+
935
+ A separate 20/100/370 run reached the 370 stage but could not start its full
936
+ table polling pass: the Gateway rejected the Order observation while adding
937
+ row Developer Metadata because the test Spreadsheet exceeded its allowed
938
+ metadata storage. This is an independent scalability limit in the current
939
+ anchor-based polling design; it prevents claiming a 370-row polling result.
940
+
941
+ ## 2026-07-27 — polling phase trace
942
+
943
+ The observation Gateway and local ingestion path were instrumented to separate
944
+ remote Apps Script work from SQLite comparison and observation persistence. The
945
+ same 20-Order/40-OrderItem operational fixture scanned 66 rows across the three
946
+ domain tabs.
947
+
948
+ | Poll pass | User Gateway | Order Gateway | OrderItem Gateway | Local SQLite/observation | Total polling |
949
+ | --- | ---: | ---: | ---: | ---: | ---: |
950
+ | First, 60 anchors assigned | 994 ms | 8,917 ms | 18,464 ms | 8 ms | 31,371 ms |
951
+ | Second, steady state | 635 ms | 6,748 ms | 17,661 ms | 6 ms | 27,652 ms |
952
+
953
+ The first pass spent 14,544 ms assigning 60 row anchors: 4,808 ms for Order
954
+ and 9,735 ms for OrderItem. The second pass assigned no anchors, but still
955
+ spent 10,402 ms reading Developer Metadata, so removing anchor writes alone is
956
+ not enough.
957
+
958
+ The largest steady-state remote phases were:
959
+
960
+ | Phase | User | Order | OrderItem | Combined |
961
+ | --- | ---: | ---: | ---: | ---: |
962
+ | `anchor_metadata_read` | 228 ms | 2,794 ms | 7,380 ms | 10,402 ms |
963
+ | `row_normalization` | 30 ms | 786 ms | 5,705 ms | 6,521 ms |
964
+ | `snapshot_hash` | 111 ms | 2,502 ms | 4,204 ms | 6,817 ms |
965
+
966
+ These phases are nested inside `snapshot_build`, so their combined values must
967
+ not be added again to the per-table Gateway totals. The raw `values_read`
968
+ phase was only 12 ms in steady state, and local SQLite shadow comparison plus
969
+ observation persistence took 6 ms. Therefore the current polling bottleneck is
970
+ not SQLite and not the basic Sheet range read; it is the Developer Metadata
971
+ finder plus repeated Apps Script hashing/normalization work. OrderItem alone
972
+ accounted for 17,661 ms of the 27,652 ms steady-state pass.
973
+
974
+ This confirms that the current full-snapshot polling path is not suitable as a
975
+ frequent normal synchronization mechanism at this size. It is currently more
976
+ appropriate as an infrequent safety scan, while `onEdit` or a redesigned
977
+ lightweight identity/change path handles the normal user-edit signal. The
978
+ 370-row polling pass remains unmeasured because the current anchor metadata
979
+ quota is reached before the scan completes.
980
+
981
+ ## 2026-07-27 — lightweight values-only polling
982
+
983
+ - Branch: `refactor/thin-sync-gateway`
984
+ - Harness: `.local/typed-sheets-e2e.mjs`
985
+ - Backend: built `typed-sheets` package, MikroORM, and local SQLite
986
+ - Package boundary: the server imported `dist/index.js`; the runner did not
987
+ import `src/**` or call SQLite/Gateway operations directly
988
+ - Gateway: the supplied Apps Script deployment; URL, secret, and spreadsheet
989
+ ID are intentionally not recorded
990
+ - Reconciliation: disabled
991
+ - Dataset: one smoke User plus 20 Orders and 40 OrderItems (66 Sheet rows)
992
+ - Scenario: the server-owned worker drained the outbox, then `/poll` issued one
993
+ batched values-only read for User, Order, and OrderItem twice
994
+ - Command shape (Gateway environment variables were supplied separately):
995
+
996
+ ```sh
997
+ npm run build
998
+ TYPED_SHEETS_OPERATIONAL_RECONCILIATION=0 \
999
+ TYPED_SHEETS_OPERATIONAL_LOAD_STAGES=20 \
1000
+ TYPED_SHEETS_OPERATIONAL_POLLING_BENCHMARK=1 \
1001
+ TYPED_SHEETS_OPERATIONAL_POLLING_RUNS=2 \
1002
+ TYPED_SHEETS_OPERATIONAL_VERIFY_STAGE_SHEETS=0 \
1003
+ TYPED_SHEETS_OPERATIONAL_SKIP_TIMING_SUMMARY=1 \
1004
+ node .local/typed-sheets-e2e.mjs
1005
+ ```
1006
+
1007
+ | Poll pass | Rows scanned | Unchanged | Changed | Unknown/invalid | Elapsed | Remote read total |
1008
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: |
1009
+ | First | 66 | 66 | 0 | 0 / 0 | 2,109 ms | 573 ms |
1010
+ | Second, steady state | 66 | 66 | 0 | 0 / 0 | 2,240 ms | 530 ms |
1011
+
1012
+ Each pass used one signed Apps Script request containing three independent
1013
+ `getValues()` operations. No `LockService`, Developer Metadata, snapshot hash,
1014
+ receipt, or observation persistence was used. The remote phases per table were
1015
+ approximately 210–220 ms for User, 155–210 ms for Order, and 156–163 ms for
1016
+ OrderItem; the remainder was HTTP dispatch and local canonical comparison.
1017
+
1018
+ The test passed through the real HTTP server, package boundary, SQLite outbox,
1019
+ background worker, and deployed Gateway. The final rows were classified as
1020
+ unchanged, including active and tombstoned System_State rows, and the server
1021
+ restart check passed. Compared with the previous steady-state full-snapshot
1022
+ poll (27,652 ms for the same 66-row shape), the lightweight path was roughly
1023
+ 12 times faster. This is a read/compare benchmark: changed rows are returned
1024
+ to the caller, but this pass does not yet turn them into evaluated user-edit
1025
+ events or canonical writes.
1026
+
1027
+ ## Caveats
1028
+
1029
+ - The raw benchmark measures a throughput upper bound, not production sync
1030
+ behavior.
1031
+ - The benchmark uses an isolated sheet and synthetic string values.
1032
+ - Network, HTTP, lock acquisition, retry, and response-loss behavior are not
1033
+ included.
1034
+ - The result strongly indicates that the current Gateway validation and
1035
+ metadata path, rather than raw `setValues()` throughput, is the dominant
1036
+ bottleneck.