@harperfast/harper 5.3.0-beta.4 → 5.3.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 (314) hide show
  1. package/bin/backup.ts +6 -1
  2. package/components/Application.ts +486 -115
  3. package/components/DESIGN.md +109 -21
  4. package/components/deploymentProvenance.ts +28 -0
  5. package/components/operations.js +14 -7
  6. package/components/packageComponent.ts +5 -1
  7. package/config-root.schema.json +1 -1
  8. package/dataLayer/DESIGN.md +35 -7
  9. package/dataLayer/blobBackup.ts +71 -30
  10. package/dataLayer/harperBridge/ResourceBridge.ts +16 -2
  11. package/dataLayer/rocksdbBackup.ts +88 -34
  12. package/dataLayer/schemaDescribe.ts +45 -1
  13. package/dist/bin/backup.js +1 -1
  14. package/dist/bin/backup.js.map +1 -1
  15. package/dist/components/Application.d.ts +11 -21
  16. package/dist/components/Application.js +436 -105
  17. package/dist/components/Application.js.map +1 -1
  18. package/dist/components/deploymentProvenance.d.ts +4 -0
  19. package/dist/components/deploymentProvenance.js +32 -0
  20. package/dist/components/deploymentProvenance.js.map +1 -0
  21. package/dist/components/operations.js +14 -7
  22. package/dist/components/operations.js.map +1 -1
  23. package/dist/components/packageComponent.js +6 -1
  24. package/dist/components/packageComponent.js.map +1 -1
  25. package/dist/dataLayer/blobBackup.d.ts +30 -3
  26. package/dist/dataLayer/blobBackup.js +73 -36
  27. package/dist/dataLayer/blobBackup.js.map +1 -1
  28. package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +1 -0
  29. package/dist/dataLayer/harperBridge/ResourceBridge.js +15 -3
  30. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  31. package/dist/dataLayer/rocksdbBackup.d.ts +4 -1
  32. package/dist/dataLayer/rocksdbBackup.js +74 -36
  33. package/dist/dataLayer/rocksdbBackup.js.map +1 -1
  34. package/dist/dataLayer/schemaDescribe.js +45 -1
  35. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  36. package/dist/resources/DatabaseTransaction.d.ts +1 -0
  37. package/dist/resources/DatabaseTransaction.js +33 -8
  38. package/dist/resources/DatabaseTransaction.js.map +1 -1
  39. package/dist/resources/ResourceInterface.d.ts +7 -3
  40. package/dist/resources/ResourceInterface.js +2 -0
  41. package/dist/resources/ResourceInterface.js.map +1 -1
  42. package/dist/resources/Table.d.ts +151 -445
  43. package/dist/resources/Table.js +209 -8
  44. package/dist/resources/Table.js.map +1 -1
  45. package/dist/resources/analytics/write.d.ts +11 -0
  46. package/dist/resources/analytics/write.js +3 -0
  47. package/dist/resources/analytics/write.js.map +1 -1
  48. package/dist/resources/blob.d.ts +30 -0
  49. package/dist/resources/blob.js +189 -1
  50. package/dist/resources/blob.js.map +1 -1
  51. package/dist/resources/databases.d.ts +1 -0
  52. package/dist/resources/databases.js +80 -15
  53. package/dist/resources/databases.js.map +1 -1
  54. package/dist/resources/derivedIndexRuntime.d.ts +13 -0
  55. package/dist/resources/derivedIndexRuntime.js +57 -5
  56. package/dist/resources/derivedIndexRuntime.js.map +1 -1
  57. package/dist/resources/derivedIndexes.d.ts +1 -0
  58. package/dist/resources/derivedIndexes.js +116 -15
  59. package/dist/resources/derivedIndexes.js.map +1 -1
  60. package/dist/resources/fullTextSchema.d.ts +5 -3
  61. package/dist/resources/fullTextSchema.js +48 -5
  62. package/dist/resources/fullTextSchema.js.map +1 -1
  63. package/dist/resources/fullTextSchemaLifecycle.d.ts +2 -0
  64. package/dist/resources/fullTextSchemaLifecycle.js +33 -0
  65. package/dist/resources/fullTextSchemaLifecycle.js.map +1 -1
  66. package/dist/resources/graphql.js +30 -12
  67. package/dist/resources/graphql.js.map +1 -1
  68. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +21 -1
  69. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +114 -5
  70. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  71. package/dist/resources/indexes/fullTextDerivedIndex.d.ts +11 -2
  72. package/dist/resources/indexes/fullTextDerivedIndex.js +187 -24
  73. package/dist/resources/indexes/fullTextDerivedIndex.js.map +1 -1
  74. package/dist/resources/indexes/fullTextNativeBinding.d.ts +99 -1
  75. package/dist/resources/indexes/fullTextNativeBinding.js +35 -1
  76. package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -1
  77. package/dist/resources/indexes/fullTextQueryIndex.d.ts +58 -0
  78. package/dist/resources/indexes/fullTextQueryIndex.js +943 -0
  79. package/dist/resources/indexes/fullTextQueryIndex.js.map +1 -0
  80. package/dist/resources/indexes/fullTextQueryProtocol.d.ts +6 -0
  81. package/dist/resources/indexes/fullTextQueryProtocol.js +32 -0
  82. package/dist/resources/indexes/fullTextQueryProtocol.js.map +1 -0
  83. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +4 -1
  84. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +30 -7
  85. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -1
  86. package/dist/resources/search.d.ts +41 -0
  87. package/dist/resources/search.js +468 -26
  88. package/dist/resources/search.js.map +1 -1
  89. package/dist/resources/transactionBroadcast.js +101 -50
  90. package/dist/resources/transactionBroadcast.js.map +1 -1
  91. package/dist/resources/typeParity.d.ts +9 -0
  92. package/dist/resources/typeParity.js +3 -0
  93. package/dist/resources/typeParity.js.map +1 -0
  94. package/dist/server/REST.js.map +1 -1
  95. package/dist/server/itc/serverHandlers.js +48 -8
  96. package/dist/server/itc/serverHandlers.js.map +1 -1
  97. package/dist/server/serverHelpers/contentTypes.d.ts +7 -0
  98. package/dist/server/serverHelpers/contentTypes.js +36 -23
  99. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  100. package/dist/server/serverHelpers/uwsServer.js +28 -5
  101. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  102. package/dist/server/threads/itc.js +19 -0
  103. package/dist/server/threads/itc.js.map +1 -1
  104. package/dist/server/threads/manageThreads.d.ts +3 -0
  105. package/dist/server/threads/manageThreads.js +90 -23
  106. package/dist/server/threads/manageThreads.js.map +1 -1
  107. package/dist/utility/operation_authorization.js +25 -7
  108. package/dist/utility/operation_authorization.js.map +1 -1
  109. package/dist/utility/signalling.js +3 -0
  110. package/dist/utility/signalling.js.map +1 -1
  111. package/dist/validation/searchValidator.js +22 -2
  112. package/dist/validation/searchValidator.js.map +1 -1
  113. package/npm-shrinkwrap.json +22 -22
  114. package/package.json +2 -4
  115. package/resources/DESIGN.md +19 -2
  116. package/resources/DatabaseTransaction.ts +40 -10
  117. package/resources/FULL_TEXT.md +97 -0
  118. package/resources/ResourceInterface.ts +8 -2
  119. package/resources/Table.ts +767 -17
  120. package/resources/analytics/write.ts +3 -3
  121. package/resources/blob.ts +183 -1
  122. package/resources/databases.ts +126 -14
  123. package/resources/derivedIndexRuntime.ts +79 -6
  124. package/resources/derivedIndexes.ts +142 -18
  125. package/resources/fullTextSchema.ts +55 -9
  126. package/resources/fullTextSchemaLifecycle.ts +45 -0
  127. package/resources/graphql.ts +33 -12
  128. package/resources/indexes/DESIGN.md +167 -15
  129. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +125 -5
  130. package/resources/indexes/fullTextDerivedIndex.ts +216 -32
  131. package/resources/indexes/fullTextNativeBinding.ts +133 -3
  132. package/resources/indexes/fullTextQueryIndex.ts +1120 -0
  133. package/resources/indexes/fullTextQueryProtocol.ts +35 -0
  134. package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +28 -8
  135. package/resources/search.ts +513 -26
  136. package/resources/transactionBroadcast.ts +86 -47
  137. package/resources/typeParity.ts +20 -0
  138. package/schema.graphql +18 -5
  139. package/server/DESIGN.md +18 -0
  140. package/server/REST.ts +1 -1
  141. package/server/itc/serverHandlers.js +61 -8
  142. package/server/serverHelpers/contentTypes.ts +31 -26
  143. package/server/serverHelpers/uwsServer.ts +35 -7
  144. package/server/threads/itc.js +21 -0
  145. package/server/threads/manageThreads.js +95 -21
  146. package/studio/web/assets/{Chat-Bq60AO-g.js → Chat-CNEKMllJ.js} +13 -13
  147. package/studio/web/assets/FloatingChat-CEnWhHeR.js +23 -0
  148. package/studio/web/assets/SearchDialog-CwhJdEdY.js +1 -0
  149. package/studio/web/assets/{abnfDiagram-O67JEVCF-DpPZWMOf.js → abnfDiagram-O67JEVCF-raXE027J.js} +1 -1
  150. package/studio/web/assets/alertDialog-BbWD9Xxa.js +1 -0
  151. package/studio/web/assets/apiToken-DC5vsaaO.js +1 -0
  152. package/studio/web/assets/applications-DQZxDy0x.js +296 -0
  153. package/studio/web/assets/architecture-7GRP2DOG-DDmz3hpI.js +1 -0
  154. package/studio/web/assets/{architectureDiagram-NJMV4G6O-OmgLraID.js → architectureDiagram-NJMV4G6O-C8_Eet7w.js} +1 -1
  155. package/studio/web/assets/arrow-up-D2g_57vT.js +1 -0
  156. package/studio/web/assets/authStore-CJCaADw9.js +3 -0
  157. package/studio/web/assets/{blockDiagram-BEXU5L5S-Cv7CD3Ai.js → blockDiagram-BEXU5L5S-DR6S5-1D.js} +1 -1
  158. package/studio/web/assets/{button-MFP9n8Ck.js → button-DWwfgGWI.js} +2 -2
  159. package/studio/web/assets/{c4Diagram-YGBWAQC7-Ds1vKYnN.js → c4Diagram-YGBWAQC7-BAfZ1G01.js} +1 -1
  160. package/studio/web/assets/channel-D0AkPjJ8.js +1 -0
  161. package/studio/web/assets/{chevron-up-CFe8x5Bp.js → chevron-up-CFxbMGv2.js} +1 -1
  162. package/studio/web/assets/{chunk-3FUC2YCW-DpnBPH8B.js → chunk-3FUC2YCW-DuRp-j19.js} +1 -1
  163. package/studio/web/assets/{chunk-5DYCD2WN-LLLCQSzg.js → chunk-5DYCD2WN-BceBfIHT.js} +1 -1
  164. package/studio/web/assets/{chunk-742MDFTN-BL8J_pIO.js → chunk-742MDFTN-CIkt8ijt.js} +1 -1
  165. package/studio/web/assets/{chunk-7INBJB4K-C-hJHiiK.js → chunk-7INBJB4K-BpeAXyOi.js} +1 -1
  166. package/studio/web/assets/{chunk-7M6MHVWA-C6ZqkFbA.js → chunk-7M6MHVWA-h6WlaTzO.js} +1 -1
  167. package/studio/web/assets/{chunk-7PRAP22T-CUXM8BG6.js → chunk-7PRAP22T-KyqX6x3f.js} +1 -1
  168. package/studio/web/assets/{chunk-GTNCS2PH-DJ2BFvS1.js → chunk-GTNCS2PH-DGHLXyow.js} +1 -1
  169. package/studio/web/assets/{chunk-GWA4HPMP-C42bSqLL.js → chunk-GWA4HPMP-DsejB6gA.js} +1 -1
  170. package/studio/web/assets/{chunk-MBY4JIJT-C-5jz6Lo.js → chunk-MBY4JIJT-BwSGweQP.js} +1 -1
  171. package/studio/web/assets/{chunk-NETBCI7D-D-bzlN4r.js → chunk-NETBCI7D-C8UnOw_f.js} +1 -1
  172. package/studio/web/assets/{chunk-O7XYJQB3-ep14zA23.js → chunk-O7XYJQB3-CBS3nZYV.js} +1 -1
  173. package/studio/web/assets/{chunk-UA2S7LBM-BMRWRHED.js → chunk-UA2S7LBM-BHpEdH8D.js} +1 -1
  174. package/studio/web/assets/{chunk-WEXAMYUT-DdLYVdX1.js → chunk-WEXAMYUT-BAIhTwyh.js} +1 -1
  175. package/studio/web/assets/{chunk-Z7XXMR3K-BmKEDjcP.js → chunk-Z7XXMR3K-DSHbImdf.js} +1 -1
  176. package/studio/web/assets/{chunk-ZIGJFQKS-DLZgdNxA.js → chunk-ZIGJFQKS-Cs8fUn7y.js} +1 -1
  177. package/studio/web/assets/{classDiagram-v2-NBCMYWYE-DOI5vgKT.js → classDiagram-v2-NBCMYWYE-DmDPkt2u.js} +1 -1
  178. package/studio/web/assets/cn-6T8VranJ.js +1 -0
  179. package/studio/web/assets/{codicon-Brq4_Ui5.ttf → codicon-DF1abBS2.ttf} +0 -0
  180. package/studio/web/assets/{createLucideIcon-bziYxJli.js → createLucideIcon-fO5dL7D5.js} +1 -1
  181. package/studio/web/assets/{css.worker-l1psR_wG.js → css.worker-DoT-OOzx.js} +36 -36
  182. package/studio/web/assets/{cssMode-DJyc9mHj.js → cssMode-Dz2DxF4e.js} +1 -1
  183. package/studio/web/assets/{cynefin-OW5HDTMX-BSW-DB7S.js → cynefin-OW5HDTMX-DIvbXLSj.js} +1 -1
  184. package/studio/web/assets/{cynefinDiagram-VND7K2PF-CbAHEwxt.js → cynefinDiagram-VND7K2PF-BdtaXiCE.js} +1 -1
  185. package/studio/web/assets/{dagre-6A5THRUB-Csxewgs4.js → dagre-6A5THRUB-C7RUWHyz.js} +1 -1
  186. package/studio/web/assets/{diagram-22UHCM2B-a0rt8tY4.js → diagram-22UHCM2B-DJCvcaWP.js} +1 -1
  187. package/studio/web/assets/{diagram-3UASUU5V-lEj2SVNh.js → diagram-3UASUU5V-Du6VKWln.js} +1 -1
  188. package/studio/web/assets/{diagram-ATOU4E4O-HcrAeB-q.js → diagram-ATOU4E4O-dxRfeG4B.js} +1 -1
  189. package/studio/web/assets/{diagram-CDSNMT55-CYxiuyEx.js → diagram-CDSNMT55-l5Ga0u-W.js} +1 -1
  190. package/studio/web/assets/{diagram-MLGK6HIB-ChbCJTgJ.js → diagram-MLGK6HIB-CNQY47LA.js} +1 -1
  191. package/studio/web/assets/{diagram-MPIPVDR6-DaX9Y-N3.js → diagram-MPIPVDR6-7prveD1B.js} +1 -1
  192. package/studio/web/assets/{dist-Cz2Y6-6G.js → dist-BrRuMUzt.js} +1 -1
  193. package/studio/web/assets/{download-CtostjYn.js → download-Cn6tPG4L.js} +1 -1
  194. package/studio/web/assets/{ebnfDiagram-ZINNZB2B-CwAsENB1.js → ebnfDiagram-ZINNZB2B-5OnkTpfi.js} +1 -1
  195. package/studio/web/assets/{editor-DDIsQfj6.js → editor-B3mjd3tz.js} +1 -1
  196. package/studio/web/assets/editor.worker-Yd1NR0QT.js +33 -0
  197. package/studio/web/assets/{elk-276RUBZZ-Hexg9E4D.js → elk-276RUBZZ-DxDQPjQT.js} +1 -1
  198. package/studio/web/assets/{erDiagram-OPXOYQCR-CeC-wwO1.js → erDiagram-OPXOYQCR-CQlCX84D.js} +1 -1
  199. package/studio/web/assets/eventmodeling-NTZA5JFV-DONQf5aG.js +1 -0
  200. package/studio/web/assets/flowDiagram-KWPJA3E3-BoZ8_QR1.js +1 -0
  201. package/studio/web/assets/{ganttDiagram-FUAMR5RP-C2_Hhl1-.js → ganttDiagram-FUAMR5RP-bloX3Voy.js} +1 -1
  202. package/studio/web/assets/{getAnalytics-CjthMQqF.js → getAnalytics-C0FHjtdQ.js} +1 -1
  203. package/studio/web/assets/{gitGraph-4MIJSDKK-xIk5Vuew.js → gitGraph-4MIJSDKK-BrXWmJt2.js} +1 -1
  204. package/studio/web/assets/{gitGraphDiagram-X574FWY7-CMxJ34up.js → gitGraphDiagram-X574FWY7-C2KnnZeY.js} +1 -1
  205. package/studio/web/assets/{html-g1BJHxLy.js → html-BsRGM7tp.js} +1 -1
  206. package/studio/web/assets/{html.worker-DdwM4IrA.js → html.worker-B1dwY0Ay.js} +27 -27
  207. package/studio/web/assets/{htmlMode-Vh5IglCQ.js → htmlMode-tUKJaA3o.js} +1 -1
  208. package/studio/web/assets/index-72cEH474.css +1 -0
  209. package/studio/web/assets/index-BuqFjN5K.js +828 -0
  210. package/studio/web/assets/index.lazy-CcpfQFY_.js +2 -0
  211. package/studio/web/assets/{info-A6RAGUB7-o49TEbj-.js → info-A6RAGUB7-BhzBHe4U.js} +1 -1
  212. package/studio/web/assets/{infoDiagram-VRGFBTTK-Cjhh7yzZ.js → infoDiagram-VRGFBTTK-B0J1132p.js} +1 -1
  213. package/studio/web/assets/{ishikawaDiagram-OU5B5YK6-C63aXRmh.js → ishikawaDiagram-OU5B5YK6-BNk0tuQx.js} +1 -1
  214. package/studio/web/assets/{javascript-5VIolqf7.js → javascript-BZ_uRGHl.js} +1 -1
  215. package/studio/web/assets/{journeyDiagram-ZHPQQLJL-D_o0P5Mu.js → journeyDiagram-ZHPQQLJL-Be79RMQM.js} +1 -1
  216. package/studio/web/assets/json.worker-BQqXGI3X.js +65 -0
  217. package/studio/web/assets/{jsonMode-DG4zQraI.js → jsonMode-DgB-aFMj.js} +1 -1
  218. package/studio/web/assets/{kanban-definition-PNTS6WVX-CTI9SSxY.js → kanban-definition-PNTS6WVX-4uuoQP9W.js} +1 -1
  219. package/studio/web/assets/{languageServices-t5FBpX-i.js → languageServices-BleuBw17.js} +1 -1
  220. package/studio/web/assets/localStorageKeys-etzFY_xV.js +1 -0
  221. package/studio/web/assets/{lspLanguageFeatures-DgVnNPh-.js → lspLanguageFeatures-ByVYLSsQ.js} +1 -1
  222. package/studio/web/assets/{mermaid-parser.core-C2lcH_jP.js → mermaid-parser.core-V1UYMeXV.js} +3 -3
  223. package/studio/web/assets/{mermaid.core-BG5bjfIG.js → mermaid.core-1PX6R49U.js} +4 -4
  224. package/studio/web/assets/{mindmap-definition-NLK3R4M7-BK7ho6JD.js → mindmap-definition-NLK3R4M7-C70Ctm0J.js} +1 -1
  225. package/studio/web/assets/notifications-D1Nf-Edn.js +1 -0
  226. package/studio/web/assets/notifications-TuzN0IMi.js +1 -0
  227. package/studio/web/assets/{packet-AYTQ26CC-BHPy6KBV.js → packet-AYTQ26CC-DWbXHx5g.js} +1 -1
  228. package/studio/web/assets/{pegDiagram-GJSIUBJH-DzjwG3jO.js → pegDiagram-GJSIUBJH-d6z1cc7W.js} +1 -1
  229. package/studio/web/assets/{pie-WAS4IAKB-D0cP-DTU.js → pie-WAS4IAKB-DjoTm3yr.js} +1 -1
  230. package/studio/web/assets/{pieDiagram-5QR66LMP-C_hOP9Lv.js → pieDiagram-5QR66LMP-Dz3RHpDA.js} +1 -1
  231. package/studio/web/assets/profile-D7C7Lupz.js +1 -0
  232. package/studio/web/assets/{quadrantDiagram-O4NWA36T-CZW8PbV1.js → quadrantDiagram-O4NWA36T-CIF5Qkou.js} +1 -1
  233. package/studio/web/assets/{radar-RG4KPBEZ-C8fk0U81.js → radar-RG4KPBEZ-CVBgjCOj.js} +1 -1
  234. package/studio/web/assets/{railroad-74A4TZTK-B60FWm17.js → railroad-74A4TZTK-BdhXw10Q.js} +1 -1
  235. package/studio/web/assets/railroad-abnf-HS5TGJTU-BcoQPN39.js +1 -0
  236. package/studio/web/assets/railroad-ebnf-LZEXJU2U-B1Lke2JN.js +1 -0
  237. package/studio/web/assets/railroad-peg-WCYAUIDC-D-oJFkiV.js +1 -0
  238. package/studio/web/assets/{railroadDiagram-XR7U4H2S-DyyNmgbU.js → railroadDiagram-XR7U4H2S-y3Jv2U6N.js} +1 -1
  239. package/studio/web/assets/regions-nGQCm-v8.js +1 -0
  240. package/studio/web/assets/register-C3x9Ze6P.js +909 -0
  241. package/studio/web/assets/register-DjpsE8to.css +1 -0
  242. package/studio/web/assets/{requirementDiagram-PLB6GJNP-C4H-mLKu.js → requirementDiagram-PLB6GJNP-ChINZEmu.js} +1 -1
  243. package/studio/web/assets/restartGate-nHAFLDh-.js +1 -0
  244. package/studio/web/assets/{sankeyDiagram-IPEJSGJF-DHDyga4c.js → sankeyDiagram-IPEJSGJF-D9AyLce2.js} +1 -1
  245. package/studio/web/assets/{sequenceDiagram-PO4LG4MO-CdNcHXRI.js → sequenceDiagram-PO4LG4MO-hfk4aIEm.js} +1 -1
  246. package/studio/web/assets/{setComponentFile-z4-qJCbA.js → setComponentFile-DpjrRqeG.js} +1 -1
  247. package/studio/web/assets/{setup-CS50uJ4x.js → setup-CGiKLZ9e.js} +2 -2
  248. package/studio/web/assets/{stateDiagram-v2-GCMORJYK-CpKuXq15.js → stateDiagram-v2-GCMORJYK-Bs8RIb9K.js} +1 -1
  249. package/studio/web/assets/status-CGJwrNPH.js +61 -0
  250. package/studio/web/assets/{swimlanes-2SLR337P-NRHa2D8I.js → swimlanes-2SLR337P-B52xrf6t.js} +1 -1
  251. package/studio/web/assets/swimlanesDiagram-TC7HE7FX-DfN7BAP5.js +8 -0
  252. package/studio/web/assets/tabs-BCTQzoQK.js +1 -0
  253. package/studio/web/assets/{timeline-definition-EJHVYXUP-D39Y-xpq.js → timeline-definition-EJHVYXUP-DJSITpQX.js} +1 -1
  254. package/studio/web/assets/toggleHighContrast-C-Ne6P7T.js +51 -0
  255. package/studio/web/assets/toggleHighContrast-n7clI0si.css +1 -0
  256. package/studio/web/assets/{treeView-Q6P3EWNA-CRWR9OqD.js → treeView-Q6P3EWNA-C-epdxRo.js} +1 -1
  257. package/studio/web/assets/{treemap-WGGIJYW6-ZfXyJgLO.js → treemap-WGGIJYW6-DOVyp_zw.js} +1 -1
  258. package/studio/web/assets/{ts.worker-DDMUrpqh.js → ts.worker-Clm12uY6.js} +131 -131
  259. package/studio/web/assets/{tsMode-D83Gg3cq.js → tsMode-CSreYTys.js} +1 -1
  260. package/studio/web/assets/{typescript-Dfp-GrNq.js → typescript-MnxBk89W.js} +1 -1
  261. package/studio/web/assets/{useEntityRestURL-BKetlINf.js → useEntityRestURL-TNrh77-s.js} +1 -1
  262. package/studio/web/assets/{usecaseDiagram-POWQR4AR-jf-zIDZ_.js → usecaseDiagram-POWQR4AR-B6252cbq.js} +1 -1
  263. package/studio/web/assets/{vendor-core-BdkdbUek.js → vendor-core-6LhfJlRC.js} +1 -1
  264. package/studio/web/assets/vendor-datadog-ozKf-pts.js +6 -0
  265. package/studio/web/assets/{vendor-react-izWchTRL.js → vendor-react-BOliS0BL.js} +1 -1
  266. package/studio/web/assets/vendor-tanstack-Bntfka-M.js +2 -0
  267. package/studio/web/assets/vendor-ui-9EYYDgoN.js +41 -0
  268. package/studio/web/assets/{vennDiagram-UO4OBE2U-BKYAZUh_.js → vennDiagram-UO4OBE2U-DZTsDA4k.js} +1 -1
  269. package/studio/web/assets/{wardley-WFR3VGLG-BO7f36ci.js → wardley-WFR3VGLG-Cfqoe6z4.js} +1 -1
  270. package/studio/web/assets/{wardleyDiagram-VNRHLVJA-Djb0JDQc.js → wardleyDiagram-VNRHLVJA-qu_f7Tvl.js} +1 -1
  271. package/studio/web/assets/{workers-CCvmDLH3.js → workers-BfZlUNVI.js} +3 -3
  272. package/studio/web/assets/{xml-06pZp00U.js → xml-BHd6qGH7.js} +1 -1
  273. package/studio/web/assets/{xychartDiagram-PMCCYNJV-BQh9ubvb.js → xychartDiagram-PMCCYNJV-UrOKRsmR.js} +1 -1
  274. package/studio/web/assets/{yaml-iSnWMVlV.js → yaml-C53R8OQq.js} +1 -1
  275. package/studio/web/assets/{yaml.worker-DAK2o_H1.js → yaml.worker-D9JekGdp.js} +183 -183
  276. package/studio/web/index.html +15 -15
  277. package/utility/operation_authorization.ts +27 -7
  278. package/utility/signalling.ts +3 -0
  279. package/validation/searchValidator.ts +22 -13
  280. package/studio/web/assets/FloatingChat-D4560T0p.js +0 -23
  281. package/studio/web/assets/alertDialog-UrbiMgQt.js +0 -1
  282. package/studio/web/assets/apiToken-BEvbFPMc.js +0 -1
  283. package/studio/web/assets/applications-CSUVTH4E.js +0 -296
  284. package/studio/web/assets/architecture-7GRP2DOG-11l4Ny1l.js +0 -1
  285. package/studio/web/assets/authStore-5jSS5pTW.js +0 -3
  286. package/studio/web/assets/channel-BDtouPd6.js +0 -1
  287. package/studio/web/assets/dialog-DTepachC.js +0 -1
  288. package/studio/web/assets/editor.worker-CkkvdXvO.js +0 -33
  289. package/studio/web/assets/eventmodeling-NTZA5JFV-CFb03e0o.js +0 -1
  290. package/studio/web/assets/flowDiagram-KWPJA3E3-Cidi00_Y.js +0 -1
  291. package/studio/web/assets/index-CPoDzQTe.css +0 -1
  292. package/studio/web/assets/index-DGRAzvO4.js +0 -824
  293. package/studio/web/assets/index.lazy-vgSic7et.js +0 -2
  294. package/studio/web/assets/json.worker-CSi2hF-a.js +0 -65
  295. package/studio/web/assets/notifications-BeWcX04m.js +0 -1
  296. package/studio/web/assets/notifications-DUGWrWoO.js +0 -1
  297. package/studio/web/assets/profile-BvUEDBzW.js +0 -1
  298. package/studio/web/assets/railroad-abnf-HS5TGJTU-CLMVZvH0.js +0 -1
  299. package/studio/web/assets/railroad-ebnf-LZEXJU2U--tlwFoVB.js +0 -1
  300. package/studio/web/assets/railroad-peg-WCYAUIDC-Dpuslblm.js +0 -1
  301. package/studio/web/assets/regions-CB-_2m7M.js +0 -1
  302. package/studio/web/assets/register-1w1Zr3b2.js +0 -848
  303. package/studio/web/assets/register-jjEx9u7D.css +0 -1
  304. package/studio/web/assets/setLocalStorage-D_kflv4U.js +0 -1
  305. package/studio/web/assets/status-CI0wOdOE.js +0 -61
  306. package/studio/web/assets/swimlanesDiagram-TC7HE7FX-BBk4t4hO.js +0 -8
  307. package/studio/web/assets/tabs-CnQ_zenf.js +0 -1
  308. package/studio/web/assets/toggleHighContrast--ixms01u.css +0 -1
  309. package/studio/web/assets/toggleHighContrast-BsAE4jPe.js +0 -49
  310. package/studio/web/assets/useLocalStorage-BYoZF60G.js +0 -1
  311. package/studio/web/assets/vendor-datadog-fCQhBHGk.js +0 -6
  312. package/studio/web/assets/vendor-tanstack-CQ1aRjVa.js +0 -2
  313. package/studio/web/assets/vendor-ui-aE1tHcW7.js +0 -41
  314. package/studio/web/assets/x-vYntCxm4.js +0 -1
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { CONFIG_PARAMS, OPERATIONS_ENUM, MAX_SET_TIMEOUT_MS } from '../utility/hdbTerms.ts';
8
- import { type Database } from 'lmdb';
8
+ import { type Database, type Transaction as LMDBReadTransaction } from 'lmdb';
9
9
  import { Script } from 'node:vm';
