@static-suite/static-suite-data-server 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/http/routes/diff.d.ts +7 -0
  2. package/dist/http/routes/diff.d.ts.map +1 -0
  3. package/dist/http/routes/diff.js +40 -0
  4. package/dist/http/routes/home.d.ts.map +1 -1
  5. package/dist/http/routes/home.js +1 -0
  6. package/dist/http/routes/index.d.ts.map +1 -1
  7. package/dist/http/routes/index.js +5 -0
  8. package/dist/http/views/diffIndex.pug +14 -0
  9. package/dist/lib/dataServer.d.ts.map +1 -1
  10. package/dist/lib/dataServer.js +11 -0
  11. package/dist/lib/store/diff/diff.types.d.ts.map +1 -1
  12. package/dist/lib/store/diff/diffManager.d.ts.map +1 -1
  13. package/dist/lib/store/dump/dump.types.d.ts +4 -0
  14. package/dist/lib/store/dump/dump.types.d.ts.map +1 -1
  15. package/dist/lib/store/dump/dumpIndexHelper.d.ts +14 -0
  16. package/dist/lib/store/dump/dumpIndexHelper.d.ts.map +1 -0
  17. package/dist/lib/store/dump/dumpIndexHelper.js +112 -0
  18. package/dist/lib/store/dump/dumpManager.d.ts.map +1 -1
  19. package/dist/lib/store/dump/dumpManager.js +81 -48
  20. package/dist/lib/store/dump/dumpMetadataHelper.d.ts.map +1 -1
  21. package/dist/lib/store/dump/dumpMetadataHelper.js +1 -0
  22. package/dist/lib/utils/string/stringUtils.d.ts +6 -0
  23. package/dist/lib/utils/string/stringUtils.d.ts.map +1 -1
  24. package/dist/lib/utils/string/stringUtils.js +8 -1
  25. package/package.json +3 -2
  26. package/src/http/routes/diff.ts +47 -0
  27. package/src/http/routes/home.ts +1 -0
  28. package/src/http/routes/index.ts +5 -0
  29. package/src/http/routes/subset.ts +1 -1
  30. package/src/http/views/diffIndex.pug +14 -0
  31. package/src/lib/dataServer.ts +17 -1
  32. package/src/lib/store/diff/diff.types.ts +1 -0
  33. package/src/lib/store/diff/diffManager.ts +1 -0
  34. package/src/lib/store/dump/dump.types.ts +5 -0
  35. package/src/lib/store/dump/dumpIndexHelper.ts +134 -0
  36. package/src/lib/store/dump/dumpManager.ts +112 -58
  37. package/src/lib/store/dump/dumpMetadataHelper.ts +1 -0
  38. package/src/lib/utils/string/stringUtils.ts +8 -0
  39. package/tsconfig.json +2 -2
