@primate/core 0.6.3 → 0.7.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/lib/private/App.d.ts +76 -149
- package/lib/private/App.js +22 -6
- package/lib/private/Flags.d.ts +5 -3
- package/lib/private/Flags.js +4 -2
- package/lib/private/app/Facade.d.ts +77 -156
- package/lib/private/app/Facade.js +4 -1
- package/lib/private/build/App.d.ts +4 -2
- package/lib/private/build/App.js +13 -4
- package/lib/private/build/client/index.js +24 -10
- package/lib/private/build/client/plugin/routes.d.ts +4 -0
- package/lib/private/build/client/plugin/routes.js +74 -0
- package/lib/private/build/hook.js +14 -6
- package/lib/private/build/index.d.ts +3 -2
- package/lib/private/build/index.js +9 -15
- package/lib/private/build/preclient/index.d.ts +3 -0
- package/lib/private/build/preclient/index.js +69 -0
- package/lib/private/build/preclient/plugin/routes.d.ts +4 -0
- package/lib/private/build/preclient/plugin/routes.js +44 -0
- package/lib/private/build/server/index.js +8 -5
- package/lib/private/build/server/plugin/assets.js +3 -3
- package/lib/private/build/server/plugin/native-addons.js +6 -8
- package/lib/private/build/server/plugin/node-imports.js +5 -7
- package/lib/private/build/server/plugin/route-client.d.ts +4 -0
- package/lib/private/build/server/plugin/route-client.js +110 -0
- package/lib/private/build/server/plugin/route.js +3 -10
- package/lib/private/build/server/plugin/virtual-pages.js +3 -3
- package/lib/private/build/server/plugin/virtual-routes.d.ts +2 -1
- package/lib/private/build/server/plugin/virtual-routes.js +27 -8
- package/lib/private/build/server/plugin/wasm.js +3 -2
- package/lib/private/client/Data.d.ts +1 -1
- package/lib/private/client/boot.js +2 -2
- package/lib/private/client/create-form.d.ts +11 -1
- package/lib/private/client/create-form.js +21 -3
- package/lib/private/client/index.d.ts +2 -2
- package/lib/private/client/navigate.d.ts +1 -0
- package/lib/private/client/navigate.js +7 -6
- package/lib/private/client/submit.d.ts +2 -1
- package/lib/private/client/submit.js +8 -7
- package/lib/private/client/{http.d.ts → transport.d.ts} +3 -3
- package/lib/private/client/{http.js → transport.js} +7 -9
- package/lib/private/config/schema.d.ts +5 -13
- package/lib/private/config/schema.js +1 -5
- package/lib/private/db/DB.d.ts +3 -1
- package/lib/private/db/MemoryDB.d.ts +5 -2
- package/lib/private/db/MemoryDB.js +33 -19
- package/lib/private/db/SQLDB.d.ts +23 -0
- package/lib/private/db/SQLDB.js +2 -0
- package/lib/private/db/errors.d.ts +74 -7
- package/lib/private/db/errors.js +31 -7
- package/lib/private/db/migrate/apply.js +8 -9
- package/lib/private/db/migrate/bundle.js +2 -2
- package/lib/private/db/migrate/create.js +18 -20
- package/lib/private/db/migrate/status.js +9 -10
- package/lib/private/db/migrate/store.d.ts +3 -3
- package/lib/private/db/migrate/store.js +5 -5
- package/lib/private/db/test.js +256 -115
- package/lib/private/db/testSQL.d.ts +50 -0
- package/lib/private/db/testSQL.js +196 -0
- package/lib/private/errors.d.ts +66 -9
- package/lib/private/errors.js +37 -16
- package/lib/private/frontend.d.ts +4 -4
- package/lib/private/frontend.js +11 -8
- package/lib/private/i18n/errors.d.ts +7 -1
- package/lib/private/i18n/errors.js +1 -1
- package/lib/private/i18n/index/types.d.ts +1 -1
- package/lib/private/i18n/locale.d.ts +1 -1
- package/lib/private/i18n/module.js +6 -5
- package/lib/private/index.d.ts +10 -2
- package/lib/private/index.js +13 -1
- package/lib/private/logger.d.ts +24 -0
- package/lib/private/logger.js +66 -0
- package/lib/private/module/Setup.d.ts +3 -0
- package/lib/private/module/create.d.ts +2 -1
- package/lib/private/module/create.js +4 -0
- package/lib/private/paths.d.ts +2 -3
- package/lib/private/paths.js +6 -12
- package/lib/private/request/ContentType.d.ts +3 -0
- package/lib/private/request/ContentType.js +2 -0
- package/lib/private/request/RequestBag.d.ts +2 -18
- package/lib/private/request/RequestBag.js +4 -16
- package/lib/private/request/RequestBody.d.ts +20 -28
- package/lib/private/request/RequestBody.js +63 -86
- package/lib/private/request/RequestFacade.d.ts +2 -2
- package/lib/private/request/handle.js +2 -2
- package/lib/private/request/parse.js +2 -4
- package/lib/private/request/route.js +15 -8
- package/lib/private/response/binary.d.ts +2 -2
- package/lib/private/response/binary.js +6 -6
- package/lib/private/response/error.js +6 -2
- package/lib/private/response/json.js +2 -2
- package/lib/private/response/null.d.ts +3 -0
- package/lib/private/response/null.js +6 -0
- package/lib/private/response/redirect.js +4 -3
- package/lib/private/response/respond.js +4 -4
- package/lib/private/response/sse.js +2 -2
- package/lib/private/response/text.js +2 -2
- package/lib/private/route/ContentTypeMap.d.ts +10 -0
- package/lib/private/route/ContentTypeMap.js +2 -0
- package/lib/private/route/Handler.d.ts +3 -2
- package/lib/private/route/NarrowedRequest.d.ts +23 -0
- package/lib/private/route/NarrowedRequest.js +2 -0
- package/lib/private/route/Options.d.ts +6 -1
- package/lib/private/route/Path.d.ts +2 -2
- package/lib/private/route/hook.d.ts +3 -1
- package/lib/private/route/hook.js +1 -2
- package/lib/private/route/router.d.ts +7 -11
- package/lib/private/route/router.js +13 -20
- package/lib/private/route.client.d.ts +36 -0
- package/lib/private/route.client.js +8 -0
- package/lib/private/route.d.ts +21 -5
- package/lib/private/route.js +21 -5
- package/lib/private/serve/App.d.ts +1 -2
- package/lib/private/serve/App.js +64 -58
- package/lib/private/serve/Init.d.ts +2 -0
- package/lib/private/serve/dev-module.js +2 -3
- package/lib/private/serve/index.js +5 -6
- package/lib/private/server/TAG.d.ts +1 -1
- package/lib/private/server/TAG.js +1 -1
- package/lib/private/session/index.d.ts +1 -1
- package/lib/private/session/index.js +3 -2
- package/lib/private/session/module.js +3 -3
- package/lib/private/session/schema.d.ts +3 -3
- package/lib/private/session/schema.js +4 -3
- package/lib/private/store/ExtractRelation.d.ts +7 -0
- package/lib/private/store/ExtractRelation.js +2 -0
- package/lib/private/store/ExtractSchema.d.ts +10 -0
- package/lib/private/{orm → store}/ForeignKey.d.ts +1 -1
- package/lib/private/store/Init.d.ts +13 -0
- package/lib/private/store/Init.js +2 -0
- package/lib/private/{orm/store.d.ts → store/Store.d.ts} +50 -50
- package/lib/private/{orm/store.js → store/Store.js} +163 -107
- package/lib/private/store/StoreInput.d.ts +11 -0
- package/lib/private/store/key.d.ts +8 -0
- package/lib/private/store/key.js +8 -0
- package/lib/private/{orm → store}/parse.d.ts +3 -3
- package/lib/private/{orm → store}/parse.js +7 -2
- package/lib/private/store/relation.d.ts +29 -0
- package/lib/private/store/relation.js +26 -0
- package/lib/private/store.d.ts +24 -0
- package/lib/private/store.js +10 -0
- package/lib/public/db/errors.d.ts +1 -1
- package/lib/public/db/errors.js +1 -1
- package/lib/public/db/testSQL.d.ts +2 -0
- package/lib/public/db/testSQL.js +2 -0
- package/lib/public/db.d.ts +1 -0
- package/lib/public/index.d.ts +1 -0
- package/lib/public/index.js +1 -1
- package/lib/public/response.d.ts +6 -6
- package/lib/public/response.js +4 -1
- package/lib/public/route.client.d.ts +2 -0
- package/lib/public/route.client.js +2 -0
- package/lib/public/store.d.ts +2 -0
- package/lib/public/store.js +2 -0
- package/package.json +24 -17
- package/lib/private/bye.d.ts +0 -3
- package/lib/private/bye.js +0 -4
- package/lib/private/log.d.ts +0 -20
- package/lib/private/log.js +0 -47
- package/lib/private/orm/ExtractSchema.d.ts +0 -9
- package/lib/private/orm/StoreInput.d.ts +0 -10
- package/lib/private/orm/key.d.ts +0 -8
- package/lib/private/orm/key.js +0 -8
- package/lib/private/orm/relation.d.ts +0 -43
- package/lib/private/orm/relation.js +0 -26
- package/lib/private/request/Verb.d.ts +0 -4
- package/lib/private/request/Verb.js +0 -2
- package/lib/private/request/verbs.d.ts +0 -3
- package/lib/private/request/verbs.js +0 -12
- package/lib/private/route/wrap.d.ts +0 -2
- package/lib/private/route/wrap.js +0 -12
- package/lib/public/log.d.ts +0 -2
- package/lib/public/log.js +0 -2
- package/lib/public/orm/key.d.ts +0 -2
- package/lib/public/orm/key.js +0 -2
- package/lib/public/orm/relation.d.ts +0 -2
- package/lib/public/orm/relation.js +0 -2
- package/lib/public/orm/store.d.ts +0 -2
- package/lib/public/orm/store.js +0 -2
- package/lib/public/request/verbs.d.ts +0 -2
- package/lib/public/request/verbs.js +0 -2
- /package/lib/private/{orm → store}/ExtractSchema.js +0 -0
- /package/lib/private/{orm → store}/ForeignKey.js +0 -0
- /package/lib/private/{orm → store}/PrimaryKey.d.ts +0 -0
- /package/lib/private/{orm → store}/PrimaryKey.js +0 -0
- /package/lib/private/{orm → store}/StoreInput.js +0 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import E from "#db/errors";
|
|
2
|
-
import parse from "#
|
|
2
|
+
import parse from "#store/parse";
|
|
3
|
+
import relation from "#store/relation";
|
|
3
4
|
import assert from "@rcompat/assert";
|
|
5
|
+
import dict from "@rcompat/dict";
|
|
4
6
|
import is from "@rcompat/is";
|
|
5
7
|
import StoreType from "pema/StoreType";
|
|
8
|
+
const brand = Symbol.for("@primate/core/Store/v0");
|
|
6
9
|
const NUMBER_KEYS = [
|
|
7
10
|
"u8", "u16", "u32",
|
|
8
11
|
"i8", "i16", "i32",
|
|
@@ -13,7 +16,7 @@ const is_number_key = (d) => NUMBER_KEYS.includes(d);
|
|
|
13
16
|
const is_bigint_key = (d) => BIGINT_KEYS.includes(d);
|
|
14
17
|
const VALID_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
15
18
|
function guard_options(options, allowed) {
|
|
16
|
-
if (options
|
|
19
|
+
if (is.undefined(options))
|
|
17
20
|
return;
|
|
18
21
|
const allowed_set = new Set(allowed);
|
|
19
22
|
for (const k of Object.keys(options)) {
|
|
@@ -38,7 +41,7 @@ const BIGINT_LIMITS = {
|
|
|
38
41
|
function assert_number_value(key, datatype, value) {
|
|
39
42
|
if (!is.finite(value))
|
|
40
43
|
throw E.where_invalid_value(key, value);
|
|
41
|
-
if (datatype
|
|
44
|
+
if (dict.has(INT_LIMITS, datatype)) {
|
|
42
45
|
if (!is.safeint(value))
|
|
43
46
|
throw E.where_invalid_value(key, value);
|
|
44
47
|
const [min, max] = INT_LIMITS[datatype];
|
|
@@ -51,11 +54,10 @@ function assert_bigint_value(key, datatype, value) {
|
|
|
51
54
|
if (value < min || value > max)
|
|
52
55
|
throw E.where_invalid_value(key, value);
|
|
53
56
|
}
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const DATE_OPS = ["$before", "$after", "$ne"];
|
|
57
|
+
const STRING_OPS = ["$like", "$ilike", "$in"];
|
|
58
|
+
const NUMBER_OPS = ["$gt", "$gte", "$lt", "$lte", "$ne", "$in"];
|
|
59
|
+
const BIGINT_OPS = ["$gt", "$gte", "$lt", "$lte", "$ne", "$in"];
|
|
60
|
+
const DATE_OPS = ["$before", "$after", "$ne", "$in"];
|
|
59
61
|
/**
|
|
60
62
|
* Database-backed store.
|
|
61
63
|
*
|
|
@@ -63,13 +65,13 @@ const DATE_OPS = ["$before", "$after", "$ne"];
|
|
|
63
65
|
* document database table/collection. It pairs a Pema schema with a uniform
|
|
64
66
|
* CRUD/query API.
|
|
65
67
|
*/
|
|
66
|
-
export class Store {
|
|
68
|
+
export default class Store {
|
|
69
|
+
[brand] = true;
|
|
67
70
|
#input;
|
|
68
71
|
#schema;
|
|
69
|
-
#type;
|
|
70
72
|
#types;
|
|
71
73
|
#nullables;
|
|
72
|
-
#
|
|
74
|
+
#table;
|
|
73
75
|
#db;
|
|
74
76
|
#pk;
|
|
75
77
|
#generate_pk;
|
|
@@ -77,65 +79,60 @@ export class Store {
|
|
|
77
79
|
#relations;
|
|
78
80
|
#migrate;
|
|
79
81
|
#id;
|
|
82
|
+
static is(x) {
|
|
83
|
+
return is.branded(x, brand);
|
|
84
|
+
}
|
|
80
85
|
constructor(init) {
|
|
81
|
-
const {
|
|
86
|
+
const { table, db, migrate, id } = init;
|
|
82
87
|
const { pk, generate_pk, fks, schema } = parse(init.schema);
|
|
83
|
-
if (
|
|
84
|
-
throw E.
|
|
85
|
-
assert.string(
|
|
86
|
-
if (!VALID_IDENTIFIER.test(
|
|
87
|
-
throw E.identifier_invalid(
|
|
88
|
+
if (is.undefined(table))
|
|
89
|
+
throw E.store_table_required();
|
|
90
|
+
assert.string(table);
|
|
91
|
+
if (!VALID_IDENTIFIER.test(table))
|
|
92
|
+
throw E.identifier_invalid(table);
|
|
88
93
|
assert.defined(db, E.db_missing);
|
|
89
94
|
assert.dict(schema);
|
|
90
95
|
assert.maybe.boolean(migrate);
|
|
91
96
|
assert.maybe.dict(init.extend);
|
|
92
97
|
assert.maybe.symbol(id);
|
|
93
98
|
this.#id = id ?? Symbol();
|
|
94
|
-
this.#schema = schema;
|
|
95
|
-
this.#type = new StoreType(schema, pk);
|
|
99
|
+
this.#schema = new StoreType(schema, pk);
|
|
96
100
|
this.#types = Object.fromEntries(Object.entries(schema).map(([key, value]) => [key, value.datatype]));
|
|
97
|
-
this.#
|
|
101
|
+
this.#table = table;
|
|
98
102
|
this.#db = db;
|
|
99
103
|
this.#pk = pk;
|
|
100
104
|
this.#generate_pk = generate_pk;
|
|
101
105
|
this.#fks = fks;
|
|
102
106
|
this.#input = init.schema;
|
|
103
|
-
this.#relations = init.
|
|
107
|
+
this.#relations = Object.fromEntries(Object.entries(init.schema).filter(([, v]) => relation.is(v)));
|
|
104
108
|
this.#migrate = migrate ?? true;
|
|
105
|
-
|
|
106
|
-
if (relation in schema)
|
|
107
|
-
throw E.relation_conflicts_with_field(relation);
|
|
108
|
-
}
|
|
109
|
-
this.#nullables = new Set(Object.entries(this.#type.properties)
|
|
109
|
+
this.#nullables = new Set(Object.entries(this.#schema.properties)
|
|
110
110
|
.filter(([, v]) => v.nullable)
|
|
111
111
|
.map(([k]) => k));
|
|
112
|
-
if (init.extend
|
|
112
|
+
if (is.defined(init.extend)) {
|
|
113
113
|
for (const [k, v] of Object.entries(init.extend)) {
|
|
114
114
|
if (k in this)
|
|
115
115
|
throw E.key_duplicate(k);
|
|
116
116
|
this[k] = v;
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
|
-
registry.set(init.schema, this);
|
|
120
119
|
}
|
|
121
120
|
get #as() {
|
|
122
121
|
return {
|
|
123
|
-
table: this
|
|
122
|
+
table: this.#table,
|
|
124
123
|
pk: this.#pk,
|
|
125
124
|
generate_pk: this.#generate_pk,
|
|
126
125
|
types: this.#types,
|
|
127
126
|
};
|
|
128
127
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const pk = {
|
|
128
|
+
async create() {
|
|
129
|
+
return this.db.schema.create(this.#table, {
|
|
132
130
|
name: this.#pk,
|
|
133
131
|
generate: this.#generate_pk,
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
};
|
|
132
|
+
}, this.#types);
|
|
133
|
+
}
|
|
134
|
+
async drop() {
|
|
135
|
+
return this.db.schema.delete(this.#table);
|
|
139
136
|
}
|
|
140
137
|
get infer() {
|
|
141
138
|
return undefined;
|
|
@@ -143,17 +140,14 @@ export class Store {
|
|
|
143
140
|
get schema() {
|
|
144
141
|
return this.#schema;
|
|
145
142
|
}
|
|
146
|
-
get type() {
|
|
147
|
-
return this.#type;
|
|
148
|
-
}
|
|
149
143
|
get migrate() {
|
|
150
144
|
return this.#migrate;
|
|
151
145
|
}
|
|
152
146
|
get id() {
|
|
153
147
|
return this.#id;
|
|
154
148
|
}
|
|
155
|
-
get
|
|
156
|
-
return this.#
|
|
149
|
+
get table() {
|
|
150
|
+
return this.#table;
|
|
157
151
|
}
|
|
158
152
|
get pk() {
|
|
159
153
|
return this.#pk;
|
|
@@ -166,10 +160,10 @@ export class Store {
|
|
|
166
160
|
}
|
|
167
161
|
#parse_pk(pkv) {
|
|
168
162
|
const pk = this.#pk;
|
|
169
|
-
if (pk
|
|
170
|
-
throw E.pk_undefined(this
|
|
163
|
+
if (is.null(pk))
|
|
164
|
+
throw E.pk_undefined(this.#table);
|
|
171
165
|
try {
|
|
172
|
-
this.#schema[pk].parse(pkv);
|
|
166
|
+
this.#schema.properties[pk].parse(pkv);
|
|
173
167
|
}
|
|
174
168
|
catch {
|
|
175
169
|
throw E.pk_invalid(pkv);
|
|
@@ -177,52 +171,58 @@ export class Store {
|
|
|
177
171
|
return pk;
|
|
178
172
|
}
|
|
179
173
|
#parse_query(query, types) {
|
|
180
|
-
const { where, select, sort, limit } = query;
|
|
181
|
-
if (where
|
|
174
|
+
const { where, select, sort, limit, offset } = query;
|
|
175
|
+
if (is.defined(where))
|
|
182
176
|
this.#parse_where(where, types);
|
|
183
|
-
if (select
|
|
177
|
+
if (is.defined(select))
|
|
184
178
|
this.#parse_select(select, types);
|
|
185
|
-
if (sort
|
|
179
|
+
if (is.defined(sort))
|
|
186
180
|
this.#parse_sort(sort, types);
|
|
187
|
-
if (limit
|
|
181
|
+
if (is.defined(limit) && !is.uint(limit))
|
|
188
182
|
throw E.limit_invalid();
|
|
183
|
+
if (is.defined(offset)) {
|
|
184
|
+
if (!is.uint(offset))
|
|
185
|
+
throw E.offset_invalid();
|
|
186
|
+
if (is.undefined(limit))
|
|
187
|
+
throw E.offset_requires_limit();
|
|
188
|
+
}
|
|
189
189
|
}
|
|
190
190
|
#with(options) {
|
|
191
|
-
if (options
|
|
191
|
+
if (is.undefined(options))
|
|
192
192
|
return undefined;
|
|
193
193
|
const plan = {};
|
|
194
|
-
for (const [name,
|
|
195
|
-
if (
|
|
194
|
+
for (const [name, input] of dict.entries(options)) {
|
|
195
|
+
if (is.undefined(input))
|
|
196
196
|
continue;
|
|
197
197
|
const relation = this.#relations[name];
|
|
198
|
-
if (relation
|
|
198
|
+
if (is.undefined(relation))
|
|
199
199
|
throw E.relation_unknown(name);
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
200
|
+
const is_query = is.dict(input) && "store" in input;
|
|
201
|
+
const passed_store = is_query
|
|
202
|
+
? input.store
|
|
203
|
+
: Store.is(input) ? input : undefined;
|
|
204
|
+
if (is.undefined(passed_store))
|
|
205
|
+
throw E.relation_store_required(name);
|
|
206
|
+
const query = is_query
|
|
207
|
+
? input
|
|
208
|
+
: undefined;
|
|
209
|
+
if (passed_store.table !== relation.table) {
|
|
210
|
+
throw E.relation_table_mismatch(relation.table, passed_store.table);
|
|
211
|
+
}
|
|
212
|
+
this.#parse_query(query ?? {}, passed_store.types);
|
|
213
|
+
plan[name] = {
|
|
206
214
|
as: {
|
|
207
|
-
table:
|
|
208
|
-
pk:
|
|
209
|
-
types:
|
|
215
|
+
table: passed_store.table,
|
|
216
|
+
pk: passed_store.pk,
|
|
217
|
+
types: passed_store.types,
|
|
210
218
|
},
|
|
211
219
|
kind: relation.type,
|
|
212
220
|
fk: relation.fk,
|
|
213
221
|
reverse: "reverse" in relation && relation.reverse === true,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
this.#parse_query(query, target_types);
|
|
220
|
-
plan[name] = {
|
|
221
|
-
...base,
|
|
222
|
-
where: query.where ?? {},
|
|
223
|
-
fields: query.select ? [...query.select] : undefined,
|
|
224
|
-
sort: query.sort,
|
|
225
|
-
limit: query.limit,
|
|
222
|
+
where: query?.where ?? {},
|
|
223
|
+
fields: query?.select ? [...query.select] : undefined,
|
|
224
|
+
sort: query?.sort,
|
|
225
|
+
limit: query?.limit,
|
|
226
226
|
};
|
|
227
227
|
}
|
|
228
228
|
return plan;
|
|
@@ -233,13 +233,13 @@ export class Store {
|
|
|
233
233
|
for (const [k, value] of Object.entries(where)) {
|
|
234
234
|
if (!VALID_IDENTIFIER.test(k))
|
|
235
235
|
throw E.identifier_invalid(k);
|
|
236
|
-
if (!(k
|
|
236
|
+
if (!dict.has(types, k))
|
|
237
237
|
throw E.field_unknown(k, "where");
|
|
238
|
-
if (value
|
|
238
|
+
if (is.undefined(value))
|
|
239
239
|
throw E.field_undefined(k, "where");
|
|
240
240
|
const datatype = types[k];
|
|
241
241
|
// null criteria (IS NULL semantics)
|
|
242
|
-
if (value
|
|
242
|
+
if (is.null(value))
|
|
243
243
|
continue;
|
|
244
244
|
// arrays are always invalid
|
|
245
245
|
if (is.array(value))
|
|
@@ -254,6 +254,17 @@ export class Store {
|
|
|
254
254
|
if (datatype === "string" || datatype === "time") {
|
|
255
255
|
if (!STRING_OPS.includes(op))
|
|
256
256
|
throw E.operator_unknown(k, op);
|
|
257
|
+
if (op === "$in") {
|
|
258
|
+
if (!is.array(op_val))
|
|
259
|
+
throw E.wrong_type("array", k, op_val, op);
|
|
260
|
+
if (op_val.length === 0)
|
|
261
|
+
throw E.operator_empty_in(k);
|
|
262
|
+
for (const v of op_val) {
|
|
263
|
+
if (!is.string(v))
|
|
264
|
+
throw E.wrong_type("string", k, v, op);
|
|
265
|
+
}
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
257
268
|
if (!is.string(op_val))
|
|
258
269
|
throw E.wrong_type("string", k, op_val, op);
|
|
259
270
|
continue;
|
|
@@ -261,6 +272,18 @@ export class Store {
|
|
|
261
272
|
if (is_number_key(datatype)) {
|
|
262
273
|
if (!NUMBER_OPS.includes(op))
|
|
263
274
|
throw E.operator_unknown(k, op);
|
|
275
|
+
if (op === "$in") {
|
|
276
|
+
if (!is.array(op_val))
|
|
277
|
+
throw E.wrong_type("array", k, op_val, op);
|
|
278
|
+
if (op_val.length === 0)
|
|
279
|
+
throw E.operator_empty_in(k);
|
|
280
|
+
for (const v of op_val) {
|
|
281
|
+
if (!is.number(v))
|
|
282
|
+
throw E.wrong_type("number", k, v, op);
|
|
283
|
+
assert_number_value(k, datatype, v);
|
|
284
|
+
}
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
264
287
|
if (!is.number(op_val))
|
|
265
288
|
throw E.wrong_type("number", k, op_val, op);
|
|
266
289
|
assert_number_value(k, datatype, op_val);
|
|
@@ -269,6 +292,18 @@ export class Store {
|
|
|
269
292
|
if (is_bigint_key(datatype)) {
|
|
270
293
|
if (!BIGINT_OPS.includes(op))
|
|
271
294
|
throw E.operator_unknown(k, op);
|
|
295
|
+
if (op === "$in") {
|
|
296
|
+
if (!is.array(op_val))
|
|
297
|
+
throw E.wrong_type("array", k, op_val, op);
|
|
298
|
+
if (op_val.length === 0)
|
|
299
|
+
throw E.operator_empty_in(k);
|
|
300
|
+
for (const v of op_val) {
|
|
301
|
+
if (!is.bigint(v))
|
|
302
|
+
throw E.wrong_type("bigint", k, v, op);
|
|
303
|
+
assert_bigint_value(k, datatype, v);
|
|
304
|
+
}
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
272
307
|
if (!is.bigint(op_val))
|
|
273
308
|
throw E.wrong_type("bigint", k, op_val, op);
|
|
274
309
|
assert_bigint_value(k, datatype, op_val);
|
|
@@ -277,10 +312,34 @@ export class Store {
|
|
|
277
312
|
if (datatype === "datetime") {
|
|
278
313
|
if (!DATE_OPS.includes(op))
|
|
279
314
|
throw E.operator_unknown(k, op);
|
|
315
|
+
if (op === "$in") {
|
|
316
|
+
if (!is.array(op_val))
|
|
317
|
+
throw E.wrong_type("array", k, op_val, op);
|
|
318
|
+
if (op_val.length === 0)
|
|
319
|
+
throw E.operator_empty_in(k);
|
|
320
|
+
for (const v of op_val) {
|
|
321
|
+
if (!is.date(v))
|
|
322
|
+
throw E.wrong_type("date", k, v, op);
|
|
323
|
+
}
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
280
326
|
if (!is.date(op_val))
|
|
281
327
|
throw E.wrong_type("date", k, op_val, op);
|
|
282
328
|
continue;
|
|
283
329
|
}
|
|
330
|
+
if (datatype === "uuid" || datatype === "uuid_v4" || datatype === "uuid_v7") {
|
|
331
|
+
if (op !== "$in")
|
|
332
|
+
throw E.operator_unknown(k, op);
|
|
333
|
+
if (!is.array(op_val))
|
|
334
|
+
throw E.wrong_type("array", k, op_val, op);
|
|
335
|
+
if (op_val.length === 0)
|
|
336
|
+
throw E.operator_empty_in(k);
|
|
337
|
+
for (const v of op_val) {
|
|
338
|
+
if (!is.string(v))
|
|
339
|
+
throw E.wrong_type("string", k, v, op);
|
|
340
|
+
}
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
284
343
|
// url/boolean/blob: no operator objects
|
|
285
344
|
throw E.operator_unknown(k, op);
|
|
286
345
|
}
|
|
@@ -350,7 +409,7 @@ export class Store {
|
|
|
350
409
|
throw E.select_invalid_value(i, v);
|
|
351
410
|
if (!VALID_IDENTIFIER.test(v))
|
|
352
411
|
throw E.identifier_invalid(v);
|
|
353
|
-
if (!(v
|
|
412
|
+
if (!dict.has(types, v))
|
|
354
413
|
throw E.field_unknown(v, "select");
|
|
355
414
|
// duplicate isn't harmful, but usually a thought error
|
|
356
415
|
if (seen.has(v))
|
|
@@ -367,22 +426,25 @@ export class Store {
|
|
|
367
426
|
for (const [k, direction] of Object.entries(sort)) {
|
|
368
427
|
if (!is.string(direction))
|
|
369
428
|
throw E.sort_invalid_value(k, direction);
|
|
370
|
-
if (!(k
|
|
429
|
+
if (!dict.has(types, k))
|
|
371
430
|
throw E.field_unknown(k, "sort");
|
|
372
431
|
const l = direction.toLowerCase();
|
|
373
432
|
if (l !== "asc" && l !== "desc")
|
|
374
433
|
throw E.sort_invalid_value(k, direction);
|
|
375
434
|
}
|
|
376
435
|
}
|
|
377
|
-
#
|
|
436
|
+
#prepare_insert(record) {
|
|
437
|
+
const out = {};
|
|
378
438
|
for (const [k, v] of Object.entries(record)) {
|
|
379
|
-
if (!(
|
|
439
|
+
if (!dict.has(this.#types, k))
|
|
380
440
|
throw E.field_unknown(k, "insert");
|
|
381
|
-
if (v
|
|
382
|
-
|
|
383
|
-
if (v
|
|
441
|
+
if (is.undefined(v))
|
|
442
|
+
continue; // treat as omission
|
|
443
|
+
if (is.null(v))
|
|
384
444
|
throw E.null_not_allowed(k);
|
|
445
|
+
out[k] = v;
|
|
385
446
|
}
|
|
447
|
+
return out;
|
|
386
448
|
}
|
|
387
449
|
/**
|
|
388
450
|
* Count records
|
|
@@ -422,13 +484,13 @@ export class Store {
|
|
|
422
484
|
}
|
|
423
485
|
const raw = records[0];
|
|
424
486
|
// if projected, keep it as-is (no parse)
|
|
425
|
-
if (options?.select)
|
|
487
|
+
if (is.defined(options?.select))
|
|
426
488
|
return raw;
|
|
427
489
|
// parse *only* base fields (exclude relation keys)
|
|
428
490
|
const base_only = Object.fromEntries(Object.entries(raw)
|
|
429
|
-
.filter(([k]) =>
|
|
430
|
-
const parsed = this.#
|
|
431
|
-
if ($with
|
|
491
|
+
.filter(([k]) => dict.has(this.#types, k)));
|
|
492
|
+
const parsed = this.#schema.parse(base_only);
|
|
493
|
+
if (is.undefined($with))
|
|
432
494
|
return parsed;
|
|
433
495
|
return {
|
|
434
496
|
...parsed,
|
|
@@ -450,10 +512,8 @@ export class Store {
|
|
|
450
512
|
*/
|
|
451
513
|
async insert(record) {
|
|
452
514
|
assert.dict(record);
|
|
453
|
-
this.#
|
|
454
|
-
|
|
455
|
-
const to_parse = Object.fromEntries(entries.filter(([k, v]) => !(v === null && this.#nullables.has(k))));
|
|
456
|
-
return this.db.create(this.#as, this.#type.parse(to_parse));
|
|
515
|
+
const prepared = this.#prepare_insert(record);
|
|
516
|
+
return this.db.create(this.#as, this.#schema.parse(prepared));
|
|
457
517
|
}
|
|
458
518
|
async update(arg0, options) {
|
|
459
519
|
const by_pk = options !== undefined;
|
|
@@ -466,9 +526,9 @@ export class Store {
|
|
|
466
526
|
if (pk !== null && pk in set)
|
|
467
527
|
throw E.pk_immutable(pk);
|
|
468
528
|
for (const [k, v] of Object.entries(set)) {
|
|
469
|
-
if (!(
|
|
529
|
+
if (!dict.has(this.#types, k))
|
|
470
530
|
throw E.field_unknown(k, "set");
|
|
471
|
-
if (v
|
|
531
|
+
if (is.null(v) && !this.#nullables.has(k))
|
|
472
532
|
throw E.null_not_allowed(k);
|
|
473
533
|
}
|
|
474
534
|
const entries = Object.entries(set);
|
|
@@ -477,13 +537,13 @@ export class Store {
|
|
|
477
537
|
const nulls = Object.fromEntries(entries
|
|
478
538
|
.filter(([key, value]) => value === null && this.#nullables.has(key)));
|
|
479
539
|
const parsed = {
|
|
480
|
-
...this.#
|
|
540
|
+
...this.#schema.partial().parse(to_parse),
|
|
481
541
|
...nulls,
|
|
482
542
|
};
|
|
483
543
|
if (by_pk)
|
|
484
544
|
return this.#update_1(arg0, parsed);
|
|
485
545
|
const where = arg0.where;
|
|
486
|
-
if (where
|
|
546
|
+
if (is.defined(where))
|
|
487
547
|
this.#parse_where(where, this.#types);
|
|
488
548
|
return this.#update_n((where ?? {}), parsed);
|
|
489
549
|
}
|
|
@@ -518,34 +578,30 @@ export class Store {
|
|
|
518
578
|
*
|
|
519
579
|
*/
|
|
520
580
|
async find(options) {
|
|
521
|
-
guard_options(options, ["where", "select", "sort", "limit", "with"]);
|
|
581
|
+
guard_options(options, ["where", "select", "sort", "limit", "offset", "with"]);
|
|
522
582
|
this.#parse_query(options ?? {}, this.#types);
|
|
523
583
|
const result = await this.db.read(this.#as, {
|
|
524
584
|
where: options?.where ?? {},
|
|
525
585
|
fields: options?.select !== undefined ? [...options.select] : undefined,
|
|
526
586
|
limit: options?.limit,
|
|
587
|
+
offset: options?.offset,
|
|
527
588
|
sort: options?.sort,
|
|
528
589
|
with: this.#with(options?.with),
|
|
529
590
|
});
|
|
530
591
|
return result;
|
|
531
592
|
}
|
|
532
593
|
toJSON() {
|
|
533
|
-
return this.#
|
|
594
|
+
return this.#schema.toJSON();
|
|
534
595
|
}
|
|
535
596
|
extend(extensor) {
|
|
536
597
|
return new Store({
|
|
537
|
-
|
|
538
|
-
db: this
|
|
598
|
+
table: this.#table,
|
|
599
|
+
db: this.#db,
|
|
539
600
|
schema: this.#input,
|
|
540
|
-
relations: this.#relations,
|
|
541
601
|
migrate: this.#migrate,
|
|
542
602
|
id: this.#id,
|
|
543
603
|
extend: extensor(this),
|
|
544
604
|
});
|
|
545
605
|
}
|
|
546
606
|
}
|
|
547
|
-
|
|
548
|
-
return new Store(init);
|
|
549
|
-
}
|
|
550
|
-
export default store;
|
|
551
|
-
//# sourceMappingURL=store.js.map
|
|
607
|
+
//# sourceMappingURL=Store.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type ForeignKey from "#store/ForeignKey";
|
|
2
|
+
import type { AllowedFKType } from "#store/ForeignKey";
|
|
3
|
+
import type PrimaryKey from "#store/PrimaryKey";
|
|
4
|
+
import type { AllowedPKType } from "#store/PrimaryKey";
|
|
5
|
+
import type { Relation } from "#store/relation";
|
|
6
|
+
import type { Dict } from "@rcompat/type";
|
|
7
|
+
import type { DataKey, Storable } from "pema";
|
|
8
|
+
type StoreField = Storable<DataKey> | PrimaryKey<AllowedPKType> | ForeignKey<AllowedFKType> | Relation;
|
|
9
|
+
type StoreInput = Dict<StoreField>;
|
|
10
|
+
export type { StoreInput as default };
|
|
11
|
+
//# sourceMappingURL=StoreInput.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type PK from "#db/PK";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import type StoreInput from "#
|
|
2
|
+
import type { AllowedFKType } from "#store/ForeignKey";
|
|
3
|
+
import ForeignKey from "#store/ForeignKey";
|
|
4
|
+
import type StoreInput from "#store/StoreInput";
|
|
5
5
|
import type { Dict } from "@rcompat/type";
|
|
6
6
|
import type { Storable } from "pema";
|
|
7
7
|
export default function parse(input: StoreInput): {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import E from "#db/errors";
|
|
2
|
-
import ForeignKey from "#
|
|
3
|
-
import PrimaryKey from "#
|
|
2
|
+
import ForeignKey from "#store/ForeignKey";
|
|
3
|
+
import PrimaryKey from "#store/PrimaryKey";
|
|
4
|
+
import relation from "#store/relation";
|
|
4
5
|
const is_pk = (x) => x instanceof PrimaryKey;
|
|
5
6
|
const is_fk = (x) => x instanceof ForeignKey;
|
|
6
7
|
export default function parse(input) {
|
|
@@ -20,6 +21,10 @@ export default function parse(input) {
|
|
|
20
21
|
fks.set(key, value);
|
|
21
22
|
schema[key] = value.type;
|
|
22
23
|
}
|
|
24
|
+
else if (relation.is(value)) {
|
|
25
|
+
// skip — relations are extracted separately
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
23
28
|
else {
|
|
24
29
|
schema[key] = value;
|
|
25
30
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type OneRelation<N extends string, FK extends string> = {
|
|
2
|
+
type: "one";
|
|
3
|
+
table: N;
|
|
4
|
+
fk: FK;
|
|
5
|
+
reverse: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type ManyRelation<N extends string, FK extends string> = {
|
|
8
|
+
type: "many";
|
|
9
|
+
table: N;
|
|
10
|
+
fk: FK;
|
|
11
|
+
};
|
|
12
|
+
export type Relation = OneRelation<string, string> | ManyRelation<string, string>;
|
|
13
|
+
declare function one<const N extends string, const FK extends string>(options: {
|
|
14
|
+
table: N;
|
|
15
|
+
by: FK;
|
|
16
|
+
reverse?: boolean;
|
|
17
|
+
}): OneRelation<N, FK>;
|
|
18
|
+
declare function many<const N extends string, const FK extends string>(options: {
|
|
19
|
+
table: N;
|
|
20
|
+
by: FK;
|
|
21
|
+
}): ManyRelation<N, FK>;
|
|
22
|
+
declare function is_relation(x: unknown): x is Relation;
|
|
23
|
+
declare const relation: {
|
|
24
|
+
one: typeof one;
|
|
25
|
+
many: typeof many;
|
|
26
|
+
is: typeof is_relation;
|
|
27
|
+
};
|
|
28
|
+
export default relation;
|
|
29
|
+
//# sourceMappingURL=relation.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import is from "@rcompat/is";
|
|
2
|
+
function one(options) {
|
|
3
|
+
return {
|
|
4
|
+
type: "one",
|
|
5
|
+
table: options.table,
|
|
6
|
+
fk: options.by,
|
|
7
|
+
reverse: options.reverse ?? false,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function many(options) {
|
|
11
|
+
return {
|
|
12
|
+
type: "many",
|
|
13
|
+
table: options.table,
|
|
14
|
+
fk: options.by,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function is_relation(x) {
|
|
18
|
+
return is.dict(x) && "type" in x && (x.type === "one" || x.type === "many");
|
|
19
|
+
}
|
|
20
|
+
const relation = {
|
|
21
|
+
one,
|
|
22
|
+
many,
|
|
23
|
+
is: is_relation,
|
|
24
|
+
};
|
|
25
|
+
export default relation;
|
|
26
|
+
//# sourceMappingURL=relation.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type Init from "#store/Init";
|
|
2
|
+
import Store from "#store/Store";
|
|
3
|
+
import type StoreInput from "#store/StoreInput";
|
|
4
|
+
declare function store<T extends StoreInput, const N extends string = string>(init: Init<T, N>): Store<T, N>;
|
|
5
|
+
declare namespace store {
|
|
6
|
+
var key: {
|
|
7
|
+
foreign: typeof import("./store/ForeignKey.js").default.new;
|
|
8
|
+
primary: typeof import("./store/PrimaryKey.js").default.new;
|
|
9
|
+
};
|
|
10
|
+
var relation: {
|
|
11
|
+
one: <const N extends string, const FK extends string>(options: {
|
|
12
|
+
table: N;
|
|
13
|
+
by: FK;
|
|
14
|
+
reverse?: boolean;
|
|
15
|
+
}) => import("#store/relation").OneRelation<N, FK>;
|
|
16
|
+
many: <const N extends string, const FK extends string>(options: {
|
|
17
|
+
table: N;
|
|
18
|
+
by: FK;
|
|
19
|
+
}) => import("#store/relation").ManyRelation<N, FK>;
|
|
20
|
+
is: (x: unknown) => x is import("#store/relation").Relation;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export default store;
|
|
24
|
+
//# sourceMappingURL=store.d.ts.map
|