@primate/jsondb 0.1.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/LICENSE +19 -0
- package/lib/private/ColumnTypes.d.ts +13 -0
- package/lib/private/ColumnTypes.js +2 -0
- package/lib/private/JSONDB.d.ts +38 -0
- package/lib/private/JSONDB.js +436 -0
- package/lib/private/typemap.d.ts +5 -0
- package/lib/private/typemap.js +33 -0
- package/lib/public/index.d.ts +4 -0
- package/lib/public/index.js +3 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) Terrablue <terrablue@proton.me> and contributors.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
16
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { JSONValue } from "@rcompat/type";
|
|
2
|
+
type ColumnTypes = {
|
|
3
|
+
BIGINT: bigint;
|
|
4
|
+
BLOB: Blob;
|
|
5
|
+
BOOLEAN: boolean;
|
|
6
|
+
DATE: Date;
|
|
7
|
+
JSON: JSONValue;
|
|
8
|
+
NUMBER: number;
|
|
9
|
+
STRING: string;
|
|
10
|
+
URL: URL;
|
|
11
|
+
};
|
|
12
|
+
export type { ColumnTypes as default };
|
|
13
|
+
//# sourceMappingURL=ColumnTypes.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { As, DataDict, DB, Schema, Sort, With } from "@primate/core/db";
|
|
2
|
+
import type { Dict, PartialDict } from "@rcompat/type";
|
|
3
|
+
type Tables = PartialDict<Dict[]>;
|
|
4
|
+
declare const config_schema: import("pema").ObjectType<import("pema").NormalizeSchemaObject<{
|
|
5
|
+
readonly directory: import("pema").DefaultType<import("pema").StringType, "data">;
|
|
6
|
+
}>, {
|
|
7
|
+
directory: string;
|
|
8
|
+
}>;
|
|
9
|
+
export default class JSONDB implements DB<Tables> {
|
|
10
|
+
#private;
|
|
11
|
+
static config: typeof config_schema.input;
|
|
12
|
+
constructor(config?: typeof config_schema.input);
|
|
13
|
+
get client(): Tables;
|
|
14
|
+
get schema(): Schema;
|
|
15
|
+
close(): void;
|
|
16
|
+
create<O extends Dict>(as: As, record: Dict): Promise<O>;
|
|
17
|
+
read(as: As, args: {
|
|
18
|
+
count: true;
|
|
19
|
+
where: DataDict;
|
|
20
|
+
with?: never;
|
|
21
|
+
}): Promise<number>;
|
|
22
|
+
read(as: As, args: {
|
|
23
|
+
where: DataDict;
|
|
24
|
+
fields?: string[];
|
|
25
|
+
limit?: number;
|
|
26
|
+
sort?: Sort;
|
|
27
|
+
with?: With;
|
|
28
|
+
}): Promise<Dict[]>;
|
|
29
|
+
update(as: As, args: {
|
|
30
|
+
set: DataDict;
|
|
31
|
+
where: DataDict;
|
|
32
|
+
}): Promise<number>;
|
|
33
|
+
delete(as: As, args: {
|
|
34
|
+
where: DataDict;
|
|
35
|
+
}): Promise<number>;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=JSONDB.d.ts.map
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import E from "@primate/core/db/errors";
|
|
2
|
+
import assert from "@rcompat/assert";
|
|
3
|
+
import fs from "@rcompat/fs";
|
|
4
|
+
import is from "@rcompat/is";
|
|
5
|
+
import p from "pema";
|
|
6
|
+
function escape_re(s) {
|
|
7
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8
|
+
}
|
|
9
|
+
function comparable(x) {
|
|
10
|
+
if (is.date(x))
|
|
11
|
+
return x.getTime();
|
|
12
|
+
if (is.url(x))
|
|
13
|
+
return x.href;
|
|
14
|
+
return x;
|
|
15
|
+
}
|
|
16
|
+
function like_re(pattern, flags = "") {
|
|
17
|
+
return new RegExp("^" +
|
|
18
|
+
escape_re(pattern)
|
|
19
|
+
.replace(/\\%/g, "<<PERCENT>>")
|
|
20
|
+
.replace(/\\_/g, "<<UNDERSCORE>>")
|
|
21
|
+
.replace(/%/g, "[\\s\\S]*")
|
|
22
|
+
.replace(/_/g, "[\\s\\S]")
|
|
23
|
+
.replace(/<<PERCENT>>/g, "%")
|
|
24
|
+
.replace(/<<UNDERSCORE>>/g, "_") +
|
|
25
|
+
"$", flags);
|
|
26
|
+
}
|
|
27
|
+
function match(record, where) {
|
|
28
|
+
return Object.entries(where).every(([k, v]) => {
|
|
29
|
+
const value = record[k];
|
|
30
|
+
if (v === null)
|
|
31
|
+
return value === null || value === undefined;
|
|
32
|
+
if (is.dict(v)) {
|
|
33
|
+
if (value === null || value === undefined)
|
|
34
|
+
return false;
|
|
35
|
+
for (const [op, op_val] of Object.entries(v)) {
|
|
36
|
+
switch (op) {
|
|
37
|
+
case "$like": {
|
|
38
|
+
if (!is.string(value))
|
|
39
|
+
return false;
|
|
40
|
+
if (!like_re(op_val).test(value))
|
|
41
|
+
return false;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
case "$ilike": {
|
|
45
|
+
if (!is.string(value))
|
|
46
|
+
return false;
|
|
47
|
+
if (!like_re(op_val, "i").test(value))
|
|
48
|
+
return false;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case "$gte":
|
|
52
|
+
if (!(comparable(value) >= comparable(op_val)))
|
|
53
|
+
return false;
|
|
54
|
+
break;
|
|
55
|
+
case "$gt":
|
|
56
|
+
case "$after":
|
|
57
|
+
if (!(comparable(value) > comparable(op_val)))
|
|
58
|
+
return false;
|
|
59
|
+
break;
|
|
60
|
+
case "$lte":
|
|
61
|
+
if (!(comparable(value) <= comparable(op_val)))
|
|
62
|
+
return false;
|
|
63
|
+
break;
|
|
64
|
+
case "$lt":
|
|
65
|
+
case "$before":
|
|
66
|
+
if (!(comparable(value) < comparable(op_val)))
|
|
67
|
+
return false;
|
|
68
|
+
break;
|
|
69
|
+
case "$ne": {
|
|
70
|
+
if (is.date(value) && is.date(op_val)) {
|
|
71
|
+
if (value.getTime() === op_val.getTime())
|
|
72
|
+
return false;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
if (is.url(value) && is.url(op_val)) {
|
|
76
|
+
if (value.href === op_val.href)
|
|
77
|
+
return false;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
if (value === op_val)
|
|
81
|
+
return false;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "$in": {
|
|
85
|
+
const arr = op_val;
|
|
86
|
+
if (is.date(value)) {
|
|
87
|
+
if (!arr.some(v => is.date(v) && v.getTime() === value.getTime()))
|
|
88
|
+
return false;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
if (is.url(value)) {
|
|
92
|
+
if (!arr.some(v => is.url(v) && v.href === value.href))
|
|
93
|
+
return false;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
if (!arr.includes(value))
|
|
97
|
+
return false;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
default:
|
|
101
|
+
throw E.operator_unknown(k, op);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (is.date(value) && is.date(v))
|
|
107
|
+
return value.getTime() === v.getTime();
|
|
108
|
+
if (is.url(value) && is.url(v))
|
|
109
|
+
return value.href === v.href;
|
|
110
|
+
return value === v;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function filter(record, fields) {
|
|
114
|
+
return Object.fromEntries(Object.entries(record).filter(([key]) => fields.includes(key)));
|
|
115
|
+
}
|
|
116
|
+
function to_sorted(d1, d2, sort) {
|
|
117
|
+
return Object.entries(sort)
|
|
118
|
+
.map(([k, value]) => [k, value === "asc" ? 1 : -1])
|
|
119
|
+
.reduce((sorting, [field, direction]) => {
|
|
120
|
+
const left = d1[field];
|
|
121
|
+
const right = d2[field];
|
|
122
|
+
if (sorting !== 0)
|
|
123
|
+
return sorting;
|
|
124
|
+
if (left === right)
|
|
125
|
+
return sorting;
|
|
126
|
+
if (left < right)
|
|
127
|
+
return -1 * direction;
|
|
128
|
+
return direction;
|
|
129
|
+
}, 0);
|
|
130
|
+
}
|
|
131
|
+
// JSON serialization for types that JSON can't represent natively
|
|
132
|
+
async function prepare_for_json(rows) {
|
|
133
|
+
return Promise.all(rows.map(async (row) => {
|
|
134
|
+
const out = {};
|
|
135
|
+
for (const [key, value] of Object.entries(row)) {
|
|
136
|
+
if (typeof value === "bigint") {
|
|
137
|
+
out[key] = { __type: "bigint", value: String(value) };
|
|
138
|
+
}
|
|
139
|
+
else if (value instanceof Blob) {
|
|
140
|
+
out[key] = { __type: "blob", value: (await value.bytes()).toBase64() };
|
|
141
|
+
}
|
|
142
|
+
else if (is.date(value)) {
|
|
143
|
+
out[key] = { __type: "datetime", value: value.toISOString() };
|
|
144
|
+
}
|
|
145
|
+
else if (is.url(value)) {
|
|
146
|
+
out[key] = { __type: "url", value: value.href };
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
out[key] = value;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
function revive_from_json(rows) {
|
|
156
|
+
return rows.map(row => {
|
|
157
|
+
const out = {};
|
|
158
|
+
for (const [key, value] of Object.entries(row)) {
|
|
159
|
+
if (is.dict(value) && "__type" in value && "value" in value) {
|
|
160
|
+
switch (value.__type) {
|
|
161
|
+
case "bigint":
|
|
162
|
+
out[key] = BigInt(value.value);
|
|
163
|
+
break;
|
|
164
|
+
case "blob": {
|
|
165
|
+
out[key] = new Blob([Uint8Array.fromBase64(value.value)], { type: "application/octet-stream" });
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
case "datetime":
|
|
169
|
+
out[key] = new Date(value.value);
|
|
170
|
+
break;
|
|
171
|
+
case "url":
|
|
172
|
+
out[key] = new URL(value.value);
|
|
173
|
+
break;
|
|
174
|
+
default:
|
|
175
|
+
out[key] = value;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
out[key] = value;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const config_schema = p({
|
|
186
|
+
directory: p.string.default("data"),
|
|
187
|
+
});
|
|
188
|
+
export default class JSONDB {
|
|
189
|
+
static config;
|
|
190
|
+
#directory;
|
|
191
|
+
#tables = {};
|
|
192
|
+
#types = {};
|
|
193
|
+
constructor(config) {
|
|
194
|
+
const parsed = config_schema.parse(config);
|
|
195
|
+
this.#directory = fs.ref(parsed.directory);
|
|
196
|
+
}
|
|
197
|
+
get client() {
|
|
198
|
+
return this.#tables;
|
|
199
|
+
}
|
|
200
|
+
#ref(table) {
|
|
201
|
+
return this.#directory.join(`${table}.json`);
|
|
202
|
+
}
|
|
203
|
+
async #load(table) {
|
|
204
|
+
if (this.#tables[table] !== undefined)
|
|
205
|
+
return;
|
|
206
|
+
const ref = this.#ref(table);
|
|
207
|
+
if (await ref.exists()) {
|
|
208
|
+
const text = await ref.text();
|
|
209
|
+
const raw = JSON.parse(text);
|
|
210
|
+
this.#tables[table] = revive_from_json(raw);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
this.#tables[table] = [];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async #persist(table) {
|
|
217
|
+
const rows = this.#use(table);
|
|
218
|
+
const prepared = await prepare_for_json(rows);
|
|
219
|
+
const text = JSON.stringify(prepared, null, 2);
|
|
220
|
+
if (!await this.#directory.exists()) {
|
|
221
|
+
await this.#directory.create();
|
|
222
|
+
}
|
|
223
|
+
await this.#ref(table).write(text);
|
|
224
|
+
}
|
|
225
|
+
#use(table) {
|
|
226
|
+
this.#tables[table] ??= [];
|
|
227
|
+
return this.#tables[table];
|
|
228
|
+
}
|
|
229
|
+
get schema() {
|
|
230
|
+
return {
|
|
231
|
+
create: async (table, _pk, types) => {
|
|
232
|
+
await this.#load(table);
|
|
233
|
+
this.#types[table] = types;
|
|
234
|
+
},
|
|
235
|
+
delete: async (table) => {
|
|
236
|
+
delete this.#tables[table];
|
|
237
|
+
delete this.#types[table];
|
|
238
|
+
const ref = this.#ref(table);
|
|
239
|
+
if (await ref.exists()) {
|
|
240
|
+
await ref.remove();
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
introspect: (table) => {
|
|
244
|
+
if (this.#tables[table] === undefined)
|
|
245
|
+
return null;
|
|
246
|
+
const types = this.#types[table] ?? {};
|
|
247
|
+
return Object.fromEntries(Object.entries(types).map(([field, type]) => [field, [type]]));
|
|
248
|
+
},
|
|
249
|
+
alter: (table, diff) => {
|
|
250
|
+
if (this.#types[table] === undefined)
|
|
251
|
+
throw E.table_not_found(table);
|
|
252
|
+
const types = { ...this.#types[table] };
|
|
253
|
+
const { add, drop, rename } = diff;
|
|
254
|
+
for (const [field, type] of Object.entries(add))
|
|
255
|
+
types[field] = type;
|
|
256
|
+
for (const field of drop)
|
|
257
|
+
delete types[field];
|
|
258
|
+
for (const [from, to] of rename) {
|
|
259
|
+
types[to] = types[from];
|
|
260
|
+
delete types[from];
|
|
261
|
+
}
|
|
262
|
+
this.#types[table] = types;
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
close() { }
|
|
267
|
+
#next_id(as) {
|
|
268
|
+
const table = this.#use(as.table);
|
|
269
|
+
const size = table.length;
|
|
270
|
+
const pk = assert.defined(as.pk);
|
|
271
|
+
const type = as.types[pk];
|
|
272
|
+
if (type === "uuid" || type === "uuid_v4" || type === "uuid_v7") {
|
|
273
|
+
return crypto.randomUUID();
|
|
274
|
+
}
|
|
275
|
+
else if (["u64", "u128", "i64", "i128"].includes(type)) {
|
|
276
|
+
return size === 0 ? 0n : table[size - 1][pk] + 1n;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
return size === 0 ? 0 : table[size - 1][pk] + 1;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async create(as, record) {
|
|
283
|
+
assert.nonempty(record);
|
|
284
|
+
await this.#load(as.table);
|
|
285
|
+
const table = this.#use(as.table);
|
|
286
|
+
const pk = as.pk;
|
|
287
|
+
let to_insert = record;
|
|
288
|
+
if (pk !== null && !(pk in record)) {
|
|
289
|
+
if (as.generate_pk === false)
|
|
290
|
+
throw E.pk_required(pk);
|
|
291
|
+
to_insert = { ...record, [pk]: this.#next_id(as) };
|
|
292
|
+
}
|
|
293
|
+
if (pk !== null) {
|
|
294
|
+
const pv = to_insert[pk];
|
|
295
|
+
if (table.find(s => s[pk] === pv) !== undefined) {
|
|
296
|
+
throw E.pk_duplicate(pk);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
table.push({ ...to_insert });
|
|
300
|
+
await this.#persist(as.table);
|
|
301
|
+
return to_insert;
|
|
302
|
+
}
|
|
303
|
+
async read(as, args) {
|
|
304
|
+
assert.dict(args.where);
|
|
305
|
+
await this.#load(as.table);
|
|
306
|
+
const table = this.#use(as.table);
|
|
307
|
+
const matches = table.filter(r => match(r, args.where));
|
|
308
|
+
if (args.count === true)
|
|
309
|
+
return matches.length;
|
|
310
|
+
const sort = args.sort ?? {};
|
|
311
|
+
const sorted = Object.keys(sort).length === 0
|
|
312
|
+
? matches
|
|
313
|
+
: matches.toSorted((a, b) => to_sorted(a, b, sort));
|
|
314
|
+
const offset = args.offset ?? 0;
|
|
315
|
+
const limit = args.limit ?? sorted.length;
|
|
316
|
+
const base_rows = sorted.slice(offset, offset + limit);
|
|
317
|
+
if (args.with === undefined) {
|
|
318
|
+
const fields = args.fields ?? [];
|
|
319
|
+
return fields.length === 0
|
|
320
|
+
? base_rows
|
|
321
|
+
: base_rows.map(r => filter(r, fields));
|
|
322
|
+
}
|
|
323
|
+
assert.dict(args.with);
|
|
324
|
+
const base_fields = args.fields ?? [];
|
|
325
|
+
const out = base_rows.map(full => base_fields.length === 0 ? { ...full } : filter(full, base_fields));
|
|
326
|
+
const base_full = base_rows;
|
|
327
|
+
for (const [r_name, rel] of Object.entries(args.with)) {
|
|
328
|
+
const target_as = rel.as;
|
|
329
|
+
const kind = rel.kind;
|
|
330
|
+
const fk = rel.fk;
|
|
331
|
+
const reverse = rel.reverse === true;
|
|
332
|
+
const r_where = rel.where;
|
|
333
|
+
const r_sort = rel.sort;
|
|
334
|
+
const r_limit = rel.limit;
|
|
335
|
+
const r_fields = rel.fields ?? [];
|
|
336
|
+
await this.#load(target_as.table);
|
|
337
|
+
const target = this.#use(target_as.table);
|
|
338
|
+
function project_rel(row) {
|
|
339
|
+
if (r_fields.length === 0)
|
|
340
|
+
return { ...row };
|
|
341
|
+
return filter(row, r_fields);
|
|
342
|
+
}
|
|
343
|
+
if (kind === "one" && reverse) {
|
|
344
|
+
const target_pk = target_as.pk;
|
|
345
|
+
if (target_pk === null)
|
|
346
|
+
throw E.relation_requires_pk("target");
|
|
347
|
+
for (let i = 0; i < out.length; i++) {
|
|
348
|
+
const parent_full = base_full[i];
|
|
349
|
+
const fk_value = parent_full[fk];
|
|
350
|
+
if (fk_value == null) {
|
|
351
|
+
out[i][r_name] = null;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
let candidates = target.filter(t => t[target_pk] === fk_value && match(t, r_where));
|
|
355
|
+
if (r_sort !== undefined && Object.keys(r_sort).length > 0) {
|
|
356
|
+
candidates = candidates.toSorted((a, b) => to_sorted(a, b, r_sort));
|
|
357
|
+
}
|
|
358
|
+
if (r_limit !== undefined)
|
|
359
|
+
candidates = candidates.slice(0, r_limit);
|
|
360
|
+
const got = candidates[0];
|
|
361
|
+
out[i][r_name] = got ? project_rel(got) : null;
|
|
362
|
+
}
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
const parent_pk = as.pk;
|
|
366
|
+
if (parent_pk === null)
|
|
367
|
+
throw E.relation_requires_pk("parent");
|
|
368
|
+
const parent_keys = new Set(base_full.map(r => r[parent_pk]));
|
|
369
|
+
const grouped = new Map();
|
|
370
|
+
for (const row of target) {
|
|
371
|
+
const key = row[fk];
|
|
372
|
+
if (!parent_keys.has(key))
|
|
373
|
+
continue;
|
|
374
|
+
if (!match(row, r_where))
|
|
375
|
+
continue;
|
|
376
|
+
const bucket = grouped.get(key);
|
|
377
|
+
if (bucket !== undefined)
|
|
378
|
+
bucket.push(row);
|
|
379
|
+
else
|
|
380
|
+
grouped.set(key, [row]);
|
|
381
|
+
}
|
|
382
|
+
for (let i = 0; i < out.length; i++) {
|
|
383
|
+
const parent_full = base_full[i];
|
|
384
|
+
const key = parent_full[parent_pk];
|
|
385
|
+
let rows = grouped.get(key) ?? [];
|
|
386
|
+
if (r_sort !== undefined && Object.keys(r_sort).length > 0) {
|
|
387
|
+
rows = rows.toSorted((a, b) => to_sorted(a, b, r_sort));
|
|
388
|
+
}
|
|
389
|
+
if (kind === "one") {
|
|
390
|
+
if (r_limit !== undefined)
|
|
391
|
+
rows = rows.slice(0, r_limit);
|
|
392
|
+
const got = rows[0];
|
|
393
|
+
out[i][r_name] = got ? project_rel(got) : null;
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
if (r_limit !== undefined)
|
|
397
|
+
rows = rows.slice(0, r_limit);
|
|
398
|
+
out[i][r_name] = rows.map(project_rel);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return out;
|
|
403
|
+
}
|
|
404
|
+
async update(as, args) {
|
|
405
|
+
assert.nonempty(args.set);
|
|
406
|
+
assert.dict(args.where);
|
|
407
|
+
await this.#load(as.table);
|
|
408
|
+
const table = this.#use(as.table);
|
|
409
|
+
const matched = table.filter(record => match(record, args.where));
|
|
410
|
+
const pk = as.pk;
|
|
411
|
+
const count = matched.map(record => {
|
|
412
|
+
const merged = { ...record, ...args.set };
|
|
413
|
+
const changed = Object.fromEntries(Object.entries(merged).filter(([, value]) => !is.null(value)));
|
|
414
|
+
const index = pk !== null
|
|
415
|
+
? table.findIndex(stored => stored[pk] === record[pk])
|
|
416
|
+
: table.findIndex(stored => stored === record);
|
|
417
|
+
table.splice(index, 1, changed);
|
|
418
|
+
return changed;
|
|
419
|
+
}).length;
|
|
420
|
+
if (count > 0)
|
|
421
|
+
await this.#persist(as.table);
|
|
422
|
+
return count;
|
|
423
|
+
}
|
|
424
|
+
async delete(as, args) {
|
|
425
|
+
assert.nonempty(args.where);
|
|
426
|
+
await this.#load(as.table);
|
|
427
|
+
const table = this.#use(as.table);
|
|
428
|
+
const size_before = table.length;
|
|
429
|
+
this.#tables[as.table] = table.filter(record => !match(record, args.where));
|
|
430
|
+
const count = size_before - this.#use(as.table).length;
|
|
431
|
+
if (count > 0)
|
|
432
|
+
await this.#persist(as.table);
|
|
433
|
+
return count;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
//# sourceMappingURL=JSONDB.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function identity(column) {
|
|
2
|
+
return {
|
|
3
|
+
bind: value => value,
|
|
4
|
+
column,
|
|
5
|
+
unbind: value => value,
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
const typemap = {
|
|
9
|
+
blob: identity("BLOB"),
|
|
10
|
+
boolean: identity("BOOLEAN"),
|
|
11
|
+
datetime: identity("DATE"),
|
|
12
|
+
f32: identity("NUMBER"),
|
|
13
|
+
f64: identity("NUMBER"),
|
|
14
|
+
i128: identity("BIGINT"),
|
|
15
|
+
i16: identity("NUMBER"),
|
|
16
|
+
i32: identity("NUMBER"),
|
|
17
|
+
i64: identity("BIGINT"),
|
|
18
|
+
i8: identity("NUMBER"),
|
|
19
|
+
json: identity("JSON"),
|
|
20
|
+
string: identity("STRING"),
|
|
21
|
+
time: identity("STRING"),
|
|
22
|
+
u128: identity("BIGINT"),
|
|
23
|
+
u16: identity("NUMBER"),
|
|
24
|
+
u32: identity("NUMBER"),
|
|
25
|
+
u64: identity("BIGINT"),
|
|
26
|
+
u8: identity("NUMBER"),
|
|
27
|
+
url: identity("URL"),
|
|
28
|
+
uuid: identity("STRING"),
|
|
29
|
+
uuid_v4: identity("STRING"),
|
|
30
|
+
uuid_v7: identity("STRING"),
|
|
31
|
+
};
|
|
32
|
+
export default typemap;
|
|
33
|
+
//# sourceMappingURL=typemap.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@primate/jsondb",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "JSON file databases for Primate",
|
|
5
|
+
"homepage": "https://primate.run/docs/database/jsondb",
|
|
6
|
+
"bugs": "https://github.com/primate-run/primate/issues",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/primate-run/primate",
|
|
12
|
+
"directory": "packages/jsondb"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"/lib/**/*.js",
|
|
16
|
+
"/lib/**/*.d.ts",
|
|
17
|
+
"!/**/*.spec.*"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@rcompat/assert": "^0.13.0",
|
|
21
|
+
"@rcompat/fs": "^0.33.2",
|
|
22
|
+
"@rcompat/is": "^0.11.0",
|
|
23
|
+
"@primate/core": "^0.7.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@rcompat/type": "^0.17.0",
|
|
27
|
+
"pema": "^0.7.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"primate": "^0.38.0"
|
|
31
|
+
},
|
|
32
|
+
"imports": {
|
|
33
|
+
"#*": {
|
|
34
|
+
"@primate/source": "./src/private/*.ts",
|
|
35
|
+
"default": "./lib/private/*.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"@primate/source": "./src/public/index.ts",
|
|
41
|
+
"default": "./lib/public/index.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "npm run clean && tsc",
|
|
46
|
+
"clean": "rm -rf ./lib",
|
|
47
|
+
"lint": "eslint .",
|
|
48
|
+
"test": "npx proby",
|
|
49
|
+
"test:bun": "bunx --bun proby",
|
|
50
|
+
"test:deno": "deno run -A npm:proby"
|
|
51
|
+
}
|
|
52
|
+
}
|