@protobase/server 0.25.5 → 0.25.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/dist/batch/reorder.js
CHANGED
|
@@ -6,7 +6,7 @@ import { fetchRecord } from "../records.js";
|
|
|
6
6
|
import { validateBody } from "../validate-body.js";
|
|
7
7
|
import { missing, shown } from "../write-shared.js";
|
|
8
8
|
import { applyUpdate, planUpdate } from "../write-update.js";
|
|
9
|
-
import { checkRankAfter } from "
|
|
9
|
+
import { checkRankAfter } from "../rank-after.js";
|
|
10
10
|
import { encodeKey } from "@protobase/schema/internal";
|
|
11
11
|
import { sql } from "kysely";
|
|
12
12
|
//#region src/batch/reorder.ts
|
package/dist/check-views.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { relatedErrors } from "./view-related.js";
|
|
1
|
+
import { relatedActionErrors, relatedErrors } from "./view-related.js";
|
|
2
2
|
import { cardPhotosErrors } from "./view-card-photos.js";
|
|
3
3
|
import { cardFavoriteErrors, favoriteOwnerErrors } from "./view-card-favorite.js";
|
|
4
4
|
import { saveSearchErrors, searchesOfErrors } from "./view-saved-search.js";
|
|
@@ -154,7 +154,7 @@ const checkViews = (registry, views, pages = [], fieldTypes = {}) => {
|
|
|
154
154
|
const pageNames = new Set(pages);
|
|
155
155
|
const createPages = new Set(views.filter((view) => view.create).map((view) => view.resource));
|
|
156
156
|
const viewProblems = (view, model) => {
|
|
157
|
-
const related = [...layoutItems(view.layout).flatMap((item) => item.kind === "related" ? relatedErrors(item, view.resource, models).map((error) => `related "${item.title}": ${error}`) : []), ...relatedColumnsErrors(view, model, models)];
|
|
157
|
+
const related = [...layoutItems(view.layout).flatMap((item) => item.kind === "related" ? [...relatedErrors(item, view.resource, models), ...relatedActionErrors(item, views)].map((error) => `related "${item.title}": ${error}`) : []), ...relatedColumnsErrors(view, model, models)];
|
|
158
158
|
const photos = [
|
|
159
159
|
...(view.list?.displays ?? []).flatMap((display) => display.kind === "cards" && display.photos ? cardPhotosErrors(display.photos, view.resource, models).map((error) => `display "${display.name}" photos: ${error}`) : []),
|
|
160
160
|
...sharedPhotosErrors(view),
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { HttpProblem } from "
|
|
2
|
-
import { checkedFilter } from "
|
|
3
|
-
import { tenantScope } from "
|
|
4
|
-
import { columnId, filterCondition, scopeConditions, tableId, whereClause } from "
|
|
1
|
+
import { HttpProblem } from "./problem.js";
|
|
2
|
+
import { checkedFilter } from "./filter-input.js";
|
|
3
|
+
import { tenantScope } from "./tenant.js";
|
|
4
|
+
import { columnId, filterCondition, scopeConditions, tableId, whereClause } from "./sql-parts.js";
|
|
5
5
|
import { newlyAbove } from "./rank-pairs.js";
|
|
6
6
|
import { encodeKey } from "@protobase/schema/internal";
|
|
7
7
|
import { sql } from "kysely";
|
|
8
|
-
//#region src/
|
|
8
|
+
//#region src/rank-after.ts
|
|
9
9
|
/**
|
|
10
|
-
* Refuses a
|
|
11
|
-
* at or below it (see `PositionAfterModel`), in one query of the pairs that involve the
|
|
12
|
-
* order before the
|
|
10
|
+
* Refuses a write of the resource's position (a create, an update or a reorder) that puts a record at or above a record
|
|
11
|
+
* it depends on, or one of those at or below it (see `PositionAfterModel`), in one query of the pairs that involve the
|
|
12
|
+
* changed records. Pairs out of order before the write do not count, so existing data never blocks a move that did not
|
|
13
|
+
* break it.
|
|
13
14
|
*/
|
|
14
15
|
const checkRankAfter = async (trx, deps, access, field, moved) => {
|
|
15
16
|
const { full, entry, session } = access;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { encodeKey } from "@protobase/schema/internal";
|
|
2
|
-
//#region src/
|
|
2
|
+
//#region src/rank-pairs.ts
|
|
3
3
|
const atOrAbove = (dependent, depended) => dependent !== null && depended !== null && Number(dependent) <= Number(depended);
|
|
4
4
|
/**
|
|
5
5
|
* The first pair whose record now ranks at or above the record it depends on but did not before: `before` holds the
|
|
6
|
-
* ranks the records a
|
|
7
|
-
*
|
|
6
|
+
* ranks the records a write changed had, by their encoded key, `null` for a new one. A pair out of order already stays
|
|
7
|
+
* as it is, so a write never fails on an order it did not break.
|
|
8
8
|
*/
|
|
9
9
|
const newlyAbove = (pairs, before) => {
|
|
10
10
|
const was = (key, now) => {
|
package/dist/view-related.js
CHANGED
|
@@ -75,8 +75,15 @@ const relatedErrors = (item, resource, models) => {
|
|
|
75
75
|
return resolved.steps.at(-1).field.sensitive ? [`column: "${column}" is sensitive, so it cannot be listed`] : [];
|
|
76
76
|
}),
|
|
77
77
|
...item.limit === void 0 || Number.isInteger(item.limit) && item.limit >= 1 && item.limit <= 100 ? [] : ["limit must be a whole number from 1 to 100"],
|
|
78
|
-
...openErrors(item, models)
|
|
78
|
+
...openErrors(item, models),
|
|
79
|
+
...item.presentation === void 0 || item.presentation === "cards" ? [] : [`presentation must be "cards", not "${String(item.presentation)}"`],
|
|
80
|
+
...item.actions === void 0 || item.presentation === "cards" ? [] : ["actions need presentation \"cards\""]
|
|
79
81
|
];
|
|
80
82
|
};
|
|
83
|
+
/** The `actions` of a related section that are no named action of its resource's view, among `views`. */
|
|
84
|
+
const relatedActionErrors = (item, views) => {
|
|
85
|
+
const named = new Set(views.find((view) => view.resource === item.resource)?.actions.map((action) => action.name));
|
|
86
|
+
return (item.actions ?? []).flatMap((name) => named.has(name) ? [] : [`actions: "${name}" is no named action of the ${item.resource} view`]);
|
|
87
|
+
};
|
|
81
88
|
//#endregion
|
|
82
|
-
export { filterErrors, matchErrors, openErrors, relatedErrors, sortErrors, throughKey };
|
|
89
|
+
export { filterErrors, matchErrors, openErrors, relatedActionErrors, relatedErrors, sortErrors, throughKey };
|
package/dist/write-pipeline.js
CHANGED
|
@@ -8,8 +8,9 @@ import { isDatabaseError } from "./error-handler.js";
|
|
|
8
8
|
import { attachFiles } from "./files/attach.js";
|
|
9
9
|
import { actorSettings, wakeDispatch } from "./changes/actor.js";
|
|
10
10
|
import { validateBody } from "./validate-body.js";
|
|
11
|
-
import { assertWritten, checkPrecondition, decide, denied, firstKeyField, missing, notifier, shown } from "./write-shared.js";
|
|
11
|
+
import { assertWritten, checkPrecondition, decide, denied, firstKeyField, keyOf, missing, notifier, shown } from "./write-shared.js";
|
|
12
12
|
import { applyUpdate, planUpdate } from "./write-update.js";
|
|
13
|
+
import { checkRankAfter } from "./rank-after.js";
|
|
13
14
|
//#region src/write-pipeline.ts
|
|
14
15
|
const withOwner = (access, values) => {
|
|
15
16
|
const { owner } = access.full;
|
|
@@ -32,13 +33,23 @@ const create = async (trx, deps, access, body) => {
|
|
|
32
33
|
const filter = await decide(access, "create", void 0, row);
|
|
33
34
|
const created = await insertRecord(trx, entry, row, { etagFields: Object.keys(access.model.fields) });
|
|
34
35
|
await assertWritten(trx, access, created, "create", access.resolved.rowFilter.create, filter);
|
|
36
|
+
if (full.position) await checkRankAfter(trx, deps, access, full.position.field, [{
|
|
37
|
+
key: keyOf(access, created.record),
|
|
38
|
+
before: null
|
|
39
|
+
}]);
|
|
35
40
|
await notifier(deps, access)(trx, "create", void 0, created.record);
|
|
36
41
|
return shown(access, created);
|
|
37
42
|
};
|
|
38
43
|
const update = async (trx, deps, access, request) => {
|
|
39
44
|
const plan = await planUpdate(trx, access, request.key, request.body, request.ifMatch);
|
|
40
45
|
if (Object.keys(plan.values).length === 0) return shown(access, plan.current);
|
|
41
|
-
|
|
46
|
+
const updated = await applyUpdate(trx, deps, access, request.key, plan);
|
|
47
|
+
const rank = access.full.position?.field;
|
|
48
|
+
if (rank && rank in plan.values) await checkRankAfter(trx, deps, access, rank, [{
|
|
49
|
+
key: keyOf(access, updated.record),
|
|
50
|
+
before: plan.current.record[rank]
|
|
51
|
+
}]);
|
|
52
|
+
return shown(access, updated);
|
|
42
53
|
};
|
|
43
54
|
const removeOrRestore = async (trx, deps, access, request) => {
|
|
44
55
|
const { entry, full } = access;
|
|
@@ -83,7 +94,8 @@ const removeOrRestore = async (trx, deps, access, request) => {
|
|
|
83
94
|
/**
|
|
84
95
|
* One write inside an open transaction, for a caller whose access is already resolved: operations and field access,
|
|
85
96
|
* tenant scope, validation against the fields they may write, optimistic concurrency, row filters and record-level
|
|
86
|
-
* rules checked in the database, then the hooks
|
|
97
|
+
* rules checked in the database, then the hooks; a rank written by the resource's position with `after` must keep the
|
|
98
|
+
* record below what it depends on (see `checkRankAfter`). Batches run many of these in one transaction.
|
|
87
99
|
*/
|
|
88
100
|
const writeIn = async (trx, deps, access, request) => {
|
|
89
101
|
requireOperation(access, request.operation === "undelete" ? "update" : request.operation);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.7",
|
|
4
4
|
"description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@better-auth/passkey": "^1.7.7",
|
|
34
34
|
"@better-auth/sso": "^1.7.7",
|
|
35
|
-
"@protobase/layout": "0.25.
|
|
36
|
-
"@protobase/query": "0.25.
|
|
37
|
-
"@protobase/schema": "0.25.
|
|
35
|
+
"@protobase/layout": "0.25.7",
|
|
36
|
+
"@protobase/query": "0.25.7",
|
|
37
|
+
"@protobase/schema": "0.25.7",
|
|
38
38
|
"@react-email/render": "^2.1.0",
|
|
39
39
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
40
40
|
"@sentry/core": "^11.7.0",
|