@_linked/server 2.3.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @\_linked/server
2
2
 
3
+ ## 2.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#38](https://github.com/linked-fw/server/pull/38) [`bf1e83c`](https://github.com/linked-fw/server/commit/bf1e83cdfc949baec88a645c2750479bc33f1637) Thanks [@flyon](https://github.com/flyon)! - `LocalFileStore` accepts the new `SaveFileOptions` third argument of `saveFile`
8
+ (still accepting a positional mime-type string) and gains `statFile()`, which
9
+ returns the size and a sha256 of a stored file, or `null` when it does not
10
+ exist, for verify-after-upload. A local file has no headers, so `cacheControl`
11
+ and `metadata` are accepted and ignored.
12
+
13
+ Core reports an unspecified `preventDuplicates` as `undefined` and applies no
14
+ default of its own, so this store keeps applying its own: a caller that says
15
+ nothing still gets the random file-name suffix, and only an explicit
16
+ `preventDuplicates: false` overwrites an existing name.
17
+
3
18
  ## 2.3.1
4
19
 
5
20
  ### Patch Changes
@@ -7,8 +7,8 @@ import fs from 'fs/promises';
7
7
  export * from './shapes/quadstores/BackendAPIStoreProvider.js';
8
8
  export default class LincdServerBackendProvider extends BackendProvider {
9
9
  async setupBeforeControllers() {
10
- if (!LinkedFileStorage.getDefaultDataset()) {
11
- LinkedFileStorage.setDefaultDataset(new LocalFileStore(process.env.NODE_ENV + '-filestore'));
10
+ if (!LinkedFileStorage.getDefaultStore()) {
11
+ LinkedFileStorage.setDefaultStore(new LocalFileStore(process.env.NODE_ENV + '-filestore'));
12
12
  }
13
13
  let fileSystemUploadPath = path.join('data', 'filestores');
14
14
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/backend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,aAAa,CAAC;AAE7B,cAAc,gDAAgD,CAAC;AAE/D,MAAM,CAAC,OAAO,OAAO,0BAA2B,SAAQ,eAAe;IACrE,KAAK,CAAC,sBAAsB;QAC1B,IAAI,CAAC,iBAAiB,CAAC,iBAAiB,EAAE,EAAE,CAAC;YAC3C,iBAAiB,CAAC,iBAAiB,CACjC,IAAI,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,YAAY,CAAC,CACxD,CAAC;QACJ,CAAC;QACD,IAAI,oBAAoB,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAC3D,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACxC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,YAAY,CAAC,EAAE;gBAC5D,SAAS,EAAE,IAAI;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,SAAS;QACP,OAAO,aAAa,EAAE,CAAC;IACzB,CAAC;CACF"}
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/backend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,aAAa,CAAC;AAE7B,cAAc,gDAAgD,CAAC;AAE/D,MAAM,CAAC,OAAO,OAAO,0BAA2B,SAAQ,eAAe;IACrE,KAAK,CAAC,sBAAsB;QAC1B,IAAI,CAAC,iBAAiB,CAAC,eAAe,EAAE,EAAE,CAAC;YACzC,iBAAiB,CAAC,eAAe,CAC/B,IAAI,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,YAAY,CAAC,CACxD,CAAC;QACJ,CAAC;QACD,IAAI,oBAAoB,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAC3D,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACxC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,YAAY,CAAC,EAAE;gBAC5D,SAAS,EAAE,IAAI;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,SAAS;QACP,OAAO,aAAa,EAAE,CAAC;IACzB,CAAC;CACF"}
@@ -1,4 +1,4 @@
1
- import { IFileStore } from '@_linked/core/interfaces/IFileStore';
1
+ import { FileStat, IFileStore, SaveFileOptions } from '@_linked/core/interfaces/IFileStore';
2
2
  import { Shape } from '@_linked/core/shapes/Shape';
3
3
  export declare class LocalFileStore extends Shape implements IFileStore {
4
4
  private readonly basePath;
@@ -36,7 +36,26 @@ export declare class LocalFileStore extends Shape implements IFileStore {
36
36
  * Save a file to the local filesystem
37
37
  * @param filePath The path to save the file to, relative to the base upload folder
38
38
  * @param fileContent The contents of the file as a buffer
39
+ * @param options Save options, or a string read as the old positional mimeType.
40
+ * A local file has no headers to attach, so only `mimeType` (used to add a
41
+ * missing extension) and `preventDuplicates` mean anything here;
42
+ * `cacheControl` and `metadata` are accepted and ignored.
43
+ * @param preventDuplicates Only honoured when `options` is a string
39
44
  * @returns A promise that resolves to the public URL of the file
40
45
  */
41
- saveFile(filePath: string, fileContent: Buffer, mimeType?: string): Promise<string>;
46
+ saveFile(filePath: string, fileContent: Buffer, options?: SaveFileOptions | string, preventDuplicates?: boolean): Promise<string>;
47
+ /**
48
+ * Read back the metadata of a stored file, for verify-after-upload.
49
+ *
50
+ * The path is resolved exactly like getFile/fileExists/deleteFile do, against
51
+ * the base upload folder, which by default is the same folder saveFile writes
52
+ * into, so a file that was just saved can be stat'ed by its stored name.
53
+ *
54
+ * The sha256 is computed on demand from the bytes: statFile is only called
55
+ * during publish verification, not on every upload.
56
+ *
57
+ * @param filePath The path to the file, relative to the base upload folder
58
+ * @returns The size and sha256 of the file, or null if it does not exist
59
+ */
60
+ statFile(filePath: string): Promise<FileStat | null>;
42
61
  }
@@ -1,5 +1,7 @@
1
1
  import { relativeFileSystemUploadPath, getUploadTarget, } from '@_linked/server-utils/utils/Upload';
2
+ import { normalizeSaveFileOptions, } from '@_linked/core/interfaces/IFileStore';
2
3
  import { Shape } from '@_linked/core/shapes/Shape';
4
+ import { createHash } from 'node:crypto';
3
5
  import * as fsSync from 'node:fs';
4
6
  import fs from 'node:fs/promises';
5
7
  import path from 'node:path';
@@ -78,10 +80,21 @@ export class LocalFileStore extends Shape {
78
80
  * Save a file to the local filesystem
79
81
  * @param filePath The path to save the file to, relative to the base upload folder
80
82
  * @param fileContent The contents of the file as a buffer
83
+ * @param options Save options, or a string read as the old positional mimeType.
84
+ * A local file has no headers to attach, so only `mimeType` (used to add a
85
+ * missing extension) and `preventDuplicates` mean anything here;
86
+ * `cacheControl` and `metadata` are accepted and ignored.
87
+ * @param preventDuplicates Only honoured when `options` is a string
81
88
  * @returns A promise that resolves to the public URL of the file
82
89
  */
83
- async saveFile(filePath, fileContent, mimeType) {
84
- const { publicURL, targetFilePath } = getUploadTarget(filePath, mimeType, null, '', this.accessURL);
90
+ async saveFile(filePath, fileContent, options, preventDuplicates) {
91
+ var _a;
92
+ const normalized = normalizeSaveFileOptions(options, preventDuplicates);
93
+ // Core reports an unspecified preventDuplicates as undefined, leaving the
94
+ // default to each store. This one has always let getUploadTarget append its
95
+ // random suffix, so only an explicit `false` turns that off.
96
+ const suffixDuplicates = (_a = normalized.preventDuplicates) !== null && _a !== void 0 ? _a : true;
97
+ const { publicURL, targetFilePath } = getUploadTarget(filePath, normalized.mimeType, null, '', this.accessURL, suffixDuplicates);
85
98
  //make sure the target folder exists
86
99
  if (!fsSync.existsSync(path.dirname(targetFilePath))) {
87
100
  fsSync.mkdirSync(path.dirname(targetFilePath), { recursive: true });
@@ -89,5 +102,34 @@ export class LocalFileStore extends Shape {
89
102
  await fs.writeFile(targetFilePath, fileContent);
90
103
  return publicURL;
91
104
  }
105
+ /**
106
+ * Read back the metadata of a stored file, for verify-after-upload.
107
+ *
108
+ * The path is resolved exactly like getFile/fileExists/deleteFile do, against
109
+ * the base upload folder, which by default is the same folder saveFile writes
110
+ * into, so a file that was just saved can be stat'ed by its stored name.
111
+ *
112
+ * The sha256 is computed on demand from the bytes: statFile is only called
113
+ * during publish verification, not on every upload.
114
+ *
115
+ * @param filePath The path to the file, relative to the base upload folder
116
+ * @returns The size and sha256 of the file, or null if it does not exist
117
+ */
118
+ async statFile(filePath) {
119
+ const fileToStat = path.join(this.basePath, filePath);
120
+ let stat;
121
+ try {
122
+ stat = await fs.stat(fileToStat);
123
+ }
124
+ catch (err) {
125
+ return null;
126
+ }
127
+ if (!stat.isFile()) {
128
+ return null;
129
+ }
130
+ const contents = await fs.readFile(fileToStat);
131
+ const sha256 = createHash('sha256').update(contents).digest('hex');
132
+ return { size: stat.size, sha256 };
133
+ }
92
134
  }
93
135
  //# sourceMappingURL=LocalFileStore.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LocalFileStore.js","sourceRoot":"","sources":["../../../../src/shapes/filestores/LocalFileStore.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LocalFileStore.js","sourceRoot":"","sources":["../../../../src/shapes/filestores/LocalFileStore.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/linked-fw/server.git"
12
12
  },
13
- "version": "2.3.1",
13
+ "version": "2.4.0",
14
14
  "linkedPackage": true,
15
15
  "type": "module",
16
16
  "main": "lib/esm/index.js",
@@ -35,7 +35,7 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "@_linked/cli": "^1.2",
38
- "@_linked/core": "^2.18.1",
38
+ "@_linked/core": "^2.20.0",
39
39
  "@_linked/server-utils": "^1.2.0",
40
40
  "adm-zip": "^0.5.16",
41
41
  "archiver": "^7.0.1",
package/readme.md CHANGED
@@ -319,8 +319,8 @@ shape.
319
319
 
320
320
  ### LocalFileStore
321
321
 
322
- This is the default file store for LINCD. It is a simple file store that stores files in a local directory. Its usage
323
- follows the general pattern of other file stores in LINCD:
322
+ An `IFileStore` that stores files in a local directory. Its usage follows the general pattern of other file stores in
323
+ LINCD:
324
324
 
325
325
  - Define all storage locations when the server or application starts up
326
326
  - Access methods of the file store through the `LinkedFileStorage` class
@@ -330,15 +330,71 @@ It's important to note that this file store is not suitable for frontend usage -
330
330
  environment that has access to the [`fs` module](https://nodejs.org/api/fs.html).
331
331
 
332
332
  ```ts
333
- import { LinkedFileStorage } from '@_linked/core/lib/utils/LinkedFileStorage';
334
- import { LocalFileStore } from 'lincd-server/lib/shapes/LocalFileStore';
333
+ import { LinkedFileStorage } from '@_linked/core/utils/LinkedFileStorage';
334
+ import { LocalFileStore } from '@_linked/server/shapes/filestores/LocalFileStore';
335
335
  import path from 'path';
336
336
 
337
+ // the second argument is the base folder on disk; it defaults to the server's
338
+ // upload folder (`./data/uploads`, relative to the working directory)
337
339
  const pathToStore = path.join(process.cwd(), 'my-file-store');
338
340
  const store = new LocalFileStore('my-file-store', pathToStore);
339
341
  LinkedFileStorage.setDefaultStore(store);
340
342
  ```
341
343
 
344
+ `accessURL` comes from `SITE_ROOT`, and every path you pass to `saveFile`, `getFile`, `fileExists`, `deleteFile` and
345
+ `statFile` is relative to that base folder.
346
+
347
+ Registering stores per purpose (`LinkedFileStorage.setStore`, `getStore`, `registerPurpose`) is core's concern — see
348
+ [`@_linked/core`](https://github.com/linked-cm/core), `utils/LinkedFileStorage` and `interfaces/IFileStore`.
349
+
350
+ #### Saving files
351
+
352
+ `saveFile` takes core's `SaveFileOptions` as its third argument:
353
+
354
+ ```ts
355
+ await store.saveFile('report.pdf', fileBuffer, {
356
+ mimeType: 'application/pdf',
357
+ preventDuplicates: true,
358
+ });
359
+ ```
360
+
361
+ A local file has no headers to attach, so only two of the options do anything here:
362
+
363
+ - `mimeType` — used to add a missing file extension.
364
+ - `preventDuplicates` — see below.
365
+ - `cacheControl` and `metadata` are accepted (so the same call works against an S3-backed store) and ignored.
366
+
367
+ The old positional form still works: a plain string third argument is read as the mime type, with `preventDuplicates`
368
+ following it.
369
+
370
+ ```ts
371
+ await store.saveFile('report.pdf', fileBuffer, 'application/pdf', true);
372
+ ```
373
+
374
+ ##### LocalFileStore suffixes by default
375
+
376
+ `preventDuplicates` left unspecified means `true` **here**. A second save of the same name does **not** overwrite the
377
+ first: the store appends a random suffix, so `report.pdf` becomes `report_a1b2c3.pdf`. Only an explicit `false`
378
+ overwrites.
379
+
380
+ Core supplies no default of its own — an unspecified `preventDuplicates` reaches the store as `undefined` and each store
381
+ decides. Do not assume this store's choice holds elsewhere: `S3FileStore` in `@_linked/s3` overwrites by default.
382
+
383
+ #### Reading file metadata
384
+
385
+ `statFile(filePath)` returns `{size, sha256}` for a stored file, or `null` when it does not exist (or is not a regular
386
+ file). The `sha256` is a hex digest computed from the file's bytes on demand, so a caller can verify an upload it just
387
+ made:
388
+
389
+ ```ts
390
+ const stat = await store.statFile('report.pdf');
391
+ if (stat === null) throw new Error('upload went missing');
392
+ if (stat.sha256 !== expectedSha256) throw new Error('content mismatch');
393
+ ```
394
+
395
+ `statFile` is optional on `IFileStore`, so a caller holding an `IFileStore` rather than a `LocalFileStore` must check
396
+ that it exists before calling it.
397
+
342
398
  ## TODO
343
399
 
344
400
  - [ ] Add tests