@muze-nl/simplystore 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (366) hide show
  1. package/.dockerignore +8 -0
  2. package/.github/workflows/docker-build.yml +45 -0
  3. package/.gitignore~ +1 -0
  4. package/Dockerfile +60 -0
  5. package/LICENSE +21 -0
  6. package/README.md +99 -0
  7. package/data.jsontag +20 -0
  8. package/design/access-management.md +25 -0
  9. package/design/acid.md +31 -0
  10. package/design/commands.md +25 -0
  11. package/design/identity.md +71 -0
  12. package/design/immutability.md +26 -0
  13. package/design/jsontag-selector.md +365 -0
  14. package/design/multitasking.md +13 -0
  15. package/design/thoughts.md +32 -0
  16. package/docs/docker.md +129 -0
  17. package/package.json +29 -0
  18. package/package.json~ +29 -0
  19. package/src/run.mjs +3 -0
  20. package/src/server.mjs +323 -0
  21. package/src/triplestore.mjs +182 -0
  22. package/www/assets/css/style.css +2296 -0
  23. package/www/assets/feather-sprite.svg +1 -0
  24. package/www/assets/img/simplystore-narrow-white.svg +138 -0
  25. package/www/assets/img/simplystore-narrow.svg +138 -0
  26. package/www/assets/img/simplystore-white.svg +153 -0
  27. package/www/assets/img/simplystore.svg +153 -0
  28. package/www/codemirror/AUTHORS +979 -0
  29. package/www/codemirror/CHANGELOG.md +2208 -0
  30. package/www/codemirror/CONTRIBUTING.md +92 -0
  31. package/www/codemirror/LICENSE +21 -0
  32. package/www/codemirror/README.md +47 -0
  33. package/www/codemirror/addon/comment/comment.js +211 -0
  34. package/www/codemirror/addon/comment/continuecomment.js +114 -0
  35. package/www/codemirror/addon/dialog/dialog.css +32 -0
  36. package/www/codemirror/addon/dialog/dialog.js +163 -0
  37. package/www/codemirror/addon/display/autorefresh.js +47 -0
  38. package/www/codemirror/addon/display/fullscreen.css +6 -0
  39. package/www/codemirror/addon/display/fullscreen.js +41 -0
  40. package/www/codemirror/addon/display/panel.js +133 -0
  41. package/www/codemirror/addon/display/placeholder.js +78 -0
  42. package/www/codemirror/addon/display/rulers.js +51 -0
  43. package/www/codemirror/addon/edit/closebrackets.js +201 -0
  44. package/www/codemirror/addon/edit/closetag.js +185 -0
  45. package/www/codemirror/addon/edit/continuelist.js +101 -0
  46. package/www/codemirror/addon/edit/matchbrackets.js +160 -0
  47. package/www/codemirror/addon/edit/matchtags.js +66 -0
  48. package/www/codemirror/addon/edit/trailingspace.js +27 -0
  49. package/www/codemirror/addon/fold/brace-fold.js +119 -0
  50. package/www/codemirror/addon/fold/comment-fold.js +59 -0
  51. package/www/codemirror/addon/fold/foldcode.js +159 -0
  52. package/www/codemirror/addon/fold/foldgutter.css +20 -0
  53. package/www/codemirror/addon/fold/foldgutter.js +169 -0
  54. package/www/codemirror/addon/fold/indent-fold.js +48 -0
  55. package/www/codemirror/addon/fold/markdown-fold.js +49 -0
  56. package/www/codemirror/addon/fold/xml-fold.js +184 -0
  57. package/www/codemirror/addon/hint/anyword-hint.js +41 -0
  58. package/www/codemirror/addon/hint/css-hint.js +66 -0
  59. package/www/codemirror/addon/hint/html-hint.js +351 -0
  60. package/www/codemirror/addon/hint/javascript-hint.js +162 -0
  61. package/www/codemirror/addon/hint/show-hint.css +37 -0
  62. package/www/codemirror/addon/hint/show-hint.js +523 -0
  63. package/www/codemirror/addon/hint/sql-hint.js +304 -0
  64. package/www/codemirror/addon/hint/xml-hint.js +132 -0
  65. package/www/codemirror/addon/lint/coffeescript-lint.js +47 -0
  66. package/www/codemirror/addon/lint/css-lint.js +40 -0
  67. package/www/codemirror/addon/lint/html-lint.js +59 -0
  68. package/www/codemirror/addon/lint/javascript-lint.js +65 -0
  69. package/www/codemirror/addon/lint/json-lint.js +40 -0
  70. package/www/codemirror/addon/lint/lint.css +79 -0
  71. package/www/codemirror/addon/lint/lint.js +291 -0
  72. package/www/codemirror/addon/lint/yaml-lint.js +41 -0
  73. package/www/codemirror/addon/merge/merge.css +119 -0
  74. package/www/codemirror/addon/merge/merge.js +1018 -0
  75. package/www/codemirror/addon/mode/loadmode.js +66 -0
  76. package/www/codemirror/addon/mode/multiplex.js +136 -0
  77. package/www/codemirror/addon/mode/multiplex_test.js +49 -0
  78. package/www/codemirror/addon/mode/overlay.js +90 -0
  79. package/www/codemirror/addon/mode/simple.js +216 -0
  80. package/www/codemirror/addon/runmode/colorize.js +40 -0
  81. package/www/codemirror/addon/runmode/runmode-standalone.js +334 -0
  82. package/www/codemirror/addon/runmode/runmode.js +76 -0
  83. package/www/codemirror/addon/runmode/runmode.node.js +329 -0
  84. package/www/codemirror/addon/scroll/annotatescrollbar.js +128 -0
  85. package/www/codemirror/addon/scroll/scrollpastend.js +48 -0
  86. package/www/codemirror/addon/scroll/simplescrollbars.css +66 -0
  87. package/www/codemirror/addon/scroll/simplescrollbars.js +152 -0
  88. package/www/codemirror/addon/search/jump-to-line.js +53 -0
  89. package/www/codemirror/addon/search/match-highlighter.js +167 -0
  90. package/www/codemirror/addon/search/matchesonscrollbar.css +8 -0
  91. package/www/codemirror/addon/search/matchesonscrollbar.js +97 -0
  92. package/www/codemirror/addon/search/search.js +295 -0
  93. package/www/codemirror/addon/search/searchcursor.js +305 -0
  94. package/www/codemirror/addon/selection/active-line.js +72 -0
  95. package/www/codemirror/addon/selection/mark-selection.js +119 -0
  96. package/www/codemirror/addon/selection/selection-pointer.js +98 -0
  97. package/www/codemirror/addon/tern/tern.css +87 -0
  98. package/www/codemirror/addon/tern/tern.js +756 -0
  99. package/www/codemirror/addon/tern/worker.js +44 -0
  100. package/www/codemirror/addon/wrap/hardwrap.js +160 -0
  101. package/www/codemirror/bin/source-highlight +48 -0
  102. package/www/codemirror/keymap/emacs.js +545 -0
  103. package/www/codemirror/keymap/sublime.js +720 -0
  104. package/www/codemirror/keymap/vim.js +5978 -0
  105. package/www/codemirror/lib/codemirror.css +344 -0
  106. package/www/codemirror/lib/codemirror.js +9874 -0
  107. package/www/codemirror/mode/apl/apl.js +174 -0
  108. package/www/codemirror/mode/asciiarmor/asciiarmor.js +74 -0
  109. package/www/codemirror/mode/asn.1/asn.1.js +204 -0
  110. package/www/codemirror/mode/asterisk/asterisk.js +220 -0
  111. package/www/codemirror/mode/brainfuck/brainfuck.js +85 -0
  112. package/www/codemirror/mode/clike/clike.js +940 -0
  113. package/www/codemirror/mode/clojure/clojure.js +292 -0
  114. package/www/codemirror/mode/cmake/cmake.js +97 -0
  115. package/www/codemirror/mode/cobol/cobol.js +255 -0
  116. package/www/codemirror/mode/coffeescript/coffeescript.js +359 -0
  117. package/www/codemirror/mode/commonlisp/commonlisp.js +125 -0
  118. package/www/codemirror/mode/crystal/crystal.js +433 -0
  119. package/www/codemirror/mode/css/css.js +862 -0
  120. package/www/codemirror/mode/cypher/cypher.js +152 -0
  121. package/www/codemirror/mode/d/d.js +223 -0
  122. package/www/codemirror/mode/dart/dart.js +166 -0
  123. package/www/codemirror/mode/diff/diff.js +47 -0
  124. package/www/codemirror/mode/django/django.js +356 -0
  125. package/www/codemirror/mode/dockerfile/dockerfile.js +211 -0
  126. package/www/codemirror/mode/dtd/dtd.js +142 -0
  127. package/www/codemirror/mode/dylan/dylan.js +352 -0
  128. package/www/codemirror/mode/ebnf/ebnf.js +195 -0
  129. package/www/codemirror/mode/ecl/ecl.js +206 -0
  130. package/www/codemirror/mode/eiffel/eiffel.js +160 -0
  131. package/www/codemirror/mode/elm/elm.js +245 -0
  132. package/www/codemirror/mode/erlang/erlang.js +619 -0
  133. package/www/codemirror/mode/factor/factor.js +85 -0
  134. package/www/codemirror/mode/fcl/fcl.js +173 -0
  135. package/www/codemirror/mode/forth/forth.js +180 -0
  136. package/www/codemirror/mode/fortran/fortran.js +188 -0
  137. package/www/codemirror/mode/gas/gas.js +353 -0
  138. package/www/codemirror/mode/gfm/gfm.js +129 -0
  139. package/www/codemirror/mode/gherkin/gherkin.js +178 -0
  140. package/www/codemirror/mode/go/go.js +187 -0
  141. package/www/codemirror/mode/groovy/groovy.js +247 -0
  142. package/www/codemirror/mode/haml/haml.js +161 -0
  143. package/www/codemirror/mode/handlebars/handlebars.js +70 -0
  144. package/www/codemirror/mode/haskell/haskell.js +268 -0
  145. package/www/codemirror/mode/haskell-literate/haskell-literate.js +43 -0
  146. package/www/codemirror/mode/haxe/haxe.js +515 -0
  147. package/www/codemirror/mode/htmlembedded/htmlembedded.js +37 -0
  148. package/www/codemirror/mode/htmlmixed/htmlmixed.js +153 -0
  149. package/www/codemirror/mode/http/http.js +113 -0
  150. package/www/codemirror/mode/idl/idl.js +290 -0
  151. package/www/codemirror/mode/javascript/javascript.js +960 -0
  152. package/www/codemirror/mode/jinja2/jinja2.js +193 -0
  153. package/www/codemirror/mode/jsx/jsx.js +148 -0
  154. package/www/codemirror/mode/julia/julia.js +390 -0
  155. package/www/codemirror/mode/livescript/livescript.js +280 -0
  156. package/www/codemirror/mode/lua/lua.js +160 -0
  157. package/www/codemirror/mode/markdown/markdown.js +886 -0
  158. package/www/codemirror/mode/mathematica/mathematica.js +176 -0
  159. package/www/codemirror/mode/mbox/mbox.js +129 -0
  160. package/www/codemirror/mode/meta.js +221 -0
  161. package/www/codemirror/mode/mirc/mirc.js +193 -0
  162. package/www/codemirror/mode/mllike/mllike.js +359 -0
  163. package/www/codemirror/mode/modelica/modelica.js +245 -0
  164. package/www/codemirror/mode/mscgen/mscgen.js +175 -0
  165. package/www/codemirror/mode/mumps/mumps.js +148 -0
  166. package/www/codemirror/mode/nginx/nginx.js +178 -0
  167. package/www/codemirror/mode/nsis/nsis.js +95 -0
  168. package/www/codemirror/mode/ntriples/ntriples.js +195 -0
  169. package/www/codemirror/mode/octave/octave.js +139 -0
  170. package/www/codemirror/mode/oz/oz.js +252 -0
  171. package/www/codemirror/mode/pascal/pascal.js +136 -0
  172. package/www/codemirror/mode/pegjs/pegjs.js +111 -0
  173. package/www/codemirror/mode/perl/perl.js +836 -0
  174. package/www/codemirror/mode/php/php.js +234 -0
  175. package/www/codemirror/mode/pig/pig.js +178 -0
  176. package/www/codemirror/mode/powershell/powershell.js +398 -0
  177. package/www/codemirror/mode/properties/properties.js +78 -0
  178. package/www/codemirror/mode/protobuf/protobuf.js +72 -0
  179. package/www/codemirror/mode/pug/pug.js +591 -0
  180. package/www/codemirror/mode/puppet/puppet.js +220 -0
  181. package/www/codemirror/mode/python/python.js +402 -0
  182. package/www/codemirror/mode/q/q.js +139 -0
  183. package/www/codemirror/mode/r/r.js +190 -0
  184. package/www/codemirror/mode/rpm/changes/index.html +66 -0
  185. package/www/codemirror/mode/rpm/rpm.js +109 -0
  186. package/www/codemirror/mode/rst/rst.js +557 -0
  187. package/www/codemirror/mode/ruby/ruby.js +303 -0
  188. package/www/codemirror/mode/rust/rust.js +72 -0
  189. package/www/codemirror/mode/sas/sas.js +303 -0
  190. package/www/codemirror/mode/sass/sass.js +459 -0
  191. package/www/codemirror/mode/scheme/scheme.js +284 -0
  192. package/www/codemirror/mode/shell/shell.js +168 -0
  193. package/www/codemirror/mode/sieve/sieve.js +193 -0
  194. package/www/codemirror/mode/slim/slim.js +575 -0
  195. package/www/codemirror/mode/smalltalk/smalltalk.js +168 -0
  196. package/www/codemirror/mode/smarty/smarty.js +225 -0
  197. package/www/codemirror/mode/solr/solr.js +104 -0
  198. package/www/codemirror/mode/soy/soy.js +665 -0
  199. package/www/codemirror/mode/sparql/sparql.js +184 -0
  200. package/www/codemirror/mode/spreadsheet/spreadsheet.js +112 -0
  201. package/www/codemirror/mode/sql/sql.js +525 -0
  202. package/www/codemirror/mode/stex/stex.js +264 -0
  203. package/www/codemirror/mode/stylus/stylus.js +775 -0
  204. package/www/codemirror/mode/swift/swift.js +221 -0
  205. package/www/codemirror/mode/tcl/tcl.js +140 -0
  206. package/www/codemirror/mode/textile/textile.js +469 -0
  207. package/www/codemirror/mode/tiddlywiki/tiddlywiki.css +14 -0
  208. package/www/codemirror/mode/tiddlywiki/tiddlywiki.js +308 -0
  209. package/www/codemirror/mode/tiki/tiki.css +26 -0
  210. package/www/codemirror/mode/tiki/tiki.js +312 -0
  211. package/www/codemirror/mode/toml/toml.js +88 -0
  212. package/www/codemirror/mode/tornado/tornado.js +68 -0
  213. package/www/codemirror/mode/troff/troff.js +84 -0
  214. package/www/codemirror/mode/ttcn/ttcn.js +283 -0
  215. package/www/codemirror/mode/ttcn-cfg/ttcn-cfg.js +214 -0
  216. package/www/codemirror/mode/turtle/turtle.js +162 -0
  217. package/www/codemirror/mode/twig/twig.js +141 -0
  218. package/www/codemirror/mode/vb/vb.js +275 -0
  219. package/www/codemirror/mode/vbscript/vbscript.js +350 -0
  220. package/www/codemirror/mode/velocity/velocity.js +202 -0
  221. package/www/codemirror/mode/verilog/verilog.js +781 -0
  222. package/www/codemirror/mode/vhdl/vhdl.js +189 -0
  223. package/www/codemirror/mode/vue/vue.js +77 -0
  224. package/www/codemirror/mode/wast/wast.js +132 -0
  225. package/www/codemirror/mode/webidl/webidl.js +195 -0
  226. package/www/codemirror/mode/xml/xml.js +417 -0
  227. package/www/codemirror/mode/xquery/xquery.js +448 -0
  228. package/www/codemirror/mode/yacas/yacas.js +204 -0
  229. package/www/codemirror/mode/yaml/yaml.js +120 -0
  230. package/www/codemirror/mode/yaml-frontmatter/yaml-frontmatter.js +72 -0
  231. package/www/codemirror/mode/z80/z80.js +116 -0
  232. package/www/codemirror/package.json +52 -0
  233. package/www/codemirror/rollup.config.js +52 -0
  234. package/www/codemirror/src/addon/runmode/codemirror-standalone.js +24 -0
  235. package/www/codemirror/src/addon/runmode/codemirror.node.js +21 -0
  236. package/www/codemirror/src/addon/runmode/runmode-standalone.js +2 -0
  237. package/www/codemirror/src/addon/runmode/runmode.node.js +2 -0
  238. package/www/codemirror/src/codemirror.js +3 -0
  239. package/www/codemirror/src/display/Display.js +116 -0
  240. package/www/codemirror/src/display/focus.js +50 -0
  241. package/www/codemirror/src/display/gutters.js +44 -0
  242. package/www/codemirror/src/display/highlight_worker.js +55 -0
  243. package/www/codemirror/src/display/line_numbers.js +48 -0
  244. package/www/codemirror/src/display/mode_state.js +22 -0
  245. package/www/codemirror/src/display/operations.js +206 -0
  246. package/www/codemirror/src/display/scroll_events.js +132 -0
  247. package/www/codemirror/src/display/scrollbars.js +194 -0
  248. package/www/codemirror/src/display/scrolling.js +186 -0
  249. package/www/codemirror/src/display/selection.js +173 -0
  250. package/www/codemirror/src/display/update_display.js +267 -0
  251. package/www/codemirror/src/display/update_line.js +189 -0
  252. package/www/codemirror/src/display/update_lines.js +82 -0
  253. package/www/codemirror/src/display/view_tracking.js +153 -0
  254. package/www/codemirror/src/edit/CodeMirror.js +217 -0
  255. package/www/codemirror/src/edit/commands.js +178 -0
  256. package/www/codemirror/src/edit/deleteNearSelection.js +30 -0
  257. package/www/codemirror/src/edit/drop_events.js +130 -0
  258. package/www/codemirror/src/edit/fromTextArea.js +61 -0
  259. package/www/codemirror/src/edit/global_events.js +45 -0
  260. package/www/codemirror/src/edit/key_events.js +163 -0
  261. package/www/codemirror/src/edit/legacy.js +62 -0
  262. package/www/codemirror/src/edit/main.js +69 -0
  263. package/www/codemirror/src/edit/methods.js +555 -0
  264. package/www/codemirror/src/edit/mouse_events.js +417 -0
  265. package/www/codemirror/src/edit/options.js +194 -0
  266. package/www/codemirror/src/edit/utils.js +7 -0
  267. package/www/codemirror/src/input/ContentEditableInput.js +546 -0
  268. package/www/codemirror/src/input/TextareaInput.js +380 -0
  269. package/www/codemirror/src/input/indent.js +71 -0
  270. package/www/codemirror/src/input/input.js +134 -0
  271. package/www/codemirror/src/input/keymap.js +147 -0
  272. package/www/codemirror/src/input/keynames.js +17 -0
  273. package/www/codemirror/src/input/movement.js +111 -0
  274. package/www/codemirror/src/line/highlight.js +284 -0
  275. package/www/codemirror/src/line/line_data.js +349 -0
  276. package/www/codemirror/src/line/pos.js +40 -0
  277. package/www/codemirror/src/line/saw_special_spans.js +10 -0
  278. package/www/codemirror/src/line/spans.js +390 -0
  279. package/www/codemirror/src/line/utils_line.js +85 -0
  280. package/www/codemirror/src/measurement/position_measurement.js +702 -0
  281. package/www/codemirror/src/measurement/widgets.js +26 -0
  282. package/www/codemirror/src/model/Doc.js +436 -0
  283. package/www/codemirror/src/model/change_measurement.js +61 -0
  284. package/www/codemirror/src/model/changes.js +339 -0
  285. package/www/codemirror/src/model/chunk.js +167 -0
  286. package/www/codemirror/src/model/document_data.js +112 -0
  287. package/www/codemirror/src/model/history.js +228 -0
  288. package/www/codemirror/src/model/line_widget.js +78 -0
  289. package/www/codemirror/src/model/mark_text.js +293 -0
  290. package/www/codemirror/src/model/selection.js +84 -0
  291. package/www/codemirror/src/model/selection_updates.js +216 -0
  292. package/www/codemirror/src/modes.js +96 -0
  293. package/www/codemirror/src/util/StringStream.js +90 -0
  294. package/www/codemirror/src/util/bidi.js +215 -0
  295. package/www/codemirror/src/util/browser.js +34 -0
  296. package/www/codemirror/src/util/dom.js +101 -0
  297. package/www/codemirror/src/util/event.js +103 -0
  298. package/www/codemirror/src/util/feature_detection.js +84 -0
  299. package/www/codemirror/src/util/misc.js +168 -0
  300. package/www/codemirror/src/util/operation_group.js +72 -0
  301. package/www/codemirror/theme/3024-day.css +41 -0
  302. package/www/codemirror/theme/3024-night.css +39 -0
  303. package/www/codemirror/theme/abbott.css +268 -0
  304. package/www/codemirror/theme/abcdef.css +32 -0
  305. package/www/codemirror/theme/ambiance-mobile.css +5 -0
  306. package/www/codemirror/theme/ambiance.css +74 -0
  307. package/www/codemirror/theme/ayu-dark.css +44 -0
  308. package/www/codemirror/theme/ayu-mirage.css +45 -0
  309. package/www/codemirror/theme/base16-dark.css +40 -0
  310. package/www/codemirror/theme/base16-light.css +38 -0
  311. package/www/codemirror/theme/bespin.css +34 -0
  312. package/www/codemirror/theme/blackboard.css +32 -0
  313. package/www/codemirror/theme/cobalt.css +25 -0
  314. package/www/codemirror/theme/colorforth.css +33 -0
  315. package/www/codemirror/theme/darcula.css +53 -0
  316. package/www/codemirror/theme/dracula.css +40 -0
  317. package/www/codemirror/theme/duotone-dark.css +35 -0
  318. package/www/codemirror/theme/duotone-light.css +36 -0
  319. package/www/codemirror/theme/eclipse.css +23 -0
  320. package/www/codemirror/theme/elegant.css +13 -0
  321. package/www/codemirror/theme/erlang-dark.css +34 -0
  322. package/www/codemirror/theme/gruvbox-dark.css +39 -0
  323. package/www/codemirror/theme/hopscotch.css +34 -0
  324. package/www/codemirror/theme/icecoder.css +43 -0
  325. package/www/codemirror/theme/idea.css +42 -0
  326. package/www/codemirror/theme/isotope.css +34 -0
  327. package/www/codemirror/theme/juejin.css +30 -0
  328. package/www/codemirror/theme/lesser-dark.css +47 -0
  329. package/www/codemirror/theme/liquibyte.css +95 -0
  330. package/www/codemirror/theme/lucario.css +37 -0
  331. package/www/codemirror/theme/material-darker.css +135 -0
  332. package/www/codemirror/theme/material-ocean.css +141 -0
  333. package/www/codemirror/theme/material-palenight.css +141 -0
  334. package/www/codemirror/theme/material.css +141 -0
  335. package/www/codemirror/theme/mbo.css +37 -0
  336. package/www/codemirror/theme/mdn-like.css +46 -0
  337. package/www/codemirror/theme/midnight.css +39 -0
  338. package/www/codemirror/theme/monokai.css +41 -0
  339. package/www/codemirror/theme/moxer.css +143 -0
  340. package/www/codemirror/theme/neat.css +12 -0
  341. package/www/codemirror/theme/neo.css +43 -0
  342. package/www/codemirror/theme/night.css +27 -0
  343. package/www/codemirror/theme/nord.css +42 -0
  344. package/www/codemirror/theme/oceanic-next.css +46 -0
  345. package/www/codemirror/theme/panda-syntax.css +85 -0
  346. package/www/codemirror/theme/paraiso-dark.css +38 -0
  347. package/www/codemirror/theme/paraiso-light.css +38 -0
  348. package/www/codemirror/theme/pastel-on-dark.css +52 -0
  349. package/www/codemirror/theme/railscasts.css +34 -0
  350. package/www/codemirror/theme/rubyblue.css +25 -0
  351. package/www/codemirror/theme/seti.css +44 -0
  352. package/www/codemirror/theme/shadowfox.css +52 -0
  353. package/www/codemirror/theme/solarized.css +165 -0
  354. package/www/codemirror/theme/ssms.css +16 -0
  355. package/www/codemirror/theme/the-matrix.css +30 -0
  356. package/www/codemirror/theme/tomorrow-night-bright.css +35 -0
  357. package/www/codemirror/theme/tomorrow-night-eighties.css +38 -0
  358. package/www/codemirror/theme/ttcn.css +64 -0
  359. package/www/codemirror/theme/twilight.css +32 -0
  360. package/www/codemirror/theme/vibrant-ink.css +34 -0
  361. package/www/codemirror/theme/xq-dark.css +53 -0
  362. package/www/codemirror/theme/xq-light.css +43 -0
  363. package/www/codemirror/theme/yeti.css +44 -0
  364. package/www/codemirror/theme/yonce.css +59 -0
  365. package/www/codemirror/theme/zenburn.css +37 -0
  366. package/www/index.html +216 -0
