marcidb-client 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/marci-generate-darwin-arm64 +0 -0
- package/bin/marci-generate-linux-x64 +0 -0
- package/bin/marci-generate-win32-x64.exe +0 -0
- package/bin/marci-migrate-darwin-arm64 +0 -0
- package/bin/marci-migrate-linux-x64 +0 -0
- package/bin/marci-migrate-win32-x64.exe +0 -0
- package/package.json +1 -1
- package/runtime/index.d.ts +40 -3
- package/runtime/index.js +47 -10
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/runtime/index.d.ts
CHANGED
|
@@ -207,6 +207,8 @@ type MarciOp = {
|
|
|
207
207
|
query?: any;
|
|
208
208
|
data?: any;
|
|
209
209
|
id?: any;
|
|
210
|
+
key?: any;
|
|
211
|
+
onInsert?: any;
|
|
210
212
|
journal?: JournalArgs;
|
|
211
213
|
};
|
|
212
214
|
/** The arguments of a journal action (`$journalOpen` / `$journalRead` / `$journalDrop`). `wait` is in seconds. */
|
|
@@ -275,9 +277,11 @@ interface Query<
|
|
|
275
277
|
readonly [__sel]: Effective<T, Sel>;
|
|
276
278
|
/** Filter (marcidb `$where`). Repeated calls are ANDed. */
|
|
277
279
|
where(where: T["where"]): Query<T, Sel>;
|
|
278
|
-
/** Sort
|
|
280
|
+
/** Sort: `order("age", "desc")` or `order({ age: "desc" })`. A second `order()` adds the next key — ties on
|
|
281
|
+
* the first are broken by it (`order("createdAt", "desc").order("id")`); the keys may also come as an array
|
|
282
|
+
* in one call. Several keys sort in memory. */
|
|
279
283
|
order(field: keyof T["order"] & string, direction?: "asc" | "desc"): Query<T, Sel>;
|
|
280
|
-
order(order: T["order"]): Query<T, Sel>;
|
|
284
|
+
order(order: T["order"] | T["order"][]): Query<T, Sel>;
|
|
281
285
|
limit(n: number): Query<T, Sel>;
|
|
282
286
|
skip(n: number): Query<T, Sel>;
|
|
283
287
|
/** Keyset cursor: rows strictly after this id in the current order (`$cursor`). */
|
|
@@ -302,7 +306,26 @@ interface Query<
|
|
|
302
306
|
}>;
|
|
303
307
|
aggregate<A extends T["aggregate"]>(query: A): Op<AggregateResult<T["model"], A>> & Sub<T["name"], NoInfer<A>>;
|
|
304
308
|
insert(data: T["insert"]): Op<T["id"]>;
|
|
309
|
+
/** The new row by `select`'s shape — its defaults, dates and files as stored: one call, nothing to read back. */
|
|
310
|
+
insert<S extends T["query"]>(data: T["insert"], options: {
|
|
311
|
+
select: S;
|
|
312
|
+
}): Promise<Rows<T, S>>;
|
|
305
313
|
update(id: IdArg<T["id"]>, data: T["update"]): Op<void>;
|
|
314
|
+
/** Writes the row `key` names — the id, or the fields of ONE unique index (a `@unique` field, all the fields of
|
|
315
|
+
* a `@unique(<name>)` group): an update of `data` when it exists, an insert of key + data when not. `data` is
|
|
316
|
+
* in the insert form (plain values, `{ id }` for a relation; no `$` operators). `onInsert` adds fields used
|
|
317
|
+
* only when the row is created. Resolves to the id, like `insert`. */
|
|
318
|
+
upsert<K extends Partial<T["insert"]> | T["id"]>(key: K, data: Omit<T["insert"], keyof K>, options?: {
|
|
319
|
+
onInsert?: Partial<T["insert"]>;
|
|
320
|
+
}): Op<T["id"]>;
|
|
321
|
+
/** The row written, by `select`'s shape — as stored, whichever path the upsert took. */
|
|
322
|
+
upsert<
|
|
323
|
+
K extends Partial<T["insert"]> | T["id"],
|
|
324
|
+
S extends T["query"]
|
|
325
|
+
>(key: K, data: Omit<T["insert"], keyof K>, options: {
|
|
326
|
+
onInsert?: Partial<T["insert"]>;
|
|
327
|
+
select: S;
|
|
328
|
+
}): Promise<Rows<T, S>>;
|
|
306
329
|
/** Applies `data` to every row the chain's `where` matches (all rows without one); resolves to the number of rows. */
|
|
307
330
|
updateMany(data: T["update"]): Op<number>;
|
|
308
331
|
/** @deprecated pass the filter through the chain: `db.user.where(w).updateMany(data)` (removed in the next minor). */
|
|
@@ -376,6 +399,9 @@ type QueryLayerOptions = {
|
|
|
376
399
|
run(op: MarciOp): Promise<any>;
|
|
377
400
|
/** Hook for `insert` payloads (the lecodes SDK generates uuid ids here). */
|
|
378
401
|
prepareInsert?(model: string, data: any): any;
|
|
402
|
+
/** Hook for the `onInsert` part of an `upsert` — the fields used only when the row is created (the lecodes SDK
|
|
403
|
+
* puts a uuid id here). Receives what the caller gave (or undefined), returns what is sent. */
|
|
404
|
+
prepareUpsert?(model: string, onInsert: any): any;
|
|
379
405
|
};
|
|
380
406
|
/**
|
|
381
407
|
* The query builder over a set of models and a `run` function. Returns `op(descriptor)` — a lazy operation
|
|
@@ -386,6 +412,17 @@ declare function createQueryLayer(options: QueryLayerOptions): {
|
|
|
386
412
|
op: (descriptor: MarciOp) => any;
|
|
387
413
|
collection: (model: string) => any;
|
|
388
414
|
};
|
|
415
|
+
/** The class of a refused request — the server's `kind`: a constraint (`unique`, `foreign_key`, `restrict`,
|
|
416
|
+
* `duplicate_id`), `not_found`, `bad_request`, `unauthorized`, or `internal` for a fault of the server. */
|
|
417
|
+
type MarciErrorKind = "unique" | "foreign_key" | "restrict" | "duplicate_id" | "not_found" | "bad_request" | "unauthorized" | "internal";
|
|
418
|
+
/** A failed request: `kind` is what to switch on, `field` the `Model.field` a constraint is about, the message is for a person. */
|
|
419
|
+
declare class MarciError extends Error {
|
|
420
|
+
name: "MarciError";
|
|
421
|
+
readonly status: number;
|
|
422
|
+
readonly kind: MarciErrorKind;
|
|
423
|
+
readonly field?: string;
|
|
424
|
+
constructor(status: number, message: string, kind: MarciErrorKind, field?: string);
|
|
425
|
+
}
|
|
389
426
|
declare function request(method: string, url: string, body?: any): Promise<any>;
|
|
390
427
|
/**
|
|
391
428
|
* A binary-eligible read over HTTP. Advertises `Accept: <binary>, json` plus the client's schema
|
|
@@ -408,4 +445,4 @@ declare function createDecoderRegistry(MODELS: Record<string, FieldDesc[]>): {
|
|
|
408
445
|
decodeBuffer(decoder: (reader: any) => any, bytes: Uint8Array, many: boolean): any;
|
|
409
446
|
};
|
|
410
447
|
declare function encodeId(id: any): string;
|
|
411
|
-
export { AggregateKeys, AggregateResult, BareId, Collection, CompareNumValue, CompareRefListValue, CompareRefValue, CompareStrValue, CompareValue, CustomSearch, CustomSearchValue, Effective, FieldDesc, FieldOps, FullTextSearch, GetResult, IdArg, Journal, JournalArgs, JournalEntry, JournalOp, JournalOptions, JsonCondition, JsonPathWhere, JsonType, JsonValue, MarciOp, MarciTransport, ModelTypes, ModelsMeta, Never, NumOps, Only, Op, PrimitiveListUpdate, Query, QueryLayerOptions, RefListUpdate, RefListUpdateOrdered, RefListUpdateStruct, RefUpdate, RefUpdateStruct, Resolve, Rows, ServiceKeys, StrOps, Sub, UpdateNumValue, ValueOps, VectorSearch, WhereValue, createDecoderRegistry, createQueryLayer, encodeId, request, requestBinary };
|
|
448
|
+
export { AggregateKeys, AggregateResult, BareId, Collection, CompareNumValue, CompareRefListValue, CompareRefValue, CompareStrValue, CompareValue, CustomSearch, CustomSearchValue, Effective, FieldDesc, FieldOps, FullTextSearch, GetResult, IdArg, Journal, JournalArgs, JournalEntry, JournalOp, JournalOptions, JsonCondition, JsonPathWhere, JsonType, JsonValue, MarciError, MarciErrorKind, MarciOp, MarciTransport, ModelTypes, ModelsMeta, Never, NumOps, Only, Op, PrimitiveListUpdate, Query, QueryLayerOptions, RefListUpdate, RefListUpdateOrdered, RefListUpdateStruct, RefUpdate, RefUpdateStruct, Resolve, Rows, ServiceKeys, StrOps, Sub, UpdateNumValue, ValueOps, VectorSearch, WhereValue, createDecoderRegistry, createQueryLayer, encodeId, request, requestBinary };
|
package/runtime/index.js
CHANGED
|
@@ -101,6 +101,11 @@ function createQueryLayer(options) {
|
|
|
101
101
|
return w ? { $where: w } : {};
|
|
102
102
|
};
|
|
103
103
|
const findMany = () => ({ model, action: "findMany", query: build() });
|
|
104
|
+
const readBack = (written, select) => written.then((id) => run({ model, action: "findFirst", query: resolveShape(model, { ...select, $where: id }) })).then((row) => {
|
|
105
|
+
if (row === null)
|
|
106
|
+
throw new Error(`marcidb: ${model}: the row just written is gone`);
|
|
107
|
+
return row;
|
|
108
|
+
});
|
|
104
109
|
return {
|
|
105
110
|
get __op() {
|
|
106
111
|
return findMany();
|
|
@@ -112,7 +117,12 @@ function createQueryLayer(options) {
|
|
|
112
117
|
catch: (onRejected) => run(findMany()).catch(onRejected),
|
|
113
118
|
finally: (onFinally) => run(findMany()).finally(onFinally),
|
|
114
119
|
where: (where) => make({ ...st, where: and(st.where, where) }),
|
|
115
|
-
order: (field, direction) =>
|
|
120
|
+
order: (field, direction) => {
|
|
121
|
+
const given = typeof field === "string" ? [{ [field]: direction ?? "asc" }] : Array.isArray(field) ? field : Object.entries(field).map(([k, v]) => ({ [k]: v }));
|
|
122
|
+
const prev = st.order === undefined ? [] : Array.isArray(st.order) ? st.order : [st.order];
|
|
123
|
+
const keys = [...prev, ...given];
|
|
124
|
+
return make({ ...st, order: keys.length === 1 ? keys[0] : keys });
|
|
125
|
+
},
|
|
116
126
|
limit: (n) => make({ ...st, limit: n }),
|
|
117
127
|
skip: (n) => make({ ...st, skip: n }),
|
|
118
128
|
after: (id) => make({ ...st, cursor: idObject(model, id) }),
|
|
@@ -128,8 +138,16 @@ function createQueryLayer(options) {
|
|
|
128
138
|
delete q.$where;
|
|
129
139
|
return Object.assign(op({ model, action: "aggregate", query: q }), { __select: q });
|
|
130
140
|
},
|
|
131
|
-
insert: (data) =>
|
|
141
|
+
insert: (data, extra) => {
|
|
142
|
+
const inserted = op({ model, action: "insert", data: options.prepareInsert ? options.prepareInsert(model, data) : data });
|
|
143
|
+
return extra?.select ? readBack(inserted, extra.select) : inserted;
|
|
144
|
+
},
|
|
132
145
|
update: (id, data) => op({ model, action: "update", id: idObject(model, id), data }),
|
|
146
|
+
upsert: (key, data, o) => {
|
|
147
|
+
const onInsert = options.prepareUpsert ? options.prepareUpsert(model, o?.onInsert) : o?.onInsert;
|
|
148
|
+
const written = op({ model, action: "upsert", key, data, ...onInsert !== undefined ? { onInsert } : {} });
|
|
149
|
+
return o?.select ? readBack(written, o.select) : written;
|
|
150
|
+
},
|
|
133
151
|
updateMany: (a, b) => b === undefined ? op({ model, action: "updateMany", query: whereOnly(), data: a }) : op({ model, action: "updateMany", query: whereOnly(a), data: b }),
|
|
134
152
|
delete: (id) => op({ model, action: "delete", id: idObject(model, id) }),
|
|
135
153
|
deleteMany: () => {
|
|
@@ -149,16 +167,36 @@ function createQueryLayer(options) {
|
|
|
149
167
|
}
|
|
150
168
|
|
|
151
169
|
// src/index.ts
|
|
170
|
+
class MarciError extends Error {
|
|
171
|
+
name = "MarciError";
|
|
172
|
+
status;
|
|
173
|
+
kind;
|
|
174
|
+
field;
|
|
175
|
+
constructor(status, message, kind, field) {
|
|
176
|
+
super(message);
|
|
177
|
+
this.status = status;
|
|
178
|
+
this.kind = kind;
|
|
179
|
+
this.field = field;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
var kindOfStatus = (status) => status === 404 ? "not_found" : status === 401 ? "unauthorized" : status >= 500 ? "internal" : "bad_request";
|
|
183
|
+
async function failure(res) {
|
|
184
|
+
const text = await res.text();
|
|
185
|
+
try {
|
|
186
|
+
const body = JSON.parse(text);
|
|
187
|
+
if (body && typeof body.error === "string")
|
|
188
|
+
return new MarciError(res.status, body.error, body.kind ?? kindOfStatus(res.status), body.field);
|
|
189
|
+
} catch {}
|
|
190
|
+
return new MarciError(res.status, text || `HTTP ${res.status}`, kindOfStatus(res.status));
|
|
191
|
+
}
|
|
152
192
|
async function request(method, url, body) {
|
|
153
193
|
const res = await fetch(url, {
|
|
154
194
|
method,
|
|
155
195
|
headers: { "Content-Type": "application/json" },
|
|
156
196
|
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
157
197
|
});
|
|
158
|
-
if (!res.ok)
|
|
159
|
-
|
|
160
|
-
throw new Error(`MarciDB [${res.status}]: ${text}`);
|
|
161
|
-
}
|
|
198
|
+
if (!res.ok)
|
|
199
|
+
throw await failure(res);
|
|
162
200
|
return res.json();
|
|
163
201
|
}
|
|
164
202
|
var BINARY_MEDIA_TYPE = "application/x-marcidb-rows";
|
|
@@ -172,10 +210,8 @@ async function requestBinary(url, body, schemaHash) {
|
|
|
172
210
|
},
|
|
173
211
|
body: JSON.stringify(body ?? {})
|
|
174
212
|
});
|
|
175
|
-
if (!res.ok)
|
|
176
|
-
|
|
177
|
-
throw new Error(`MarciDB [${res.status}]: ${text}`);
|
|
178
|
-
}
|
|
213
|
+
if (!res.ok)
|
|
214
|
+
throw await failure(res);
|
|
179
215
|
const contentType = res.headers.get("content-type") ?? "";
|
|
180
216
|
if (contentType.includes(BINARY_MEDIA_TYPE)) {
|
|
181
217
|
return new Uint8Array(await res.arrayBuffer());
|
|
@@ -377,6 +413,7 @@ function encodeId(id) {
|
|
|
377
413
|
return String(id);
|
|
378
414
|
}
|
|
379
415
|
export {
|
|
416
|
+
MarciError,
|
|
380
417
|
createDecoderRegistry,
|
|
381
418
|
createQueryLayer,
|
|
382
419
|
encodeId,
|