@synnaxlabs/client 0.56.1 → 0.57.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/dist/client-B08VDLSA.js +14463 -0
- package/dist/client-B08VDLSA.js.map +1 -0
- package/dist/client.js +1128 -14876
- package/dist/client.js.map +1 -0
- package/dist/src/access/client.d.ts +10 -1
- package/dist/src/access/client.d.ts.map +1 -1
- package/dist/src/access/enforce.d.ts.map +1 -1
- package/dist/src/access/external.d.ts +1 -0
- package/dist/src/access/external.d.ts.map +1 -1
- package/dist/src/access/granted.d.ts +39 -0
- package/dist/src/access/granted.d.ts.map +1 -0
- package/dist/src/access/policy/client.d.ts +136 -147
- package/dist/src/access/policy/client.d.ts.map +1 -1
- package/dist/src/access/policy/types.gen.d.ts +15 -68
- package/dist/src/access/policy/types.gen.d.ts.map +1 -1
- package/dist/src/access/role/client.d.ts +77 -58
- package/dist/src/access/role/client.d.ts.map +1 -1
- package/dist/src/access/role/types.gen.d.ts +8 -14
- package/dist/src/access/role/types.gen.d.ts.map +1 -1
- package/dist/src/actions/actions.d.ts +5 -6
- package/dist/src/actions/actions.d.ts.map +1 -1
- package/dist/src/actions/controller.d.ts +194 -0
- package/dist/src/actions/controller.d.ts.map +1 -0
- package/dist/src/actions/external.d.ts +1 -0
- package/dist/src/actions/external.d.ts.map +1 -1
- package/dist/src/arc/actions.d.ts +575 -0
- package/dist/src/arc/actions.d.ts.map +1 -0
- package/dist/src/arc/actions.gen.d.ts +1439 -0
- package/dist/src/arc/actions.gen.d.ts.map +1 -0
- package/dist/src/arc/client.d.ts +97 -13
- package/dist/src/arc/client.d.ts.map +1 -1
- package/dist/src/arc/compiler/types.gen.d.ts +3 -3
- package/dist/src/arc/compiler/types.gen.d.ts.map +1 -1
- package/dist/src/arc/external.d.ts +2 -0
- package/dist/src/arc/external.d.ts.map +1 -1
- package/dist/src/arc/graph/types.gen.d.ts +45 -597
- package/dist/src/arc/graph/types.gen.d.ts.map +1 -1
- package/dist/src/arc/ir/index.d.ts +1 -1
- package/dist/src/arc/ir/index.d.ts.map +1 -1
- package/dist/src/arc/ir/ir.d.ts +2 -0
- package/dist/src/arc/ir/ir.d.ts.map +1 -0
- package/dist/src/arc/ir/types.gen.d.ts +58 -2807
- package/dist/src/arc/ir/types.gen.d.ts.map +1 -1
- package/dist/src/arc/module/types.gen.d.ts +18 -1090
- package/dist/src/arc/module/types.gen.d.ts.map +1 -1
- package/dist/src/arc/program/types.gen.d.ts +18 -1090
- package/dist/src/arc/program/types.gen.d.ts.map +1 -1
- package/dist/src/arc/text/types.gen.d.ts +54 -1
- package/dist/src/arc/text/types.gen.d.ts.map +1 -1
- package/dist/src/arc/types/types.gen.d.ts +35 -153
- package/dist/src/arc/types/types.gen.d.ts.map +1 -1
- package/dist/src/arc/types.gen.d.ts +98 -2278
- package/dist/src/arc/types.gen.d.ts.map +1 -1
- package/dist/src/auth/auth.d.ts +14 -2
- package/dist/src/auth/auth.d.ts.map +1 -1
- package/dist/src/channel/client.d.ts +113 -75
- package/dist/src/channel/client.d.ts.map +1 -1
- package/dist/src/channel/external.d.ts +0 -1
- package/dist/src/channel/external.d.ts.map +1 -1
- package/dist/src/channel/payload.d.ts +65 -56
- package/dist/src/channel/payload.d.ts.map +1 -1
- package/dist/src/channel/types.gen.d.ts +26 -73
- package/dist/src/channel/types.gen.d.ts.map +1 -1
- package/dist/src/channel/writer.d.ts +1 -3
- package/dist/src/channel/writer.d.ts.map +1 -1
- package/dist/src/client.d.ts +55 -27
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/connection/client.d.ts +146 -0
- package/dist/src/connection/client.d.ts.map +1 -0
- package/dist/src/connection/external.d.ts +3 -0
- package/dist/src/connection/external.d.ts.map +1 -0
- package/dist/src/connection/index.d.ts +1 -1
- package/dist/src/connection/index.d.ts.map +1 -1
- package/dist/src/connection/status.d.ts +252 -0
- package/dist/src/connection/status.d.ts.map +1 -0
- package/dist/src/control/client.d.ts +31 -7
- package/dist/src/control/client.d.ts.map +1 -1
- package/dist/src/control/state.d.ts +47 -16
- package/dist/src/control/state.d.ts.map +1 -1
- package/dist/src/device/client.d.ts +59 -17
- package/dist/src/device/client.d.ts.map +1 -1
- package/dist/src/device/types.gen.d.ts +17 -67
- package/dist/src/device/types.gen.d.ts.map +1 -1
- package/dist/src/errors.d.ts +17 -20
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/ethercat/external.d.ts +2 -0
- package/dist/src/ethercat/external.d.ts.map +1 -0
- package/dist/src/ethercat/index.d.ts +2 -0
- package/dist/src/ethercat/index.d.ts.map +1 -0
- package/dist/src/ethercat/types.gen.d.ts +243 -0
- package/dist/src/ethercat/types.gen.d.ts.map +1 -0
- package/dist/src/framer/adapter.d.ts +8 -5
- package/dist/src/framer/adapter.d.ts.map +1 -1
- package/dist/src/framer/benchutil.d.ts +19 -0
- package/dist/src/framer/benchutil.d.ts.map +1 -0
- package/dist/src/framer/cache/cache.d.ts +38 -0
- package/dist/src/framer/cache/cache.d.ts.map +1 -0
- package/dist/src/framer/cache/dynamic.d.ts +71 -0
- package/dist/src/framer/cache/dynamic.d.ts.map +1 -0
- package/dist/src/framer/cache/reader.d.ts +43 -0
- package/dist/src/framer/cache/reader.d.ts.map +1 -0
- package/dist/src/framer/cache/static.d.ts +66 -0
- package/dist/src/framer/cache/static.d.ts.map +1 -0
- package/dist/src/framer/cache/streamer.d.ts +86 -0
- package/dist/src/framer/cache/streamer.d.ts.map +1 -0
- package/dist/src/framer/cache/transform.d.ts +18 -0
- package/dist/src/framer/cache/transform.d.ts.map +1 -0
- package/dist/src/framer/cache/unary.d.ts +37 -0
- package/dist/src/framer/cache/unary.d.ts.map +1 -0
- package/dist/src/framer/client.d.ts +35 -14
- package/dist/src/framer/client.d.ts.map +1 -1
- package/dist/src/framer/codec.d.ts.map +1 -1
- package/dist/src/framer/deleter.d.ts +3 -2
- package/dist/src/framer/deleter.d.ts.map +1 -1
- package/dist/src/framer/external.d.ts +6 -0
- package/dist/src/framer/external.d.ts.map +1 -1
- package/dist/src/framer/feed.d.ts +40 -0
- package/dist/src/framer/feed.d.ts.map +1 -0
- package/dist/src/framer/frame.d.ts +16 -20
- package/dist/src/framer/frame.d.ts.map +1 -1
- package/dist/src/framer/hardened.d.ts +70 -0
- package/dist/src/framer/hardened.d.ts.map +1 -0
- package/dist/src/framer/iterator.d.ts +36 -43
- package/dist/src/framer/iterator.d.ts.map +1 -1
- package/dist/src/framer/reader.d.ts +3 -2
- package/dist/src/framer/reader.d.ts.map +1 -1
- package/dist/src/framer/streamer.d.ts +132 -205
- package/dist/src/framer/streamer.d.ts.map +1 -1
- package/dist/src/framer/writer.d.ts +130 -161
- package/dist/src/framer/writer.d.ts.map +1 -1
- package/dist/src/group/client.d.ts +66 -9
- package/dist/src/group/client.d.ts.map +1 -1
- package/dist/src/group/types.gen.d.ts +2 -2
- package/dist/src/http/external.d.ts +2 -0
- package/dist/src/http/external.d.ts.map +1 -0
- package/dist/src/http/index.d.ts +2 -0
- package/dist/src/http/index.d.ts.map +1 -0
- package/dist/src/http/types.gen.d.ts +451 -0
- package/dist/src/http/types.gen.d.ts.map +1 -0
- package/dist/src/imex/client.d.ts +88 -0
- package/dist/src/imex/client.d.ts.map +1 -0
- package/dist/src/imex/index.d.ts +2 -0
- package/dist/src/imex/index.d.ts.map +1 -0
- package/dist/src/index.d.ts +18 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/label/client.d.ts +55 -36
- package/dist/src/label/client.d.ts.map +1 -1
- package/dist/src/label/external.d.ts +2 -0
- package/dist/src/label/external.d.ts.map +1 -1
- package/dist/src/label/payload.d.ts +4 -46
- package/dist/src/label/payload.d.ts.map +1 -1
- package/dist/src/label/store.d.ts +8 -0
- package/dist/src/label/store.d.ts.map +1 -0
- package/dist/src/label/types.gen.d.ts +45 -0
- package/dist/src/label/types.gen.d.ts.map +1 -0
- package/dist/src/labjack/external.d.ts +2 -0
- package/dist/src/labjack/external.d.ts.map +1 -0
- package/dist/src/labjack/index.d.ts +2 -0
- package/dist/src/labjack/index.d.ts.map +1 -0
- package/dist/src/labjack/types.gen.d.ts +442 -0
- package/dist/src/labjack/types.gen.d.ts.map +1 -0
- package/dist/src/lineplot/actions.d.ts +914 -0
- package/dist/src/lineplot/actions.d.ts.map +1 -0
- package/dist/src/lineplot/actions.gen.d.ts +4208 -0
- package/dist/src/lineplot/actions.gen.d.ts.map +1 -0
- package/dist/src/lineplot/client.d.ts +66 -16
- package/dist/src/lineplot/client.d.ts.map +1 -1
- package/dist/src/lineplot/external.d.ts +3 -0
- package/dist/src/lineplot/external.d.ts.map +1 -1
- package/dist/src/lineplot/line.d.ts +16 -0
- package/dist/src/lineplot/line.d.ts.map +1 -0
- package/dist/src/lineplot/types.gen.d.ts +837 -14
- package/dist/src/lineplot/types.gen.d.ts.map +1 -1
- package/dist/src/log/actions.d.ts +271 -0
- package/dist/src/log/actions.d.ts.map +1 -0
- package/dist/src/log/actions.gen.d.ts +1529 -0
- package/dist/src/log/actions.gen.d.ts.map +1 -0
- package/dist/src/log/client.d.ts +66 -16
- package/dist/src/log/client.d.ts.map +1 -1
- package/dist/src/log/external.d.ts +2 -0
- package/dist/src/log/external.d.ts.map +1 -1
- package/dist/src/log/types.gen.d.ts +139 -14
- package/dist/src/log/types.gen.d.ts.map +1 -1
- package/dist/src/modbus/external.d.ts +2 -0
- package/dist/src/modbus/external.d.ts.map +1 -0
- package/dist/src/modbus/index.d.ts +2 -0
- package/dist/src/modbus/index.d.ts.map +1 -0
- package/dist/src/modbus/types.gen.d.ts +280 -0
- package/dist/src/modbus/types.gen.d.ts.map +1 -0
- package/dist/src/ni/external.d.ts +2 -0
- package/dist/src/ni/external.d.ts.map +1 -0
- package/dist/src/ni/index.d.ts +2 -0
- package/dist/src/ni/index.d.ts.map +1 -0
- package/dist/src/ni/types.gen.d.ts +21029 -0
- package/dist/src/ni/types.gen.d.ts.map +1 -0
- package/dist/src/node/index.d.ts +2 -0
- package/dist/src/node/index.d.ts.map +1 -0
- package/dist/src/node/types.gen.d.ts +4 -0
- package/dist/src/node/types.gen.d.ts.map +1 -0
- package/dist/src/ontology/client.d.ts +314 -67
- package/dist/src/ontology/client.d.ts.map +1 -1
- package/dist/src/ontology/external.d.ts +1 -0
- package/dist/src/ontology/external.d.ts.map +1 -1
- package/dist/src/ontology/payload.d.ts +60 -18
- package/dist/src/ontology/payload.d.ts.map +1 -1
- package/dist/src/ontology/store.d.ts +32 -0
- package/dist/src/ontology/store.d.ts.map +1 -0
- package/dist/src/ontology/types.gen.d.ts +3 -2
- package/dist/src/ontology/types.gen.d.ts.map +1 -1
- package/dist/src/ontology/writer.d.ts +16 -10
- package/dist/src/ontology/writer.d.ts.map +1 -1
- package/dist/src/opcua/external.d.ts +2 -0
- package/dist/src/opcua/external.d.ts.map +1 -0
- package/dist/src/opcua/index.d.ts +2 -0
- package/dist/src/opcua/index.d.ts.map +1 -0
- package/dist/src/opcua/types.gen.d.ts +82 -0
- package/dist/src/opcua/types.gen.d.ts.map +1 -0
- package/dist/src/pagerduty/external.d.ts +2 -0
- package/dist/src/pagerduty/external.d.ts.map +1 -0
- package/dist/src/pagerduty/index.d.ts +2 -0
- package/dist/src/pagerduty/index.d.ts.map +1 -0
- package/dist/src/pagerduty/types.gen.d.ts +30 -0
- package/dist/src/pagerduty/types.gen.d.ts.map +1 -0
- package/dist/src/panel/actions.d.ts +273 -0
- package/dist/src/panel/actions.d.ts.map +1 -0
- package/dist/src/panel/actions.gen.d.ts +1255 -0
- package/dist/src/panel/actions.gen.d.ts.map +1 -0
- package/dist/src/panel/client.d.ts +148 -0
- package/dist/src/panel/client.d.ts.map +1 -0
- package/dist/src/panel/external.d.ts +6 -0
- package/dist/src/panel/external.d.ts.map +1 -0
- package/dist/src/panel/index.d.ts +2 -0
- package/dist/src/panel/index.d.ts.map +1 -0
- package/dist/src/panel/tree.d.ts +50 -0
- package/dist/src/panel/tree.d.ts.map +1 -0
- package/dist/src/panel/types.gen.d.ts +370 -0
- package/dist/src/panel/types.gen.d.ts.map +1 -0
- package/dist/src/project/client.d.ts +89 -0
- package/dist/src/project/client.d.ts.map +1 -0
- package/dist/src/project/external.d.ts.map +1 -0
- package/dist/src/project/index.d.ts +2 -0
- package/dist/src/project/index.d.ts.map +1 -0
- package/dist/src/project/types.gen.d.ts +27 -0
- package/dist/src/project/types.gen.d.ts.map +1 -0
- package/dist/src/query/cache.d.ts +132 -0
- package/dist/src/query/cache.d.ts.map +1 -0
- package/dist/src/query/deleted.d.ts +20 -0
- package/dist/src/query/deleted.d.ts.map +1 -0
- package/dist/src/query/derived.d.ts +33 -0
- package/dist/src/query/derived.d.ts.map +1 -0
- package/dist/src/query/external.d.ts +10 -0
- package/dist/src/query/external.d.ts.map +1 -0
- package/dist/src/query/index.d.ts +2 -0
- package/dist/src/query/index.d.ts.map +1 -0
- package/dist/src/query/query.d.ts +235 -0
- package/dist/src/query/query.d.ts.map +1 -0
- package/dist/src/query/retriever.d.ts +135 -0
- package/dist/src/query/retriever.d.ts.map +1 -0
- package/dist/src/query/streamer.d.ts +67 -0
- package/dist/src/query/streamer.d.ts.map +1 -0
- package/dist/src/query/table.d.ts +219 -0
- package/dist/src/query/table.d.ts.map +1 -0
- package/dist/src/query/types.d.ts +35 -0
- package/dist/src/query/types.d.ts.map +1 -0
- package/dist/src/query/write.d.ts +21 -0
- package/dist/src/query/write.d.ts.map +1 -0
- package/dist/src/rack/client.d.ts +58 -20
- package/dist/src/rack/client.d.ts.map +1 -1
- package/dist/src/rack/types.gen.d.ts +11 -12
- package/dist/src/rack/types.gen.d.ts.map +1 -1
- package/dist/src/range/alias/types.gen.d.ts +2 -3
- package/dist/src/range/alias/types.gen.d.ts.map +1 -1
- package/dist/src/ranger/client.d.ts +138 -27
- package/dist/src/ranger/client.d.ts.map +1 -1
- package/dist/src/ranger/kv/client.d.ts +7 -4
- package/dist/src/ranger/kv/client.d.ts.map +1 -1
- package/dist/src/ranger/payload.d.ts +3 -0
- package/dist/src/ranger/payload.d.ts.map +1 -1
- package/dist/src/ranger/types.gen.d.ts +45 -101
- package/dist/src/ranger/types.gen.d.ts.map +1 -1
- package/dist/src/ranger/writer.d.ts +2 -74
- package/dist/src/ranger/writer.d.ts.map +1 -1
- package/dist/src/schematic/actions.d.ts +90 -33
- package/dist/src/schematic/actions.d.ts.map +1 -1
- package/dist/src/schematic/actions.gen.d.ts +207 -114
- package/dist/src/schematic/actions.gen.d.ts.map +1 -1
- package/dist/src/schematic/client.d.ts +70 -68
- package/dist/src/schematic/client.d.ts.map +1 -1
- package/dist/src/schematic/symbol/client.d.ts +133 -27
- package/dist/src/schematic/symbol/client.d.ts.map +1 -1
- package/dist/src/schematic/symbol/types.gen.d.ts +258 -153
- package/dist/src/schematic/symbol/types.gen.d.ts.map +1 -1
- package/dist/src/schematic/types.gen.d.ts +17 -96
- package/dist/src/schematic/types.gen.d.ts.map +1 -1
- package/dist/src/status/client.d.ts +69 -14
- package/dist/src/status/client.d.ts.map +1 -1
- package/dist/src/status/external.d.ts +2 -0
- package/dist/src/status/external.d.ts.map +1 -1
- package/dist/src/status/payload.d.ts +2 -36
- package/dist/src/status/payload.d.ts.map +1 -1
- package/dist/src/status/status.d.ts +65 -0
- package/dist/src/status/status.d.ts.map +1 -0
- package/dist/src/status/types.gen.d.ts +58 -0
- package/dist/src/status/types.gen.d.ts.map +1 -0
- package/dist/src/table/actions.d.ts +72 -13
- package/dist/src/table/actions.d.ts.map +1 -1
- package/dist/src/table/actions.gen.d.ts +221 -137
- package/dist/src/table/actions.gen.d.ts.map +1 -1
- package/dist/src/table/client.d.ts +64 -40
- package/dist/src/table/client.d.ts.map +1 -1
- package/dist/src/table/types.gen.d.ts +20 -55
- package/dist/src/table/types.gen.d.ts.map +1 -1
- package/dist/src/task/client.d.ts +149 -28
- package/dist/src/task/client.d.ts.map +1 -1
- package/dist/src/task/types.gen.d.ts +71 -89
- package/dist/src/task/types.gen.d.ts.map +1 -1
- package/dist/src/testutil/access.d.ts +27 -1
- package/dist/src/testutil/access.d.ts.map +1 -1
- package/dist/src/testutil/channels.d.ts +2 -0
- package/dist/src/testutil/channels.d.ts.map +1 -1
- package/dist/src/testutil/client.d.ts +5 -0
- package/dist/src/testutil/client.d.ts.map +1 -1
- package/dist/src/testutil/connection.d.ts +10 -0
- package/dist/src/testutil/connection.d.ts.map +1 -0
- package/dist/src/testutil/index.d.ts +9 -0
- package/dist/src/testutil/index.d.ts.map +1 -0
- package/dist/src/testutil/panels.d.ts +8 -0
- package/dist/src/testutil/panels.d.ts.map +1 -0
- package/dist/src/testutil/proxy.d.ts +29 -0
- package/dist/src/testutil/proxy.d.ts.map +1 -0
- package/dist/src/testutil/query.d.ts +7 -0
- package/dist/src/testutil/query.d.ts.map +1 -0
- package/dist/src/testutil/telem.d.ts +4 -0
- package/dist/src/testutil/telem.d.ts.map +1 -1
- package/dist/src/transport.d.ts +10 -4
- package/dist/src/transport.d.ts.map +1 -1
- package/dist/src/user/client.d.ts +29 -28
- package/dist/src/user/client.d.ts.map +1 -1
- package/dist/src/user/external.d.ts +1 -1
- package/dist/src/user/external.d.ts.map +1 -1
- package/dist/src/user/types.gen.d.ts +4 -4
- package/dist/src/util/retrieve.d.ts +2 -2
- package/dist/src/util/retrieve.d.ts.map +1 -1
- package/dist/src/util/telem.d.ts.map +1 -1
- package/dist/src/view/client.d.ts +30 -14
- package/dist/src/view/client.d.ts.map +1 -1
- package/dist/src/view/types.gen.d.ts +7 -15
- package/dist/src/view/types.gen.d.ts.map +1 -1
- package/dist/testutil.js +189 -0
- package/dist/testutil.js.map +1 -0
- package/package.json +40 -17
- package/src/access/client.ts +14 -3
- package/src/access/enforce.ts +0 -3
- package/src/access/external.ts +1 -0
- package/src/access/granted.ts +114 -0
- package/src/access/policy/client.ts +135 -43
- package/src/access/policy/types.gen.ts +9 -11
- package/src/access/role/client.ts +160 -38
- package/src/access/role/types.gen.ts +8 -12
- package/src/actions/actions.ts +5 -6
- package/src/actions/controller.ts +626 -0
- package/src/actions/external.ts +11 -0
- package/src/arc/actions.gen.ts +313 -0
- package/src/arc/actions.ts +192 -0
- package/src/arc/client.ts +424 -41
- package/src/arc/compiler/types.gen.ts +5 -6
- package/src/arc/external.ts +2 -0
- package/src/arc/graph/types.gen.ts +24 -22
- package/src/arc/ir/index.ts +1 -1
- package/{eslint.config.ts → src/arc/ir/ir.ts} +1 -4
- package/src/arc/ir/types.gen.ts +42 -41
- package/src/arc/text/types.gen.ts +21 -2
- package/src/arc/types/types.gen.ts +40 -32
- package/src/arc/types.gen.ts +31 -29
- package/src/auth/auth.ts +40 -8
- package/src/channel/client.ts +529 -132
- package/src/channel/external.ts +0 -1
- package/src/channel/payload.ts +30 -3
- package/src/channel/types.gen.ts +37 -48
- package/src/channel/writer.ts +2 -11
- package/src/client.ts +215 -91
- package/src/connection/client.ts +495 -0
- package/src/connection/external.ts +34 -0
- package/src/connection/index.ts +1 -1
- package/src/connection/status.ts +442 -0
- package/src/control/client.ts +84 -12
- package/src/control/state.ts +17 -36
- package/src/device/client.ts +293 -41
- package/src/device/types.gen.ts +15 -26
- package/src/errors.ts +45 -22
- package/src/{workspace → ethercat}/external.ts +1 -2
- package/src/{workspace → ethercat}/index.ts +1 -1
- package/src/ethercat/types.gen.ts +151 -0
- package/src/framer/adapter.ts +60 -37
- package/src/framer/benchutil.ts +67 -0
- package/src/framer/cache/cache.ts +115 -0
- package/src/framer/cache/dynamic.ts +279 -0
- package/src/framer/cache/reader.ts +160 -0
- package/src/framer/cache/static.ts +215 -0
- package/src/framer/cache/streamer.ts +391 -0
- package/src/framer/cache/transform.ts +31 -0
- package/src/framer/cache/unary.ts +104 -0
- package/src/framer/client.ts +71 -43
- package/src/framer/codec.ts +55 -18
- package/src/framer/deleter.ts +4 -6
- package/src/framer/external.ts +10 -0
- package/src/framer/feed.ts +108 -0
- package/src/framer/frame.ts +13 -23
- package/src/framer/hardened.ts +283 -0
- package/src/framer/iterator.ts +31 -42
- package/src/framer/reader.ts +11 -11
- package/src/framer/streamer.ts +18 -178
- package/src/framer/writer.ts +39 -59
- package/src/group/client.ts +158 -23
- package/src/http/external.ts +10 -0
- package/src/{cluster → http}/index.ts +1 -1
- package/src/http/types.gen.ts +245 -0
- package/src/imex/client.ts +125 -0
- package/src/imex/index.ts +10 -0
- package/src/index.ts +18 -6
- package/src/label/client.ts +193 -54
- package/src/label/external.ts +2 -0
- package/src/label/payload.ts +4 -9
- package/src/label/store.ts +28 -0
- package/src/label/types.gen.ts +31 -0
- package/src/labjack/external.ts +10 -0
- package/src/labjack/index.ts +10 -0
- package/src/labjack/types.gen.ts +251 -0
- package/src/lineplot/actions.gen.ts +880 -0
- package/src/lineplot/actions.ts +535 -0
- package/src/lineplot/client.ts +224 -52
- package/src/lineplot/external.ts +3 -0
- package/src/lineplot/line.ts +80 -0
- package/src/lineplot/types.gen.ts +225 -12
- package/src/log/actions.gen.ts +474 -0
- package/src/log/actions.ts +242 -0
- package/src/log/client.ts +223 -52
- package/src/log/external.ts +2 -0
- package/src/log/types.gen.ts +45 -14
- package/src/modbus/external.ts +10 -0
- package/src/modbus/index.ts +10 -0
- package/src/modbus/types.gen.ts +185 -0
- package/src/ni/external.ts +10 -0
- package/src/ni/index.ts +10 -0
- package/src/ni/types.gen.ts +1632 -0
- package/src/node/index.ts +10 -0
- package/src/{cluster → node}/types.gen.ts +2 -2
- package/src/ontology/client.ts +408 -99
- package/src/ontology/external.ts +7 -0
- package/src/ontology/payload.ts +48 -7
- package/src/ontology/store.ts +83 -0
- package/src/ontology/types.gen.ts +2 -1
- package/src/ontology/writer.ts +1 -0
- package/src/opcua/external.ts +10 -0
- package/src/opcua/index.ts +10 -0
- package/src/opcua/types.gen.ts +65 -0
- package/src/pagerduty/external.ts +10 -0
- package/src/pagerduty/index.ts +10 -0
- package/src/pagerduty/types.gen.ts +41 -0
- package/src/panel/actions.gen.ts +267 -0
- package/src/panel/actions.ts +357 -0
- package/src/panel/client.ts +327 -0
- package/src/panel/external.ts +14 -0
- package/src/panel/index.ts +10 -0
- package/src/panel/tree.ts +163 -0
- package/src/panel/types.gen.ts +164 -0
- package/src/project/client.ts +273 -0
- package/src/project/external.ts +11 -0
- package/src/project/index.ts +10 -0
- package/src/project/types.gen.ts +40 -0
- package/src/query/cache.ts +326 -0
- package/src/query/deleted.ts +38 -0
- package/src/query/derived.ts +101 -0
- package/src/query/external.ts +51 -0
- package/src/query/index.ts +10 -0
- package/src/query/query.ts +865 -0
- package/src/query/retriever.ts +308 -0
- package/src/query/streamer.ts +207 -0
- package/src/query/table.ts +625 -0
- package/src/query/types.ts +54 -0
- package/src/query/write.ts +42 -0
- package/src/rack/client.ts +233 -45
- package/src/rack/types.gen.ts +15 -16
- package/src/range/alias/types.gen.ts +2 -3
- package/src/ranger/client.ts +581 -70
- package/src/ranger/kv/client.ts +52 -21
- package/src/ranger/payload.ts +3 -0
- package/src/ranger/types.gen.ts +21 -23
- package/src/ranger/writer.ts +4 -8
- package/src/schematic/actions.gen.ts +62 -53
- package/src/schematic/actions.ts +44 -42
- package/src/schematic/client.ts +226 -70
- package/src/schematic/symbol/client.ts +277 -48
- package/src/schematic/symbol/types.gen.ts +22 -34
- package/src/schematic/types.gen.ts +22 -29
- package/src/status/client.ts +321 -44
- package/src/status/external.ts +2 -0
- package/src/status/payload.ts +1 -23
- package/src/status/status.ts +241 -0
- package/src/status/types.gen.ts +108 -0
- package/src/table/actions.gen.ts +96 -63
- package/src/table/actions.ts +44 -19
- package/src/table/client.ts +220 -56
- package/src/table/types.gen.ts +24 -31
- package/src/task/client.ts +580 -101
- package/src/task/types.gen.ts +68 -75
- package/src/testutil/access.ts +102 -6
- package/src/testutil/channels.ts +29 -0
- package/src/testutil/client.ts +27 -2
- package/src/testutil/connection.ts +47 -0
- package/src/testutil/index.ts +17 -0
- package/src/testutil/panels.ts +26 -0
- package/src/testutil/proxy.ts +106 -0
- package/src/testutil/query.ts +28 -0
- package/src/testutil/telem.ts +17 -1
- package/src/transport.ts +18 -7
- package/src/user/client.ts +165 -64
- package/src/user/external.ts +1 -1
- package/src/user/types.gen.ts +5 -5
- package/src/util/retrieve.ts +4 -4
- package/src/util/telem.ts +0 -1
- package/src/view/client.ts +120 -37
- package/src/view/types.gen.ts +8 -11
- package/.turbo/turbo-build.log +0 -20
- package/.vscode/settings.json +0 -3
- package/CONTRIBUTING.md +0 -47
- package/dist/client.cjs +0 -118
- package/dist/eslint.config.d.ts +0 -4
- package/dist/eslint.config.d.ts.map +0 -1
- package/dist/src/access/enforce.spec.d.ts +0 -2
- package/dist/src/access/enforce.spec.d.ts.map +0 -1
- package/dist/src/access/policy/access.spec.d.ts +0 -2
- package/dist/src/access/policy/access.spec.d.ts.map +0 -1
- package/dist/src/access/role/role.spec.d.ts +0 -2
- package/dist/src/access/role/role.spec.d.ts.map +0 -1
- package/dist/src/actions/actions.spec.d.ts +0 -2
- package/dist/src/actions/actions.spec.d.ts.map +0 -1
- package/dist/src/arc/access.spec.d.ts +0 -2
- package/dist/src/arc/access.spec.d.ts.map +0 -1
- package/dist/src/arc/arc.spec.d.ts +0 -2
- package/dist/src/arc/arc.spec.d.ts.map +0 -1
- package/dist/src/arc/lsp.spec.d.ts +0 -2
- package/dist/src/arc/lsp.spec.d.ts.map +0 -1
- package/dist/src/auth/auth.spec.d.ts +0 -2
- package/dist/src/auth/auth.spec.d.ts.map +0 -1
- package/dist/src/channel/access.spec.d.ts +0 -2
- package/dist/src/channel/access.spec.d.ts.map +0 -1
- package/dist/src/channel/batchRetriever.spec.d.ts +0 -2
- package/dist/src/channel/batchRetriever.spec.d.ts.map +0 -1
- package/dist/src/channel/channel.spec.d.ts +0 -2
- package/dist/src/channel/channel.spec.d.ts.map +0 -1
- package/dist/src/channel/payload.spec.d.ts +0 -2
- package/dist/src/channel/payload.spec.d.ts.map +0 -1
- package/dist/src/channel/retriever.d.ts +0 -64
- package/dist/src/channel/retriever.d.ts.map +0 -1
- package/dist/src/cluster/index.d.ts +0 -2
- package/dist/src/cluster/index.d.ts.map +0 -1
- package/dist/src/cluster/types.gen.d.ts +0 -4
- package/dist/src/cluster/types.gen.d.ts.map +0 -1
- package/dist/src/connection/checker.d.ts +0 -86
- package/dist/src/connection/checker.d.ts.map +0 -1
- package/dist/src/connection/connection.spec.d.ts +0 -2
- package/dist/src/connection/connection.spec.d.ts.map +0 -1
- package/dist/src/connection.spec.d.ts +0 -2
- package/dist/src/connection.spec.d.ts.map +0 -1
- package/dist/src/control/state.spec.d.ts +0 -2
- package/dist/src/control/state.spec.d.ts.map +0 -1
- package/dist/src/device/access.spec.d.ts +0 -2
- package/dist/src/device/access.spec.d.ts.map +0 -1
- package/dist/src/device/device.spec.d.ts +0 -2
- package/dist/src/device/device.spec.d.ts.map +0 -1
- package/dist/src/device/payload.spec.d.ts +0 -2
- package/dist/src/device/payload.spec.d.ts.map +0 -1
- package/dist/src/errors.spec.d.ts +0 -2
- package/dist/src/errors.spec.d.ts.map +0 -1
- package/dist/src/framer/adapter.spec.d.ts +0 -2
- package/dist/src/framer/adapter.spec.d.ts.map +0 -1
- package/dist/src/framer/client.spec.d.ts +0 -2
- package/dist/src/framer/client.spec.d.ts.map +0 -1
- package/dist/src/framer/codec.spec.d.ts +0 -2
- package/dist/src/framer/codec.spec.d.ts.map +0 -1
- package/dist/src/framer/deleter.spec.d.ts +0 -2
- package/dist/src/framer/deleter.spec.d.ts.map +0 -1
- package/dist/src/framer/frame.spec.d.ts +0 -2
- package/dist/src/framer/frame.spec.d.ts.map +0 -1
- package/dist/src/framer/iterator.spec.d.ts +0 -2
- package/dist/src/framer/iterator.spec.d.ts.map +0 -1
- package/dist/src/framer/reader.spec.d.ts +0 -2
- package/dist/src/framer/reader.spec.d.ts.map +0 -1
- package/dist/src/framer/streamer.spec.d.ts +0 -2
- package/dist/src/framer/streamer.spec.d.ts.map +0 -1
- package/dist/src/framer/writer.spec.d.ts +0 -2
- package/dist/src/framer/writer.spec.d.ts.map +0 -1
- package/dist/src/group/access.spec.d.ts +0 -2
- package/dist/src/group/access.spec.d.ts.map +0 -1
- package/dist/src/group/group.spec.d.ts +0 -2
- package/dist/src/group/group.spec.d.ts.map +0 -1
- package/dist/src/label/access.spec.d.ts +0 -2
- package/dist/src/label/access.spec.d.ts.map +0 -1
- package/dist/src/label/label.spec.d.ts +0 -2
- package/dist/src/label/label.spec.d.ts.map +0 -1
- package/dist/src/lineplot/access.spec.d.ts +0 -2
- package/dist/src/lineplot/access.spec.d.ts.map +0 -1
- package/dist/src/lineplot/lineplot.spec.d.ts +0 -2
- package/dist/src/lineplot/lineplot.spec.d.ts.map +0 -1
- package/dist/src/log/access.spec.d.ts +0 -2
- package/dist/src/log/access.spec.d.ts.map +0 -1
- package/dist/src/log/log.spec.d.ts +0 -2
- package/dist/src/log/log.spec.d.ts.map +0 -1
- package/dist/src/ontology/ontology.spec.d.ts +0 -2
- package/dist/src/ontology/ontology.spec.d.ts.map +0 -1
- package/dist/src/rack/access.spec.d.ts +0 -2
- package/dist/src/rack/access.spec.d.ts.map +0 -1
- package/dist/src/rack/rack.spec.d.ts +0 -2
- package/dist/src/rack/rack.spec.d.ts.map +0 -1
- package/dist/src/ranger/access.spec.d.ts +0 -2
- package/dist/src/ranger/access.spec.d.ts.map +0 -1
- package/dist/src/ranger/alias/payload.spec.d.ts +0 -2
- package/dist/src/ranger/alias/payload.spec.d.ts.map +0 -1
- package/dist/src/ranger/ranger.spec.d.ts +0 -2
- package/dist/src/ranger/ranger.spec.d.ts.map +0 -1
- package/dist/src/schematic/access.spec.d.ts +0 -2
- package/dist/src/schematic/access.spec.d.ts.map +0 -1
- package/dist/src/schematic/actions.spec.d.ts +0 -2
- package/dist/src/schematic/actions.spec.d.ts.map +0 -1
- package/dist/src/schematic/schematic.spec.d.ts +0 -2
- package/dist/src/schematic/schematic.spec.d.ts.map +0 -1
- package/dist/src/schematic/symbol/access.spec.d.ts +0 -2
- package/dist/src/schematic/symbol/access.spec.d.ts.map +0 -1
- package/dist/src/schematic/symbol/client.spec.d.ts +0 -2
- package/dist/src/schematic/symbol/client.spec.d.ts.map +0 -1
- package/dist/src/status/access.spec.d.ts +0 -2
- package/dist/src/status/access.spec.d.ts.map +0 -1
- package/dist/src/status/status.spec.d.ts +0 -2
- package/dist/src/status/status.spec.d.ts.map +0 -1
- package/dist/src/table/access.spec.d.ts +0 -2
- package/dist/src/table/access.spec.d.ts.map +0 -1
- package/dist/src/table/table.spec.d.ts +0 -2
- package/dist/src/table/table.spec.d.ts.map +0 -1
- package/dist/src/task/access.spec.d.ts +0 -2
- package/dist/src/task/access.spec.d.ts.map +0 -1
- package/dist/src/task/payload.spec.d.ts +0 -2
- package/dist/src/task/payload.spec.d.ts.map +0 -1
- package/dist/src/task/task.spec.d.ts +0 -2
- package/dist/src/task/task.spec.d.ts.map +0 -1
- package/dist/src/user/access.spec.d.ts +0 -2
- package/dist/src/user/access.spec.d.ts.map +0 -1
- package/dist/src/user/user.spec.d.ts +0 -2
- package/dist/src/user/user.spec.d.ts.map +0 -1
- package/dist/src/util/retrieve.spec.d.ts +0 -2
- package/dist/src/util/retrieve.spec.d.ts.map +0 -1
- package/dist/src/view/view.spec.d.ts +0 -2
- package/dist/src/view/view.spec.d.ts.map +0 -1
- package/dist/src/workspace/access.spec.d.ts +0 -2
- package/dist/src/workspace/access.spec.d.ts.map +0 -1
- package/dist/src/workspace/client.d.ts +0 -36
- package/dist/src/workspace/client.d.ts.map +0 -1
- package/dist/src/workspace/external.d.ts.map +0 -1
- package/dist/src/workspace/index.d.ts +0 -2
- package/dist/src/workspace/index.d.ts.map +0 -1
- package/dist/src/workspace/types.gen.d.ts +0 -34
- package/dist/src/workspace/types.gen.d.ts.map +0 -1
- package/dist/src/workspace/workspace.spec.d.ts +0 -2
- package/dist/src/workspace/workspace.spec.d.ts.map +0 -1
- package/examples/node/basicReadWrite.js +0 -86
- package/examples/node/createChannels.js +0 -50
- package/examples/node/createRange.js +0 -67
- package/examples/node/package-lock.json +0 -133
- package/examples/node/package.json +0 -17
- package/examples/node/readLatest.js +0 -76
- package/examples/node/seriesAndFrames.js +0 -173
- package/examples/node/streamRead.js +0 -50
- package/examples/node/streamWrite.js +0 -87
- package/src/access/enforce.spec.ts +0 -188
- package/src/access/policy/access.spec.ts +0 -147
- package/src/access/role/role.spec.ts +0 -95
- package/src/actions/actions.spec.ts +0 -229
- package/src/arc/access.spec.ts +0 -149
- package/src/arc/arc.spec.ts +0 -44
- package/src/arc/lsp.spec.ts +0 -717
- package/src/auth/auth.spec.ts +0 -101
- package/src/channel/access.spec.ts +0 -116
- package/src/channel/batchRetriever.spec.ts +0 -115
- package/src/channel/channel.spec.ts +0 -352
- package/src/channel/payload.spec.ts +0 -117
- package/src/channel/retriever.ts +0 -252
- package/src/connection/checker.ts +0 -203
- package/src/connection/connection.spec.ts +0 -152
- package/src/connection.spec.ts +0 -145
- package/src/control/state.spec.ts +0 -22
- package/src/device/access.spec.ts +0 -159
- package/src/device/device.spec.ts +0 -461
- package/src/device/payload.spec.ts +0 -142
- package/src/errors.spec.ts +0 -71
- package/src/framer/adapter.spec.ts +0 -620
- package/src/framer/client.spec.ts +0 -180
- package/src/framer/codec.spec.ts +0 -383
- package/src/framer/deleter.spec.ts +0 -103
- package/src/framer/frame.spec.ts +0 -973
- package/src/framer/iterator.spec.ts +0 -169
- package/src/framer/reader.spec.ts +0 -865
- package/src/framer/streamer.spec.ts +0 -871
- package/src/framer/writer.spec.ts +0 -486
- package/src/group/access.spec.ts +0 -77
- package/src/group/group.spec.ts +0 -65
- package/src/label/access.spec.ts +0 -109
- package/src/label/label.spec.ts +0 -83
- package/src/lineplot/access.spec.ts +0 -134
- package/src/lineplot/lineplot.spec.ts +0 -120
- package/src/log/access.spec.ts +0 -132
- package/src/log/log.spec.ts +0 -106
- package/src/ontology/ontology.spec.ts +0 -521
- package/src/rack/access.spec.ts +0 -102
- package/src/rack/rack.spec.ts +0 -171
- package/src/ranger/access.spec.ts +0 -115
- package/src/ranger/alias/payload.spec.ts +0 -52
- package/src/ranger/ranger.spec.ts +0 -343
- package/src/schematic/access.spec.ts +0 -134
- package/src/schematic/actions.spec.ts +0 -699
- package/src/schematic/schematic.spec.ts +0 -340
- package/src/schematic/symbol/access.spec.ts +0 -172
- package/src/schematic/symbol/client.spec.ts +0 -160
- package/src/status/access.spec.ts +0 -129
- package/src/status/status.spec.ts +0 -448
- package/src/table/access.spec.ts +0 -128
- package/src/table/table.spec.ts +0 -651
- package/src/task/access.spec.ts +0 -131
- package/src/task/payload.spec.ts +0 -113
- package/src/task/task.spec.ts +0 -657
- package/src/user/access.spec.ts +0 -107
- package/src/user/user.spec.ts +0 -290
- package/src/util/retrieve.spec.ts +0 -49
- package/src/view/view.spec.ts +0 -165
- package/src/workspace/access.spec.ts +0 -108
- package/src/workspace/client.ts +0 -112
- package/src/workspace/types.gen.ts +0 -46
- package/src/workspace/workspace.spec.ts +0 -111
- package/tsconfig.json +0 -5
- package/tsconfig.vite.json +0 -4
- package/vite.config.ts +0 -29
- /package/dist/src/{workspace → project}/external.d.ts +0 -0
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
// Copyright 2026 Synnax Labs, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Use of this software is governed by the Business Source License included in the file
|
|
4
|
+
// licenses/BSL.txt.
|
|
5
|
+
//
|
|
6
|
+
// As of the Change Date specified in that file, in accordance with the Business Source
|
|
7
|
+
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
|
+
// included in the file licenses/APL.txt.
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
compare,
|
|
12
|
+
type destructor,
|
|
13
|
+
errors,
|
|
14
|
+
primitive,
|
|
15
|
+
type record,
|
|
16
|
+
type state,
|
|
17
|
+
TimeSpan,
|
|
18
|
+
TimeStamp,
|
|
19
|
+
} from "@synnaxlabs/x";
|
|
20
|
+
|
|
21
|
+
import { AccessDeniedError, NotFoundError } from "@/errors";
|
|
22
|
+
import { Deleted } from "@/query/deleted";
|
|
23
|
+
import { type Table, type TableEvent } from "@/query/table";
|
|
24
|
+
import { type Data, type FetchOptions, type Params } from "@/query/types";
|
|
25
|
+
|
|
26
|
+
const hashes = new WeakMap<object, string>();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Deterministically serializes query params to a stable string. Keys are sorted
|
|
30
|
+
* recursively so `{a: 1, b: 2}` and `{b: 2, a: 1}` collapse to the same key, and
|
|
31
|
+
* explicitly-undefined fields hash like absent ones (matching JSON semantics). Class
|
|
32
|
+
* instances implementing {@link primitive.Hashable} delegate to their `hash()` method;
|
|
33
|
+
* plain objects and arrays recurse structurally, memoized per object identity ({@link
|
|
34
|
+
* Params} is readonly, so an object's hash never changes).
|
|
35
|
+
*/
|
|
36
|
+
export const hash = (params: Params): string => {
|
|
37
|
+
if (params === null) return "null";
|
|
38
|
+
if (params === undefined) return "undefined";
|
|
39
|
+
if (typeof params === "bigint") return `${params.toString()}n`;
|
|
40
|
+
if (typeof params !== "object") return JSON.stringify(params);
|
|
41
|
+
if (primitive.isHashable(params)) return params.hash();
|
|
42
|
+
const held = hashes.get(params);
|
|
43
|
+
if (held !== undefined) return held;
|
|
44
|
+
let result: string;
|
|
45
|
+
if (Array.isArray(params)) result = `[${params.map(hash).join(",")}]`;
|
|
46
|
+
else {
|
|
47
|
+
const fields = Object.entries(params)
|
|
48
|
+
.filter(([, v]) => v !== undefined)
|
|
49
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
50
|
+
result = `{${fields.map(([k, v]) => `${JSON.stringify(k)}:${hash(v)}`).join(",")}}`;
|
|
51
|
+
}
|
|
52
|
+
hashes.set(params, result);
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A cached answer as delivered to consumers: the query's current data, or the
|
|
58
|
+
* {@link Deleted} corpse of a record that was deleted. A corpse is never
|
|
59
|
+
* delivered as bare data, so deletion cannot be mistaken for a live result.
|
|
60
|
+
* Narrow with {@link isLive}, or {@link Deleted.matches} to reach the corpse.
|
|
61
|
+
*/
|
|
62
|
+
export type Cached<D extends Data> = D | Deleted<D>;
|
|
63
|
+
|
|
64
|
+
/** True when a cached answer is live: cached at all, and not a deletion. */
|
|
65
|
+
export const isLive = <D extends Data>(value: Cached<D> | undefined): value is D =>
|
|
66
|
+
value != null && !Deleted.matches<D>(value);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Returns the corpse of a deleted answer, for callers that need the record's
|
|
70
|
+
* last value (restoring it, say) and have no use for any other outcome.
|
|
71
|
+
* @throws {NotFoundError} if the answer is live or nothing is cached.
|
|
72
|
+
*/
|
|
73
|
+
export const requireCorpse = <D extends Data>(value: Cached<D> | undefined): D => {
|
|
74
|
+
if (!Deleted.matches<D>(value))
|
|
75
|
+
throw new NotFoundError("the deleted record is no longer cached");
|
|
76
|
+
return value.corpse;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Receives the new cached answer for a query. `undefined` means the answer was
|
|
81
|
+
* invalidated; the next read refetches.
|
|
82
|
+
*/
|
|
83
|
+
export interface ChangeHandler<D extends Data> {
|
|
84
|
+
(result: Cached<D> | undefined): void;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The read surface of one query space: fetch, subscribe, snapshot. The public
|
|
89
|
+
* face of every answer space, including the named spaces domain clients
|
|
90
|
+
* expose (children, parents, kv).
|
|
91
|
+
*/
|
|
92
|
+
export interface Retrieves<P extends Params, D extends Data> {
|
|
93
|
+
retrieve: (params: P, options?: FetchOptions) => Promise<D>;
|
|
94
|
+
onChange: (params: P, handler: ChangeHandler<D>) => destructor.Destructor;
|
|
95
|
+
getCached: (params: P) => Cached<D> | undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A foreign-table subscription for an answer space: projects that table's
|
|
100
|
+
* events onto the space's primary keys. Build with {@link watch}.
|
|
101
|
+
*/
|
|
102
|
+
export interface Watch<P extends Params, K extends record.Key> {
|
|
103
|
+
attach: (
|
|
104
|
+
params: P,
|
|
105
|
+
onEvent: (result: K[] | "refetch") => void,
|
|
106
|
+
) => destructor.Destructor;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Declares that events on the given table affect answers in this space.
|
|
111
|
+
* `affects` maps an event to the primary keys whose membership or content it
|
|
112
|
+
* touches, "refetch" to invalidate wholesale, or null when unaffected.
|
|
113
|
+
*/
|
|
114
|
+
export const watch = <
|
|
115
|
+
P extends Params,
|
|
116
|
+
K extends record.Key,
|
|
117
|
+
ForeignKey extends record.Key,
|
|
118
|
+
ForeignValue extends state.State,
|
|
119
|
+
>(
|
|
120
|
+
table: Table<ForeignKey, ForeignValue>,
|
|
121
|
+
affects: (
|
|
122
|
+
event: TableEvent<ForeignKey, ForeignValue>,
|
|
123
|
+
params: P,
|
|
124
|
+
) => K[] | "refetch" | null,
|
|
125
|
+
): Watch<P, K> => ({
|
|
126
|
+
attach: (params, onEvent) =>
|
|
127
|
+
// Batched so one foreign write fires onEvent once: a refetch verdict for any event
|
|
128
|
+
// supersedes the batch's keys, which otherwise union.
|
|
129
|
+
table.subscribeBatch((events) => {
|
|
130
|
+
let keys: K[] = [];
|
|
131
|
+
for (const event of events) {
|
|
132
|
+
const result = affects(event, params);
|
|
133
|
+
if (result == null) continue;
|
|
134
|
+
if (result === "refetch") return onEvent("refetch");
|
|
135
|
+
keys = keys.concat(result);
|
|
136
|
+
}
|
|
137
|
+
if (keys.length > 0) onEvent(keys);
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Declaration of one query space: which table owns its records, how to fetch
|
|
143
|
+
* an answer's keys, how to assemble content, and how membership is maintained.
|
|
144
|
+
*
|
|
145
|
+
* Maintenance is chosen per query instance:
|
|
146
|
+
* 1. exact-key — `keyOf` returns a key: track that entry; deletion flips the
|
|
147
|
+
* answer to deleted.
|
|
148
|
+
* 2. client-checkable — `matches` compares a record against the query:
|
|
149
|
+
* admit/evict exactly, no network.
|
|
150
|
+
* 3. server-computed — any field named in `serverFields` is set on the query
|
|
151
|
+
* (or neither `keyOf` nor `matches` applies): debounced wholesale refetch.
|
|
152
|
+
*/
|
|
153
|
+
export interface SpaceConfig<
|
|
154
|
+
P extends Params,
|
|
155
|
+
D extends Data,
|
|
156
|
+
K extends record.Key = record.Key,
|
|
157
|
+
V extends state.State = state.State,
|
|
158
|
+
> {
|
|
159
|
+
/** Resource name used in error messages, e.g. "range". */
|
|
160
|
+
name: string;
|
|
161
|
+
/** The table owning this space's record content. */
|
|
162
|
+
table: Table<K, V>;
|
|
163
|
+
/**
|
|
164
|
+
* Fetches the answer from the cluster: writes fetched records into their
|
|
165
|
+
* tables and returns the member keys.
|
|
166
|
+
*/
|
|
167
|
+
fetch: (params: P, options?: FetchOptions) => Promise<K[]>;
|
|
168
|
+
/** Assembles the answer from resolved member records at read time. */
|
|
169
|
+
compose: (records: V[], params: P) => D;
|
|
170
|
+
/** Rule 1: the single key a query addresses, or null when it doesn't. */
|
|
171
|
+
keyOf?: (params: P) => K | null;
|
|
172
|
+
/**
|
|
173
|
+
* The exact key set a keys-only query addresses, or null when it doesn't. Lets
|
|
174
|
+
* getCached compose the answer straight from the table (params order, deduped,
|
|
175
|
+
* tombstoned members omitted) and lets maintenance seed a ready answer on
|
|
176
|
+
* subscribe, so a fully cached key set never fetches.
|
|
177
|
+
*/
|
|
178
|
+
keysOf?: (params: P) => K[] | null;
|
|
179
|
+
/** Rule 2: whether a record satisfies the query. Pure; no network. */
|
|
180
|
+
matches?: (record: V, params: P) => boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Query fields only the server can evaluate (searchTerm, limit, offset).
|
|
183
|
+
* A query instance with any of them set is maintained by rule 3.
|
|
184
|
+
*/
|
|
185
|
+
serverFields?: readonly string[];
|
|
186
|
+
/** Foreign tables whose events affect this space's answers. */
|
|
187
|
+
watch?: Watch<P, K>[];
|
|
188
|
+
/**
|
|
189
|
+
* Marks a space whose answer addresses one record and cannot compose an
|
|
190
|
+
* empty membership: rule-2 eviction of the last member flips the query to
|
|
191
|
+
* deleted (when the entry tombstoned) or invalidates it, instead of
|
|
192
|
+
* composing []. Leave unset for spaces where [] is a valid answer.
|
|
193
|
+
*/
|
|
194
|
+
single?: boolean;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Wiring a {@link Space} receives from the cache that owns it. */
|
|
198
|
+
export interface SpaceHooks {
|
|
199
|
+
/** Started (not awaited) on reads and subscriptions to open change delivery. */
|
|
200
|
+
ensureStreaming?: () => Promise<void>;
|
|
201
|
+
/** Subscribes to connection-epoch changes; maintained answers refetch on bump. */
|
|
202
|
+
onEpoch?: (callback: (epoch: number) => void) => destructor.Destructor;
|
|
203
|
+
/**
|
|
204
|
+
* Whether change delivery keeps subscribed answers current. False when the
|
|
205
|
+
* cache is detached or the cluster refused the stream. Defaults to true.
|
|
206
|
+
*/
|
|
207
|
+
maintained?: () => boolean;
|
|
208
|
+
/** Reports maintenance errors that have no caller to throw to. Defaults to
|
|
209
|
+
* console logging. */
|
|
210
|
+
onError?: (error: Error) => void;
|
|
211
|
+
/**
|
|
212
|
+
* How long maintenance survives the last unsubscribe, so a quick remount finds
|
|
213
|
+
* the answer still maintained and skips the reconfirm refetch. Zero tears down
|
|
214
|
+
* synchronously. Defaults to 5 seconds.
|
|
215
|
+
*/
|
|
216
|
+
teardownGrace?: TimeSpan;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const DEFAULT_DEBOUNCE = TimeSpan.milliseconds(100);
|
|
220
|
+
const DEFAULT_TEARDOWN_GRACE = TimeSpan.seconds(5);
|
|
221
|
+
|
|
222
|
+
type QueryState<K extends record.Key, D extends Data> =
|
|
223
|
+
| { variant: "unfetched" }
|
|
224
|
+
| { variant: "loading"; promise: Promise<D> }
|
|
225
|
+
| { variant: "ready"; keys: K[] }
|
|
226
|
+
| { variant: "error"; error: Error }
|
|
227
|
+
| { variant: "deleted"; key: K };
|
|
228
|
+
|
|
229
|
+
interface Query<
|
|
230
|
+
P extends Params,
|
|
231
|
+
K extends record.Key,
|
|
232
|
+
D extends Data,
|
|
233
|
+
V extends state.State,
|
|
234
|
+
> {
|
|
235
|
+
params: P;
|
|
236
|
+
state: QueryState<K, D>;
|
|
237
|
+
handlers: Set<ChangeHandler<D>>;
|
|
238
|
+
/** Rule teardown; present exactly while the query is maintained. */
|
|
239
|
+
teardown?: destructor.Destructor[];
|
|
240
|
+
/** Set when maintenance ends: changes since then went unobserved. */
|
|
241
|
+
unmaintained?: boolean;
|
|
242
|
+
/** Keys whose membership a fetch in flight deferred; drained on settle. */
|
|
243
|
+
pendingRechecks?: Set<K>;
|
|
244
|
+
/** Set when a fetch in flight deferred a refetch; honored on settle. */
|
|
245
|
+
refetchOnSettle?: boolean;
|
|
246
|
+
refetchTimer?: ReturnType<typeof setTimeout>;
|
|
247
|
+
/** When the last subscriber left; the sweep tears the query down once this
|
|
248
|
+
* outlives the grace window. Cleared on resubscribe. */
|
|
249
|
+
idleSince?: TimeStamp;
|
|
250
|
+
/** Answer interned against the records it was composed from, so repeated
|
|
251
|
+
* reads stay referentially stable for useSyncExternalStore consumers. A
|
|
252
|
+
* table replaces an entry only when its content changes, so a surviving entry
|
|
253
|
+
* set means a surviving answer. */
|
|
254
|
+
composed?: { records: V[]; value: D };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Cached read path for one query space of a domain client: deduped fetches, per-query
|
|
259
|
+
* lifecycle, subscriptions, and deletion delivery. Answers hold only key lists; record
|
|
260
|
+
* content lives in tables and is assembled at read time, so answer content cannot
|
|
261
|
+
* diverge from the record cache. Answers are maintained only while subscribed. An
|
|
262
|
+
* unsubscribed read refetches; an unsubscribed getCached serves the retained (possibly
|
|
263
|
+
* stale) answer, recomposed against live tables.
|
|
264
|
+
*/
|
|
265
|
+
export class Space<
|
|
266
|
+
P extends Params,
|
|
267
|
+
D extends Data,
|
|
268
|
+
K extends record.Key = record.Key,
|
|
269
|
+
V extends state.State = state.State,
|
|
270
|
+
> {
|
|
271
|
+
private readonly queries = new Map<string, Query<P, K, D, V>>();
|
|
272
|
+
/** Composed corpses interned per tombstone and params, so deleted answers
|
|
273
|
+
* stay referentially stable. Identity composes reuse the tombstone. */
|
|
274
|
+
private readonly corpses = new WeakMap<Deleted<V>, Map<string, Deleted<D>>>();
|
|
275
|
+
/** Answers for reads with no live query, interned per entry and params. */
|
|
276
|
+
private readonly entryAnswers = new WeakMap<object, Map<string, D>>();
|
|
277
|
+
private readonly config: SpaceConfig<P, D, K, V>;
|
|
278
|
+
private readonly hooks: SpaceHooks & {
|
|
279
|
+
onError: NonNullable<SpaceHooks["onError"]>;
|
|
280
|
+
};
|
|
281
|
+
private readonly detachEpoch?: destructor.Destructor;
|
|
282
|
+
private readonly grace: TimeSpan;
|
|
283
|
+
private sweepTimer?: ReturnType<typeof setTimeout>;
|
|
284
|
+
|
|
285
|
+
constructor(config: SpaceConfig<P, D, K, V>, hooks: SpaceHooks = {}) {
|
|
286
|
+
this.config = config;
|
|
287
|
+
this.hooks = { ...hooks, onError: hooks.onError ?? console.error };
|
|
288
|
+
this.grace = hooks.teardownGrace ?? DEFAULT_TEARDOWN_GRACE;
|
|
289
|
+
this.detachEpoch = hooks.onEpoch?.((epoch) => {
|
|
290
|
+
// 0 is a return to cold (cluster replacement): the fresh stream's own
|
|
291
|
+
// epoch bump refetches, not the reset itself.
|
|
292
|
+
if (epoch === 0) return;
|
|
293
|
+
this.queries.forEach((query) => {
|
|
294
|
+
if (query.teardown != null) void this.refetch(query);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Returns the answer to the query: instantly when cached and subscribed,
|
|
301
|
+
* joining the in-flight fetch when one exists, fetching otherwise. Settled
|
|
302
|
+
* answers are kept fresh only while maintained (subscribed, or within the teardown
|
|
303
|
+
* grace window after the last unsubscribe, and only while change delivery is on),
|
|
304
|
+
* so an unmaintained read always refetches. A previously failed query refetches.
|
|
305
|
+
* @throws {NotFoundError} if the queried record was deleted.
|
|
306
|
+
*/
|
|
307
|
+
retrieve(params: P, options?: FetchOptions): Promise<D> {
|
|
308
|
+
const query = this.ensure(params);
|
|
309
|
+
const { state } = query;
|
|
310
|
+
if (state.variant === "loading") return state.promise;
|
|
311
|
+
if (query.teardown != null && this.hooks.maintained?.() !== false)
|
|
312
|
+
switch (state.variant) {
|
|
313
|
+
case "ready":
|
|
314
|
+
return Promise.resolve(this.composedOf(query, state.keys));
|
|
315
|
+
case "deleted":
|
|
316
|
+
return Promise.reject(new NotFoundError(`${this.config.name} was deleted`));
|
|
317
|
+
}
|
|
318
|
+
void this.startStreaming();
|
|
319
|
+
return this.fetch(query, options);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Returns the cached answer without touching the network: the current data,
|
|
324
|
+
* the corpse of a deleted record, or undefined when nothing is cached.
|
|
325
|
+
* Exact-key queries also resolve from a table entry cached by any other
|
|
326
|
+
* query. May be stale for queries nothing subscribes to.
|
|
327
|
+
*/
|
|
328
|
+
getCached(params: P): Cached<D> | undefined {
|
|
329
|
+
const query = this.queries.get(hash(params));
|
|
330
|
+
if (query != null) {
|
|
331
|
+
const cached = this.cachedOf(query);
|
|
332
|
+
if (cached != null) return cached;
|
|
333
|
+
}
|
|
334
|
+
// Rule-1 queries read straight through to the table: an entry (or tombstone)
|
|
335
|
+
// answers the query even before any fetch or subscription exists.
|
|
336
|
+
const key = this.config.keyOf?.(params);
|
|
337
|
+
if (key != null) {
|
|
338
|
+
const { table } = this.config;
|
|
339
|
+
const entry = table.get(key);
|
|
340
|
+
if (entry != null) return this.entryAnswerOf(entry, params);
|
|
341
|
+
const tombstone = table.getTombstone(key);
|
|
342
|
+
if (tombstone == null) return undefined;
|
|
343
|
+
return this.deletedOf(tombstone, params);
|
|
344
|
+
}
|
|
345
|
+
// Keys-only queries compose from the table the same way, when every key resolves.
|
|
346
|
+
// The answer matches what the fetch would return.
|
|
347
|
+
const keys = this.config.keysOf?.(params);
|
|
348
|
+
if (keys == null) return undefined;
|
|
349
|
+
const members = this.composableMembersOf(keys);
|
|
350
|
+
if (members == null) return undefined;
|
|
351
|
+
return this.composedOf(this.ensure(params), members);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Resolves a keys-only query's key list against the table: deduped, in params order,
|
|
356
|
+
* tombstoned keys omitted to match the fetch answer. Returns null when any key is
|
|
357
|
+
* unknown, since only a fetch can tell an uncached record from a nonexistent one.
|
|
358
|
+
*/
|
|
359
|
+
private composableMembersOf(keys: K[]): K[] | null {
|
|
360
|
+
const { table } = this.config;
|
|
361
|
+
const members: K[] = [];
|
|
362
|
+
const seen = new Set<K>();
|
|
363
|
+
for (const key of keys) {
|
|
364
|
+
if (seen.has(key)) continue;
|
|
365
|
+
seen.add(key);
|
|
366
|
+
const status = table.status(key);
|
|
367
|
+
if (status === "unknown") return null;
|
|
368
|
+
if (status === "present") members.push(key);
|
|
369
|
+
}
|
|
370
|
+
return members;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Subscribes to changes in the query's cached answer. The handler fires with the new
|
|
375
|
+
* answer on every change or deletion. Maintenance for the query runs while at least
|
|
376
|
+
* one subscriber exists and survives the last unsubscribe by the grace window, so a
|
|
377
|
+
* quick remount skips the reconfirm refetch. Returns a destructor that unsubscribes.
|
|
378
|
+
*/
|
|
379
|
+
onChange(params: P, handler: ChangeHandler<D>): destructor.Destructor {
|
|
380
|
+
void this.startStreaming();
|
|
381
|
+
const query = this.ensure(params);
|
|
382
|
+
query.handlers.add(handler);
|
|
383
|
+
query.idleSince = undefined;
|
|
384
|
+
if (query.teardown == null) this.maintain(query);
|
|
385
|
+
return () => {
|
|
386
|
+
query.handlers.delete(handler);
|
|
387
|
+
if (query.handlers.size > 0) return;
|
|
388
|
+
if (this.grace.isZero) return this.teardownQuery(query);
|
|
389
|
+
query.idleSince = TimeStamp.now();
|
|
390
|
+
this.armSweep();
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Resets every answer to unfetched and notifies subscribers that their answer was
|
|
396
|
+
* invalidated, so the next read refetches. Maintenance subscriptions stay mounted.
|
|
397
|
+
* Called by the cache when the cluster behind the connection is replaced.
|
|
398
|
+
*/
|
|
399
|
+
reset(): void {
|
|
400
|
+
this.queries.forEach((query) => {
|
|
401
|
+
if (query.refetchTimer != null) {
|
|
402
|
+
clearTimeout(query.refetchTimer);
|
|
403
|
+
query.refetchTimer = undefined;
|
|
404
|
+
}
|
|
405
|
+
query.state = { variant: "unfetched" };
|
|
406
|
+
query.handlers.forEach((handler) => {
|
|
407
|
+
try {
|
|
408
|
+
handler(undefined);
|
|
409
|
+
} catch (exc) {
|
|
410
|
+
this.report(exc, `failed to notify ${this.config.name} answer subscriber`);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Detaches the epoch subscription. Queries and handlers are dropped. */
|
|
417
|
+
close(): void {
|
|
418
|
+
this.detachEpoch?.();
|
|
419
|
+
if (this.sweepTimer != null) {
|
|
420
|
+
clearTimeout(this.sweepTimer);
|
|
421
|
+
this.sweepTimer = undefined;
|
|
422
|
+
}
|
|
423
|
+
this.queries.forEach((query) => {
|
|
424
|
+
query.teardown?.forEach((d) => d());
|
|
425
|
+
if (query.refetchTimer != null) clearTimeout(query.refetchTimer);
|
|
426
|
+
});
|
|
427
|
+
this.queries.clear();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
private teardownQuery(query: Query<P, K, D, V>): void {
|
|
431
|
+
query.idleSince = undefined;
|
|
432
|
+
query.teardown?.forEach((d) => d());
|
|
433
|
+
query.teardown = undefined;
|
|
434
|
+
query.unmaintained = true;
|
|
435
|
+
query.pendingRechecks = undefined;
|
|
436
|
+
query.refetchOnSettle = false;
|
|
437
|
+
if (query.refetchTimer != null) {
|
|
438
|
+
clearTimeout(query.refetchTimer);
|
|
439
|
+
query.refetchTimer = undefined;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Arms the sweep while any query sits in the grace window. One timer per
|
|
444
|
+
* space, no matter how many queries go idle at once. */
|
|
445
|
+
private armSweep(): void {
|
|
446
|
+
this.sweepTimer ??= setTimeout(() => {
|
|
447
|
+
this.sweepTimer = undefined;
|
|
448
|
+
this.sweep();
|
|
449
|
+
}, this.grace.milliseconds / 2);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private sweep(): void {
|
|
453
|
+
let anyIdle = false;
|
|
454
|
+
const now = TimeStamp.now();
|
|
455
|
+
this.queries.forEach((query) => {
|
|
456
|
+
if (query.idleSince == null) return;
|
|
457
|
+
if (query.handlers.size > 0) {
|
|
458
|
+
query.idleSince = undefined;
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (now.span(query.idleSince).lessThan(this.grace)) {
|
|
462
|
+
anyIdle = true;
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
this.teardownQuery(query);
|
|
466
|
+
});
|
|
467
|
+
if (anyIdle) this.armSweep();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
private ensure(params: P): Query<P, K, D, V> {
|
|
471
|
+
const hashed = hash(params);
|
|
472
|
+
let query = this.queries.get(hashed);
|
|
473
|
+
if (query == null) {
|
|
474
|
+
query = { params, state: { variant: "unfetched" }, handlers: new Set() };
|
|
475
|
+
this.queries.set(hashed, query);
|
|
476
|
+
}
|
|
477
|
+
return query;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Streaming failure must never block reads, so the change stream opens in the
|
|
481
|
+
// background rather than being awaited. A denial belongs to the connection, which
|
|
482
|
+
// reports it once, not to every query that reads. Never rejects.
|
|
483
|
+
private async startStreaming(): Promise<void> {
|
|
484
|
+
try {
|
|
485
|
+
await this.hooks.ensureStreaming?.();
|
|
486
|
+
} catch (exc) {
|
|
487
|
+
if (AccessDeniedError.matches(exc)) return;
|
|
488
|
+
this.report(exc);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private report(
|
|
493
|
+
exc: unknown,
|
|
494
|
+
message = `failed to maintain ${this.config.name} answers`,
|
|
495
|
+
): void {
|
|
496
|
+
this.hooks.onError(new Error(message, { cause: exc }));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/** Composes a ready answer, interned against the records it was built from. */
|
|
500
|
+
private composedOf(query: Query<P, K, D, V>, keys: K[]): D {
|
|
501
|
+
const records = this.config.table.get(keys);
|
|
502
|
+
const { composed } = query;
|
|
503
|
+
if (composed != null && compare.arraysEqual(composed.records, records))
|
|
504
|
+
return composed.value;
|
|
505
|
+
const value = this.config.compose(records, query.params);
|
|
506
|
+
query.composed = { records, value };
|
|
507
|
+
return value;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
private cachedOf(query: Query<P, K, D, V>): Cached<D> | undefined {
|
|
511
|
+
const { state } = query;
|
|
512
|
+
if (state.variant === "ready") return this.composedOf(query, state.keys);
|
|
513
|
+
if (state.variant === "deleted") {
|
|
514
|
+
const tombstone = this.config.table.getTombstone(state.key);
|
|
515
|
+
if (tombstone == null) return undefined;
|
|
516
|
+
return this.deletedOf(tombstone, query.params);
|
|
517
|
+
}
|
|
518
|
+
return undefined;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** Composes a rule-1 answer for a read with no live query, interned per entry
|
|
522
|
+
* and params. The memo dies with the entry it is keyed on, which a table
|
|
523
|
+
* replaces on every content change. */
|
|
524
|
+
private entryAnswerOf(entry: V, params: P): D {
|
|
525
|
+
if (typeof entry !== "object" || entry === null)
|
|
526
|
+
return this.config.compose([entry], params);
|
|
527
|
+
const hashed = hash(params);
|
|
528
|
+
let perEntry = this.entryAnswers.get(entry);
|
|
529
|
+
if (perEntry == null) {
|
|
530
|
+
perEntry = new Map();
|
|
531
|
+
this.entryAnswers.set(entry, perEntry);
|
|
532
|
+
}
|
|
533
|
+
if (perEntry.has(hashed)) return perEntry.get(hashed) as D;
|
|
534
|
+
const value = this.config.compose([entry], params);
|
|
535
|
+
perEntry.set(hashed, value);
|
|
536
|
+
return value;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private deletedOf(tombstone: Deleted<V>, params: P): Deleted<D> {
|
|
540
|
+
const hashed = hash(params);
|
|
541
|
+
let perTombstone = this.corpses.get(tombstone);
|
|
542
|
+
if (perTombstone == null) {
|
|
543
|
+
perTombstone = new Map();
|
|
544
|
+
this.corpses.set(tombstone, perTombstone);
|
|
545
|
+
}
|
|
546
|
+
let deleted = perTombstone.get(hashed);
|
|
547
|
+
if (deleted == null) {
|
|
548
|
+
deleted = new Deleted(
|
|
549
|
+
this.config.compose([tombstone.corpse], params),
|
|
550
|
+
tombstone.deletedAt,
|
|
551
|
+
);
|
|
552
|
+
perTombstone.set(hashed, deleted);
|
|
553
|
+
}
|
|
554
|
+
return deleted;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/** Notifies subscribers when any of the keys is a member of the ready
|
|
558
|
+
* answer; otherwise just drops the memo so the next read recomposes. */
|
|
559
|
+
private touchMembers(query: Query<P, K, D, V>, keys: K[]): void {
|
|
560
|
+
query.composed = undefined;
|
|
561
|
+
if (query.state.variant !== "ready") return;
|
|
562
|
+
const members = new Set(query.state.keys);
|
|
563
|
+
if (keys.some((key) => members.has(key))) this.touch(query);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** Notifies every subscriber with the query's current answer. */
|
|
567
|
+
private touch(query: Query<P, K, D, V>): void {
|
|
568
|
+
// A watched foreign table is an input the records do not carry, so an
|
|
569
|
+
// event is the only signal that a composed answer changed.
|
|
570
|
+
query.composed = undefined;
|
|
571
|
+
if (query.handlers.size === 0) return;
|
|
572
|
+
const result = this.cachedOf(query);
|
|
573
|
+
query.handlers.forEach((handler) => {
|
|
574
|
+
try {
|
|
575
|
+
handler(result);
|
|
576
|
+
} catch (exc) {
|
|
577
|
+
this.report(exc, `failed to notify ${this.config.name} answer subscriber`);
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
private settle(
|
|
583
|
+
query: Query<P, K, D, V>,
|
|
584
|
+
expected: QueryState<K, D>,
|
|
585
|
+
next: QueryState<K, D>,
|
|
586
|
+
): void {
|
|
587
|
+
// A late promise resolution must not clobber a maintenance update.
|
|
588
|
+
if (query.state !== expected) return;
|
|
589
|
+
query.state = next;
|
|
590
|
+
this.drainRechecks(query);
|
|
591
|
+
if (next.variant === "error") {
|
|
592
|
+
query.refetchOnSettle = false;
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (query.refetchOnSettle === true) {
|
|
596
|
+
query.refetchOnSettle = false;
|
|
597
|
+
if (query.teardown != null) this.scheduleRefetch(query);
|
|
598
|
+
}
|
|
599
|
+
this.touch(query);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Replays membership changes a fetch in flight deferred. The fetch answers the query
|
|
604
|
+
* as of when it ran, so a change that raced it would otherwise be lost under the keys
|
|
605
|
+
* it publishes. Applies quietly: the settle that calls this notifies once for the
|
|
606
|
+
* whole answer.
|
|
607
|
+
*/
|
|
608
|
+
private drainRechecks(query: Query<P, K, D, V>): void {
|
|
609
|
+
const pending = query.pendingRechecks;
|
|
610
|
+
if (pending == null) return;
|
|
611
|
+
query.pendingRechecks = undefined;
|
|
612
|
+
this.applyRechecks(query, pending);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private fetch(query: Query<P, K, D, V>, options?: FetchOptions): Promise<D> {
|
|
616
|
+
const promise = this.config.fetch(query.params, options).then(
|
|
617
|
+
(keys) => {
|
|
618
|
+
this.settle(query, loading, { variant: "ready", keys });
|
|
619
|
+
// The settle drains membership changes that raced the fetch, so the answer
|
|
620
|
+
// comes off the query: the keys the fetch returned are already stale.
|
|
621
|
+
const { state } = query;
|
|
622
|
+
return this.composedOf(query, state.variant === "ready" ? state.keys : keys);
|
|
623
|
+
},
|
|
624
|
+
(reason: unknown) => {
|
|
625
|
+
const error = errors.fromUnknown(reason);
|
|
626
|
+
this.settle(query, loading, { variant: "error", error });
|
|
627
|
+
throw error;
|
|
628
|
+
},
|
|
629
|
+
);
|
|
630
|
+
const loading: QueryState<K, D> = { variant: "loading", promise };
|
|
631
|
+
query.state = loading;
|
|
632
|
+
return promise;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** Never rejects: a failed refetch leaves the previous answer in place. */
|
|
636
|
+
private async refetch(query: Query<P, K, D, V>): Promise<void> {
|
|
637
|
+
if (query.refetchTimer != null) {
|
|
638
|
+
clearTimeout(query.refetchTimer);
|
|
639
|
+
query.refetchTimer = undefined;
|
|
640
|
+
}
|
|
641
|
+
const before = query.state;
|
|
642
|
+
let keys: K[];
|
|
643
|
+
try {
|
|
644
|
+
keys = await this.config.fetch(query.params);
|
|
645
|
+
} catch (reason) {
|
|
646
|
+
if (query.state !== before) return;
|
|
647
|
+
// A refetch revalidates an answer the caller already has. Failing to
|
|
648
|
+
// confirm it is not grounds for taking it away.
|
|
649
|
+
if (before.variant === "ready") return;
|
|
650
|
+
query.state = { variant: "error", error: errors.fromUnknown(reason) };
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
// Only apply if maintenance hasn't been torn down and no newer fetch
|
|
654
|
+
// superseded this one.
|
|
655
|
+
if (query.state !== before) return;
|
|
656
|
+
query.state = { variant: "ready", keys };
|
|
657
|
+
this.touch(query);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
private scheduleRefetch(query: Query<P, K, D, V>): void {
|
|
661
|
+
// A fetch in flight publishes its own keys on settle, and a refetch racing
|
|
662
|
+
// it resolves into a state its result no longer matches, so it is dropped.
|
|
663
|
+
if (query.state.variant === "loading") {
|
|
664
|
+
query.refetchOnSettle = true;
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
if (query.refetchTimer != null) clearTimeout(query.refetchTimer);
|
|
668
|
+
const wait = DEFAULT_DEBOUNCE.milliseconds;
|
|
669
|
+
query.refetchTimer = setTimeout(() => {
|
|
670
|
+
query.refetchTimer = undefined;
|
|
671
|
+
void this.refetch(query);
|
|
672
|
+
}, wait);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
private isServerComputed(params: P): boolean {
|
|
676
|
+
const { serverFields } = this.config;
|
|
677
|
+
if (serverFields == null || serverFields.length === 0) return false;
|
|
678
|
+
if (typeof params !== "object" || params === null) return false;
|
|
679
|
+
return Object.entries(params).some(
|
|
680
|
+
([field, value]) => value != null && serverFields.includes(field),
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
private maintain(query: Query<P, K, D, V>): void {
|
|
685
|
+
const teardown: destructor.Destructor[] = [];
|
|
686
|
+
query.teardown = teardown;
|
|
687
|
+
const { table, keyOf, matches, watch: watches } = this.config;
|
|
688
|
+
const { params } = query;
|
|
689
|
+
// Rules 2 and 3 build membership from events, and an unmaintained query
|
|
690
|
+
// received none, so its answer must be reconfirmed. Rule 1 re-seeds from the
|
|
691
|
+
// table below and needs no network.
|
|
692
|
+
if (
|
|
693
|
+
query.unmaintained === true &&
|
|
694
|
+
query.state.variant === "ready" &&
|
|
695
|
+
keyOf?.(params) == null
|
|
696
|
+
)
|
|
697
|
+
this.scheduleRefetch(query);
|
|
698
|
+
query.unmaintained = false;
|
|
699
|
+
|
|
700
|
+
// A keys-only query whose members the table already holds seeds a ready answer:
|
|
701
|
+
// rules 2 and 3 gate notifications on a ready state, so without the seed an
|
|
702
|
+
// answer getCached composes would look live while never updating.
|
|
703
|
+
const composableKeys = this.config.keysOf?.(params);
|
|
704
|
+
if (
|
|
705
|
+
composableKeys != null &&
|
|
706
|
+
query.state.variant !== "loading" &&
|
|
707
|
+
query.state.variant !== "ready"
|
|
708
|
+
) {
|
|
709
|
+
const members = this.composableMembersOf(composableKeys);
|
|
710
|
+
if (members != null) {
|
|
711
|
+
query.state = { variant: "ready", keys: members };
|
|
712
|
+
this.touch(query);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
if (this.isServerComputed(params) || (keyOf?.(params) == null && matches == null)) {
|
|
717
|
+
// Rule 3: server-computed — any relevant event triggers a debounced
|
|
718
|
+
// wholesale refetch; membership is never patched locally. A change to a
|
|
719
|
+
// current member's entry notifies immediately, so optimistic writes render
|
|
720
|
+
// without waiting on the refetch.
|
|
721
|
+
teardown.push(
|
|
722
|
+
table.subscribeBatch((events) => {
|
|
723
|
+
this.scheduleRefetch(query);
|
|
724
|
+
this.touchMembers(
|
|
725
|
+
query,
|
|
726
|
+
events.map((event) => event.key),
|
|
727
|
+
);
|
|
728
|
+
}),
|
|
729
|
+
);
|
|
730
|
+
watches?.forEach((w) =>
|
|
731
|
+
teardown.push(
|
|
732
|
+
w.attach(params, (result) => {
|
|
733
|
+
this.scheduleRefetch(query);
|
|
734
|
+
if (result !== "refetch") this.touchMembers(query, result);
|
|
735
|
+
}),
|
|
736
|
+
),
|
|
737
|
+
);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const key = keyOf?.(params);
|
|
742
|
+
if (key != null) {
|
|
743
|
+
// Rule 1: exact-key — track one entry; deletion flips the answer. An entry
|
|
744
|
+
// already in the table answers the query without a fetch. The state is a
|
|
745
|
+
// cache of the table's presence for this key and goes stale while
|
|
746
|
+
// unsubscribed (a delete observed, then a recreate), so maintenance
|
|
747
|
+
// re-seeds from the table instead of trusting the last answer. A loading
|
|
748
|
+
// query owns an in-flight promise its own settle will resolve.
|
|
749
|
+
if (query.state.variant !== "loading") {
|
|
750
|
+
const status = table.status(key);
|
|
751
|
+
if (status === "present" && query.state.variant !== "ready") {
|
|
752
|
+
query.state = { variant: "ready", keys: [key] };
|
|
753
|
+
this.touch(query);
|
|
754
|
+
} else if (status === "tombstoned" && query.state.variant !== "deleted") {
|
|
755
|
+
query.state = { variant: "deleted", key };
|
|
756
|
+
this.touch(query);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
teardown.push(
|
|
760
|
+
table.subscribe((event) => {
|
|
761
|
+
if (event.variant === "set") query.state = { variant: "ready", keys: [key] };
|
|
762
|
+
else
|
|
763
|
+
query.state =
|
|
764
|
+
table.status(key) === "tombstoned"
|
|
765
|
+
? { variant: "deleted", key }
|
|
766
|
+
: { variant: "unfetched" };
|
|
767
|
+
this.touch(query);
|
|
768
|
+
}, key),
|
|
769
|
+
);
|
|
770
|
+
watches?.forEach((w) =>
|
|
771
|
+
teardown.push(
|
|
772
|
+
w.attach(params, (result) => {
|
|
773
|
+
if (result === "refetch") return this.scheduleRefetch(query);
|
|
774
|
+
if (result.includes(key) && query.state.variant === "ready")
|
|
775
|
+
this.touch(query);
|
|
776
|
+
}),
|
|
777
|
+
),
|
|
778
|
+
);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Rule 2: client-checkable — admit/evict exactly against `matches`.
|
|
783
|
+
teardown.push(
|
|
784
|
+
table.subscribeBatch((events) =>
|
|
785
|
+
this.recheckKeys(
|
|
786
|
+
query,
|
|
787
|
+
events.map((event) => event.key),
|
|
788
|
+
),
|
|
789
|
+
),
|
|
790
|
+
);
|
|
791
|
+
watches?.forEach((w) =>
|
|
792
|
+
teardown.push(
|
|
793
|
+
w.attach(params, (result) => {
|
|
794
|
+
if (result === "refetch") return this.scheduleRefetch(query);
|
|
795
|
+
void this.recheckMany(query, result);
|
|
796
|
+
}),
|
|
797
|
+
),
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Applies membership rechecks for the given keys against the table's current entries,
|
|
803
|
+
* without notifying. Admissions append in iteration order; evicting a single space's
|
|
804
|
+
* last member flips the query to deleted or unfetched. Returns whether the answer
|
|
805
|
+
* changed: membership moved, or a member's content was touched.
|
|
806
|
+
*/
|
|
807
|
+
private applyRechecks(query: Query<P, K, D, V>, keys: Iterable<K>): boolean {
|
|
808
|
+
if (query.state.variant === "loading") {
|
|
809
|
+
const pending = (query.pendingRechecks ??= new Set<K>());
|
|
810
|
+
for (const key of keys) pending.add(key);
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
if (query.state.variant !== "ready") return false;
|
|
814
|
+
const { table, matches, single } = this.config;
|
|
815
|
+
const memberSet = new Set(query.state.keys);
|
|
816
|
+
const admitted: K[] = [];
|
|
817
|
+
let lastEvicted: K | null = null;
|
|
818
|
+
let touched = false;
|
|
819
|
+
for (const key of new Set(keys)) {
|
|
820
|
+
const entry = table.get(key);
|
|
821
|
+
const matched = entry != null && matches!(entry, query.params);
|
|
822
|
+
if (matched === memberSet.has(key)) {
|
|
823
|
+
touched ||= matched;
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
if (matched) {
|
|
827
|
+
memberSet.add(key);
|
|
828
|
+
admitted.push(key);
|
|
829
|
+
} else {
|
|
830
|
+
memberSet.delete(key);
|
|
831
|
+
lastEvicted = key;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (admitted.length === 0 && lastEvicted == null) return touched;
|
|
835
|
+
const next = [...query.state.keys.filter((k) => memberSet.has(k)), ...admitted];
|
|
836
|
+
if (single === true && next.length === 0 && lastEvicted != null)
|
|
837
|
+
query.state =
|
|
838
|
+
table.status(lastEvicted) === "tombstoned"
|
|
839
|
+
? { variant: "deleted", key: lastEvicted }
|
|
840
|
+
: { variant: "unfetched" };
|
|
841
|
+
else query.state = { variant: "ready", keys: next };
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/** Applies rechecks for the keys, then notifies once when anything changed. */
|
|
846
|
+
private recheckKeys(query: Query<P, K, D, V>, keys: Iterable<K>): void {
|
|
847
|
+
if (this.applyRechecks(query, keys)) this.touch(query);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/** Never rejects: a failed backfill is reported and the recheck is skipped. */
|
|
851
|
+
private async recheckMany(query: Query<P, K, D, V>, keys: K[]): Promise<void> {
|
|
852
|
+
const { table } = this.config;
|
|
853
|
+
this.recheckKeys(query, keys);
|
|
854
|
+
const missing = keys.filter((key) => table.status(key) === "unknown");
|
|
855
|
+
if (missing.length === 0) return;
|
|
856
|
+
// Backfill through the table's fetch so membership can be rechecked;
|
|
857
|
+
// fetch-less tables serve cached entries only and the recheck is a no-op.
|
|
858
|
+
try {
|
|
859
|
+
await table.retrieve(missing);
|
|
860
|
+
this.recheckKeys(query, missing);
|
|
861
|
+
} catch (exc) {
|
|
862
|
+
this.report(exc, `failed to hydrate ${this.config.name} answers`);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|