@@ -0,0 +1,7 @@
1
+ import { Request, Response } from 'express';
2
+ declare const diffIndex: (req: Request, res: Response) => void;
3
+ declare const diffIncremental: (req: Request, res: Response) => void;
4
+ declare const diffFull: (req: Request, res: Response) => void;
5
+ declare const diffReset: (req: Request, res: Response) => void;
6
+ export { diffIndex, diffIncremental, diffFull, diffReset };
7
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/http/routes/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAI5C,QAAA,MAAM,SAAS,GAAI,KAAK,OAAO,EAAE,KAAK,QAAQ,KAAG,IAShD,CAAC;AAeF,QAAA,MAAM,eAAe,GAAI,KAAK,OAAO,EAAE,KAAK,QAAQ,KAAG,IAEtD,CAAC;AAEF,QAAA,MAAM,QAAQ,GAAI,KAAK,OAAO,EAAE,KAAK,QAAQ,KAAG,IAE/C,CAAC;AAEF,QAAA,MAAM,SAAS,GAAI,KAAK,OAAO,EAAE,KAAK,QAAQ,KAAG,IAQhD,CAAC;AAEF,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.diffReset = exports.diffFull = exports.diffIncremental = exports.diffIndex = void 0;
4
+ const diff_1 = require("../../lib/store/diff");
5
+ const object_1 = require("../../lib/utils/object");
6
+ const diffIndex = (req, res) => {
7
+ res.render('diffIndex', {
8
+ links: {
9
+ '/diff/incremental': 'Execute an incremental dump',
10
+ '/diff/full': 'Execute a full dump',
11
+ '/diff/reset': 'Clears intermediate changes tracked by dependency manager',
12
+ },
13
+ });
14
+ };
15
+ exports.diffIndex = diffIndex;
16
+ const diffAction = (req, res, incremental) => {
17
+ const diff = diff_1.diffManager.getDiff({ incremental });
18
+ const diffAsJson = (0, object_1.jsonify)(diff);
19
+ res.status(200);
20
+ res.set({ 'Content-Type': 'application/json' });
21
+ res.send(diffAsJson);
22
+ };
23
+ const diffIncremental = (req, res) => {
24
+ diffAction(req, res, true);
25
+ };
26
+ exports.diffIncremental = diffIncremental;
27
+ const diffFull = (req, res) => {
28
+ diffAction(req, res, false);
29
+ };
30
+ exports.diffFull = diffFull;
31
+ const diffReset = (req, res) => {
32
+ const args = req.query;
33
+ if (args?.uniqueId) {
34
+ diff_1.diffManager.reset(args.uniqueId);
35
+ }
36
+ res.status(200);
37
+ res.set({ 'Content-Type': 'application/json' });
38
+ res.send({ status: 'done' });
39
+ };
40
+ exports.diffReset = diffReset;
@@ -1 +1 @@
1
- {"version":3,"file":"home.d.ts","sourceRoot":"","sources":["../../../src/http/routes/home.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE5C,eAAO,MAAM,IAAI,GAAI,KAAK,OAAO,EAAE,KAAK,QAAQ,KAAG,IAalD,CAAC"}
1
+ {"version":3,"file":"home.d.ts","sourceRoot":"","sources":["../../../src/http/routes/home.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE5C,eAAO,MAAM,IAAI,GAAI,KAAK,OAAO,EAAE,KAAK,QAAQ,KAAG,IAclD,CAAC"}
@@ -6,6 +6,7 @@ const home = (req, res) => {
6
6
  links: {
7
7
  '/data': 'Browse all data loaded into the server',
8
8
  '/dependency': 'Dependency data',
9
+ '/diff': 'Diff operations',
9
10
  '/dump': 'Dump operations',
10
11
  '/query': 'List of available queries',
11
12
  '/task': 'List of available tasks',
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/http/routes/index.ts"],"names":[],"mappings":"AAmCA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAiCxB,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/http/routes/index.ts"],"names":[],"mappings":"AAoCA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAqCxB,eAAe,MAAM,CAAC"}
@@ -13,6 +13,7 @@ const dependency_1 = require("./dependency");
13
13
  const indexUrl_1 = require("./indexUrl");
14
14
  const indexUUID_1 = require("./indexUUID");
15
15
  const subset_1 = require("./subset");
16
+ const diff_1 = require("./diff");
16
17
  const routes = (0, express_1.Router)();
17
18
  routes.get('/', home_1.home);
18
19
  routes.get('/docs', docs_1.docs);
@@ -28,6 +29,10 @@ routes.get('/task', task_1.taskIndex);
28
29
  routes.get('/task/:taskId', task_1.runTask);
29
30
  routes.get('/cache', cache_1.cacheIndex);
30
31
  routes.get('/cache/:cacheBinId/clear', cache_1.cacheClear);
32
+ routes.get('/diff', diff_1.diffIndex);
33
+ routes.get('/diff/incremental', diff_1.diffIncremental);
34
+ routes.get('/diff/full', diff_1.diffFull);
35
+ routes.get('/diff/reset', diff_1.diffReset);
31
36
  routes.get('/dump', dump_1.dumpIndex);
32
37
  routes.get('/dump/incremental', dump_1.dumpIncremental);
33
38
  routes.get('/dump/full', dump_1.dumpFull);
@@ -0,0 +1,14 @@
1
+ extends includes/layout.pug
2
+
3
+ block vars
4
+ - var title = "Diff | Static Suite Data Server"
5
+
6
+ block content
7
+ h1(class="title is-1")="Diff"
8
+ table(class="table is-fullwidth is-hoverable")
9
+ tbody
10
+ each description, link in links
11
+ tr
12
+ td
13
+ a(href=link)= link
14
+ td=description
@@ -1 +1 @@
1
- {"version":3,"file":"dataServer.d.ts","sourceRoot":"","sources":["../../src/lib/dataServer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAKhE,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAG5C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EACL,WAAW,EACX,uBAAuB,EACvB,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,OAAO,EACP,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,oBAAoB,CAAC;AAE5B;;;;GAIG;AACH,QAAA,MAAM,UAAU;IACd;;;;;;OAMG;oBACa,qBAAqB,KAAG,gBAAgB;CA6BzD,CAAC;AACF,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,gBAAgB,EAChB,OAAO,EACP,QAAQ,EACR,OAAO,EACP,KAAK,EACL,WAAW,EACX,uBAAuB,EACvB,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,UAAU,EACV,WAAW,GACZ,CAAC"}
1
+ {"version":3,"file":"dataServer.d.ts","sourceRoot":"","sources":["../../src/lib/dataServer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAKhE,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAG5C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EACL,WAAW,EACX,uBAAuB,EACvB,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,OAAO,EACP,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,oBAAoB,CAAC;AAG5B;;;;GAIG;AACH,QAAA,MAAM,UAAU;IACd;;;;;;OAMG;oBACa,qBAAqB,KAAG,gBAAgB;CA4CzD,CAAC;AACF,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,gBAAgB,EAChB,OAAO,EACP,QAAQ,EACR,OAAO,EACP,KAAK,EACL,WAAW,EACX,uBAAuB,EACvB,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,UAAU,EACV,WAAW,GACZ,CAAC"}
@@ -14,6 +14,7 @@ const query_types_1 = require("./query/query.types");
14
14
  Object.defineProperty(exports, "CacheStatus", { enumerable: true, get: function () { return query_types_1.CacheStatus; } });
15
15
  const dataServer_types_1 = require("./dataServer.types");
16
16
  Object.defineProperty(exports, "RunMode", { enumerable: true, get: function () { return dataServer_types_1.RunMode; } });
17
+ const dumpIndexHelper_1 = require("./store/dump/dumpIndexHelper");
17
18
  /**
18
19
  * The Data Server instance.
19
20
  *
@@ -28,6 +29,7 @@ const dataServer = {
28
29
  * @returns An object with the data store and the queryRunner service.
29
30
  */
30
31
  init: (options) => {
32
+ const startDate = Date.now();
31
33
  // Configure logger.
32
34
  (0, logger_1.configureLogger)(options.logLevel, options.logFile);
33
35
  // Configure data server.
@@ -46,6 +48,15 @@ const dataServer = {
46
48
  }
47
49
  // Load data from dataDir.
48
50
  dataDir_1.dataDirManager.load();
51
+ // Create and load dump index.
52
+ if (config.dumpDir) {
53
+ if (dumpIndexHelper_1.dumpIndexHelper.isDumpIndexStale() ||
54
+ !dumpIndexHelper_1.dumpIndexHelper.isDumpIndexPresent()) {
55
+ dumpIndexHelper_1.dumpIndexHelper.createDumpIndex();
56
+ }
57
+ dumpIndexHelper_1.dumpIndexHelper.loadDumpIndex();
58
+ }
59
+ logger_1.logger.info(`Data Server loaded in ${Date.now() - startDate}ms.`);
49
60
  return {
50
61
  store: store_1.store,
51
62
  queryRunner: query_1.queryRunner,
@@ -1 +1 @@
1
- {"version":3,"file":"diff.types.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/diff/diff.types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB;;;;OAIG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAE9B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACnD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,IAAI,GAAG;IACjB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAErB;;OAEG;IACH,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACtB,CAAC"}
1
+ {"version":3,"file":"diff.types.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/diff/diff.types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB;;;;OAIG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAE9B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACnD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,IAAI,GAAG;IACjB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAErB;;OAEG;IACH,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACtB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"diffManager.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/diff/diffManager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAQ,MAAM,cAAc,CAAC;AAQjD,eAAO,MAAM,WAAW,EAAE,WAuEzB,CAAC"}
1
+ {"version":3,"file":"diffManager.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/diff/diffManager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAQ,MAAM,cAAc,CAAC;AAQjD,eAAO,MAAM,WAAW,EAAE,WAwEzB,CAAC"}
@@ -72,4 +72,8 @@ export type DumpMetadata = {
72
72
  current: string;
73
73
  dumps: Dump[];
74
74
  };
75
+ export type IndexEntry = {
76
+ hash: string;
77
+ url: string | null;
78
+ };
75
79
  //# sourceMappingURL=dump.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dump.types.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/dump/dump.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAE1C;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAE/C;;;;;;;OAOG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,IAAI,GAAG;IACjB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;;;OAIG;IACH,OAAO,EAAE,GAAG,CACV,MAAM,EACN;QAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAC7D,CAAC;IAEF;;;;;OAKG;IACH,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,YAAY,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IAE1E;;OAEG;IACH,IAAI,EAAE,IAAI,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf,CAAC"}
1
+ {"version":3,"file":"dump.types.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/dump/dump.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAE1C;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAE/C;;;;;;;OAOG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,IAAI,GAAG;IACjB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;;;OAIG;IACH,OAAO,EAAE,GAAG,CACV,MAAM,EACN;QAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAC7D,CAAC;IAEF;;;;;OAKG;IACH,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,YAAY,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IAE1E;;OAEG;IACH,IAAI,EAAE,IAAI,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { IndexEntry } from './dump.types';
2
+ export declare const dumpIndexHelper: {
3
+ isDumpIndexStale: () => boolean;
4
+ isDumpIndexPresent: () => boolean;
5
+ saveDumpIndex: () => void;
6
+ createDumpIndex: () => void;
7
+ loadDumpIndex: () => void;
8
+ hasEntry: (relativeFilePath: string) => boolean;
9
+ getEntry: (relativeFilePath: string) => IndexEntry | undefined;
10
+ getKeys: () => MapIterator<string>;
11
+ addEntry: (relativeFilePath: string, entry: IndexEntry) => void;
12
+ removeEntry: (relativeFilePath: string) => void;
13
+ };
14
+ //# sourceMappingURL=dumpIndexHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dumpIndexHelper.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/dump/dumpIndexHelper.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAiC1C,eAAO,MAAM,eAAe;4BACJ,OAAO;8BAYL,OAAO;yBAEZ,IAAI;2BASF,IAAI;yBAgCN,IAAI;iCAqBM,MAAM,KAAG,OAAO;iCAEhB,MAAM,KAAG,UAAU,GAAG,SAAS;;iCAK/B,MAAM,SAAS,UAAU,KAAG,IAAI;oCAK7B,MAAM,KAAG,IAAI;CAI9C,CAAC"}
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.dumpIndexHelper = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const logger_1 = require("../../utils/logger");
9
+ const string_1 = require("../../utils/string");
10
+ const config_1 = require("../../config");
11
+ // import { Dump, DumpMetadata } from './dump.types';
12
+ // import { unixEpochUniqueId } from '../workDir';
13
+ const fs_2 = require("../../utils/fs");
14
+ const index = new Map();
15
+ let isStale = undefined;
16
+ const getIndexFilepath = () => `${config_1.config.dumpDir}/files.idx`;
17
+ const getIndexIsStaleFlagFilepath = () => `${config_1.config.dumpDir}/index-is-stale.flag`;
18
+ const indexToString = () => {
19
+ const indexLines = [];
20
+ index.forEach((value, filePath) => {
21
+ const line = [filePath, value.hash, value.url];
22
+ indexLines.push(line.join('\t'));
23
+ });
24
+ return indexLines.join('\n');
25
+ };
26
+ const setIsStale = (value) => {
27
+ if (isStale !== value) {
28
+ isStale = value;
29
+ const indexIsStaleFlagFilepath = getIndexIsStaleFlagFilepath();
30
+ if (isStale === true) {
31
+ fs_1.default.writeFileSync(indexIsStaleFlagFilepath, '');
32
+ }
33
+ else {
34
+ if (fs_1.default.existsSync(indexIsStaleFlagFilepath)) {
35
+ fs_1.default.unlinkSync(indexIsStaleFlagFilepath);
36
+ }
37
+ }
38
+ }
39
+ };
40
+ exports.dumpIndexHelper = {
41
+ isDumpIndexStale: () => {
42
+ if (isStale === undefined) {
43
+ const indexIsStaleFlagFilepath = getIndexIsStaleFlagFilepath();
44
+ if (fs_1.default.existsSync(indexIsStaleFlagFilepath)) {
45
+ setIsStale(true);
46
+ }
47
+ else {
48
+ setIsStale(false);
49
+ }
50
+ }
51
+ return !!isStale;
52
+ },
53
+ isDumpIndexPresent: () => fs_1.default.existsSync(getIndexFilepath()),
54
+ saveDumpIndex: () => {
55
+ if (isStale) {
56
+ const indexFilepath = getIndexFilepath();
57
+ const indexAsString = indexToString().trim();
58
+ fs_1.default.writeFileSync(indexFilepath, indexAsString);
59
+ setIsStale(false);
60
+ }
61
+ },
62
+ createDumpIndex: () => {
63
+ if (config_1.config.dumpDir) {
64
+ setIsStale(true);
65
+ logger_1.logger.info('Creating dump index...');
66
+ const startDate = Date.now();
67
+ index.clear();
68
+ const filesDumpDir = `${config_1.config.dumpDir}/files`;
69
+ const relativeFilePaths = (0, fs_2.findFilesInDir)(filesDumpDir);
70
+ relativeFilePaths.forEach(relativeFilePath => {
71
+ const fileContent = (0, fs_2.getFileContent)(`${filesDumpDir}/${relativeFilePath}`);
72
+ if (fileContent.raw) {
73
+ const url = fileContent.json
74
+ ? fileContent.json.data?.content?.url?.path
75
+ : null;
76
+ const hash = (0, string_1.createHash)(fileContent.raw);
77
+ index.set(relativeFilePath, { hash, url });
78
+ }
79
+ });
80
+ // Save index into disk
81
+ exports.dumpIndexHelper.saveDumpIndex();
82
+ logger_1.logger.info(`${relativeFilePaths.length} files in dump indexed in ${Date.now() - startDate}ms.`);
83
+ }
84
+ },
85
+ loadDumpIndex: () => {
86
+ if (config_1.config.dumpDir) {
87
+ logger_1.logger.info('Loading dump index...');
88
+ const startDate = Date.now();
89
+ const indexFilepath = getIndexFilepath();
90
+ const data = fs_1.default.readFileSync(indexFilepath).toString().trim();
91
+ index.clear();
92
+ // Split lines and remove empty ones.
93
+ const dataLines = data.split('\n').filter(line => !!line);
94
+ dataLines.forEach(line => {
95
+ const [relativeFilePath, hash, url] = line.split('\t');
96
+ index.set(relativeFilePath, { hash, url });
97
+ });
98
+ logger_1.logger.info(`${dataLines.length} entries loaded from dump index in ${Date.now() - startDate}ms.`);
99
+ }
100
+ },
101
+ hasEntry: (relativeFilePath) => index.has(relativeFilePath),
102
+ getEntry: (relativeFilePath) => index.get(relativeFilePath),
103
+ getKeys: () => index.keys(),
104
+ addEntry: (relativeFilePath, entry) => {
105
+ setIsStale(true);
106
+ index.set(relativeFilePath, entry);
107
+ },
108
+ removeEntry: (relativeFilePath) => {
109
+ setIsStale(true);
110
+ index.delete(relativeFilePath);
111
+ },
112
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"dumpManager.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/dump/dumpManager.ts"],"names":[],"mappings":"AAcA,OAAO,EAAQ,WAAW,EAAE,MAAM,cAAc,CAAC;AA2GjD,eAAO,MAAM,WAAW,EAAE,WA8EzB,CAAC"}
1
+ {"version":3,"file":"dumpManager.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/dump/dumpManager.ts"],"names":[],"mappings":"AASA,OAAO,EAAQ,WAAW,EAAE,MAAM,cAAc,CAAC;AAqKjD,eAAO,MAAM,WAAW,EAAE,WA+EzB,CAAC"}
@@ -14,59 +14,49 @@ const store_1 = require("../store");
14
14
  const diffManager_1 = require("../diff/diffManager");
15
15
  const hook_1 = require("../hook");
16
16
  const dumpMetadataHelper_1 = require("./dumpMetadataHelper");
17
- const storeUpdatedFiles = (diff, dump, dumpDir) => {
17
+ const dumpIndexHelper_1 = require("./dumpIndexHelper");
18
+ const string_1 = require("../../utils/string");
19
+ const storeUpdatedFiles = (diff, dump, filesDumpDir) => {
18
20
  diff.updated.forEach(relativeFilepath => {
19
21
  const storeFileContentData = store_1.store.data.get(relativeFilepath);
20
22
  if (storeFileContentData) {
21
- const absoluteFilepathInDumpDir = `${dumpDir}/${relativeFilepath}`;
23
+ const absoluteFilepathInDumpDir = `${filesDumpDir}/${relativeFilepath}`;
22
24
  try {
23
- // Create parent directories if they are missing.
24
- const dir = path_1.default.dirname(absoluteFilepathInDumpDir);
25
- if (!fs_1.default.existsSync(dir)) {
26
- fs_1.default.mkdirSync(dir, { recursive: true });
27
- }
28
25
  // If store data is an object, stringify it to execute its
29
26
  // queries and resolve its includes.
30
27
  const isJson = (0, fs_2.isJsonFile)(relativeFilepath);
31
28
  const storeFileContentString = isJson
32
29
  ? JSON.stringify(storeFileContentData)
33
30
  : storeFileContentData;
34
- // Before overwriting a file, check it has changed.
31
+ // Before overwriting a file, check if it has changed.
35
32
  let needsSave = true;
36
33
  let oldPublicUrl = null;
37
34
  const newPublicUrl = storeFileContentData.data?.content?.url?.path || null;
38
- if (fs_1.default.existsSync(absoluteFilepathInDumpDir)) {
39
- const dumpFileContentString = (0, fs_2.readFile)(absoluteFilepathInDumpDir);
40
- if (storeFileContentString === dumpFileContentString) {
35
+ const newHash = (0, string_1.createHash)(storeFileContentString);
36
+ const indexEntry = dumpIndexHelper_1.dumpIndexHelper.getEntry(relativeFilepath);
37
+ if (indexEntry) {
38
+ if (newHash === indexEntry.hash) {
41
39
  // No need to save it, file has not changed.
42
40
  needsSave = false;
43
41
  }
44
- else if (isJson && dumpFileContentString) {
45
- // Get old public URL before it changes on disk.
46
- oldPublicUrl =
47
- JSON.parse(dumpFileContentString)?.data?.content?.url?.path ||
48
- null;
49
- /*
50
- // Save a copy for later reference.
51
- const backupAbsoluteFilepathInDumpDir =
52
- absoluteFilepathInDumpDir.replace(
53
- '/dump/files/',
54
- '/dump/files.bak/',
55
- );
56
- const backupDir = path.dirname(backupAbsoluteFilepathInDumpDir);
57
- if (!fs.existsSync(backupDir)) {
58
- fs.mkdirSync(backupDir, { recursive: true });
59
- }
60
- fs.renameSync(
61
- absoluteFilepathInDumpDir,
62
- backupAbsoluteFilepathInDumpDir,
63
- );
64
- */
42
+ else if (isJson) {
43
+ // Get old public URL before it changes on disk and index.
44
+ oldPublicUrl = indexEntry.url;
65
45
  }
66
46
  }
67
47
  if (needsSave) {
68
- // Save it.
48
+ // Create parent directories if they are missing.
49
+ const dir = path_1.default.dirname(absoluteFilepathInDumpDir);
50
+ if (!fs_1.default.existsSync(dir)) {
51
+ fs_1.default.mkdirSync(dir, { recursive: true });
52
+ }
53
+ // Save it on disk.
69
54
  fs_1.default.writeFileSync(absoluteFilepathInDumpDir, storeFileContentString);
55
+ // Save it to index.
56
+ dumpIndexHelper_1.dumpIndexHelper.addEntry(relativeFilepath, {
57
+ hash: newHash,
58
+ url: newPublicUrl,
59
+ });
70
60
  // Mark it as updated.
71
61
  dump.updated.set(relativeFilepath, {
72
62
  oldPublicUrl,
@@ -83,18 +73,20 @@ const storeUpdatedFiles = (diff, dump, dumpDir) => {
83
73
  }
84
74
  });
85
75
  };
86
- const removeDeletedFiles = (diff, dump, dumpDir) => {
76
+ const removeDeletedFiles = (diff, dump, filesDumpDir) => {
87
77
  diff.deleted.forEach(relativeFilepath => {
88
- const absoluteFilepath = `${dumpDir}/${relativeFilepath}`;
78
+ const absoluteFilepath = `${filesDumpDir}/${relativeFilepath}`;
89
79
  try {
90
- if (fs_1.default.existsSync(absoluteFilepath)) {
91
- // Get old public URLs before they change on disk.
92
- const dumpContent = (0, fs_2.getFileContent)(absoluteFilepath);
93
- const oldPublicUrl = dumpContent.json?.data?.content?.url?.path;
80
+ const indexEntry = dumpIndexHelper_1.dumpIndexHelper.getEntry(relativeFilepath);
81
+ if (indexEntry) {
82
+ // Get old public URLs before they change on disk and index.
83
+ const oldPublicUrl = indexEntry.url;
94
84
  // Delete it.
95
85
  fs_1.default.unlinkSync(absoluteFilepath);
96
86
  // Delete any empty directory.
97
87
  (0, fs_2.removeEmptyDirsUpwards)(path_1.default.dirname(absoluteFilepath));
88
+ // Delete it from index.
89
+ dumpIndexHelper_1.dumpIndexHelper.removeEntry(relativeFilepath);
98
90
  // Mark it as deleted.
99
91
  dump.deleted.set(relativeFilepath, {
100
92
  oldPublicUrl,
@@ -107,6 +99,48 @@ const removeDeletedFiles = (diff, dump, dumpDir) => {
107
99
  }
108
100
  });
109
101
  };
102
+ const removeStaleFiles = (diff, dump, filesDumpDir) => {
103
+ const iterator = dumpIndexHelper_1.dumpIndexHelper.getKeys();
104
+ for (const relativeFilepath of iterator) {
105
+ const absoluteFilepath = `${filesDumpDir}/${relativeFilepath}`;
106
+ if (!store_1.store.data.has(relativeFilepath) && fs_1.default.existsSync(absoluteFilepath)) {
107
+ logger_1.logger.info(`Dump: removing stale file: ${relativeFilepath}`);
108
+ try {
109
+ // Get old public URLs before they change on disk.
110
+ const dumpStaleFileString = fs_1.default
111
+ .readFileSync(absoluteFilepath)
112
+ .toString();
113
+ const isJson = (0, fs_2.isJsonFile)(relativeFilepath);
114
+ const dumpStaleFileContentData = isJson
115
+ ? JSON.parse(dumpStaleFileString)
116
+ : dumpStaleFileString;
117
+ let oldPublicUrl = dumpStaleFileContentData.data?.content?.url?.path || null;
118
+ // An stale file can be a manually copied file.
119
+ // In that case, that stale file would have an oldPublicUrl from a valid file,
120
+ // which would lead to deleting a valid URL.
121
+ // To solve that case, check that oldPublicUrl is not a valid URL.
122
+ if (oldPublicUrl && store_1.store.index.url.has(oldPublicUrl)) {
123
+ logger_1.logger.info(`Dump: skipping valid url from stale file: ${oldPublicUrl}`);
124
+ oldPublicUrl = null;
125
+ }
126
+ // Delete it.
127
+ fs_1.default.unlinkSync(absoluteFilepath);
128
+ // Delete any empty directory.
129
+ (0, fs_2.removeEmptyDirsUpwards)(path_1.default.dirname(absoluteFilepath));
130
+ // Delete it from index.
131
+ dumpIndexHelper_1.dumpIndexHelper.removeEntry(relativeFilepath);
132
+ // Mark it as deleted.
133
+ dump.deleted.set(relativeFilepath, {
134
+ oldPublicUrl,
135
+ newPublicUrl: null,
136
+ });
137
+ }
138
+ catch (e) {
139
+ logger_1.logger.error(`Dump: error deleting stale file "${absoluteFilepath}": ${e}`);
140
+ }
141
+ }
142
+ }
143
+ };
110
144
  exports.dumpManager = {
111
145
  dump(options = { incremental: true }) {
112
146
  const startDate = microtime_1.default.now();
@@ -121,12 +155,17 @@ exports.dumpManager = {
121
155
  diff,
122
156
  };
123
157
  if (config_1.config.dumpDir) {
124
- const dumpDir = `${config_1.config.dumpDir}/files`;
158
+ const filesDumpDir = `${config_1.config.dumpDir}/files`;
125
159
  if (diff.updated.size || diff.deleted.size) {
160
+ dumpMetadataHelper_1.dumpMetadataHelper.storeDumpMetadata(dump);
126
161
  // Store updated files.
127
- storeUpdatedFiles(diff, dump, dumpDir);
162
+ storeUpdatedFiles(diff, dump, filesDumpDir);
128
163
  // Remove deleted files.
129
- removeDeletedFiles(diff, dump, dumpDir);
164
+ removeDeletedFiles(diff, dump, filesDumpDir);
165
+ // Remove stale files.
166
+ removeStaleFiles(diff, dump, filesDumpDir);
167
+ // Save dump index
168
+ dumpIndexHelper_1.dumpIndexHelper.saveDumpIndex();
130
169
  // Invoke "onDumpCreate" hook.
131
170
  dump = hook_1.hookManager.invokeOnDumpCreate(dump);
132
171
  const execTimeMs = (microtime_1.default.now() - startDate) / 1000;
@@ -140,12 +179,6 @@ exports.dumpManager = {
140
179
  diffManager_1.diffManager.reset(diff.toUniqueId);
141
180
  }
142
181
  logger_1.logger.info(`Dump created in ${execTimeMs} ms. Updated: ${dump.updated.size} / Deleted: ${dump.deleted.size}`);
143
- /*
144
- // Log dump if not empty
145
- if (dump.updated.size || dump.deleted.size) {
146
- logger.debug(`Dump: "${JSON.stringify(jsonify(dump))}"`);
147
- }
148
- */
149
182
  }
150
183
  else {
151
184
  // Resetting the diff must happen when no other operations are pending.
@@ -1 +1 @@
1
- {"version":3,"file":"dumpMetadataHelper.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/dump/dumpMetadataHelper.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAKlD,eAAO,MAAM,kBAAkB;8BACH,IAAI,KAAG,OAAO;sCA6BN,MAAM,GAAG,YAAY;8BAuB7B,MAAM;CAWjC,CAAC"}
1
+ {"version":3,"file":"dumpMetadataHelper.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/dump/dumpMetadataHelper.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAKlD,eAAO,MAAM,kBAAkB;8BACH,IAAI,KAAG,OAAO;sCA6BN,MAAM,GAAG,YAAY;8BAuB7B,MAAM;CAYjC,CAAC"}
@@ -54,6 +54,7 @@ exports.dumpMetadataHelper = {
54
54
  },
55
55
  getCurrentDumpUniqueId() {
56
56
  const metadataFilepath = getMetadataFilepath();
57
+ logger_1.logger.debug(`metadataFilepath: ${metadataFilepath}`);
57
58
  const metadata = JSON.parse(fs_1.default.existsSync(metadataFilepath)
58
59
  ? fs_1.default.readFileSync(metadataFilepath).toString()
59
60
  : '[]');
@@ -48,4 +48,10 @@ export declare const parseUniqueId: (uniqueId: string) => Date | null;
48
48
  * @param uniqueId - A unique id to be checked
49
49
  */
50
50
  export declare const isUniqueId: (uniqueId: string) => boolean;
51
+ /**
52
+ * Creates a hash from a string
53
+ *
54
+ * @param data - A string of data
55
+ */
56
+ export declare const createHash: (data: string) => string;
51
57
  //# sourceMappingURL=stringUtils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stringUtils.d.ts","sourceRoot":"","sources":["../../../../src/lib/utils/string/stringUtils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,YAAY,MAAM,KAAG,GAQpD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAC/B,aAAa,MAAM,KAClB,qBAWF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAI,UAAU,MAAM,KAAG,IAAI,GAAG,IAevD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,UAAU,MAAM,KAAG,OAC+B,CAAC"}
1
+ {"version":3,"file":"stringUtils.d.ts","sourceRoot":"","sources":["../../../../src/lib/utils/string/stringUtils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,YAAY,MAAM,KAAG,GAQpD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAC/B,aAAa,MAAM,KAClB,qBAWF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAI,UAAU,MAAM,KAAG,IAAI,GAAG,IAevD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,UAAU,MAAM,KAAG,OAC+B,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,MAC0B,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isUniqueId = exports.parseUniqueId = exports.parseURLSearchParams = exports.parseJsonString = exports.VARIANT_SEPARATOR = void 0;
3
+ exports.createHash = exports.isUniqueId = exports.parseUniqueId = exports.parseURLSearchParams = exports.parseJsonString = exports.VARIANT_SEPARATOR = void 0;
4
4
  const logger_1 = require("../logger");
5
5
  /**
6
6
  * Separator for variant data files saved to storage.
@@ -87,3 +87,10 @@ exports.parseUniqueId = parseUniqueId;
87
87
  */
88
88
  const isUniqueId = (uniqueId) => /(\d{4})-(\d{2})-(\d{2})_(\d{2})-(\d{2})-(\d{2})\.(\d{3}).*/.test(uniqueId);
89
89
  exports.isUniqueId = isUniqueId;
90
+ /**
91
+ * Creates a hash from a string
92
+ *
93
+ * @param data - A string of data
94
+ */
95
+ const createHash = (data) => require('crypto').createHash('sha1').update(data).digest('base64');
96
+ exports.createHash = createHash;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@static-suite/static-suite-data-server",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "A flexible, fast and developer friendly Data Server for Static Suite",
5
5
  "main": "./dist/lib/index.js",
6
6
  "typings": "./dist/lib/index.d.ts",
@@ -84,5 +84,6 @@
84
84
  "src/__tests__/fixtures/*",
85
85
  "src/__tests__/mocks/*"
86
86
  ]
87
- }
87
+ },
88
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
88
89
  }
@@ -0,0 +1,47 @@
1
+ import { Request, Response } from 'express';
2
+ import { diffManager } from '../../lib/store/diff';
3
+ import { jsonify } from '../../lib/utils/object';
4
+
5
+ const diffIndex = (req: Request, res: Response): void => {
6
+ res.render('diffIndex', {
7
+ links: {
8
+ '/diff/incremental': 'Execute an incremental dump',
9
+ '/diff/full': 'Execute a full dump',
10
+ '/diff/reset':
11
+ 'Clears intermediate changes tracked by dependency manager',
12
+ },
13
+ });
14
+ };
15
+
16
+ const diffAction = (
17
+ req: Request,
18
+ res: Response,
19
+ incremental: boolean,
20
+ ): void => {
21
+ const diff = diffManager.getDiff({ incremental });
22
+ const diffAsJson: any = jsonify(diff);
23
+
24
+ res.status(200);
25
+ res.set({ 'Content-Type': 'application/json' });
26
+ res.send(diffAsJson);
27
+ };
28
+
29
+ const diffIncremental = (req: Request, res: Response): void => {
30
+ diffAction(req, res, true);
31
+ };
32
+
33
+ const diffFull = (req: Request, res: Response): void => {
34
+ diffAction(req, res, false);
35
+ };
36
+
37
+ const diffReset = (req: Request, res: Response): void => {
38
+ const args: any = req.query;
39
+ if (args?.uniqueId) {
40
+ diffManager.reset(args.uniqueId);
41
+ }
42
+ res.status(200);
43
+ res.set({ 'Content-Type': 'application/json' });
44
+ res.send({ status: 'done' });
45
+ };
46
+
47
+ export { diffIndex, diffIncremental, diffFull, diffReset };
@@ -5,6 +5,7 @@ export const home = (req: Request, res: Response): void => {
5
5
  links: {
6
6
  '/data': 'Browse all data loaded into the server',
7
7
  '/dependency': 'Dependency data',
8
+ '/diff': 'Diff operations',
8
9
  '/dump': 'Dump operations',
9
10
  '/query': 'List of available queries',
10
11
  '/task': 'List of available tasks',
@@ -32,6 +32,7 @@ import {
32
32
  import { indexUrl } from './indexUrl';
33
33
  import { indexUUID } from './indexUUID';
34
34
  import { subset } from './subset';
35
+ import { diffFull, diffIncremental, diffIndex, diffReset } from './diff';
35
36
 
36
37
  const routes = Router();
37
38
 
@@ -49,6 +50,10 @@ routes.get('/task', taskIndex);
49
50
  routes.get('/task/:taskId', runTask);
50
51
  routes.get('/cache', cacheIndex);
51
52
  routes.get('/cache/:cacheBinId/clear', cacheClear);
53
+ routes.get('/diff', diffIndex);
54
+ routes.get('/diff/incremental', diffIncremental);
55
+ routes.get('/diff/full', diffFull);
56
+ routes.get('/diff/reset', diffReset);
52
57
  routes.get('/dump', dumpIndex);
53
58
  routes.get('/dump/incremental', dumpIncremental);
54
59
  routes.get('/dump/full', dumpFull);
@@ -1,5 +1,5 @@
1
1
  import { Request, Response } from 'express';
2
- import { StoreSubsetOptions } from 'lib/store/store.types';
2
+ import { StoreSubsetOptions } from '../../lib/store/store.types';
3
3
  import { store } from '../../lib/store';
4
4
  import { dataDirManager } from '../../lib/store/dataDir';
5
5
 
@@ -0,0 +1,14 @@
1
+ extends includes/layout.pug
2
+
3
+ block vars
4
+ - var title = "Diff | Static Suite Data Server"
5
+
6
+ block content
7
+ h1(class="title is-1")="Diff"
8
+ table(class="table is-fullwidth is-hoverable")
9
+ tbody
10
+ each description, link in links
11
+ tr
12
+ td
13
+ a(href=link)= link
14
+ td=description
@@ -1,4 +1,4 @@
1
- import { configureLogger } from './utils/logger';
1
+ import { configureLogger, logger } from './utils/logger';
2
2
  import { LogLevel, LogFile } from './utils/logger/logger.types';
3
3
  import { initWatcher } from './watcher';
4
4
  import { setConfig } from './config';
@@ -21,6 +21,7 @@ import {
21
21
  DataServerReturn,
22
22
  DataServerInitOptions,
23
23
  } from './dataServer.types';
24
+ import { dumpIndexHelper } from './store/dump/dumpIndexHelper';
24
25
 
25
26
  /**
26
27
  * The Data Server instance.
@@ -36,6 +37,8 @@ const dataServer = {
36
37
  * @returns An object with the data store and the queryRunner service.
37
38
  */
38
39
  init: (options: DataServerInitOptions): DataServerReturn => {
40
+ const startDate = Date.now();
41
+
39
42
  // Configure logger.
40
43
  configureLogger(options.logLevel, options.logFile);
41
44
 
@@ -58,6 +61,19 @@ const dataServer = {
58
61
  // Load data from dataDir.
59
62
  dataDirManager.load();
60
63
 
64
+ // Create and load dump index.
65
+ if (config.dumpDir) {
66
+ if (
67
+ dumpIndexHelper.isDumpIndexStale() ||
68
+ !dumpIndexHelper.isDumpIndexPresent()
69
+ ) {
70
+ dumpIndexHelper.createDumpIndex();
71
+ }
72
+ dumpIndexHelper.loadDumpIndex();
73
+ }
74
+
75
+ logger.info(`Data Server loaded in ${Date.now() - startDate}ms.`);
76
+
61
77
  return {
62
78
  store,
63
79
  queryRunner,
@@ -38,6 +38,7 @@ export type Diff = {
38
38
  * Execution time taken by the diff, in milliseconds.
39
39
  */
40
40
  execTimeMs: number;
41
+
41
42
  /**
42
43
  * A unique id representing the date from which this diff is obtained.
43
44
  */
@@ -27,6 +27,7 @@ export const diffManager: DiffManager = {
27
27
  const currentDumpUniqueId = dumpMetadataHelper.getCurrentDumpUniqueId();
28
28
  const isFirstDiffAfterReboot = lastDiffUniqueId === unixEpochUniqueId;
29
29
  const isADumpAvailable = currentDumpUniqueId !== unixEpochUniqueId;
30
+
30
31
  // If both checks passes, check if something has changed after last dump to avoid a full diff:
31
32
  if (isFirstDiffAfterReboot && isADumpAvailable) {
32
33
  const dataDirModificationUniqueId = changedFiles.toUniqueId;
@@ -76,3 +76,8 @@ export type DumpMetadata = {
76
76
  current: string;
77
77
  dumps: Dump[];
78
78
  };
79
+
80
+ export type IndexEntry = {
81
+ hash: string;
82
+ url: string | null;
83
+ };
@@ -0,0 +1,134 @@
1
+ import fs from 'fs';
2
+ import { logger } from '../../utils/logger';
3
+ import { createHash } from '../../utils/string';
4
+ import { config } from '../../config';
5
+ // import { Dump, DumpMetadata } from './dump.types';
6
+ // import { unixEpochUniqueId } from '../workDir';
7
+ import { findFilesInDir, getFileContent } from '../../utils/fs';
8
+ import { IndexEntry } from './dump.types';
9
+
10
+ const index = new Map<string, IndexEntry>();
11
+
12
+ let isStale: boolean | undefined = undefined;
13
+
14
+ const getIndexFilepath = () => `${config.dumpDir}/files.idx`;
15
+ const getIndexIsStaleFlagFilepath = () =>
16
+ `${config.dumpDir}/index-is-stale.flag`;
17
+
18
+ const indexToString = (): string => {
19
+ const indexLines: string[] = [];
20
+ index.forEach((value, filePath) => {
21
+ const line = [filePath, value.hash, value.url];
22
+ indexLines.push(line.join('\t'));
23
+ });
24
+ return indexLines.join('\n');
25
+ };
26
+
27
+ const setIsStale = (value: boolean): void => {
28
+ if (isStale !== value) {
29
+ isStale = value;
30
+ const indexIsStaleFlagFilepath = getIndexIsStaleFlagFilepath();
31
+ if (isStale === true) {
32
+ fs.writeFileSync(indexIsStaleFlagFilepath, '');
33
+ } else {
34
+ if (fs.existsSync(indexIsStaleFlagFilepath)) {
35
+ fs.unlinkSync(indexIsStaleFlagFilepath);
36
+ }
37
+ }
38
+ }
39
+ };
40
+
41
+ export const dumpIndexHelper = {
42
+ isDumpIndexStale: (): boolean => {
43
+ if (isStale === undefined) {
44
+ const indexIsStaleFlagFilepath = getIndexIsStaleFlagFilepath();
45
+ if (fs.existsSync(indexIsStaleFlagFilepath)) {
46
+ setIsStale(true);
47
+ } else {
48
+ setIsStale(false);
49
+ }
50
+ }
51
+ return !!isStale;
52
+ },
53
+
54
+ isDumpIndexPresent: (): boolean => fs.existsSync(getIndexFilepath()),
55
+
56
+ saveDumpIndex: (): void => {
57
+ if (isStale) {
58
+ const indexFilepath = getIndexFilepath();
59
+ const indexAsString = indexToString().trim();
60
+ fs.writeFileSync(indexFilepath, indexAsString);
61
+ setIsStale(false);
62
+ }
63
+ },
64
+
65
+ createDumpIndex: (): void => {
66
+ if (config.dumpDir) {
67
+ setIsStale(true);
68
+ logger.info('Creating dump index...');
69
+ const startDate = Date.now();
70
+ index.clear();
71
+ const filesDumpDir = `${config.dumpDir}/files`;
72
+ const relativeFilePaths = findFilesInDir(filesDumpDir);
73
+ relativeFilePaths.forEach(relativeFilePath => {
74
+ const fileContent = getFileContent(
75
+ `${filesDumpDir}/${relativeFilePath}`,
76
+ );
77
+ if (fileContent.raw) {
78
+ const url = fileContent.json
79
+ ? fileContent.json.data?.content?.url?.path
80
+ : null;
81
+ const hash = createHash(fileContent.raw);
82
+ index.set(relativeFilePath, { hash, url });
83
+ }
84
+ });
85
+
86
+ // Save index into disk
87
+ dumpIndexHelper.saveDumpIndex();
88
+
89
+ logger.info(
90
+ `${relativeFilePaths.length} files in dump indexed in ${
91
+ Date.now() - startDate
92
+ }ms.`,
93
+ );
94
+ }
95
+ },
96
+
97
+ loadDumpIndex: (): void => {
98
+ if (config.dumpDir) {
99
+ logger.info('Loading dump index...');
100
+ const startDate = Date.now();
101
+ const indexFilepath = getIndexFilepath();
102
+ const data = fs.readFileSync(indexFilepath).toString().trim();
103
+ index.clear();
104
+ // Split lines and remove empty ones.
105
+ const dataLines: string[] = data.split('\n').filter(line => !!line);
106
+ dataLines.forEach(line => {
107
+ const [relativeFilePath, hash, url] = line.split('\t');
108
+ index.set(relativeFilePath, { hash, url });
109
+ });
110
+ logger.info(
111
+ `${dataLines.length} entries loaded from dump index in ${
112
+ Date.now() - startDate
113
+ }ms.`,
114
+ );
115
+ }
116
+ },
117
+
118
+ hasEntry: (relativeFilePath: string): boolean => index.has(relativeFilePath),
119
+
120
+ getEntry: (relativeFilePath: string): IndexEntry | undefined =>
121
+ index.get(relativeFilePath),
122
+
123
+ getKeys: () => index.keys(),
124
+
125
+ addEntry: (relativeFilePath: string, entry: IndexEntry): void => {
126
+ setIsStale(true);
127
+ index.set(relativeFilePath, entry);
128
+ },
129
+
130
+ removeEntry: (relativeFilePath: string): void => {
131
+ setIsStale(true);
132
+ index.delete(relativeFilePath);
133
+ },
134
+ };
@@ -2,12 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import microtime from 'microtime';
4
4
  import { logger } from '../../utils/logger';
5
- import {
6
- getFileContent,
7
- isJsonFile,
8
- readFile,
9
- removeEmptyDirsUpwards,
10
- } from '../../utils/fs';
5
+ import { isJsonFile, removeEmptyDirsUpwards } from '../../utils/fs';
11
6
  import { config } from '../../config';
12
7
  import { store } from '../store';
13
8
  import { diffManager } from '../diff/diffManager';
@@ -15,19 +10,19 @@ import { Diff } from '../diff/diff.types';
15
10
  import { Dump, DumpManager } from './dump.types';
16
11
  import { hookManager } from '../hook';
17
12
  import { dumpMetadataHelper } from './dumpMetadataHelper';
18
-
19
- const storeUpdatedFiles = (diff: Diff, dump: Dump, dumpDir: string): void => {
13
+ import { dumpIndexHelper } from './dumpIndexHelper';
14
+ import { createHash } from '../../utils/string';
15
+
16
+ const storeUpdatedFiles = (
17
+ diff: Diff,
18
+ dump: Dump,
19
+ filesDumpDir: string,
20
+ ): void => {
20
21
  diff.updated.forEach(relativeFilepath => {
21
22
  const storeFileContentData = store.data.get(relativeFilepath);
22
23
  if (storeFileContentData) {
23
- const absoluteFilepathInDumpDir = `${dumpDir}/${relativeFilepath}`;
24
+ const absoluteFilepathInDumpDir = `${filesDumpDir}/${relativeFilepath}`;
24
25
  try {
25
- // Create parent directories if they are missing.
26
- const dir = path.dirname(absoluteFilepathInDumpDir);
27
- if (!fs.existsSync(dir)) {
28
- fs.mkdirSync(dir, { recursive: true });
29
- }
30
-
31
26
  // If store data is an object, stringify it to execute its
32
27
  // queries and resolve its includes.
33
28
  const isJson = isJsonFile(relativeFilepath);
@@ -35,43 +30,39 @@ const storeUpdatedFiles = (diff: Diff, dump: Dump, dumpDir: string): void => {
35
30
  ? JSON.stringify(storeFileContentData)
36
31
  : storeFileContentData;
37
32
 
38
- // Before overwriting a file, check it has changed.
33
+ // Before overwriting a file, check if it has changed.
39
34
  let needsSave = true;
40
35
  let oldPublicUrl: string | null = null;
41
36
  const newPublicUrl: string | null =
42
37
  storeFileContentData.data?.content?.url?.path || null;
43
- if (fs.existsSync(absoluteFilepathInDumpDir)) {
44
- const dumpFileContentString = readFile(absoluteFilepathInDumpDir);
45
- if (storeFileContentString === dumpFileContentString) {
38
+ const newHash = createHash(storeFileContentString);
39
+ const indexEntry = dumpIndexHelper.getEntry(relativeFilepath);
40
+ if (indexEntry) {
41
+ if (newHash === indexEntry.hash) {
46
42
  // No need to save it, file has not changed.
47
43
  needsSave = false;
48
- } else if (isJson && dumpFileContentString) {
49
- // Get old public URL before it changes on disk.
50
- oldPublicUrl =
51
- JSON.parse(dumpFileContentString)?.data?.content?.url?.path ||
52
- null;
53
- /*
54
- // Save a copy for later reference.
55
- const backupAbsoluteFilepathInDumpDir =
56
- absoluteFilepathInDumpDir.replace(
57
- '/dump/files/',
58
- '/dump/files.bak/',
59
- );
60
- const backupDir = path.dirname(backupAbsoluteFilepathInDumpDir);
61
- if (!fs.existsSync(backupDir)) {
62
- fs.mkdirSync(backupDir, { recursive: true });
63
- }
64
- fs.renameSync(
65
- absoluteFilepathInDumpDir,
66
- backupAbsoluteFilepathInDumpDir,
67
- );
68
- */
44
+ } else if (isJson) {
45
+ // Get old public URL before it changes on disk and index.
46
+ oldPublicUrl = indexEntry.url;
69
47
  }
70
48
  }
49
+
71
50
  if (needsSave) {
72
- // Save it.
51
+ // Create parent directories if they are missing.
52
+ const dir = path.dirname(absoluteFilepathInDumpDir);
53
+ if (!fs.existsSync(dir)) {
54
+ fs.mkdirSync(dir, { recursive: true });
55
+ }
56
+
57
+ // Save it on disk.
73
58
  fs.writeFileSync(absoluteFilepathInDumpDir, storeFileContentString);
74
59
 
60
+ // Save it to index.
61
+ dumpIndexHelper.addEntry(relativeFilepath, {
62
+ hash: newHash,
63
+ url: newPublicUrl,
64
+ });
65
+
75
66
  // Mark it as updated.
76
67
  dump.updated.set(relativeFilepath, {
77
68
  oldPublicUrl,
@@ -91,15 +82,18 @@ const storeUpdatedFiles = (diff: Diff, dump: Dump, dumpDir: string): void => {
91
82
  });
92
83
  };
93
84
 
94
- const removeDeletedFiles = (diff: Diff, dump: Dump, dumpDir: string): void => {
85
+ const removeDeletedFiles = (
86
+ diff: Diff,
87
+ dump: Dump,
88
+ filesDumpDir: string,
89
+ ): void => {
95
90
  diff.deleted.forEach(relativeFilepath => {
96
- const absoluteFilepath = `${dumpDir}/${relativeFilepath}`;
91
+ const absoluteFilepath = `${filesDumpDir}/${relativeFilepath}`;
97
92
  try {
98
- if (fs.existsSync(absoluteFilepath)) {
99
- // Get old public URLs before they change on disk.
100
- const dumpContent = getFileContent(absoluteFilepath);
101
- const oldPublicUrl: string | null =
102
- dumpContent.json?.data?.content?.url?.path;
93
+ const indexEntry = dumpIndexHelper.getEntry(relativeFilepath);
94
+ if (indexEntry) {
95
+ // Get old public URLs before they change on disk and index.
96
+ const oldPublicUrl = indexEntry.url;
103
97
 
104
98
  // Delete it.
105
99
  fs.unlinkSync(absoluteFilepath);
@@ -107,6 +101,9 @@ const removeDeletedFiles = (diff: Diff, dump: Dump, dumpDir: string): void => {
107
101
  // Delete any empty directory.
108
102
  removeEmptyDirsUpwards(path.dirname(absoluteFilepath));
109
103
 
104
+ // Delete it from index.
105
+ dumpIndexHelper.removeEntry(relativeFilepath);
106
+
110
107
  // Mark it as deleted.
111
108
  dump.deleted.set(relativeFilepath, {
112
109
  oldPublicUrl,
@@ -119,6 +116,62 @@ const removeDeletedFiles = (diff: Diff, dump: Dump, dumpDir: string): void => {
119
116
  });
120
117
  };
121
118
 
119
+ const removeStaleFiles = (
120
+ diff: Diff,
121
+ dump: Dump,
122
+ filesDumpDir: string,
123
+ ): void => {
124
+ const iterator = dumpIndexHelper.getKeys();
125
+ for (const relativeFilepath of iterator) {
126
+ const absoluteFilepath = `${filesDumpDir}/${relativeFilepath}`;
127
+ if (!store.data.has(relativeFilepath) && fs.existsSync(absoluteFilepath)) {
128
+ logger.info(`Dump: removing stale file: ${relativeFilepath}`);
129
+ try {
130
+ // Get old public URLs before they change on disk.
131
+ const dumpStaleFileString = fs
132
+ .readFileSync(absoluteFilepath)
133
+ .toString();
134
+ const isJson = isJsonFile(relativeFilepath);
135
+ const dumpStaleFileContentData = isJson
136
+ ? JSON.parse(dumpStaleFileString)
137
+ : dumpStaleFileString;
138
+ let oldPublicUrl: string | null =
139
+ dumpStaleFileContentData.data?.content?.url?.path || null;
140
+
141
+ // An stale file can be a manually copied file.
142
+ // In that case, that stale file would have an oldPublicUrl from a valid file,
143
+ // which would lead to deleting a valid URL.
144
+ // To solve that case, check that oldPublicUrl is not a valid URL.
145
+ if (oldPublicUrl && store.index.url.has(oldPublicUrl)) {
146
+ logger.info(
147
+ `Dump: skipping valid url from stale file: ${oldPublicUrl}`,
148
+ );
149
+ oldPublicUrl = null;
150
+ }
151
+
152
+ // Delete it.
153
+ fs.unlinkSync(absoluteFilepath);
154
+
155
+ // Delete any empty directory.
156
+ removeEmptyDirsUpwards(path.dirname(absoluteFilepath));
157
+
158
+ // Delete it from index.
159
+ dumpIndexHelper.removeEntry(relativeFilepath);
160
+
161
+ // Mark it as deleted.
162
+ dump.deleted.set(relativeFilepath, {
163
+ oldPublicUrl,
164
+ newPublicUrl: null,
165
+ });
166
+ } catch (e) {
167
+ logger.error(
168
+ `Dump: error deleting stale file "${absoluteFilepath}": ${e}`,
169
+ );
170
+ }
171
+ }
172
+ }
173
+ };
174
+
122
175
  export const dumpManager: DumpManager = {
123
176
  dump(options = { incremental: true }): Dump {
124
177
  const startDate = microtime.now();
@@ -135,14 +188,22 @@ export const dumpManager: DumpManager = {
135
188
  };
136
189
 
137
190
  if (config.dumpDir) {
138
- const dumpDir = `${config.dumpDir}/files`;
191
+ const filesDumpDir = `${config.dumpDir}/files`;
139
192
 
140
193
  if (diff.updated.size || diff.deleted.size) {
194
+ dumpMetadataHelper.storeDumpMetadata(dump);
195
+
141
196
  // Store updated files.
142
- storeUpdatedFiles(diff, dump, dumpDir);
197
+ storeUpdatedFiles(diff, dump, filesDumpDir);
143
198
 
144
199
  // Remove deleted files.
145
- removeDeletedFiles(diff, dump, dumpDir);
200
+ removeDeletedFiles(diff, dump, filesDumpDir);
201
+
202
+ // Remove stale files.
203
+ removeStaleFiles(diff, dump, filesDumpDir);
204
+
205
+ // Save dump index
206
+ dumpIndexHelper.saveDumpIndex();
146
207
 
147
208
  // Invoke "onDumpCreate" hook.
148
209
  dump = hookManager.invokeOnDumpCreate(dump);
@@ -162,13 +223,6 @@ export const dumpManager: DumpManager = {
162
223
  logger.info(
163
224
  `Dump created in ${execTimeMs} ms. Updated: ${dump.updated.size} / Deleted: ${dump.deleted.size}`,
164
225
  );
165
-
166
- /*
167
- // Log dump if not empty
168
- if (dump.updated.size || dump.deleted.size) {
169
- logger.debug(`Dump: "${JSON.stringify(jsonify(dump))}"`);
170
- }
171
- */
172
226
  } else {
173
227
  // Resetting the diff must happen when no other operations are pending.
174
228
  diffManager.reset(diff.toUniqueId);
@@ -63,6 +63,7 @@ export const dumpMetadataHelper = {
63
63
  getCurrentDumpUniqueId(): string {
64
64
  const metadataFilepath = getMetadataFilepath();
65
65
 
66
+ logger.debug(`metadataFilepath: ${metadataFilepath}`);
66
67
  const metadata = JSON.parse(
67
68
  fs.existsSync(metadataFilepath)
68
69
  ? fs.readFileSync(metadataFilepath).toString()
@@ -91,3 +91,11 @@ export const parseUniqueId = (uniqueId: string): Date | null => {
91
91
  */
92
92
  export const isUniqueId = (uniqueId: string): boolean =>
93
93
  /(\d{4})-(\d{2})-(\d{2})_(\d{2})-(\d{2})-(\d{2})\.(\d{3}).*/.test(uniqueId);
94
+
95
+ /**
96
+ * Creates a hash from a string
97
+ *
98
+ * @param data - A string of data
99
+ */
100
+ export const createHash = (data: string): string =>
101
+ require('crypto').createHash('sha1').update(data).digest('base64');
package/tsconfig.json CHANGED
@@ -30,9 +30,9 @@
30
30
 
31
31
  /* Strict Type-Checking Options */
32
32
  "strict": true /* Enable all strict type-checking options. */,
33
- "paths": {
33
+ /* "paths": {
34
34
  "*": ["./src/*"]
35
- } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */,
35
+ } */ /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
36
36
  // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
37
37
  // "typeRoots": [], /* List of folders to include type definitions from. */
38
38
  "types": [