@static-suite/static-suite-data-server 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +564 -104
  2. package/dist/http/README.md +564 -104
  3. package/dist/http/routes/dependency.d.ts +10 -0
  4. package/dist/http/routes/dependency.d.ts.map +1 -0
  5. package/dist/http/routes/dependency.js +71 -0
  6. package/dist/http/routes/dump.d.ts.map +1 -1
  7. package/dist/http/routes/dump.js +15 -10
  8. package/dist/http/routes/home.js +2 -1
  9. package/dist/http/routes/index.d.ts.map +1 -1
  10. package/dist/http/routes/index.js +8 -0
  11. package/dist/http/routes/status.d.ts.map +1 -1
  12. package/dist/http/routes/status.js +1 -6
  13. package/dist/http/views/dependencyIndex.pug +14 -0
  14. package/dist/lib/dataServer.d.ts +2 -1
  15. package/dist/lib/dataServer.d.ts.map +1 -1
  16. package/dist/lib/query/query.types.d.ts +2 -2
  17. package/dist/lib/query/query.types.d.ts.map +1 -1
  18. package/dist/lib/query/query.types.js +2 -2
  19. package/dist/lib/query/queryRunner.d.ts.map +1 -1
  20. package/dist/lib/query/queryRunner.js +3 -1
  21. package/dist/lib/store/dataDir/dataDir.types.d.ts +5 -5
  22. package/dist/lib/store/dataDir/dataDir.types.d.ts.map +1 -1
  23. package/dist/lib/store/dataDir/dataDirManager.d.ts.map +1 -1
  24. package/dist/lib/store/dataDir/dataDirManager.js +23 -12
  25. package/dist/lib/store/dependency/dependency.types.d.ts.map +1 -1
  26. package/dist/lib/store/dependency/dependencyFileHelper.d.ts +1 -1
  27. package/dist/lib/store/dependency/dependencyFileHelper.d.ts.map +1 -1
  28. package/dist/lib/store/dependency/dependencyFileHelper.js +5 -3
  29. package/dist/lib/store/dependency/dependencyManager.d.ts +12 -0
  30. package/dist/lib/store/dependency/dependencyManager.d.ts.map +1 -1
  31. package/dist/lib/store/dependency/dependencyManager.js +7 -5
  32. package/dist/lib/store/dependency/dependencyTagger.d.ts.map +1 -1
  33. package/dist/lib/store/dependency/dependencyTagger.js +3 -38
  34. package/dist/lib/store/diff/{diffManager.types.d.ts → diff.types.d.ts} +14 -6
  35. package/dist/lib/store/diff/diff.types.d.ts.map +1 -0
  36. package/dist/lib/store/{dependency/dependencyManager.types.js → diff/diff.types.js} +0 -0
  37. package/dist/lib/store/diff/diffManager.d.ts +1 -1
  38. package/dist/lib/store/diff/diffManager.d.ts.map +1 -1
  39. package/dist/lib/store/diff/diffManager.js +38 -81
  40. package/dist/lib/store/dump/{dumpManager.types.d.ts → dump.types.d.ts} +21 -8
  41. package/dist/lib/store/dump/dump.types.d.ts.map +1 -0
  42. package/dist/lib/store/{diff/diffManager.types.js → dump/dump.types.js} +0 -0
  43. package/dist/lib/store/dump/dumpManager.d.ts +1 -1
  44. package/dist/lib/store/dump/dumpManager.d.ts.map +1 -1
  45. package/dist/lib/store/dump/dumpManager.js +31 -56
  46. package/dist/lib/store/dump/dumpMetadataHelper.d.ts +7 -0
  47. package/dist/lib/store/dump/dumpMetadataHelper.d.ts.map +1 -0
  48. package/dist/lib/store/dump/dumpMetadataHelper.js +61 -0
  49. package/dist/lib/store/hook/hook.types.d.ts +49 -23
  50. package/dist/lib/store/hook/hook.types.d.ts.map +1 -1
  51. package/dist/lib/store/hook/hookManager.d.ts.map +1 -1
  52. package/dist/lib/store/hook/hookManager.js +19 -8
  53. package/dist/lib/store/store.d.ts.map +1 -1
  54. package/dist/lib/store/store.js +9 -4
  55. package/dist/lib/store/store.types.d.ts +14 -37
  56. package/dist/lib/store/store.types.d.ts.map +1 -1
  57. package/dist/lib/store/storeManager.d.ts.map +1 -1
  58. package/dist/lib/store/storeManager.js +20 -10
  59. package/dist/lib/store/workDir/storage/getChangedLinesBetween.d.ts +16 -0
  60. package/dist/lib/store/workDir/storage/getChangedLinesBetween.d.ts.map +1 -0
  61. package/dist/lib/store/workDir/storage/{getChangedLinesSince.js → getChangedLinesBetween.js} +22 -20
  62. package/dist/lib/store/workDir/storage/getDataFromLogLine.d.ts.map +1 -1
  63. package/dist/lib/store/workDir/storage/getDataFromLogLine.js +16 -3
  64. package/dist/lib/store/workDir/storage/index.d.ts +1 -1
  65. package/dist/lib/store/workDir/storage/index.d.ts.map +1 -1
  66. package/dist/lib/store/workDir/storage/index.js +1 -1
  67. package/dist/lib/store/workDir/workDir.types.d.ts +9 -1
  68. package/dist/lib/store/workDir/workDir.types.d.ts.map +1 -1
  69. package/dist/lib/store/workDir/workDirHelper.d.ts +13 -6
  70. package/dist/lib/store/workDir/workDirHelper.d.ts.map +1 -1
  71. package/dist/lib/store/workDir/workDirHelper.js +42 -11
  72. package/dist/lib/utils/fs/fsUtils.d.ts +6 -0
  73. package/dist/lib/utils/fs/fsUtils.d.ts.map +1 -1
  74. package/dist/lib/utils/fs/fsUtils.js +20 -1
  75. package/dist/lib/utils/module/moduleManager.d.ts.map +1 -1
  76. package/dist/lib/utils/module/moduleManager.js +5 -1
  77. package/dist/lib/utils/string/stringUtils.d.ts +14 -0
  78. package/dist/lib/utils/string/stringUtils.d.ts.map +1 -1
  79. package/dist/lib/utils/string/stringUtils.js +28 -1
  80. package/dist/static-suite-data-server.d.ts +19 -39
  81. package/package.json +3 -3
  82. package/src/http/routes/dependency.ts +91 -0
  83. package/src/http/routes/dump.ts +21 -10
  84. package/src/http/routes/home.ts +2 -2
  85. package/src/http/routes/index.ts +16 -0
  86. package/src/http/routes/status.ts +1 -9
  87. package/src/http/views/dependencyIndex.pug +14 -0
  88. package/src/lib/dataServer.ts +2 -0
  89. package/src/lib/query/query.types.ts +2 -2
  90. package/src/lib/query/queryRunner.ts +3 -1
  91. package/src/lib/store/dataDir/dataDir.types.ts +5 -5
  92. package/src/lib/store/dataDir/dataDirManager.ts +30 -14
  93. package/src/lib/store/dependency/dependency.types.ts +0 -5
  94. package/src/lib/store/dependency/dependencyFileHelper.ts +5 -3
  95. package/src/lib/store/dependency/dependencyManager.ts +6 -3
  96. package/src/lib/store/dependency/dependencyTagger.ts +3 -39
  97. package/src/lib/store/diff/{diffManager.types.ts → diff.types.ts} +14 -5
  98. package/src/lib/store/diff/diffManager.ts +45 -84
  99. package/src/lib/store/dump/{dumpManager.types.ts → dump.types.ts} +24 -7
  100. package/src/lib/store/dump/dumpManager.ts +40 -75
  101. package/src/lib/store/dump/dumpMetadataHelper.ts +73 -0
  102. package/src/lib/store/hook/hook.types.ts +58 -23
  103. package/src/lib/store/hook/hookManager.ts +24 -9
  104. package/src/lib/store/store.ts +9 -4
  105. package/src/lib/store/store.types.ts +16 -37
  106. package/src/lib/store/storeManager.ts +28 -12
  107. package/src/lib/store/workDir/__tests__/workDirHelper.test.ts +6 -1
  108. package/src/lib/store/workDir/storage/__tests__/{getChangedLinesSince.test.ts → getChangedLinesBetween.test.ts} +9 -4
  109. package/src/lib/store/workDir/storage/getChangedLinesBetween.ts +52 -0
  110. package/src/lib/store/workDir/storage/getDataFromLogLine.ts +22 -3
  111. package/src/lib/store/workDir/storage/index.ts +1 -1
  112. package/src/lib/store/workDir/workDir.types.ts +11 -1
  113. package/src/lib/store/workDir/workDirHelper.ts +45 -11
  114. package/src/lib/utils/fs/fsUtils.ts +18 -0
  115. package/src/lib/utils/module/moduleManager.ts +5 -1
  116. package/src/lib/utils/string/__tests__/stringUtils.test.ts +30 -1
  117. package/src/lib/utils/string/stringUtils.ts +32 -0
  118. package/dist/lib/query/queryRunner copy.d.ts +0 -3
  119. package/dist/lib/query/queryRunner copy.d.ts.map +0 -1
  120. package/dist/lib/query/queryRunner copy.js +0 -87
  121. package/dist/lib/store/dependency/dependencyManager copy.d.ts +0 -6
  122. package/dist/lib/store/dependency/dependencyManager copy.d.ts.map +0 -1
  123. package/dist/lib/store/dependency/dependencyManager copy.js +0 -91
  124. package/dist/lib/store/dependency/dependencyManager.types.d.ts +0 -48
  125. package/dist/lib/store/dependency/dependencyManager.types.d.ts.map +0 -1
  126. package/dist/lib/store/dependency/dependencyTagManager.d.ts +0 -6
  127. package/dist/lib/store/dependency/dependencyTagManager.d.ts.map +0 -1
  128. package/dist/lib/store/dependency/dependencyTagManager.js +0 -33
  129. package/dist/lib/store/diff/diffManager.types.d.ts.map +0 -1
  130. package/dist/lib/store/diff/tracker.d.ts +0 -3
  131. package/dist/lib/store/diff/tracker.d.ts.map +0 -1
  132. package/dist/lib/store/diff/tracker.js +0 -82
  133. package/dist/lib/store/diff/tracker.types.d.ts +0 -31
  134. package/dist/lib/store/diff/tracker.types.d.ts.map +0 -1
  135. package/dist/lib/store/diff/tracker.types.js +0 -2
  136. package/dist/lib/store/dump/dumpManager.types.d.ts.map +0 -1
  137. package/dist/lib/store/dump/dumpManager.types.js +0 -2
  138. package/dist/lib/store/include/includeIndex.d.ts +0 -6
  139. package/dist/lib/store/include/includeIndex.d.ts.map +0 -1
  140. package/dist/lib/store/include/includeIndex.js +0 -65
  141. package/dist/lib/store/include/includeIndex.types.d.ts +0 -29
  142. package/dist/lib/store/include/includeIndex.types.d.ts.map +0 -1
  143. package/dist/lib/store/include/includeIndex.types.js +0 -2
  144. package/dist/lib/store/workDir/storage/getChangedLinesSince.d.ts +0 -9
  145. package/dist/lib/store/workDir/storage/getChangedLinesSince.d.ts.map +0 -1
  146. package/src/lib/store/workDir/storage/getChangedLinesSince.ts +0 -47
