@protobase/server 0.25.6 → 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.
@@ -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 "./rank-after.js";
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
@@ -1,15 +1,16 @@
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";
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/batch/rank-after.ts
8
+ //#region src/rank-after.ts
9
9
  /**
10
- * Refuses a reorder by the resource's position that puts a record at or above a record it depends on, or one of those
11
- * at or below it (see `PositionAfterModel`), in one query of the pairs that involve the changed records. Pairs out of
12
- * order before the reorder do not count, so existing data never blocks a move that did not break it.
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/batch/rank-pairs.ts
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 reorder changed had, by their encoded key. A pair out of order already stays as it is, so a
7
- * reorder never fails on an order it did not break.
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) => {
@@ -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
- return shown(access, await applyUpdate(trx, deps, access, request.key, plan));
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. Batches run many of these in one transaction.
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.6",
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.6",
36
- "@protobase/query": "0.25.6",
37
- "@protobase/schema": "0.25.6",
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",