@_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.
- package/CHANGELOG.md +63 -0
- package/README.md +104 -3
- package/lib/esm/index.d.ts +3 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/interfaces/IDataset.d.ts +23 -0
- package/lib/esm/interfaces/IFileStore.d.ts +64 -1
- package/lib/esm/interfaces/IFileStore.js +21 -1
- package/lib/esm/interfaces/IFileStore.js.map +1 -1
- package/lib/esm/queries/CountBuilder.d.ts +69 -0
- package/lib/esm/queries/CountBuilder.js +162 -0
- package/lib/esm/queries/CountBuilder.js.map +1 -0
- package/lib/esm/queries/CountQuery.d.ts +80 -0
- package/lib/esm/queries/CountQuery.js +7 -0
- package/lib/esm/queries/CountQuery.js.map +1 -0
- package/lib/esm/queries/IntermediateRepresentation.d.ts +31 -1
- package/lib/esm/queries/QueryBuilder.d.ts +80 -0
- package/lib/esm/queries/QueryBuilder.js +98 -13
- package/lib/esm/queries/QueryBuilder.js.map +1 -1
- package/lib/esm/queries/fromJSON.d.ts +5 -3
- package/lib/esm/queries/fromJSON.js +4 -1
- package/lib/esm/queries/fromJSON.js.map +1 -1
- package/lib/esm/queries/lower.d.ts +17 -2
- package/lib/esm/queries/lower.js +61 -0
- package/lib/esm/queries/lower.js.map +1 -1
- package/lib/esm/queries/queryDispatch.d.ts +35 -0
- package/lib/esm/queries/queryDispatch.js +36 -0
- package/lib/esm/queries/queryDispatch.js.map +1 -1
- package/lib/esm/shapes/Shape.d.ts +32 -0
- package/lib/esm/shapes/Shape.js +47 -0
- package/lib/esm/shapes/Shape.js.map +1 -1
- package/lib/esm/sparql/SparqlDataset.d.ts +11 -0
- package/lib/esm/sparql/SparqlDataset.js +19 -2
- package/lib/esm/sparql/SparqlDataset.js.map +1 -1
- package/lib/esm/sparql/index.d.ts +3 -3
- package/lib/esm/sparql/index.js +3 -3
- package/lib/esm/sparql/index.js.map +1 -1
- package/lib/esm/sparql/irToAlgebra.d.ts +32 -1
- package/lib/esm/sparql/irToAlgebra.js +74 -0
- package/lib/esm/sparql/irToAlgebra.js.map +1 -1
- package/lib/esm/sparql/resultMapping.d.ts +14 -1
- package/lib/esm/sparql/resultMapping.js +40 -0
- package/lib/esm/sparql/resultMapping.js.map +1 -1
- package/lib/esm/utils/LinkedFileStorage.d.ts +96 -3
- package/lib/esm/utils/LinkedFileStorage.js +164 -12
- package/lib/esm/utils/LinkedFileStorage.js.map +1 -1
- 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
|
|
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
|
|
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
|
|
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}> */
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -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';
|
package/lib/esm/index.js.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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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"}
|