@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.
Files changed (185) hide show
  1. package/lib/private/App.d.ts +76 -149
  2. package/lib/private/App.js +22 -6
  3. package/lib/private/Flags.d.ts +5 -3
  4. package/lib/private/Flags.js +4 -2
  5. package/lib/private/app/Facade.d.ts +77 -156
  6. package/lib/private/app/Facade.js +4 -1
  7. package/lib/private/build/App.d.ts +4 -2
  8. package/lib/private/build/App.js +13 -4
  9. package/lib/private/build/client/index.js +24 -10
  10. package/lib/private/build/client/plugin/routes.d.ts +4 -0
  11. package/lib/private/build/client/plugin/routes.js +74 -0
  12. package/lib/private/build/hook.js +14 -6
  13. package/lib/private/build/index.d.ts +3 -2
  14. package/lib/private/build/index.js +9 -15
  15. package/lib/private/build/preclient/index.d.ts +3 -0
  16. package/lib/private/build/preclient/index.js +69 -0
  17. package/lib/private/build/preclient/plugin/routes.d.ts +4 -0
  18. package/lib/private/build/preclient/plugin/routes.js +44 -0
  19. package/lib/private/build/server/index.js +8 -5
  20. package/lib/private/build/server/plugin/assets.js +3 -3
  21. package/lib/private/build/server/plugin/native-addons.js +6 -8
  22. package/lib/private/build/server/plugin/node-imports.js +5 -7
  23. package/lib/private/build/server/plugin/route-client.d.ts +4 -0
  24. package/lib/private/build/server/plugin/route-client.js +110 -0
  25. package/lib/private/build/server/plugin/route.js +3 -10
  26. package/lib/private/build/server/plugin/virtual-pages.js +3 -3
  27. package/lib/private/build/server/plugin/virtual-routes.d.ts +2 -1
  28. package/lib/private/build/server/plugin/virtual-routes.js +27 -8
  29. package/lib/private/build/server/plugin/wasm.js +3 -2
  30. package/lib/private/client/Data.d.ts +1 -1
  31. package/lib/private/client/boot.js +2 -2
  32. package/lib/private/client/create-form.d.ts +11 -1
  33. package/lib/private/client/create-form.js +21 -3
  34. package/lib/private/client/index.d.ts +2 -2
  35. package/lib/private/client/navigate.d.ts +1 -0
  36. package/lib/private/client/navigate.js +7 -6
  37. package/lib/private/client/submit.d.ts +2 -1
  38. package/lib/private/client/submit.js +8 -7
  39. package/lib/private/client/{http.d.ts → transport.d.ts} +3 -3
  40. package/lib/private/client/{http.js → transport.js} +7 -9
  41. package/lib/private/config/schema.d.ts +5 -13
  42. package/lib/private/config/schema.js +1 -5
  43. package/lib/private/db/DB.d.ts +3 -1
  44. package/lib/private/db/MemoryDB.d.ts +5 -2
  45. package/lib/private/db/MemoryDB.js +33 -19
  46. package/lib/private/db/SQLDB.d.ts +23 -0
  47. package/lib/private/db/SQLDB.js +2 -0
  48. package/lib/private/db/errors.d.ts +74 -7
  49. package/lib/private/db/errors.js +31 -7
  50. package/lib/private/db/migrate/apply.js +8 -9
  51. package/lib/private/db/migrate/bundle.js +2 -2
  52. package/lib/private/db/migrate/create.js +18 -20
  53. package/lib/private/db/migrate/status.js +9 -10
  54. package/lib/private/db/migrate/store.d.ts +3 -3
  55. package/lib/private/db/migrate/store.js +5 -5
  56. package/lib/private/db/test.js +256 -115
  57. package/lib/private/db/testSQL.d.ts +50 -0
  58. package/lib/private/db/testSQL.js +196 -0
  59. package/lib/private/errors.d.ts +66 -9
  60. package/lib/private/errors.js +37 -16
  61. package/lib/private/frontend.d.ts +4 -4
  62. package/lib/private/frontend.js +11 -8
  63. package/lib/private/i18n/errors.d.ts +7 -1
  64. package/lib/private/i18n/errors.js +1 -1
  65. package/lib/private/i18n/index/types.d.ts +1 -1
  66. package/lib/private/i18n/locale.d.ts +1 -1
  67. package/lib/private/i18n/module.js +6 -5
  68. package/lib/private/index.d.ts +10 -2
  69. package/lib/private/index.js +13 -1
  70. package/lib/private/logger.d.ts +24 -0
  71. package/lib/private/logger.js +66 -0
  72. package/lib/private/module/Setup.d.ts +3 -0
  73. package/lib/private/module/create.d.ts +2 -1
  74. package/lib/private/module/create.js +4 -0
  75. package/lib/private/paths.d.ts +2 -3
  76. package/lib/private/paths.js +6 -12
  77. package/lib/private/request/ContentType.d.ts +3 -0
  78. package/lib/private/request/ContentType.js +2 -0
  79. package/lib/private/request/RequestBag.d.ts +2 -18
  80. package/lib/private/request/RequestBag.js +4 -16
  81. package/lib/private/request/RequestBody.d.ts +20 -28
  82. package/lib/private/request/RequestBody.js +63 -86
  83. package/lib/private/request/RequestFacade.d.ts +2 -2
  84. package/lib/private/request/handle.js +2 -2
  85. package/lib/private/request/parse.js +2 -4
  86. package/lib/private/request/route.js +15 -8
  87. package/lib/private/response/binary.d.ts +2 -2
  88. package/lib/private/response/binary.js +6 -6
  89. package/lib/private/response/error.js +6 -2
  90. package/lib/private/response/json.js +2 -2
  91. package/lib/private/response/null.d.ts +3 -0
  92. package/lib/private/response/null.js +6 -0
  93. package/lib/private/response/redirect.js +4 -3
  94. package/lib/private/response/respond.js +4 -4
  95. package/lib/private/response/sse.js +2 -2
  96. package/lib/private/response/text.js +2 -2
  97. package/lib/private/route/ContentTypeMap.d.ts +10 -0
  98. package/lib/private/route/ContentTypeMap.js +2 -0
  99. package/lib/private/route/Handler.d.ts +3 -2
  100. package/lib/private/route/NarrowedRequest.d.ts +23 -0
  101. package/lib/private/route/NarrowedRequest.js +2 -0
  102. package/lib/private/route/Options.d.ts +6 -1
  103. package/lib/private/route/Path.d.ts +2 -2
  104. package/lib/private/route/hook.d.ts +3 -1
  105. package/lib/private/route/hook.js +1 -2
  106. package/lib/private/route/router.d.ts +7 -11
  107. package/lib/private/route/router.js +13 -20
  108. package/lib/private/route.client.d.ts +36 -0
  109. package/lib/private/route.client.js +8 -0
  110. package/lib/private/route.d.ts +21 -5
  111. package/lib/private/route.js +21 -5
  112. package/lib/private/serve/App.d.ts +1 -2
  113. package/lib/private/serve/App.js +64 -58
  114. package/lib/private/serve/Init.d.ts +2 -0
  115. package/lib/private/serve/dev-module.js +2 -3
  116. package/lib/private/serve/index.js +5 -6
  117. package/lib/private/server/TAG.d.ts +1 -1
  118. package/lib/private/server/TAG.js +1 -1
  119. package/lib/private/session/index.d.ts +1 -1
  120. package/lib/private/session/index.js +3 -2
  121. package/lib/private/session/module.js +3 -3
  122. package/lib/private/session/schema.d.ts +3 -3
  123. package/lib/private/session/schema.js +4 -3
  124. package/lib/private/store/ExtractRelation.d.ts +7 -0
  125. package/lib/private/store/ExtractRelation.js +2 -0
  126. package/lib/private/store/ExtractSchema.d.ts +10 -0
  127. package/lib/private/{orm → store}/ForeignKey.d.ts +1 -1
  128. package/lib/private/store/Init.d.ts +13 -0
  129. package/lib/private/store/Init.js +2 -0
  130. package/lib/private/{orm/store.d.ts → store/Store.d.ts} +50 -50
  131. package/lib/private/{orm/store.js → store/Store.js} +163 -107
  132. package/lib/private/store/StoreInput.d.ts +11 -0
  133. package/lib/private/store/key.d.ts +8 -0
  134. package/lib/private/store/key.js +8 -0
  135. package/lib/private/{orm → store}/parse.d.ts +3 -3
  136. package/lib/private/{orm → store}/parse.js +7 -2
  137. package/lib/private/store/relation.d.ts +29 -0
  138. package/lib/private/store/relation.js +26 -0
  139. package/lib/private/store.d.ts +24 -0
  140. package/lib/private/store.js +10 -0
  141. package/lib/public/db/errors.d.ts +1 -1
  142. package/lib/public/db/errors.js +1 -1
  143. package/lib/public/db/testSQL.d.ts +2 -0
  144. package/lib/public/db/testSQL.js +2 -0
  145. package/lib/public/db.d.ts +1 -0
  146. package/lib/public/index.d.ts +1 -0
  147. package/lib/public/index.js +1 -1
  148. package/lib/public/response.d.ts +6 -6
  149. package/lib/public/response.js +4 -1
  150. package/lib/public/route.client.d.ts +2 -0
  151. package/lib/public/route.client.js +2 -0
  152. package/lib/public/store.d.ts +2 -0
  153. package/lib/public/store.js +2 -0
  154. package/package.json +24 -17
  155. package/lib/private/bye.d.ts +0 -3
  156. package/lib/private/bye.js +0 -4
  157. package/lib/private/log.d.ts +0 -20
  158. package/lib/private/log.js +0 -47
  159. package/lib/private/orm/ExtractSchema.d.ts +0 -9
  160. package/lib/private/orm/StoreInput.d.ts +0 -10
  161. package/lib/private/orm/key.d.ts +0 -8
  162. package/lib/private/orm/key.js +0 -8
  163. package/lib/private/orm/relation.d.ts +0 -43
  164. package/lib/private/orm/relation.js +0 -26
  165. package/lib/private/request/Verb.d.ts +0 -4
  166. package/lib/private/request/Verb.js +0 -2
  167. package/lib/private/request/verbs.d.ts +0 -3
  168. package/lib/private/request/verbs.js +0 -12
  169. package/lib/private/route/wrap.d.ts +0 -2
  170. package/lib/private/route/wrap.js +0 -12
  171. package/lib/public/log.d.ts +0 -2
  172. package/lib/public/log.js +0 -2
  173. package/lib/public/orm/key.d.ts +0 -2
  174. package/lib/public/orm/key.js +0 -2
  175. package/lib/public/orm/relation.d.ts +0 -2
  176. package/lib/public/orm/relation.js +0 -2
  177. package/lib/public/orm/store.d.ts +0 -2
  178. package/lib/public/orm/store.js +0 -2
  179. package/lib/public/request/verbs.d.ts +0 -2
  180. package/lib/public/request/verbs.js +0 -2
  181. /package/lib/private/{orm → store}/ExtractSchema.js +0 -0
  182. /package/lib/private/{orm → store}/ForeignKey.js +0 -0
  183. /package/lib/private/{orm → store}/PrimaryKey.d.ts +0 -0
  184. /package/lib/private/{orm → store}/PrimaryKey.js +0 -0
  185. /package/lib/private/{orm → store}/StoreInput.js +0 -0
