@primate/core 0.9.5 → 0.9.7
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/client/Data.d.ts +1 -0
- package/lib/private/client/boot.d.ts +1 -1
- package/lib/private/client/boot.js +2 -2
- package/lib/private/client/index.d.ts +1 -1
- package/lib/private/client/navigate.js +8 -2
- package/lib/private/client/root.d.ts +2 -1
- package/lib/private/client/root.js +6 -1
- package/lib/private/db/errors.d.ts +3 -0
- package/lib/private/db/errors.js +4 -0
- package/lib/private/db/test.js +18 -0
- package/lib/private/frontend.js +7 -5
- package/lib/private/response/json.d.ts +1 -1
- package/lib/private/store/Store.d.ts +11 -0
- package/lib/private/store/Store.js +10 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Updater } from "#client/root";
|
|
2
2
|
import type { Dict } from "@rcompat/type";
|
|
3
3
|
export default _default;
|
|
4
|
-
declare function _default<T extends Dict>(u: Updater<T>): void;
|
|
4
|
+
declare function _default<T extends Dict>(frontend: string, u: Updater<T>): void;
|
|
5
5
|
//# sourceMappingURL=boot.d.ts.map
|
|
@@ -3,8 +3,8 @@ import root from "#client/root";
|
|
|
3
3
|
import storage from "#client/storage";
|
|
4
4
|
import submit from "#client/submit";
|
|
5
5
|
import http from "@rcompat/http";
|
|
6
|
-
export default (u) => {
|
|
7
|
-
root.set(u);
|
|
6
|
+
export default (frontend, u) => {
|
|
7
|
+
root.set(u, frontend);
|
|
8
8
|
const { location, history } = globalThis;
|
|
9
9
|
if (document.contentType === "application/json") {
|
|
10
10
|
location.reload();
|
|
@@ -11,7 +11,7 @@ import type ValidateUpdater from "#client/ValidateUpdater";
|
|
|
11
11
|
import type ValidationError from "#client/ValidationError";
|
|
12
12
|
import type ViewResponse from "#client/ViewResponse";
|
|
13
13
|
declare const client: {
|
|
14
|
-
boot: <T extends import("@rcompat/type").Dict>(u: import("./root.js").Updater<T>) => void;
|
|
14
|
+
boot: <T extends import("@rcompat/type").Dict>(frontend: string, u: import("./root.js").Updater<T>) => void;
|
|
15
15
|
navigate: (href: string, event?: Event) => Promise<void>;
|
|
16
16
|
submit: typeof submit;
|
|
17
17
|
createForm: typeof createForm;
|
|
@@ -21,8 +21,14 @@ async function goto(target, state = false, after) {
|
|
|
21
21
|
const path = target.pathname + target.search;
|
|
22
22
|
const { requested, response } = await transport.refetch(path, { headers });
|
|
23
23
|
if (transport.is_json(response)) {
|
|
24
|
-
if (response.ok)
|
|
25
|
-
|
|
24
|
+
if (response.ok) {
|
|
25
|
+
const data = await response.json();
|
|
26
|
+
if (!root.same(data)) {
|
|
27
|
+
location.assign(requested.toString() + target.hash);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
root.update(data);
|
|
31
|
+
}
|
|
26
32
|
if (state) {
|
|
27
33
|
storage.new({ hash: location.hash, pathname: location.pathname, scrollTop });
|
|
28
34
|
history.pushState({}, "", `${path}${target.hash}`);
|
|
@@ -2,7 +2,8 @@ import type ClientData from "#client/Data";
|
|
|
2
2
|
import type { Dict } from "@rcompat/type";
|
|
3
3
|
export type Updater<T extends Dict> = (json: ClientData<T>, after?: () => void) => void;
|
|
4
4
|
declare const root: {
|
|
5
|
-
set(updater: Updater<any
|
|
5
|
+
set(updater: Updater<any>, name: string): void;
|
|
6
|
+
same(data: ClientData): boolean;
|
|
6
7
|
update(data: any, after?: () => void): void;
|
|
7
8
|
};
|
|
8
9
|
export default root;
|
|
@@ -34,6 +34,7 @@ declare function sort_invalid_value(field: string, value: unknown): import("@rco
|
|
|
34
34
|
declare function select_empty(): import("@rcompat/error").TemplateError;
|
|
35
35
|
declare function select_invalid(): import("@rcompat/error").TemplateError;
|
|
36
36
|
declare function limit_invalid(): import("@rcompat/error").TemplateError;
|
|
37
|
+
declare function first_limit_invalid(): import("@rcompat/error").TemplateError;
|
|
37
38
|
declare function select_invalid_value(index: number, x: unknown): import("@rcompat/error").TemplateError;
|
|
38
39
|
declare function where_required(): import("@rcompat/error").TemplateError;
|
|
39
40
|
declare function where_invalid(): import("@rcompat/error").TemplateError;
|
|
@@ -94,6 +95,7 @@ declare const errors: {
|
|
|
94
95
|
where_invalid_value: typeof where_invalid_value;
|
|
95
96
|
set_empty: typeof set_empty;
|
|
96
97
|
limit_invalid: typeof limit_invalid;
|
|
98
|
+
first_limit_invalid: typeof first_limit_invalid;
|
|
97
99
|
offset_invalid: typeof offset_invalid;
|
|
98
100
|
offset_requires_limit: typeof offset_requires_limit;
|
|
99
101
|
relation_unknown: typeof relation_unknown;
|
|
@@ -150,6 +152,7 @@ export declare const Code: {
|
|
|
150
152
|
where_invalid_value: "where_invalid_value";
|
|
151
153
|
set_empty: "set_empty";
|
|
152
154
|
limit_invalid: "limit_invalid";
|
|
155
|
+
first_limit_invalid: "first_limit_invalid";
|
|
153
156
|
offset_invalid: "offset_invalid";
|
|
154
157
|
offset_requires_limit: "offset_requires_limit";
|
|
155
158
|
relation_unknown: "relation_unknown";
|
package/lib/private/db/errors.js
CHANGED
|
@@ -152,6 +152,9 @@ function select_invalid() {
|
|
|
152
152
|
function limit_invalid() {
|
|
153
153
|
return t `invalid limit`;
|
|
154
154
|
}
|
|
155
|
+
function first_limit_invalid() {
|
|
156
|
+
return t `${"first"} does not accept ${"limit"}`;
|
|
157
|
+
}
|
|
155
158
|
function select_invalid_value(index, x) {
|
|
156
159
|
return t `select[${index}]: must be string, received ${kind_of(x)}`;
|
|
157
160
|
}
|
|
@@ -185,6 +188,7 @@ const QUERY = error.coded({
|
|
|
185
188
|
where_invalid_value,
|
|
186
189
|
set_empty,
|
|
187
190
|
limit_invalid,
|
|
191
|
+
first_limit_invalid,
|
|
188
192
|
offset_invalid,
|
|
189
193
|
offset_requires_limit,
|
|
190
194
|
});
|
package/lib/private/db/test.js
CHANGED
|
@@ -1498,6 +1498,24 @@ export default (db) => {
|
|
|
1498
1498
|
assert(yes.length).equals(1);
|
|
1499
1499
|
assert(yes[0].name).equals("MiXeDCase");
|
|
1500
1500
|
});
|
|
1501
|
+
$user("first: returns first matching record", async (assert) => {
|
|
1502
|
+
await User.insert({ name: "FirstLow", age: 1 });
|
|
1503
|
+
await User.insert({ name: "FirstHigh", age: 2 });
|
|
1504
|
+
const user = await User.first({
|
|
1505
|
+
where: { name: { $like: "First%" } },
|
|
1506
|
+
sort: { age: "desc" },
|
|
1507
|
+
});
|
|
1508
|
+
assert(user?.name).equals("FirstHigh");
|
|
1509
|
+
});
|
|
1510
|
+
$user("first: returns undefined when no record matches", async (assert) => {
|
|
1511
|
+
const user = await User.first({ where: { name: "MissingFirst" } });
|
|
1512
|
+
assert(user).undefined();
|
|
1513
|
+
});
|
|
1514
|
+
$user$("first: rejects limit", async (assert) => {
|
|
1515
|
+
await throws(assert, Code.first_limit_invalid, () => {
|
|
1516
|
+
return User.first({ limit: 10 });
|
|
1517
|
+
});
|
|
1518
|
+
});
|
|
1501
1519
|
$user("find: $ilike is case-insensitive", async (assert) => {
|
|
1502
1520
|
await User.insert({ name: "MiXeDCase2" });
|
|
1503
1521
|
const rows = await User.find({ where: { name: { $ilike: "mixed%" } } });
|
package/lib/private/frontend.js
CHANGED
|
@@ -18,8 +18,9 @@ export default function frontend_module(init) {
|
|
|
18
18
|
const conditions = init.conditions ?? [];
|
|
19
19
|
const schema = base_schema.extend(init.schema ?? {});
|
|
20
20
|
async function normalize(path) {
|
|
21
|
-
const
|
|
22
|
-
|
|
21
|
+
const extension = init.extensions.find(extension => path.endsWith(extension))
|
|
22
|
+
?? fs.ref(path).fullExtension;
|
|
23
|
+
const basename = path.slice(0, -extension.length);
|
|
23
24
|
return `p_${await hash(`${basename}.${module_name}`)}`;
|
|
24
25
|
}
|
|
25
26
|
return (input) => {
|
|
@@ -57,7 +58,7 @@ export default function frontend_module(init) {
|
|
|
57
58
|
if (app_asset === undefined)
|
|
58
59
|
throw E.frontend_missing_app_js();
|
|
59
60
|
const app_script = `<script type="module" src="${app_asset.src}"></script>`;
|
|
60
|
-
const props = JSON.stringify(
|
|
61
|
+
const props = JSON.stringify(client);
|
|
61
62
|
const hydrated = await inline(props, http.MIME.APPLICATION_JSON, "hydration");
|
|
62
63
|
const script_src = [hydrated.integrity];
|
|
63
64
|
return {
|
|
@@ -83,6 +84,7 @@ export default function frontend_module(init) {
|
|
|
83
84
|
}
|
|
84
85
|
: { props, request: $request };
|
|
85
86
|
const client = {
|
|
87
|
+
frontend: init.name,
|
|
86
88
|
view: init.layouts ? "root" : await normalize(view),
|
|
87
89
|
csr,
|
|
88
90
|
ssr: ssr(),
|
|
@@ -206,11 +208,11 @@ export default function frontend_module(init) {
|
|
|
206
208
|
build.onLoad({ filter: views_filter }, async () => {
|
|
207
209
|
const views = await views_base.files({
|
|
208
210
|
recursive: true,
|
|
209
|
-
filter: c => extensions.
|
|
211
|
+
filter: c => extensions.some(ext => c.path.endsWith(ext)),
|
|
210
212
|
});
|
|
211
213
|
const pages = await pages_base.files({
|
|
212
214
|
recursive: true,
|
|
213
|
-
filter: c => extensions.
|
|
215
|
+
filter: c => extensions.some(ext => c.path.endsWith(ext)),
|
|
214
216
|
});
|
|
215
217
|
let contents = "";
|
|
216
218
|
for (const view of views) {
|
|
@@ -5,5 +5,5 @@ import type ResponseFunction from "#response/ResponseFunction";
|
|
|
5
5
|
* @param options response options
|
|
6
6
|
* @return Response rendering function
|
|
7
7
|
*/
|
|
8
|
-
export default function json<
|
|
8
|
+
export default function json<T>(body: T, init?: ResponseInit): ResponseFunction<never, T>;
|
|
9
9
|
//# sourceMappingURL=json.d.ts.map
|
|
@@ -179,6 +179,17 @@ export default class Store<T extends StoreInput, N extends string = string> impl
|
|
|
179
179
|
offset?: number;
|
|
180
180
|
with?: W;
|
|
181
181
|
}): Promise<WithRelations<Projected<Schema<T>, S>, ExtractRelations<T>, W>[]>;
|
|
182
|
+
/**
|
|
183
|
+
* Find the first matching record.
|
|
184
|
+
*/
|
|
185
|
+
first<const S extends Select<Schema<T>> | undefined = undefined, const W extends WithInput<ExtractRelations<T>> | undefined = undefined>(options?: {
|
|
186
|
+
where?: Where<T>;
|
|
187
|
+
select?: S;
|
|
188
|
+
sort?: Sort<Schema<T>>;
|
|
189
|
+
offset?: number;
|
|
190
|
+
with?: W;
|
|
191
|
+
limit?: never;
|
|
192
|
+
}): Promise<WithRelations<Projected<Schema<T>, S>, ExtractRelations<T>, W> | undefined>;
|
|
182
193
|
toJSON(): {
|
|
183
194
|
type: "object";
|
|
184
195
|
properties: Dict<import("pema").Serialized>;
|
|
@@ -590,6 +590,16 @@ export default class Store {
|
|
|
590
590
|
});
|
|
591
591
|
return result;
|
|
592
592
|
}
|
|
593
|
+
/**
|
|
594
|
+
* Find the first matching record.
|
|
595
|
+
*/
|
|
596
|
+
async first(options) {
|
|
597
|
+
guard_options(options, ["where", "select", "sort", "offset", "with", "limit"]);
|
|
598
|
+
if (is.defined(options?.limit))
|
|
599
|
+
throw E.first_limit_invalid();
|
|
600
|
+
const [first] = await this.find({ ...options, limit: 1 });
|
|
601
|
+
return first;
|
|
602
|
+
}
|
|
593
603
|
toJSON() {
|
|
594
604
|
return this.#schema.toJSON();
|
|
595
605
|
}
|