@loams/client 0.1.0 → 0.1.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/README.md +78 -2
- package/dist/app.d.ts +149 -0
- package/dist/app.js +317 -0
- package/dist/builders/filter.d.ts +96 -0
- package/dist/builders/filter.js +190 -0
- package/dist/builders/schema.d.ts +50 -0
- package/dist/builders/schema.js +92 -0
- package/dist/builders/search.d.ts +159 -0
- package/dist/builders/search.js +307 -0
- package/dist/gen/facade.d.ts +267 -2
- package/dist/gen/facade.js +194 -0
- package/dist/handle.d.ts +67 -0
- package/dist/handle.js +107 -0
- package/dist/index.d.ts +12 -5
- package/dist/index.js +12 -3
- package/dist/loams.d.ts +77 -2
- package/dist/loams.js +84 -6
- package/dist/runtime/batch.d.ts +29 -0
- package/dist/runtime/batch.js +95 -0
- package/dist/runtime/call.d.ts +63 -9
- package/dist/runtime/call.js +243 -41
- package/dist/runtime/consistency.d.ts +65 -11
- package/dist/runtime/consistency.js +212 -46
- package/dist/runtime/errors.d.ts +29 -2
- package/dist/runtime/errors.js +26 -0
- package/dist/runtime/pagination.js +8 -2
- package/dist/runtime/subscribe.d.ts +37 -0
- package/dist/runtime/subscribe.js +128 -0
- package/dist/vendor/proto/collection-v1.d.ts +3 -0
- package/dist/vendor/proto/collection-v1.js +7 -0
- package/dist/vendor/proto/gen/loams/collection/v1/collection_pb.d.ts +1610 -0
- package/dist/vendor/proto/gen/loams/collection/v1/collection_pb.js +399 -0
- package/dist/vendor/proto/gen/loams/collection/v1/document_pb.d.ts +1054 -0
- package/dist/vendor/proto/gen/loams/collection/v1/document_pb.js +327 -0
- package/dist/vendor/proto/gen/loams/collection/v1/query_pb.d.ts +2101 -0
- package/dist/vendor/proto/gen/loams/collection/v1/query_pb.js +608 -0
- package/dist/vendor/proto/gen/loams/graph/v1/graph_pb.d.ts +2299 -0
- package/dist/vendor/proto/gen/loams/graph/v1/graph_pb.js +596 -0
- package/dist/vendor/proto/gen/loams/operations/v1/operations_pb.d.ts +399 -0
- package/dist/vendor/proto/gen/loams/operations/v1/operations_pb.js +118 -0
- package/dist/vendor/proto/gen/loams/sql/v1/sql_pb.d.ts +182 -0
- package/dist/vendor/proto/gen/loams/sql/v1/sql_pb.js +72 -0
- package/dist/vendor/proto/gen/loams/stream/v1/stream_pb.d.ts +688 -0
- package/dist/vendor/proto/gen/loams/stream/v1/stream_pb.js +180 -0
- package/package.json +2 -2
package/dist/loams.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Transport } from '@connectrpc/connect';
|
|
2
|
-
import { type AdminAgentsModule, type AdminAuditModule, type AdminKeysModule, type AdminOrgModule, type AdminProjectsModule, type AuthModule, type CallBinding, type InstanceModule, type LiveAdminModule, type LiveModule, type ModuleBinding, type TablesModule } from './gen/facade.js';
|
|
2
|
+
import { type AdminAgentsModule, type AdminAuditModule, type AdminKeysModule, type AdminOrgModule, type AdminProjectsModule, type AuthModule, type CallBinding, type CollectionsModule, type DocumentsModule, type GraphModule, type SqlModule, type StreamsModule, type NamespacesModule, type SearchModule, type VectorModule, type InstanceModule, type LiveAdminModule, type LiveModule, type ModuleBinding, type TablesModule } from './gen/facade.js';
|
|
3
|
+
import { type documentCalls, type searchCalls, type sqlCalls, type streamCalls } from './app.js';
|
|
4
|
+
import { CollectionHandle } from './handle.js';
|
|
3
5
|
import { type CallOptions, type ConsistencyTokenStore } from './runtime/call.js';
|
|
4
6
|
import { type PageRequestOptions } from './runtime/pagination.js';
|
|
5
7
|
import { SystemApi } from './system.js';
|
|
@@ -38,8 +40,29 @@ export interface LoamsOptions {
|
|
|
38
40
|
* read-your-writes across processes.
|
|
39
41
|
*/
|
|
40
42
|
sessionConsistency?: boolean;
|
|
41
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* The deadline of every unary call that does not set its own `timeoutMs`
|
|
45
|
+
* (runtime contract R12). Default 30 000 ms; `0` disables it. Streams have
|
|
46
|
+
* no default deadline. The budget covers a call's retries, and a call that
|
|
47
|
+
* runs out raises `DeadlineExceededError` with reason
|
|
48
|
+
* `client_deadline_exceeded`, which is never retried.
|
|
49
|
+
*/
|
|
42
50
|
timeoutMs?: number;
|
|
51
|
+
/**
|
|
52
|
+
* The namespace of every request that has a `namespace` field and leaves it
|
|
53
|
+
* empty (runtime contract R11). An explicit `namespace` always wins; with
|
|
54
|
+
* neither, such a call fails with `InvalidArgumentError` before any network
|
|
55
|
+
* call.
|
|
56
|
+
*/
|
|
57
|
+
namespace?: string;
|
|
58
|
+
}
|
|
59
|
+
/** The environment `Loams.fromEnv` reads (R1). */
|
|
60
|
+
export interface LoamsEnvironment {
|
|
61
|
+
readonly LOAMS_ENDPOINT?: string;
|
|
62
|
+
readonly LOAMS_API_KEY?: string;
|
|
63
|
+
readonly LOAMS_TOKEN?: string;
|
|
64
|
+
readonly LOAMS_NAMESPACE?: string;
|
|
65
|
+
readonly [name: string]: string | undefined;
|
|
43
66
|
}
|
|
44
67
|
export type { Catalogue, VersionReport } from './system.js';
|
|
45
68
|
/** The generated modules, keyed by name. A module the generator has not seen
|
|
@@ -56,6 +79,27 @@ export type LiveNamespace = LiveModule & {
|
|
|
56
79
|
/** `loams.live.admin` — apps, deploy, roll back, index status and logs. */
|
|
57
80
|
readonly admin: LiveAdminModule;
|
|
58
81
|
};
|
|
82
|
+
/** A paged call's `<call>All` iterator, which `attachAppCalls` adds. */
|
|
83
|
+
type AllOf<Item> = (request?: object, options?: CallOptions) => AsyncGenerator<Item, void, undefined>;
|
|
84
|
+
/** `loams.namespaces`, with `listAll`. */
|
|
85
|
+
export type NamespacesNamespace = NamespacesModule & {
|
|
86
|
+
readonly listAll: AllOf<unknown>;
|
|
87
|
+
};
|
|
88
|
+
/** `loams.collections`, with `listAll` and `listVersionsAll`. */
|
|
89
|
+
export type CollectionsNamespace = CollectionsModule & {
|
|
90
|
+
readonly listAll: AllOf<unknown>;
|
|
91
|
+
readonly listVersionsAll: AllOf<unknown>;
|
|
92
|
+
};
|
|
93
|
+
/** `loams.documents`, with the hand-written conveniences (SDKP §2.3). */
|
|
94
|
+
export type DocumentsNamespace = DocumentsModule & ReturnType<typeof documentCalls>;
|
|
95
|
+
/** `loams.search`, with `text`, `hybrid` and `queryAll` (SDKP §2.4). */
|
|
96
|
+
export type SearchNamespace = SearchModule & ReturnType<typeof searchCalls>;
|
|
97
|
+
/** `loams.sql`, with `records` (SDKP §2.5). */
|
|
98
|
+
export type SqlNamespace = SqlModule & ReturnType<typeof sqlCalls>;
|
|
99
|
+
/** `loams.streams`, with `subscribe` and `<call>All` (SDKP §2.5, R14). */
|
|
100
|
+
export type StreamsNamespace = StreamsModule & ReturnType<typeof streamCalls> & {
|
|
101
|
+
readonly listAll: AllOf<unknown>;
|
|
102
|
+
};
|
|
59
103
|
/** `loams.admin`: the console's administration modules (design §44 §7.2). */
|
|
60
104
|
export interface AdminNamespace {
|
|
61
105
|
readonly agents: AdminAgentsModule;
|
|
@@ -78,13 +122,44 @@ export declare class Loams {
|
|
|
78
122
|
readonly admin: AdminNamespace;
|
|
79
123
|
/** `loams.auth` — sessions and sign-in. */
|
|
80
124
|
readonly auth: AuthModule;
|
|
125
|
+
/** `loams.namespaces` — the containers every collection and stream live in. */
|
|
126
|
+
readonly namespaces: NamespacesNamespace;
|
|
127
|
+
/** `loams.collections` — collections, their schemas, aliases and versions. */
|
|
128
|
+
readonly collections: CollectionsNamespace;
|
|
129
|
+
/** `loams.documents` — write, read, scroll and count documents. */
|
|
130
|
+
readonly documents: DocumentsNamespace;
|
|
131
|
+
/** `loams.search` — text, vector and hybrid search (`Search` builds one). */
|
|
132
|
+
readonly search: SearchNamespace;
|
|
133
|
+
/** `loams.vector` — the kNN half of `search`, by its own name (Q613). */
|
|
134
|
+
readonly vector: VectorModule;
|
|
135
|
+
/** `loams.graph` — Loams Graph, queried with GQL. Its package is `unstable`. */
|
|
136
|
+
readonly graph: GraphModule;
|
|
137
|
+
/** `loams.sql` — one read-only SQL statement over a namespace's collections. */
|
|
138
|
+
readonly sql: SqlNamespace;
|
|
139
|
+
/** `loams.streams` — a namespace's logs: produce, fetch, CloudEvents and `subscribe`. */
|
|
140
|
+
readonly streams: StreamsNamespace;
|
|
81
141
|
/** The module catalogue, feature detection and the version check. */
|
|
82
142
|
readonly system: SystemApi;
|
|
83
143
|
/** Every generated module, by its dotted name (`'live.admin'`): the
|
|
84
144
|
* catalogue a caller iterates. The same objects are reachable as
|
|
85
145
|
* properties, `loams.live.admin` for `'live.admin'`. */
|
|
86
146
|
readonly modules: Readonly<Record<string, unknown>>;
|
|
147
|
+
/** The default namespace (R11), if the client has one. */
|
|
148
|
+
readonly namespace: string | undefined;
|
|
87
149
|
constructor(options: LoamsOptions);
|
|
150
|
+
/**
|
|
151
|
+
* A client from the environment: `LOAMS_ENDPOINT`, then `LOAMS_API_KEY` or
|
|
152
|
+
* `LOAMS_TOKEN`, then `LOAMS_NAMESPACE` (R1). `options` wins over the
|
|
153
|
+
* environment where both say something.
|
|
154
|
+
*/
|
|
155
|
+
static fromEnv(options?: Partial<LoamsOptions>, environment?: LoamsEnvironment): Loams;
|
|
156
|
+
/**
|
|
157
|
+
* A handle on one collection: its document and search calls with
|
|
158
|
+
* `collection` (and `namespace`) filled in (SDKP §2.8). Holds no state.
|
|
159
|
+
*/
|
|
160
|
+
collection(name: string, options?: {
|
|
161
|
+
namespace?: string;
|
|
162
|
+
}): CollectionHandle;
|
|
88
163
|
/** The session consistency token store, when `sessionConsistency` is on. */
|
|
89
164
|
get consistency(): ConsistencyTokenStore | undefined;
|
|
90
165
|
/** The generated module bindings, as the SDK sees them. */
|
package/dist/loams.js
CHANGED
|
@@ -16,13 +16,15 @@
|
|
|
16
16
|
// why annotating a proto is enough to add an SDK method.
|
|
17
17
|
import { Code } from '@connectrpc/connect';
|
|
18
18
|
import { MODULES, PROTO_PACKAGES, PROTO_REV, SERVICE_DESCRIPTORS, } from './gen/facade.js';
|
|
19
|
-
import {
|
|
19
|
+
import { attachAppCalls } from './app.js';
|
|
20
|
+
import { CollectionHandle } from './handle.js';
|
|
21
|
+
import { CallInvoker, DEFAULT_TIMEOUT_MS, } from './runtime/call.js';
|
|
20
22
|
import { ConsistencySession } from './runtime/consistency.js';
|
|
21
23
|
import { LoamsError } from './runtime/errors.js';
|
|
22
24
|
import { paginate } from './runtime/pagination.js';
|
|
23
25
|
import { SystemApi } from './system.js';
|
|
24
26
|
import { DEFAULT_MAX_RETRIES } from './runtime/retry.js';
|
|
25
|
-
import { apiKey } from './runtime/token-source.js';
|
|
27
|
+
import { apiKey, envToken } from './runtime/token-source.js';
|
|
26
28
|
import { createLoamsTransport } from './runtime/transports.js';
|
|
27
29
|
import { watch } from './runtime/streams.js';
|
|
28
30
|
/** One SDK, over one instance. */
|
|
@@ -38,12 +40,30 @@ export class Loams {
|
|
|
38
40
|
admin;
|
|
39
41
|
/** `loams.auth` — sessions and sign-in. */
|
|
40
42
|
auth;
|
|
43
|
+
/** `loams.namespaces` — the containers every collection and stream live in. */
|
|
44
|
+
namespaces;
|
|
45
|
+
/** `loams.collections` — collections, their schemas, aliases and versions. */
|
|
46
|
+
collections;
|
|
47
|
+
/** `loams.documents` — write, read, scroll and count documents. */
|
|
48
|
+
documents;
|
|
49
|
+
/** `loams.search` — text, vector and hybrid search (`Search` builds one). */
|
|
50
|
+
search;
|
|
51
|
+
/** `loams.vector` — the kNN half of `search`, by its own name (Q613). */
|
|
52
|
+
vector;
|
|
53
|
+
/** `loams.graph` — Loams Graph, queried with GQL. Its package is `unstable`. */
|
|
54
|
+
graph;
|
|
55
|
+
/** `loams.sql` — one read-only SQL statement over a namespace's collections. */
|
|
56
|
+
sql;
|
|
57
|
+
/** `loams.streams` — a namespace's logs: produce, fetch, CloudEvents and `subscribe`. */
|
|
58
|
+
streams;
|
|
41
59
|
/** The module catalogue, feature detection and the version check. */
|
|
42
60
|
system;
|
|
43
61
|
/** Every generated module, by its dotted name (`'live.admin'`): the
|
|
44
62
|
* catalogue a caller iterates. The same objects are reachable as
|
|
45
63
|
* properties, `loams.live.admin` for `'live.admin'`. */
|
|
46
64
|
modules;
|
|
65
|
+
/** The default namespace (R11), if the client has one. */
|
|
66
|
+
namespace;
|
|
47
67
|
#invoker;
|
|
48
68
|
#consistency;
|
|
49
69
|
constructor(options) {
|
|
@@ -59,20 +79,62 @@ export class Loams {
|
|
|
59
79
|
// that asks for the session's consistency token reads and records through
|
|
60
80
|
// the same store the client exposes.
|
|
61
81
|
this.#consistency = options.sessionConsistency === true ? new ConsistencySession() : undefined;
|
|
62
|
-
this.#invoker = new CallInvoker(transport, tokenSource, options.maxRetries ?? DEFAULT_MAX_RETRIES, this.#consistency
|
|
82
|
+
this.#invoker = new CallInvoker(transport, tokenSource, options.maxRetries ?? DEFAULT_MAX_RETRIES, this.#consistency, {
|
|
83
|
+
namespace: options.namespace,
|
|
84
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
85
|
+
});
|
|
86
|
+
this.namespace = options.namespace;
|
|
63
87
|
this.modules = buildModules(this.#invoker);
|
|
88
|
+
attachAppCalls(this.modules, MODULES);
|
|
64
89
|
const tree = nestModules(this.modules);
|
|
65
90
|
this.instance = tree.instance;
|
|
66
91
|
this.live = tree.live;
|
|
67
92
|
this.tables = tree.tables;
|
|
68
93
|
this.admin = tree.admin;
|
|
69
94
|
this.auth = tree.auth;
|
|
95
|
+
this.namespaces = tree.namespaces;
|
|
96
|
+
this.collections = tree.collections;
|
|
97
|
+
this.documents = tree.documents;
|
|
98
|
+
this.search = tree.search;
|
|
99
|
+
this.vector = tree.vector;
|
|
100
|
+
this.graph = tree.graph;
|
|
101
|
+
this.sql = tree.sql;
|
|
102
|
+
this.streams = tree.streams;
|
|
70
103
|
this.system = new SystemApi((request, callOptions) => this.instance.getInstance(request, callOptions), {
|
|
71
104
|
endpoint: options.endpoint,
|
|
72
105
|
maxRetries: options.maxRetries,
|
|
73
106
|
timeoutMs: options.timeoutMs,
|
|
74
107
|
});
|
|
75
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* A client from the environment: `LOAMS_ENDPOINT`, then `LOAMS_API_KEY` or
|
|
111
|
+
* `LOAMS_TOKEN`, then `LOAMS_NAMESPACE` (R1). `options` wins over the
|
|
112
|
+
* environment where both say something.
|
|
113
|
+
*/
|
|
114
|
+
static fromEnv(options = {}, environment = readEnvironment()) {
|
|
115
|
+
const endpoint = options.endpoint ?? environment.LOAMS_ENDPOINT;
|
|
116
|
+
if (endpoint === undefined || endpoint === '') {
|
|
117
|
+
throw new LoamsError('Loams.fromEnv: LOAMS_ENDPOINT is not set', {
|
|
118
|
+
code: Code.InvalidArgument,
|
|
119
|
+
metadata: { field: 'LOAMS_ENDPOINT' },
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const hasKey = (environment.LOAMS_API_KEY ?? '') !== '' || (environment.LOAMS_TOKEN ?? '') !== '';
|
|
123
|
+
const namespace = options.namespace ?? environment.LOAMS_NAMESPACE;
|
|
124
|
+
return new Loams({
|
|
125
|
+
...options,
|
|
126
|
+
endpoint,
|
|
127
|
+
...(options.auth === undefined && hasKey ? { auth: envToken(environment) } : {}),
|
|
128
|
+
...(namespace === undefined || namespace === '' ? {} : { namespace }),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* A handle on one collection: its document and search calls with
|
|
133
|
+
* `collection` (and `namespace`) filled in (SDKP §2.8). Holds no state.
|
|
134
|
+
*/
|
|
135
|
+
collection(name, options = {}) {
|
|
136
|
+
return new CollectionHandle(this.modules, name, options);
|
|
137
|
+
}
|
|
76
138
|
/** The session consistency token store, when `sessionConsistency` is on. */
|
|
77
139
|
get consistency() {
|
|
78
140
|
return this.#consistency;
|
|
@@ -130,6 +192,12 @@ export class Loams {
|
|
|
130
192
|
this.system.invalidate();
|
|
131
193
|
}
|
|
132
194
|
}
|
|
195
|
+
/** The process environment, where there is one (Node, Deno, Bun); empty in a
|
|
196
|
+
* browser. */
|
|
197
|
+
function readEnvironment() {
|
|
198
|
+
const scope = globalThis;
|
|
199
|
+
return scope.process?.env ?? {};
|
|
200
|
+
}
|
|
133
201
|
/**
|
|
134
202
|
* Builds one object per generated module: one function per generated call,
|
|
135
203
|
* each delegating to the same invoker.
|
|
@@ -141,11 +209,21 @@ export class Loams {
|
|
|
141
209
|
function buildModules(invoker) {
|
|
142
210
|
const modules = {};
|
|
143
211
|
for (const module of MODULES) {
|
|
144
|
-
|
|
145
|
-
|
|
212
|
+
// A module may span two services (`loams.graph` is `GraphAdminService`
|
|
213
|
+
// and `GraphService`), so every call's own service is registered.
|
|
214
|
+
const services = new Set([module.service, ...module.calls.map((call) => call.service)]);
|
|
215
|
+
let missing = false;
|
|
216
|
+
for (const service of services) {
|
|
217
|
+
const descriptor = SERVICE_DESCRIPTORS[service];
|
|
218
|
+
if (descriptor === undefined) {
|
|
219
|
+
missing = true;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
invoker.register(service, descriptor);
|
|
223
|
+
}
|
|
224
|
+
if (missing) {
|
|
146
225
|
continue;
|
|
147
226
|
}
|
|
148
|
-
invoker.register(module.service, descriptor);
|
|
149
227
|
const calls = {
|
|
150
228
|
module: module.name,
|
|
151
229
|
service: module.service,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CallOptions } from './call.js';
|
|
2
|
+
/** The default chunk bounds (§44 §7.5). */
|
|
3
|
+
export declare const DEFAULT_CHUNK_ROWS = 10000;
|
|
4
|
+
export declare const DEFAULT_CHUNK_BYTES: number;
|
|
5
|
+
/** How `writeBatch` chunks. */
|
|
6
|
+
export interface BatchOptions {
|
|
7
|
+
/** The most operations in one chunk. Default 10 000. */
|
|
8
|
+
chunkRows?: number;
|
|
9
|
+
/** The most bytes of operation JSON in one chunk. Default 4 MiB. A single
|
|
10
|
+
* operation larger than this goes in a chunk of its own. */
|
|
11
|
+
chunkBytes?: number;
|
|
12
|
+
}
|
|
13
|
+
/** What a finished batch reports. */
|
|
14
|
+
export interface BatchResult {
|
|
15
|
+
/** The merge of every chunk's consistency token. */
|
|
16
|
+
readonly token: string | undefined;
|
|
17
|
+
/** How many chunks were written. */
|
|
18
|
+
readonly chunks: number;
|
|
19
|
+
/** How many operations those chunks carried. */
|
|
20
|
+
readonly rows: number;
|
|
21
|
+
}
|
|
22
|
+
/** Sends one chunk; `idempotencyKey` is that chunk's own key. */
|
|
23
|
+
export type ChunkWriter<Op> = (ops: Op[], options: CallOptions & {
|
|
24
|
+
idempotencyKey: string;
|
|
25
|
+
}) => Promise<{
|
|
26
|
+
token?: string;
|
|
27
|
+
} | undefined>;
|
|
28
|
+
/** Splits `ops` into chunks and writes them one after another. */
|
|
29
|
+
export declare function writeBatch<Op>(write: ChunkWriter<Op>, ops: Iterable<Op> | AsyncIterable<Op>, options?: BatchOptions & CallOptions): Promise<BatchResult>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// `documents.writeBatch` (runtime contract R13, SDKP §2.3; design §44 §7.5).
|
|
2
|
+
//
|
|
3
|
+
// A bulk load as a loop of ordinary `WriteDocuments` calls, chunked by rows
|
|
4
|
+
// and by bytes:
|
|
5
|
+
//
|
|
6
|
+
// - **One chunk in flight at a time.** Order is preserved and a failure stops
|
|
7
|
+
// the load at a known point, which concurrency would blur.
|
|
8
|
+
// - **One idempotency key per chunk**, minted before the chunk's first attempt
|
|
9
|
+
// and reused on every retry of that chunk, so a retried chunk is the same
|
|
10
|
+
// write (D610) and two chunks are never mistaken for one.
|
|
11
|
+
// - **The tokens merge.** The result is the merge of every chunk's
|
|
12
|
+
// consistency token, so a read held to it sees the whole load.
|
|
13
|
+
// - **The first failed chunk stops the load** with a `BatchError` that says
|
|
14
|
+
// how many chunks (and documents) were applied before it, and carries their
|
|
15
|
+
// merged token. Those chunks stay applied; nothing is rolled back.
|
|
16
|
+
//
|
|
17
|
+
// Browser-safe: sizes are measured with `TextEncoder`, not `Buffer`.
|
|
18
|
+
import { Code } from '@connectrpc/connect';
|
|
19
|
+
import { ConsistencyToken } from './consistency.js';
|
|
20
|
+
import { BatchError, toLoamsError } from './errors.js';
|
|
21
|
+
import { uuidv7 } from './uuidv7.js';
|
|
22
|
+
/** The default chunk bounds (§44 §7.5). */
|
|
23
|
+
export const DEFAULT_CHUNK_ROWS = 10_000;
|
|
24
|
+
export const DEFAULT_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
25
|
+
const encoder = new TextEncoder();
|
|
26
|
+
function sizeOf(op) {
|
|
27
|
+
return encoder.encode(JSON.stringify(op, (_key, value) => typeof value === 'bigint' ? value.toString() : value)).length;
|
|
28
|
+
}
|
|
29
|
+
function mergeToken(current, next) {
|
|
30
|
+
if (next === undefined || next === '') {
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const parsed = ConsistencyToken.parse(next);
|
|
35
|
+
return current === undefined ? parsed.normalized() : current.merge(parsed);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return current;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Splits `ops` into chunks and writes them one after another. */
|
|
42
|
+
export async function writeBatch(write, ops, options = {}) {
|
|
43
|
+
const { chunkRows = DEFAULT_CHUNK_ROWS, chunkBytes = DEFAULT_CHUNK_BYTES, ...call } = options;
|
|
44
|
+
if (!(chunkRows > 0) || !(chunkBytes > 0)) {
|
|
45
|
+
throw new RangeError('writeBatch: chunkRows and chunkBytes must be positive');
|
|
46
|
+
}
|
|
47
|
+
let token;
|
|
48
|
+
let chunks = 0;
|
|
49
|
+
let rows = 0;
|
|
50
|
+
let pending = [];
|
|
51
|
+
let pendingBytes = 0;
|
|
52
|
+
const flush = async () => {
|
|
53
|
+
if (pending.length === 0) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const chunk = pending;
|
|
57
|
+
pending = [];
|
|
58
|
+
pendingBytes = 0;
|
|
59
|
+
try {
|
|
60
|
+
// The key is minted here, once per chunk, and the invoker reuses it on
|
|
61
|
+
// every retry of this chunk.
|
|
62
|
+
const answer = await write(chunk, { ...call, idempotencyKey: uuidv7() });
|
|
63
|
+
token = mergeToken(token, answer?.token);
|
|
64
|
+
}
|
|
65
|
+
catch (thrown) {
|
|
66
|
+
const cause = toLoamsError(thrown);
|
|
67
|
+
throw new BatchError(`writeBatch stopped at chunk ${chunks + 1}: ${chunks} chunks (${rows} operations) were applied before it; ${cause.message}`, {
|
|
68
|
+
code: cause.code === Code.Unknown ? Code.Aborted : cause.code,
|
|
69
|
+
reason: 'batch_partially_applied',
|
|
70
|
+
metadata: {
|
|
71
|
+
chunks_applied: String(chunks),
|
|
72
|
+
rows_applied: String(rows),
|
|
73
|
+
...(cause.reason === undefined ? {} : { cause_reason: cause.reason }),
|
|
74
|
+
},
|
|
75
|
+
rpc: cause.rpc,
|
|
76
|
+
cause,
|
|
77
|
+
chunksApplied: chunks,
|
|
78
|
+
rowsApplied: rows,
|
|
79
|
+
token: token?.toString(),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
chunks += 1;
|
|
83
|
+
rows += chunk.length;
|
|
84
|
+
};
|
|
85
|
+
for await (const op of ops) {
|
|
86
|
+
const size = sizeOf(op);
|
|
87
|
+
if (pending.length > 0 && (pending.length >= chunkRows || pendingBytes + size > chunkBytes)) {
|
|
88
|
+
await flush();
|
|
89
|
+
}
|
|
90
|
+
pending.push(op);
|
|
91
|
+
pendingBytes += size;
|
|
92
|
+
}
|
|
93
|
+
await flush();
|
|
94
|
+
return { token: token?.toString(), chunks, rows };
|
|
95
|
+
}
|
package/dist/runtime/call.d.ts
CHANGED
|
@@ -26,6 +26,14 @@ export interface CallOptions extends ConnectCallOptions {
|
|
|
26
26
|
/** The consistency token to read at, and the store to record writes in
|
|
27
27
|
* (§44 §7.4, D609). */
|
|
28
28
|
consistency?: ConsistencyOptions;
|
|
29
|
+
/**
|
|
30
|
+
* This call's deadline in milliseconds (runtime contract R12). Unary calls
|
|
31
|
+
* default to the client's `timeoutMs` (30 s); streams have none unless one is
|
|
32
|
+
* given here. `0` means no deadline. The whole call, retries included, has to
|
|
33
|
+
* fit: a call that runs out raises `DeadlineExceededError` with reason
|
|
34
|
+
* `client_deadline_exceeded` and is never retried.
|
|
35
|
+
*/
|
|
36
|
+
timeoutMs?: number;
|
|
29
37
|
}
|
|
30
38
|
/** How consistency tokens are carried (D609). */
|
|
31
39
|
export interface ConsistencyOptions {
|
|
@@ -33,15 +41,18 @@ export interface ConsistencyOptions {
|
|
|
33
41
|
* / `'eventual'` to read without waiting. */
|
|
34
42
|
token?: string;
|
|
35
43
|
/** Record the consistency token every response carries, so later reads are
|
|
36
|
-
* read-your-writes without the caller threading tokens by hand.
|
|
44
|
+
* read-your-writes without the caller threading tokens by hand. Unset means
|
|
45
|
+
* the client's session, when `sessionConsistency` is on; `false` opts this
|
|
46
|
+
* call out. */
|
|
37
47
|
session?: ConsistencyTokenStore | true | false;
|
|
38
48
|
}
|
|
39
49
|
/** A session's merged consistency token (D609). Off unless a call opts in. */
|
|
40
50
|
export interface ConsistencyTokenStore {
|
|
41
|
-
/** The token to attach to the next read
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
/** The token to attach to the next read in `scope` (a proto package), or
|
|
52
|
+
* undefined for none. */
|
|
53
|
+
current(scope?: string): string | undefined;
|
|
54
|
+
/** Folds a token the server returned, in `scope`, into the session's. */
|
|
55
|
+
record(token: string | undefined, scope?: string): void;
|
|
45
56
|
}
|
|
46
57
|
/** One attempt of a call, as the transport sees it. */
|
|
47
58
|
export type Send = (attempt: Attempt) => Promise<unknown>;
|
|
@@ -60,6 +71,8 @@ export interface RetryPlan {
|
|
|
60
71
|
readonly maxRetries: number;
|
|
61
72
|
readonly signal?: AbortSignal;
|
|
62
73
|
readonly onRefresh?: () => Promise<void>;
|
|
74
|
+
/** True once the call's own deadline has passed: no further attempt. */
|
|
75
|
+
readonly expired?: () => boolean;
|
|
63
76
|
}
|
|
64
77
|
/**
|
|
65
78
|
* Runs `send` until it answers or the plan says stop, and returns whatever it
|
|
@@ -87,6 +100,31 @@ declared?: boolean): {
|
|
|
87
100
|
request: unknown;
|
|
88
101
|
keyed: boolean;
|
|
89
102
|
};
|
|
103
|
+
/** The unary deadline when neither the client nor the call sets one (R12). */
|
|
104
|
+
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
105
|
+
/** The response header a consistency token is read from (R4). */
|
|
106
|
+
export declare const CONSISTENCY_TOKEN_HEADER = "loams-consistency-token";
|
|
107
|
+
/**
|
|
108
|
+
* The packages whose `namespace` field is a Loams namespace (R11) and whose
|
|
109
|
+
* `consistency` field reads at a consistency token (R4). Other packages have
|
|
110
|
+
* fields of the same name that mean something else: `loams.live.v1`'s
|
|
111
|
+
* `CreateAppRequest.namespace` is free text naming an app's environment, and
|
|
112
|
+
* must never be filled from, or refused for lack of, the client's default.
|
|
113
|
+
*/
|
|
114
|
+
export declare const APP_PACKAGES: ReadonlySet<string>;
|
|
115
|
+
/**
|
|
116
|
+
* The packages whose reads take the session's token as `at_least` (R4,
|
|
117
|
+
* SDKP-17): collections and SQL. A graph's token names one graph's stream and
|
|
118
|
+
* a Graph read refuses any other, so graph reads never take the session's.
|
|
119
|
+
*/
|
|
120
|
+
export declare const SESSION_PACKAGES: ReadonlySet<string>;
|
|
121
|
+
/** Options the invoker applies to every call (R11, R12). */
|
|
122
|
+
export interface InvokerDefaults {
|
|
123
|
+
/** The namespace a request with an empty `namespace` field is sent with. */
|
|
124
|
+
namespace?: string;
|
|
125
|
+
/** The unary deadline; `0` disables it. */
|
|
126
|
+
timeoutMs?: number;
|
|
127
|
+
}
|
|
90
128
|
/**
|
|
91
129
|
* The client's call path: a generated `CallBinding` plus a request becomes a
|
|
92
130
|
* response message, with everything the SDK owns applied on the way.
|
|
@@ -98,17 +136,31 @@ export declare class CallInvoker {
|
|
|
98
136
|
private readonly consistency;
|
|
99
137
|
private readonly clients;
|
|
100
138
|
private readonly descriptors;
|
|
101
|
-
/**
|
|
102
|
-
private readonly
|
|
103
|
-
|
|
139
|
+
/** What each RPC's request schema declares, per RPC. */
|
|
140
|
+
private readonly shapes;
|
|
141
|
+
private readonly defaults;
|
|
142
|
+
constructor(transport: Transport, tokenSource: TokenSource | undefined, maxRetries: number, consistency: ConsistencyTokenStore | undefined, defaults?: InvokerDefaults);
|
|
143
|
+
/** The client's default namespace, if it has one (R11). */
|
|
144
|
+
get namespace(): string | undefined;
|
|
104
145
|
/**
|
|
105
146
|
* A module's Connect client, built once from the service descriptor
|
|
106
147
|
* `protoc-gen-es` generated. This is what makes the facade generated rather
|
|
107
148
|
* than hand-written: the SDK never writes a client, it wraps the stubs.
|
|
108
149
|
*/
|
|
109
150
|
register(service: string, descriptor: DescService): void;
|
|
151
|
+
/** What this call's request message declares. */
|
|
152
|
+
private shape;
|
|
110
153
|
/** Whether this call's request message declares an `idempotency_key`. */
|
|
111
154
|
private takesIdempotencyKey;
|
|
155
|
+
/**
|
|
156
|
+
* The request as it goes out: the default namespace filled in (R11) and the
|
|
157
|
+
* session's token set as `consistency.at_least` when the caller left
|
|
158
|
+
* `consistency` unset (R4). A request the caller set either on is left as
|
|
159
|
+
* written: an explicit argument always wins.
|
|
160
|
+
*/
|
|
161
|
+
private prepare;
|
|
162
|
+
/** The deadline a unary call runs under, in milliseconds, or undefined. */
|
|
163
|
+
private unaryTimeout;
|
|
112
164
|
/** The connect-es method a binding names, or a clear internal error. */
|
|
113
165
|
private method;
|
|
114
166
|
/** One unary call: the whole runtime contract, applied to one binding. */
|
|
@@ -127,6 +179,8 @@ export declare class CallInvoker {
|
|
|
127
179
|
stream(binding: CallBinding, request: unknown, options?: CallOptions): AsyncIterable<unknown>;
|
|
128
180
|
/** Wraps an iterable so every throw becomes a `LoamsError`. */
|
|
129
181
|
private mapped;
|
|
130
|
-
/** Merges
|
|
182
|
+
/** Merges every consistency token a response carried into the session:
|
|
183
|
+
* `consistency_token`, `token` and `read_token`, and the
|
|
184
|
+
* `loams-consistency-token` header (R4, SDKP §2.1). */
|
|
131
185
|
private recordConsistency;
|
|
132
186
|
}
|