@jskit-ai/database-runtime 0.1.32 → 0.1.34
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/package.descriptor.mjs +2 -2
- package/package.json +2 -2
- package/src/server/providers/DatabaseRuntimeServiceProvider.js +3 -2
- package/src/shared/databaseConnection.js +31 -2
- package/src/shared/index.js +6 -2
- package/src/shared/repositoryOptions.js +50 -1
- package/src/shared/retention.js +4 -3
- package/src/shared/visibility.js +14 -14
- package/templates/knexfile.js +3 -2
- package/test/repositoryOptions.test.js +21 -1
- package/test/repositoryScope.test.js +8 -8
- package/test/runtimeCore.test.js +11 -1
- package/test/visibility.test.js +7 -7
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/database-runtime",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.34",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
dependsOn: [
|
|
7
7
|
"@jskit-ai/kernel"
|
|
@@ -58,7 +58,7 @@ export default Object.freeze({
|
|
|
58
58
|
mutations: {
|
|
59
59
|
dependencies: {
|
|
60
60
|
runtime: {
|
|
61
|
-
"@jskit-ai/kernel": "0.1.
|
|
61
|
+
"@jskit-ai/kernel": "0.1.34",
|
|
62
62
|
"dotenv": "^16.4.5",
|
|
63
63
|
"knex": "^3.1.0"
|
|
64
64
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/database-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -25,6 +25,6 @@
|
|
|
25
25
|
"./shared/transactions": "./src/shared/transactions.js"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@jskit-ai/kernel": "0.1.
|
|
28
|
+
"@jskit-ai/kernel": "0.1.34"
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
normalizeDatabaseClient,
|
|
6
6
|
toKnexClientId
|
|
7
7
|
} from "../../shared/databaseClient.js";
|
|
8
|
-
import {
|
|
8
|
+
import { resolveKnexConnectionFromEnvironment } from "../../shared/databaseConnection.js";
|
|
9
9
|
|
|
10
10
|
const DATABASE_RUNTIME_SERVER_API = Object.freeze({
|
|
11
11
|
...databaseRuntime
|
|
@@ -120,7 +120,8 @@ function createKnexConfig(scope) {
|
|
|
120
120
|
|
|
121
121
|
const client = toKnexClientId(dialectId);
|
|
122
122
|
const defaultPort = dialectId === "pg" ? 5432 : 3306;
|
|
123
|
-
const connection =
|
|
123
|
+
const connection = resolveKnexConnectionFromEnvironment(env, {
|
|
124
|
+
client: dialectId,
|
|
124
125
|
defaultPort,
|
|
125
126
|
context: "database runtime"
|
|
126
127
|
});
|
|
@@ -100,7 +100,6 @@ function resolveDatabaseConnectionFromEnvironment(
|
|
|
100
100
|
const hasDbPassword = Object.prototype.hasOwnProperty.call(source, "DB_PASSWORD");
|
|
101
101
|
const password = hasDbPassword ? String(source.DB_PASSWORD ?? "") : String(parsedUrl?.password || "");
|
|
102
102
|
|
|
103
|
-
// Knex may redefine connection.password as a hidden property; keep this mutable.
|
|
104
103
|
return {
|
|
105
104
|
host,
|
|
106
105
|
port,
|
|
@@ -110,8 +109,38 @@ function resolveDatabaseConnectionFromEnvironment(
|
|
|
110
109
|
};
|
|
111
110
|
}
|
|
112
111
|
|
|
112
|
+
function resolveKnexConnectionFromEnvironment(
|
|
113
|
+
env = {},
|
|
114
|
+
{
|
|
115
|
+
client = "",
|
|
116
|
+
defaultHost = "localhost",
|
|
117
|
+
defaultPort = 3306,
|
|
118
|
+
context = "database runtime"
|
|
119
|
+
} = {}
|
|
120
|
+
) {
|
|
121
|
+
const resolvedClient = client
|
|
122
|
+
? normalizeDatabaseClient(client, { allowEmpty: true })
|
|
123
|
+
: resolveDatabaseClientFromEnvironment(env, { allowEmpty: true });
|
|
124
|
+
const connection = resolveDatabaseConnectionFromEnvironment(env, {
|
|
125
|
+
defaultHost,
|
|
126
|
+
defaultPort,
|
|
127
|
+
context
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (resolvedClient === "mysql2") {
|
|
131
|
+
return {
|
|
132
|
+
...connection,
|
|
133
|
+
supportBigNumbers: true,
|
|
134
|
+
bigNumberStrings: true
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return connection;
|
|
139
|
+
}
|
|
140
|
+
|
|
113
141
|
export {
|
|
114
142
|
parseDatabaseUrl,
|
|
115
143
|
resolveDatabaseClientFromEnvironment,
|
|
116
|
-
resolveDatabaseConnectionFromEnvironment
|
|
144
|
+
resolveDatabaseConnectionFromEnvironment,
|
|
145
|
+
resolveKnexConnectionFromEnvironment
|
|
117
146
|
};
|
package/src/shared/index.js
CHANGED
|
@@ -18,7 +18,8 @@ export { normalizeText, normalizeDatabaseClient, toKnexClientId } from "./databa
|
|
|
18
18
|
export {
|
|
19
19
|
parseDatabaseUrl,
|
|
20
20
|
resolveDatabaseClientFromEnvironment,
|
|
21
|
-
resolveDatabaseConnectionFromEnvironment
|
|
21
|
+
resolveDatabaseConnectionFromEnvironment,
|
|
22
|
+
resolveKnexConnectionFromEnvironment
|
|
22
23
|
} from "./databaseConnection.js";
|
|
23
24
|
export { isDuplicateEntryError } from "./duplicateEntry.js";
|
|
24
25
|
export { normalizePath, jsonTextExpression, whereJsonTextEquals } from "./json.js";
|
|
@@ -35,11 +36,14 @@ export {
|
|
|
35
36
|
stringifyMetadataJson,
|
|
36
37
|
normalizeMetadataJsonInput,
|
|
37
38
|
normalizeNullableString,
|
|
39
|
+
normalizeDbRecordId,
|
|
40
|
+
resolveInsertedRecordId,
|
|
38
41
|
normalizeIdList,
|
|
39
42
|
normalizeCountRow,
|
|
40
43
|
parseJsonValue,
|
|
41
44
|
toDbJson,
|
|
42
|
-
runInTransaction
|
|
45
|
+
runInTransaction,
|
|
46
|
+
createWithTransaction
|
|
43
47
|
} from "./repositoryOptions.js";
|
|
44
48
|
export {
|
|
45
49
|
normalizeBatchSize,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeCanonicalRecordIdText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
+
|
|
1
3
|
function resolveQueryOptions(options = {}) {
|
|
2
4
|
if (!options || typeof options !== "object") {
|
|
3
5
|
return {
|
|
@@ -120,6 +122,44 @@ function normalizeNullableString(value, { trim = true } = {}) {
|
|
|
120
122
|
return normalized || null;
|
|
121
123
|
}
|
|
122
124
|
|
|
125
|
+
function normalizeDbRecordId(value, { fallback = null } = {}) {
|
|
126
|
+
if (value == null) {
|
|
127
|
+
return fallback;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (typeof value === "string") {
|
|
131
|
+
return normalizeCanonicalRecordIdText(value, { fallback });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (typeof value === "number") {
|
|
135
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
136
|
+
return fallback;
|
|
137
|
+
}
|
|
138
|
+
return normalizeCanonicalRecordIdText(value, { fallback });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (typeof value === "bigint") {
|
|
142
|
+
if (value < 1n) {
|
|
143
|
+
return fallback;
|
|
144
|
+
}
|
|
145
|
+
return normalizeCanonicalRecordIdText(value, { fallback });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (typeof value === "object" && !Array.isArray(value) && Object.hasOwn(value, "id")) {
|
|
149
|
+
return normalizeDbRecordId(value.id, { fallback });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return normalizeCanonicalRecordIdText(value, { fallback });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function resolveInsertedRecordId(insertResult, { fallback = null } = {}) {
|
|
156
|
+
if (Array.isArray(insertResult) && insertResult.length > 0) {
|
|
157
|
+
return normalizeDbRecordId(insertResult[0], { fallback });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return normalizeDbRecordId(insertResult, { fallback });
|
|
161
|
+
}
|
|
162
|
+
|
|
123
163
|
function normalizeIdList(values, { parseValue } = {}) {
|
|
124
164
|
const source = Array.isArray(values) ? values : [];
|
|
125
165
|
const parser = typeof parseValue === "function" ? parseValue : (value) => value;
|
|
@@ -196,6 +236,12 @@ async function runInTransaction(knex, callback) {
|
|
|
196
236
|
return knex.transaction(callback);
|
|
197
237
|
}
|
|
198
238
|
|
|
239
|
+
function createWithTransaction(knex) {
|
|
240
|
+
return function withTransaction(work) {
|
|
241
|
+
return runInTransaction(knex, work);
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
199
245
|
export {
|
|
200
246
|
resolveQueryOptions,
|
|
201
247
|
resolveRepoClient,
|
|
@@ -207,9 +253,12 @@ export {
|
|
|
207
253
|
stringifyMetadataJson,
|
|
208
254
|
normalizeMetadataJsonInput,
|
|
209
255
|
normalizeNullableString,
|
|
256
|
+
normalizeDbRecordId,
|
|
257
|
+
resolveInsertedRecordId,
|
|
210
258
|
normalizeIdList,
|
|
211
259
|
normalizeCountRow,
|
|
212
260
|
parseJsonValue,
|
|
213
261
|
toDbJson,
|
|
214
|
-
runInTransaction
|
|
262
|
+
runInTransaction,
|
|
263
|
+
createWithTransaction
|
|
215
264
|
};
|
package/src/shared/retention.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { toDatabaseDateTimeUtc } from "./dateUtils.js";
|
|
2
|
+
import { normalizeDbRecordId } from "./repositoryOptions.js";
|
|
2
3
|
|
|
3
4
|
function normalizeBatchSize(value, { fallback = 1000, max = 10_000 } = {}) {
|
|
4
5
|
const parsed = Number(value);
|
|
@@ -40,12 +41,12 @@ async function deleteRowsOlderThan({ client, tableName, dateColumn, cutoffDate,
|
|
|
40
41
|
return 0;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
44
|
+
const recordIds = ids.map((entry) => normalizeDbRecordId(entry?.id)).filter(Boolean);
|
|
45
|
+
if (recordIds.length < 1) {
|
|
45
46
|
return 0;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
const deleted = await client(tableName).whereIn("id",
|
|
49
|
+
const deleted = await client(tableName).whereIn("id", recordIds).del();
|
|
49
50
|
return normalizeDeletedRowCount(deleted);
|
|
50
51
|
}
|
|
51
52
|
|
package/src/shared/visibility.js
CHANGED
|
@@ -2,14 +2,14 @@ import { normalizeVisibilityContext } from "@jskit-ai/kernel/shared/support/visi
|
|
|
2
2
|
|
|
3
3
|
const ALWAYS_FALSE_SQL = "1 = 0";
|
|
4
4
|
const DEFAULT_VISIBILITY_COLUMNS = Object.freeze({
|
|
5
|
-
scopeOwnerId: "
|
|
6
|
-
|
|
5
|
+
scopeOwnerId: "workspace_id",
|
|
6
|
+
userId: "user_id"
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
function applyVisibility(queryBuilder, visibilityContext = {}) {
|
|
10
10
|
const context = normalizeVisibilityContext(visibilityContext);
|
|
11
11
|
const workspaceColumn = DEFAULT_VISIBILITY_COLUMNS.scopeOwnerId;
|
|
12
|
-
const userColumn = DEFAULT_VISIBILITY_COLUMNS.
|
|
12
|
+
const userColumn = DEFAULT_VISIBILITY_COLUMNS.userId;
|
|
13
13
|
|
|
14
14
|
if (context.visibility === "public") {
|
|
15
15
|
return queryBuilder;
|
|
@@ -23,17 +23,17 @@ function applyVisibility(queryBuilder, visibilityContext = {}) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
if (context.visibility === "user") {
|
|
26
|
-
if (!context.
|
|
26
|
+
if (!context.userId) {
|
|
27
27
|
return queryBuilder.whereRaw(ALWAYS_FALSE_SQL);
|
|
28
28
|
}
|
|
29
|
-
return queryBuilder.where(userColumn, context.
|
|
29
|
+
return queryBuilder.where(userColumn, context.userId);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
if (!context.scopeOwnerId || !context.
|
|
32
|
+
if (!context.scopeOwnerId || !context.userId) {
|
|
33
33
|
return queryBuilder.whereRaw(ALWAYS_FALSE_SQL);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
return queryBuilder.where(workspaceColumn, context.scopeOwnerId).where(userColumn, context.
|
|
36
|
+
return queryBuilder.where(workspaceColumn, context.scopeOwnerId).where(userColumn, context.userId);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function applyVisibilityOwners(payload = {}, visibilityContext = {}) {
|
|
@@ -49,11 +49,11 @@ function applyVisibilityOwners(payload = {}, visibilityContext = {}) {
|
|
|
49
49
|
if (context.visibility === "workspace" && !context.scopeOwnerId) {
|
|
50
50
|
throw new Error("Visibility context requires scopeOwnerId.");
|
|
51
51
|
}
|
|
52
|
-
if (context.visibility === "user" && !context.
|
|
53
|
-
throw new Error("Visibility context requires
|
|
52
|
+
if (context.visibility === "user" && !context.userId) {
|
|
53
|
+
throw new Error("Visibility context requires userId.");
|
|
54
54
|
}
|
|
55
|
-
if (context.visibility === "workspace_user" && (!context.scopeOwnerId || !context.
|
|
56
|
-
throw new Error("Visibility context requires scopeOwnerId and
|
|
55
|
+
if (context.visibility === "workspace_user" && (!context.scopeOwnerId || !context.userId)) {
|
|
56
|
+
throw new Error("Visibility context requires scopeOwnerId and userId.");
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
const ownedPayload = {
|
|
@@ -61,10 +61,10 @@ function applyVisibilityOwners(payload = {}, visibilityContext = {}) {
|
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
if (context.scopeOwnerId) {
|
|
64
|
-
ownedPayload.
|
|
64
|
+
ownedPayload.workspace_id = context.scopeOwnerId;
|
|
65
65
|
}
|
|
66
|
-
if (context.
|
|
67
|
-
ownedPayload.
|
|
66
|
+
if (context.userId) {
|
|
67
|
+
ownedPayload.user_id = context.userId;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
return ownedPayload;
|
package/templates/knexfile.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
normalizeText,
|
|
5
5
|
toKnexClientId,
|
|
6
6
|
resolveDatabaseClientFromEnvironment,
|
|
7
|
-
|
|
7
|
+
resolveKnexConnectionFromEnvironment
|
|
8
8
|
} from "@jskit-ai/database-runtime/shared";
|
|
9
9
|
|
|
10
10
|
const appRoot = process.cwd();
|
|
@@ -20,7 +20,8 @@ const migrationsDirectory = path.resolve(appRoot, normalizeText(process.env.DB_M
|
|
|
20
20
|
|
|
21
21
|
export default {
|
|
22
22
|
client,
|
|
23
|
-
connection:
|
|
23
|
+
connection: resolveKnexConnectionFromEnvironment(process.env, {
|
|
24
|
+
client: dialectId,
|
|
24
25
|
defaultPort,
|
|
25
26
|
context: "knex migrations"
|
|
26
27
|
}),
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
normalizeDbRecordId,
|
|
5
|
+
parseMetadataJson,
|
|
6
|
+
resolveInsertedRecordId,
|
|
7
|
+
stringifyMetadataJson
|
|
8
|
+
} from "../src/shared/repositoryOptions.js";
|
|
4
9
|
|
|
5
10
|
test("parseMetadataJson parses object-like metadata payloads", () => {
|
|
6
11
|
assert.deepEqual(parseMetadataJson(""), {});
|
|
@@ -20,3 +25,18 @@ test("stringifyMetadataJson returns fallback for non-serializable values", () =>
|
|
|
20
25
|
circular.self = circular;
|
|
21
26
|
assert.equal(stringifyMetadataJson(circular), "{}");
|
|
22
27
|
});
|
|
28
|
+
|
|
29
|
+
test("normalizeDbRecordId preserves canonical DB ids and rejects unsafe JS numbers", () => {
|
|
30
|
+
const unsafeNumericId = Number(9007199254740993n);
|
|
31
|
+
assert.equal(normalizeDbRecordId("9007199254740993"), "9007199254740993");
|
|
32
|
+
assert.equal(normalizeDbRecordId(42), "42");
|
|
33
|
+
assert.equal(normalizeDbRecordId(42n), "42");
|
|
34
|
+
assert.equal(normalizeDbRecordId(unsafeNumericId), null);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("resolveInsertedRecordId normalizes insert ids without accepting unsafe JS numbers", () => {
|
|
38
|
+
const unsafeNumericId = Number(9007199254740993n);
|
|
39
|
+
assert.equal(resolveInsertedRecordId(["9007199254740993"]), "9007199254740993");
|
|
40
|
+
assert.equal(resolveInsertedRecordId([42]), "42");
|
|
41
|
+
assert.equal(resolveInsertedRecordId([unsafeNumericId]), null);
|
|
42
|
+
});
|
|
@@ -37,7 +37,7 @@ test("createRepositoryScope builds explicit scoped query helpers", () => {
|
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
assert.deepEqual(calls, [["table", "contacts"], ["where", "
|
|
40
|
+
assert.deepEqual(calls, [["table", "contacts"], ["where", "workspace_id", "12"]]);
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
test("createRepositoryScope supports scopedById with custom id column", () => {
|
|
@@ -49,11 +49,11 @@ test("createRepositoryScope supports scopedById with custom id column", () => {
|
|
|
49
49
|
scope.scopedById(33, {
|
|
50
50
|
visibilityContext: {
|
|
51
51
|
visibility: "user",
|
|
52
|
-
|
|
52
|
+
userId: 7
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
assert.deepEqual(calls, [["table", "contacts"], ["where", "
|
|
56
|
+
assert.deepEqual(calls, [["table", "contacts"], ["where", "user_id", "7"], ["where", "contact_id", 33]]);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
test("createRepositoryScope exposes applyToQuery and owner stamping", () => {
|
|
@@ -65,11 +65,11 @@ test("createRepositoryScope exposes applyToQuery and owner stamping", () => {
|
|
|
65
65
|
visibilityContext: {
|
|
66
66
|
visibility: "workspace_user",
|
|
67
67
|
scopeOwnerId: 4,
|
|
68
|
-
|
|
68
|
+
userId: 9
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
assert.deepEqual(calls, [["where", "
|
|
72
|
+
assert.deepEqual(calls, [["where", "workspace_id", "4"], ["where", "user_id", "9"]]);
|
|
73
73
|
|
|
74
74
|
assert.deepEqual(
|
|
75
75
|
scope.withOwners(
|
|
@@ -80,14 +80,14 @@ test("createRepositoryScope exposes applyToQuery and owner stamping", () => {
|
|
|
80
80
|
visibilityContext: {
|
|
81
81
|
visibility: "workspace_user",
|
|
82
82
|
scopeOwnerId: 4,
|
|
83
|
-
|
|
83
|
+
userId: 9
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
),
|
|
87
87
|
{
|
|
88
88
|
name: "Ada",
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
workspace_id: "4",
|
|
90
|
+
user_id: "9"
|
|
91
91
|
}
|
|
92
92
|
);
|
|
93
93
|
|
package/test/runtimeCore.test.js
CHANGED
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
BaseRepository,
|
|
6
6
|
buildPaginationMeta,
|
|
7
7
|
createTransactionManager,
|
|
8
|
-
|
|
8
|
+
createWithTransaction,
|
|
9
|
+
registerDatabaseRuntime,
|
|
9
10
|
} from "../src/shared/index.js";
|
|
10
11
|
|
|
11
12
|
function createKnexStub() {
|
|
@@ -74,6 +75,15 @@ test("base repository withTransaction delegates to transaction manager", async (
|
|
|
74
75
|
assert.deepEqual(result, { id: "trx-1" });
|
|
75
76
|
});
|
|
76
77
|
|
|
78
|
+
test("createWithTransaction creates a reusable withTransaction function", async () => {
|
|
79
|
+
const knex = Object.assign(() => {
|
|
80
|
+
throw new Error("query execution not expected");
|
|
81
|
+
}, createKnexStub());
|
|
82
|
+
const withTransaction = createWithTransaction(knex);
|
|
83
|
+
const result = await withTransaction(async (trx) => ({ id: trx.trxId }));
|
|
84
|
+
assert.deepEqual(result, { id: "trx-1" });
|
|
85
|
+
});
|
|
86
|
+
|
|
77
87
|
test("pagination helpers generate stable metadata", () => {
|
|
78
88
|
const meta = buildPaginationMeta({ total: 51, page: 2, pageSize: 25 });
|
|
79
89
|
assert.deepEqual(meta, {
|
package/test/visibility.test.js
CHANGED
|
@@ -28,14 +28,14 @@ test("applyVisibility appends scope filters to query builders", () => {
|
|
|
28
28
|
visibility: "workspace",
|
|
29
29
|
scopeOwnerId: 12
|
|
30
30
|
});
|
|
31
|
-
assert.deepEqual(workspaceQuery.calls, [["where", "
|
|
31
|
+
assert.deepEqual(workspaceQuery.calls, [["where", "workspace_id", "12"]]);
|
|
32
32
|
|
|
33
33
|
const userQuery = createQueryBuilderStub();
|
|
34
34
|
applyVisibility(userQuery, {
|
|
35
35
|
visibility: "user",
|
|
36
|
-
|
|
36
|
+
userId: 7
|
|
37
37
|
});
|
|
38
|
-
assert.deepEqual(userQuery.calls, [["where", "
|
|
38
|
+
assert.deepEqual(userQuery.calls, [["where", "user_id", "7"]]);
|
|
39
39
|
|
|
40
40
|
const deniedQuery = createQueryBuilderStub();
|
|
41
41
|
applyVisibility(deniedQuery, {
|
|
@@ -68,13 +68,13 @@ test("applyVisibilityOwners injects owner columns for write payloads", () => {
|
|
|
68
68
|
{
|
|
69
69
|
visibility: "workspace_user",
|
|
70
70
|
scopeOwnerId: 4,
|
|
71
|
-
|
|
71
|
+
userId: 9
|
|
72
72
|
}
|
|
73
73
|
),
|
|
74
74
|
{
|
|
75
75
|
name: "Alice",
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
workspace_id: "4",
|
|
77
|
+
user_id: "9"
|
|
78
78
|
}
|
|
79
79
|
);
|
|
80
80
|
|
|
@@ -88,6 +88,6 @@ test("applyVisibilityOwners injects owner columns for write payloads", () => {
|
|
|
88
88
|
visibility: "user"
|
|
89
89
|
}
|
|
90
90
|
),
|
|
91
|
-
/requires
|
|
91
|
+
/requires userId/
|
|
92
92
|
);
|
|
93
93
|
});
|