@_linked/core 2.18.1 → 2.20.1

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 (47) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +104 -3
  3. package/lib/esm/index.d.ts +3 -0
  4. package/lib/esm/index.js +1 -0
  5. package/lib/esm/index.js.map +1 -1
  6. package/lib/esm/interfaces/IDataset.d.ts +23 -0
  7. package/lib/esm/interfaces/IFileStore.d.ts +64 -1
  8. package/lib/esm/interfaces/IFileStore.js +21 -1
  9. package/lib/esm/interfaces/IFileStore.js.map +1 -1
  10. package/lib/esm/queries/CountBuilder.d.ts +69 -0
  11. package/lib/esm/queries/CountBuilder.js +162 -0
  12. package/lib/esm/queries/CountBuilder.js.map +1 -0
  13. package/lib/esm/queries/CountQuery.d.ts +80 -0
  14. package/lib/esm/queries/CountQuery.js +7 -0
  15. package/lib/esm/queries/CountQuery.js.map +1 -0
  16. package/lib/esm/queries/IntermediateRepresentation.d.ts +31 -1
  17. package/lib/esm/queries/QueryBuilder.d.ts +80 -0
  18. package/lib/esm/queries/QueryBuilder.js +98 -13
  19. package/lib/esm/queries/QueryBuilder.js.map +1 -1
  20. package/lib/esm/queries/fromJSON.d.ts +5 -3
  21. package/lib/esm/queries/fromJSON.js +4 -1
  22. package/lib/esm/queries/fromJSON.js.map +1 -1
  23. package/lib/esm/queries/lower.d.ts +17 -2
  24. package/lib/esm/queries/lower.js +61 -0
  25. package/lib/esm/queries/lower.js.map +1 -1
  26. package/lib/esm/queries/queryDispatch.d.ts +35 -0
  27. package/lib/esm/queries/queryDispatch.js +36 -0
  28. package/lib/esm/queries/queryDispatch.js.map +1 -1
  29. package/lib/esm/shapes/Shape.d.ts +32 -0
  30. package/lib/esm/shapes/Shape.js +47 -0
  31. package/lib/esm/shapes/Shape.js.map +1 -1
  32. package/lib/esm/sparql/SparqlDataset.d.ts +11 -0
  33. package/lib/esm/sparql/SparqlDataset.js +19 -2
  34. package/lib/esm/sparql/SparqlDataset.js.map +1 -1
  35. package/lib/esm/sparql/index.d.ts +3 -3
  36. package/lib/esm/sparql/index.js +3 -3
  37. package/lib/esm/sparql/index.js.map +1 -1
  38. package/lib/esm/sparql/irToAlgebra.d.ts +32 -1
  39. package/lib/esm/sparql/irToAlgebra.js +74 -0
  40. package/lib/esm/sparql/irToAlgebra.js.map +1 -1
  41. package/lib/esm/sparql/resultMapping.d.ts +14 -1
  42. package/lib/esm/sparql/resultMapping.js +40 -0
  43. package/lib/esm/sparql/resultMapping.js.map +1 -1
  44. package/lib/esm/utils/LinkedFileStorage.d.ts +96 -3
  45. package/lib/esm/utils/LinkedFileStorage.js +164 -12
  46. package/lib/esm/utils/LinkedFileStorage.js.map +1 -1
  47. package/package.json +3 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.20.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#235](https://github.com/linked-fw/core/pull/235) [`19624f7`](https://github.com/linked-fw/core/commit/19624f7bb7b1d83bd99d9797a7da41fe7e09432b) Thanks [@flyon](https://github.com/flyon)! - Point `repository.url` at the linked-fw organisation, so npm provenance verification matches the repository that builds the package.
