@_linked/server 3.8.0 → 3.9.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.
@@ -134,7 +134,7 @@ export declare class RemoteDataset implements IDataset {
134
134
  graphQuery(query: GraphQuery): Promise<GraphResult>;
135
135
  /**
136
136
  * The key this store's graph-registry cache is kept under: the data server's url and the
137
- * dataset it named in its `ping` answer (CN's gateway answers `{dataset}`), so stores of the
137
+ * dataset it named in its `ping` answer (a gateway answers `{dataset}`), so stores of the
138
138
  * same dataset share one entry; else one key per instance. (`POST /admin/graph-registry-changed`
139
139
  * drops every entry of the process, whatever its key.)
140
140
  */
@@ -222,7 +222,7 @@ export class RemoteDataset {
222
222
  }
223
223
  /**
224
224
  * The key this store's graph-registry cache is kept under: the data server's url and the
225
- * dataset it named in its `ping` answer (CN's gateway answers `{dataset}`), so stores of the
225
+ * dataset it named in its `ping` answer (a gateway answers `{dataset}`), so stores of the
226
226
  * same dataset share one entry; else one key per instance. (`POST /admin/graph-registry-changed`
227
227
  * drops every entry of the process, whatever its key.)
228
228
  */
@@ -1 +1 @@
1
- {"version":3,"file":"RemoteDataset.js","sourceRoot":"","sources":["../../../../src/shapes/quadstores/RemoteDataset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAmB5D,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,qBAAqB,GAEtB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,iBAAiB,GAElB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AA2B7E;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAEnD,2FAA2F;AAC3F,sFAAsF;AACtF,+FAA+F;AAC/F,iFAAiF;AACjF,MAAM,gBAAgB,GAAG,MAAM,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;AAEzE,SAAS,kBAAkB;IACzB,MAAM,KAAK,GAAI,UAAuE,CAAC,gBAAgB,CAAC,CAAC;IACzG,OAAO,OAAO,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,QAAQ,CAAA,KAAK,UAAU,CAAC,CAAC,CAAE,KAAK,CAAC,QAAQ,EAA8B,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3G,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;;IACjC,MAAM,GAAG,GAAG,kBAAkB,EAAE,CAAC;IACjC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,SAAS,CAAC;IAClD,MAAM,KAAK,GAAG,MAAA,MAAA,MAAA,MAAA,GAAG,CAAC,OAAO,0CAAE,UAAU,0CAAE,WAAW,0CAAE,SAAS,0CAAE,EAAE,CAAC;IAClE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAChE,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAQ3C,YAAY,OAAe,EAAE,GAAW,EAAE,MAAc,EAAE,IAAa;QACrE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;QAC5B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,IAAI,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAC3C,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,6BAA8B,SAAQ,kBAAkB;CAAG;AAExE,4EAA4E;AAC5E,MAAM,OAAO,8BAA+B,SAAQ,kBAAkB;CAAG;AAEzE,kGAAkG;AAClG,gFAAgF;AAChF,iGAAiG;AACjG,gBAAgB,CAAC,kBAAkB,EAAE,iBAAiB,EAAE,oBAAoB,CAAC,CAAC;AAC9E,gBAAgB,CAAC,6BAA6B,EAAE,iBAAiB,EAAE,+BAA+B,CAAC,CAAC;AACpG,gBAAgB,CAAC,8BAA8B,EAAE,iBAAiB,EAAE,gCAAgC,CAAC,CAAC;AAkBtG,sFAAsF;AACtF,MAAM,mBAAmB,GAAwB,IAAI,GAAG,CAAsB;IAC5E,wBAAwB;IACxB,sBAAsB;IACtB,sBAAsB;IACtB,cAAc;CACf,CAAC,CAAC;AAEH;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,GAAY,EAAE,KAAiB;IACxD,IAAI,iBAAiB,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC;IACvC,MAAM,IAAI,GAAI,GAAiC,aAAjC,GAAG,uBAAH,GAAG,CAAgC,IAAI,CAAC;IACtD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,IAAI,OAAoC,CAAC;IACzC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9D,OAAO,GAAG,IAAI,eAAe,CAAC,IAA2B,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACnF,CAAC;SAAM,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAI,GAAmC,aAAnC,GAAG,uBAAH,GAAG,CAAkC,MAAM,CAAC;QAC5D,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,GAAG,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACxF,CAAC;IACD,IAAI,CAAC,OAAO;QAAE,OAAO,GAAG,CAAC;IACxB,OAA+B,CAAC,KAAK,GAAG,GAAG,CAAC;IAC7C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,sGAAsG;AACtG,MAAM,iBAAiB,GAAgC,IAAI,GAAG,CAAC;IAC7D,CAAC,MAAM,CAAC,aAAa,EAAE,aAAa,CAAC,WAAW,CAAC,EAAE,CAAC;IACpD,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;CAC/C,CAAC,CAAC;AAEH,IAAI,eAAe,GAAG,CAAC,CAAC;AAExB,gFAAgF;AAChF,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,aAAa;IAgBxB,YAAY,MAAqC;QAPjD,oGAAoG;QAC3F,mBAAc,GAAY,IAAI,CAAC;QAGvB,wBAAmB,GAAG,kBAAkB,eAAe,EAAE,EAAE,CAAC;QACrE,uBAAkB,GAAG,KAAK,CAAC;QAGjC,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,sBAAsB,MAAM,EAAE,CAAC;YACjE,OAAO;QACT,CAAC;QACD,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YACnC,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YACjD,MAAM,KAAK,GAAG,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1E,IAAI,KAAK;gBAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QAChC,CAAC;QACD,IAAI,MAAM,CAAC,cAAc,KAAK,KAAK,IAAI,MAAM,CAAC,cAAc,KAAK,OAAO,EAAE,CAAC;YACzE,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;QAC9B,CAAC;QACD,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACd,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,sBAAsB,MAAM,CAAC,IAAI,EAAE,CAAC;QACxE,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED;;;OAGG;IACH,IAAI;QACF,6EAA6E;QAC7E,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC9D,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,CAC3C,CAAC,IAAI,EAAE,EAAE;gBACP,MAAM,IAAI,GAA0B,MAAM,CAAC,MAAM,CAC/C,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;oBACtD,CAAC,CAAC,EAAE,GAAI,IAAgC,EAAE;oBAC1C,CAAC,CAAC,EAAE,CACP,CAAC;gBACF,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACrB,OAAO,IAAI,CAAC;YACd,CAAC,EACD,CAAC,GAAG,EAAE,EAAE;gBACN,4CAA4C;gBAC5C,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;gBAC7B,MAAM,GAAG,CAAC;YACZ,CAAC,CACF,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,yFAAyF;IACzF,oFAAoF;IACpF,gBAAgB;IAChB,KAAK,CAAC,WAAW,CAAC,KAA+B;QAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,KAAe;QAC5B,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC1E,CAAC;IAED,WAAW,CAAC,KAAkB;QAC5B,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,WAAW,CAAC,KAAkB;QAC5B,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,WAAW,CAAC,KAAkB;QAC5B,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IAClD,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,UAAU,CAAC,KAAiB;QAChC,IAAI,MAAmB,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAc,YAAY,EAAE,KAAK,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,iBAAiB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;YAAE,cAAc,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,KAAK,CAAC,CAAC;QAC3E,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;OAKG;IACO,gBAAgB;;QACxB,MAAM,OAAO,GAAG,MAAA,IAAI,CAAC,QAAQ,0CAAE,OAAO,CAAC;QACvC,OAAO,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,IAAI,IAAI,CAAC,GAAG;YACvD,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,EAAE;YAC1B,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACH,uBAAuB,CAAC,OAAgB;QACtC,uBAAuB,CAAC,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED;;;OAGG;IACO,KAAK,CAAC,iBAAiB;QAC/B,MAAM,IAAI,GAAuB,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,CAAC,aAAa,EAAE,WAAW,CAAU,CAAC;QACpD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjF,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE;;YAC7B,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5C,KAAK,MAAM,MAAM,IAAI,MAAC,OAA0C,mCAAI,EAAE,EAAE,CAAC;gBACvE,IAAI,OAAO,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,EAAE,CAAA,KAAK,QAAQ,IAAI,MAAM,CAAC,EAAE;oBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;YAChG,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;;OASG;IACK,KAAK,CAAC,cAAc,CAAC,IAAa;QACxC,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC7E,MAAM,KAAK,GAAI,IAA6B,CAAC,MAAM,CAAC;QACpD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAC7F,IAAI,QAAuD,CAAC;QAC5D,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC;QAC9F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC7B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;gBAC/B,OAAO,CAAC,IAAI,CACV,uDAAuD,IAAI,CAAC,GAAG,sCAAsC,EACrG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CACzC,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,EAAE;YAC1B,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,CAAC,CAAC;QACF,KAAK,MAAM,IAAI,IAAI,KAAiB,EAAE,CAAC;YACrC,MAAM,WAAW,GAAG,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAChD,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,GAAG,CAAC,IAAI,CAAC,CAAC;gBACV,SAAS;YACX,CAAC;YACD,MAAM,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;YACvG,IAAI,MAAM,CAAC,MAAM;gBAAE,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;;gBACzC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC;QACD,OAAO,EAAE,GAAI,IAAgC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IAC/D,CAAC;IAEO,IAAI,CAAI,MAAc,EAAE,IAAa;QAC3C,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAClE,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAe,CAAC;IACjD,CAAC;IAED;;;;;OAKG;IACK,WAAW,CAAI,MAAc,EAAE,IAAa;QAClD,oDAAoD;QACpD,+EAA+E;QAC/E,0EAA0E;QAC1E,yDAAyD;QACzD,OAAO,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;IACzE,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,IAAI,CAAC,MAAc,EAAE,IAAe;;QAChD,MAAM,IAAI,GAAG,MAAA,IAAI,CAAC,GAAG,mCAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,6BAA6B,CACrC,kFAAkF,MAAM,EAAE,EAC1F,EAAE,EACF,CAAC,CACF,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,GAAG,IAAI,SAAS,WAAW,IAAI,MAAM,EAAE,CAAC;QACvD,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;YAClC,MAAM,EAAE,kBAAkB;SAC3B,CAAC;QACF,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,aAAa,GAAG,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC;QAC/D,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,mBAAmB,EAAE,CAAC;YACtC,IAAI,OAAO;gBAAE,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC;QAClD,CAAC;QAED,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE;gBACxC,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC;aAC/B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC/D,MAAM,IAAI,6BAA6B,CACrC,kDAAkD,IAAI,KAAK,MAAM,MAAM,KAAK,EAAE,EAC9E,IAAI,EACJ,CAAC,CACF,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,IAAI,GAAY,SAAS,CAAC;QAC9B,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC;gBACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACxB,MAAM,GAAG,IAAI,CAAC;YAChB,CAAC;YAAC,WAAM,CAAC;gBACP,IAAI,GAAG,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YAChB,IAAI,MAAM;gBAAE,OAAO,IAAI,CAAC;YACxB,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBACtB,MAAM,IAAI,6BAA6B,CACrC,qCAAqC,IAAI,6CAA6C,EACtF,IAAI,EACJ,QAAQ,CAAC,MAAM,CAChB,CAAC;YACJ,CAAC;YACD,IAAI,CAAC,IAAI;gBAAE,OAAO,SAAS,CAAC;YAC5B,MAAM,IAAI,kBAAkB,CAC1B,qCAAqC,IAAI,aAAa,MAAM,+BAA+B,EAC3F,IAAI,EACJ,QAAQ,CAAC,MAAM,CAChB,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAE,IAAgC,CAAC,CAAC,CAAC,SAAS,CAAC;QAChG,MAAM,IAAI,GAAG,OAAO,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,IAAI,CAAA,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,MAAM,MAAM,GACV,OAAO,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,KAAK,CAAA,KAAK,QAAQ;YAC/B,CAAC,CAAC,MAAM,CAAC,KAAK;YACd,CAAC,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI;gBAChC,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC5B,MAAM,OAAO,GAAG,qCAAqC,IAAI,aAAa,MAAM,SAAS,QAAQ,CAAC,MAAM,GAClG,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,EACxB,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAEjC,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvD,MAAM,IAAI,8BAA8B,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACjF,CAAC;QACD,IAAI,MAAM,KAAK,MAAM,IAAI,CAAC,yBAAyB,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACrF,MAAM,IAAI,6BAA6B,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAChF,CAAC;QACD,MAAM,IAAI,kBAAkB,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACrE,CAAC;CACF","sourcesContent":["import { packageName } from '../../package.js';\nimport { brandLinkedError } from '@_linked/core/utils/linkedError';\nimport { Server } from '@_linked/server-utils/utils/Server';\nimport type { CallContext } from '@_linked/server-utils/utils/CallContext';\nimport type { IDataset } from '@_linked/core/interfaces/IDataset';\nimport type { SelectQuery } from '@_linked/core/queries/SelectQuery';\nimport type { CountQuery } from '@_linked/core/queries/CountQuery';\nimport type { AskQuery } from '@_linked/core/queries/AskQuery';\nimport type { UpdateQuery } from '@_linked/core/queries/UpdateQuery';\nimport type { CreateQuery } from '@_linked/core/queries/CreateQuery';\nimport type {\n DeleteQuery,\n DeleteResponse,\n} from '@_linked/core/queries/DeleteQuery';\nimport type {\n SelectResult,\n UpdateResult,\n CreateResult,\n GraphQuery,\n GraphResult,\n} from '@_linked/core/queries/IntermediateRepresentation';\nimport {\n GraphQueryError,\n isGraphQueryError,\n unsupportedGraphQuery,\n type GraphQueryErrorCode,\n} from '@_linked/core/queries/graphQuery';\nimport {\n invalidateGraphRegistry,\n noteGraphQuery,\n readGraphRegistry,\n type GraphRegistryRow,\n} from '@_linked/core/utils/graphRegistryCache';\nimport { graphs } from '@_linked/core/ontologies/graphs';\nimport { OntologyGraph, ShapesGraph } from '@_linked/core/shapes/NamedGraph';\n\n/**\n * Constructor argument for `new RemoteDataset(config)`: the `config` object of a\n * `linked.{frontend,backend}.datasets.json` entry, passed verbatim.\n */\nexport interface RemoteDatasetConfig {\n /**\n * Base URL of the data server, e.g. `http://localhost:4040/gateway`; routes are appended\n * to it. Without it, queries go to this process's own backend through `Server.call`.\n * Present but empty is an error, never the no-url form.\n */\n url?: string;\n /** Sent as `Authorization: Bearer <token>` when set and `url` is set. */\n token?: string;\n /** Stable name; becomes the suffix of the store URI: ${DATA_ROOT}/backend-api-store/<name>. */\n name?: string;\n /** Or pass a fully-qualified URI directly. */\n id?: string;\n /**\n * In the url form, send the signed-in user's WebID of the current call as\n * `x-linked-end-user` (see {@link END_USER_HEADER}). Default true; `false` (or the\n * string `'false'`, as an interpolated config gives it) turns it off.\n */\n forwardEndUser?: boolean | string;\n}\n\n/**\n * The header the url form sends with a query made while a signed-in user's HTTP request is\n * being answered: that user's WebID. The data server decides what to do with it; the bearer\n * token still decides which data the store reaches.\n */\nexport const END_USER_HEADER = 'x-linked-end-user';\n\n// The store `@_linked/server-utils/utils/CallContext` keeps the call context in, shared on\n// `globalThis` under this key. Read here instead of importing that module: it imports\n// `node:async_hooks`, and this store also runs in the browser (the no-url form). In a browser,\n// and in a server before any call ran, the key is unset and there is no context.\nconst CALL_CONTEXT_KEY = Symbol.for('@_linked/server-utils:callContext');\n\nfunction currentCallContext(): CallContext | undefined {\n const store = (globalThis as Record<symbol, { getStore?: () => unknown } | undefined>)[CALL_CONTEXT_KEY];\n return typeof store?.getStore === 'function' ? (store.getStore() as CallContext | undefined) : undefined;\n}\n\n/**\n * The WebID of the signed-in user behind the current call: the http context's\n * `request.linkedAuth.userAccount.accountOf.id`. Undefined in a system context\n * (`runAsSystem`), outside any call, and for a request without a session.\n */\nexport function currentEndUserWebId(): string | undefined {\n const ctx = currentCallContext();\n if (!ctx || ctx.kind !== 'http') return undefined;\n const webId = ctx.request?.linkedAuth?.userAccount?.accountOf?.id;\n return typeof webId === 'string' && webId ? webId : undefined;\n}\n\n/**\n * A failed call to the data server at `url`. `status` is the HTTP status (0 when no HTTP\n * answer came back); `code` is the error body's `code` when the server sent one.\n */\nexport class RemoteDatasetError extends Error {\n /** The data server base url ('' when the configured url is empty). */\n readonly url: string;\n /** The HTTP status; 0 = no HTTP answer. */\n readonly status: number;\n /** The error body's `code`, when the server sent one. */\n readonly code?: string;\n\n constructor(message: string, url: string, status: number, code?: string) {\n super(message);\n this.name = new.target.name;\n this.url = url;\n this.status = status;\n if (code !== undefined) this.code = code;\n }\n}\n\n/**\n * Nothing at `url` speaks the query wire: no HTTP answer, an empty url, or (on `ping`) an\n * answer that is not the server's. A boot that gets this should stop.\n */\nexport class RemoteDatasetUnreachableError extends RemoteDatasetError {}\n\n/** The data server answered 401 or 403: it refused this dataset's token. */\nexport class RemoteDatasetUnauthorizedError extends RemoteDatasetError {}\n\n// Branded, so `isLinkedError(err, RemoteDatasetUnreachableError)` from `@_linked/core` matches an\n// error thrown by another copy of this module, where `instanceof` does not. See\n// https://github.com/linked-fw/cli/blob/main/docs/architecture/shared-state-and-error-brands.md.\nbrandLinkedError(RemoteDatasetError, '@_linked/server', 'RemoteDatasetError');\nbrandLinkedError(RemoteDatasetUnreachableError, '@_linked/server', 'RemoteDatasetUnreachableError');\nbrandLinkedError(RemoteDatasetUnauthorizedError, '@_linked/server', 'RemoteDatasetUnauthorizedError');\n\n/**\n * What the data server's `ping` answered, as is. The generic `@_linked/server` ping answers\n * `{ok: true}`; a gateway may add fields that say which dataset it serves, such as\n * `{ok, projectId, branchName, dataset, forked}`. A body that is not a JSON object reads as `{}`.\n */\nexport type RemoteDatasetPingInfo = Readonly<Record<string, unknown>>;\n\ntype Method =\n | 'ping'\n | 'selectQuery'\n | 'askQuery'\n | 'updateQuery'\n | 'createQuery'\n | 'deleteQuery'\n | 'graphQuery';\n\n/** The refusal codes a data server sends back for a graph query (`{error, code}`). */\nconst GRAPH_REFUSAL_CODES: ReadonlySet<string> = new Set<GraphQueryErrorCode>([\n 'unsupported-query-kind',\n 'graph-not-registered',\n 'graph-name-read-only',\n 'graph-exists',\n]);\n\n/**\n * A failed graph query as the caller expects it: a {@link GraphQueryError} when the data server\n * refused it with one of the graph refusal codes, or when it has no graph method at all (404 or\n * 501 without any code: an older server or gateway), which reads as `unsupported-query-kind`\n * so the ontology sync falls back to the default graph. Anything else is passed on as is.\n */\nfunction graphQueryFailure(err: unknown, query: GraphQuery): unknown {\n if (isGraphQueryError(err)) return err;\n const code = (err as { code?: unknown } | null)?.code;\n const message = err instanceof Error ? err.message : String(err);\n let refusal: GraphQueryError | undefined;\n if (typeof code === 'string' && GRAPH_REFUSAL_CODES.has(code)) {\n refusal = new GraphQueryError(code as GraphQueryErrorCode, message, query.graph);\n } else if (code === undefined) {\n const status = (err as { status?: unknown } | null)?.status;\n if (status === 404 || status === 501) refusal = unsupportedGraphQuery(query, message);\n }\n if (!refusal) return err;\n (refusal as { cause?: unknown }).cause = err;\n return refusal;\n}\n\n/** The portable names a remote store expands from its own registry cache, with their record class. */\nconst REGISTRY_EXPANDED: ReadonlyMap<string, string> = new Map([\n [graphs.allOntologies, OntologyGraph.targetClass.id],\n [graphs.allShapes, ShapesGraph.targetClass.id],\n]);\n\nlet nextRegistryKey = 0;\n\n/** On `ping`, these statuses mean nothing at that url serves the query wire. */\nconst PING_UNREACHABLE_STATUSES = new Set([404, 501, 502, 503, 504]);\n\n/**\n * An {@link IDataset} whose queries are answered by a linked server.\n *\n * - **Without `url`** the server is this app's own backend: every query goes through\n * `Server.call`'s package form (`/call/@_linked/server/<method>` with a body of `{args}`),\n * and on a backend `Server`'s local shortcut answers it in process. This is the store a\n * browser uses to talk to its backend.\n * - **With `url`** the server is a remote data server: every query posts\n * `{\"args\": [query.toJSON()]}` to `{url}/call/@_linked/server/<method>` with the bearer\n * `token`, through `fetch`, never through `Server` (whose local shortcut would answer the\n * app's own provider) and never retried (a retried write could apply twice). `init()` asks\n * the server's `ping` and throws {@link RemoteDatasetUnreachableError} or\n * {@link RemoteDatasetUnauthorizedError} when it cannot be used.\n *\n * A store is not a Shape: it addresses the backend by package name, so nothing about the\n * store instance crosses the wire. Query routing is decided by the query's shape.\n *\n * `LinkedStorage.setDefaultDataset` calls `init()` without awaiting it, so a boot that must\n * fail loudly awaits `init()` itself first; a successful `init()` is memoized, and resolves\n * the ping body (also readable as `info`), so a boot can log which dataset the server serves.\n */\nexport class RemoteDataset implements IDataset {\n /**\n * Kept so the config form is still accepted and a store can be named; it is not an\n * addressing mechanism, nothing transmits or resolves it.\n */\n readonly id?: string;\n /** The data server base url, trailing slashes trimmed; undefined in the no-url form. */\n readonly url?: string;\n private readonly token?: string;\n /** Whether the url form sends {@link END_USER_HEADER}; see `RemoteDatasetConfig.forwardEndUser`. */\n readonly forwardEndUser: boolean = true;\n private initialized?: Promise<RemoteDatasetPingInfo | undefined>;\n private pingInfo?: RemoteDatasetPingInfo;\n private readonly instanceRegistryKey = `remote-dataset:${nextRegistryKey++}`;\n private warnedRegistryRead = false;\n\n constructor(config?: RemoteDatasetConfig | string) {\n if (!config) return;\n if (typeof config === 'string') {\n this.id = `${process.env.DATA_ROOT}/backend-api-store/${config}`;\n return;\n }\n if (typeof config.url === 'string') {\n this.url = config.url.trim().replace(/\\/+$/, '');\n const token = typeof config.token === 'string' ? config.token.trim() : '';\n if (token) this.token = token;\n }\n if (config.forwardEndUser === false || config.forwardEndUser === 'false') {\n this.forwardEndUser = false;\n }\n if (config.id) {\n this.id = config.id;\n return;\n }\n if (config.name) {\n this.id = `${process.env.DATA_ROOT}/backend-api-store/${config.name}`;\n }\n }\n\n /**\n * The body of the last successful `ping` (see {@link init}); undefined before one, and\n * always in the no-url form.\n */\n get info(): RemoteDatasetPingInfo | undefined {\n return this.pingInfo;\n }\n\n /**\n * In the url form, asks the server's `ping` and resolves its body (also kept as\n * {@link info}); resolves undefined in the no-url form.\n */\n init(): Promise<RemoteDatasetPingInfo | undefined> {\n // No initialization needed when queries are routed to the app's own backend.\n if (this.url === undefined) return Promise.resolve(undefined);\n if (!this.initialized) {\n this.initialized = this.post('ping', []).then(\n (body) => {\n const info: RemoteDatasetPingInfo = Object.freeze(\n body && typeof body === 'object' && !Array.isArray(body)\n ? { ...(body as Record<string, unknown>) }\n : {},\n );\n this.pingInfo = info;\n return info;\n },\n (err) => {\n // Not memoized: the next init() asks again.\n this.initialized = undefined;\n throw err;\n },\n );\n }\n return this.initialized;\n }\n\n // Queries serialize to DSL-JSON for the wire: the live (closed) query can't cross a call\n // as-is, so we ship `toJSON()` and the server's generic provider rehydrates it with\n // `fromJSON()`.\n async selectQuery(query: SelectQuery | CountQuery): Promise<SelectResult | number> {\n return this.call('selectQuery', await this.withReadGraphs(query.toJSON()));\n }\n\n async askQuery(query: AskQuery): Promise<boolean> {\n return this.call('askQuery', await this.withReadGraphs(query.toJSON()));\n }\n\n updateQuery(query: UpdateQuery): Promise<UpdateResult> {\n return this.call('updateQuery', query.toJSON());\n }\n\n createQuery(query: CreateQuery): Promise<CreateResult> {\n return this.call('createQuery', query.toJSON());\n }\n\n deleteQuery(query: DeleteQuery): Promise<DeleteResponse> {\n return this.call('deleteQuery', query.toJSON());\n }\n\n /**\n * A whole-graph operation (`graph_create`, `graph_replace`, `graph_patch`, `graph_drop`): the\n * query is plain data, so it is sent as is, the same way and with the same headers as every\n * other query. On success this store's registry cache notes it in the same step (a create adds\n * the graph, a drop removes it), so its next `.from(graphs.allOntologies)` read sees the change.\n * A refusal rejects with a `GraphQueryError` (see `graphQueryFailure`): a server or gateway\n * without the method answers `unsupported-query-kind`.\n */\n async graphQuery(query: GraphQuery): Promise<GraphResult> {\n let result: GraphResult;\n try {\n result = await this.call<GraphResult>('graphQuery', query);\n } catch (err) {\n throw graphQueryFailure(err, query);\n }\n if (this.url !== undefined) noteGraphQuery(this.graphRegistryKey(), query);\n return result;\n }\n\n /**\n * The key this store's graph-registry cache is kept under: the data server's url and the\n * dataset it named in its `ping` answer (CN's gateway answers `{dataset}`), so stores of the\n * same dataset share one entry; else one key per instance. (`POST /admin/graph-registry-changed`\n * drops every entry of the process, whatever its key.)\n */\n protected graphRegistryKey(): string {\n const dataset = this.pingInfo?.dataset;\n return typeof dataset === 'string' && dataset && this.url\n ? `${this.url}#${dataset}`\n : this.instanceRegistryKey;\n }\n\n /**\n * Drop the cached graph registry of `dataset` (default: this store's, see `graphRegistryKey`):\n * the next read that expands `graphs.allOntologies` re-reads it.\n */\n invalidateGraphRegistry(dataset?: string): void {\n invalidateGraphRegistry(dataset ?? this.graphRegistryKey());\n }\n\n /**\n * Read the data server's graph registry: the records of the ontology and shapes graphs, by\n * class, through the same query wire (the record shapes read the registry graph themselves).\n */\n protected async loadGraphRegistry(): Promise<GraphRegistryRow[]> {\n const rows: GraphRegistryRow[] = [];\n const kinds = [OntologyGraph, ShapesGraph] as const;\n const results = await Promise.all(kinds.map((kind) => kind.select().exec(this)));\n results.forEach((records, i) => {\n const recordClass = kinds[i].targetClass.id;\n for (const record of (records as Array<{ id?: unknown }> | null) ?? []) {\n if (typeof record?.id === 'string' && record.id) rows.push({ graph: record.id, recordClass });\n }\n });\n return rows;\n }\n\n /**\n * In the url form, expand `graphs.allOntologies` and `graphs.allShapes` in a read's `graphs`\n * from this store's registry cache (per dataset, updated by its own graph queries, dropped by a\n * pushed invalidation, re-read after 30 seconds), so the data server reads exactly the graphs\n * this process knows of. Other names (graph IRIs, `graphs.default`, `graphs.union`) go as they\n * are, for the data server's store to resolve. A name that expands to no graph is sent as is,\n * so the data server answers from its own registry; so is every name when the registry cannot\n * be read (the data server refused or failed the record selects), with one warning per store.\n * The no-url form sends `graphs` unchanged: the backend's store expands them.\n */\n private async withReadGraphs(json: unknown): Promise<unknown> {\n if (this.url === undefined || !json || typeof json !== 'object') return json;\n const names = (json as { graphs?: unknown }).graphs;\n if (!Array.isArray(names) || !names.some((name) => REGISTRY_EXPANDED.has(name))) return json;\n let registry: Awaited<ReturnType<typeof readGraphRegistry>>;\n try {\n registry = await readGraphRegistry(this.graphRegistryKey(), () => this.loadGraphRegistry());\n } catch (err) {\n if (!this.warnedRegistryRead) {\n this.warnedRegistryRead = true;\n console.warn(\n `RemoteDataset: could not read the graph registry at ${this.url}; reads send graph names unexpanded:`,\n err instanceof Error ? err.message : err,\n );\n }\n return json;\n }\n const out: string[] = [];\n const add = (iri: string) => {\n if (!out.includes(iri)) out.push(iri);\n };\n for (const name of names as string[]) {\n const recordClass = REGISTRY_EXPANDED.get(name);\n if (!recordClass) {\n add(name);\n continue;\n }\n const listed = [...registry].filter(([, classes]) => classes.has(recordClass)).map(([graph]) => graph);\n if (listed.length) listed.sort().forEach(add);\n else add(name);\n }\n return { ...(json as Record<string, unknown>), graphs: out };\n }\n\n private call<T>(method: Method, json: unknown): Promise<T> {\n if (this.url === undefined) return this.callBackend(method, json);\n return this.post(method, [json]) as Promise<T>;\n }\n\n /**\n * Opts in to `rejectOnError`, so a failed call (HTTP error status, or no\n * provider on the backend) rejects with a `ServerCallError` carrying the\n * status and the server's message instead of reading as an empty result.\n * Whatever a successful call returns, `undefined` included, resolves as is.\n */\n private callBackend<T>(method: string, json: unknown): Promise<T> {\n // The PACKAGE form, not the shape form. It posts to\n // `/call/<packageName>/<method>` with a body of `{args}` only — no `shapeURI`,\n // no `instanceNode` — and the backend answers from this package's generic\n // provider without resolving or instantiating any Shape.\n return Server.call(packageName, { method, rejectOnError: true }, json);\n }\n\n /**\n * The url form: one POST to `{url}/call/@_linked/server/<method>`. Resolves the parsed\n * body (`null` is a valid result: a method that returned `undefined`). Messages name the\n * url and the method, never the token.\n */\n private async post(method: Method, args: unknown[]): Promise<unknown> {\n const base = this.url ?? '';\n if (!base) {\n throw new RemoteDatasetUnreachableError(\n `RemoteDataset: no data server url is set (the configured url is empty); cannot ${method}`,\n '',\n 0,\n );\n }\n const target = `${base}/call/${packageName}/${method}`;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n if (this.token) headers.Authorization = `Bearer ${this.token}`;\n if (this.forwardEndUser) {\n const endUser = currentEndUserWebId();\n if (endUser) headers[END_USER_HEADER] = endUser;\n }\n\n let response: Response;\n try {\n response = await globalThis.fetch(target, {\n method: 'POST',\n headers,\n body: JSON.stringify({ args }),\n });\n } catch (err) {\n const cause = err instanceof Error ? err.message : String(err);\n throw new RemoteDatasetUnreachableError(\n `RemoteDataset: cannot reach the data server at ${base} (${method}): ${cause}`,\n base,\n 0,\n );\n }\n\n const text = await response.text();\n let body: unknown = undefined;\n let isJson = false;\n if (text) {\n try {\n body = JSON.parse(text);\n isJson = true;\n } catch {\n body = text;\n }\n }\n\n if (response.ok) {\n if (isJson) return body;\n if (method === 'ping') {\n throw new RemoteDatasetUnreachableError(\n `RemoteDataset: the data server at ${base} answered ping with a body that is not JSON`,\n base,\n response.status,\n );\n }\n if (!text) return undefined;\n throw new RemoteDatasetError(\n `RemoteDataset: the data server at ${base} answered ${method} with a body that is not JSON`,\n base,\n response.status,\n );\n }\n\n const record = body && typeof body === 'object' ? (body as Record<string, unknown>) : undefined;\n const code = typeof record?.code === 'string' ? record.code : undefined;\n const detail =\n typeof record?.error === 'string'\n ? record.error\n : typeof body === 'string' && body\n ? body\n : response.statusText;\n const message = `RemoteDataset: the data server at ${base} answered ${method} with ${response.status}${\n code ? ` (${code})` : ''\n }${detail ? `: ${detail}` : ''}`;\n\n if (response.status === 401 || response.status === 403) {\n throw new RemoteDatasetUnauthorizedError(message, base, response.status, code);\n }\n if (method === 'ping' && (PING_UNREACHABLE_STATUSES.has(response.status) || !isJson)) {\n throw new RemoteDatasetUnreachableError(message, base, response.status, code);\n }\n throw new RemoteDatasetError(message, base, response.status, code);\n }\n}\n"]}
1
+ {"version":3,"file":"RemoteDataset.js","sourceRoot":"","sources":["../../../../src/shapes/quadstores/RemoteDataset.ts"],"names":[],"mappings":"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","sourcesContent":["import { packageName } from '../../package.js';\nimport { brandLinkedError } from '@_linked/core/utils/linkedError';\nimport { Server } from '@_linked/server-utils/utils/Server';\nimport type { CallContext } from '@_linked/server-utils/utils/CallContext';\nimport type { IDataset } from '@_linked/core/interfaces/IDataset';\nimport type { SelectQuery } from '@_linked/core/queries/SelectQuery';\nimport type { CountQuery } from '@_linked/core/queries/CountQuery';\nimport type { AskQuery } from '@_linked/core/queries/AskQuery';\nimport type { UpdateQuery } from '@_linked/core/queries/UpdateQuery';\nimport type { CreateQuery } from '@_linked/core/queries/CreateQuery';\nimport type {\n DeleteQuery,\n DeleteResponse,\n} from '@_linked/core/queries/DeleteQuery';\nimport type {\n SelectResult,\n UpdateResult,\n CreateResult,\n GraphQuery,\n GraphResult,\n} from '@_linked/core/queries/IntermediateRepresentation';\nimport {\n GraphQueryError,\n isGraphQueryError,\n unsupportedGraphQuery,\n type GraphQueryErrorCode,\n} from '@_linked/core/queries/graphQuery';\nimport {\n invalidateGraphRegistry,\n noteGraphQuery,\n readGraphRegistry,\n type GraphRegistryRow,\n} from '@_linked/core/utils/graphRegistryCache';\nimport { graphs } from '@_linked/core/ontologies/graphs';\nimport { OntologyGraph, ShapesGraph } from '@_linked/core/shapes/NamedGraph';\n\n/**\n * Constructor argument for `new RemoteDataset(config)`: the `config` object of a\n * `linked.{frontend,backend}.datasets.json` entry, passed verbatim.\n */\nexport interface RemoteDatasetConfig {\n /**\n * Base URL of the data server, e.g. `http://localhost:4040/gateway`; routes are appended\n * to it. Without it, queries go to this process's own backend through `Server.call`.\n * Present but empty is an error, never the no-url form.\n */\n url?: string;\n /** Sent as `Authorization: Bearer <token>` when set and `url` is set. */\n token?: string;\n /** Stable name; becomes the suffix of the store URI: ${DATA_ROOT}/backend-api-store/<name>. */\n name?: string;\n /** Or pass a fully-qualified URI directly. */\n id?: string;\n /**\n * In the url form, send the signed-in user's WebID of the current call as\n * `x-linked-end-user` (see {@link END_USER_HEADER}). Default true; `false` (or the\n * string `'false'`, as an interpolated config gives it) turns it off.\n */\n forwardEndUser?: boolean | string;\n}\n\n/**\n * The header the url form sends with a query made while a signed-in user's HTTP request is\n * being answered: that user's WebID. The data server decides what to do with it; the bearer\n * token still decides which data the store reaches.\n */\nexport const END_USER_HEADER = 'x-linked-end-user';\n\n// The store `@_linked/server-utils/utils/CallContext` keeps the call context in, shared on\n// `globalThis` under this key. Read here instead of importing that module: it imports\n// `node:async_hooks`, and this store also runs in the browser (the no-url form). In a browser,\n// and in a server before any call ran, the key is unset and there is no context.\nconst CALL_CONTEXT_KEY = Symbol.for('@_linked/server-utils:callContext');\n\nfunction currentCallContext(): CallContext | undefined {\n const store = (globalThis as Record<symbol, { getStore?: () => unknown } | undefined>)[CALL_CONTEXT_KEY];\n return typeof store?.getStore === 'function' ? (store.getStore() as CallContext | undefined) : undefined;\n}\n\n/**\n * The WebID of the signed-in user behind the current call: the http context's\n * `request.linkedAuth.userAccount.accountOf.id`. Undefined in a system context\n * (`runAsSystem`), outside any call, and for a request without a session.\n */\nexport function currentEndUserWebId(): string | undefined {\n const ctx = currentCallContext();\n if (!ctx || ctx.kind !== 'http') return undefined;\n const webId = ctx.request?.linkedAuth?.userAccount?.accountOf?.id;\n return typeof webId === 'string' && webId ? webId : undefined;\n}\n\n/**\n * A failed call to the data server at `url`. `status` is the HTTP status (0 when no HTTP\n * answer came back); `code` is the error body's `code` when the server sent one.\n */\nexport class RemoteDatasetError extends Error {\n /** The data server base url ('' when the configured url is empty). */\n readonly url: string;\n /** The HTTP status; 0 = no HTTP answer. */\n readonly status: number;\n /** The error body's `code`, when the server sent one. */\n readonly code?: string;\n\n constructor(message: string, url: string, status: number, code?: string) {\n super(message);\n this.name = new.target.name;\n this.url = url;\n this.status = status;\n if (code !== undefined) this.code = code;\n }\n}\n\n/**\n * Nothing at `url` speaks the query wire: no HTTP answer, an empty url, or (on `ping`) an\n * answer that is not the server's. A boot that gets this should stop.\n */\nexport class RemoteDatasetUnreachableError extends RemoteDatasetError {}\n\n/** The data server answered 401 or 403: it refused this dataset's token. */\nexport class RemoteDatasetUnauthorizedError extends RemoteDatasetError {}\n\n// Branded, so `isLinkedError(err, RemoteDatasetUnreachableError)` from `@_linked/core` matches an\n// error thrown by another copy of this module, where `instanceof` does not. See\n// https://github.com/linked-fw/cli/blob/main/docs/architecture/shared-state-and-error-brands.md.\nbrandLinkedError(RemoteDatasetError, '@_linked/server', 'RemoteDatasetError');\nbrandLinkedError(RemoteDatasetUnreachableError, '@_linked/server', 'RemoteDatasetUnreachableError');\nbrandLinkedError(RemoteDatasetUnauthorizedError, '@_linked/server', 'RemoteDatasetUnauthorizedError');\n\n/**\n * What the data server's `ping` answered, as is. The generic `@_linked/server` ping answers\n * `{ok: true}`; a gateway may add fields that say which dataset it serves, such as\n * `{ok, projectId, branchName, dataset, forked}`. A body that is not a JSON object reads as `{}`.\n */\nexport type RemoteDatasetPingInfo = Readonly<Record<string, unknown>>;\n\ntype Method =\n | 'ping'\n | 'selectQuery'\n | 'askQuery'\n | 'updateQuery'\n | 'createQuery'\n | 'deleteQuery'\n | 'graphQuery';\n\n/** The refusal codes a data server sends back for a graph query (`{error, code}`). */\nconst GRAPH_REFUSAL_CODES: ReadonlySet<string> = new Set<GraphQueryErrorCode>([\n 'unsupported-query-kind',\n 'graph-not-registered',\n 'graph-name-read-only',\n 'graph-exists',\n]);\n\n/**\n * A failed graph query as the caller expects it: a {@link GraphQueryError} when the data server\n * refused it with one of the graph refusal codes, or when it has no graph method at all (404 or\n * 501 without any code: an older server or gateway), which reads as `unsupported-query-kind`\n * so the ontology sync falls back to the default graph. Anything else is passed on as is.\n */\nfunction graphQueryFailure(err: unknown, query: GraphQuery): unknown {\n if (isGraphQueryError(err)) return err;\n const code = (err as { code?: unknown } | null)?.code;\n const message = err instanceof Error ? err.message : String(err);\n let refusal: GraphQueryError | undefined;\n if (typeof code === 'string' && GRAPH_REFUSAL_CODES.has(code)) {\n refusal = new GraphQueryError(code as GraphQueryErrorCode, message, query.graph);\n } else if (code === undefined) {\n const status = (err as { status?: unknown } | null)?.status;\n if (status === 404 || status === 501) refusal = unsupportedGraphQuery(query, message);\n }\n if (!refusal) return err;\n (refusal as { cause?: unknown }).cause = err;\n return refusal;\n}\n\n/** The portable names a remote store expands from its own registry cache, with their record class. */\nconst REGISTRY_EXPANDED: ReadonlyMap<string, string> = new Map([\n [graphs.allOntologies, OntologyGraph.targetClass.id],\n [graphs.allShapes, ShapesGraph.targetClass.id],\n]);\n\nlet nextRegistryKey = 0;\n\n/** On `ping`, these statuses mean nothing at that url serves the query wire. */\nconst PING_UNREACHABLE_STATUSES = new Set([404, 501, 502, 503, 504]);\n\n/**\n * An {@link IDataset} whose queries are answered by a linked server.\n *\n * - **Without `url`** the server is this app's own backend: every query goes through\n * `Server.call`'s package form (`/call/@_linked/server/<method>` with a body of `{args}`),\n * and on a backend `Server`'s local shortcut answers it in process. This is the store a\n * browser uses to talk to its backend.\n * - **With `url`** the server is a remote data server: every query posts\n * `{\"args\": [query.toJSON()]}` to `{url}/call/@_linked/server/<method>` with the bearer\n * `token`, through `fetch`, never through `Server` (whose local shortcut would answer the\n * app's own provider) and never retried (a retried write could apply twice). `init()` asks\n * the server's `ping` and throws {@link RemoteDatasetUnreachableError} or\n * {@link RemoteDatasetUnauthorizedError} when it cannot be used.\n *\n * A store is not a Shape: it addresses the backend by package name, so nothing about the\n * store instance crosses the wire. Query routing is decided by the query's shape.\n *\n * `LinkedStorage.setDefaultDataset` calls `init()` without awaiting it, so a boot that must\n * fail loudly awaits `init()` itself first; a successful `init()` is memoized, and resolves\n * the ping body (also readable as `info`), so a boot can log which dataset the server serves.\n */\nexport class RemoteDataset implements IDataset {\n /**\n * Kept so the config form is still accepted and a store can be named; it is not an\n * addressing mechanism, nothing transmits or resolves it.\n */\n readonly id?: string;\n /** The data server base url, trailing slashes trimmed; undefined in the no-url form. */\n readonly url?: string;\n private readonly token?: string;\n /** Whether the url form sends {@link END_USER_HEADER}; see `RemoteDatasetConfig.forwardEndUser`. */\n readonly forwardEndUser: boolean = true;\n private initialized?: Promise<RemoteDatasetPingInfo | undefined>;\n private pingInfo?: RemoteDatasetPingInfo;\n private readonly instanceRegistryKey = `remote-dataset:${nextRegistryKey++}`;\n private warnedRegistryRead = false;\n\n constructor(config?: RemoteDatasetConfig | string) {\n if (!config) return;\n if (typeof config === 'string') {\n this.id = `${process.env.DATA_ROOT}/backend-api-store/${config}`;\n return;\n }\n if (typeof config.url === 'string') {\n this.url = config.url.trim().replace(/\\/+$/, '');\n const token = typeof config.token === 'string' ? config.token.trim() : '';\n if (token) this.token = token;\n }\n if (config.forwardEndUser === false || config.forwardEndUser === 'false') {\n this.forwardEndUser = false;\n }\n if (config.id) {\n this.id = config.id;\n return;\n }\n if (config.name) {\n this.id = `${process.env.DATA_ROOT}/backend-api-store/${config.name}`;\n }\n }\n\n /**\n * The body of the last successful `ping` (see {@link init}); undefined before one, and\n * always in the no-url form.\n */\n get info(): RemoteDatasetPingInfo | undefined {\n return this.pingInfo;\n }\n\n /**\n * In the url form, asks the server's `ping` and resolves its body (also kept as\n * {@link info}); resolves undefined in the no-url form.\n */\n init(): Promise<RemoteDatasetPingInfo | undefined> {\n // No initialization needed when queries are routed to the app's own backend.\n if (this.url === undefined) return Promise.resolve(undefined);\n if (!this.initialized) {\n this.initialized = this.post('ping', []).then(\n (body) => {\n const info: RemoteDatasetPingInfo = Object.freeze(\n body && typeof body === 'object' && !Array.isArray(body)\n ? { ...(body as Record<string, unknown>) }\n : {},\n );\n this.pingInfo = info;\n return info;\n },\n (err) => {\n // Not memoized: the next init() asks again.\n this.initialized = undefined;\n throw err;\n },\n );\n }\n return this.initialized;\n }\n\n // Queries serialize to DSL-JSON for the wire: the live (closed) query can't cross a call\n // as-is, so we ship `toJSON()` and the server's generic provider rehydrates it with\n // `fromJSON()`.\n async selectQuery(query: SelectQuery | CountQuery): Promise<SelectResult | number> {\n return this.call('selectQuery', await this.withReadGraphs(query.toJSON()));\n }\n\n async askQuery(query: AskQuery): Promise<boolean> {\n return this.call('askQuery', await this.withReadGraphs(query.toJSON()));\n }\n\n updateQuery(query: UpdateQuery): Promise<UpdateResult> {\n return this.call('updateQuery', query.toJSON());\n }\n\n createQuery(query: CreateQuery): Promise<CreateResult> {\n return this.call('createQuery', query.toJSON());\n }\n\n deleteQuery(query: DeleteQuery): Promise<DeleteResponse> {\n return this.call('deleteQuery', query.toJSON());\n }\n\n /**\n * A whole-graph operation (`graph_create`, `graph_replace`, `graph_patch`, `graph_drop`): the\n * query is plain data, so it is sent as is, the same way and with the same headers as every\n * other query. On success this store's registry cache notes it in the same step (a create adds\n * the graph, a drop removes it), so its next `.from(graphs.allOntologies)` read sees the change.\n * A refusal rejects with a `GraphQueryError` (see `graphQueryFailure`): a server or gateway\n * without the method answers `unsupported-query-kind`.\n */\n async graphQuery(query: GraphQuery): Promise<GraphResult> {\n let result: GraphResult;\n try {\n result = await this.call<GraphResult>('graphQuery', query);\n } catch (err) {\n throw graphQueryFailure(err, query);\n }\n if (this.url !== undefined) noteGraphQuery(this.graphRegistryKey(), query);\n return result;\n }\n\n /**\n * The key this store's graph-registry cache is kept under: the data server's url and the\n * dataset it named in its `ping` answer (a gateway answers `{dataset}`), so stores of the\n * same dataset share one entry; else one key per instance. (`POST /admin/graph-registry-changed`\n * drops every entry of the process, whatever its key.)\n */\n protected graphRegistryKey(): string {\n const dataset = this.pingInfo?.dataset;\n return typeof dataset === 'string' && dataset && this.url\n ? `${this.url}#${dataset}`\n : this.instanceRegistryKey;\n }\n\n /**\n * Drop the cached graph registry of `dataset` (default: this store's, see `graphRegistryKey`):\n * the next read that expands `graphs.allOntologies` re-reads it.\n */\n invalidateGraphRegistry(dataset?: string): void {\n invalidateGraphRegistry(dataset ?? this.graphRegistryKey());\n }\n\n /**\n * Read the data server's graph registry: the records of the ontology and shapes graphs, by\n * class, through the same query wire (the record shapes read the registry graph themselves).\n */\n protected async loadGraphRegistry(): Promise<GraphRegistryRow[]> {\n const rows: GraphRegistryRow[] = [];\n const kinds = [OntologyGraph, ShapesGraph] as const;\n const results = await Promise.all(kinds.map((kind) => kind.select().exec(this)));\n results.forEach((records, i) => {\n const recordClass = kinds[i].targetClass.id;\n for (const record of (records as Array<{ id?: unknown }> | null) ?? []) {\n if (typeof record?.id === 'string' && record.id) rows.push({ graph: record.id, recordClass });\n }\n });\n return rows;\n }\n\n /**\n * In the url form, expand `graphs.allOntologies` and `graphs.allShapes` in a read's `graphs`\n * from this store's registry cache (per dataset, updated by its own graph queries, dropped by a\n * pushed invalidation, re-read after 30 seconds), so the data server reads exactly the graphs\n * this process knows of. Other names (graph IRIs, `graphs.default`, `graphs.union`) go as they\n * are, for the data server's store to resolve. A name that expands to no graph is sent as is,\n * so the data server answers from its own registry; so is every name when the registry cannot\n * be read (the data server refused or failed the record selects), with one warning per store.\n * The no-url form sends `graphs` unchanged: the backend's store expands them.\n */\n private async withReadGraphs(json: unknown): Promise<unknown> {\n if (this.url === undefined || !json || typeof json !== 'object') return json;\n const names = (json as { graphs?: unknown }).graphs;\n if (!Array.isArray(names) || !names.some((name) => REGISTRY_EXPANDED.has(name))) return json;\n let registry: Awaited<ReturnType<typeof readGraphRegistry>>;\n try {\n registry = await readGraphRegistry(this.graphRegistryKey(), () => this.loadGraphRegistry());\n } catch (err) {\n if (!this.warnedRegistryRead) {\n this.warnedRegistryRead = true;\n console.warn(\n `RemoteDataset: could not read the graph registry at ${this.url}; reads send graph names unexpanded:`,\n err instanceof Error ? err.message : err,\n );\n }\n return json;\n }\n const out: string[] = [];\n const add = (iri: string) => {\n if (!out.includes(iri)) out.push(iri);\n };\n for (const name of names as string[]) {\n const recordClass = REGISTRY_EXPANDED.get(name);\n if (!recordClass) {\n add(name);\n continue;\n }\n const listed = [...registry].filter(([, classes]) => classes.has(recordClass)).map(([graph]) => graph);\n if (listed.length) listed.sort().forEach(add);\n else add(name);\n }\n return { ...(json as Record<string, unknown>), graphs: out };\n }\n\n private call<T>(method: Method, json: unknown): Promise<T> {\n if (this.url === undefined) return this.callBackend(method, json);\n return this.post(method, [json]) as Promise<T>;\n }\n\n /**\n * Opts in to `rejectOnError`, so a failed call (HTTP error status, or no\n * provider on the backend) rejects with a `ServerCallError` carrying the\n * status and the server's message instead of reading as an empty result.\n * Whatever a successful call returns, `undefined` included, resolves as is.\n */\n private callBackend<T>(method: string, json: unknown): Promise<T> {\n // The PACKAGE form, not the shape form. It posts to\n // `/call/<packageName>/<method>` with a body of `{args}` only — no `shapeURI`,\n // no `instanceNode` — and the backend answers from this package's generic\n // provider without resolving or instantiating any Shape.\n return Server.call(packageName, { method, rejectOnError: true }, json);\n }\n\n /**\n * The url form: one POST to `{url}/call/@_linked/server/<method>`. Resolves the parsed\n * body (`null` is a valid result: a method that returned `undefined`). Messages name the\n * url and the method, never the token.\n */\n private async post(method: Method, args: unknown[]): Promise<unknown> {\n const base = this.url ?? '';\n if (!base) {\n throw new RemoteDatasetUnreachableError(\n `RemoteDataset: no data server url is set (the configured url is empty); cannot ${method}`,\n '',\n 0,\n );\n }\n const target = `${base}/call/${packageName}/${method}`;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n if (this.token) headers.Authorization = `Bearer ${this.token}`;\n if (this.forwardEndUser) {\n const endUser = currentEndUserWebId();\n if (endUser) headers[END_USER_HEADER] = endUser;\n }\n\n let response: Response;\n try {\n response = await globalThis.fetch(target, {\n method: 'POST',\n headers,\n body: JSON.stringify({ args }),\n });\n } catch (err) {\n const cause = err instanceof Error ? err.message : String(err);\n throw new RemoteDatasetUnreachableError(\n `RemoteDataset: cannot reach the data server at ${base} (${method}): ${cause}`,\n base,\n 0,\n );\n }\n\n const text = await response.text();\n let body: unknown = undefined;\n let isJson = false;\n if (text) {\n try {\n body = JSON.parse(text);\n isJson = true;\n } catch {\n body = text;\n }\n }\n\n if (response.ok) {\n if (isJson) return body;\n if (method === 'ping') {\n throw new RemoteDatasetUnreachableError(\n `RemoteDataset: the data server at ${base} answered ping with a body that is not JSON`,\n base,\n response.status,\n );\n }\n if (!text) return undefined;\n throw new RemoteDatasetError(\n `RemoteDataset: the data server at ${base} answered ${method} with a body that is not JSON`,\n base,\n response.status,\n );\n }\n\n const record = body && typeof body === 'object' ? (body as Record<string, unknown>) : undefined;\n const code = typeof record?.code === 'string' ? record.code : undefined;\n const detail =\n typeof record?.error === 'string'\n ? record.error\n : typeof body === 'string' && body\n ? body\n : response.statusText;\n const message = `RemoteDataset: the data server at ${base} answered ${method} with ${response.status}${\n code ? ` (${code})` : ''\n }${detail ? `: ${detail}` : ''}`;\n\n if (response.status === 401 || response.status === 403) {\n throw new RemoteDatasetUnauthorizedError(message, base, response.status, code);\n }\n if (method === 'ping' && (PING_UNREACHABLE_STATUSES.has(response.status) || !isJson)) {\n throw new RemoteDatasetUnreachableError(message, base, response.status, code);\n }\n throw new RemoteDatasetError(message, base, response.status, code);\n }\n}\n"]}
@@ -1,12 +1,36 @@
1
1
  import type express from 'express';
2
+ import type { ShapeGraphsReport } from './syncShapes.js';
3
+ import { type EffectiveRoutes } from './effectiveRoutes.js';
4
+ /** The environment variable that holds the control channel's secret. */
5
+ export declare const ADMIN_SECRET_ENV = "APP_ADMIN_SECRET";
6
+ /** Its former name, read when {@link ADMIN_SECRET_ENV} is unset. @deprecated */
7
+ export declare const LEGACY_ADMIN_SECRET_ENV = "CN_APP_ADMIN_SECRET";
8
+ /** The request header that carries the secret. */
9
+ export declare const ADMIN_SECRET_HEADER = "x-app-admin-secret";
10
+ /** Its former name, read when {@link ADMIN_SECRET_HEADER} is absent. @deprecated */
11
+ export declare const LEGACY_ADMIN_SECRET_HEADER = "x-cn-admin-secret";
12
+ /**
13
+ * The control channel's secret: `APP_ADMIN_SECRET`, else the deprecated `CN_APP_ADMIN_SECRET`
14
+ * (logged once per process), else `undefined` (no control channel).
15
+ */
16
+ export declare function resolveAdminSecret(env?: Record<string, string | undefined>, warn?: (message: string) => void): string | undefined;
2
17
  /** What one shape sync did. */
3
18
  export interface ShapeSyncResult {
4
- /** Shapes written: those whose stored hash differed from the registered shape. */
19
+ /**
20
+ * Shape graphs written (package graphs and the app's own graph) because their stored hash
21
+ * differed; on a store without graph queries, the default-graph sync's writes.
22
+ */
5
23
  written: number;
6
24
  /** The orphan policy the sync ran with (see utils/syncShapes). */
7
25
  orphanScope?: 'all' | 'none';
8
26
  /** Set when no sync ran: shape sync is turned off, or there is no default dataset. */
9
27
  skipped?: 'disabled' | 'no-default-dataset';
28
+ /**
29
+ * The shape graph sync: packages written and skipped, the graphs written, the app's own shape
30
+ * graph, the registered packages without `shapes.ttl` (so without a shape graph) and the
31
+ * default-graph shapes removed. Absent when no sync ran.
32
+ */
33
+ shapeGraphs?: ShapeGraphsReport;
10
34
  /**
11
35
  * The requested packages whose source was reloaded before the sync (dev only: a built
12
36
  * app reloads nothing). Set by `POST /admin/sync-shapes`.
@@ -54,6 +78,10 @@ export interface AdminChannelHandlers {
54
78
  * Default: {@link dropGraphRegistryCache}.
55
79
  */
56
80
  graphRegistryChanged?: (dataset: string) => void;
81
+ /** The effective graph routes (see `GET /admin/routes`). Default: {@link effectiveGraphRoutes}. */
82
+ routes?: () => EffectiveRoutes;
83
+ /** Where deprecation warnings go. Default: `console.warn`. */
84
+ warn?: (message: string) => void;
57
85
  }
58
86
  /**
59
87
  * Drop the process's cached graph registry after the host changed the graphs of `dataset`.
@@ -69,7 +97,10 @@ export declare function dropGraphRegistryCache(_dataset: string): void;
69
97
  export declare function parseGraphRegistryChangedBody(body: unknown): {
70
98
  dataset: string;
71
99
  };
72
- /** Compare the provided secret with the configured one in constant time. */
100
+ /**
101
+ * Compare the provided secret with the configured one in constant time. Both sides are hashed
102
+ * first, so the comparison does not reveal the secret's length either.
103
+ */
73
104
  export declare function adminSecretMatches(secret: string | undefined, provided: string | undefined): boolean;
74
105
  /** The `packages` of a sync-shapes body: a list of package names, or none. */
75
106
  export declare function parseSyncShapesBody(body: unknown): {
@@ -1,17 +1,20 @@
1
+ var _a;
2
+ var _b, _c;
1
3
  /**
2
- * The host→app control channel: `/admin/*` routes a host (Create Now) uses on an
3
- * app process it started. Present only when the process was started with a
4
- * shared secret in `CN_APP_ADMIN_SECRET`; without it every route answers 404, so
5
- * a standalone app has no control channel. Every request carries the secret in
6
- * the `x-cn-admin-secret` header.
4
+ * The host→app control channel: `/admin/*` routes a host uses on an app process it
5
+ * started. Present only when the process was started with a shared secret in
6
+ * `APP_ADMIN_SECRET` (see {@link resolveAdminSecret}); without it every route answers
7
+ * 404, so a standalone app has no control channel. Every request carries the secret in
8
+ * the `x-app-admin-secret` header (401 otherwise).
7
9
  *
8
10
  * - `GET /admin/health` → 200 `{status: 'ok'}` once the server listens (the boot
9
11
  * shape sync has finished by then).
10
12
  * - `POST /admin/restart` → 202, then the process exits; the host starts it again.
11
13
  * - `POST /admin/sync-shapes` → re-syncs the app's ontologies (each registered package's
12
14
  * ontology graph, skipped when unchanged), runs the shape check and
13
- * re-syncs its shapes into its app data, as the boot does, and answers 200 with
14
- * {@link ShapeSyncResult}. Body (optional):
15
+ * re-syncs its shapes into its app data (each package's shape graph and the app's own,
16
+ * skipped when unchanged), as the boot does, and answers 200 with
17
+ * {@link ShapeSyncResult}, whose `shapeGraphs` reports the shape graphs. Body (optional):
15
18
  * `{packages: string[]}`, packages whose source changed on disk: in dev they are
16
19
  * reloaded first, so the sync sees shapes added or changed since the boot. Each
17
20
  * name must be a valid npm package name and the app itself or one of its direct
@@ -23,9 +26,61 @@
23
26
  * its next `.from(graphs.allOntologies)` read re-reads it, and answers 200
24
27
  * `{invalidated: dataset}`. A body without a dataset name answers 400. Without the push the
25
28
  * cache's 30-second expiry applies.
29
+ * - `GET /admin/routes` → 200 {@link EffectiveRoutes}: the graph routes of every dataset the
30
+ * router knows (shape IRI → graph IRI) and the shapes each one writes to its default graph,
31
+ * as core's routing holds them once the app's storage file has run. The routes are written
32
+ * only in the app's storage file; this route lets a host check what actually runs.
33
+ *
34
+ * Deprecated names, still read: `CN_APP_ADMIN_SECRET` when `APP_ADMIN_SECRET` is unset, and the
35
+ * `x-cn-admin-secret` header when `x-app-admin-secret` is absent; each logs one warning.
26
36
  */
27
- import { timingSafeEqual } from 'crypto';
37
+ import { createHash, timingSafeEqual } from 'crypto';
28
38
  import { invalidateGraphRegistry } from '@_linked/core/utils/graphRegistryCache';
39
+ import { effectiveGraphRoutes } from './effectiveRoutes.js';
40
+ /** The environment variable that holds the control channel's secret. */
41
+ export const ADMIN_SECRET_ENV = 'APP_ADMIN_SECRET';
42
+ /** Its former name, read when {@link ADMIN_SECRET_ENV} is unset. @deprecated */
43
+ export const LEGACY_ADMIN_SECRET_ENV = 'CN_APP_ADMIN_SECRET';
44
+ /** The request header that carries the secret. */
45
+ export const ADMIN_SECRET_HEADER = 'x-app-admin-secret';
46
+ /** Its former name, read when {@link ADMIN_SECRET_HEADER} is absent. @deprecated */
47
+ export const LEGACY_ADMIN_SECRET_HEADER = 'x-cn-admin-secret';
48
+ /**
49
+ * Which deprecation warnings this process has logged. On the shared global, so a module that
50
+ * evaluates twice still warns once.
51
+ */
52
+ const warned = ((_a = (_b = globalThis)[_c = Symbol.for('@_linked/server:adminChannelDeprecationsWarned')]) !== null && _a !== void 0 ? _a : (_b[_c] = new Set()));
53
+ function warnOnce(key, message, warn) {
54
+ if (warned.has(key))
55
+ return;
56
+ warned.add(key);
57
+ warn(message);
58
+ }
59
+ /**
60
+ * The control channel's secret: `APP_ADMIN_SECRET`, else the deprecated `CN_APP_ADMIN_SECRET`
61
+ * (logged once per process), else `undefined` (no control channel).
62
+ */
63
+ export function resolveAdminSecret(env = process.env, warn = console.warn) {
64
+ const secret = env[ADMIN_SECRET_ENV];
65
+ if (secret)
66
+ return secret;
67
+ const legacy = env[LEGACY_ADMIN_SECRET_ENV];
68
+ if (!legacy)
69
+ return undefined;
70
+ warnOnce('env', `[linked] ${LEGACY_ADMIN_SECRET_ENV} is deprecated; set ${ADMIN_SECRET_ENV} instead (read as a fallback for now).`, warn);
71
+ return legacy;
72
+ }
73
+ /** The secret a request carries: the `x-app-admin-secret` header, else the deprecated one. */
74
+ function providedSecret(req, warn) {
75
+ const current = req.get(ADMIN_SECRET_HEADER);
76
+ if (current)
77
+ return current;
78
+ const legacy = req.get(LEGACY_ADMIN_SECRET_HEADER);
79
+ if (legacy) {
80
+ warnOnce('header', `[linked] the ${LEGACY_ADMIN_SECRET_HEADER} header is deprecated; send ${ADMIN_SECRET_HEADER} instead.`, warn);
81
+ }
82
+ return legacy;
83
+ }
29
84
  /** npm's package-name rule: optional `@scope/`, lower case, URL-safe, no leading `.` or `_` (a scope may start with `_`, as `@_linked` does). */
30
85
  const NPM_NAME = /^(?:@[a-z0-9-~_][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
31
86
  /** Whether `name` is a valid npm package name (and so can never be a path like `..`). */
@@ -71,13 +126,15 @@ export function parseGraphRegistryChangedBody(body) {
71
126
  }
72
127
  return { dataset: dataset.trim() };
73
128
  }
74
- /** Compare the provided secret with the configured one in constant time. */
129
+ /**
130
+ * Compare the provided secret with the configured one in constant time. Both sides are hashed
131
+ * first, so the comparison does not reveal the secret's length either.
132
+ */
75
133
  export function adminSecretMatches(secret, provided) {
76
134
  if (!secret || !provided)
77
135
  return false;
78
- const a = Buffer.from(provided);
79
- const b = Buffer.from(secret);
80
- return a.length === b.length && timingSafeEqual(a, b);
136
+ const digest = (value) => createHash('sha256').update(value).digest();
137
+ return timingSafeEqual(digest(provided), digest(secret));
81
138
  }
82
139
  /** The `packages` of a sync-shapes body: a list of package names, or none. */
83
140
  export function parseSyncShapesBody(body) {
@@ -101,11 +158,12 @@ export function parseSyncShapesBody(body) {
101
158
  /** Mount the control channel's routes on `app`. */
102
159
  export function registerAdminRoutes(app, secret, handlers) {
103
160
  const requireAdmin = (req, res) => {
161
+ var _a;
104
162
  if (!secret) {
105
163
  res.sendStatus(404); // no control channel in this mode
106
164
  return false;
107
165
  }
108
- if (!adminSecretMatches(secret, req.get('x-cn-admin-secret'))) {
166
+ if (!adminSecretMatches(secret, providedSecret(req, (_a = handlers.warn) !== null && _a !== void 0 ? _a : console.warn))) {
109
167
  res.sendStatus(401);
110
168
  return false;
111
169
  }
@@ -122,6 +180,17 @@ export function registerAdminRoutes(app, secret, handlers) {
122
180
  res.sendStatus(202);
123
181
  setTimeout(handlers.exit, 50);
124
182
  });
183
+ app.get('/admin/routes', (req, res) => {
184
+ var _a;
185
+ if (!requireAdmin(req, res))
186
+ return;
187
+ try {
188
+ res.status(200).json(((_a = handlers.routes) !== null && _a !== void 0 ? _a : effectiveGraphRoutes)());
189
+ }
190
+ catch (err) {
191
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
192
+ }
193
+ });
125
194
  app.post('/admin/graph-registry-changed', (req, res) => {
126
195
  var _a;
127
196
  if (!requireAdmin(req, res))
@@ -1 +1 @@
1
- {"version":3,"file":"adminChannel.js","sourceRoot":"","sources":["../../../src/utils/adminChannel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAEzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AA2BjF,iJAAiJ;AACjJ,MAAM,QAAQ,GAAG,2DAA2D,CAAC;AAE7E,yFAAyF;AACzF,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC;AACrH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,QAAkB,EAAE,OAA4B;IACvF,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,iBAAiB,CAAC,uBAAuB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxG,IAAI,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC;YAAE,MAAM,IAAI,iBAAiB,CAAC,wCAAwC,GAAG,EAAE,CAAC,CAAC;QAC5G,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,iBAAiB,CAAC,oBAAoB,GAAG,wDAAwD,CAAC,CAAC;QAC/G,CAAC;IACH,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,MAAM,OAAO,iBAAkB,SAAQ,KAAK;CAAG;AAc/C;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAgB;IACrD,uBAAuB,EAAE,CAAC;AAC5B,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,6BAA6B,CAAC,IAAa;IACzD,MAAM,OAAO,GAAG,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAE,IAA8B,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACvG,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;QAC5E,MAAM,IAAI,iBAAiB,CAAC,8DAA8D,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;AACrC,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,kBAAkB,CAChC,MAA0B,EAC1B,QAA4B;IAE5B,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IACvC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,mBAAmB,CAAC,IAAa;IAC/C,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAChF,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,IAAI,iBAAiB,CAAC,gCAAgC,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,QAAQ,GAAI,IAA+B,CAAC,QAAQ,CAAC;IAC3D,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IACpD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAClF,MAAM,IAAI,iBAAiB,CAAC,0CAA0C,CAAC,CAAC;IAC1E,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,QAAoB,EAAE,CAAC;QACrC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,iBAAiB,CAAC,uBAAuB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtG,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,QAAoB,CAAC,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,mBAAmB,CACjC,GAAoB,EACpB,MAA0B,EAC1B,QAA8B;IAE9B,MAAM,YAAY,GAAG,CAAC,GAAoB,EAAE,GAAqB,EAAW,EAAE;QAC5E,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,kCAAkC;YACvD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC;YAC9D,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YACpB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACpC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QACpC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACtC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QACpC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACpB,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,+BAA+B,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;;QACrD,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QACpC,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,CAAC,EAAE,OAAO,EAAE,GAAG,6BAA6B,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YACxD,OAAO;QACT,CAAC;QACD,CAAC,MAAA,QAAQ,CAAC,oBAAoB,mCAAI,sBAAsB,CAAC,CAAC,OAAO,CAAC,CAAC;QACnE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,oBAAoB,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAChD,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QACpC,IAAI,OAA+B,CAAC;QACpC,IAAI,CAAC;YACH,OAAO,GAAG,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YACxD,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,YAAY,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YAC5D,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvF,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * The host→app control channel: `/admin/*` routes a host (Create Now) uses on an\n * app process it started. Present only when the process was started with a\n * shared secret in `CN_APP_ADMIN_SECRET`; without it every route answers 404, so\n * a standalone app has no control channel. Every request carries the secret in\n * the `x-cn-admin-secret` header.\n *\n * - `GET /admin/health` → 200 `{status: 'ok'}` once the server listens (the boot\n * shape sync has finished by then).\n * - `POST /admin/restart` → 202, then the process exits; the host starts it again.\n * - `POST /admin/sync-shapes` → re-syncs the app's ontologies (each registered package's\n * ontology graph, skipped when unchanged), runs the shape check and\n * re-syncs its shapes into its app data, as the boot does, and answers 200 with\n * {@link ShapeSyncResult}. Body (optional):\n * `{packages: string[]}`, packages whose source changed on disk: in dev they are\n * reloaded first, so the sync sees shapes added or changed since the boot. Each\n * name must be a valid npm package name and the app itself or one of its direct\n * dependencies, never an `@_linked/*` framework package; anything else answers\n * 400 `{error}`. The list never filters the syncs. A failed or timed-out sync\n * answers 500 `{error}` (a failed ontology sync too, after the shape sync ran).\n * - `POST /admin/graph-registry-changed` `{dataset}` → the host changed the named graphs of that\n * dataset (a graph query through its gateway): the process drops its cached graph registry, so\n * its next `.from(graphs.allOntologies)` read re-reads it, and answers 200\n * `{invalidated: dataset}`. A body without a dataset name answers 400. Without the push the\n * cache's 30-second expiry applies.\n */\nimport { timingSafeEqual } from 'crypto';\nimport type express from 'express';\nimport { invalidateGraphRegistry } from '@_linked/core/utils/graphRegistryCache';\n\n/** What one shape sync did. */\nexport interface ShapeSyncResult {\n /** Shapes written: those whose stored hash differed from the registered shape. */\n written: number;\n /** The orphan policy the sync ran with (see utils/syncShapes). */\n orphanScope?: 'all' | 'none';\n /** Set when no sync ran: shape sync is turned off, or there is no default dataset. */\n skipped?: 'disabled' | 'no-default-dataset';\n /**\n * The requested packages whose source was reloaded before the sync (dev only: a built\n * app reloads nothing). Set by `POST /admin/sync-shapes`.\n */\n reloaded?: string[];\n /** The node shape IRIs registered in the process after the reload. Set by `POST /admin/sync-shapes`. */\n shapes?: string[];\n /**\n * The ontology sync: packages whose ontology graph (or, on a store without graph queries,\n * class hierarchy) was written, packages left as they were (unchanged, or their data could not\n * be read), and the graphs written and dropped. Set by `POST /admin/sync-shapes`.\n */\n ontologies?: { written: string[]; skipped: string[]; graphs?: string[]; droppedGraphs?: string[] };\n /** The shape check's findings, counted by severity (each is logged). Set by `POST /admin/sync-shapes`. */\n shapeCheck?: { errors: number; warnings: number };\n}\n\n/** npm's package-name rule: optional `@scope/`, lower case, URL-safe, no leading `.` or `_` (a scope may start with `_`, as `@_linked` does). */\nconst NPM_NAME = /^(?:@[a-z0-9-~_][a-z0-9-._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*$/;\n\n/** Whether `name` is a valid npm package name (and so can never be a path like `..`). */\nexport function isValidPackageName(name: string): boolean {\n return name.length <= 214 && NPM_NAME.test(name) && !name.split('/').some((part) => part === '.' || part === '..');\n}\n\n/**\n * Refuse a package a host may not ask to reload: not a valid npm name, a framework\n * (`@_linked/*`) package, or not in `allowed` (the app's own name and its direct\n * dependencies). Throws {@link AdminRequestError}.\n */\nexport function assertReloadablePackages(packages: string[], allowed: ReadonlySet<string>): void {\n for (const pkg of packages) {\n if (!isValidPackageName(pkg)) throw new AdminRequestError(`not a package name: ${JSON.stringify(pkg)}`);\n if (pkg.startsWith('@_linked/')) throw new AdminRequestError(`framework packages are not reloaded: ${pkg}`);\n if (!allowed.has(pkg)) {\n throw new AdminRequestError(`unknown package: ${pkg} is neither the app nor one of its direct dependencies`);\n }\n }\n}\n\n/** Thrown by a handler for a request the channel refuses with 400. */\nexport class AdminRequestError extends Error {}\n\nexport interface AdminChannelHandlers {\n /** Exit the process (the host restarts it). Called after the 202 is sent. */\n exit: () => void;\n /** Reload `packages` (when given) and sync the registered shapes into app data. */\n syncShapes: (request: { packages: string[] }) => Promise<ShapeSyncResult>;\n /**\n * Drop the cached graph registry for `dataset` (see `POST /admin/graph-registry-changed`).\n * Default: {@link dropGraphRegistryCache}.\n */\n graphRegistryChanged?: (dataset: string) => void;\n}\n\n/**\n * Drop the process's cached graph registry after the host changed the graphs of `dataset`.\n *\n * Every entry is dropped, not only that dataset's: an entry's key is each store's own\n * (`graphRegistryKey()`: a `FusekiStore` keys by its dataset URL, a `RemoteDataset` by the\n * dataset its server names), no store tells which key it uses, and the stores are built by the\n * app's storage config, whose aliases the server does not see. The registry is small, so the cost\n * is one registry read per dataset on its next expanding read.\n */\nexport function dropGraphRegistryCache(_dataset: string): void {\n invalidateGraphRegistry();\n}\n\n/** The `dataset` of a graph-registry-changed body: a non-empty name. */\nexport function parseGraphRegistryChangedBody(body: unknown): { dataset: string } {\n const dataset = body && typeof body === 'object' ? (body as { dataset?: unknown }).dataset : undefined;\n if (typeof dataset !== 'string' || !dataset.trim() || dataset.length > 1024) {\n throw new AdminRequestError('dataset must be the name of the dataset whose graphs changed');\n }\n return { dataset: dataset.trim() };\n}\n\n/** Compare the provided secret with the configured one in constant time. */\nexport function adminSecretMatches(\n secret: string | undefined,\n provided: string | undefined\n): boolean {\n if (!secret || !provided) return false;\n const a = Buffer.from(provided);\n const b = Buffer.from(secret);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\n/** The `packages` of a sync-shapes body: a list of package names, or none. */\nexport function parseSyncShapesBody(body: unknown): { packages: string[] } {\n if (body === undefined || body === null || body === '') return { packages: [] };\n if (typeof body !== 'object') {\n throw new AdminRequestError('the body must be a JSON object');\n }\n const packages = (body as { packages?: unknown }).packages;\n if (packages === undefined) return { packages: [] };\n if (!Array.isArray(packages) || packages.some((p) => typeof p !== 'string' || !p)) {\n throw new AdminRequestError('packages must be a list of package names');\n }\n for (const p of packages as string[]) {\n if (!isValidPackageName(p)) throw new AdminRequestError(`not a package name: ${JSON.stringify(p)}`);\n }\n return { packages: [...new Set(packages as string[])] };\n}\n\n/** Mount the control channel's routes on `app`. */\nexport function registerAdminRoutes(\n app: express.Express,\n secret: string | undefined,\n handlers: AdminChannelHandlers\n): void {\n const requireAdmin = (req: express.Request, res: express.Response): boolean => {\n if (!secret) {\n res.sendStatus(404); // no control channel in this mode\n return false;\n }\n if (!adminSecretMatches(secret, req.get('x-cn-admin-secret'))) {\n res.sendStatus(401);\n return false;\n }\n return true;\n };\n\n app.get('/admin/health', (req, res) => {\n if (!requireAdmin(req, res)) return;\n res.status(200).json({ status: 'ok' });\n });\n\n app.post('/admin/restart', (req, res) => {\n if (!requireAdmin(req, res)) return;\n res.sendStatus(202);\n setTimeout(handlers.exit, 50);\n });\n\n app.post('/admin/graph-registry-changed', (req, res) => {\n if (!requireAdmin(req, res)) return;\n let dataset: string;\n try {\n ({ dataset } = parseGraphRegistryChangedBody(req.body));\n } catch (err) {\n res.status(400).json({ error: (err as Error).message });\n return;\n }\n (handlers.graphRegistryChanged ?? dropGraphRegistryCache)(dataset);\n res.status(200).json({ invalidated: dataset });\n });\n\n app.post('/admin/sync-shapes', async (req, res) => {\n if (!requireAdmin(req, res)) return;\n let request: { packages: string[] };\n try {\n request = parseSyncShapesBody(req.body);\n } catch (err) {\n res.status(400).json({ error: (err as Error).message });\n return;\n }\n try {\n res.status(200).json(await handlers.syncShapes(request));\n } catch (err) {\n const status = err instanceof AdminRequestError ? 400 : 500;\n res.status(status).json({ error: err instanceof Error ? err.message : String(err) });\n }\n });\n}\n"]}
1
+ {"version":3,"file":"adminChannel.js","sourceRoot":"","sources":["../../../src/utils/adminChannel.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The host→app control channel: `/admin/*` routes a host uses on an app process it\n * started. Present only when the process was started with a shared secret in\n * `APP_ADMIN_SECRET` (see {@link resolveAdminSecret}); without it every route answers\n * 404, so a standalone app has no control channel. Every request carries the secret in\n * the `x-app-admin-secret` header (401 otherwise).\n *\n * - `GET /admin/health` → 200 `{status: 'ok'}` once the server listens (the boot\n * shape sync has finished by then).\n * - `POST /admin/restart` → 202, then the process exits; the host starts it again.\n * - `POST /admin/sync-shapes` → re-syncs the app's ontologies (each registered package's\n * ontology graph, skipped when unchanged), runs the shape check and\n * re-syncs its shapes into its app data (each package's shape graph and the app's own,\n * skipped when unchanged), as the boot does, and answers 200 with\n * {@link ShapeSyncResult}, whose `shapeGraphs` reports the shape graphs. Body (optional):\n * `{packages: string[]}`, packages whose source changed on disk: in dev they are\n * reloaded first, so the sync sees shapes added or changed since the boot. Each\n * name must be a valid npm package name and the app itself or one of its direct\n * dependencies, never an `@_linked/*` framework package; anything else answers\n * 400 `{error}`. The list never filters the syncs. A failed or timed-out sync\n * answers 500 `{error}` (a failed ontology sync too, after the shape sync ran).\n * - `POST /admin/graph-registry-changed` `{dataset}` → the host changed the named graphs of that\n * dataset (a graph query through its gateway): the process drops its cached graph registry, so\n * its next `.from(graphs.allOntologies)` read re-reads it, and answers 200\n * `{invalidated: dataset}`. A body without a dataset name answers 400. Without the push the\n * cache's 30-second expiry applies.\n * - `GET /admin/routes` → 200 {@link EffectiveRoutes}: the graph routes of every dataset the\n * router knows (shape IRI → graph IRI) and the shapes each one writes to its default graph,\n * as core's routing holds them once the app's storage file has run. The routes are written\n * only in the app's storage file; this route lets a host check what actually runs.\n *\n * Deprecated names, still read: `CN_APP_ADMIN_SECRET` when `APP_ADMIN_SECRET` is unset, and the\n * `x-cn-admin-secret` header when `x-app-admin-secret` is absent; each logs one warning.\n */\nimport { createHash, timingSafeEqual } from 'crypto';\nimport type express from 'express';\nimport { invalidateGraphRegistry } from '@_linked/core/utils/graphRegistryCache';\nimport type { ShapeGraphsReport } from './syncShapes.js';\nimport { effectiveGraphRoutes, type EffectiveRoutes } from './effectiveRoutes.js';\n\n/** The environment variable that holds the control channel's secret. */\nexport const ADMIN_SECRET_ENV = 'APP_ADMIN_SECRET';\n/** Its former name, read when {@link ADMIN_SECRET_ENV} is unset. @deprecated */\nexport const LEGACY_ADMIN_SECRET_ENV = 'CN_APP_ADMIN_SECRET';\n/** The request header that carries the secret. */\nexport const ADMIN_SECRET_HEADER = 'x-app-admin-secret';\n/** Its former name, read when {@link ADMIN_SECRET_HEADER} is absent. @deprecated */\nexport const LEGACY_ADMIN_SECRET_HEADER = 'x-cn-admin-secret';\n\n/**\n * Which deprecation warnings this process has logged. On the shared global, so a module that\n * evaluates twice still warns once.\n */\nconst warned: Set<string> = ((globalThis as Record<symbol, unknown>)[\n Symbol.for('@_linked/server:adminChannelDeprecationsWarned')\n] ??= new Set<string>()) as Set<string>;\n\nfunction warnOnce(key: string, message: string, warn: (message: string) => void): void {\n if (warned.has(key)) return;\n warned.add(key);\n warn(message);\n}\n\n/**\n * The control channel's secret: `APP_ADMIN_SECRET`, else the deprecated `CN_APP_ADMIN_SECRET`\n * (logged once per process), else `undefined` (no control channel).\n */\nexport function resolveAdminSecret(\n env: Record<string, string | undefined> = process.env,\n warn: (message: string) => void = console.warn\n): string | undefined {\n const secret = env[ADMIN_SECRET_ENV];\n if (secret) return secret;\n const legacy = env[LEGACY_ADMIN_SECRET_ENV];\n if (!legacy) return undefined;\n warnOnce(\n 'env',\n `[linked] ${LEGACY_ADMIN_SECRET_ENV} is deprecated; set ${ADMIN_SECRET_ENV} instead (read as a fallback for now).`,\n warn\n );\n return legacy;\n}\n\n/** The secret a request carries: the `x-app-admin-secret` header, else the deprecated one. */\nfunction providedSecret(req: express.Request, warn: (message: string) => void): string | undefined {\n const current = req.get(ADMIN_SECRET_HEADER);\n if (current) return current;\n const legacy = req.get(LEGACY_ADMIN_SECRET_HEADER);\n if (legacy) {\n warnOnce(\n 'header',\n `[linked] the ${LEGACY_ADMIN_SECRET_HEADER} header is deprecated; send ${ADMIN_SECRET_HEADER} instead.`,\n warn\n );\n }\n return legacy;\n}\n\n/** What one shape sync did. */\nexport interface ShapeSyncResult {\n /**\n * Shape graphs written (package graphs and the app's own graph) because their stored hash\n * differed; on a store without graph queries, the default-graph sync's writes.\n */\n written: number;\n /** The orphan policy the sync ran with (see utils/syncShapes). */\n orphanScope?: 'all' | 'none';\n /** Set when no sync ran: shape sync is turned off, or there is no default dataset. */\n skipped?: 'disabled' | 'no-default-dataset';\n /**\n * The shape graph sync: packages written and skipped, the graphs written, the app's own shape\n * graph, the registered packages without `shapes.ttl` (so without a shape graph) and the\n * default-graph shapes removed. Absent when no sync ran.\n */\n shapeGraphs?: ShapeGraphsReport;\n /**\n * The requested packages whose source was reloaded before the sync (dev only: a built\n * app reloads nothing). Set by `POST /admin/sync-shapes`.\n */\n reloaded?: string[];\n /** The node shape IRIs registered in the process after the reload. Set by `POST /admin/sync-shapes`. */\n shapes?: string[];\n /**\n * The ontology sync: packages whose ontology graph (or, on a store without graph queries,\n * class hierarchy) was written, packages left as they were (unchanged, or their data could not\n * be read), and the graphs written and dropped. Set by `POST /admin/sync-shapes`.\n */\n ontologies?: { written: string[]; skipped: string[]; graphs?: string[]; droppedGraphs?: string[] };\n /** The shape check's findings, counted by severity (each is logged). Set by `POST /admin/sync-shapes`. */\n shapeCheck?: { errors: number; warnings: number };\n}\n\n/** npm's package-name rule: optional `@scope/`, lower case, URL-safe, no leading `.` or `_` (a scope may start with `_`, as `@_linked` does). */\nconst NPM_NAME = /^(?:@[a-z0-9-~_][a-z0-9-._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*$/;\n\n/** Whether `name` is a valid npm package name (and so can never be a path like `..`). */\nexport function isValidPackageName(name: string): boolean {\n return name.length <= 214 && NPM_NAME.test(name) && !name.split('/').some((part) => part === '.' || part === '..');\n}\n\n/**\n * Refuse a package a host may not ask to reload: not a valid npm name, a framework\n * (`@_linked/*`) package, or not in `allowed` (the app's own name and its direct\n * dependencies). Throws {@link AdminRequestError}.\n */\nexport function assertReloadablePackages(packages: string[], allowed: ReadonlySet<string>): void {\n for (const pkg of packages) {\n if (!isValidPackageName(pkg)) throw new AdminRequestError(`not a package name: ${JSON.stringify(pkg)}`);\n if (pkg.startsWith('@_linked/')) throw new AdminRequestError(`framework packages are not reloaded: ${pkg}`);\n if (!allowed.has(pkg)) {\n throw new AdminRequestError(`unknown package: ${pkg} is neither the app nor one of its direct dependencies`);\n }\n }\n}\n\n/** Thrown by a handler for a request the channel refuses with 400. */\nexport class AdminRequestError extends Error {}\n\nexport interface AdminChannelHandlers {\n /** Exit the process (the host restarts it). Called after the 202 is sent. */\n exit: () => void;\n /** Reload `packages` (when given) and sync the registered shapes into app data. */\n syncShapes: (request: { packages: string[] }) => Promise<ShapeSyncResult>;\n /**\n * Drop the cached graph registry for `dataset` (see `POST /admin/graph-registry-changed`).\n * Default: {@link dropGraphRegistryCache}.\n */\n graphRegistryChanged?: (dataset: string) => void;\n /** The effective graph routes (see `GET /admin/routes`). Default: {@link effectiveGraphRoutes}. */\n routes?: () => EffectiveRoutes;\n /** Where deprecation warnings go. Default: `console.warn`. */\n warn?: (message: string) => void;\n}\n\n/**\n * Drop the process's cached graph registry after the host changed the graphs of `dataset`.\n *\n * Every entry is dropped, not only that dataset's: an entry's key is each store's own\n * (`graphRegistryKey()`: a `FusekiStore` keys by its dataset URL, a `RemoteDataset` by the\n * dataset its server names), no store tells which key it uses, and the stores are built by the\n * app's storage config, whose aliases the server does not see. The registry is small, so the cost\n * is one registry read per dataset on its next expanding read.\n */\nexport function dropGraphRegistryCache(_dataset: string): void {\n invalidateGraphRegistry();\n}\n\n/** The `dataset` of a graph-registry-changed body: a non-empty name. */\nexport function parseGraphRegistryChangedBody(body: unknown): { dataset: string } {\n const dataset = body && typeof body === 'object' ? (body as { dataset?: unknown }).dataset : undefined;\n if (typeof dataset !== 'string' || !dataset.trim() || dataset.length > 1024) {\n throw new AdminRequestError('dataset must be the name of the dataset whose graphs changed');\n }\n return { dataset: dataset.trim() };\n}\n\n/**\n * Compare the provided secret with the configured one in constant time. Both sides are hashed\n * first, so the comparison does not reveal the secret's length either.\n */\nexport function adminSecretMatches(\n secret: string | undefined,\n provided: string | undefined\n): boolean {\n if (!secret || !provided) return false;\n const digest = (value: string) => createHash('sha256').update(value).digest();\n return timingSafeEqual(digest(provided), digest(secret));\n}\n\n/** The `packages` of a sync-shapes body: a list of package names, or none. */\nexport function parseSyncShapesBody(body: unknown): { packages: string[] } {\n if (body === undefined || body === null || body === '') return { packages: [] };\n if (typeof body !== 'object') {\n throw new AdminRequestError('the body must be a JSON object');\n }\n const packages = (body as { packages?: unknown }).packages;\n if (packages === undefined) return { packages: [] };\n if (!Array.isArray(packages) || packages.some((p) => typeof p !== 'string' || !p)) {\n throw new AdminRequestError('packages must be a list of package names');\n }\n for (const p of packages as string[]) {\n if (!isValidPackageName(p)) throw new AdminRequestError(`not a package name: ${JSON.stringify(p)}`);\n }\n return { packages: [...new Set(packages as string[])] };\n}\n\n/** Mount the control channel's routes on `app`. */\nexport function registerAdminRoutes(\n app: express.Express,\n secret: string | undefined,\n handlers: AdminChannelHandlers\n): void {\n const requireAdmin = (req: express.Request, res: express.Response): boolean => {\n if (!secret) {\n res.sendStatus(404); // no control channel in this mode\n return false;\n }\n if (!adminSecretMatches(secret, providedSecret(req, handlers.warn ?? console.warn))) {\n res.sendStatus(401);\n return false;\n }\n return true;\n };\n\n app.get('/admin/health', (req, res) => {\n if (!requireAdmin(req, res)) return;\n res.status(200).json({ status: 'ok' });\n });\n\n app.post('/admin/restart', (req, res) => {\n if (!requireAdmin(req, res)) return;\n res.sendStatus(202);\n setTimeout(handlers.exit, 50);\n });\n\n app.get('/admin/routes', (req, res) => {\n if (!requireAdmin(req, res)) return;\n try {\n res.status(200).json((handlers.routes ?? effectiveGraphRoutes)());\n } catch (err) {\n res.status(500).json({ error: err instanceof Error ? err.message : String(err) });\n }\n });\n\n app.post('/admin/graph-registry-changed', (req, res) => {\n if (!requireAdmin(req, res)) return;\n let dataset: string;\n try {\n ({ dataset } = parseGraphRegistryChangedBody(req.body));\n } catch (err) {\n res.status(400).json({ error: (err as Error).message });\n return;\n }\n (handlers.graphRegistryChanged ?? dropGraphRegistryCache)(dataset);\n res.status(200).json({ invalidated: dataset });\n });\n\n app.post('/admin/sync-shapes', async (req, res) => {\n if (!requireAdmin(req, res)) return;\n let request: { packages: string[] };\n try {\n request = parseSyncShapesBody(req.body);\n } catch (err) {\n res.status(400).json({ error: (err as Error).message });\n return;\n }\n try {\n res.status(200).json(await handlers.syncShapes(request));\n } catch (err) {\n const status = err instanceof AdminRequestError ? 400 : 500;\n res.status(status).json({ error: err instanceof Error ? err.message : String(err) });\n }\n });\n}\n"]}
@@ -0,0 +1,26 @@
1
+ /** One dataset the router knows, and its graph routes. */
2
+ export interface DatasetRoutes {
3
+ /**
4
+ * The store's class name (`FusekiStore`, `RemoteDataset`, …). The server never sees the
5
+ * aliases of the storage config, so a dataset is told apart by this, `default` and `id`.
6
+ */
7
+ store: string;
8
+ /** The store's `id`, when it has one (a named `RemoteDataset` does). */
9
+ id?: string;
10
+ /** Whether this is the default dataset (the one for every shape without a pin). */
11
+ default: boolean;
12
+ /** The dataset's routes as registered: shape IRI → graph IRI. */
13
+ routes: Record<string, string>;
14
+ /**
15
+ * The registered shapes that resolve to this dataset and whose instances are written to its
16
+ * default graph: no route of their own and no routed super-shape. Sorted.
17
+ */
18
+ defaultGraphShapes: string[];
19
+ }
20
+ /** The body of `GET /admin/routes`. */
21
+ export interface EffectiveRoutes {
22
+ /** Every dataset the router knows, the default one first, then in the order they were pinned. */
23
+ datasets: DatasetRoutes[];
24
+ }
25
+ /** The routes of every dataset the router knows (see {@link EffectiveRoutes}). */
26
+ export declare function effectiveGraphRoutes(): EffectiveRoutes;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The graph routes the process runs with, as core's routing holds them once the app's storage
3
+ * file has run (`LinkedStorage.setDatasetForShapes(dataset, shapes, {graph})`, or `loadStores`
4
+ * for a datasets config that still carries routes). Answered by `GET /admin/routes`, so a host
5
+ * can compare what it wrote into the storage file with what actually runs.
6
+ */
7
+ import { LinkedStorage } from '@_linked/core/utils/LinkedStorage';
8
+ import { getShapeGraphs, graphRoutingFor } from '@_linked/core/utils/graphRouting';
9
+ import { getAllShapeClasses } from '@_linked/core/utils/ShapeClass';
10
+ import { graphs } from '@_linked/core/ontologies/graphs';
11
+ /** The routes of every dataset the router knows (see {@link EffectiveRoutes}). */
12
+ export function effectiveGraphRoutes() {
13
+ var _a, _b;
14
+ const datasets = [...LinkedStorage.getDatasets()];
15
+ const defaultDataset = LinkedStorage.getDefaultDataset();
16
+ const defaultGraphShapes = new Map(datasets.map((ds) => [ds, []]));
17
+ for (const [iri, shapeClass] of getAllShapeClasses()) {
18
+ const dataset = LinkedStorage.getDatasetForShapeClass(shapeClass);
19
+ const list = dataset && defaultGraphShapes.get(dataset);
20
+ if (!list)
21
+ continue;
22
+ const graph = (_b = (_a = graphRoutingFor(dataset)) === null || _a === void 0 ? void 0 : _a.graphForShape(iri)) !== null && _b !== void 0 ? _b : graphs.default;
23
+ if (graph === graphs.default)
24
+ list.push(iri);
25
+ }
26
+ return {
27
+ datasets: datasets.map((dataset) => {
28
+ var _a, _b;
29
+ const id = dataset.id;
30
+ return {
31
+ store: ((_a = dataset === null || dataset === void 0 ? void 0 : dataset.constructor) === null || _a === void 0 ? void 0 : _a.name) || 'unknown',
32
+ ...(typeof id === 'string' && id ? { id } : {}),
33
+ default: dataset === defaultDataset,
34
+ routes: getShapeGraphs(dataset),
35
+ defaultGraphShapes: ((_b = defaultGraphShapes.get(dataset)) !== null && _b !== void 0 ? _b : []).sort(),
36
+ };
37
+ }),
38
+ };
39
+ }
40
+ //# sourceMappingURL=effectiveRoutes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effectiveRoutes.js","sourceRoot":"","sources":["../../../src/utils/effectiveRoutes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC;AA6BzD,kFAAkF;AAClF,MAAM,UAAU,oBAAoB;;IAClC,MAAM,QAAQ,GAAG,CAAC,GAAG,aAAa,CAAC,WAAW,EAAE,CAAC,CAAC;IAClD,MAAM,cAAc,GAAG,aAAa,CAAC,iBAAiB,EAAE,CAAC;IACzD,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAqB,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACvF,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,kBAAkB,EAAE,EAAE,CAAC;QACrD,MAAM,OAAO,GAAG,aAAa,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC;QAClE,MAAM,IAAI,GAAG,OAAO,IAAI,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,KAAK,GAAG,MAAA,MAAA,eAAe,CAAC,OAAO,CAAC,0CAAE,aAAa,CAAC,GAAG,CAAC,mCAAI,MAAM,CAAC,OAAO,CAAC;QAC7E,IAAI,KAAK,KAAK,MAAM,CAAC,OAAO;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;;YACjC,MAAM,EAAE,GAAI,OAA4B,CAAC,EAAE,CAAC;YAC5C,OAAO;gBACL,KAAK,EAAE,CAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,WAAW,0CAAE,IAAI,KAAI,SAAS;gBAC9C,GAAG,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/C,OAAO,EAAE,OAAO,KAAK,cAAc;gBACnC,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC;gBAC/B,kBAAkB,EAAE,CAAC,MAAA,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,mCAAI,EAAE,CAAC,CAAC,IAAI,EAAE;aACnE,CAAC;QACJ,CAAC,CAAC;KACH,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The graph routes the process runs with, as core's routing holds them once the app's storage\n * file has run (`LinkedStorage.setDatasetForShapes(dataset, shapes, {graph})`, or `loadStores`\n * for a datasets config that still carries routes). Answered by `GET /admin/routes`, so a host\n * can compare what it wrote into the storage file with what actually runs.\n */\nimport { LinkedStorage } from '@_linked/core/utils/LinkedStorage';\nimport { getShapeGraphs, graphRoutingFor } from '@_linked/core/utils/graphRouting';\nimport { getAllShapeClasses } from '@_linked/core/utils/ShapeClass';\nimport { graphs } from '@_linked/core/ontologies/graphs';\nimport type { IDataset } from '@_linked/core/interfaces/IDataset';\n\n/** One dataset the router knows, and its graph routes. */\nexport interface DatasetRoutes {\n /**\n * The store's class name (`FusekiStore`, `RemoteDataset`, …). The server never sees the\n * aliases of the storage config, so a dataset is told apart by this, `default` and `id`.\n */\n store: string;\n /** The store's `id`, when it has one (a named `RemoteDataset` does). */\n id?: string;\n /** Whether this is the default dataset (the one for every shape without a pin). */\n default: boolean;\n /** The dataset's routes as registered: shape IRI → graph IRI. */\n routes: Record<string, string>;\n /**\n * The registered shapes that resolve to this dataset and whose instances are written to its\n * default graph: no route of their own and no routed super-shape. Sorted.\n */\n defaultGraphShapes: string[];\n}\n\n/** The body of `GET /admin/routes`. */\nexport interface EffectiveRoutes {\n /** Every dataset the router knows, the default one first, then in the order they were pinned. */\n datasets: DatasetRoutes[];\n}\n\n/** The routes of every dataset the router knows (see {@link EffectiveRoutes}). */\nexport function effectiveGraphRoutes(): EffectiveRoutes {\n const datasets = [...LinkedStorage.getDatasets()];\n const defaultDataset = LinkedStorage.getDefaultDataset();\n const defaultGraphShapes = new Map<IDataset, string[]>(datasets.map((ds) => [ds, []]));\n for (const [iri, shapeClass] of getAllShapeClasses()) {\n const dataset = LinkedStorage.getDatasetForShapeClass(shapeClass);\n const list = dataset && defaultGraphShapes.get(dataset);\n if (!list) continue;\n const graph = graphRoutingFor(dataset)?.graphForShape(iri) ?? graphs.default;\n if (graph === graphs.default) list.push(iri);\n }\n return {\n datasets: datasets.map((dataset) => {\n const id = (dataset as { id?: unknown }).id;\n return {\n store: dataset?.constructor?.name || 'unknown',\n ...(typeof id === 'string' && id ? { id } : {}),\n default: dataset === defaultDataset,\n routes: getShapeGraphs(dataset),\n defaultGraphShapes: (defaultGraphShapes.get(dataset) ?? []).sort(),\n };\n }),\n };\n}\n"]}
@@ -101,8 +101,8 @@ export declare function bootSyncsOntologies(env: Record<string, string | undefin
101
101
  export declare function loadOntologyCore(): Promise<OntologyCore>;
102
102
  /**
103
103
  * The package directories whose ontologies register before the sync: every linked package
104
- * discovered in the app's dependency tree, then the app's own workspace packages (a CN clone's
105
- * `packages/<slug>-ont` is one no package imports, so the dependency walk never reaches it).
104
+ * discovered in the app's dependency tree, then the app's own workspace packages (an ontology
105
+ * package in `packages/` that no package imports is one, so the dependency walk never reaches it).
106
106
  * Order kept, duplicates dropped.
107
107
  */
108
108
  export declare function ontologyPackageDirs(linkedPackageDirs: Iterable<string>, workspacePackageDirs: Iterable<string>): string[];
@@ -36,8 +36,8 @@ export async function loadOntologyCore() {
36
36
  }
37
37
  /**
38
38
  * The package directories whose ontologies register before the sync: every linked package
39
- * discovered in the app's dependency tree, then the app's own workspace packages (a CN clone's
40
- * `packages/<slug>-ont` is one no package imports, so the dependency walk never reaches it).
39
+ * discovered in the app's dependency tree, then the app's own workspace packages (an ontology
40
+ * package in `packages/` that no package imports is one, so the dependency walk never reaches it).
41
41
  * Order kept, duplicates dropped.
42
42
  */
43
43
  export function ontologyPackageDirs(linkedPackageDirs, workspacePackageDirs) {