@@ -2,16 +2,17 @@
2
2
 
3
3
  A flexible, fast and developer friendly Data Server for [Static Suite](https://www.drupal.org/project/static_suite).
4
4
 
5
- - flexible: it honors the structure of Static Suite's data directory, non imposing any opinionated structure. Every directory and its contents can be retrieved as simple arrays, so they can be queried using common [array functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) like `filter()`, `find()`, `forEach()`, etc.
6
- - fast: on a cold start, ~20k json files are usually loaded in ~2 seconds. Once the server is running, reloading those ~20k files usually takes ~100ms. Queries take ~1 ms, depending on their complexity. Once queries are cached, it executes ~150k queries per second.
7
- - developer friendly: when run on `dev` mode, any change made to data files, queries or hooks (more on this later) is automatically reloaded in milliseconds. Queries are easy to create (they are just a function) and debug using a HTTP endpoint.
5
+ - flexible: it honors the structure of Static Suite's data directory, non imposing any opinionated structure. Every directory and its contents can be retrieved as simple arrays, and therefore queried using common [array functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) like `filter()`, `find()`, `forEach()`, etc.
6
+ - fast: on a cold start, ~20k json files are usually loaded in ~2 seconds. Queries take ~1 ms, depending on their complexity. Once queries are cached, it executes ~150k queries per second.
7
+ - developer friendly: when run in `dev` mode, changes made to queries or hooks (more on this later) are automatically reloaded. Queries are easy to create (they are just a function) and debug using a HTTP endpoint.
8
8
 
9
- This Data Server supports static JSON includes (`entityInclude`, `configInclude`, `localeInclude` and `customInclude`) and the dynamic `queryInclude`.
9
+ Data Server supports static JSON includes (`entityInclude`, `configInclude`, `localeInclude` and `customInclude`) and the dynamic `queryInclude`, keeping track of the relationships between includes and data: when a file changes, it knows which other files should also change, thus enabling _incremental builds_ for any kind of SSG (Gatsby, Next.js, etc).
10
10
 
11
11
  ## Options
12
12
 
13
13
  - `dataDir` / `--data-dir`: path to the directory where data from Static Suite is stored. Required.
14
14
  - `workDir` / `--work-dir`: path to the directory where work data from Static Suite is stored. Optional.
15
+ - `dumpDir` / `--dump-dir`: path to the directory where data from Static Suite is dumped after being processed by Data Server. Optional.
15
16
  - `queryDir` / `--query-dir`: path to the directory where queries are stored. Optional.
16
17
  - `hookDir` / `--hook-dir`: path to the directory where hooks are stored. Optional.
17
18
  - `logLevel` / `--log-level`: log level verbosity: `error`, `warn`, `info` or `debug`. Optional. Defaults to `info`.
@@ -35,46 +36,52 @@ Require `dist/lib/dataServer.js` inside the SSG, and call its `init()` function:
35
36
  ```javascript
36
37
  const { dataServer } = require('dist/lib/dataServer');
37
38
 
38
- // Init the Data Server.
39
- const { store, queryRunner } = dataServer.init({
40
- dataDir: '../data/prod',
41
- workDir: '../data/prod/.work',
42
- queryDir: './src/data-server/query',
43
- hookDir: './src/data-server/hook',
44
- logFile: '/var/log/static-suite-data-server/error.log',
45
- logLevel: 'debug',
46
- runMode: 'prod',
47
- });
39
+ // Init Data Server.
40
+ const { store } = dataServer.init({ dataDir: '../data/prod' });
48
41
 
49
- // Loop over all articles of a given language
50
- store.data.subset({ dir: 'en/entity/node/article/' }).forEach(article => {
42
+ // Loop over all articles in english.
43
+ store.data.subset({ dir: 'en/entity/node/article/' }).items.forEach(article => {
51
44
  // do something
52
45
  });
53
46
  ```
54
47
 
55
48
  ### Previewing a content from Drupal
56
49
 
57
- Static Suite detects any use of the `queryInclude` tag and asks the Data Server to execute that query with its parameters. That communication is done using HTTP, sending an internal request from Drupal to an HTTP endpoint where the Data Server is listening. That HTTP endpoint is provided by Static Suite Data Server and must be started before any `queryInclude` can be resolved.
50
+ Static Suite detects any use of the `queryInclude` tag and asks Data Server to execute that query with its parameters. That communication is done using HTTP, sending an internal request from Drupal to an HTTP endpoint where Data Server is listening. That HTTP endpoint is provided by Static Suite Data Server and must be started before any `queryInclude` can be resolved.
58
51
 
59
52
  ```shell
60
- yarn serve http --port 57471 --data-dir=../data/prod --work-dir=../data/prod/.work/ --query-dir=./src/data-server/query --hook-dir=./src/data-server/hook --log-file=/var/log/static-suite-data-server/error.log --run-mode=prod
53
+ yarn serve http --port 57471 --data-dir=../data/prod --work-dir=../data/prod/.work/ --query-dir=./src/data-server/query --hook-dir=./src/data-server/hook --run-mode=prod
61
54
  ```
62
55
 
63
56
  The above command starts an HTTP server listening at http://localhost:57471
64
57
 
65
- Given a `queryInclude` defined as `"queryInclude": "queryId?arg1=value1&arg2=value2&argN=valueN"`, Drupal will send a request to http://localhost:57471/?query=queryId&arg1=value1&arg2=value2&argN=valueN and include its results in the original JSON data.
58
+ Given a `queryInclude` defined as `"queryInclude": "queryId?arg1=value1&arg2=value2"`, Drupal will send a request to http://localhost:57471/query/queryId?arg1=value1&arg2=value2 and include its results in the original JSON data.
66
59
 
67
- The Data Server will load queries from the directory defined in `queryDir` / `--query-dir`, based on their id. Hence, it will try to find and load a file named "queryId.query.js". Please refer to "Queries" section for more information on queries.
60
+ Data Server will load queries from the directory defined in `queryDir` / `--query-dir`, based on their id. Hence, it will try to find and load a file named "queryId.query.js". Please refer to "[Queries](#queries)" section for more information on queries.
68
61
 
69
62
  ### Developing/debugging queries on a local environment
70
63
 
71
64
  While developing on a local environment, you can access the same HTTP endpoint that Drupal uses to execute and debug any query.
72
65
 
73
- To be able to do so, you need to start the Data Server HTTP endpoint as stated in "Usage > Previewing a content from Drupal". It should be started in "dev" mode. Please refer to "Run modes > Dev run mode" to learn more about `dev` mode.
66
+ To be able to do so, you need to start the Data Server HTTP endpoint as stated in "Usage > [Previewing a content from Drupal](#previewing-a-content-from-drupal)". It should be started in "dev" mode. Please refer to "[Run modes](#run-modes) > [Dev run mode](#dev-run-mode)" to learn more about `dev` mode.
74
67
 
75
68
  ## Data structure
76
69
 
77
- The Data Server honors the structure of Static Suite's data directory. Each file is stored in a Map, keyed by its filepath, and subsets of files can be easily retrieved.
70
+ Data Server honors the structure of Static Suite's data directory. Each file is stored in a Map, keyed by its filepath, and subsets of files (groups of files belonging to a directory) can be easily retrieved.
71
+
72
+ Data is stored in the `store` object, the main point of access to all data. It is an object passed as argument in multiple parts of Data Server (queries, hooks, etc) and also returned by `dataServer.init()` function when executing Data Server inside Node.js.
73
+
74
+ ### Store internal structure
75
+
76
+ - `data`: a Map that holds all data for all files, keyed by their relative filepath. No directory or any other structure is stored here.
77
+
78
+ - `index`: an object to hold accessory index data:
79
+
80
+ - `url`: a Map that holds all data for all files, keyed by their URL.
81
+
82
+ - `uuid`: a Map that holds all data for all files, keyed by their langcode and UUID.
83
+
84
+ - `custom`: a Map to hold custom data defined in hooks or queries.
78
85
 
79
86
  ### Getting a file
80
87
 
@@ -87,7 +94,7 @@ const fileContents = store.data.get('en/entity/node/article/40000/41234.json');
87
94
 
88
95
  ### Getting a subset of files
89
96
 
90
- To get a subset of the files stored in the aforementioned Map, you should use the `subset()` helper function provided at `store.data`, which returns an array with all files in store that match the given arguments.
97
+ To get a subset of files stored in the aforementioned Map, you should use the `subset()` helper function provided at `store.data`, which returns an object with access to all files in store that match the given arguments.
91
98
 
92
99
  #### Subset options
93
100
 
@@ -102,181 +109,634 @@ To get a subset of the files stored in the aforementioned Map, you should use th
102
109
  - `ext`: Optional file extension, without dots. Defaults to `json`.
103
110
  - `recursive`: Optional flag to search for files recursively. Defaults to `true`.
104
111
 
105
- All subsets are automatically cached, so passing the same arguments will return the same subset from the cache.
112
+ #### Return value
113
+
114
+ `subset()` function returns an object with the following members:
115
+
116
+ - `map`: map of elements, with the filename as key
117
+ - `filenames`: array of filenames in the subset
118
+ - `items`: array of items in the subset
119
+
120
+ > All subsets are automatically cached, so passing the same arguments will return the same subset from the cache.
106
121
 
107
122
  This is the preferred way of getting a subset of the store files, since it is a simple function that most of the times only requires the first argument.
108
123
 
109
124
  ```javascript
110
- // Get a subset of all nodes with "json" extension.
111
- dataServer.store.data.subset({ dir: 'en/entity/node/' });
125
+ // Get a subset of all files with "json" extension.
126
+ store.data.subset();
127
+
128
+ // Get a subset of all nodes in English with "json" extension.
129
+ store.data.subset({ dir: 'en/entity/node/' });
112
130
 
113
131
  // Get a subset of all articles in all languages with "json" extension.
114
- dataServer.store.data.subset({ dir: '.+/entity/node/article/' });
132
+ store.data.subset({ dir: '.+/entity/node/article/' });
115
133
 
116
134
  // Get a subset of all articles in English, regardless of their extension.
117
- dataServer.store.data.subset({ dir: 'en/entity/node/article/', ext: null });
135
+ store.data.subset({ dir: 'en/entity/node/article/', ext: null });
118
136
 
119
137
  // Get a subset of all card variants for articles in English, with "json" extension.
120
- dataServer.store.data.subset({ dir: 'en/entity/node/article/', variant: null });
138
+ store.data.subset({ dir: 'en/entity/node/article/', variant: null });
121
139
 
122
140
  // Get a subset of articles in English, with "yml" extension, non-recursively.
123
- dataServer.store.data.subset({
141
+ store.data.subset({
124
142
  dir: 'en/entity/node/article/',
125
143
  ext: 'yml',
126
144
  recursive: false,
127
145
  });
128
146
  ```
129
147
 
130
- This way, queries can be run on any set of data, limiting the amount of data to by analyzed.
148
+ This way, queries can be run on any set of data, limiting the amount of data to be analyzed.
131
149
 
132
150
  ```javascript
133
151
  // Find articles that contain "foo" inside their title.
134
152
  const results = store.data
135
153
  .subset({ dir: '.+/entity/node/article/' })
136
- .filter(article => article.data.content.title.indexOf('foo') !== 1);
154
+ .items.filter(article => article.data.content.title.includes('foo'));
137
155
  ```
138
156
 
139
157
  ```javascript
140
158
  // Find "teaser" article variants that contain "foo" inside their title.
141
159
  const results = store.data
142
160
  .subset({ dir: '.+/entity/node/article/', variant: 'teaser' })
143
- .filter(article => article.data.content.title.indexOf('foo') !== 1);
161
+ .items.filter(article => article.data.content.title.includes('foo'));
162
+ ```
163
+
164
+ ### Indexes
165
+
166
+ Apart from the main data Map, files are also automatically stored in two more maps (indexes) to be able to access them faster:
167
+
168
+ - `url`: a Map keyed by the URL of each file. Only JSON files containing `data.content.url.path` are stored.
169
+ - `uuid`: a Map keyed by the langcode and UUID of each file. Only JSON files containing `data.content.uuid` are stored.
170
+
171
+ ```javascript
172
+ // Find a content with a URL of "lorem/ipsum".
173
+ const fileByUrl = store.index.url.get('lorem/ipsum');
174
+
175
+ // Find a content with an UUID of "f2329fe4-c04c-4709-9673-046683704d38", using its english and french version.
176
+ const englishFileByUuid = store.index.uuid
177
+ .get('en-gb')
178
+ .get('f2329fe4-c04c-4709-9673-046683704d38');
179
+
180
+ const frenchFileByUuid = store.index.uuid
181
+ .get('fr')
182
+ .get('f2329fe4-c04c-4709-9673-046683704d38');
183
+ ```
184
+
185
+ #### Custom indexes
186
+
187
+ There is also an option to create your own custom indexes, usually useful to speed up queries. Custom indexes are stored inside a Map located at `store.index.custom`.
188
+
189
+ ```javascript
190
+ // Create a new custom index as a Map (or as an array, Set, etc).
191
+ store.index.custom.set('myIndex', new Map());
192
+
193
+ // Add data to index.
194
+ const myIndexMap = store.index.custom.get('myIndexMap');
195
+ myIndexMap.set('my-key', 'my-value');
196
+
197
+ // Use custom index in your queries.
198
+ const customData = myIndexMap.get('my-key);
199
+ // Do something with customData
144
200
  ```
145
201
 
202
+ Custom indexes are intended to be created by hooks and consumed by queries. Please refer to "[Hooks](#hooks)" and "[Queries](#queries)" sections for more information.
203
+
146
204
  ## Queries
147
205
 
148
- When the Data Server is executed within Node.js, you have full access to all its data through the `store` object returned by the `dataServer.init()` function.
206
+ When Data Server is executed within Node.js, you have full access to all its data through the `store` object returned by `dataServer.init()` function.
207
+
208
+ However, when using a `queryInclude` or the preview system from Drupal, Data Server needs to run a query, which is stored in a file inside a directory defined by the `queryDir` / `--query-dir` option.
149
209
 
150
- However, when using a `queryInclude` or the preview system from Drupal, the Data Server needs each query to be stored in its own file, inside a directory defined by the `queryDir` / `--query-dir` option.
210
+ Each query is a file named `${queryId}.query.js`, and it must export a default function that receives an object as an argument, containing two keys (`store` and `args`):
151
211
 
152
- Each query is a file named `${queryId}.query.js`, and it must export a default function which receives two arguments:
212
+ - `store`: the object that holds all data. See [store internal structure](#store-internal-structure).
153
213
 
154
- - `store`: the object that holds all data. Being a shared object, any operation that could alter it must be executed on a local copy (i.e.- using `slice()` or similar functions)
155
- - `args`: object with arguments used by that query
214
+ - `args`: object with arguments used by that query.
156
215
 
157
- It must return an object with the following keys:
216
+ > PLEASE NOTE: since `store` is a shared object, you MUST NOT alter it from your queries.
158
217
 
159
- - `results`: result returned .
218
+ Queries must return an object with the following keys:
219
+
220
+ - `result`: the list of returned results. It must be an structure representable as JSON (an object, array, string, etc)
221
+ - `contentType`: optional content-type for the `result` field. Defaults to `application/json`.
222
+ - `tags`: optional array of dependency tags. Defaults to `['*']`. Please refer to "[Dependency Tags](#dependency-tags)" section for more information on this topic.
160
223
  - `cacheable`: optional boolean flag telling whether this query can be cached or not. Defaults to true.
161
224
 
162
225
  Example: find all contents by title passed in "q" argument
163
226
 
164
227
  ```javascript
165
- const findContentsByTitle = (storeData, args) => {
166
- const q = args.q?.toLowerCase();
167
- const results = storeData._json.main
168
- .filter(
169
- node =>
170
- node.data?.content?.title &&
171
- node.data?.content?.title?.toLowerCase().indexOf(q) !== -1,
172
- )
228
+ const findContentsByTitle = ({ store, args }) => {
229
+ const q = args?.q?.toLowerCase();
230
+ const result = store.data
231
+ .subset()
232
+ .items.filter(node => node.data?.content?.title?.toLowerCase().includes(q))
173
233
  .map(node => ({
174
234
  id: node.data?.content?.id,
175
235
  title: node.data?.content?.title,
176
236
  path: node.data?.content?.url.path,
177
237
  }));
178
- return { results, cacheable: true };
238
+ return { result };
179
239
  };
180
240
 
181
- module.exports = findContentByTitle;
241
+ module.exports.default = findContentsByTitle;
182
242
  ```
183
243
 
184
- Example: list latest contents. Note the use of slice() to make a copy of the data and avoid `sort()` altering the original object.
244
+ Example: get latest 10 articles, with optional "limit" parameter.
185
245
 
186
246
  ```javascript
187
- const latestContents = (storeData, args = {}) => {
188
- const results = storeData._json.main
189
- .slice()
190
- .sort((a, b) => b.changed - a.changed)
191
- .slice(0, args.limit || 10)
247
+ const latestContents = ({ store, args }) => {
248
+ const result = store.data
249
+ .subset({ dir: '.+/entity/node/article/' })
250
+ .items.sort((a, b) => b.changed - a.changed)
251
+ .slice(0, args?.limit || 10)
192
252
  .map(node => ({
193
253
  id: node.data?.content?.id,
194
254
  title: node.data?.content?.title,
195
255
  path: node.data?.content?.url.path,
196
256
  }));
197
- return { results, cacheable: false };
257
+ return { result };
198
258
  };
199
259
 
200
- module.exports = latestContents;
260
+ module.exports.default = latestContents;
201
261
  ```
202
262
 
203
- The Data Server uses internally a `queryRunner` to execute those queries, encapsulating the query function results into an object with the following shape:
263
+ Data Server uses internally a `queryRunner` to execute those queries, encapsulating the query function results into an object with the following shape:
204
264
 
205
265
  ```javascript
206
266
  {
207
267
  data: results,
208
268
  metadata: {
209
- contentType: 'application/json',
210
- execTime: [int],
211
- cache: ['miss'|'hit'],
212
- num: results.length,
269
+ contentType: string,
270
+ execTimeMs: int,
271
+ cache: 'miss'|'hit',
272
+ tags: string[],
213
273
  }
214
274
  },
215
275
  ```
216
276
 
217
- ## Post processors
277
+ ## Hooks
278
+
279
+ Data Server allows altering the way data is processed and added to the store. To do so, it uses `hooks`, a kind of event-driven system which executes custom code at different points of the processing workflow.
280
+
281
+ Hooks are stored in a file inside a directory defined by the `hookDir` / `--hook-dir` option.
282
+
283
+ ### Available hooks
284
+
285
+ Each hook is a file named `${hookId}.hook.js`, and it must export an object with several optional functions as keys:
286
+
287
+ #### `onModuleLoad({config, store, logger }): void`
288
+
289
+ Called when a hook module is loaded or reloaded. It is aimed at bootstrapping some data structure, or establishing some connection to an external system before other hooks run.
290
+
291
+ Received parameters:
292
+
293
+ - `config`: object with configuration options defined at Data Server start: `dataDir`, `workDir`, `queryDir`, etc
294
+ - `store`: the object that holds all data. See [store internal structure](#store-internal-structure).
295
+ - `logger`: the logger service width several functions to log messages: `error()`, `warn()`, `info()`, `error()`
296
+
297
+ #### `onStoreLoadStart({ store, logger, dependencyTagger }): void`
298
+
299
+ Called before the store starts loading for the first time.
300
+
301
+ Received parameters:
302
+
303
+ - `store`: the object that holds all data. See [store internal structure](#store-internal-structure).
304
+ - `logger`: the logger service width several functions to log messages: `error()`, `warn()`, `info()`, `error()`
305
+ - `dependencyTagger`: the dependency tagger service. See [Dependency Tags](#dependency-tags) section.
306
+
307
+ #### `onProcessFile({ store, logger, dependencyTagger, relativeFilepath, fileContent }): fileContent`
308
+
309
+ Called after a file is read from disk, before adding it to the store. It is aimed at altering the contents of the file before adding it to the store.
310
+
311
+ Received parameters:
312
+
313
+ - `store`: the object that holds all data. See [store internal structure](#store-internal-structure).
314
+ - `logger`: the logger service width several functions to log messages: `error()`, `warn()`, `info()`, `error()`
315
+ - `dependencyTagger`: the dependency tagger service. See [Dependency Tags](#dependency-tags) section.
316
+ - `relativeFilepath`: relative file path inside the data dir.
317
+ - `fileContent`: file contents, an object with "raw" and "json" members.
318
+
319
+ Return value:
320
+
321
+ - The passed `fileContent` object (an object with "raw" and "json" members) with any modification applied.
322
+
323
+ #### `onStoreItemAdd({ store, logger, dependencyTagger, relativeFilepath, storeItem }): void`
324
+
325
+ Called after a file is added into the store for the first time.
326
+
327
+ Received parameters:
328
+
329
+ - `store`: the object that holds all data. See [store internal structure](#store-internal-structure).
330
+ - `logger`: the logger service width several functions to log messages: `error()`, `warn()`, `info()`, `error()`
331
+ - `dependencyTagger`: the dependency tagger service. See [Dependency Tags](#dependency-tags) section.
332
+ - `relativeFilepath`: relative file path inside the data dir.
333
+ - `storeItem`: contents of the store item.
334
+
335
+ #### `onStoreLoadDone({ store, logger, dependencyTagger }): void`
336
+
337
+ Called after the store finishes loading for the first time.
338
+
339
+ Received parameters:
340
+
341
+ - `store`: the object that holds all data. See [store internal structure](#store-internal-structure).
342
+ - `logger`: the logger service width several functions to log messages: `error()`, `warn()`, `info()`, `error()`
343
+ - `dependencyTagger`: the dependency tagger service. See [Dependency Tags](#dependency-tags) section.
344
+
345
+ #### `onStoreChangeStart({ store, logger, dependencyTagger, changedFiles }): void`
346
+
347
+ Called before the store starts updating.
348
+
349
+ Received parameters:
350
+
351
+ - `store`: the object that holds all data. See [store internal structure](#store-internal-structure).
352
+ - `logger`: the logger service width several functions to log messages: `error()`, `warn()`, `info()`, `error()`
353
+ - `dependencyTagger`: the dependency tagger service. See [Dependency Tags](#dependency-tags) section.
354
+ - `changedFiles`: a group of changed files in Static Suite's data dir, an object with the following keys:
355
+ - `fromUniqueId`: a unique id representing the date from which those changes are obtained.
356
+ - `toUniqueId`: a unique id representing the date until which those changes are obtained.
357
+ - `updated`: a list of updated files (both newly added and changed files).
358
+ - `deleted`: a list of deleted files.
359
+
360
+ #### `onStoreItemBeforeUpdate({ store, logger, dependencyTagger, relativeFilepath, storeItem }): void`
361
+
362
+ Called before a file is updated in the store.
363
+
364
+ Received parameters:
365
+
366
+ - `store`: the object that holds all data. See [store internal structure](#store-internal-structure).
367
+ - `logger`: the logger service width several functions to log messages: `error()`, `warn()`, `info()`, `error()`
368
+ - `dependencyTagger`: the dependency tagger service. See [Dependency Tags](#dependency-tags) section.
369
+ - `relativeFilepath`: relative file path inside the data dir.
370
+ - `storeItem`: contents of the store item.
371
+
372
+ #### `onStoreItemAfterUpdate({ store, logger, dependencyTagger, relativeFilepath, storeItem, previousStoreItem }): void`
373
+
374
+ Called after a file is updated in the store.
375
+
376
+ Received parameters:
377
+
378
+ - `store`: the object that holds all data. See [store internal structure](#store-internal-structure).
379
+ - `logger`: the logger service width several functions to log messages: `error()`, `warn()`, `info()`, `error()`
380
+ - `dependencyTagger`: the dependency tagger service. See [Dependency Tags](#dependency-tags) section.
381
+ - `relativeFilepath`: relative file path inside the data dir.
382
+ - `storeItem`: contents of the store item.
383
+ - `previousStoreItem`: contents of the previous store item before store was updated
384
+
385
+ #### `onStoreItemDelete({ store, logger, dependencyTagger, relativeFilepath, storeItem }): void`
386
+
387
+ Called after a file is deleted from the store.
388
+
389
+ Received parameters:
390
+
391
+ - `store`: the object that holds all data. See [store internal structure](#store-internal-structure).
392
+ - `logger`: the logger service width several functions to log messages: `error()`, `warn()`, `info()`, `error()`
393
+ - `dependencyTagger`: the dependency tagger service. See [Dependency Tags](#dependency-tags) section.
394
+ - `relativeFilepath`: relative file path inside the data dir.
395
+ - `storeItem`: contents of the store item.
396
+
397
+ #### `onStoreChangeDone({ store, logger, dependencyTagger, changedFiles }): void`
398
+
399
+ Called after the store ends updating.
400
+
401
+ Received parameters:
218
402
 
219
- The Data Server allows altering the way data is processed and added to the store. To do so, it uses a special file, called the "post processor". This file must export a default object with three optional functions as keys:
403
+ - `store`: the object that holds all data. See [store internal structure](#store-internal-structure).
404
+ - `logger`: the logger service width several functions to log messages: `error()`, `warn()`, `info()`, `error()`
405
+ - `dependencyTagger`: the dependency tagger service. See [Dependency Tags](#dependency-tags) section.
406
+ - `changedFiles`: a group of changed files in Static Suite's data dir, an object with the following keys:
407
+
408
+ - `fromUniqueId`: a unique id representing the date from which those changes are obtained.
409
+ - `toUniqueId`: a unique id representing the date until which those changes are obtained.
410
+ - `updated`: a list of updated files (both newly added and changed files).
411
+ - `deleted`: a list of deleted files.
412
+
413
+ #### `onDumpCreate(options: OnDumpHookOptions): Dump`
414
+
415
+ Called after a dump object is created, aimed at altering the contents of the dump being saved, and adding/removing items.
416
+
417
+ Received parameters:
418
+
419
+ - `store`: the object that holds all data. See [store internal structure](#store-internal-structure).
420
+ - `dumpDir`: path to the dump directory.
421
+ - `dump`: the dump to be processed. See [Dumps section](#dumps) for more information.
422
+
423
+ Return value:
424
+
425
+ - The passed `dump` object with any modification applied.
426
+
427
+ ### Hook example
428
+
429
+ The main use case of hooks is creating index data that will be later consumed by queries. In this example, we will create:
430
+
431
+ 1. a hook that manages a custom index of contents indexed by their taxonomy term id.
432
+ 2. a query that consumes that custom index.
433
+
434
+ #### `File taxonomy.hook.js`
220
435
 
221
436
  ```javascript
222
- const main = {
223
- processFile: (options = {}) => {
224
- // Do something...
225
- return {
226
- rawFileContents: foo,
227
- jsonFileContents: bar,
228
- };
437
+ // file taxonomy.hook.js
438
+
439
+ // A reference to our custom index.
440
+ let filepathsByTermIdIndex = null;
441
+
442
+ /**
443
+ * Helper function to add content to index
444
+ */
445
+ const addContentToIndex = (relativeFilepath, storeItem) => {
446
+ const termId = storeItem.data?.content?.primaryTaxonomy?.entity?.tid;
447
+ // Only add content to index if it has a taxonomy term id.
448
+ if (termId) {
449
+ // Initialize Set for this taxonomy term id.
450
+ if (!filepathsByTermIdIndex.has(termId)) {
451
+ filepathsByTermIdIndex.set(termId, new Set());
452
+ }
453
+ const taxonomySet = filepathsByTermIdIndex.get(termId);
454
+ // Add file path to index.
455
+ taxonomySet.add(relativeFilepath);
456
+ }
457
+ };
458
+
459
+ /**
460
+ * Helper function to search and remove any reference to relativeFilepath in index.
461
+ */
462
+ const deleteContentFromIndex = relativeFilepath => {
463
+ filepathsByTermIdIndex.forEach(taxonomySet => {
464
+ if (taxonomySet.has(relativeFilepath)) {
465
+ taxonomySet.delete(relativeFilepath);
466
+ }
467
+ });
468
+ };
469
+
470
+ module.exports = {
471
+ // Initialize custom index.
472
+ onModuleLoad: ({ store }) => {
473
+ if (!store.index.custom.has('filepathsByTermId')) {
474
+ store.index.custom.set('filepathsByTermId', new Map());
475
+ }
476
+ filepathsByTermIdIndex = store.index.custom.get('filepathsByTermId');
477
+ },
478
+
479
+ // Add content to index during store's first load.
480
+ onStoreItemAdd: ({ relativeFilepath, storeItem }) => {
481
+ addContentToIndex(relativeFilepath, storeItem);
229
482
  },
230
483
 
231
- storeAdd: (options = {}) => {
232
- // Do something...
484
+ // Remove content from index to rollback any change.
485
+ onStoreItemBeforeUpdate: ({ relativeFilepath }) => {
486
+ deleteContentFromIndex(relativeFilepath);
233
487
  },
234
488
 
235
- storeRemove: (options = {}) => {
236
- // Do something...
489
+ // Add content again to index. This hook runs after
490
+ // `onStoreItemBeforeUpdate`, so content is already
491
+ // removed from index and we can simply add it again.
492
+ onStoreItemAfterUpdate: ({ relativeFilepath, storeItem }) => {
493
+ addContentToIndex(relativeFilepath, storeItem);
494
+ },
495
+
496
+ // Remove content from index
497
+ onStoreItemDelete: ({ relativeFilepath }) => {
498
+ deleteContentFromIndex(relativeFilepath);
237
499
  },
238
500
  };
501
+ ```
502
+
503
+ #### `File listContentsByTermId.query.js`
504
+
505
+ ```javascript
506
+ const listContentsByTermId = ({ store, args }) => {
507
+ // Get the passed parameter "taxonomyTermId",
508
+ const { termId: termIdString } = args;
509
+
510
+ // Please note that args.termId is a string, but we need an integer.
511
+ const termId = parseInt(termIdString, 10);
512
+
513
+ // Get the Set of filepaths belonging to this taxonomy term id.
514
+ const filepathsByTermId = store.index.custom
515
+ .get('filepathsByTermId')
516
+ .get(termId);
517
+
518
+ const contentsByTermId = [];
519
+ // Iterate over all items and get their data from store.data.
520
+ if (filepathsByTermId) {
521
+ filepathsByTermId.forEach(filepath => {
522
+ // Get data from store.
523
+ const content = store.data.get(filepath);
524
+ // Add to contentsByTaxonomyTermId array, getting only the set of values we need.
525
+ contentsByTermId.push({
526
+ id: content.data?.content?.id,
527
+ title: content.data?.content?.title,
528
+ path: content.data?.content?.url.path,
529
+ });
530
+ });
531
+ }
532
+
533
+ return {
534
+ result: contentsByTermId,
535
+ };
536
+ };
537
+
538
+ module.exports.default = listContentsByTermId;
539
+ ```
540
+
541
+ ## Dumps
542
+
543
+ Data Server is able to detect when a file is being used (included) by other file. When a file changes, Data Server provides a list of all files affected by that change. This list can therefore be used to generate incremental builds of your site, building only the HTML pages for those changed files.
544
+
545
+ To obtain that list of incremental changes, Data Server makes use of a `dump`. A dump is a directory where Data Server outputs all its data once all its includes are resolved and its queries executed. Think of a dump as an exported copy of the `data dir`, without any include nor query.
546
+
547
+ This `dump` directory is used to keep track of any change, and to be able to build only the exact files that have changes:
239
548
 
240
- module.exports = main;
549
+ 1. When a dump is run, it calculates which files are probably affected by latest changes.
550
+ 2. With that set of candidates, Data Server checks those changes against the dump directory, narrowing the final set of changed files and saving them to that directory.
551
+
552
+ A dump is executed by accessing an HTTP endpoint located at `/dump/incremental`, which returns a response like this one:
553
+
554
+ ```json
555
+ {
556
+ "execTimeMs": 214.666,
557
+ "fromUniqueId": "2022-06-20_11-29-12.834701__4492",
558
+ "toUniqueId": "2022-06-20_11-59-57.045645__8520",
559
+ "updated": {
560
+ "en-gb/entity/node/article/60000/61416.json": {
561
+ "oldPublicUrl": "/my-article",
562
+ "newPublicUrl": "/my-renamed-article"
563
+ }
564
+ },
565
+ "deleted": {},
566
+ "diff": {
567
+ "execTimeMs": 110.636,
568
+ "fromUniqueId": "2022-06-20_11-29-12.834701__4492",
569
+ "toUniqueId": "2022-06-20_11-59-57.045645__8520",
570
+ "updated": [
571
+ "en-gb/entity/node/article/1234.json",
572
+ "en-gb/entity/node/page/1.json"
573
+ ],
574
+ "deleted": []
575
+ }
576
+ }
241
577
  ```
242
578
 
243
- The post processor is a single file, instead of several ones like queries. The reason for this is to be able to easily execute different post processors in a given order, or bypass some of them (or even sorting them differently) given some conditions.
579
+ All data inside the `diff` key corresponds to the first step of the dump, when Data Server calculates which files are affected by changes. It is only informative, and should not be used for incremental builds.
580
+
581
+ Data inside the `dump` key is the one to be used for incremental builds. It contains the set of updated files that actually have changes, along with the set of deleted files. Use this data to decide which files should be incrementally rebuilt or deleted. Please note than when a file gets its URL renamed, Data Server tracks down that change so you can act accordingly (by using the `oldPublicUrl` and `newPublicUrl` keys).
582
+
583
+ All this data returned by the HTTP endpoint is also stored in a metadata file named `metadata.json`, located inside the dump directory. Once you are finished creating the incremental pages for your site, you should clean up that metadata file, manually or by calling the HTTP endpoint located at `/dump/metadata/reset?uniqueId=[toUniqueId]`.
584
+
585
+ ## Dependency tags
586
+
587
+ When a query is executed, it can return an array of dependency tags, declaring which files, strings, keys, indexes, etc it depends on. By default, all queries depend on `*` (asterisk), which means that they depend on any change on any file, whatever it is.
588
+
589
+ Hence, when executing a dump, all queries are detected as files probably affected by changes. Therefore, they are re-executed and compared against the dump directory, to finally detect the files that actually have changed.
590
+
591
+ Data Server is fast enough to execute thousands of queries in a single second, so re-executing all queries on a dump is rarely a performance bottleneck.
592
+
593
+ Anyway, there are some cases with tens of thousands of queries, or slow disks, which need to be addressed to avoid re-executing all queries.
594
+
595
+ > PLEASE NOTE that using dependency tags is a performance optimization, and should be avoided if performance is not a problem.
596
+
597
+ ### Dependency tagger
244
598
 
245
- ### processFile
599
+ Dependency tags are managed by a service called `dependencyTagger`, which is passed to most hooks as a parameter. It has the following functions:
246
600
 
247
- Executed after a file is read from disk, and before adding it to the store. It receives an object with the following options:
601
+ #### `setDependency(tag: string, dependsOnTags: string[]): void`
248
602
 
249
- - `dataDir`: path to the data directory
250
- - `file`: relative path to the file being processed
251
- - `rawFileContents`: raw file contents
252
- - `jsonFileContents`: JSON file contents
253
- - `store`
603
+ Sets a dependency relationship between a tag that depends on another tags. It replaces any previous dependency relationship.
254
604
 
255
- It must return an object with two keys, that will replace the original file contents:
605
+ #### `addDependency(tag: string, dependsOnTags: string[]): void`
256
606
 
257
- - `rawFileContents`
258
- - `jsonFileContents`
607
+ Adds a dependency relationship between a tag that depends on another tags. It adds data to any previous dependency relationship.
259
608
 
260
- ### storeAdd
609
+ #### `deleteDependency(tag: string, dependsOnTags?: string[]): void`
261
610
 
262
- Executed after a file is added to the store. It receives an object with the following options:
611
+ Deletes a dependency relationship between a tag that depends on another tags. If `dependsOnTags` is not provided, it deletes all dependencies for `tag`.
263
612
 
264
- - `dataDir`: path to the data directory
265
- - `file`: relative path to the file being processed
266
- - `rawFileContents`: raw file contents
267
- - `jsonFileContents`: JSON file contents
268
- - `store`
613
+ #### `invalidateTags(tags: string[]): void`
269
614
 
270
- It must return nothing (`void`).
615
+ Marks dependency items with any of the specified tags as invalid.
271
616
 
272
- ### storeRemove
617
+ ### Dependency tags example
273
618
 
274
- Executed after a file is removed from the store. It receives an object with the following options:
619
+ Following the previous [hook example](#hook-example), let's add dependency tags to the hook and query used there.
275
620
 
276
- - `file`: relative path to the file being processed
277
- - `store`
621
+ #### `File taxonomy.hook.js`
622
+
623
+ Since we are creating an index based on taxonomy term ids, we only need to invalidate tags when that index changes.
624
+
625
+ ```javascript
626
+ // file taxonomy.hook.js
627
+
628
+ // A reference to our custom index.
629
+ let filepathsByTermIdIndex = null;
630
+
631
+ /**
632
+ * Helper function to add content to index
633
+ */
634
+ const addContentToIndex = (relativeFilepath, storeItem, dependencyTagger) => {
635
+ const termId = storeItem.data?.content?.primaryTaxonomy?.entity?.tid;
636
+ // Only add content to index if it has a taxonomy term id.
637
+ if (termId) {
638
+ // Initialize Set for this taxonomy term id.
639
+ if (!filepathsByTermIdIndex.has(termId)) {
640
+ filepathsByTermIdIndex.set(termId, new Set());
641
+ }
642
+ const taxonomySet = filepathsByTermIdIndex.get(termId);
643
+ // Add file path to index.
644
+ taxonomySet.add(relativeFilepath);
645
+ // Invalidate dependencies.
646
+ dependencyTagger.invalidateTags([`filepathsByTermId:${termId}`]);
647
+ }
648
+ };
649
+
650
+ /**
651
+ * Helper function to search and remove any reference to relativeFilepath in index.
652
+ */
653
+ const deleteContentFromIndex = (relativeFilepath, dependencyTagger) => {
654
+ filepathsByTermIdIndex.forEach((taxonomySet, termId) => {
655
+ if (taxonomySet.has(relativeFilepath)) {
656
+ taxonomySet.delete(relativeFilepath);
657
+ // Invalidate dependencies.
658
+ dependencyTagger.invalidateTags([`filepathsByTermId:${termId}`]);
659
+ }
660
+ });
661
+ };
662
+
663
+ module.exports = {
664
+ // Initialize custom index.
665
+ onModuleLoad: ({ store }) => {
666
+ if (!store.index.custom.has('filepathsByTermId')) {
667
+ store.index.custom.set('filepathsByTermId', new Map());
668
+ }
669
+ filepathsByTermIdIndex = store.index.custom.get('filepathsByTermId');
670
+ },
278
671
 
279
- It must return nothing (`void`).
672
+ // Add content to index during store's first load.
673
+ onStoreItemAdd: ({ relativeFilepath, storeItem, dependencyTagger }) => {
674
+ addContentToIndex(relativeFilepath, storeItem, dependencyTagger);
675
+ },
676
+
677
+ // Remove content from index to rollback any change.
678
+ onStoreItemBeforeUpdate: ({ relativeFilepath, dependencyTagger }) => {
679
+ deleteContentFromIndex(relativeFilepath, dependencyTagger);
680
+ },
681
+
682
+ // Add content again to index. This hook runs after
683
+ // `onStoreItemBeforeUpdate`, so content is already
684
+ // removed from index and we can simply add it again.
685
+ onStoreItemAfterUpdate: ({
686
+ relativeFilepath,
687
+ storeItem,
688
+ dependencyTagger,
689
+ }) => {
690
+ addContentToIndex(relativeFilepath, storeItem, dependencyTagger);
691
+ },
692
+
693
+ // Remove content from index
694
+ onStoreItemDelete: ({ relativeFilepath, dependencyTagger }) => {
695
+ deleteContentFromIndex(relativeFilepath, dependencyTagger);
696
+ },
697
+ };
698
+ ```
699
+
700
+ #### `File listContentsByTermId.query.js`
701
+
702
+ Once the hook properly invalidates tags, we only need to declare which query depends on which tags. This is done by returning a `tags` array inside the query response (it internally sets dependencies using the `setDependency()` function from `dependencyTagger`).
703
+
704
+ ```javascript
705
+ const listContentsByTermId = ({ store, args }) => {
706
+ // Get the passed parameter "taxonomyTermId",
707
+ const { termId: termIdString } = args;
708
+
709
+ // Please note that args.termId is a string, but we need an integer.
710
+ const termId = parseInt(termIdString, 10);
711
+
712
+ // Get the Set of filepaths belonging to this taxonomy term id.
713
+ const filepathsByTermId = store.index.custom
714
+ .get('filepathsByTermId')
715
+ .get(termId);
716
+
717
+ const contentsByTermId = [];
718
+ // Iterate over all items and get their data from store.data.
719
+ if (filepathsByTermId) {
720
+ filepathsByTermId.forEach(filepath => {
721
+ // Get data from store.
722
+ const content = store.data.get(filepath);
723
+ // Add to contentsByTaxonomyTermId array, getting only the set of values we need.
724
+ contentsByTermId.push({
725
+ id: content.data?.content?.id,
726
+ title: content.data?.content?.title,
727
+ path: content.data?.content?.url.path,
728
+ });
729
+ });
730
+ }
731
+
732
+ return {
733
+ result: contentsByTermId,
734
+ tags: [`filepathsByTermId:${termId}`],
735
+ };
736
+ };
737
+
738
+ module.exports.default = listContentsByTermId;
739
+ ```
280
740
 
281
741
  ## Run modes
282
742
 
@@ -284,16 +744,16 @@ There are two valid run modes: `dev` and `prod`
284
744
 
285
745
  ### Dev run mode
286
746
 
287
- Disables the query cache and watches for changes in the post processor and query files:
747
+ It watches for changes in the post processor and query files:
288
748
 
289
749
  - When a file inside the `queryDir` / `--query-dir` changes, all files inside that directory are reloaded to ensure any module or sub module is properly refreshed
290
- - When a file inside the directory that holds the post processor changes, all files inside that directory are reloaded, and the store is reloaded and passed through the post processor, so any changes in it are applied to all data files.
750
+ - When a file inside the `hookDir` / `--hook-dir` changes, all files inside that directory are reloaded, and the store is reloaded and passed again through all hooks, so any changes in them are applied to all data files.
291
751
 
292
752
  This is the intended mode when developing queries (executing it by directly requesting the HTTP endpoint to execute a query). This mode is not required for the develop phase of a SSG, since the SSG will get the data from the Data Server only once.
293
753
 
294
754
  ### Prod run mode
295
755
 
296
- The query cache is enabled, and no watching is enabled for the post processor and query files.
756
+ The query cache is enabled, and no watching is enabled for hook or query files.
297
757
 
298
758
  This is the intended mode for the HTTP endpoint in production, and for the develop/build phase of the SSG.
299
759