10
10
  import { randomUUID } from 'node:crypto';
11
11
  import { performance } from 'node:perf_hooks';
@@ -22,8 +22,13 @@ import type {
22
22
  Sort,
23
23
  SubSelect,
24
24
  RequestTargetOrId,
25
+ Query,
26
+ SourceContext,
25
27
  } from './ResourceInterface.ts';
26
28
  import type { User } from '../security/user.ts';
29
+ import type { AssertNoDrift, AssertTrue, ExactlyEqual, MemberDrift, ParitySentinel } from './typeParity.ts';
30
+ import type { IterableEventQueue } from './IterableEventQueue.ts';
31
+ import type { Contract, SchemaClass } from './defineResource.ts';
27
32
  import lmdbProcessRows from '../dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbProcessRows.js';
28
33
  import { Resource, SEARCH_AUTHORIZATION, transformForSelect } from './Resource.ts';
29
34
  import { settleBeforeDeadline, when, promiseNormalize } from '../utility/when.ts';
@@ -40,6 +45,7 @@ import {
40
45
  commitTrackedRocksTransaction,
41
46
  getDatabaseCommitDrainTimeoutMilliseconds,
42
47
  type WriteGeneration,
48
+ type Transaction as DatabaseTransactionRecord,
43
49
  } from './DatabaseTransaction.ts';