package/.dockerignore ADDED
@@ -0,0 +1,8 @@
1
+ .git/
2
+ design/
3
+ node_modules/
4
+
5
+ .dockerignore
6
+ Dockerfile
7
+ LICENSE
8
+ README.md
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: Build Docker Image
3
+
4
+ on:
5
+ push:
6
+ branches:
7
+ - master
8
+ - main
9
+ pull_request:
10
+ branches: [ main, master ]
11
+ # Allow manually triggering the workflow.
12
+ workflow_dispatch:
13
+
14
+ # Cancels all previous workflow runs for the same branch that have not yet completed.
15
+ concurrency:
16
+ # The concurrency group contains the workflow name and the branch name.
17
+ group: ${{ github.workflow }}-${{ github.ref }}
18
+ cancel-in-progress: true
19
+
20
+ jobs:
21
+ build-docker:
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - name: Create docker tag (from git reference)
25
+ # A tag name may only contain lower- and uppercase letters, digits, underscores, periods and dashes.
26
+ run: |
27
+ echo "TAG=$(echo -n "${{ github.ref_name }}" \
28
+ | tr --complement --squeeze-repeats '[:alnum:]._-' '_')" \
29
+ >> "${GITHUB_ENV}"
30
+
31
+ - uses: actions/checkout@v3
32
+
33
+ - name: Login to GitHub Container Registry
34
+ uses: docker/login-action@v2
35
+ with:
36
+ registry: ghcr.io
37
+ username: ${{ github.actor }}
38
+ password: ${{ secrets.GITHUB_TOKEN }}
39
+
40
+ - name: Build Docker Image
41
+ run: |
42
+ docker build \
43
+ --tag "ghcr.io/poef/jsontag-rest-server:${{ env.TAG }}" \
44
+ .
45
+ docker push "ghcr.io/poef/jsontag-rest-server:${{ env.TAG }}"
package/.gitignore~ ADDED
@@ -0,0 +1 @@
1
+ node_modules
package/Dockerfile ADDED
@@ -0,0 +1,60 @@
1
+ # ==============================================================================
2
+ # Dependencies
3
+ # ------------------------------------------------------------------------------
4
+ FROM node:18-alpine3.17 as builder
5
+
6
+ WORKDIR /usr/src/app
7
+ COPY package.json ./
8
+
9
+ # @TODO: Once development is stable, `npm ci --only=production` should be used instead of `npm install`
10
+ RUN npm install --omit=dev
11
+ # ==============================================================================
12
+
13
+
14
+ # ==============================================================================
15
+ # Application
16
+ # ------------------------------------------------------------------------------
17
+ FROM alpine:3.17
18
+
19
+ ENV NODE_ENV=production
20
+
21
+ RUN addgroup -g 1000 node \
22
+ && adduser -u 1000 -G node -s /bin/sh -D node \
23
+ && apk add --no-cache nodejs=~18.14
24
+
25
+ COPY --chown=node:node . /app
26
+ COPY --chown=node:node --from=builder /usr/src/app/node_modules /app/node_modules
27
+
28
+ WORKDIR /app
29
+
30
+ CMD [ "node", "/app/src/main.mjs" ]
31
+ # ==============================================================================
32
+
33
+
34
+ # ==============================================================================
35
+ # Metadata
36
+ # ------------------------------------------------------------------------------
37
+ # @TODO: Once development is stable, the following should also be added
38
+ # org.label-schema.build-date=${BUILD_DATE} \ # Usually $(date --iso-8601=seconds)
39
+ # org.label-schema.vcs-ref=${BUILD_REF} \ # Usually $(git describe --tags --always)
40
+ # org.label-schema.version=${VERSION} \ # Usually $(git describe --tags --abbrev=0)
41
+ # org.opencontainers.image.created="${BUILD_DATE}" \
42
+ # org.opencontainers.image.version="${VERSION}"
43
+ LABEL maintainer="Auke van Slooten <auke@muze.nl>" \
44
+ org.label-schema.description="JSONTag REST Server" \
45
+ org.label-schema.docker.cmd='docker run --expose 3000 --interactive --name=jsontag-rest-server --rm --tty --volume "\$PWD/my-data.json:/app/data.jsontag" jsontag-rest-server' \
46
+ org.label-schema.schema-version="1.0" \
47
+ org.label-schema.name="jsontag-rest-server" \
48
+ org.label-schema.url="https://github.com/poef/jsontag-rest-server" \
49
+ org.label-schema.usage="https://github.com/poef/jsontag-rest-server" \
50
+ org.label-schema.vcs-url="https://github.com/poef/jsontag-rest-server" \
51
+ org.label-schema.vendor="Auke van Slooten" \
52
+ org.opencontainers.image.authors="Auke van Slooten <auke@muze.nl>" \
53
+ org.opencontainers.image.description="JSONTag REST Server" \
54
+ org.opencontainers.image.documentation="https://github.com/poef/jsontag-rest-server" \
55
+ org.opencontainers.image.licenses="MIT" \
56
+ org.opencontainers.image.source="https://github.com/poef/jsontag-rest-server" \
57
+ org.opencontainers.image.title="jsontag-rest-server" \
58
+ org.opencontainers.image.url="https://github.com/poef/jsontag-rest-server" \
59
+ org.opencontainers.image.vendor="Auke van Slooten"
60
+ # ==============================================================================
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Muze
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # SimplyStore
2
+
3
+ SimplyStore is an attempt to create a radically simpler backend storage server. It does not have a database, certainly no SQL or GraphQL, it is not REST. In return it has a well defined API that is automatically derived from your dataset. It supports JSONTag to allow for semantically meaningful data, without having to do the full switch to Linked Data and triple stores. The query format is javascript, you can post javascript queries that will run on the server. All data is read into memory and is available to these javascript queries without needing (or allowing) disk access or indexes.
4
+
5
+ [JSONTag](https://github.com/poef/jsontag) is an enhancement over JSON that allows you to tag JSON data with metadata using HTML-like tags.
6
+ Javascript queries are run in a [VM2](https://www.npmjs.com/package/vm2) sandbox.
7
+
8
+
9
+ ## Installation
10
+
11
+ SimplyStore is a Node application. Start it by downloading this git repository:
12
+
13
+ ```shell
14
+ git clone git@github.com:simplyedit/simplystore
15
+ ```
16
+
17
+ Then install its dependencies:
18
+
19
+ ```shell
20
+ cd simplystore
21
+ npm install
22
+ ```
23
+
24
+ And start it up:
25
+
26
+ ```shell
27
+ npm start
28
+ ```
29
+
30
+ The server comes with a small demo dataset, which you can take a look at here `http://localhost:3000/`:
31
+
32
+ This page will show this:
33
+
34
+ ```
35
+ {
36
+ "persons":<link>"persons/"
37
+ }
38
+ ```
39
+
40
+ By following the link to `http://localhost:3000/persons/` you get:
41
+
42
+ ```
43
+ [
44
+ <object class="Person">{
45
+ "name":"John",
46
+ "dob":<date>"1972-09-20"
47
+ },
48
+ <object class="Person">{
49
+ "name":"Jane",
50
+ "dob":<date>"1986-01-01"
51
+ }
52
+ ]
53
+ ```
54
+
55
+ ## Goals of this project
56
+
57
+ SimplyStore is an attemp to see if we can create a more defined and usable REST like service, out of the box. One where all you need to do is change the data and add some access rights and get a self-describing, browseable, working API.
58
+
59
+ The SimplyStore design is predicated on the following realisations:
60
+
61
+ 1. Most data today will fit comfortably in memory in a commodity server.
62
+ 2. REST today is usually JSON-over-HTTP, but JSON crucially misses a <link> type.
63
+ 3. JSON is never just JSON. You need additional things like JSON-LD or JSON-Schema, to make sense of it.
64
+ 4. There is no clear onramp from JSON to Linked Data.
65
+ 5. Linked Data is very good for data / information exchange, but very costly for data manipulation and querying.
66
+
67
+ So the scope for jsontag-rest-server is:
68
+
69
+ - datasets that will fit comfortably in memory, for now I've set a test goal of about 1GB of data.
70
+ - usecases that are mostly-read, with sparse updates.
71
+ - scale-in-depth, so scale up is limited to the limits of a single computer system
72
+ - linked data (RDF et al) is not an immediate concern, but there must be a plausible onramp / conversion to and from linked data.
73
+
74
+ In addition, SimplyStore is meant to be a real-world testcase for JSONTag.
75
+
76
+ ## Roadmap
77
+
78
+ - [v] immutable dataset
79
+ - allow changes to dataset by creating a new root
80
+ - command handling with crud commands and command log
81
+ - backup current dataset to JSONTag file
82
+ - on startup check if any commands in the log haven't been resolved, if so run them
83
+
84
+ - improved web client with type-specific views and form elements
85
+
86
+ - Datalog query support
87
+ - [v] compile triple store from jsontag data
88
+ - [v] add query method
89
+ - [v] extend datalog query to allow for custom match functions
90
+ - [v] run /query post body in VM2 sandbox
91
+ - [v] immutable dataset in query vm
92
+ - add indexing and other optimizations
93
+ - add standard library of matching functions
94
+ - allow vanilla javascript array map/reduce/filter approach
95
+
96
+ - add support for metadata on each JSON pointer path (or better: each object)
97
+ - allow custom templates, instead of the default index.html
98
+ - add support for access control, based on webid / openid connect
99
+
package/data.jsontag ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "persons": [
3
+ <object id="john" class="Person">{
4
+ "name": "John",
5
+ "lastName": "Doe",
6
+ "dob": <date>"1972-09-20",
7
+ "foaf": [
8
+ <link>"jane"
9
+ ]
10
+ },
11
+ <object id="jane" class="Person">{
12
+ "name": "Jane",
13
+ "lastName": "Doe",
14
+ "dob": <date>"1986-01-01",
15
+ "foaf": [
16
+ <link>"john"
17
+ ]
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,25 @@
1
+ # access management (grants/rights/roles)
2
+
3
+ ## Read grants
4
+
5
+ You can easily create a read access check that just checks the query/ endpoint, but this is not interesting.
6
+ Much more interesting is how to implement granular read access rights on subtrees of the dataset.
7
+
8
+ Ariadne has a mechanism that has been succesfully used since 1998. It allows you to defined grant strings, like 'read', 'edit', etc. and configure them on a path inside a tree of data. Each entity below that path will automatically inherit the grant.
9
+
10
+ The grant is then checked for each interaction with the data. Because all interactions in Ariadne are through templates, which can be custom made, the grants can also be custom strings. This way you can grow the access management system to your own needs.
11
+
12
+ However, this means that all read access must come through the data tree. Earlier we defined a /uuid/ endpoint which would give direct access to any object. This breaks this paradigm. Objects can be linked in multiple locations in the dataset. To check for read grants, you must find all valid paths to an object and check if any of them allow the user to 'read' the object. This is potentially very costly.
13
+
14
+ The easiest solution is to drop the /uuid endpoint, unless all data is publically readable. Each object can still have a uuid, but it is no longer a url that points to the objects contents.
15
+
16
+ Another problem is the changing nature of the JSON Pointer path in the URL. If an object is part of an array, and another object earlier (with smalled index) is removed, the URL for this object changes. Its index is lowered. So you cannot assign grants on a JSON Pointer path, containing an array index, with any hope of the grants staying in the correct spot.
17
+
18
+ If you instead assign grants to objects directly, the grants will correctly move when an array is updated. However this opens up the possibility that grants appear in multiple places, if the object is linked in multiple places. This should not be a problem though, since the subtree of objects is the same as long as the object is the same.
19
+
20
+ This opens up the possibility of storing the grants in an attribute on the JSONTag tag of the object. e.g.
21
+
22
+ <object class="Site" grants="user1: read edit, user2: read edit delete">{ ... }
23
+
24
+ Here we need to carefully consider how to treat links. Do we need read access to follow a link, or is that implied?
25
+
package/design/acid.md ADDED
@@ -0,0 +1,31 @@
1
+ # JSONTAG REST Server - Commands / CQRS
2
+
3
+ Why the need for CQRS (Command Query Responsibility Segregation?)
4
+
5
+ The design of the server is purposely simple. It does away with a seperate database system, instead reading all data in memory, giving access to that data to queries written in javascript, using native javascript objects. This does away with the relational-object impedance mismatch. It does away with SQL and ORM solutions.
6
+
7
+ However, databases have the nice property that they are ACID compliant. We don't want to lose that. ACID stands for 'Atomicity', 'Consistency', 'Isolation' and 'Durability'. Our server should exhibit these same properties. Isolation is handled by handling each query in a seperate VM, using immutable data. But immutable data also means we need a different mechanism to update data, other than plain javascript.
8
+
9
+ Why the need for immutable data? This is tied to the ACID requirements. If you have multiple processes working with the same, shared, data, there is a good chance of creating inconsistent data. One process changes the data in some way, while another process tries to change the exact same data. One of these processes will 'win', potentially undoing the other process' change. Imagine you have a shop with an inventory. If product X has 1 item in the inventory, and there are two requests to buy that product being handled by two different processes at the same time, you could end up selling the same item twice.
10
+
11
+ There are ways around this, using locks and mutexes, but these are notoriously difficult to get right.
12
+
13
+ Just using immutable data alone won't solve this problem. Both processes will still see that there is 1 product X in store. But if we only allow updates / changes, through a single sequential process, and we allow for the possibility that a change request can fail, we can solve this problem much easier.
14
+
15
+ So both processes see that there is one item left in inventory. Both processes now create a command (buyProduct(X)) and send it to the command queue. They both get a unique ID as a result, and they can listen for an update on that ID. But there is only a single command handler, which handles commands on a First In First Out basis. The first buyProduct(X) command succeeds, and the inventory is decreased by 1. The next buyProduct(X) command fails its precondition, the inventory is 0. So it fails. Both processes get an update, one is a success, the other is a failure.
16
+
17
+ Now as the data is immutable, this is not entirely correct. In fact there is a difference between the client process calling the server, and the server process which only allows you to query the data. The server process can't create a command or listen for updates, only the client can. So when the client receives a success or failure resutl, asynchronously, it can send a new query to the server. The server in the mean time creates a new query handler, with the updated data after the changes from the last commands.
18
+
19
+ Because changes are only applied using the single process command handler, Atomicity is preserved. Each command is an atomic update, it either succeeds of fails. If it fails, none of the updates are kept, since the immutable data structure root is not updated.
20
+
21
+ Consistency is preserved. Each query request sees a concistent dataset, it may be slightly out of date, but it is internally consistent. Later queries will see a more up to date version of the data, so in the context of updates, the data is eventually consistent.
22
+
23
+ Isolation is preserved. Each query runs in its own VM, with its own access to the immutable data. Each command is run seperate from queries and custom javascript.
24
+
25
+ Durability is preserved. Each command is first written to a log, then the unique ID of the command is returned. The whole dataset is backup to disk once in a while, with the last command ID that has been processed. A backup can be restored, then all commands after the last processes one can be processed again.
26
+
27
+ The query handlers can run parallel, since they only have access to immutable data. In fact, each query handler process can use the exact same shared memory, keeping memory usage low.
28
+
29
+ There is a potential problem that a client sends a command, which the server enters into the command log, but before the command log id can be sent back to the client, the server dies. In that case the client cannot know if the command has been processed into the log. The only option is to send the command again. This means that a command that is meant to be processes once could be processed more than once. Commands must be written to ensure that this does not result in an inconsistent dataset. The easiest way to ensure this is to make sure the client gives each command a unique ID, a UUID. The server will then ignore any subsequent commands with the same id, and send a confirm reply back anyway.
30
+
31
+ The server must thus keep a log of all processed command id's. This can potentially grow so big as to impact performance, so there needs to be a timelimit on this processed log. We can enforce this by requiring a UUID with a timestamp, so either UUID V1 or V2. Any commands with a UUID older than the timelimit will be denied.
@@ -0,0 +1,25 @@
1
+ # commands (CQRS)
2
+
3
+ Updates/changes can only be done through commands. It uses a seperate endpoint (POST /update). There is only one process that handles this endpoint.
4
+
5
+ Each command must have a unique uuid, set by the client. A command is handled asynchronously, when fetching /update you will only get a response like 'command accepted'.
6
+
7
+ When the command is executed, the client is notified of the result (success or failure). This can be through a server-sent event, or though (long) polling. There should be an extra endpoint for each command, say GET /update/{uuid}
8
+
9
+ Commands are executed only once, but may be received more than once, this is why the client assigns the uuid. Commands with the same uuid (within a specified time window, say one day) are ignored.
10
+
11
+ commands are written to the command log. When this log is synced, then a response 'command accepted' is sent.
12
+
13
+ Each server can/should define its own commands, with as much semantics as possible. So instead of defining simple CRUD commands, a server should have meaningfull commands, with opaque inner workings.
14
+
15
+ A simple, but wrong, solution would be to implement a generic patch command, which uses jsonpatch. This woiuld allow for atomicity, thus fullfilling ACID requirements, but you cannot deduce from the command what the meaning of the change is. And you cannot change the data structure and command handling and then re-run all commands.
16
+
17
+ However if you create a data structure for support tickets, you could create a command 'createTicket', which would know what to change and where in the dataset. If the dataset changes, you can change the code in the createTicket command handler in tandem. This avoids most of the problems related to schema changes. And this means there is less need of versioning of the API.
18
+
19
+ You may still need to have versions of commands, so that if parameters to a command change, you can sense old commands through a version number.
20
+
21
+ Similarly, the query endpoint will need versioning to handle changes in the dataset structure, as all code is defined on the client side. This may be mitigated somewhat by providing a custom library of semantic methods for the client to use, e.g. `searchTickets(...)`.
22
+
23
+ Older versions of the query api can be simulated through a transformer, which translates/transforms from/to the new structure.
24
+
25
+ Commands can change the datastructure, by creating a new immutable root and then starving/stopping the current VM processes and starting new VM processes with the new root.
@@ -0,0 +1,71 @@
1
+ # Identity
2
+
3
+ One of many pitfalls in REST API's is mismanaging of the identity of entities. So we need to get this right here.
4
+
5
+ - Each entity should have exactly one identity, over time. You can have different versions of an entity, as in older versions that have been updated since. The main identity always points to the latest version in that case.
6
+ - Each identity should be a URL. This is because we want to make sure we can easily upgrade the API to a Linked Data API.
7
+ - If you implement versioning, you should be able to reference a specific version, also through a URL.
8
+ - If the JSONTag data adds a link to an object/entity, it should use the same ID (URL)
9
+ - Each entity must have a unique ID
10
+
11
+ A simple solution would be to use the API URL + JSON Pointer as the id of all entities. This fails for these reasons (at least):
12
+ - Entities may appear on multiple JSON Pointer paths, as they can be linked in multiple places
13
+ - Entities that are part of an array of entities, may have there JSON Pointer path changed when another, prior, entity is removed from the array. e.g.
14
+
15
+ ```jsontag
16
+ [
17
+ {
18
+ "title":"Entity 1"
19
+ },
20
+ {
21
+ "title":"Entity 2"
22
+ }
23
+ ]
24
+ ```
25
+
26
+ Here you could select entity 2 using the JSON Pointer '/1/'. However as soon as entity 1 is removed, the JSON Pointer would become '/0/'.
27
+
28
+ Another solution would be to explicitly assign all objects a unique id, perhaps a UUID. Then you can use that with a specific Map container, e.g.
29
+
30
+ ```
31
+ /uuid/9b91dcfd-dae7-47dc-b90f-a51b37e6dd3e
32
+ ```
33
+
34
+ And all results would have their id encoded like this:
35
+
36
+ ```jsontag
37
+ [
38
+ <object id="/uuid/9b91dcfd-dae7-47dc-b90f-a51b37e6dd3e">{
39
+ "title":"Entity 2"
40
+ }
41
+ ]
42
+ ```
43
+
44
+ The problem here is that the system now forces everything to use a specific UUID type as ID. This may not be the best option. And the URL that is used as the ID is much less expressive than it could be. All entities are forced to use the same non-descriptive ID style. The benefit is that at least some common pitfalls in assigning ID values are avoided...
45
+
46
+ The /uuid/ map should be invisible, as in, it should not be returned in any result. You can only use it to query a specific entity by ID.
47
+ UUID's should only be assigned to object values. All other values are supposed to be just values, even if they get parsed into Value Objects, like `<date>`.
48
+
49
+ One possible problem is that we've now encoded the identity as an attribute, instead of a property. This may come as a surprise to users of the API. However I do think that it is a better fit. Consider the JSON-LD '@id' property, which fulfills a similar role. It too is encoded seperate from normal properties, using the '@' prefix.
50
+
51
+ The /uuid/ endpoint can be written as a search in the dataset for an entity with the given ID. This way it is not part of the dataset itself, and thus 'invisible'.
52
+
53
+ If you need versioning, you could add a second parameter for the version, like this:
54
+
55
+ ```
56
+ /uuid/9b91dcfd-dae7-47dc-b90f-a51b37e6dd3e/1
57
+ ```
58
+
59
+ or
60
+
61
+ ```
62
+ /uuid/9b91dcfd-dae7-47dc-b90f-a51b37e6dd3e/latest
63
+ ```
64
+
65
+ To prevent collisions with a 'uuid' property in the dataset root, a normal JSON Pointer for the dataset could start with the `/query/` root path, e.g.:
66
+
67
+ ```
68
+ api.url/query/tasks/
69
+ ```
70
+
71
+ This would also make room for a seperate `/update/` path, which would handle update commands to the dataset.
@@ -0,0 +1,26 @@
1
+ # Immutable JSONTag data
2
+
3
+ 1 - When parsing jsontag data, each entry is frozen immediately after instantiation.
4
+ 2 - Add a clone method to clone an object (shallow clone)
5
+ 3 - a clone is mutable
6
+ 4 - all objects linking to the original object get cloned as well, and the link updated to the new clone
7
+ 5 - once all updates are done, you can freeze the root object again and it will also freeze all clones
8
+ 6 - identity. Each object should have a clear identity, that is the same over mutations, as well as a static identity that changes with mutations. (one indentity-over-time, one identity-per-version)
9
+ 7 - cleanup. Any object no longer linked should be removed, unless you want automatic versioning. In that case the root objects versions should all be kept, and therefor all objects remain linked.
10
+
11
+ ## Problems
12
+
13
+ ### 4 - all objects linking to the original object get cloned as well
14
+
15
+ This means that we need an index for each object, containing all other objects linking to it.
16
+ We can do this while parsing the JSONTag data, just make a weakmap for all objects, while treewalking the dataset.
17
+
18
+ ### which acl grants apply to objects that are linked more than once?
19
+
20
+ The simplest solution seems to just use the acl metadata gathered while following the path used.
21
+
22
+ ## existing solutions
23
+
24
+ - immutable.js
25
+ - immer
26
+ - redux