@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.
- package/bin/backup.ts +6 -1
- package/components/Application.ts +486 -115
- package/components/DESIGN.md +109 -21
- package/components/deploymentProvenance.ts +28 -0
- package/components/operations.js +14 -7
- package/components/packageComponent.ts +5 -1
- package/config-root.schema.json +1 -1
- package/dataLayer/DESIGN.md +35 -7
- package/dataLayer/blobBackup.ts +71 -30
- package/dataLayer/harperBridge/ResourceBridge.ts +16 -2
- package/dataLayer/rocksdbBackup.ts +88 -34
- package/dataLayer/schemaDescribe.ts +45 -1
- package/dist/bin/backup.js +1 -1
- package/dist/bin/backup.js.map +1 -1
- package/dist/components/Application.d.ts +11 -21
- package/dist/components/Application.js +436 -105
- package/dist/components/Application.js.map +1 -1
- package/dist/components/deploymentProvenance.d.ts +4 -0
- package/dist/components/deploymentProvenance.js +32 -0
- package/dist/components/deploymentProvenance.js.map +1 -0
- package/dist/components/operations.js +14 -7
- package/dist/components/operations.js.map +1 -1
- package/dist/components/packageComponent.js +6 -1
- package/dist/components/packageComponent.js.map +1 -1
- package/dist/dataLayer/blobBackup.d.ts +30 -3
- package/dist/dataLayer/blobBackup.js +73 -36
- package/dist/dataLayer/blobBackup.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +1 -0
- package/dist/dataLayer/harperBridge/ResourceBridge.js +15 -3
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/rocksdbBackup.d.ts +4 -1
- package/dist/dataLayer/rocksdbBackup.js +74 -36
- package/dist/dataLayer/rocksdbBackup.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +45 -1
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/resources/DatabaseTransaction.d.ts +1 -0
- package/dist/resources/DatabaseTransaction.js +33 -8
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +7 -3
- package/dist/resources/ResourceInterface.js +2 -0
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Table.d.ts +151 -445
- package/dist/resources/Table.js +209 -8
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.d.ts +11 -0
- package/dist/resources/analytics/write.js +3 -0
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/blob.d.ts +30 -0
- package/dist/resources/blob.js +189 -1
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/databases.d.ts +1 -0
- package/dist/resources/databases.js +80 -15
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/derivedIndexRuntime.d.ts +13 -0
- package/dist/resources/derivedIndexRuntime.js +57 -5
- package/dist/resources/derivedIndexRuntime.js.map +1 -1
- package/dist/resources/derivedIndexes.d.ts +1 -0
- package/dist/resources/derivedIndexes.js +116 -15
- package/dist/resources/derivedIndexes.js.map +1 -1
- package/dist/resources/fullTextSchema.d.ts +5 -3
- package/dist/resources/fullTextSchema.js +48 -5
- package/dist/resources/fullTextSchema.js.map +1 -1
- package/dist/resources/fullTextSchemaLifecycle.d.ts +2 -0
- package/dist/resources/fullTextSchemaLifecycle.js +33 -0
- package/dist/resources/fullTextSchemaLifecycle.js.map +1 -1
- package/dist/resources/graphql.js +30 -12
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +21 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +114 -5
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/indexes/fullTextDerivedIndex.d.ts +11 -2
- package/dist/resources/indexes/fullTextDerivedIndex.js +187 -24
- package/dist/resources/indexes/fullTextDerivedIndex.js.map +1 -1
- package/dist/resources/indexes/fullTextNativeBinding.d.ts +99 -1
- package/dist/resources/indexes/fullTextNativeBinding.js +35 -1
- package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -1
- package/dist/resources/indexes/fullTextQueryIndex.d.ts +58 -0
- package/dist/resources/indexes/fullTextQueryIndex.js +943 -0
- package/dist/resources/indexes/fullTextQueryIndex.js.map +1 -0
- package/dist/resources/indexes/fullTextQueryProtocol.d.ts +6 -0
- package/dist/resources/indexes/fullTextQueryProtocol.js +32 -0
- package/dist/resources/indexes/fullTextQueryProtocol.js.map +1 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +4 -1
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +30 -7
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -1
- package/dist/resources/search.d.ts +41 -0
- package/dist/resources/search.js +468 -26
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/transactionBroadcast.js +101 -50
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/resources/typeParity.d.ts +9 -0
- package/dist/resources/typeParity.js +3 -0
- package/dist/resources/typeParity.js.map +1 -0
- package/dist/server/REST.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +48 -8
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.d.ts +7 -0
- package/dist/server/serverHelpers/contentTypes.js +36 -23
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.js +28 -5
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/server/threads/itc.js +19 -0
- package/dist/server/threads/itc.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +3 -0
- package/dist/server/threads/manageThreads.js +90 -23
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/utility/operation_authorization.js +25 -7
- package/dist/utility/operation_authorization.js.map +1 -1
- package/dist/utility/signalling.js +3 -0
- package/dist/utility/signalling.js.map +1 -1
- package/dist/validation/searchValidator.js +22 -2
- package/dist/validation/searchValidator.js.map +1 -1
- package/npm-shrinkwrap.json +22 -22
- package/package.json +2 -4
- package/resources/DESIGN.md +19 -2
- package/resources/DatabaseTransaction.ts +40 -10
- package/resources/FULL_TEXT.md +97 -0
- package/resources/ResourceInterface.ts +8 -2
- package/resources/Table.ts +767 -17
- package/resources/analytics/write.ts +3 -3
- package/resources/blob.ts +183 -1
- package/resources/databases.ts +126 -14
- package/resources/derivedIndexRuntime.ts +79 -6
- package/resources/derivedIndexes.ts +142 -18
- package/resources/fullTextSchema.ts +55 -9
- package/resources/fullTextSchemaLifecycle.ts +45 -0
- package/resources/graphql.ts +33 -12
- package/resources/indexes/DESIGN.md +167 -15
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +125 -5
- package/resources/indexes/fullTextDerivedIndex.ts +216 -32
- package/resources/indexes/fullTextNativeBinding.ts +133 -3
- package/resources/indexes/fullTextQueryIndex.ts +1120 -0
- package/resources/indexes/fullTextQueryProtocol.ts +35 -0
- package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +28 -8
- package/resources/search.ts +513 -26
- package/resources/transactionBroadcast.ts +86 -47
- package/resources/typeParity.ts +20 -0
- package/schema.graphql +18 -5
- package/server/DESIGN.md +18 -0
- package/server/REST.ts +1 -1
- package/server/itc/serverHandlers.js +61 -8
- package/server/serverHelpers/contentTypes.ts +31 -26
- package/server/serverHelpers/uwsServer.ts +35 -7
- package/server/threads/itc.js +21 -0
- package/server/threads/manageThreads.js +95 -21
- package/studio/web/assets/{Chat-Bq60AO-g.js → Chat-CNEKMllJ.js} +13 -13
- package/studio/web/assets/FloatingChat-CEnWhHeR.js +23 -0
- package/studio/web/assets/SearchDialog-CwhJdEdY.js +1 -0
- package/studio/web/assets/{abnfDiagram-O67JEVCF-DpPZWMOf.js → abnfDiagram-O67JEVCF-raXE027J.js} +1 -1
- package/studio/web/assets/alertDialog-BbWD9Xxa.js +1 -0
- package/studio/web/assets/apiToken-DC5vsaaO.js +1 -0
- package/studio/web/assets/applications-DQZxDy0x.js +296 -0
- package/studio/web/assets/architecture-7GRP2DOG-DDmz3hpI.js +1 -0
- package/studio/web/assets/{architectureDiagram-NJMV4G6O-OmgLraID.js → architectureDiagram-NJMV4G6O-C8_Eet7w.js} +1 -1
- package/studio/web/assets/arrow-up-D2g_57vT.js +1 -0
- package/studio/web/assets/authStore-CJCaADw9.js +3 -0
- package/studio/web/assets/{blockDiagram-BEXU5L5S-Cv7CD3Ai.js → blockDiagram-BEXU5L5S-DR6S5-1D.js} +1 -1
- package/studio/web/assets/{button-MFP9n8Ck.js → button-DWwfgGWI.js} +2 -2
- package/studio/web/assets/{c4Diagram-YGBWAQC7-Ds1vKYnN.js → c4Diagram-YGBWAQC7-BAfZ1G01.js} +1 -1
- package/studio/web/assets/channel-D0AkPjJ8.js +1 -0
- package/studio/web/assets/{chevron-up-CFe8x5Bp.js → chevron-up-CFxbMGv2.js} +1 -1
- package/studio/web/assets/{chunk-3FUC2YCW-DpnBPH8B.js → chunk-3FUC2YCW-DuRp-j19.js} +1 -1
- package/studio/web/assets/{chunk-5DYCD2WN-LLLCQSzg.js → chunk-5DYCD2WN-BceBfIHT.js} +1 -1
- package/studio/web/assets/{chunk-742MDFTN-BL8J_pIO.js → chunk-742MDFTN-CIkt8ijt.js} +1 -1
- package/studio/web/assets/{chunk-7INBJB4K-C-hJHiiK.js → chunk-7INBJB4K-BpeAXyOi.js} +1 -1
- package/studio/web/assets/{chunk-7M6MHVWA-C6ZqkFbA.js → chunk-7M6MHVWA-h6WlaTzO.js} +1 -1
- package/studio/web/assets/{chunk-7PRAP22T-CUXM8BG6.js → chunk-7PRAP22T-KyqX6x3f.js} +1 -1
- package/studio/web/assets/{chunk-GTNCS2PH-DJ2BFvS1.js → chunk-GTNCS2PH-DGHLXyow.js} +1 -1
- package/studio/web/assets/{chunk-GWA4HPMP-C42bSqLL.js → chunk-GWA4HPMP-DsejB6gA.js} +1 -1
- package/studio/web/assets/{chunk-MBY4JIJT-C-5jz6Lo.js → chunk-MBY4JIJT-BwSGweQP.js} +1 -1
- package/studio/web/assets/{chunk-NETBCI7D-D-bzlN4r.js → chunk-NETBCI7D-C8UnOw_f.js} +1 -1
- package/studio/web/assets/{chunk-O7XYJQB3-ep14zA23.js → chunk-O7XYJQB3-CBS3nZYV.js} +1 -1
- package/studio/web/assets/{chunk-UA2S7LBM-BMRWRHED.js → chunk-UA2S7LBM-BHpEdH8D.js} +1 -1
- package/studio/web/assets/{chunk-WEXAMYUT-DdLYVdX1.js → chunk-WEXAMYUT-BAIhTwyh.js} +1 -1
- package/studio/web/assets/{chunk-Z7XXMR3K-BmKEDjcP.js → chunk-Z7XXMR3K-DSHbImdf.js} +1 -1
- package/studio/web/assets/{chunk-ZIGJFQKS-DLZgdNxA.js → chunk-ZIGJFQKS-Cs8fUn7y.js} +1 -1
- package/studio/web/assets/{classDiagram-v2-NBCMYWYE-DOI5vgKT.js → classDiagram-v2-NBCMYWYE-DmDPkt2u.js} +1 -1
- package/studio/web/assets/cn-6T8VranJ.js +1 -0
- package/studio/web/assets/{codicon-Brq4_Ui5.ttf → codicon-DF1abBS2.ttf} +0 -0
- package/studio/web/assets/{createLucideIcon-bziYxJli.js → createLucideIcon-fO5dL7D5.js} +1 -1
- package/studio/web/assets/{css.worker-l1psR_wG.js → css.worker-DoT-OOzx.js} +36 -36
- package/studio/web/assets/{cssMode-DJyc9mHj.js → cssMode-Dz2DxF4e.js} +1 -1
- package/studio/web/assets/{cynefin-OW5HDTMX-BSW-DB7S.js → cynefin-OW5HDTMX-DIvbXLSj.js} +1 -1
- package/studio/web/assets/{cynefinDiagram-VND7K2PF-CbAHEwxt.js → cynefinDiagram-VND7K2PF-BdtaXiCE.js} +1 -1
- package/studio/web/assets/{dagre-6A5THRUB-Csxewgs4.js → dagre-6A5THRUB-C7RUWHyz.js} +1 -1
- package/studio/web/assets/{diagram-22UHCM2B-a0rt8tY4.js → diagram-22UHCM2B-DJCvcaWP.js} +1 -1
- package/studio/web/assets/{diagram-3UASUU5V-lEj2SVNh.js → diagram-3UASUU5V-Du6VKWln.js} +1 -1
- package/studio/web/assets/{diagram-ATOU4E4O-HcrAeB-q.js → diagram-ATOU4E4O-dxRfeG4B.js} +1 -1
- package/studio/web/assets/{diagram-CDSNMT55-CYxiuyEx.js → diagram-CDSNMT55-l5Ga0u-W.js} +1 -1
- package/studio/web/assets/{diagram-MLGK6HIB-ChbCJTgJ.js → diagram-MLGK6HIB-CNQY47LA.js} +1 -1
- package/studio/web/assets/{diagram-MPIPVDR6-DaX9Y-N3.js → diagram-MPIPVDR6-7prveD1B.js} +1 -1
- package/studio/web/assets/{dist-Cz2Y6-6G.js → dist-BrRuMUzt.js} +1 -1
- package/studio/web/assets/{download-CtostjYn.js → download-Cn6tPG4L.js} +1 -1
- package/studio/web/assets/{ebnfDiagram-ZINNZB2B-CwAsENB1.js → ebnfDiagram-ZINNZB2B-5OnkTpfi.js} +1 -1
- package/studio/web/assets/{editor-DDIsQfj6.js → editor-B3mjd3tz.js} +1 -1
- package/studio/web/assets/editor.worker-Yd1NR0QT.js +33 -0
- package/studio/web/assets/{elk-276RUBZZ-Hexg9E4D.js → elk-276RUBZZ-DxDQPjQT.js} +1 -1
- package/studio/web/assets/{erDiagram-OPXOYQCR-CeC-wwO1.js → erDiagram-OPXOYQCR-CQlCX84D.js} +1 -1
- package/studio/web/assets/eventmodeling-NTZA5JFV-DONQf5aG.js +1 -0
- package/studio/web/assets/flowDiagram-KWPJA3E3-BoZ8_QR1.js +1 -0
- package/studio/web/assets/{ganttDiagram-FUAMR5RP-C2_Hhl1-.js → ganttDiagram-FUAMR5RP-bloX3Voy.js} +1 -1
- package/studio/web/assets/{getAnalytics-CjthMQqF.js → getAnalytics-C0FHjtdQ.js} +1 -1
- package/studio/web/assets/{gitGraph-4MIJSDKK-xIk5Vuew.js → gitGraph-4MIJSDKK-BrXWmJt2.js} +1 -1
- package/studio/web/assets/{gitGraphDiagram-X574FWY7-CMxJ34up.js → gitGraphDiagram-X574FWY7-C2KnnZeY.js} +1 -1
- package/studio/web/assets/{html-g1BJHxLy.js → html-BsRGM7tp.js} +1 -1
- package/studio/web/assets/{html.worker-DdwM4IrA.js → html.worker-B1dwY0Ay.js} +27 -27
- package/studio/web/assets/{htmlMode-Vh5IglCQ.js → htmlMode-tUKJaA3o.js} +1 -1
- package/studio/web/assets/index-72cEH474.css +1 -0
- package/studio/web/assets/index-BuqFjN5K.js +828 -0
- package/studio/web/assets/index.lazy-CcpfQFY_.js +2 -0
- package/studio/web/assets/{info-A6RAGUB7-o49TEbj-.js → info-A6RAGUB7-BhzBHe4U.js} +1 -1
- package/studio/web/assets/{infoDiagram-VRGFBTTK-Cjhh7yzZ.js → infoDiagram-VRGFBTTK-B0J1132p.js} +1 -1
- package/studio/web/assets/{ishikawaDiagram-OU5B5YK6-C63aXRmh.js → ishikawaDiagram-OU5B5YK6-BNk0tuQx.js} +1 -1
- package/studio/web/assets/{javascript-5VIolqf7.js → javascript-BZ_uRGHl.js} +1 -1
- package/studio/web/assets/{journeyDiagram-ZHPQQLJL-D_o0P5Mu.js → journeyDiagram-ZHPQQLJL-Be79RMQM.js} +1 -1
- package/studio/web/assets/json.worker-BQqXGI3X.js +65 -0
- package/studio/web/assets/{jsonMode-DG4zQraI.js → jsonMode-DgB-aFMj.js} +1 -1
- package/studio/web/assets/{kanban-definition-PNTS6WVX-CTI9SSxY.js → kanban-definition-PNTS6WVX-4uuoQP9W.js} +1 -1
- package/studio/web/assets/{languageServices-t5FBpX-i.js → languageServices-BleuBw17.js} +1 -1
- package/studio/web/assets/localStorageKeys-etzFY_xV.js +1 -0
- package/studio/web/assets/{lspLanguageFeatures-DgVnNPh-.js → lspLanguageFeatures-ByVYLSsQ.js} +1 -1
- package/studio/web/assets/{mermaid-parser.core-C2lcH_jP.js → mermaid-parser.core-V1UYMeXV.js} +3 -3
- package/studio/web/assets/{mermaid.core-BG5bjfIG.js → mermaid.core-1PX6R49U.js} +4 -4
- package/studio/web/assets/{mindmap-definition-NLK3R4M7-BK7ho6JD.js → mindmap-definition-NLK3R4M7-C70Ctm0J.js} +1 -1
- package/studio/web/assets/notifications-D1Nf-Edn.js +1 -0
- package/studio/web/assets/notifications-TuzN0IMi.js +1 -0
- package/studio/web/assets/{packet-AYTQ26CC-BHPy6KBV.js → packet-AYTQ26CC-DWbXHx5g.js} +1 -1
- package/studio/web/assets/{pegDiagram-GJSIUBJH-DzjwG3jO.js → pegDiagram-GJSIUBJH-d6z1cc7W.js} +1 -1
- package/studio/web/assets/{pie-WAS4IAKB-D0cP-DTU.js → pie-WAS4IAKB-DjoTm3yr.js} +1 -1
- package/studio/web/assets/{pieDiagram-5QR66LMP-C_hOP9Lv.js → pieDiagram-5QR66LMP-Dz3RHpDA.js} +1 -1
- package/studio/web/assets/profile-D7C7Lupz.js +1 -0
- package/studio/web/assets/{quadrantDiagram-O4NWA36T-CZW8PbV1.js → quadrantDiagram-O4NWA36T-CIF5Qkou.js} +1 -1
- package/studio/web/assets/{radar-RG4KPBEZ-C8fk0U81.js → radar-RG4KPBEZ-CVBgjCOj.js} +1 -1
- package/studio/web/assets/{railroad-74A4TZTK-B60FWm17.js → railroad-74A4TZTK-BdhXw10Q.js} +1 -1
- package/studio/web/assets/railroad-abnf-HS5TGJTU-BcoQPN39.js +1 -0
- package/studio/web/assets/railroad-ebnf-LZEXJU2U-B1Lke2JN.js +1 -0
- package/studio/web/assets/railroad-peg-WCYAUIDC-D-oJFkiV.js +1 -0
- package/studio/web/assets/{railroadDiagram-XR7U4H2S-DyyNmgbU.js → railroadDiagram-XR7U4H2S-y3Jv2U6N.js} +1 -1
- package/studio/web/assets/regions-nGQCm-v8.js +1 -0
- package/studio/web/assets/register-C3x9Ze6P.js +909 -0
- package/studio/web/assets/register-DjpsE8to.css +1 -0
- package/studio/web/assets/{requirementDiagram-PLB6GJNP-C4H-mLKu.js → requirementDiagram-PLB6GJNP-ChINZEmu.js} +1 -1
- package/studio/web/assets/restartGate-nHAFLDh-.js +1 -0
- package/studio/web/assets/{sankeyDiagram-IPEJSGJF-DHDyga4c.js → sankeyDiagram-IPEJSGJF-D9AyLce2.js} +1 -1
- package/studio/web/assets/{sequenceDiagram-PO4LG4MO-CdNcHXRI.js → sequenceDiagram-PO4LG4MO-hfk4aIEm.js} +1 -1
- package/studio/web/assets/{setComponentFile-z4-qJCbA.js → setComponentFile-DpjrRqeG.js} +1 -1
- package/studio/web/assets/{setup-CS50uJ4x.js → setup-CGiKLZ9e.js} +2 -2
- package/studio/web/assets/{stateDiagram-v2-GCMORJYK-CpKuXq15.js → stateDiagram-v2-GCMORJYK-Bs8RIb9K.js} +1 -1
- package/studio/web/assets/status-CGJwrNPH.js +61 -0
- package/studio/web/assets/{swimlanes-2SLR337P-NRHa2D8I.js → swimlanes-2SLR337P-B52xrf6t.js} +1 -1
- package/studio/web/assets/swimlanesDiagram-TC7HE7FX-DfN7BAP5.js +8 -0
- package/studio/web/assets/tabs-BCTQzoQK.js +1 -0
- package/studio/web/assets/{timeline-definition-EJHVYXUP-D39Y-xpq.js → timeline-definition-EJHVYXUP-DJSITpQX.js} +1 -1
- package/studio/web/assets/toggleHighContrast-C-Ne6P7T.js +51 -0
- package/studio/web/assets/toggleHighContrast-n7clI0si.css +1 -0
- package/studio/web/assets/{treeView-Q6P3EWNA-CRWR9OqD.js → treeView-Q6P3EWNA-C-epdxRo.js} +1 -1
- package/studio/web/assets/{treemap-WGGIJYW6-ZfXyJgLO.js → treemap-WGGIJYW6-DOVyp_zw.js} +1 -1
- package/studio/web/assets/{ts.worker-DDMUrpqh.js → ts.worker-Clm12uY6.js} +131 -131
- package/studio/web/assets/{tsMode-D83Gg3cq.js → tsMode-CSreYTys.js} +1 -1
- package/studio/web/assets/{typescript-Dfp-GrNq.js → typescript-MnxBk89W.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-BKetlINf.js → useEntityRestURL-TNrh77-s.js} +1 -1
- package/studio/web/assets/{usecaseDiagram-POWQR4AR-jf-zIDZ_.js → usecaseDiagram-POWQR4AR-B6252cbq.js} +1 -1
- package/studio/web/assets/{vendor-core-BdkdbUek.js → vendor-core-6LhfJlRC.js} +1 -1
- package/studio/web/assets/vendor-datadog-ozKf-pts.js +6 -0
- package/studio/web/assets/{vendor-react-izWchTRL.js → vendor-react-BOliS0BL.js} +1 -1
- package/studio/web/assets/vendor-tanstack-Bntfka-M.js +2 -0
- package/studio/web/assets/vendor-ui-9EYYDgoN.js +41 -0
- package/studio/web/assets/{vennDiagram-UO4OBE2U-BKYAZUh_.js → vennDiagram-UO4OBE2U-DZTsDA4k.js} +1 -1
- package/studio/web/assets/{wardley-WFR3VGLG-BO7f36ci.js → wardley-WFR3VGLG-Cfqoe6z4.js} +1 -1
- package/studio/web/assets/{wardleyDiagram-VNRHLVJA-Djb0JDQc.js → wardleyDiagram-VNRHLVJA-qu_f7Tvl.js} +1 -1
- package/studio/web/assets/{workers-CCvmDLH3.js → workers-BfZlUNVI.js} +3 -3
- package/studio/web/assets/{xml-06pZp00U.js → xml-BHd6qGH7.js} +1 -1
- package/studio/web/assets/{xychartDiagram-PMCCYNJV-BQh9ubvb.js → xychartDiagram-PMCCYNJV-UrOKRsmR.js} +1 -1
- package/studio/web/assets/{yaml-iSnWMVlV.js → yaml-C53R8OQq.js} +1 -1
- package/studio/web/assets/{yaml.worker-DAK2o_H1.js → yaml.worker-D9JekGdp.js} +183 -183
- package/studio/web/index.html +15 -15
- package/utility/operation_authorization.ts +27 -7
- package/utility/signalling.ts +3 -0
- package/validation/searchValidator.ts +22 -13
- package/studio/web/assets/FloatingChat-D4560T0p.js +0 -23
- package/studio/web/assets/alertDialog-UrbiMgQt.js +0 -1
- package/studio/web/assets/apiToken-BEvbFPMc.js +0 -1
- package/studio/web/assets/applications-CSUVTH4E.js +0 -296
- package/studio/web/assets/architecture-7GRP2DOG-11l4Ny1l.js +0 -1
- package/studio/web/assets/authStore-5jSS5pTW.js +0 -3
- package/studio/web/assets/channel-BDtouPd6.js +0 -1
- package/studio/web/assets/dialog-DTepachC.js +0 -1
- package/studio/web/assets/editor.worker-CkkvdXvO.js +0 -33
- package/studio/web/assets/eventmodeling-NTZA5JFV-CFb03e0o.js +0 -1
- package/studio/web/assets/flowDiagram-KWPJA3E3-Cidi00_Y.js +0 -1
- package/studio/web/assets/index-CPoDzQTe.css +0 -1
- package/studio/web/assets/index-DGRAzvO4.js +0 -824
- package/studio/web/assets/index.lazy-vgSic7et.js +0 -2
- package/studio/web/assets/json.worker-CSi2hF-a.js +0 -65
- package/studio/web/assets/notifications-BeWcX04m.js +0 -1
- package/studio/web/assets/notifications-DUGWrWoO.js +0 -1
- package/studio/web/assets/profile-BvUEDBzW.js +0 -1
- package/studio/web/assets/railroad-abnf-HS5TGJTU-CLMVZvH0.js +0 -1
- package/studio/web/assets/railroad-ebnf-LZEXJU2U--tlwFoVB.js +0 -1
- package/studio/web/assets/railroad-peg-WCYAUIDC-Dpuslblm.js +0 -1
- package/studio/web/assets/regions-CB-_2m7M.js +0 -1
- package/studio/web/assets/register-1w1Zr3b2.js +0 -848
- package/studio/web/assets/register-jjEx9u7D.css +0 -1
- package/studio/web/assets/setLocalStorage-D_kflv4U.js +0 -1
- package/studio/web/assets/status-CI0wOdOE.js +0 -61
- package/studio/web/assets/swimlanesDiagram-TC7HE7FX-BBk4t4hO.js +0 -8
- package/studio/web/assets/tabs-CnQ_zenf.js +0 -1
- package/studio/web/assets/toggleHighContrast--ixms01u.css +0 -1
- package/studio/web/assets/toggleHighContrast-BsAE4jPe.js +0 -49
- package/studio/web/assets/useLocalStorage-BYoZF60G.js +0 -1
- package/studio/web/assets/vendor-datadog-fCQhBHGk.js +0 -6
- package/studio/web/assets/vendor-tanstack-CQ1aRjVa.js +0 -2
- package/studio/web/assets/vendor-ui-aE1tHcW7.js +0 -41
- package/studio/web/assets/x-vYntCxm4.js +0 -1
package/resources/Table.ts
CHANGED
|
@@ -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?():
|
|
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?:
|
|
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
|
|
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<
|
|
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(
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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;
|