@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.
- package/README.md +569 -107
- package/dist/http/README.md +569 -107
- package/dist/http/routes/dependency.d.ts +10 -0
- package/dist/http/routes/dependency.d.ts.map +1 -0
- package/dist/http/routes/dependency.js +71 -0
- package/dist/http/routes/dump.d.ts.map +1 -1
- package/dist/http/routes/dump.js +15 -10
- package/dist/http/routes/home.js +2 -1
- package/dist/http/routes/index.d.ts.map +1 -1
- package/dist/http/routes/index.js +8 -0
- package/dist/http/routes/status.d.ts.map +1 -1
- package/dist/http/routes/status.js +1 -6
- package/dist/http/views/dependencyIndex.pug +14 -0
- package/dist/lib/dataServer.d.ts +2 -1
- package/dist/lib/dataServer.d.ts.map +1 -1
- package/dist/lib/query/query.types.d.ts +2 -2
- package/dist/lib/query/query.types.d.ts.map +1 -1
- package/dist/lib/query/query.types.js +2 -2
- package/dist/lib/query/queryRunner.d.ts.map +1 -1
- package/dist/lib/query/queryRunner.js +3 -1
- package/dist/lib/store/dataDir/dataDir.types.d.ts +5 -5
- package/dist/lib/store/dataDir/dataDir.types.d.ts.map +1 -1
- package/dist/lib/store/dataDir/dataDirManager.d.ts.map +1 -1
- package/dist/lib/store/dataDir/dataDirManager.js +23 -12
- package/dist/lib/store/dependency/dependency.types.d.ts.map +1 -1
- package/dist/lib/store/dependency/dependencyFileHelper.d.ts +1 -1
- package/dist/lib/store/dependency/dependencyFileHelper.d.ts.map +1 -1
- package/dist/lib/store/dependency/dependencyFileHelper.js +5 -3
- package/dist/lib/store/dependency/dependencyManager.d.ts +12 -0
- package/dist/lib/store/dependency/dependencyManager.d.ts.map +1 -1
- package/dist/lib/store/dependency/dependencyManager.js +7 -5
- package/dist/lib/store/dependency/dependencyTagger.d.ts.map +1 -1
- package/dist/lib/store/dependency/dependencyTagger.js +3 -38
- package/dist/lib/store/diff/{diffManager.types.d.ts → diff.types.d.ts} +14 -6
- package/dist/lib/store/diff/diff.types.d.ts.map +1 -0
- package/dist/lib/store/{dependency/dependencyManager.types.js → diff/diff.types.js} +0 -0
- package/dist/lib/store/diff/diffManager.d.ts +1 -1
- package/dist/lib/store/diff/diffManager.d.ts.map +1 -1
- package/dist/lib/store/diff/diffManager.js +38 -81
- package/dist/lib/store/dump/{dumpManager.types.d.ts → dump.types.d.ts} +21 -8
- package/dist/lib/store/dump/dump.types.d.ts.map +1 -0
- package/dist/lib/store/{diff/diffManager.types.js → dump/dump.types.js} +0 -0
- package/dist/lib/store/dump/dumpManager.d.ts +1 -1
- package/dist/lib/store/dump/dumpManager.d.ts.map +1 -1
- package/dist/lib/store/dump/dumpManager.js +31 -56
- package/dist/lib/store/dump/dumpMetadataHelper.d.ts +7 -0
- package/dist/lib/store/dump/dumpMetadataHelper.d.ts.map +1 -0
- package/dist/lib/store/dump/dumpMetadataHelper.js +61 -0
- package/dist/lib/store/hook/hook.types.d.ts +49 -23
- package/dist/lib/store/hook/hook.types.d.ts.map +1 -1
- package/dist/lib/store/hook/hookManager.d.ts.map +1 -1
- package/dist/lib/store/hook/hookManager.js +19 -8
- package/dist/lib/store/store.d.ts.map +1 -1
- package/dist/lib/store/store.js +9 -4
- package/dist/lib/store/store.types.d.ts +14 -37
- package/dist/lib/store/store.types.d.ts.map +1 -1
- package/dist/lib/store/storeManager.d.ts.map +1 -1
- package/dist/lib/store/storeManager.js +20 -10
- package/dist/lib/store/workDir/storage/getChangedLinesBetween.d.ts +16 -0
- package/dist/lib/store/workDir/storage/getChangedLinesBetween.d.ts.map +1 -0
- package/dist/lib/store/workDir/storage/{getChangedLinesSince.js → getChangedLinesBetween.js} +22 -20
- package/dist/lib/store/workDir/storage/getDataFromLogLine.d.ts.map +1 -1
- package/dist/lib/store/workDir/storage/getDataFromLogLine.js +16 -3
- package/dist/lib/store/workDir/storage/index.d.ts +1 -1
- package/dist/lib/store/workDir/storage/index.d.ts.map +1 -1
- package/dist/lib/store/workDir/storage/index.js +1 -1
- package/dist/lib/store/workDir/workDir.types.d.ts +9 -1
- package/dist/lib/store/workDir/workDir.types.d.ts.map +1 -1
- package/dist/lib/store/workDir/workDirHelper.d.ts +13 -6
- package/dist/lib/store/workDir/workDirHelper.d.ts.map +1 -1
- package/dist/lib/store/workDir/workDirHelper.js +42 -11
- package/dist/lib/utils/fs/fsUtils.d.ts +6 -0
- package/dist/lib/utils/fs/fsUtils.d.ts.map +1 -1
- package/dist/lib/utils/fs/fsUtils.js +20 -1
- package/dist/lib/utils/module/moduleManager.d.ts.map +1 -1
- package/dist/lib/utils/module/moduleManager.js +5 -1
- package/dist/lib/utils/string/stringUtils.d.ts +14 -0
- package/dist/lib/utils/string/stringUtils.d.ts.map +1 -1
- package/dist/lib/utils/string/stringUtils.js +28 -1
- package/dist/static-suite-data-server.d.ts +19 -39
- package/package.json +3 -3
- package/src/http/routes/dependency.ts +91 -0
- package/src/http/routes/dump.ts +21 -10
- package/src/http/routes/home.ts +2 -2
- package/src/http/routes/index.ts +16 -0
- package/src/http/routes/status.ts +1 -9
- package/src/http/views/dependencyIndex.pug +14 -0
- package/src/lib/dataServer.ts +2 -0
- package/src/lib/query/query.types.ts +2 -2
- package/src/lib/query/queryRunner.ts +3 -1
- package/src/lib/store/dataDir/dataDir.types.ts +5 -5
- package/src/lib/store/dataDir/dataDirManager.ts +30 -14
- package/src/lib/store/dependency/dependency.types.ts +0 -5
- package/src/lib/store/dependency/dependencyFileHelper.ts +5 -3
- package/src/lib/store/dependency/dependencyManager.ts +6 -3
- package/src/lib/store/dependency/dependencyTagger.ts +3 -39
- package/src/lib/store/diff/{diffManager.types.ts → diff.types.ts} +14 -5
- package/src/lib/store/diff/diffManager.ts +45 -84
- package/src/lib/store/dump/{dumpManager.types.ts → dump.types.ts} +24 -7
- package/src/lib/store/dump/dumpManager.ts +40 -75
- package/src/lib/store/dump/dumpMetadataHelper.ts +73 -0
- package/src/lib/store/hook/hook.types.ts +58 -23
- package/src/lib/store/hook/hookManager.ts +24 -9
- package/src/lib/store/store.ts +9 -4
- package/src/lib/store/store.types.ts +16 -37
- package/src/lib/store/storeManager.ts +28 -12
- package/src/lib/store/workDir/__tests__/workDirHelper.test.ts +6 -1
- package/src/lib/store/workDir/storage/__tests__/{getChangedLinesSince.test.ts → getChangedLinesBetween.test.ts} +9 -4
- package/src/lib/store/workDir/storage/getChangedLinesBetween.ts +52 -0
- package/src/lib/store/workDir/storage/getDataFromLogLine.ts +22 -3
- package/src/lib/store/workDir/storage/index.ts +1 -1
- package/src/lib/store/workDir/workDir.types.ts +11 -1
- package/src/lib/store/workDir/workDirHelper.ts +45 -11
- package/src/lib/utils/fs/fsUtils.ts +18 -0
- package/src/lib/utils/module/moduleManager.ts +5 -1
- package/src/lib/utils/string/__tests__/stringUtils.test.ts +30 -1
- package/src/lib/utils/string/stringUtils.ts +32 -0
- package/dist/lib/query/queryRunner copy.d.ts +0 -3
- package/dist/lib/query/queryRunner copy.d.ts.map +0 -1
- package/dist/lib/query/queryRunner copy.js +0 -87
- package/dist/lib/store/dependency/dependencyManager copy.d.ts +0 -6
- package/dist/lib/store/dependency/dependencyManager copy.d.ts.map +0 -1
- package/dist/lib/store/dependency/dependencyManager copy.js +0 -91
- package/dist/lib/store/dependency/dependencyManager.types.d.ts +0 -48
- package/dist/lib/store/dependency/dependencyManager.types.d.ts.map +0 -1
- package/dist/lib/store/dependency/dependencyTagManager.d.ts +0 -6
- package/dist/lib/store/dependency/dependencyTagManager.d.ts.map +0 -1
- package/dist/lib/store/dependency/dependencyTagManager.js +0 -33
- package/dist/lib/store/diff/diffManager.types.d.ts.map +0 -1
- package/dist/lib/store/diff/tracker.d.ts +0 -3
- package/dist/lib/store/diff/tracker.d.ts.map +0 -1
- package/dist/lib/store/diff/tracker.js +0 -82
- package/dist/lib/store/diff/tracker.types.d.ts +0 -31
- package/dist/lib/store/diff/tracker.types.d.ts.map +0 -1
- package/dist/lib/store/diff/tracker.types.js +0 -2
- package/dist/lib/store/dump/dumpManager.types.d.ts.map +0 -1
- package/dist/lib/store/dump/dumpManager.types.js +0 -2
- package/dist/lib/store/include/includeIndex.d.ts +0 -6
- package/dist/lib/store/include/includeIndex.d.ts.map +0 -1
- package/dist/lib/store/include/includeIndex.js +0 -65
- package/dist/lib/store/include/includeIndex.types.d.ts +0 -29
- package/dist/lib/store/include/includeIndex.types.d.ts.map +0 -1
- package/dist/lib/store/include/includeIndex.types.js +0 -2
- package/dist/lib/store/workDir/storage/getChangedLinesSince.d.ts +0 -9
- package/dist/lib/store/workDir/storage/getChangedLinesSince.d.ts.map +0 -1
- package/src/lib/store/workDir/storage/getChangedLinesSince.ts +0 -47
package/dist/http/README.md
CHANGED
|
@@ -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,
|
|
6
|
-
- fast: on a cold start, ~20k json files are usually loaded in ~2 seconds.
|
|
7
|
-
- developer friendly: when run
|
|
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
|
-
|
|
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
|
|
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 {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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,
|
|
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
|
|
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.
|
|
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
|
-
|
|
216
|
+
- `args`: object with arguments used by that query.
|
|
158
217
|
|
|
159
|
-
|
|
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 = (
|
|
166
|
-
const q = args
|
|
167
|
-
const
|
|
168
|
-
.
|
|
169
|
-
|
|
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 {
|
|
240
|
+
return { result };
|
|
179
241
|
};
|
|
180
242
|
|
|
181
|
-
module.exports =
|
|
243
|
+
module.exports.default = findContentsByTitle;
|
|
182
244
|
```
|
|
183
245
|
|
|
184
|
-
Example:
|
|
246
|
+
Example: get latest 10 articles, with optional "limit" parameter.
|
|
185
247
|
|
|
186
248
|
```javascript
|
|
187
|
-
const latestContents = (
|
|
188
|
-
const
|
|
189
|
-
.
|
|
190
|
-
.sort((a, b) => b.changed - a.changed)
|
|
191
|
-
.slice(0, args
|
|
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 {
|
|
259
|
+
return { result };
|
|
198
260
|
};
|
|
199
261
|
|
|
200
|
-
module.exports = latestContents;
|
|
262
|
+
module.exports.default = latestContents;
|
|
201
263
|
```
|
|
202
264
|
|
|
203
|
-
|
|
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:
|
|
210
|
-
|
|
211
|
-
cache:
|
|
212
|
-
|
|
271
|
+
contentType: string,
|
|
272
|
+
execTimeMs: int,
|
|
273
|
+
cache: 'miss'|'hit',
|
|
274
|
+
tags: string[],
|
|
213
275
|
}
|
|
214
276
|
},
|
|
215
277
|
```
|
|
216
278
|
|
|
217
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
232
|
-
|
|
486
|
+
// Remove content from index to rollback any change.
|
|
487
|
+
onStoreItemBeforeUpdate: ({ relativeFilepath }) => {
|
|
488
|
+
deleteContentFromIndex(relativeFilepath);
|
|
233
489
|
},
|
|
234
490
|
|
|
235
|
-
|
|
236
|
-
|
|
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 =
|
|
540
|
+
module.exports.default = listContentsByTermId;
|
|
241
541
|
```
|
|
242
542
|
|
|
243
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
587
|
+
## Dependency tags
|
|
256
588
|
|
|
257
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
597
|
+
> PLEASE NOTE that using dependency tags is a performance optimization, and should be avoided if performance is not a problem.
|
|
271
598
|
|
|
272
|
-
###
|
|
599
|
+
### Dependency tagger
|
|
273
600
|
|
|
274
|
-
|
|
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
|
-
|
|
277
|
-
- `store`
|
|
603
|
+
#### `setDependency(tag: string, dependsOnTags: string[]): void`
|
|
278
604
|
|
|
279
|
-
It
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|