44
50
  import {
45
51
  acquireRecordKey,
@@ -56,6 +62,7 @@ import { addSubscription } from './transactionBroadcast.ts';
56
62
  import { databaseDropPrepared } from './databaseDropPreparation.ts';
57
63
  import {
58
64
  DerivedIndexLagError,
65
+ IndexRebuildingError,
59
66
  DatabaseClosingError,
60
67
  DatabaseDrainTimeoutError,
61
68
  handleHDBError,
@@ -91,6 +98,7 @@ import {
91
98
  COERCIBLE_OPERATORS,
92
99
  executeConditions,
93
100
  resolveComparator,
101
+ fullTextComparatorMode,
94
102
  } from './search.ts';
95
103
  import { logger } from '../utility/logging/logger.ts';
96
104
  import { isStaticResourceInstance } from './staticResourceDispatch.ts';
@@ -161,6 +169,7 @@ import {
161
169
  recordUpdater,
162
170
  removeEntry,
163
171
  PENDING_LOCAL_TIME,
172
+ VERSION_REUSED,
164
173
  RecordObject,
165
174
  type Entry,
166
175
  type StructureCounts,
@@ -177,6 +186,7 @@ import {
177
186
  removeStorageReclamation,
178
187
  removeStorageReclamationHandler,
179
188
  getStorageSpaceStats,
189
+ type StorageSpaceStats,
180
190
  } from '../server/storageReclamation.ts';
181
191
  import { RequestTarget } from './RequestTarget.ts';
182
192
  import harperLogger from '../utility/logging/harper_logger.ts';
@@ -541,6 +551,7 @@ const MAX_OUT_OF_ORDER_AUDIT_DEPTH = 1000;
541
551
  const MAX_PREVIOUS_COUNT_SCAN = 10_000;
542
552
  const AUTHORIZATION_SELECT = Symbol.for('harper.authorizationSelect');
543
553
  const SEARCH_AUTHORIZATION_TRANSFORMS = Symbol.for('harper.searchAuthorizationTransforms');
554
+ const FULL_TEXT_READ_PERMISSION = Symbol('fullTextReadPermission');
544
555
  const AUTHORIZATION_TRANSFORM_METHODS = ['map', 'filter', 'concat', 'flatMap', 'slice', 'mapError'];
545
556
 
546
557
  function propagateSearchAuthorization(iterable: any, authorization: Promise<any>, source?: any) {
@@ -593,6 +604,515 @@ export interface Table {
593
604
  }
594
605
  type ResidencyDefinition = number | string[] | void;
595
606
 
607
+ interface TableResourceInstance<Record extends object = any> {
608
+ getProperty: (name: string) => any;
609
+ _loadRecord(
610
+ target: RequestTarget,
611
+ request: Context,
612
+ resourceOptions?: any
613
+ ): MaybePromise<TableResourceInstance<Record>>;
614
+ /**
615
+ * This is a request to explicitly ensure that the record is loaded from source, rather than only using the local record.
616
+ * This will load from source if the current record is expired, missing, or invalidated.
617
+ */
618
+ ensureLoaded(): void | Promise<void>;
619
+ /**
620
+ * This retrieves the data of this resource.
621
+ * @param target - If included, is an identifier/query that specifies the requested target to retrieve and query
622
+ */
623
+ get(target?: any): any;
624
+ /**
625
+ * Determine if the user is allowed to get/read data from the current resource
626
+ * @deprecated Override the resource operation for application-specific authorization.
627
+ */
628
+ allowRead(user: User, target: RequestTarget, context: Context): boolean;
629
+ /**
630
+ * Determine if the user is allowed to update data from the current resource
631
+ * @deprecated Override the resource operation for application-specific authorization.
632
+ */
633
+ allowUpdate(user: User, updatedData: Record, context: Context): boolean;
634
+ /**
635
+ * Determine if the user is allowed to create new data in the current resource
636
+ * @deprecated Override the resource operation for application-specific authorization.
637
+ */
638
+ allowCreate(user: User, newData: Record, context: Context): boolean;
639
+ /**
640
+ * Determine if the user is allowed to delete from the current resource
641
+ * @deprecated Override the resource operation for application-specific authorization.
642
+ */
643
+ allowDelete(user: User, target: RequestTarget, context: Context): boolean;
644
+ /**
645
+ * Start updating a record. The returned resource will record changes which are written
646
+ * once the corresponding transaction is committed. These changes can (eventually) include CRDT type operations.
647
+ */
648
+ update(updates: Record & RecordObject, fullUpdate: true): any;
649
+ update(updates: Partial<Record & RecordObject>, target?: RequestTarget): any;
650
+ update(target: RequestTarget, updates?: any): any;
651
+ /**
652
+ * Save any changes into this instance to the current transaction
653
+ */
654
+ save(): any;
655
+ addTo(property: any, value: any): void;
656
+ subtractFrom(property: any, value: any): void;
657
+ getMetadata(): Entry;
658
+ getRecord(): any;
659
+ getChanges(): any;
660
+ _setChanges(changes: any): void;
661
+ setRecord(record: any): void;
662
+ invalidate(target: RequestTargetOrId): void | Promise<void>;
663
+ _writeInvalidate(id: Id, partialRecord?: any, options?: any): void;
664
+ _writeRelocate(id: Id, options: any): void;
665
+ /**
666
+ * Acquire an exclusive lock on this record (or on `target`'s) and return it ready for updates
667
+ * (harper#483, Phase 0: exclusive across every worker thread of this node). The lock is held
668
+ * in process memory only — no durable writes. Phase 0 contract: lock() is mutually exclusive
669
+ * with other lock() calls on the same key; plain writes (put/patch/delete/create) are never
670
+ * gated or blocked. The generation expires after `lease` if it is never released.
671
+ *
672
+ * Transaction-scoped (default): write through the returned record (or the table's static verbs
673
+ * in the same transaction), and the commit or abort releases it. `{ hold: true }`: the lock
674
+ * outlives the transaction; write through the returned record and release with `unlock()`, or
675
+ * let the lease expire.
676
+ */
677
+ lock(target?: RequestTargetOrId | RecordLockOptions, options?: RecordLockOptions): Promise<any>;
678
+ /**
679
+ * Release the lock this instance holds. Resolves true when this call cleared the native key lock.
680
+ * Works for both held (`{ hold: true }`) and transaction-scoped locks. After unlock() the
681
+ * instance is no longer lock-writable; writes through it require a fresh lock.
682
+ */
683
+ unlock(): Promise<boolean>;
684
+ /**
685
+ * Store the provided record data into the current resource. This is not written
686
+ * until the corresponding transaction is committed.
687
+ */
688
+ put(
689
+ target: RequestTarget,
690
+ record: Record & RecordObject
691
+ ): void | (Record & Partial<RecordObject>) | Promise<void | (Record & Partial<RecordObject>)>;
692
+ create(
693
+ target: RequestTargetOrId,
694
+ record: Partial<Record & RecordObject>
695
+ ): void | (Record & Partial<RecordObject>) | Promise<Record & Partial<RecordObject>>;
696
+ patch(
697
+ target: RequestTarget,
698
+ recordUpdate: Partial<Record & RecordObject>
699
+ ): void | (Record & Partial<RecordObject>) | Promise<void | (Record & Partial<RecordObject>)>;
700
+ _writeUpdate(id: Id, recordUpdate: any, fullUpdate: boolean, options?: any): any;
701
+ delete(target: RequestTargetOrId): Promise<boolean>;
702
+ _writeDelete(id: Id, options?: any): boolean;
703
+ search(target: RequestTarget): AsyncIterable<Record & Partial<RecordObject>>;
704
+ subscribe(request: SubscriptionRequest): Promise<AsyncIterable<Record>>;
705
+ doesExist(): boolean;
706
+ /**
707
+ * Publishing a message to a record adds an (observable) entry in the audit log, but does not change
708
+ * the record at all. This entries should be replicated and trigger subscription listeners.
709
+ */
710
+ publish(target: RequestTarget, message: Record, options?: any): void | Promise<void>;
711
+ _writePublish(id: Id, message: any, options?: any): void;
712
+ validate(record: any, patch?: boolean): void;
713
+ getUpdatedTime(): number;
714
+ [ASSERT_TRACKED_WRITABLE](generation?: WriteGeneration): void;
715
+ [GET_TRACKED_WRITE_GENERATION](): WriteGeneration;
716
+ post(target: RequestTargetOrId, newRecord: Partial<Record & RecordObject>): Promise<Record & Partial<RecordObject>>;
717
+ get isCollection(): boolean;
718
+ connect(
719
+ target: RequestTarget,
720
+ incomingMessages: IterableEventQueue<Record>
721
+ ): AsyncIterable<Record> | Promise<AsyncIterable<Record>>;
722
+ getId(): Id;
723
+ getContext(): Context | SourceContext;
724
+ getCurrentUser(): User | undefined;
725
+ }
726
+
727
+ interface TableResourceClass {
728
+ new <Record extends object = any>(identifier: Id, source: any): TableResourceInstance<Record>;
729
+ prototype: TableResourceInstance;
730
+ name: any;
731
+ primaryStore: any;
732
+ storageGeneration: any;
733
+ auditStore: any;
734
+ primaryKey: any;
735
+ tableName: any;
736
+ tableId: any;
737
+ indices: any;
738
+ derivedIndexRuntime:
739
+ | {
740
+ close(dropping?: boolean): Promise<void>;
741
+ fullTextDefinitions?(): readonly FullTextDefinition[];
742
+ matchesCurrent?(): boolean;
743
+ restoreAfterFailedDrop?(): TableResourceClass['derivedIndexRuntime'];
744
+ retireAfterConfirmedDrop?(definitions?: readonly Pick<FullTextDefinition, 'name'>[]): Promise<boolean>;
745
+ completeDrop?(dropped?: boolean): void;
746
+ }
747
+ | undefined;
748
+ audit: any;
749
+ fullTextIndexes: FullTextDefinition[];
750
+ get fullTextFields(): readonly string[];
751
+ set fullTextFields(names: readonly string[]);
752
+ assertFullTextSelection(select: unknown, sort?: any): void;
753
+ assertFullTextRecordField(name: unknown): void;
754
+ isFullTextSearchEntryCurrent(entry: Entry): boolean;
755
+ fullTextQueryIndexes: {
756
+ [name: string]: {
757
+ customIndex: unknown;
758
+ };
759
+ };
760
+ hasFullTextQueryIndexes: boolean;
761
+ fullTextIndexGenerations: FullTextIndexGenerations;
762
+ fullTextIndexRetirements: string[];
763
+ hasCurrentFullTextIndexRetirements(names: readonly string[]): boolean | Promise<boolean>;
764
+ completeFullTextIndexRetirements(names: readonly string[]): void | Promise<void>;
765
+ databasePath: any;
766
+ databaseName: any;
767
+ attributes: Attribute[];
768
+ description: any;
769
+ properties: Record<string, JsonSchemaFragment>;
770
+ hidden: any;
771
+ cacheControl: any;
772
+ outputSchemas:
773
+ | {
774
+ [verb: string]: JsonSchemaFragment;
775
+ }
776
+ | undefined;
777
+ mcp:
778
+ | {
779
+ annotations?: {
780
+ [verb: string]: any;
781
+ };
782
+ }
783
+ | undefined;
784
+ replicate: any;
785
+ sealed: any;
786
+ splitSegments: any;
787
+ createdTimeProperty: Attribute;
788
+ updatedTimeProperty: Attribute;
789
+ propertyResolvers: any;
790
+ enumerableRelationDefs: any;
791
+ userResolvers: {};
792
+ userEmbedders: {
793
+ [name: string]: Embedder;
794
+ };
795
+ userSetEmbedders: Set<string>;
796
+ embedAttributes: EmbedAttribute[];
797
+ userDeciders: {
798
+ [name: string]: Decider;
799
+ };
800
+ userSetDeciders: Set<string>;
801
+ decideAttributes: DecideAttribute[];
802
+ source?: any;
803
+ sourceOptions: any;
804
+ intermediateSource: boolean;
805
+ getResidencyById: (id: Id) => number | void;
806
+ get expirationMS(): any;
807
+ get evictionMS(): any;
808
+ dbisDB: any;
809
+ schemaDefined: any;
810
+ /**
811
+ * This defines a source for a table. This effectively makes a table into a cache, where the canonical
812
+ * source of data (or source of truth) is provided here in the Resource argument. Additional options
813
+ * can be provided to indicate how the caching should be handled.
814
+ */
815
+ sourcedFrom(source: any, options: any): any;
816
+ get isCaching(): any;
817
+ /** Indicates if the events should be revalidated when they are received. By default we do this if the get
818
+ * method is overriden */
819
+ get shouldRevalidateEvents(): boolean;
820
+ /**
821
+ * Gets a resource instance, as defined by the Resource class, adding the table-specific handling
822
+ * of also loading the stored record into the resource instance.
823
+ * @param resourceOptions An important option is ensureLoaded, which can be used to indicate that it is necessary for a caching table to load data from the source if there is not a local copy of the data in the table (usually not necessary for a delete, for example).
824
+ */
825
+ getResource<Record extends object = any>(
826
+ target: RequestTarget,
827
+ request: Context,
828
+ resourceOptions?: any
829
+ ): Promise<TableResourceInstance<Record>> | TableResourceInstance<Record>;
830
+ _updateResource(resource: any, entry: any): void;
831
+ getNewId(): any;
832
+ /**
833
+ * Set TTL expiration for records in this table. On retrieval, record timestamps are checked for expiration.
834
+ * This also informs the scheduling for record eviction.
835
+ * @param opts Time in seconds until records expire, or an options object with `expiration`, `eviction`,
836
+ * and `scanInterval` (all in seconds, all optional). Number form preserves any previously configured
837
+ * eviction/scanInterval; object form replaces all three. An internal schema ownership-only call with
838
+ * none of those values preserves the settings already loaded from the catalog.
839
+ */
840
+ setTTLExpiration(
841
+ opts:
842
+ | number
843
+ | {
844
+ expiration?: number;
845
+ eviction?: number;
846
+ scanInterval?: number;
847
+ fromSchema?: boolean;
848
+ isolatedApplicationOwner?: boolean;
849
+ }
850
+ ): void;
851
+ getResidencyRecord(id: Id): any;
852
+ setResidency(getResidency?: (record: object, context: Context) => ResidencyDefinition): void;
853
+ setResidencyById(getResidencyById?: (id: Id) => number | void): void;
854
+ getResidency(record: object, context: Context): number | void | string[];
855
+ /**
856
+ * Turn on auditing at runtime
857
+ */
858
+ enableAuditing(): void;
859
+ /**
860
+ * Coerce the id as a string to the correct type for the primary key
861
+ */
862
+ coerceId(id: string): number | string;
863
+ /**
864
+ * A branch's Table classes deliberately carry the BASE's logical database name so an
865
+ * application's schema and code resolve unchanged (harper#643). That makes every schema
866
+ * mutation resolve against the global catalog — a `dropTable()` through a branch would delete
867
+ * the live base table. Reads and writes are per-branch and unaffected; DDL is refused until a
868
+ * branch owns a schema identity of its own.
869
+ */
870
+ assertSchemaMutable(operation: string): void;
871
+ dropTable(): Promise<void>;
872
+ /**
873
+ * Record the relocation of an entry (when a record is moved to a different node), return true if it is now located locally
874
+ */
875
+ _recordRelocate(existingEntry: any, entry: any): boolean;
876
+ /**
877
+ * Evicting a record will remove it from a caching table. This is not considered a canonical data change, and it is assumed that retrieving this record from the source will still yield the same record, this is only removing the local copy of the record.
878
+ */
879
+ evict(id: any, existingRecord: any, existingVersion: any): Promise<unknown>;
880
+ /**
881
+ * Static entry point: `Table.lock(id, options?, context?)` — creates an instance in the given,
882
+ * ambient, or a fresh context and delegates to the instance lock(). This shadows Resource.static
883
+ * lock so that both callers share the same transaction link (required for cross-instance upgrade
884
+ * detection). lock() is an in-process API with no authorization hook of its own; it is not
885
+ * protocol-dispatched, so no allowUpdate/allowCreate check runs on acquisition.
886
+ *
887
+ * Dropping the trailing `context` leaks the key: the bare `{}` fallback is an
888
+ * ImmediateTransaction, which releases no record locks.
889
+ */
890
+ lock(target?: RequestTargetOrId | RecordLockOptions, options?: RecordLockOptions, context?: any): Promise<any>;
891
+ operation(operation: any, context: any): any;
892
+ /**
893
+ * This is responsible for ordering and select()ing the attributes/properties from returned entries
894
+ */
895
+ transformToOrderedSelect(
896
+ entries: any[],
897
+ select: (string | SubSelect)[],
898
+ sort: Sort,
899
+ context: Context,
900
+ readTxn: any,
901
+ transformToRecord: Function
902
+ ): any;
903
+ /**
904
+ * This is responsible for select()ing the attributes/properties from returned entries
905
+ * @param rowFilter explicit row predicate applied to the record actually being
906
+ * returned — i.e. AFTER any caching-source revalidation replaces a stale local copy — so an
907
+ * authorization verdict can't be made on bytes that differ from what the caller receives.
908
+ * @param includeExpired when true, a row past its TTL but not yet swept is treated as a live
909
+ * match rather than gone (used by the SQL engine's UPDATE/DELETE row-finder).
910
+ * @param sort post-ordering owned by this selection
911
+ */
912
+ transformEntryForSelect(
913
+ select: any,
914
+ context: any,
915
+ readTxn: any,
916
+ filtered: any,
917
+ ensure_loaded?: any,
918
+ canSkip?: any,
919
+ rowFilter?: any,
920
+ includeExpired?: any,
921
+ sort?: any
922
+ ): (entry: Entry) => any;
923
+ /**
924
+ * Subscribe on one thread unless this is a per-thread subscription
925
+ */
926
+ subscribeOnThisThread(workerIndex: any, options: any): boolean;
927
+ /**
928
+ * Write a single table-reload marker for this table (harper-pro#489): a LOCAL_ONLY audit entry of
929
+ * type 'reload' with no record, committed in its own transaction. Subscribers driven off the audit
930
+ * stream — hdb_nodes peer discovery and hdb_certificate CA install — treat it as "this table was
931
+ * bulk-reloaded, re-read it". It is needed after a copyApply base copy, whose per-row snapshot rows
932
+ * carry no audit entry, so the per-row events those subscribers rely on never fire. The marker is
933
+ * never replicated (its LOCAL_ONLY bit makes the send path skip it without decoding the
934
+ * peers-may-not-know type), and a lost marker self-heals on restart because each subscriber re-scans
935
+ * the table when it (re)subscribes.
936
+ */
937
+ writeReloadMarker(context?: any): void;
938
+ /**
939
+ * Write one cluster record-lock control entry (harper#483 Phase 1). Not local-only: replicating
940
+ * it IS the send.
941
+ *
942
+ * `recordId` must stay null. An entry carrying the locked key would share
943
+ * `(version, tableId, recordId, nodeId)` with the holder's own first write, which is stamped at
944
+ * exactly `ts_R`, and `RocksTransactionLogStore.getSync` answers with the FIRST entry at a
945
+ * timestamp and key — so `_writeUpdate`'s keyed dedup would find this one and drop that write.
946
+ * The payload goes in as bytes rather than through `recordUpdater`, which would run it through
947
+ * schema projection and the table's shared structure dictionary.
948
+ */
949
+ writeLockControlEntry(entry: LockControlEntry): Promise<number | undefined>;
950
+ /**
951
+ * The coordinator that holds this node's admissions, transport or not. Releasing and registering
952
+ * go here rather than through `lockCoordinator`, which answers undefined while a transport is
953
+ * momentarily unregistered — and a release dropped on that answer leaves the key's home holding
954
+ * its grant until the delegation's own deadline.
955
+ */
956
+ get admittingCoordinator(): LockCoordinator | undefined;
957
+ /**
958
+ * This table's cluster lock coordinator, created on first use and only while a transport is
959
+ * registered for the database. Nothing is allocated on the Phase 0 path.
960
+ */
961
+ get lockCoordinator(): LockCoordinator | undefined;
962
+ addAttributes(attributesToAdd: Attribute[]): Promise<any>;
963
+ removeAttributes(names: string[]): Promise<any>;
964
+ /**
965
+ * Get the size of the table in bytes (based on amount of pages stored in the database)
966
+ */
967
+ getSize(): number;
968
+ /** Sizes of this table's durable record-structure dictionaries. */
969
+ getStructureCounts(): StructureCounts | undefined;
970
+ getAuditSize(): number;
971
+ /**
972
+ * Get available/free/size storage stats for the table's underlying volume. Async because
973
+ * this may need to read quota-status.json (#1976); getSize/getAuditSize stay sync because
974
+ * they only read in-memory store stats.
975
+ */
976
+ getStorageStats(): Promise<StorageSpaceStats>;
977
+ getRecordCount(options?: any): Promise<
978
+ | {
979
+ recordCount: number;
980
+ estimatedRange?: undefined;
981
+ }
982
+ | {
983
+ recordCount: number;
984
+ estimatedRange: number[];
985
+ }
986
+ >;
987
+ /**
988
+ * When attributes have been changed, we update the accessors that are assigned to this table
989
+ */
990
+ updatedAttributes(): void;
991
+ setComputedAttribute(attribute_name: any, resolver: any): void;
992
+ /**
993
+ * Override the default embedder for an `@embed` attribute. Return the vector to
994
+ * store at `attribute_name`. The embedder receives the write payload (the fields
995
+ * present in the PUT/PATCH body), not the post-merge record, so multi-field
996
+ * concatenation only works when all source fields are in the same write.
997
+ */
998
+ setEmbedAttribute(attribute_name: string, embedder: Embedder): void;
999
+ /**
1000
+ * Override the default decider for a `@decide` attribute. Return `{ value, probability }`
1001
+ * to store at the attribute and its confidence attribute, or `null` to clear both. The
1002
+ * value must be one the directive allows, and the probability is required when the
1003
+ * directive names a confidence attribute. Like an embedder, the decider receives the write
1004
+ * payload, not the post-merge record, and a `signal` that aborts when a sibling hook fails.
1005
+ */
1006
+ setDecideAttribute(attribute_name: string, decider: Decider): void;
1007
+ deleteHistory(endTime?: number, cleanupDeletedRecords?: boolean): Promise<number>;
1008
+ getHistory(
1009
+ startTime?: number,
1010
+ endTime?: number
1011
+ ): AsyncGenerator<
1012
+ {
1013
+ id: any;
1014
+ localTime: any;
1015
+ version: any;
1016
+ type: any;
1017
+ value: any;
1018
+ user: any;
1019
+ operation: any;
1020
+ },
1021
+ void,
1022
+ unknown
1023
+ >;
1024
+ getHistoryOfRecord(id: any): Promise<any[]>;
1025
+ clear(): any;
1026
+ /** Release everything makeTable() registered process-wide; the class must not be used afterwards. */
1027
+ cleanup(): void;
1028
+ closeMaintenance(deadline?: number): Promise<void>;
1029
+ resumeMaintenance(): void;
1030
+ _readTxnForContext(context: any): (LMDBReadTransaction | RocksTransaction) & {
1031
+ openTimer?: number;
1032
+ retryRisk?: number;
1033
+ isDone?: boolean;
1034
+ isCommitted?: boolean;
1035
+ };
1036
+ transactions: DatabaseTransactionRecord[] & {
1037
+ timestamp: number;
1038
+ };
1039
+ path?: string;
1040
+ directURLMapping: boolean;
1041
+ loadAsInstance: boolean;
1042
+ requestContract?: Contract;
1043
+ inputSchemas?: {
1044
+ [verb: string]: {
1045
+ query?: JsonSchemaFragment;
1046
+ body?: JsonSchemaFragment;
1047
+ };
1048
+ };
1049
+ withSchema<Base extends new (...args: any[]) => any, const C extends Contract>(
1050
+ this: Base,
1051
+ contract: C
1052
+ ): SchemaClass<Base, C>;
1053
+ get: {
1054
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1055
+ reliesOnPrototype: boolean;
1056
+ };
1057
+ put: {
1058
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1059
+ reliesOnPrototype: boolean;
1060
+ };
1061
+ patch: {
1062
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1063
+ reliesOnPrototype: boolean;
1064
+ };
1065
+ delete: {
1066
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1067
+ reliesOnPrototype: boolean;
1068
+ };
1069
+ create(idPrefix: Id, record: any, context: Context): Promise<Id>;
1070
+ create(record: any, context: Context): Promise<Id>;
1071
+ invalidate: {
1072
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1073
+ reliesOnPrototype: boolean;
1074
+ };
1075
+ post: {
1076
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1077
+ reliesOnPrototype: boolean;
1078
+ };
1079
+ update: {
1080
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1081
+ reliesOnPrototype: boolean;
1082
+ };
1083
+ connect: {
1084
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1085
+ reliesOnPrototype: boolean;
1086
+ };
1087
+ subscribe: {
1088
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1089
+ reliesOnPrototype: boolean;
1090
+ };
1091
+ publish: {
1092
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1093
+ reliesOnPrototype: boolean;
1094
+ };
1095
+ search: {
1096
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1097
+ reliesOnPrototype: boolean;
1098
+ };
1099
+ query: {
1100
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1101
+ reliesOnPrototype: boolean;
1102
+ };
1103
+ copy: {
1104
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1105
+ reliesOnPrototype: boolean;
1106
+ };
1107
+ move: {
1108
+ (idOrQuery: string | Id | Query, dataOrContext?: any, context?: Context): any;
1109
+ reliesOnPrototype: boolean;
1110
+ };
1111
+ isCollection(resource: any): any;
1112
+ parseQuery(search: any, query: any): any;
1113
+ parsePath(path: any, context: any, query: any): any;
1114
+ }
1115
+
596
1116
  /**
597
1117
  * This returns a Table class for the given table settings (determined from the metadata table)
598
1118
  * Instances of the returned class are Resource instances, intended to provide a consistent view or transaction of the table
@@ -617,6 +1137,31 @@ function cloneConditions(conditions: any[]): any[] {
617
1137
  return copy;
618
1138
  });
619
1139
  }
1140
+
1141
+ function selectRequestsProperty(select: any, propertyName: string): boolean {
1142
+ if (!select) return false;
1143
+ const selected = Array.isArray(select) ? select : [select];
1144
+ return selected.some((property) => (typeof property === 'string' ? property : property?.name) === propertyName);
1145
+ }
1146
+
1147
+ function appendSelectProperty(select: any, propertyName: string): any[] {
1148
+ if (!Array.isArray(select)) return [select, propertyName];
1149
+ return Object.assign([...select, propertyName], select);
1150
+ }
1151
+
1152
+ function conditionsContainFullText(entries: any[], definitions: readonly FullTextDefinition[]): boolean {
1153
+ for (const entry of entries) {
1154
+ if (entry.conditions) {
1155
+ if (conditionsContainFullText(entry.conditions, definitions)) return true;
1156
+ } else if (
1157
+ fullTextComparatorMode(entry.comparator) &&
1158
+ typeof (entry[0] ?? entry.attribute) === 'string' &&
1159
+ definitions.some(({ name }) => name === (entry[0] ?? entry.attribute))
1160
+ )
1161
+ return true;
1162
+ }
1163
+ return false;
1164
+ }
620
1165
  // Ambient, path-scoped cycle guard for the enumerable-struct `toJSON` serialization path. A record on
621
1166
  // a cyclically-enumerable table can (transitively) reference itself, which would recurse forever through
622
1167
  // JSON.stringify. The struct getters resolve related records by id — and without a shared cache each
@@ -737,7 +1282,7 @@ setLockCoordinatorResolver(
737
1282
  }
738
1283
  );
739
1284
 
740
- export function makeTable(options) {
1285
+ export function makeTable(options): TableResourceClass {
741
1286
  const {
742
1287
  primaryKey,
743
1288
  indices,
@@ -757,9 +1302,12 @@ export function makeTable(options) {
757
1302
  cacheControl,
758
1303
  isBranch,
759
1304
  fullTextIndexes = [],
1305
+ fullTextFields = [],
760
1306
  fullTextIndexGenerations = Object.create(null),
761
1307
  fullTextIndexRetirements = [],
762
1308
  } = options;
1309
+ let declaredFullTextFields: readonly string[] = Object.freeze([...fullTextFields]);
1310
+ let fullTextFieldNames = fullTextFields.length > 0 ? new Set<string>(fullTextFields) : undefined;
763
1311
  const tableRootStore = primaryStore.rootStore;
764
1312
  const tableRootPath = tableRootStore.path;
765
1313
  let { expirationMS: expirationMs, evictionMS: evictionMs, audit, trackDeletes } = options;
@@ -1084,13 +1632,32 @@ export function makeTable(options) {
1084
1632
  close(dropping?: boolean): Promise<void>;
1085
1633
  fullTextDefinitions?(): readonly FullTextDefinition[];
1086
1634
  matchesCurrent?(): boolean;
1087
- restoreAfterFailedDrop?(): typeof TableResource.derivedIndexRuntime;
1635
+ restoreAfterFailedDrop?(): TableResourceClass['derivedIndexRuntime'];
1088
1636
  retireAfterConfirmedDrop?(definitions?: readonly Pick<FullTextDefinition, 'name'>[]): Promise<boolean>;
1089
1637
  completeDrop?(dropped?: boolean): void;
1090
1638
  }
1091
1639
  | undefined;
1092
1640
  static audit = audit;
1093
1641
  static fullTextIndexes: FullTextDefinition[] = fullTextIndexes;
1642
+ static get fullTextFields(): readonly string[] {
1643
+ return declaredFullTextFields;
1644
+ }
1645
+ static set fullTextFields(names: readonly string[]) {
1646
+ declaredFullTextFields = Object.freeze([...names]);
1647
+ fullTextFieldNames = names.length > 0 ? new Set(names) : undefined;
1648
+ }
1649
+ static assertFullTextSelection(select: unknown, sort?: any): void {
1650
+ assertFullTextSelection(select);
1651
+ for (let order = sort; order; order = order.next) assertRecordField(order.attribute);
1652
+ }
1653
+ static assertFullTextRecordField(name: unknown): void {
1654
+ assertFullTextRecordField(name);
1655
+ }
1656
+ static isFullTextSearchEntryCurrent(entry: Entry): boolean {
1657
+ return !(entry.metadataFlags & (INVALIDATED | EVICTED | VERSION_REUSED));
1658
+ }
1659
+ static fullTextQueryIndexes: { [name: string]: { customIndex: unknown } } = Object.create(null);
1660
+ static hasFullTextQueryIndexes = false;
1094
1661
  static fullTextIndexGenerations: FullTextIndexGenerations = fullTextIndexGenerations;
1095
1662
  static fullTextIndexRetirements: string[] = fullTextIndexRetirements;
1096
1663
  static hasCurrentFullTextIndexRetirements(names: readonly string[]): boolean | Promise<boolean> {
@@ -1153,7 +1720,7 @@ export function makeTable(options) {
1153
1720
  static userDeciders: { [name: string]: Decider } = {};
1154
1721
  static userSetDeciders: Set<string> = new Set();
1155
1722
  static decideAttributes: DecideAttribute[] = (attributes as any[]).filter((a) => a?.decide);
1156
- static source?: typeof TableResource;
1723
+ static source?: any;
1157
1724
  declare static sourceOptions: any;
1158
1725
  declare static intermediateSource: boolean;
1159
1726
  static getResidencyById: (id: Id) => number | void;
@@ -1174,7 +1741,7 @@ export function makeTable(options) {
1174
1741
  * @returns
1175
1742
  */
1176
1743
  // #section: resource-registry
1177
- static sourcedFrom(source, options) {
1744
+ static sourcedFrom(source: any, options: any): any {
1178
1745
  // define a source for retrieving invalidated entries for caching purposes
1179
1746
  if (options) {
1180
1747
  this.sourceOptions = options;
@@ -1305,7 +1872,7 @@ export function makeTable(options) {
1305
1872
  }
1306
1873
  if (Table && event.type === 'put' && value == null && !shouldRevalidateEvents)
1307
1874
  await reportDroppedWrite(event, context, new Error('Source-applied put has no record content'));
1308
- const resource: TableResource = await Table.getResource(id, context, options);
1875
+ const resource = await Table.getResource(id, context, options);
1309
1876
  if (event.finished) await event.finished;
1310
1877
  // an aborted source transaction's released context would otherwise commit this write on its own
1311
1878
  if (context.sourceAborted) return;
@@ -1671,13 +2238,14 @@ export function makeTable(options) {
1671
2238
  hasChanges = true;
1672
2239
  }
1673
2240
  }
1674
- if (hasChanges || event.fullTextIndexes !== undefined) {
2241
+ if (hasChanges || event.fullTextIndexes !== undefined || event.fullTextFields !== undefined) {
1675
2242
  const schemaVersion = (this as any).schemaVersion;
1676
2243
  const definedTable: any = table({
1677
2244
  table: tableName,
1678
2245
  database: databaseName,
1679
2246
  attributes: updatedAttributes,
1680
2247
  fullTextIndexes: event.fullTextIndexes,
2248
+ fullTextFields: event.fullTextFields,
1681
2249
  origin: 'cluster',
1682
2250
  });
1683
2251
  if (definedTable.schemaVersion !== schemaVersion)
@@ -1762,7 +2330,7 @@ export function makeTable(options) {
1762
2330
  target: RequestTarget,
1763
2331
  request: Context,
1764
2332
  resourceOptions?: any
1765
- ): Promise<TableResource<Record>> | TableResource<Record> {
2333
+ ): Promise<TableResourceInstance<Record>> | TableResourceInstance<Record> {
1766
2334
  if (databaseDropPrepared(tableRootPath) || databaseCommitsSuspended(tableRootStore))
1767
2335
  throw new DatabaseClosingError(
1768
2336
  databaseName,
@@ -1774,7 +2342,11 @@ export function makeTable(options) {
1774
2342
  }
1775
2343
  return resource;
1776
2344
  }
1777
- _loadRecord(target: RequestTarget, request: Context, resourceOptions?: any): MaybePromise<TableResource<Record>> {
2345
+ _loadRecord(
2346
+ target: RequestTarget,
2347
+ request: Context,
2348
+ resourceOptions?: any
2349
+ ): MaybePromise<TableResourceInstance<Record>> {
1778
2350
  const id = target && typeof target === 'object' ? target.id : target;
1779
2351
  if (id == null) return this;
1780
2352
  checkValidId(id);
@@ -2530,6 +3102,12 @@ export function makeTable(options) {
2530
3102
  */
2531
3103
  get(target?: any): any {
2532
3104
  const constructor: any = this.constructor;
3105
+ if (fullTextFieldNames || hasRelationships) assertFullTextSelection(target?.select);
3106
+ if (fullTextFieldNames) {
3107
+ assertRecordField(
3108
+ target?.property ?? (typeof target === 'string' && constructor.loadAsInstance !== false ? target : undefined)
3109
+ );
3110
+ }
2533
3111
  if (typeof target === 'string' && constructor.loadAsInstance !== false) return this.getProperty(target);
2534
3112
  if (isSearchTarget(target)) {
2535
3113
  // go back to the static search method so it gets a chance to override
@@ -2643,6 +3221,9 @@ export function makeTable(options) {
2643
3221
  */
2644
3222
  allowRead(user: User, target: RequestTarget, context: Context): boolean {
2645
3223
  const tablePermission = getTablePermissions(user, target);
3224
+ // Resource.search consumes checkPermission before full-text source-field authorization runs.
3225
+ if (TableResource.fullTextIndexes.length > 0 && target?.checkPermission && tablePermission)
3226
+ (target as any)[FULL_TEXT_READ_PERMISSION] = tablePermission;
2646
3227
  if (tablePermission?.read) {
2647
3228
  if (tablePermission.isSuperUser) return true;
2648
3229
  const attribute_permissions = tablePermission.attribute_permissions;
@@ -2658,7 +3239,13 @@ export function makeTable(options) {
2658
3239
  (target as any).select = selectArray
2659
3240
  .map((property: any) => {
2660
3241
  const propertyName = property.name || property;
2661
- if (!attrsForType || attrsForType[propertyName]) {
3242
+ if (
3243
+ !attrsForType ||
3244
+ attrsForType[propertyName] ||
3245
+ fullTextFieldNames?.has(propertyName) ||
3246
+ propertyName === '$score' ||
3247
+ propertyName === '$highlights'
3248
+ ) {
2662
3249
  const relatedTable = propertyResolvers[propertyName]?.definition?.tableClass;
2663
3250
  if (relatedTable) {
2664
3251
  // if there is a related table, we need to ensure the user has permission to read from that table and that attributes are properly restricted
@@ -2677,7 +3264,12 @@ export function makeTable(options) {
2677
3264
  .filter(Boolean);
2678
3265
  } else {
2679
3266
  target.select = attribute_permissions
2680
- .filter((attribute) => attribute.read && !propertyResolvers[attribute.attribute_name])
3267
+ .filter(
3268
+ (attribute) =>
3269
+ attribute.read &&
3270
+ !fullTextFieldNames?.has(attribute.attribute_name) &&
3271
+ !propertyResolvers[attribute.attribute_name]
3272
+ )
2681
3273
  .map((attribute) => attribute.attribute_name);
2682
3274
  }
2683
3275
  (target as any)[AUTHORIZATION_SELECT] = true;
@@ -2708,7 +3300,7 @@ export function makeTable(options) {
2708
3300
  // that the user doesn't have permission to remove
2709
3301
  for (const permission of attribute_permissions) {
2710
3302
  const key = permission.attribute_name;
2711
- if (!permission.update && !(key in updatedData)) {
3303
+ if (!permission.update && !fullTextFieldNames?.has(key) && !(key in updatedData)) {
2712
3304
  updatedData[key] = this.getProperty(key);
2713
3305
  }
2714
3306
  }
@@ -3836,6 +4428,7 @@ export function makeTable(options) {
3836
4428
  captureChanges,
3837
4429
  validate: (txnTime, committedBy = transaction) => {
3838
4430
  write.captureChanges?.();
4431
+ if ((context as any)?.source && !committedBy.isReplay) assertFullTextWrite(recordUpdate, true);
3839
4432
  if (fullUpdate || (recordUpdate && hasChanges(this.#changes === recordUpdate ? this : recordUpdate))) {
3840
4433
  if (!(context as any)?.source) {
3841
4434
  committedBy.checkOverloaded();
@@ -4804,6 +5397,7 @@ export function makeTable(options) {
4804
5397
  const txn = txnForContext(context);
4805
5398
  if (!target) throw new Error('No query provided');
4806
5399
  if (target.parseError) throw target.parseError; // if there was a parse error, we can throw it now
5400
+ if (fullTextFieldNames || hasRelationships) assertFullTextSelection(target.select);
4807
5401
  const getColumns = () => {
4808
5402
  const select = target.select;
4809
5403
  if (select) {
@@ -4952,6 +5546,7 @@ export function makeTable(options) {
4952
5546
  conditions = cloneConditions(conditions);
4953
5547
  let orderAlignedCondition;
4954
5548
  let syntheticOrderCondition;
5549
+ let includeFullTextHighlights = false;
4955
5550
  const filtered = {};
4956
5551
 
4957
5552
  function prepareConditions(conditions: any[], operator: string) {
@@ -4983,6 +5578,54 @@ export function makeTable(options) {
4983
5578
  }
4984
5579
  }
4985
5580
  const attribute_name = condition[0] ?? condition.attribute;
5581
+ const fullTextMode =
5582
+ TableResource.fullTextIndexes.length > 0 ||
5583
+ Array.isArray(attribute_name) ||
5584
+ (typeof attribute_name === 'string' && fullTextFieldNames?.has(attribute_name))
5585
+ ? fullTextComparatorMode(condition.comparator)
5586
+ : undefined;
5587
+ if (!fullTextMode && (fullTextFieldNames || hasRelationships)) assertFullTextRecordField(attribute_name);
5588
+ const fullTextDefinition =
5589
+ fullTextMode && typeof attribute_name === 'string'
5590
+ ? TableResource.fullTextIndexes.find((definition) => definition.name === attribute_name)
5591
+ : undefined;
5592
+ if (fullTextMode && Array.isArray(attribute_name))
5593
+ throw new ClientError('Full-text predicates must directly name an index on the queried table', 400);
5594
+ if (fullTextMode && !fullTextDefinition) throwUnknownFullTextIndex(context, target, attribute_name);
5595
+ if (fullTextDefinition) {
5596
+ const fields = condition.fields;
5597
+ if (fields !== undefined) {
5598
+ if (!Array.isArray(fields) || fields.length === 0 || fields.some((field) => typeof field !== 'string'))
5599
+ throw new ClientError(`Full-text index '${attribute_name}' requires a non-empty fields list`, 400);
5600
+ assertFullTextReadAccess(context, target, fullTextDefinition, fields);
5601
+ } else assertFullTextReadAccess(context, target, fullTextDefinition);
5602
+ const value = condition[1] ?? condition.value;
5603
+ if (typeof value !== 'string' || value.length === 0)
5604
+ throw new ClientError(
5605
+ `Full-text index '${attribute_name}' requires a full-text comparator and non-empty string value`,
5606
+ 400
5607
+ );
5608
+ if (fullTextMode === 'phrase' && !fullTextDefinition.positions)
5609
+ throw new ClientError(`Full-text index '${attribute_name}' does not store phrase positions`, 400);
5610
+ if ((fullTextMode === 'prefix' || fullTextMode === 'fuzzy-prefix') && !fullTextDefinition.surfaceTerms)
5611
+ throw new ClientError(`Full-text index '${attribute_name}' does not store surface terms`, 400);
5612
+ if (fields !== undefined) {
5613
+ const sourceNames = new Set(fullTextDefinition.fields.map(({ name }) => name));
5614
+ if (new Set(fields).size !== fields.length || fields.some((field) => !sourceNames.has(field)))
5615
+ throw new ClientError(
5616
+ `Full-text index '${attribute_name}' contains an unknown or duplicate field`,
5617
+ 400
5618
+ );
5619
+ }
5620
+ condition.includeHighlights =
5621
+ condition.includeHighlights === true || selectRequestsProperty(target.select, '$highlights');
5622
+ if (condition.includeHighlights && !fullTextDefinition.highlighting)
5623
+ throw new ClientError(`Full-text index '${attribute_name}' does not enable highlighting`, 400);
5624
+ if (!TableResource.fullTextQueryIndexes[attribute_name]?.customIndex)
5625
+ throw new IndexRebuildingError(`Full-text index '${attribute_name}' is not ready`);
5626
+ includeFullTextHighlights ||= condition.includeHighlights;
5627
+ continue;
5628
+ }
4986
5629
  let attribute = attribute_name == null ? primaryKeyAttribute : findAttribute(attributes, attribute_name);
4987
5630
  if (!attribute && Array.isArray(attribute_name) && attribute_name.length > 1) {
4988
5631
  // Plain JSON nested path: the leaf may not be declared in the
@@ -5058,8 +5701,19 @@ export function makeTable(options) {
5058
5701
  }
5059
5702
  const operator = target.operator;
5060
5703
  if (conditions.length > 0 || operator) conditions = prepareConditions(conditions, operator);
5061
- const sort = typeof target.sort === 'object' && target.sort;
5704
+ let sort = typeof target.sort === 'object' && target.sort;
5705
+ if (
5706
+ TableResource.fullTextIndexes.length > 0 &&
5707
+ conditionsContainFullText(conditions, TableResource.fullTextIndexes)
5708
+ ) {
5709
+ if ((target as any).reverse)
5710
+ throw new ClientError('Full-text results can only use descending $score order', 400);
5711
+ if (sort && (sort.attribute !== '$score' || sort.next || sort.descending !== true))
5712
+ throw new ClientError('Full-text results can only use descending $score order', 400);
5713
+ sort = undefined;
5714
+ }
5062
5715
  for (let order = sort; order; order = order.next) {
5716
+ if (fullTextFieldNames) assertRecordField(order.attribute);
5063
5717
  if (typeof order.attribute !== 'string') continue;
5064
5718
  const customIndex = indices[order.attribute]?.customIndex;
5065
5719
  if (customIndex?.exactDistance) customIndex.exactDistance(order, null);
@@ -5133,7 +5787,13 @@ export function makeTable(options) {
5133
5787
  postOrdering = sort;
5134
5788
  }
5135
5789
  }
5136
- const select = target.select;
5790
+ const select = includeFullTextHighlights
5791
+ ? target.select === undefined
5792
+ ? ['*', '$highlights']
5793
+ : selectRequestsProperty(target.select, '$highlights')
5794
+ ? target.select
5795
+ : appendSelectProperty(target.select, '$highlights')
5796
+ : target.select;
5137
5797
  // Whether the caller supplied real filter conditions — read from the raw request, NOT the
5138
5798
  // planner-augmented `conditions` (which by now may carry a synthetic `sort` pseudo-condition and
5139
5799
  // injected full-scan condition). Used to pick the count-estimate source below.
@@ -5263,7 +5923,10 @@ export function makeTable(options) {
5263
5923
  if (!c) return false;
5264
5924
  if (c.conditions) return touchesCustomIndex(c.conditions);
5265
5925
  const attr = Array.isArray(c.attribute) ? c.attribute[0] : (c.attribute ?? c[0]);
5266
- return typeof attr === 'string' && Boolean(indices[attr]?.customIndex);
5926
+ if (typeof attr !== 'string') return false;
5927
+ return fullTextComparatorMode(c.comparator)
5928
+ ? Boolean(TableResource.fullTextQueryIndexes?.[attr]?.customIndex)
5929
+ : Boolean(indices[attr]?.customIndex);
5267
5930
  });
5268
5931
  const approximateResultSet = typeof target.vectorFilter === 'function' || touchesCustomIndex(conditions);
5269
5932
  return (async () => {
@@ -6512,6 +7175,7 @@ export function makeTable(options) {
6512
7175
  }
6513
7176
  // #section: validation
6514
7177
  validate(record: any, patch?: boolean) {
7178
+ if (fullTextFieldNames) assertFullTextWrite(record);
6515
7179
  // Accumulate structured per-field issues so the 400 carries `{ path, code,
6516
7180
  // message }[]` matching the emitted OpenAPI, instead of a single joined string. The joined
6517
7181
  // message is still built for the HTTP title, preserving back-compat for callers that read it.
@@ -6955,6 +7619,8 @@ export function makeTable(options) {
6955
7619
  $updatedTime: (object, context, entry) => entry.version,
6956
7620
  $expiresAt: (object, context, entry) => entry.expiresAt,
6957
7621
  $record: (object, context, entry) => (entry ? { value: object } : object),
7622
+ $score: (object, context, entry) => entry?.$score,
7623
+ $highlights: (object, context, entry) => entry?.$highlights,
6958
7624
  $distance: (object, context, entry, returnEntry, sort) => {
6959
7625
  if (!entry) return;
6960
7626
  if (entry.distance !== undefined) return entry.distance;
@@ -6975,6 +7641,7 @@ export function makeTable(options) {
6975
7641
  return customIndex.propertyResolver(vector, context, entry, distanceSort);
6976
7642
  },
6977
7643
  };
7644
+ propertyResolvers.$highlights.directReturn = true;
6978
7645
  for (const attribute of this.attributes) {
6979
7646
  if (attribute.isPrimaryKey) primaryKeyAttribute = attribute;
6980
7647
  attribute.resolve = null; // reset this
@@ -7515,6 +8182,13 @@ export function makeTable(options) {
7515
8182
  return txnForContext(context).getReadTxn();
7516
8183
  }
7517
8184
  }
8185
+ type _TableResourceMatchesItsDeclaredTypes = [
8186
+ AssertNoDrift<MemberDrift<TableResource<ParitySentinel>, TableResourceInstance<ParitySentinel>>>,
8187
+ AssertNoDrift<MemberDrift<Omit<typeof TableResource, 'prototype'>, Omit<TableResourceClass, 'prototype'>>>,
8188
+ AssertTrue<ExactlyEqual<ConstructorParameters<typeof TableResource>, ConstructorParameters<TableResourceClass>>>,
8189
+ AssertTrue<ExactlyEqual<InstanceType<TableResourceClass>, TableResourceInstance<object>>>,
8190
+ AssertTrue<ExactlyEqual<TableResourceClass['prototype'], TableResourceInstance>>,
8191
+ ];
7518
8192
  const throttledCallToSource = throttle(
7519
8193
  async (source, id, sourceContext, existingEntry) => {
7520
8194
  // call the data source if it exists and will fulfill our request for data
@@ -7809,13 +8483,86 @@ export function makeTable(options) {
7809
8483
  return table;
7810
8484
  }
7811
8485
  }
8486
+ function assertRecordField(name: unknown): void {
8487
+ const field = Array.isArray(name) ? name[0] : name;
8488
+ if (typeof field === 'string' && fullTextFieldNames?.has(field))
8489
+ throw new ClientError(
8490
+ `Full-text field "${field}" is query-only; use a full-text comparator and select $score or $highlights`,
8491
+ 400
8492
+ );
8493
+ }
8494
+
8495
+ function assertFullTextRecordField(name: unknown): void {
8496
+ if (!Array.isArray(name)) return assertRecordField(name);
8497
+ const [first, ...remaining] = name;
8498
+ assertRecordField(first);
8499
+ if (remaining.length === 0) return;
8500
+ propertyResolvers[first]?.definition?.tableClass?.assertFullTextRecordField?.(
8501
+ remaining.length === 1 ? remaining[0] : remaining
8502
+ );
8503
+ }
8504
+
8505
+ function assertFullTextSelection(select: unknown): void {
8506
+ if (!fullTextFieldNames && !hasRelationships) return;
8507
+ if (typeof select === 'string') assertRecordField(select);
8508
+ else if (Array.isArray(select)) {
8509
+ for (const property of select) {
8510
+ const name = typeof property === 'object' ? property?.name : property;
8511
+ assertRecordField(name);
8512
+ if (property && typeof property === 'object')
8513
+ propertyResolvers[name]?.definition?.tableClass?.assertFullTextSelection?.(
8514
+ property.select || (Array.isArray(property) ? property : undefined),
8515
+ property.sort
8516
+ );
8517
+ }
8518
+ }
8519
+ }
8520
+
8521
+ function assertFullTextWrite(record: any, sourceFill = false): void {
8522
+ if (!fullTextFieldNames || !record || typeof record !== 'object') return;
8523
+ for (const name in record) {
8524
+ if (fullTextFieldNames.has(name)) {
8525
+ if (sourceFill)
8526
+ throw new ServerError(`Source for ${tableName} returned query-only full-text field "${name}"`, 502);
8527
+ throw new ClientError(`Full-text field "${name}" is query-only and cannot be written`, 400);
8528
+ }
8529
+ }
8530
+ }
8531
+
8532
+ function assertFullTextReadAccess(
8533
+ context: Context | undefined,
8534
+ target: RequestTarget,
8535
+ definition: FullTextDefinition,
8536
+ requestedFields?: string[]
8537
+ ) {
8538
+ const user = (context as any)?.user;
8539
+ const permission = (target as any)[FULL_TEXT_READ_PERMISSION] ?? getTablePermissions(user, target);
8540
+ // Calls without a principal or explicit permission are trusted internal calls, matching allowRead.
8541
+ if (!permission && !user) return;
8542
+ if (permission?.isSuperUser || !permission?.attribute_permissions?.length) return;
8543
+ const readable = attributesAsObject(permission.attribute_permissions, 'read');
8544
+ const searched = requestedFields ?? definition.fields.map(({ name }) => name);
8545
+ if (searched.some((name) => !readable[name])) throw new AccessViolation(user);
8546
+ }
8547
+ function throwUnknownFullTextIndex(
8548
+ context: Context | undefined,
8549
+ target: RequestTarget,
8550
+ attributeName?: unknown
8551
+ ): never {
8552
+ const user = (context as any)?.user;
8553
+ const permission = (target as any)[FULL_TEXT_READ_PERMISSION] ?? getTablePermissions(user, target);
8554
+ if (permission?.attribute_permissions?.length) throw new AccessViolation(user);
8555
+ if (typeof attributeName === 'string' && fullTextFieldNames?.has(attributeName))
8556
+ throw new IndexRebuildingError(`Full-text index '${attributeName}' is unavailable`);
8557
+ throw new ClientError('Full-text comparator requires a declared @fullText index', 400);
8558
+ }
7812
8559
 
7813
8560
  function setLoadedFromSource(target: RequestTarget | undefined, loadedFromSource: boolean) {
7814
8561
  // cache disposition is a per-get result, recorded on the RequestTarget of the get (#1576)
7815
8562
  // target may be a primitive id on instance-API calls, which can't hold the flag
7816
8563
  if (target && typeof target === 'object') target.loadedFromSource = loadedFromSource;
7817
8564
  }
7818
- function ensureLoadedFromSource(source: typeof TableResource, id, entry, context, resource?, target?) {
8565
+ function ensureLoadedFromSource(source: TableResourceClass, id, entry, context, resource?, target?) {
7819
8566
  if (context?.onlyIfCached) {
7820
8567
  if (!entry?.value) throw new ServerError('Entry is not cached', 504);
7821
8568
  return;
@@ -8091,7 +8838,7 @@ export function makeTable(options) {
8091
8838
  * This is used to record that a retrieve a record from source
8092
8839
  */
8093
8840
  async function getFromSource(
8094
- source: typeof TableResource,
8841
+ source: TableResourceClass,
8095
8842
  id: Id,
8096
8843
  existingEntry: Entry,
8097
8844
  context: Context,
@@ -8170,6 +8917,7 @@ export function makeTable(options) {
8170
8917
  const commitPromise = transaction(sourceContext, async (_txn) => {
8171
8918
  const start = performance.now();
8172
8919
  let updatedRecord, assignCreatedTime, sourceVersion;
8920
+ let reusedCachedRecord = false;
8173
8921
  let hasChanges, invalidated;
8174
8922
  try {
8175
8923
  updatedRecord = await throttledCallToSource(source, id, sourceContext, existingEntry);
@@ -8213,6 +8961,7 @@ export function makeTable(options) {
8213
8961
  if (status === 304) {
8214
8962
  // revalidation of our current cached record
8215
8963
  updatedRecord = existingRecord;
8964
+ reusedCachedRecord = true;
8216
8965
  sourceVersion = existingVersion;
8217
8966
  } else if (!CACHEABLE_STATUS_CODES.has(status)) {
8218
8967
  // non-cacheable status - propagate to client without caching
@@ -8281,6 +9030,7 @@ export function makeTable(options) {
8281
9030
  }
8282
9031
  }
8283
9032
  updatedRecord = storedFieldsOnly(primaryStore.encoder, updatedRecord);
9033
+ if (!reusedCachedRecord) assertFullTextWrite(updatedRecord, true);
8284
9034
  if (primaryKey && updatedRecord[primaryKey] !== id) updatedRecord[primaryKey] = id;
8285
9035
  }
8286
9036
  assignCreatedTime = createdTimeProperty && updatedRecord?.[createdTimeProperty.name] == null;