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

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