@@ -1,8 +1,11 @@
1
1
  import E from "#db/errors";
2
- import parse from "#orm/parse";
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 === undefined)
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 in INT_LIMITS) {
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 registry = new Map();
55
- const STRING_OPS = ["$like", "$ilike"];
56
- const NUMBER_OPS = ["$gt", "$gte", "$lt", "$lte", "$ne"];
57
- const BIGINT_OPS = ["$gt", "$gte", "$lt", "$lte", "$ne"];
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
- #name;
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 { name, db, migrate, id } = init;
86
+ const { table, db, migrate, id } = init;
82
87
  const { pk, generate_pk, fks, schema } = parse(init.schema);
83
- if (name === undefined)
84
- throw E.store_name_required();
85
- assert.string(name);
86
- if (!VALID_IDENTIFIER.test(name))
87
- throw E.identifier_invalid(name);
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.#name = name;
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.relations ?? {};
107
+ this.#relations = Object.fromEntries(Object.entries(init.schema).filter(([, v]) => relation.is(v)));
104
108
  this.#migrate = migrate ?? true;
105
- for (const relation of Object.keys(this.#relations)) {
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 !== undefined) {
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.name,
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
- get table() {
130
- const db = this.db;
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
- return {
136
- create: () => db.schema.create(this.#name, pk, this.#types),
137
- delete: () => db.schema.delete(this.#name),
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 name() {
156
- return this.#name;
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 === null)
170
- throw E.pk_undefined(this.name);
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 !== undefined)
174
+ const { where, select, sort, limit, offset } = query;
175
+ if (is.defined(where))
182
176
  this.#parse_where(where, types);
183
- if (select !== undefined)
177
+ if (is.defined(select))
184
178
  this.#parse_select(select, types);
185
- if (sort !== undefined)
179
+ if (is.defined(sort))
186
180
  this.#parse_sort(sort, types);
187
- if (limit !== undefined && !is.uint(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 === undefined)
191
+ if (is.undefined(options))
192
192
  return undefined;
193
193
  const plan = {};
194
- for (const [name, query] of Object.entries(options)) {
195
- if (query === undefined)
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 === undefined)
198
+ if (is.undefined(relation))
199
199
  throw E.relation_unknown(name);
200
- const store = registry.get(relation.schema);
201
- if (store === undefined)
202
- throw E.unregistered_schema();
203
- const { pk: target_pk } = parse(relation.schema);
204
- const target_types = store.types;
205
- const base = {
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: store.name,
208
- pk: target_pk,
209
- types: target_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
- if (query === true) {
216
- plan[name] = { ...base, where: {} };
217
- continue;
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 in types))
236
+ if (!dict.has(types, k))
237
237
  throw E.field_unknown(k, "where");
238
- if (value === undefined)
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 === null)
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 in types))
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 in types))
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
- #parse_insert(record) {
436
+ #prepare_insert(record) {
437
+ const out = {};
378
438
  for (const [k, v] of Object.entries(record)) {
379
- if (!(k in this.#types))
439
+ if (!dict.has(this.#types, k))
380
440
  throw E.field_unknown(k, "insert");
381
- if (v === undefined)
382
- throw E.field_undefined(k, "insert");
383
- if (v === null)
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]) => k in this.#types));
430
- const parsed = this.#type.parse(base_only);
431
- if ($with === undefined)
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.#parse_insert(record);
454
- const entries = Object.entries(record);
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 (!(k in this.#types))
529
+ if (!dict.has(this.#types, k))
470
530
  throw E.field_unknown(k, "set");
471
- if (v === null && !this.#nullables.has(k))
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.#type.partial().parse(to_parse),
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 !== undefined)
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.#type.toJSON();
594
+ return this.#schema.toJSON();
534
595
  }
535
596
  extend(extensor) {
536
597
  return new Store({
537
- name: this.name,
538
- db: this.db,
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
- function store(init) {
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
@@ -0,0 +1,8 @@
1
+ import ForeignKey from "#store/ForeignKey";
2
+ import PrimaryKey from "#store/PrimaryKey";
3
+ declare const key: {
4
+ foreign: typeof ForeignKey.new;
5
+ primary: typeof PrimaryKey.new;
6
+ };
7
+ export default key;
8
+ //# sourceMappingURL=key.d.ts.map
@@ -0,0 +1,8 @@
1
+ import ForeignKey from "#store/ForeignKey";
2
+ import PrimaryKey from "#store/PrimaryKey";
3
+ const key = {
4
+ foreign: ForeignKey.new,
5
+ primary: PrimaryKey.new,
6
+ };
7
+ export default key;
8
+ //# sourceMappingURL=key.js.map
@@ -1,7 +1,7 @@
1
1
  import type PK from "#db/PK";
2
- import ForeignKey from "#orm/ForeignKey";
3
- import type { AllowedFKType } from "#orm/ForeignKey";
4
- import type StoreInput from "#orm/StoreInput";
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 "#orm/ForeignKey";
3
- import PrimaryKey from "#orm/PrimaryKey";
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