@mdbase-dev/connect 0.1.0-beta.121 → 0.1.0-beta.124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -6
- package/dist/application-session.js +51 -27
- package/dist/authority-features.d.ts +29 -0
- package/dist/authority-features.js +84 -0
- package/dist/browser/integrity.json +1 -1
- package/dist/browser/mdbase-connect.min.js +4 -4
- package/dist/browser/mdbase-connect.min.js.sha384 +1 -1
- package/dist/change-events.d.ts +6 -0
- package/dist/change-events.js +95 -0
- package/dist/collection-client.d.ts +13 -3
- package/dist/collection-client.js +98 -140
- package/dist/connection.d.ts +12 -2
- package/dist/connection.js +57 -117
- package/dist/description-cache.d.ts +14 -0
- package/dist/description-cache.js +46 -0
- package/dist/file-crypto.js +10 -6
- package/dist/file-descriptor.d.ts +14 -0
- package/dist/file-descriptor.js +29 -0
- package/dist/file-metadata.d.ts +21 -0
- package/dist/file-metadata.js +112 -0
- package/dist/file-sha256.js +3 -3
- package/dist/files.d.ts +9 -19
- package/dist/files.js +49 -75
- package/dist/index.d.ts +5 -2
- package/dist/index.js +2 -0
- package/dist/local-file-transport.js +18 -16
- package/dist/operation-types.d.ts +155 -14
- package/dist/query-pagination-internals.d.ts +9 -0
- package/dist/query-pagination-internals.js +52 -0
- package/dist/query-pagination.d.ts +7 -3
- package/dist/query-pagination.js +31 -27
- package/dist/query-predicates.d.ts +11 -0
- package/dist/query-predicates.js +15 -0
- package/dist/query-wire.d.ts +61 -0
- package/dist/query-wire.js +110 -0
- package/dist/read-many.d.ts +7 -0
- package/dist/read-many.js +175 -0
- package/dist/record-session.d.ts +3 -2
- package/dist/record-session.js +11 -2
- package/dist/records.d.ts +15 -2
- package/dist/records.js +109 -7
- package/dist/saved-view-pagination.js +20 -20
- package/dist/startup-timing.d.ts +2 -0
- package/dist/startup-timing.js +20 -0
- package/dist/structured-readiness.d.ts +3 -1
- package/dist/structured-readiness.js +21 -1
- package/dist/watch-policy.d.ts +22 -1
- package/dist/watch-policy.js +104 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
sha384-
|
|
1
|
+
sha384-23aqw9c9S7YPXCd5zLVLjUFjKy3QggldsE+mDLmLzzRkxaOnQxOkLxoQ2j1oujFh
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type CollectionChange as WireChange, type CollectionChangesPage as WirePage } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import type { CollectionChange, CollectionChangesPage } from "./operation-types.js";
|
|
3
|
+
/** Normalize authority events once, without inferring metadata missing on older authorities. */
|
|
4
|
+
export declare function normalizeCollectionChange(raw: WireChange): CollectionChange;
|
|
5
|
+
export declare function normalizeChangesPage(raw: WirePage): CollectionChangesPage;
|
|
6
|
+
export declare function invalidatesDescription(change: CollectionChange): boolean;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { CHANGE_EVENT_KINDS } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import { clientFileDescriptor, validFileDescriptor } from "./file-descriptor.js";
|
|
3
|
+
const RECORD_FIELDS = Object.entries({
|
|
4
|
+
path: string, from: string, to: string, revision: string, previous_revision: string,
|
|
5
|
+
types: strings, previous_types: strings, changed_fields: strings, body_changed: boolean,
|
|
6
|
+
before: object, after: object
|
|
7
|
+
});
|
|
8
|
+
const RESOURCE_FIELDS = Object.entries({ path: string, name: string, revision: string, previous_revision: string, reason: string });
|
|
9
|
+
const REMOVED_FILE_FIELDS = Object.entries({ previous_path: string, revision: string });
|
|
10
|
+
/** Normalize authority events once, without inferring metadata missing on older authorities. */
|
|
11
|
+
export function normalizeCollectionChange(raw) {
|
|
12
|
+
const base = { cursor: raw.cursor, type: raw.type, occurredAt: raw.occurred_at, payload: raw.payload, raw };
|
|
13
|
+
const unknown = (reason) => ({ ...base, kind: "unknown", reason });
|
|
14
|
+
const kind = Object.hasOwn(CHANGE_EVENT_KINDS, raw.type)
|
|
15
|
+
? CHANGE_EVENT_KINDS[raw.type]
|
|
16
|
+
: undefined;
|
|
17
|
+
if (!kind)
|
|
18
|
+
return unknown("unrecognized_type");
|
|
19
|
+
const p = raw.payload;
|
|
20
|
+
if (!object(p))
|
|
21
|
+
return unknown("invalid_payload");
|
|
22
|
+
if (kind.startsWith("record.")) {
|
|
23
|
+
if (!validFields(p, RECORD_FIELDS))
|
|
24
|
+
return unknown("invalid_payload");
|
|
25
|
+
const data = p;
|
|
26
|
+
const metadata = {
|
|
27
|
+
...(data.revision == null ? {} : { revision: data.revision }),
|
|
28
|
+
...(data.previous_revision == null ? {} : { previousRevision: data.previous_revision }),
|
|
29
|
+
...(data.types == null ? {} : { types: data.types }),
|
|
30
|
+
...(data.previous_types == null ? {} : { previousTypes: data.previous_types }),
|
|
31
|
+
...(data.changed_fields == null ? {} : { changedFields: data.changed_fields }),
|
|
32
|
+
...(data.body_changed == null ? {} : { bodyChanged: data.body_changed }),
|
|
33
|
+
...(data.before == null ? {} : { before: data.before }),
|
|
34
|
+
...(data.after == null ? {} : { after: data.after })
|
|
35
|
+
};
|
|
36
|
+
if (kind === "record.renamed") {
|
|
37
|
+
return string(p.from) && string(p.to)
|
|
38
|
+
? { ...base, ...metadata, kind, from: p.from, to: p.to }
|
|
39
|
+
: unknown("invalid_payload");
|
|
40
|
+
}
|
|
41
|
+
if (kind === "record.created" || kind === "record.updated" || kind === "record.deleted") {
|
|
42
|
+
return string(p.path) ? { ...base, ...metadata, kind, path: p.path } : unknown("invalid_payload");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (kind === "file.put") {
|
|
46
|
+
return validFileDescriptor(p.file)
|
|
47
|
+
? { ...base, kind, file: clientFileDescriptor(p.file) }
|
|
48
|
+
: unknown("invalid_payload");
|
|
49
|
+
}
|
|
50
|
+
if (kind === "file.removed") {
|
|
51
|
+
if (!string(p.file_id) || !validFields(p, REMOVED_FILE_FIELDS))
|
|
52
|
+
return unknown("invalid_payload");
|
|
53
|
+
const data = p;
|
|
54
|
+
return { ...base, kind, fileId: p.file_id,
|
|
55
|
+
...(data.previous_path == null ? {} : { previousPath: data.previous_path }),
|
|
56
|
+
...(data.revision == null ? {} : { revision: data.revision }) };
|
|
57
|
+
}
|
|
58
|
+
if (!validFields(p, RESOURCE_FIELDS))
|
|
59
|
+
return unknown("invalid_payload");
|
|
60
|
+
const data = p;
|
|
61
|
+
if (kind === "gap")
|
|
62
|
+
return { ...base, kind, ...(data.reason == null ? {} : { reason: data.reason }) };
|
|
63
|
+
const metadata = {
|
|
64
|
+
...(data.revision == null ? {} : { revision: data.revision }),
|
|
65
|
+
...(data.previous_revision == null ? {} : { previousRevision: data.previous_revision })
|
|
66
|
+
};
|
|
67
|
+
if (kind === "file.changed")
|
|
68
|
+
return string(p.path) ? { ...base, ...metadata, kind, path: p.path } : unknown("invalid_payload");
|
|
69
|
+
if (kind === "schema.changed" || kind === "config.changed" || kind === "contract.changed" || kind === "view.changed") {
|
|
70
|
+
return { ...base, ...metadata, kind,
|
|
71
|
+
...(data.path == null ? {} : { path: data.path }),
|
|
72
|
+
...(data.name == null ? {} : { name: data.name }) };
|
|
73
|
+
}
|
|
74
|
+
throw new Error(`Unhandled change kind: ${kind}`);
|
|
75
|
+
}
|
|
76
|
+
export function normalizeChangesPage(raw) {
|
|
77
|
+
return {
|
|
78
|
+
events: raw.reset ? [{ kind: "reset", type: "reset", cursor: raw.cursor, occurredAt: null, payload: {}, raw }] : raw.events.map(normalizeCollectionChange),
|
|
79
|
+
cursor: raw.cursor,
|
|
80
|
+
hasMore: raw.has_more,
|
|
81
|
+
reset: raw.reset
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function invalidatesDescription(change) {
|
|
85
|
+
return change.kind === "schema.changed" || change.kind === "config.changed"
|
|
86
|
+
|| change.kind === "contract.changed" || change.kind === "view.changed"
|
|
87
|
+
|| change.kind === "gap" || change.kind === "reset" || change.kind === "unknown";
|
|
88
|
+
}
|
|
89
|
+
function string(value) { return typeof value === "string"; }
|
|
90
|
+
function boolean(value) { return typeof value === "boolean"; }
|
|
91
|
+
function strings(value) { return Array.isArray(value) && value.every(string); }
|
|
92
|
+
function object(value) { return value !== null && typeof value === "object" && !Array.isArray(value); }
|
|
93
|
+
function validFields(value, fields) {
|
|
94
|
+
return fields.every(([key, valid]) => value[key] == null || valid(value[key]));
|
|
95
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CollectionOperation, CollectionTypeDocument, JsonObject } from "@mdbase-dev/connect-protocol";
|
|
2
2
|
import { type CollectionChangesProblemCode, type CollectionDescriptionProblemCode, type CollectionMutationProblemCode, type CollectionQueryProblemCode, type CollectionReadProblemCode, type CollectionTypeProblemCode, type ConnectOutcome } from "./outcomes.js";
|
|
3
|
-
import type { ChangesInput, CollectionChange, CollectionChangesPage, CollectionDescription, CollectionSetupApplyResult, CollectionSetupAssessment, AssessCollectionSetupInput, AssessTypePackInput, ApplyCollectionSetupInput, ApplyTypePackInput, CreateInput, CreateTypeInput, CreateViewSourceInput, DeleteInput, DeletePreflightResult, DeleteResult, DeleteViewSourceInput, DeleteViewSourceResult, ExecuteViewInput, MdbaseCollectionTransport, MdbaseDesiredTimer, MdbaseTimer, MdbaseTimerList, MdbaseTimerReconciliation, ConnectRequestOptions, QueryAllOptions, QueryInput, QueryPage, QueryPagesOptions, QueryResult, ReadInput, ReadTypeInput, ReadViewSourceInput, RenameInput, RenamePreflightResult, RenameResult, RecordDocument, SavedViewExecution, SavedViewPage, SavedViewPagesOptions, SavedViewList, SavedViewSourceDocument, TypePackApplyResult, TypePackAssessment, UpdateInput, UpdateTypeInput, UpdateViewSourceInput, WatchOptions } from "./operation-types.js";
|
|
3
|
+
import type { ChangesInput, DescribeOptions, CollectionChange, CollectionChangesPage, CollectionDescription, CollectionSetupApplyResult, CollectionSetupAssessment, AssessCollectionSetupInput, AssessTypePackInput, ApplyCollectionSetupInput, ApplyTypePackInput, CreateInput, CreateTypeInput, CreateViewSourceInput, DeleteInput, DeletePreflightResult, DeleteResult, DeleteViewSourceInput, DeleteViewSourceResult, ExecuteViewInput, MdbaseCollectionTransport, MdbaseDesiredTimer, MdbaseTimer, MdbaseTimerList, MdbaseTimerReconciliation, ConnectRequestOptions, QueryAllOptions, QueryInput, QueryMetadataInput, QueryMetadataRecord, QueryMetadataResult, QueryMetadataPage, QueryPage, QueryPagesOptions, QueryResult, ReadInput, ReadManyOptions, ReadManyResult, ReadTypeInput, ReadViewSourceInput, RenameInput, RenamePreflightResult, RenameResult, RecordDocument, SavedViewExecution, SavedViewPage, SavedViewPagesOptions, SavedViewList, SavedViewSourceDocument, TypePackApplyResult, TypePackAssessment, UpdateInput, UpdateTypeInput, UpdateViewSourceInput, WatchOptions } from "./operation-types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Typed collection operations independent of OAuth, HTTP, or storage.
|
|
6
6
|
*
|
|
@@ -10,13 +10,23 @@ import type { ChangesInput, CollectionChange, CollectionChangesPage, CollectionD
|
|
|
10
10
|
export declare class MdbaseCollectionClient<Frontmatter extends JsonObject = JsonObject> {
|
|
11
11
|
private readonly transport;
|
|
12
12
|
private readonly requestTimeoutMs;
|
|
13
|
-
|
|
13
|
+
private readonly supportsAuthorityFeature?;
|
|
14
|
+
private readonly descriptions;
|
|
15
|
+
private lastSchemaCursor;
|
|
16
|
+
/** Local cache generation, not an authority revision or cross-connection identity. */
|
|
17
|
+
get schemaGeneration(): number;
|
|
18
|
+
constructor(transport: MdbaseCollectionTransport, requestTimeoutMs?: number | null, supportsAuthorityFeature?: ((id: string, options?: ConnectRequestOptions) => Promise<ConnectOutcome<boolean>>) | undefined);
|
|
14
19
|
operation<Result>(operation: CollectionOperation, input: unknown, options?: ConnectRequestOptions): Promise<ConnectOutcome<Result>>;
|
|
15
|
-
describe(options?:
|
|
20
|
+
describe(options?: DescribeOptions): Promise<ConnectOutcome<CollectionDescription, CollectionDescriptionProblemCode>>;
|
|
16
21
|
changes(input?: ChangesInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<CollectionChangesPage, CollectionChangesProblemCode>>;
|
|
17
22
|
read(input: ReadInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<RecordDocument<Frontmatter>, CollectionReadProblemCode>>;
|
|
23
|
+
readMany(paths: readonly string[], options?: ReadManyOptions): Promise<ConnectOutcome<ReadManyResult<Frontmatter>, CollectionQueryProblemCode>>;
|
|
24
|
+
query(input: QueryMetadataInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<QueryMetadataResult, CollectionQueryProblemCode>>;
|
|
18
25
|
query(input?: QueryInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<QueryResult<Frontmatter>, CollectionQueryProblemCode>>;
|
|
26
|
+
query(input: QueryInput | QueryMetadataInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<QueryResult<Frontmatter> | QueryMetadataResult, CollectionQueryProblemCode>>;
|
|
27
|
+
queryPages(input: QueryMetadataInput, options?: QueryPagesOptions<JsonObject, QueryMetadataRecord>): AsyncGenerator<ConnectOutcome<QueryMetadataPage, CollectionQueryProblemCode>>;
|
|
19
28
|
queryPages(input?: QueryInput, options?: QueryPagesOptions<Frontmatter>): AsyncGenerator<ConnectOutcome<QueryPage<Frontmatter>, CollectionQueryProblemCode>>;
|
|
29
|
+
queryAll(input: QueryMetadataInput, options?: QueryAllOptions<JsonObject, QueryMetadataRecord>): Promise<ConnectOutcome<QueryMetadataResult, CollectionQueryProblemCode>>;
|
|
20
30
|
queryAll(input?: QueryInput, options?: QueryAllOptions<Frontmatter>): Promise<ConnectOutcome<QueryResult<Frontmatter>, CollectionQueryProblemCode>>;
|
|
21
31
|
listViews(options?: ConnectRequestOptions): Promise<ConnectOutcome<SavedViewList, CollectionReadProblemCode>>;
|
|
22
32
|
executeView(input: ExecuteViewInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<SavedViewExecution<Frontmatter>, CollectionReadProblemCode>>;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { abortableDelay } from "./async.js";
|
|
2
|
+
import { invalidatesDescription, normalizeChangesPage } from "./change-events.js";
|
|
3
|
+
import { CollectionDescriptionCache } from "./description-cache.js";
|
|
2
4
|
import { coordinatedQueryPages } from "./query-pagination.js";
|
|
5
|
+
import { readMany as readManyRecords } from "./read-many.js";
|
|
6
|
+
import { wireDataContractIdentity, wireQueryInput, wireQueryRecord, wireQueryResult } from "./query-wire.js";
|
|
3
7
|
import { coordinatedSavedViewPages } from "./saved-view-pagination.js";
|
|
4
8
|
import { MdbaseConnectError, connectError, connectProblem, operationProblem } from "./errors.js";
|
|
5
|
-
import { COLLECTION_CHANGES_PROBLEM_CODES,
|
|
6
|
-
import { createRequestBudget, DEFAULT_REQUEST_TIMEOUT_MS, requestAbortReason } from "./request-budget.js";
|
|
9
|
+
import { COLLECTION_CHANGES_PROBLEM_CODES, COLLECTION_MUTATION_PROBLEM_CODES, COLLECTION_QUERY_PROBLEM_CODES, COLLECTION_READ_PROBLEM_CODES, COLLECTION_TYPE_PROBLEM_CODES, ALL_CONNECT_PROBLEM_CODES, captureConnectOutcome, connectFailure, connectSuccess } from "./outcomes.js";
|
|
10
|
+
import { createRequestBudget, DEFAULT_REQUEST_TIMEOUT_MS, requestAbortReason, requestOptionsWithinBudget, withRequestBudget } from "./request-budget.js";
|
|
7
11
|
import { watchRetryPolicy } from "./watch-policy.js";
|
|
8
12
|
/**
|
|
9
13
|
* Typed collection operations independent of OAuth, HTTP, or storage.
|
|
@@ -14,44 +18,69 @@ import { watchRetryPolicy } from "./watch-policy.js";
|
|
|
14
18
|
export class MdbaseCollectionClient {
|
|
15
19
|
transport;
|
|
16
20
|
requestTimeoutMs;
|
|
17
|
-
|
|
21
|
+
supportsAuthorityFeature;
|
|
22
|
+
descriptions;
|
|
23
|
+
lastSchemaCursor = -1;
|
|
24
|
+
/** Local cache generation, not an authority revision or cross-connection identity. */
|
|
25
|
+
get schemaGeneration() { return this.descriptions.schemaGeneration; }
|
|
26
|
+
constructor(transport, requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, supportsAuthorityFeature) {
|
|
18
27
|
this.transport = transport;
|
|
19
28
|
this.requestTimeoutMs = requestTimeoutMs;
|
|
29
|
+
this.supportsAuthorityFeature = supportsAuthorityFeature;
|
|
30
|
+
this.descriptions = new CollectionDescriptionCache(transport, requestTimeoutMs);
|
|
20
31
|
}
|
|
21
32
|
operation(operation, input, options) {
|
|
22
33
|
return captureConnectOutcome(() => this.transport.operation(operation, input, options), ALL_CONNECT_PROBLEM_CODES);
|
|
23
34
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return mapOutcome(outcome, wireCollectionDescription);
|
|
35
|
+
describe(options) {
|
|
36
|
+
return this.descriptions.describe(options);
|
|
27
37
|
}
|
|
28
38
|
async changes(input = {}, options) {
|
|
29
39
|
const outcome = await this.rawOperation("changes", input, COLLECTION_CHANGES_PROBLEM_CODES, options);
|
|
30
|
-
|
|
40
|
+
const mapped = mapOutcome(outcome, normalizeChangesPage);
|
|
41
|
+
if (mapped.ok) {
|
|
42
|
+
const invalidations = mapped.value.events.filter(invalidatesDescription);
|
|
43
|
+
if (mapped.value.reset || invalidations.some((event) => event.cursor > this.lastSchemaCursor)) {
|
|
44
|
+
this.descriptions.invalidate();
|
|
45
|
+
this.lastSchemaCursor = mapped.value.cursor;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return mapped;
|
|
31
49
|
}
|
|
32
50
|
async read(input, options) {
|
|
33
51
|
const outcome = await this.envelopeOperation("read", wireReadInput(input), COLLECTION_READ_PROBLEM_CODES, options);
|
|
34
52
|
return mapOutcome(outcome, wireRecordDocument);
|
|
35
53
|
}
|
|
54
|
+
readMany(paths, options = {}) {
|
|
55
|
+
return readManyRecords((input, requestOptions) => this.queryAll(input, requestOptions), (selected, includeBody, requestOptions) => this.envelopeOperation("read", { paths: selected, include_body: includeBody, include_document: false }, COLLECTION_READ_PROBLEM_CODES, requestOptions), this.supportsAuthorityFeature, paths, options, this.requestTimeoutMs);
|
|
56
|
+
}
|
|
36
57
|
async query(input = {}, options) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
if (input.output !== undefined && input.output !== "metadata") {
|
|
59
|
+
return connectFailure(connectProblem("invalid_request", "Unknown query output mode."));
|
|
60
|
+
}
|
|
61
|
+
if (input.output !== "metadata") {
|
|
62
|
+
const outcome = await this.envelopeOperation("query", wireQueryInput(input), COLLECTION_QUERY_PROBLEM_CODES, options);
|
|
63
|
+
return mapOutcome(outcome, value => wireQueryResult(value, undefined));
|
|
64
|
+
}
|
|
65
|
+
const captured = await captureConnectOutcome(() => withRequestBudget(options, this.requestTimeoutMs, async (budget) => {
|
|
66
|
+
const requestOptions = requestOptionsWithinBudget({ ...options, timeoutMs: null }, budget);
|
|
67
|
+
if (input.includeBody)
|
|
68
|
+
throw connectError("invalid_request", "Metadata queries cannot include a body.");
|
|
69
|
+
const support = await this.supportsAuthorityFeature?.("query-metadata-v1", requestOptions);
|
|
70
|
+
if (support && !support.ok)
|
|
71
|
+
throw new MdbaseConnectError(support.problem);
|
|
72
|
+
if (!support?.value)
|
|
73
|
+
throw connectError("unsupported_operation", "This authority does not support metadata queries. Use an ordinary query on legacy authorities.");
|
|
74
|
+
const outcome = await this.envelopeOperation("query", wireQueryInput(input), COLLECTION_QUERY_PROBLEM_CODES, requestOptions);
|
|
75
|
+
return mapOutcome(outcome, value => wireQueryResult(value, input.output));
|
|
76
|
+
}), COLLECTION_QUERY_PROBLEM_CODES);
|
|
77
|
+
return captured.ok ? captured.value : captured;
|
|
52
78
|
}
|
|
53
79
|
async *queryPages(input = {}, options = {}) {
|
|
54
|
-
yield* coordinatedQueryPages((pageInput, pageOptions) => this.query(pageInput, pageOptions), (cursor) => this.releaseQueryCursor(cursor), input,
|
|
80
|
+
yield* coordinatedQueryPages((pageInput, pageOptions) => this.query(pageInput, pageOptions), (cursor) => this.releaseQueryCursor(cursor, input.output), input,
|
|
81
|
+
// The overload ties callback row types to input.output; the wire parser
|
|
82
|
+
// rejects mode mismatch before pagination can deliver a page.
|
|
83
|
+
options);
|
|
55
84
|
}
|
|
56
85
|
async queryAll(input = {}, options = {}) {
|
|
57
86
|
const budget = createRequestBudget(options, this.requestTimeoutMs);
|
|
@@ -59,14 +88,18 @@ export class MdbaseCollectionClient {
|
|
|
59
88
|
let finalPage;
|
|
60
89
|
const diagnostics = [];
|
|
61
90
|
try {
|
|
62
|
-
|
|
91
|
+
const pageOptions = {
|
|
63
92
|
firstPageSize: options.firstPageSize,
|
|
64
93
|
pageSize: options.pageSize,
|
|
94
|
+
maxResults: options.maxResults,
|
|
65
95
|
signal: budget.signal,
|
|
66
96
|
pageTimeoutMs: null,
|
|
67
|
-
coordination: options.coordination
|
|
68
|
-
|
|
69
|
-
|
|
97
|
+
coordination: options.coordination
|
|
98
|
+
};
|
|
99
|
+
const pages = input.output === "metadata"
|
|
100
|
+
? this.queryPages(input, { ...pageOptions, onProgress: options.onProgress })
|
|
101
|
+
: this.queryPages(input, { ...pageOptions, onProgress: options.onProgress });
|
|
102
|
+
for await (const outcome of pages) {
|
|
70
103
|
if (!outcome.ok)
|
|
71
104
|
return outcome;
|
|
72
105
|
const page = outcome.value;
|
|
@@ -76,14 +109,19 @@ export class MdbaseCollectionClient {
|
|
|
76
109
|
}
|
|
77
110
|
if (budget.signal.aborted)
|
|
78
111
|
throw requestAbortReason(budget.signal);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
totalCount: finalPage
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
}
|
|
112
|
+
const meta = finalPage ? {
|
|
113
|
+
...(finalPage.meta?.totalCount === undefined
|
|
114
|
+
? (!finalPage.meta?.hasMore ? { totalCount: results.length } : {})
|
|
115
|
+
: { totalCount: finalPage.meta.totalCount }),
|
|
116
|
+
...(finalPage.meta?.hasMore && finalPage.meta.totalCountOutcome ? { totalCountOutcome: finalPage.meta.totalCountOutcome } : {}),
|
|
117
|
+
hasMore: finalPage.meta?.hasMore ?? false,
|
|
118
|
+
...(finalPage.snapshot ? { snapshot: finalPage.snapshot } : {})
|
|
119
|
+
} : undefined;
|
|
120
|
+
// Every page is parsed in the requested mode, never converted from full
|
|
121
|
+
// rows to revision-required metadata (or from partial rows to documents).
|
|
122
|
+
return input.output === "metadata"
|
|
123
|
+
? connectSuccess({ output: "metadata", results: results, ...(meta ? { meta } : {}) }, diagnostics)
|
|
124
|
+
: connectSuccess({ results: results, ...(meta ? { meta } : {}) }, diagnostics);
|
|
87
125
|
}
|
|
88
126
|
catch (error) {
|
|
89
127
|
if (error instanceof MdbaseConnectError) {
|
|
@@ -283,9 +321,19 @@ export class MdbaseCollectionClient {
|
|
|
283
321
|
rawOperation(operation, input, allowedCodes, options) {
|
|
284
322
|
return captureConnectOutcome(() => this.transport.operation(operation, input, options), allowedCodes);
|
|
285
323
|
}
|
|
286
|
-
async releaseQueryCursor(cursor) {
|
|
324
|
+
async releaseQueryCursor(cursor, output) {
|
|
287
325
|
try {
|
|
288
|
-
await
|
|
326
|
+
await withRequestBudget({ timeoutMs: 2_000 }, null, async (budget) => {
|
|
327
|
+
const options = requestOptionsWithinBudget({ signal: budget.signal, timeoutMs: null, coordination: { coalesce: false } }, budget);
|
|
328
|
+
if (output) {
|
|
329
|
+
// A supplied/stale cursor can reach cleanup even when the page was
|
|
330
|
+
// rejected before dispatch. Never send metadata release to an old route.
|
|
331
|
+
const support = await this.supportsAuthorityFeature?.("query-metadata-v1", options);
|
|
332
|
+
if (!support?.ok || !support.value)
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
await this.envelopeOperation("query", { release_cursor: cursor, ...(output ? { output } : {}) }, COLLECTION_QUERY_PROBLEM_CODES, options);
|
|
336
|
+
});
|
|
289
337
|
}
|
|
290
338
|
catch {
|
|
291
339
|
// Lease expiry is bounded by the authority; cleanup never masks query results.
|
|
@@ -299,11 +347,23 @@ export class MdbaseCollectionClient {
|
|
|
299
347
|
if (!envelope.valid) {
|
|
300
348
|
return connectFailure(operationProblem(envelope));
|
|
301
349
|
}
|
|
350
|
+
if (["create_type", "update_type", "apply_type_pack", "apply_collection_setup", "create_view_source", "update_view_source", "delete_view_source"].includes(operation)) {
|
|
351
|
+
this.descriptions.invalidate();
|
|
352
|
+
}
|
|
302
353
|
return connectSuccess(envelope.result, envelope.diagnostics);
|
|
303
354
|
}
|
|
304
355
|
}
|
|
305
356
|
function mapOutcome(outcome, map) {
|
|
306
|
-
|
|
357
|
+
if (!outcome.ok)
|
|
358
|
+
return outcome;
|
|
359
|
+
try {
|
|
360
|
+
return connectSuccess(map(outcome.value), outcome.diagnostics);
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
if (error instanceof MdbaseConnectError)
|
|
364
|
+
return connectFailure(error.problem);
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
307
367
|
}
|
|
308
368
|
function wireReadInput(input) {
|
|
309
369
|
return {
|
|
@@ -312,47 +372,6 @@ function wireReadInput(input) {
|
|
|
312
372
|
...(input.includeDocument === undefined ? {} : { include_document: input.includeDocument })
|
|
313
373
|
};
|
|
314
374
|
}
|
|
315
|
-
function wireQueryInput(input) {
|
|
316
|
-
return {
|
|
317
|
-
...(input.types ? { types: input.types } : {}),
|
|
318
|
-
...(input.timezone ? { timezone: input.timezone } : {}),
|
|
319
|
-
...(input.context ? { context: input.context } : {}),
|
|
320
|
-
...(input.projections ? {
|
|
321
|
-
projections: Object.fromEntries(Object.entries(input.projections).map(([name, projection]) => [
|
|
322
|
-
name,
|
|
323
|
-
{ expr: projection.expression, ...(projection.description ? { description: projection.description } : {}) }
|
|
324
|
-
]))
|
|
325
|
-
} : {}),
|
|
326
|
-
...(input.where ? { where: input.where } : {}),
|
|
327
|
-
...(input.select ? {
|
|
328
|
-
select: input.select.map((selection) => typeof selection === "string"
|
|
329
|
-
? selection
|
|
330
|
-
: {
|
|
331
|
-
name: selection.name,
|
|
332
|
-
expr: selection.expression,
|
|
333
|
-
...(selection.label ? { label: selection.label } : {}),
|
|
334
|
-
...(selection.description ? { description: selection.description } : {})
|
|
335
|
-
})
|
|
336
|
-
} : {}),
|
|
337
|
-
...(input.orderBy ? { order_by: input.orderBy } : {}),
|
|
338
|
-
...(input.groupBy ? { group_by: input.groupBy } : {}),
|
|
339
|
-
...(input.summaryFunctions ? {
|
|
340
|
-
summary_functions: Object.fromEntries(Object.entries(input.summaryFunctions).map(([name, projection]) => [
|
|
341
|
-
name,
|
|
342
|
-
{ expr: projection.expression, ...(projection.description ? { description: projection.description } : {}) }
|
|
343
|
-
]))
|
|
344
|
-
} : {}),
|
|
345
|
-
...(input.summaries ? { summaries: input.summaries } : {}),
|
|
346
|
-
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
347
|
-
...(input.offset === undefined ? {} : { offset: input.offset }),
|
|
348
|
-
...(input.pagination ? { pagination: input.pagination } : {}),
|
|
349
|
-
...(input.cursor ? { cursor: input.cursor } : {}),
|
|
350
|
-
...(input.snapshot ? { snapshot: input.snapshot } : {}),
|
|
351
|
-
...(input.includeBody === undefined ? {} : { include_body: input.includeBody }),
|
|
352
|
-
...(input.frontmatterMode ? { frontmatter_mode: input.frontmatterMode } : {}),
|
|
353
|
-
...(input.contract ? { contract: input.contract } : {})
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
375
|
function wireCreateInput(input) {
|
|
357
376
|
return {
|
|
358
377
|
...(input.path ? { path: input.path } : {}),
|
|
@@ -500,23 +519,6 @@ function wireContractSetupChoice(choice) {
|
|
|
500
519
|
...(choice.binding ? { binding: choice.binding } : {})
|
|
501
520
|
};
|
|
502
521
|
}
|
|
503
|
-
function wireDataContractIdentity(value) {
|
|
504
|
-
return {
|
|
505
|
-
id: value.id,
|
|
506
|
-
version: value.version,
|
|
507
|
-
digest: value.digest,
|
|
508
|
-
type: value.type,
|
|
509
|
-
implementationDigest: value.implementation_digest
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
function wireQueryRecord(value) {
|
|
513
|
-
const { effective_frontmatter, contract, ...record } = value;
|
|
514
|
-
return {
|
|
515
|
-
...record,
|
|
516
|
-
...(effective_frontmatter === undefined ? {} : { effectiveFrontmatter: effective_frontmatter }),
|
|
517
|
-
...(contract ? { contract: wireDataContractIdentity(contract) } : {})
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
522
|
function wireRecordDocument(value) {
|
|
521
523
|
const { effective_frontmatter, contract, ...record } = value;
|
|
522
524
|
return {
|
|
@@ -635,47 +637,3 @@ function wireCollectionSetupReceipt(value) {
|
|
|
635
637
|
cleanupDeferred: value.cleanup_deferred
|
|
636
638
|
};
|
|
637
639
|
}
|
|
638
|
-
function wireChange(value) {
|
|
639
|
-
return {
|
|
640
|
-
cursor: value.cursor,
|
|
641
|
-
type: value.type,
|
|
642
|
-
occurredAt: value.occurred_at,
|
|
643
|
-
payload: value.payload
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
function wireChangesPage(value) {
|
|
647
|
-
return {
|
|
648
|
-
events: value.events.map(wireChange),
|
|
649
|
-
cursor: value.cursor,
|
|
650
|
-
hasMore: value.has_more,
|
|
651
|
-
reset: value.reset
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
function wireCollectionDescription(value) {
|
|
655
|
-
return {
|
|
656
|
-
protocolVersion: value.protocol_version,
|
|
657
|
-
collectionId: value.collection_id,
|
|
658
|
-
displayName: value.display_name,
|
|
659
|
-
specVersion: value.spec_version,
|
|
660
|
-
operations: value.operations,
|
|
661
|
-
changeCursor: value.change_cursor,
|
|
662
|
-
types: value.types,
|
|
663
|
-
contracts: value.contracts.map((contract) => ({
|
|
664
|
-
contractType: contract.contract_type,
|
|
665
|
-
id: contract.id,
|
|
666
|
-
version: contract.version,
|
|
667
|
-
digest: contract.digest,
|
|
668
|
-
schema: contract.schema,
|
|
669
|
-
...(contract.binding_schema ? { bindingSchema: contract.binding_schema } : {}),
|
|
670
|
-
implementations: contract.implementations.map((implementation) => ({
|
|
671
|
-
typeName: implementation.type_name,
|
|
672
|
-
typeVersion: implementation.type_version,
|
|
673
|
-
...(implementation.type_path ? { typePath: implementation.type_path } : {}),
|
|
674
|
-
digest: implementation.digest,
|
|
675
|
-
fields: implementation.fields,
|
|
676
|
-
...(implementation.binding ? { binding: implementation.binding } : {})
|
|
677
|
-
}))
|
|
678
|
-
})),
|
|
679
|
-
...(value.configuration ? { configuration: value.configuration } : {})
|
|
680
|
-
};
|
|
681
|
-
}
|
package/dist/connection.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MdbasePeopleClient } from "./people-client.js";
|
|
2
2
|
import type { CollectionOperation, CollectionTypeDocument, FileCapability, GrantScope, JsonObject } from "@mdbase-dev/connect-protocol";
|
|
3
3
|
import { type MdbaseApplicationCapabilityId as ApplicationCapabilityId } from "./application-contract.js";
|
|
4
|
+
import { MdbaseCollectionClient } from "./collection-client.js";
|
|
4
5
|
import { type ConnectionTransportInternals } from "./connection-transport.js";
|
|
5
6
|
import type { DirectAccessStatus, MdbaseAuthorizationCapabilities, MdbaseConnectionInfo, MdbaseConnectionRoute, MdbaseSyncConnection } from "./connection-types.js";
|
|
6
7
|
import type { GrantKeyStore } from "./crypto.js";
|
|
@@ -8,7 +9,7 @@ import { MdbaseFileClient } from "./files.js";
|
|
|
8
9
|
import { MdbaseRecords } from "./records.js";
|
|
9
10
|
import { type Application } from "./internal-types.js";
|
|
10
11
|
import type { MdbaseNativeNotificationRegistration, MdbaseNativeNotificationRegistrationOptions, MdbaseNotificationRegistration, MdbaseNotificationRegistrationOptions } from "./notifications.js";
|
|
11
|
-
import type { ChangesInput, CollectionChangesPage, CollectionDescription, CollectionSetupApplyResult, CollectionSetupAssessment, ApplyCollectionSetupInput, ApplyTypePackInput, AssessCollectionSetupInput, AssessTypePackInput, CreateInput, CreateTypeInput, CreateViewSourceInput, DeleteInput, DeletePreflightResult, DeleteProgressOptions, DeleteResult, DeleteViewSourceInput, DeleteViewSourceResult, ExecuteViewInput, MdbaseDesiredTimer, MdbaseTimer, MdbaseTimerList, MdbaseTimerReconciliation, ConnectRequestOptions, PendingMutation, QueryAllOptions, QueryInput, QueryPage, QueryPagesOptions, QueryResult, ReadInput, ReadTypeInput, ReadViewSourceInput, RenameInput, RenamePreflightResult, RenameProgressOptions, RenameResult, RecordDocument, SavedViewExecution, SavedViewPage, SavedViewPagesOptions, SavedViewList, SavedViewSourceDocument, TypePackApplyResult, TypePackAssessment, UpdateInput, UpdateTypeInput, UpdateViewSourceInput, MdbaseWatchSubscription, WatchInput } from "./operation-types.js";
|
|
12
|
+
import type { ChangesInput, CollectionChangesPage, DescribeOptions, CollectionDescription, CollectionSetupApplyResult, CollectionSetupAssessment, ApplyCollectionSetupInput, ApplyTypePackInput, AssessCollectionSetupInput, AssessTypePackInput, CreateInput, CreateTypeInput, CreateViewSourceInput, DeleteInput, DeletePreflightResult, DeleteProgressOptions, DeleteResult, DeleteViewSourceInput, DeleteViewSourceResult, ExecuteViewInput, MdbaseDesiredTimer, MdbaseTimer, MdbaseTimerList, MdbaseTimerReconciliation, ConnectRequestOptions, PendingMutation, QueryAllOptions, QueryInput, QueryMetadataInput, QueryMetadataRecord, QueryMetadataResult, QueryMetadataPage, QueryPage, QueryPagesOptions, QueryResult, ReadInput, ReadTypeInput, ReadViewSourceInput, RenameInput, RenamePreflightResult, RenameProgressOptions, RenameResult, RecordDocument, SavedViewExecution, SavedViewPage, SavedViewPagesOptions, SavedViewList, SavedViewSourceDocument, TypePackApplyResult, TypePackAssessment, UpdateInput, UpdateTypeInput, UpdateViewSourceInput, MdbaseWatchSubscription, WatchInput } from "./operation-types.js";
|
|
12
13
|
import { type AuthorizationProblemCode, type CollectionChangesProblemCode, type CollectionDescriptionProblemCode, type CollectionMutationProblemCode, type CollectionQueryProblemCode, type CollectionReadProblemCode, type CollectionTypeProblemCode, type ConnectOutcome, type DirectAccessProblemCode, type NotificationProblemCode, type RegistrationProblemCode } from "./outcomes.js";
|
|
13
14
|
import type { MdbaseDeviceAuthorization } from "./connection-types.js";
|
|
14
15
|
import { type ResolvedConnectTimeouts } from "./request-budget.js";
|
|
@@ -75,10 +76,12 @@ export declare class MdbaseConnection<Frontmatter extends JsonObject = JsonObjec
|
|
|
75
76
|
private readonly collectionClient;
|
|
76
77
|
private readonly notifications;
|
|
77
78
|
private readonly transport;
|
|
79
|
+
private readonly authorityFeatures;
|
|
78
80
|
readonly files: MdbaseFileClient;
|
|
79
81
|
/** Editable record sessions shared by every view of this connection. */
|
|
80
82
|
readonly records: MdbaseRecords<Frontmatter>;
|
|
81
83
|
readonly people: MdbasePeopleClient;
|
|
84
|
+
readonly readMany: MdbaseCollectionClient<Frontmatter>["readMany"];
|
|
82
85
|
private readonly connectionListeners;
|
|
83
86
|
constructor(internals: MdbaseConnectionInternals<Frontmatter>, collectionId: string);
|
|
84
87
|
get displayName(): string;
|
|
@@ -87,6 +90,9 @@ export declare class MdbaseConnection<Frontmatter extends JsonObject = JsonObjec
|
|
|
87
90
|
get fileCapability(): FileCapability | null;
|
|
88
91
|
get directAccess(): DirectAccessStatus;
|
|
89
92
|
get route(): MdbaseConnectionRoute;
|
|
93
|
+
/** Implementation support for this connection lifetime; not an authorization check. */
|
|
94
|
+
supportsAuthorityFeature(id: string, options?: ConnectRequestOptions): Promise<ConnectOutcome<boolean>>;
|
|
95
|
+
get authorityCapabilities(): readonly string[];
|
|
90
96
|
register(options?: ConnectRequestOptions): Promise<ConnectOutcome<Application, RegistrationProblemCode>>;
|
|
91
97
|
info(): MdbaseConnectionInfo | null;
|
|
92
98
|
authorizationCapabilities(requiredOperations?: CollectionOperation[]): MdbaseAuthorizationCapabilities;
|
|
@@ -123,11 +129,15 @@ export declare class MdbaseConnection<Frontmatter extends JsonObject = JsonObjec
|
|
|
123
129
|
unregisterNativeNotifications(options?: ConnectRequestOptions): Promise<ConnectOutcome<void, NotificationProblemCode>>;
|
|
124
130
|
unregisterNotifications(serviceWorker?: ServiceWorkerRegistration, options?: ConnectRequestOptions): Promise<ConnectOutcome<void, NotificationProblemCode>>;
|
|
125
131
|
forget(): void;
|
|
126
|
-
|
|
132
|
+
get schemaGeneration(): number;
|
|
133
|
+
describe(options?: DescribeOptions): Promise<ConnectOutcome<CollectionDescription, CollectionDescriptionProblemCode>>;
|
|
127
134
|
changes(input?: ChangesInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<CollectionChangesPage, CollectionChangesProblemCode>>;
|
|
128
135
|
read(input: ReadInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<RecordDocument<Frontmatter>, CollectionReadProblemCode>>;
|
|
136
|
+
query(input: QueryMetadataInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<QueryMetadataResult, CollectionQueryProblemCode>>;
|
|
129
137
|
query(input?: QueryInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<QueryResult<Frontmatter>, CollectionQueryProblemCode>>;
|
|
138
|
+
queryPages(input: QueryMetadataInput, options?: QueryPagesOptions<JsonObject, QueryMetadataRecord>): AsyncGenerator<ConnectOutcome<QueryMetadataPage, CollectionQueryProblemCode>>;
|
|
130
139
|
queryPages(input?: QueryInput, options?: QueryPagesOptions<Frontmatter>): AsyncGenerator<ConnectOutcome<QueryPage<Frontmatter>, CollectionQueryProblemCode>>;
|
|
140
|
+
queryAll(input: QueryMetadataInput, options?: QueryAllOptions<JsonObject, QueryMetadataRecord>): Promise<ConnectOutcome<QueryMetadataResult, CollectionQueryProblemCode>>;
|
|
131
141
|
queryAll(input?: QueryInput, options?: QueryAllOptions<Frontmatter>): Promise<ConnectOutcome<QueryResult<Frontmatter>, CollectionQueryProblemCode>>;
|
|
132
142
|
listViews(options?: ConnectRequestOptions): Promise<ConnectOutcome<SavedViewList, CollectionReadProblemCode>>;
|
|
133
143
|
executeView(input: ExecuteViewInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<SavedViewExecution<Frontmatter>, CollectionReadProblemCode>>;
|