8
+
9
+ ## 2.20.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#230](https://github.com/linked-fw/core/pull/230) [`bf8df0c`](https://github.com/linked-fw/core/commit/bf8df0c043cdb588e4b9f8baf97f87b340f0078b) Thanks [@flyon](https://github.com/flyon)! - Purpose-named file stores, save options and `statFile`.
14
+
15
+ - `LinkedFileStorage` gains a purpose registry: `registerPurpose`, `setStore`,
16
+ `getStore`, `hasStore`, `listPurposes` and `accessURLFor`. `getStore(purpose)`
17
+ returns the store configured for that purpose; a purpose that is registered
18
+ but not configured falls back to the default store (noted once with
19
+ `console.debug`); an unregistered
20
+ purpose throws, listing the known purposes, so a typo cannot silently write to
21
+ the wrong store. The well-known purposes `uploads` and `appAssets` are exported
22
+ as `FileStorePurposes` and registered at module load. `uploads` is an ordinary
23
+ purpose: `setDefaultStore` configures no purpose of its own, so `uploads`
24
+ resolves to the default store as a fallback, `hasStore('uploads')` is true only
25
+ after an explicit `setStore('uploads', store)`, and that call has the same
26
+ effect whether it runs before or after `setDefaultStore`.
27
+ - `getDefaultDataset`/`setDefaultDataset` are renamed to
28
+ `getDefaultStore`/`setDefaultStore`. The old names remain as `@deprecated`
29
+ aliases that forward, so no caller has to change; they will be dropped in the
30
+ next major.
31
+ - `IFileStore.saveFile` now takes `SaveFileOptions` (`mimeType`, `cacheControl`,
32
+ `metadata`, `preventDuplicates`) as its third argument, still accepting a
33
+ `string` there as the old positional `mimeType`. `normalizeSaveFileOptions` is
34
+ exported for implementations; it reports `preventDuplicates` as `undefined`
35
+ when the caller did not specify one — there is no core-wide default, so each
36
+ store keeps applying its own (`S3FileStore` overwrites, `LocalFileStore` adds a
37
+ random suffix). `options.preventDuplicates` wins over the positional argument.
38
+ `LinkedFileStorage.saveFile` likewise forwards an unspecified
39
+ `preventDuplicates` as `undefined` rather than `false`, so a
40
+ `saveFile(path, bytes)` call keeps behaving exactly as before.
41
+ - `IFileStore` gains an optional `statFile(filePath): Promise<FileStat | null>`
42
+ for verify-after-upload. Existing implementations stay valid.
43
+
44
+ ## 2.19.1
45
+
46
+ ### Patch Changes
47
+
48
+ - [#231](https://github.com/linked-fw/core/pull/231) [`58a5b7a`](https://github.com/linked-fw/core/commit/58a5b7af7332584e9f40c16d17e303f18d86962a) Thanks [@flyon](https://github.com/flyon)! - Declare npm as the package manager for this repo and mark `package-lock.json` as a generated file.
49
+
50
+ ## 2.19.0
51
+
52
+ ### Minor Changes
53
+
54
+ - [#224](https://github.com/linked-cm/core/pull/224) [`ea9971c`](https://github.com/linked-cm/core/commit/ea9971c6201f610102ad7674868602e1aaeb4e1f) Thanks [@flyon](https://github.com/flyon)! - Adds a root-level count to the query DSL: `SelectBuilder.from(shape).where(…).count()` and
55
+ `Shape.count()` resolve to a real `number`, lowering to `SELECT (COUNT(DISTINCT ?s) AS ?count)
56
+ WHERE { … }`. Like an ask, a count is its own builder and IR kind, so `limit`/`offset` are dropped at
57
+ the boundary and unrepresentable thereafter — the count of a window is the count of the whole match
58
+ set. `.toCount()` is public so a router can forward the `{op: 'count'}` envelope instead of executing
59
+ it.
60
+
61
+ `IDataset.countQuery` is **optional**, so nothing breaks: every store extending `SparqlDataset` gets
62
+ it with no edit. A store or router that does not extend `SparqlDataset` — including any
63
+ `setQueryDispatch({…})` object literal in a consuming package — needs a `countQuery` arm added by
64
+ hand before `.count()` works against it; until then it fails loudly, naming the method.
65
+
3
66
  ## 2.18.1
4
67
 
5
68
  ### Patch Changes
package/README.md CHANGED
@@ -362,6 +362,86 @@ LinkedStorage.setDefaultDataset(stores.appData);
362
362
 
363
363
  Each store class must accept a single config-object argument (`new StoreClass(config)`). `@_linked/fuseki`'s `FusekiStore` and `@_linked/server`'s `BackendAPIStore` both follow this contract.
364
364
 
365
+ ## File storage
366
+
367
+ `LinkedStorage` routes *quads*; `LinkedFileStorage` routes *files*. Files are addressed by **purpose** — a short string naming what the files are for (`uploads`, `appAssets`, …) — so an app can send release bundles to a CDN bucket and user uploads to another store without any caller knowing which.
368
+
369
+ ```ts
370
+ import {LinkedFileStorage, FileStorePurposes} from '@_linked/core';
371
+ import {S3FileStore} from '@_linked/s3';
372
+
373
+ LinkedFileStorage.setDefaultStore(new S3FileStore({/* … */})); // fallback for every purpose
374
+ LinkedFileStorage.setStore(FileStorePurposes.appAssets, new S3FileStore({/* cdn */}));
375
+
376
+ await LinkedFileStorage.getStore(FileStorePurposes.appAssets).saveFile('/app.js', bytes);
377
+ LinkedFileStorage.accessURLFor(FileStorePurposes.appAssets); // → https://cdn.example.com
378
+ ```
379
+
380
+ ### Resolution rule
381
+
382
+ `getStore(purpose)` resolves in one order, and there is no fourth case:
383
+
384
+ 1. **configured** — a store was passed to `setStore(purpose, store)` → that store;
385
+ 2. **registered but not configured** → the default store, with a one-time `console.debug` line saying the purpose has no dedicated store (the normal case for a single-store app; it throws instead if no default store is set);
386
+ 3. **not registered** → throws, listing the known purposes. A typo like `'appAsset'` must not silently write release bundles into the uploads store.
387
+
388
+ `uploads` is an ordinary purpose with no special case: `setDefaultStore` configures *no* purpose, so `uploads` reaches the default store through the fallback in step 2, and `setStore('uploads', other)` wins whether it is called before or after `setDefaultStore`.
389
+
390
+ | Method | Meaning |
391
+ | --- | --- |
392
+ | `registerPurpose(purpose, {description?})` | Declare a purpose without configuring a store. |
393
+ | `setStore(purpose, store)` | Configure (and register) the store; calls `store.init()`. |
394
+ | `getStore(purpose)` | Resolve per the rule above. A **live reference** — call it per operation if config can change. |
395
+ | `hasStore(purpose)` | Whether a store was *explicitly configured*. Ignores the fallback, so it is `false` while `getStore` still works. Never use it as a guard before `getStore`. |
396
+ | `listPurposes()` | `{purpose, description?, configured}` for every declared purpose. |
397
+ | `accessURLFor(purpose)` | The `accessURL` of the resolved store. |
398
+ | `getDefaultStore()` / `setDefaultStore(store)` | The fallback store. `getDefaultDataset`/`setDefaultDataset` are `@deprecated` aliases that forward, kept until the next major. |
399
+
400
+ ### Introducing a purpose from a package
401
+
402
+ Export the constant and register it **in the same module**, so importing the constant *is* the registration and load order resolves itself:
403
+
404
+ ```ts
405
+ // @_linked/captures/purposes.ts
406
+ import {LinkedFileStorage} from '@_linked/core';
407
+
408
+ export const CapturesPurpose = 'captures';
409
+ LinkedFileStorage.registerPurpose(CapturesPurpose, {description: '3D capture artifacts.'});
410
+ ```
411
+
412
+ A consumer that imports `CapturesPurpose` can call `getStore(CapturesPurpose)` immediately; an app that never splits it out simply gets the default store. Core ships only `FileStorePurposes.uploads` and `FileStorePurposes.appAssets` — a new purpose belongs in the package that owns the concept.
413
+
414
+ ### Saving files: `SaveFileOptions`
415
+
416
+ ```ts
417
+ await store.saveFile('/app.js', bytes, {
418
+ mimeType: 'application/javascript',
419
+ cacheControl: 'public, max-age=31536000, immutable',
420
+ metadata: {release: '1.2.3'},
421
+ preventDuplicates: true, // rename instead of overwriting when the path is taken
422
+ });
423
+
424
+ // Backwards compatible: a string third argument is read as the positional mimeType.
425
+ await store.saveFile('/photo.webp', bytes, 'image/webp', true);
426
+ ```
427
+
428
+ `options.preventDuplicates` wins over the positional fourth argument. Implementations should start with `normalizeSaveFileOptions(options, preventDuplicates)`, which collapses both forms into one object.
429
+
430
+ > **There is no core-wide `preventDuplicates` default.** When a caller does not specify one it stays `undefined` all the way to the store, and **each store applies its own**: `LocalFileStore` adds a random suffix, `S3FileStore` overwrites. Pass it explicitly whenever the behaviour matters — never assume a default.
431
+
432
+ ### Optional `statFile`
433
+
434
+ ```ts
435
+ const stat = await store.statFile?.('/app.js'); // FileStat | null | undefined
436
+ if (!stat) {
437
+ // Store cannot report metadata, or the file is absent — skip verification.
438
+ } else if (stat.sha256) {
439
+ // Only a real content hash verifies. `etag` is not one on S3.
440
+ }
441
+ ```
442
+
443
+ `statFile` is optional by design: a store without it still publishes. A caller must treat a missing method (`undefined`) or a missing `sha256` as "cannot verify" and continue, rather than failing the upload or falling back to `etag`.
444
+
365
445
  ## Automatic data validation
366
446
 
367
447
  SHACL shapes are ideal for data validation. Linked generates SHACL shapes from your TypeScript Shape classes, which you can sync to your store for schema-level validation. When your store enforces those shapes at runtime, you get both schema validation and runtime enforcement for extra safety.
@@ -381,7 +461,7 @@ The query DSL is schema-parameterized: you define your own SHACL shapes, and Lin
381
461
  - `and(...)` / `or(...)` combinations
382
462
  - Set filtering with `some(...)` / `every(...)` (and implicit `some`)
383
463
  - Outer `where(...)` chaining
384
- - Counting with `.size()`
464
+ - Counting: `.count()` for how many instances match, `.size()` for a property's value count
385
465
  - Custom result formats (object mapping)
386
466
  - Computed values — derive new fields with arithmetic, string, date, and comparison methods
387
467
  - Expression-based WHERE filters (`p.name.strlen().gt(5)`)
@@ -487,12 +567,33 @@ const outer = await Person.select((p) => p.knows).where((p) =>
487
567
  );
488
568
  ```
489
569
 
490
- #### Counting (size)
570
+ #### Counting
571
+
572
+ Two different questions, two different queries.
573
+
574
+ `.count()` answers **how many instances match** — one number for the whole match set. It respects
575
+ `where` and `minus`, and ignores `limit`/`offset`, so a paged table can ask for its total row count
576
+ beside the page it is showing:
577
+
578
+ ```typescript
579
+ /* Result: number */
580
+ const total = await Person.count();
581
+ const matching = await Person.select().where((p) => p.name.equals('Semmy')).count();
582
+
583
+ // Lowers to: SELECT (COUNT(DISTINCT ?a0) AS ?count) WHERE { ?a0 a Person ; name ?n . FILTER(…) }
584
+ ```
585
+
586
+ `.size()` answers **how many values a property has**, per row:
587
+
491
588
  ```typescript
492
589
  /* Result: Array<{id: string; knows: number}> */
493
- const count = await Person.select((p) => p.knows.size());
590
+ const perPerson = await Person.select((p) => p.knows.size());
494
591
  ```
495
592
 
593
+ A count resolves to a real number and **rejects on failure** — it never reports an unreachable store
594
+ as `0`. `.toCount()` gives you the count query itself, for a router that forwards
595
+ (`builder.toCount().toJSON()`) rather than executes.
596
+
496
597
  #### Custom result formats
497
598
  ```typescript
498
599
  /* Result: Array<{id: string; nameIsMoa: boolean; numFriends: number}> */
@@ -20,6 +20,9 @@ export type { QueryBuilderJSON } from './queries/QueryBuilder.js';
20
20
  export { AskBuilder, isAskQuery } from './queries/AskBuilder.js';
21
21
  export type { AskSpec } from './queries/AskBuilder.js';
22
22
  export type { AskQuery, AskQueryJSON, RawAskInput } from './queries/AskQuery.js';
23
+ export { CountBuilder, isCountQuery } from './queries/CountBuilder.js';
24
+ export type { CountSpec } from './queries/CountBuilder.js';
25
+ export type { CountQuery, CountQueryJSON, RawCountInput } from './queries/CountQuery.js';
23
26
  export { CreateBuilder } from './queries/CreateBuilder.js';
24
27
  export { UpdateBuilder } from './queries/UpdateBuilder.js';
25
28
  export { DeleteBuilder } from './queries/DeleteBuilder.js';
package/lib/esm/index.js CHANGED
@@ -22,6 +22,7 @@ export { PropertyPath, walkPropertyPath } from './queries/PropertyPath.js';
22
22
  export { FieldSet } from './queries/FieldSet.js';
23
23
  // Phase 3b — Mutation builders
24
24
  export { AskBuilder, isAskQuery } from './queries/AskBuilder.js';
25
+ export { CountBuilder, isCountQuery } from './queries/CountBuilder.js';
25
26
  export { CreateBuilder } from './queries/CreateBuilder.js';
26
27
  export { UpdateBuilder } from './queries/UpdateBuilder.js';
27
28
  export { DeleteBuilder } from './queries/DeleteBuilder.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAC,UAAU,EAAE,SAAS,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAC,OAAO,EAAC,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAC,uBAAuB,EAAC,MAAM,qCAAqC,CAAC;AAC5E,sEAAsE;AACtE,OAAO,EAAC,KAAK,EAAC,MAAM,mBAAmB,CAAC;AACxC,kEAAkE;AAClE,OAAO,EAAC,QAAQ,EAAE,WAAW,EAAE,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAQnF,OAAO,EAAC,aAAa,EAAC,MAAM,0BAA0B,CAAC;AACvD,2CAA2C;AAC3C,OAAO,EAAC,aAAa,EAAE,YAAY,EAAC,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAC,KAAK,EAAC,MAAM,oBAAoB,CAAC;AAEzC,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAE/C,OAAO,EACL,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAC,YAAY,EAAE,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAEzE,sBAAsB;AACtB,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAM/C,+BAA+B;AAC/B,OAAO,EAAC,UAAU,EAAE,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAG/D,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AAGzD,8CAA8C;AAC9C,OAAO,EAAC,cAAc,EAAC,MAAM,iCAAiC,CAAC;AAE/D,OAAO,EAAC,IAAI,EAAC,MAAM,uBAAuB,CAAC;AAe3C,sEAAsE;AACtE,OAAO,EACL,cAAc,EACd,WAAW,GACZ,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,cAAc,EACd,iBAAiB,GAClB,MAAM,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAC,UAAU,EAAE,SAAS,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAC,OAAO,EAAC,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAC,uBAAuB,EAAC,MAAM,qCAAqC,CAAC;AAC5E,sEAAsE;AACtE,OAAO,EAAC,KAAK,EAAC,MAAM,mBAAmB,CAAC;AACxC,kEAAkE;AAClE,OAAO,EAAC,QAAQ,EAAE,WAAW,EAAE,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAQnF,OAAO,EAAC,aAAa,EAAC,MAAM,0BAA0B,CAAC;AACvD,2CAA2C;AAC3C,OAAO,EAAC,aAAa,EAAE,YAAY,EAAC,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAC,KAAK,EAAC,MAAM,oBAAoB,CAAC;AAEzC,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAE/C,OAAO,EACL,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAC,YAAY,EAAE,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAEzE,sBAAsB;AACtB,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAM/C,+BAA+B;AAC/B,OAAO,EAAC,UAAU,EAAE,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAG/D,OAAO,EAAC,YAAY,EAAE,YAAY,EAAC,MAAM,2BAA2B,CAAC;AAGrE,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AAGzD,8CAA8C;AAC9C,OAAO,EAAC,cAAc,EAAC,MAAM,iCAAiC,CAAC;AAE/D,OAAO,EAAC,IAAI,EAAC,MAAM,uBAAuB,CAAC;AAe3C,sEAAsE;AACtE,OAAO,EACL,cAAc,EACd,WAAW,GACZ,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,cAAc,EACd,iBAAiB,GAClB,MAAM,gCAAgC,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import type { SelectQuery } from '../queries/SelectQuery.js';
2
2
  import type { AskQuery } from '../queries/AskQuery.js';
3
+ import type { CountQuery } from '../queries/CountQuery.js';
3
4
  import type { CreateQuery } from '../queries/CreateQuery.js';
4
5
  import type { UpdateQuery } from '../queries/UpdateQuery.js';
5
6
  import type { DeleteQuery, DeleteResponse } from '../queries/DeleteQuery.js';
@@ -42,6 +43,28 @@ export interface IDataset {
42
43
  * API was built to remove.
43
44
  */
44
45
  askQuery(query: AskQuery): Promise<boolean>;
46
+ /**
47
+ * Count the matching instances — a number, not a result set.
48
+ *
49
+ * **Optional**, unlike {@link askQuery}. Adding a required method would break
50
+ * every existing implementer at compile time; `resolveCount` in `queryDispatch`
51
+ * turns a missing implementation into a precise runtime error instead. Every
52
+ * store extending {@link SparqlDataset} gets it with no edit.
53
+ *
54
+ * A {@link CountQuery} carries a pattern and nothing else: no projection, no
55
+ * sorting, and in particular no pagination — a count of a windowed query is
56
+ * meaningless, so the type cannot hold a window. A SPARQL-backed store emits
57
+ * `SELECT (COUNT(DISTINCT ?s) AS ?count) WHERE { … }`; another backend answers it
58
+ * however it can. This package contains **no path that rewrites a count as a
59
+ * select** and measures the array: that would hide an unbounded read behind a
60
+ * call that looks cheap.
61
+ *
62
+ * Must resolve to a real, non-negative integer — a non-number is rejected, not
63
+ * coerced. Must reject on failure: reporting an unreachable store as `0` renders
64
+ * an empty table that is indistinguishable from real data, which is the failure
65
+ * mode this API was built to remove.
66
+ */
67
+ countQuery?(query: CountQuery): Promise<number>;
45
68
  /**
46
69
  * Receives update AND upsert mutations — `lower(query)` yields `kind: 'update'`,
47
70
  * `'update_where'` or `'upsert'`. An implementation that does not handle `'upsert'`
@@ -1,4 +1,38 @@
1
1
  import type { Readable } from 'stream';
2
+ /**
3
+ * Options for `IFileStore.saveFile`.
4
+ *
5
+ * Passed as the third argument. A plain `string` is still accepted there and is
6
+ * read as the old positional `mimeType`, so existing callers keep working.
7
+ */
8
+ export interface SaveFileOptions {
9
+ /** Content type of the stored bytes, e.g. `image/webp`. */
10
+ mimeType?: string;
11
+ /** Cache header the store should attach, e.g. `public, max-age=31536000, immutable`. */
12
+ cacheControl?: string;
13
+ /** Store-level user metadata, when the backing store supports it. */
14
+ metadata?: Record<string, string>;
15
+ /**
16
+ * Rename instead of overwriting when the path is already taken.
17
+ *
18
+ * Left `undefined` when the caller did not say: each store then applies its
19
+ * own default (`S3FileStore` overwrites, `LocalFileStore` adds a suffix).
20
+ */
21
+ preventDuplicates?: boolean;
22
+ }
23
+ /**
24
+ * Metadata of a stored file, for verify-after-upload.
25
+ *
26
+ * `sha256` is only set when the store can actually report a content hash; a
27
+ * caller that does not get one must treat the file as "cannot verify" rather
28
+ * than falling back to `etag`, which is not a content hash on S3.
29
+ */
30
+ export interface FileStat {
31
+ size: number;
32
+ /** Only when the store can report it. */
33
+ sha256?: string;
34
+ etag?: string;
35
+ }
2
36
  export interface IFileStore {
3
37
  /**
4
38
  * The base URL to access the filestore's files. For example:
@@ -12,5 +46,34 @@ export interface IFileStore {
12
46
  fileExists(filePath: string): Promise<boolean>;
13
47
  getFile(filePath: string): Promise<Buffer | null>;
14
48
  listFiles(prefix?: string): Promise<string[]>;
15
- saveFile(filePath: string, fileContent: string | Uint8Array | Buffer | Readable, mimeType?: string, preventDuplicates?: boolean): Promise<string | null>;
49
+ saveFile(filePath: string, fileContent: string | Uint8Array | Buffer | Readable,
50
+ /** `SaveFileOptions`, or a `string` read as the old positional `mimeType`. */
51
+ options?: SaveFileOptions | string,
52
+ /** Only honoured when `options` is a string; otherwise use `options.preventDuplicates`. */
53
+ preventDuplicates?: boolean): Promise<string | null>;
54
+ /**
55
+ * Optional. Metadata of a stored file, for verify-after-upload.
56
+ * Resolves to `null` when the file does not exist.
57
+ *
58
+ * Optional by design (plan 011 D7): a store without it still publishes, and
59
+ * the caller simply skips verification.
60
+ */
61
+ statFile?(filePath: string): Promise<FileStat | null>;
16
62
  }
63
+ /**
64
+ * Normalise `saveFile`'s widened third argument into a single options object.
65
+ * Every `IFileStore` implementation should call this first, so the positional
66
+ * mime-type form and the options form cannot drift apart.
67
+ *
68
+ * `preventDuplicates` stays `undefined` when neither `options.preventDuplicates`
69
+ * nor the positional argument was given: "unspecified" travels end to end and
70
+ * **each store applies its own default** (`S3FileStore` overwrites,
71
+ * `LocalFileStore` adds a random suffix). This helper must never invent `false`,
72
+ * or a two-argument `saveFile(path, bytes)` would silently start overwriting.
73
+ *
74
+ * `options.preventDuplicates` wins over the positional argument when both are
75
+ * given.
76
+ */
77
+ export declare function normalizeSaveFileOptions(options?: SaveFileOptions | string, preventDuplicates?: boolean): SaveFileOptions & {
78
+ preventDuplicates?: boolean | undefined;
79
+ };
@@ -1,2 +1,22 @@
1
- export {};
1
+ /**
2
+ * Normalise `saveFile`'s widened third argument into a single options object.
3
+ * Every `IFileStore` implementation should call this first, so the positional
4
+ * mime-type form and the options form cannot drift apart.
5
+ *
6
+ * `preventDuplicates` stays `undefined` when neither `options.preventDuplicates`
7
+ * nor the positional argument was given: "unspecified" travels end to end and
8
+ * **each store applies its own default** (`S3FileStore` overwrites,
9
+ * `LocalFileStore` adds a random suffix). This helper must never invent `false`,
10
+ * or a two-argument `saveFile(path, bytes)` would silently start overwriting.
11
+ *
12
+ * `options.preventDuplicates` wins over the positional argument when both are
13
+ * given.
14
+ */
15
+ export function normalizeSaveFileOptions(options, preventDuplicates) {
16
+ var _a;
17
+ if (typeof options === 'string') {
18
+ return { mimeType: options, preventDuplicates };
19
+ }
20
+ return Object.assign(Object.assign({}, (options !== null && options !== void 0 ? options : {})), { preventDuplicates: (_a = options === null || options === void 0 ? void 0 : options.preventDuplicates) !== null && _a !== void 0 ? _a : preventDuplicates });
21
+ }
2
22
  //# sourceMappingURL=IFileStore.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"IFileStore.js","sourceRoot":"","sources":["../../../src/interfaces/IFileStore.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"IFileStore.js","sourceRoot":"","sources":["../../../src/interfaces/IFileStore.ts"],"names":[],"mappings":"AA4EA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAkC,EAClC,iBAA2B;;IAE3B,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,EAAC,QAAQ,EAAE,OAAO,EAAE,iBAAiB,EAAC,CAAC;IAChD,CAAC;IACD,uCACK,CAAC,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,EAAE,CAAC,KAClB,iBAAiB,EAAE,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,iBAAiB,mCAAI,iBAAiB,IAClE;AACJ,CAAC"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * `CountBuilder` — a query whose answer is a number.
3
+ *
4
+ * A small sibling of `SelectBuilder` rather than a mode of it, for the same reason
5
+ * {@link AskBuilder} is: it can only hold a pattern (shape, subject(s), where,
6
+ * minus), so the normalisation a count needs is a property of the *type*. There is
7
+ * no projection, ordering or **pagination** to drop, because none can be set — and
8
+ * a count of a windowed query is meaningless, so that is the point.
9
+ *
10
+ * Deliberately **non-generic**. `SelectBuilder<S, R, Result>` is heavily inferred;
11
+ * a count answers `number` no matter what the select's projection was, so this type
12
+ * takes part in none of that inference and cannot perturb it.
13
+ */
14
+ import type { ShapeConstructor } from '../shapes/Shape.js';
15
+ import type { WherePath } from './SelectQuery.js';
16
+ import type { RawMinusEntry } from './IRDesugar.js';
17
+ import type { NodeShapeData } from '../shapes/SHACL.js';
18
+ import type { NodeReferenceValue } from './QueryFactory.js';
19
+ import type { IDataset } from '../interfaces/IDataset.js';
20
+ import { PendingQueryContext } from './QueryContext.js';
21
+ import type { CountQuery, CountQueryJSON, RawCountInput } from './CountQuery.js';
22
+ /**
23
+ * Everything a count can carry. Assembled by `SelectBuilder.toCount()` or
24
+ * `Shape.count()`.
25
+ *
26
+ * `shapeClass` is required — a shapeless count would count every node in the store.
27
+ */
28
+ export type CountSpec = {
29
+ shapeClass: ShapeConstructor<any>;
30
+ subject?: NodeReferenceValue | PendingQueryContext;
31
+ subjects?: NodeReferenceValue[];
32
+ where?: WherePath;
33
+ minusEntries?: RawMinusEntry[];
34
+ /** `.for(null)` was called — there is no subject to count. */
35
+ nullSubject?: boolean;
36
+ };
37
+ export declare class CountBuilder implements PromiseLike<number>, Promise<number> {
38
+ private readonly _spec;
39
+ private constructor();
40
+ /** Build from an assembled spec — used by `.toCount()` and `Shape.count()`. */
41
+ static of(spec: CountSpec): CountBuilder;
42
+ /** Discriminator for the free `lower()` function and dataset routing. */
43
+ readonly __queryKind: "count";
44
+ /** The shape whose matching instances are counted — also the routing key. */
45
+ get shape(): NodeShapeData;
46
+ toRawInput(): RawCountInput;
47
+ toJSON(): CountQueryJSON;
48
+ static fromJSON(json: CountQueryJSON): CountBuilder;
49
+ /**
50
+ * Execute and resolve to a real number.
51
+ *
52
+ * **A failure rejects — it is never reported as `0`.** A count of `0` renders an
53
+ * empty table and is indistinguishable from real data, so a broken count that
54
+ * answered `0` would hide rather than fail. Do not wrap this in `.catch(() => 0)`.
55
+ *
56
+ * The one case that resolves `0` without querying is a query with no subject to
57
+ * count — `.for(null)`, or a `PendingQueryContext` as the subject that has not
58
+ * landed: "how many nodes with no id match?" has a correct total answer, and it
59
+ * is `0`.
60
+ */
61
+ exec(target?: IDataset): Promise<number>;
62
+ /** `await` triggers execution. */
63
+ then<TResult1 = number, TResult2 = never>(onfulfilled?: ((value: number) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
64
+ catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<number | TResult>;
65
+ finally(onfinally?: (() => void) | null): Promise<number>;
66
+ get [Symbol.toStringTag](): string;
67
+ }
68
+ /** Narrow an unknown query object to a count. */
69
+ export declare function isCountQuery(query: unknown): query is CountQuery;
@@ -0,0 +1,162 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
7
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
8
+ return new (P || (P = Promise))(function (resolve, reject) {
9
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
10
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
11
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
12
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
13
+ });
14
+ };
15
+ import { resolveShape } from './resolveShape.js';
16
+ import { PendingQueryContext } from './QueryContext.js';
17
+ import { encodeContextRef, isContextRefJSON } from './ContextRef.js';
18
+ import { WIRE_VERSION, assertWireVersion } from './wireVersion.js';
19
+ import { getQueryDispatch, resolveCount } from './queryDispatch.js';
20
+ import { serializeWherePath, serializeRawMinusEntry, deserializeWherePath, deserializeRawMinusEntry, } from './QueryBuilderSerialization.js';
21
+ export class CountBuilder {
22
+ constructor(spec) {
23
+ /** Discriminator for the free `lower()` function and dataset routing. */
24
+ this.__queryKind = 'count';
25
+ this._spec = spec;
26
+ }
27
+ /** Build from an assembled spec — used by `.toCount()` and `Shape.count()`. */
28
+ static of(spec) {
29
+ return new CountBuilder(spec);
30
+ }
31
+ /** The shape whose matching instances are counted — also the routing key. */
32
+ get shape() {
33
+ return this._spec.shapeClass.shape;
34
+ }
35
+ toRawInput() {
36
+ const { shapeClass, subject, subjects, where, minusEntries, nullSubject } = this._spec;
37
+ const input = { shape: shapeClass };
38
+ // Carried so lowering can reject it. `exec()` answers `0` before dispatching,
39
+ // but a store handed the builder directly (e.g. after `fromJSON`) would
40
+ // otherwise lower a subject-less query that matches every instance of the
41
+ // shape and report that as the count of one node.
42
+ if (nullSubject)
43
+ input.nullSubject = true;
44
+ if (subject)
45
+ input.subject = subject;
46
+ if (subjects && subjects.length > 0)
47
+ input.subjects = subjects;
48
+ if (where)
49
+ input.where = where;
50
+ if (minusEntries && minusEntries.length > 0)
51
+ input.minusEntries = minusEntries;
52
+ return input;
53
+ }
54
+ toJSON() {
55
+ var _a;
56
+ const { shapeClass, subject, subjects, where, minusEntries, nullSubject } = this._spec;
57
+ const shapeId = (_a = shapeClass.shape) === null || _a === void 0 ? void 0 : _a.id;
58
+ if (!shapeId) {
59
+ // Refuse here rather than emitting `shape: ''` for the receiver's `fromJSON`
60
+ // to reject: the caller who holds the shape can act on this, and a peer
61
+ // across a wire cannot.
62
+ throw new Error('Cannot serialize a count query whose shape has no id. A count envelope must ' +
63
+ 'name a shape — a shapeless count would count every node in the store.');
64
+ }
65
+ const json = {
66
+ v: WIRE_VERSION,
67
+ op: 'count',
68
+ shape: shapeId,
69
+ };
70
+ if (subject instanceof PendingQueryContext) {
71
+ // Carry the reference, not its (possibly unresolved) id, so the receiver
72
+ // resolves it against its own context map at lowering time.
73
+ json.subject = encodeContextRef(subject.contextName);
74
+ }
75
+ else if (subject && typeof subject === 'object' && 'id' in subject) {
76
+ json.subject = subject.id;
77
+ }
78
+ if (subjects && subjects.length > 0) {
79
+ json.subjects = subjects.map((s) => s.id);
80
+ }
81
+ if (where) {
82
+ json.where = serializeWherePath(where, shapeClass.shape);
83
+ }
84
+ if (minusEntries && minusEntries.length > 0) {
85
+ json.minusEntries = minusEntries.map((e) => serializeRawMinusEntry(e, shapeClass.shape));
86
+ }
87
+ if (nullSubject) {
88
+ json.nullSubject = true;
89
+ }
90
+ return json;
91
+ }
92
+ static fromJSON(json) {
93
+ var _a;
94
+ assertWireVersion(json.v);
95
+ if (!json.shape) {
96
+ throw new Error('A count envelope must name a `shape`. A shapeless count would count every ' +
97
+ 'node in the store, under any type or none.');
98
+ }
99
+ const shapeClass = resolveShape(json.shape);
100
+ const spec = { shapeClass };
101
+ if (json.subject !== undefined) {
102
+ spec.subject = isContextRefJSON(json.subject)
103
+ ? new PendingQueryContext(json.subject['@ctx'])
104
+ : { id: json.subject };
105
+ }
106
+ if (json.subjects && json.subjects.length > 0) {
107
+ spec.subjects = json.subjects.map((id) => ({ id }));
108
+ }
109
+ if (json.where) {
110
+ spec.where = deserializeWherePath(shapeClass.shape, json.where);
111
+ }
112
+ if ((_a = json.minusEntries) === null || _a === void 0 ? void 0 : _a.length) {
113
+ spec.minusEntries = json.minusEntries.map((e) => deserializeRawMinusEntry(shapeClass.shape, e));
114
+ }
115
+ if (json.nullSubject)
116
+ spec.nullSubject = true;
117
+ return new CountBuilder(spec);
118
+ }
119
+ /**
120
+ * Execute and resolve to a real number.
121
+ *
122
+ * **A failure rejects — it is never reported as `0`.** A count of `0` renders an
123
+ * empty table and is indistinguishable from real data, so a broken count that
124
+ * answered `0` would hide rather than fail. Do not wrap this in `.catch(() => 0)`.
125
+ *
126
+ * The one case that resolves `0` without querying is a query with no subject to
127
+ * count — `.for(null)`, or a `PendingQueryContext` as the subject that has not
128
+ * landed: "how many nodes with no id match?" has a correct total answer, and it
129
+ * is `0`.
130
+ */
131
+ exec(target) {
132
+ return __awaiter(this, void 0, void 0, function* () {
133
+ const { nullSubject, subject } = this._spec;
134
+ if (nullSubject)
135
+ return 0;
136
+ if (subject instanceof PendingQueryContext && !subject.id)
137
+ return 0;
138
+ // `async` ensures a missing global dispatch rejects rather than throwing
139
+ // synchronously past the caller's `.catch()`.
140
+ const dispatch = target !== null && target !== void 0 ? target : getQueryDispatch();
141
+ return resolveCount(dispatch, this);
142
+ });
143
+ }
144
+ /** `await` triggers execution. */
145
+ then(onfulfilled, onrejected) {
146
+ return this.exec().then(onfulfilled, onrejected);
147
+ }
148
+ catch(onrejected) {
149
+ return this.then().catch(onrejected);
150
+ }
151
+ finally(onfinally) {
152
+ return this.then().finally(onfinally);
153
+ }
154
+ get [Symbol.toStringTag]() {
155
+ return 'CountBuilder';
156
+ }
157
+ }
158
+ /** Narrow an unknown query object to a count. */
159
+ export function isCountQuery(query) {
160
+ return !!query && query.__queryKind === 'count';
161
+ }
162
+ //# sourceMappingURL=CountBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CountBuilder.js","sourceRoot":"","sources":["../../../src/queries/CountBuilder.ts"],"names":[],"mappings":"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"}