@protobase/server 0.68.1 → 0.68.3
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/comments/record-comments.js +1 -0
- package/dist/history/activity.js +10 -0
- package/dist/jobs/failed-jobs.js +3 -0
- package/dist/package.js +1 -1
- package/dist/user-tables/check-definition.js +1 -1
- package/dist/user-tables/definitions-resources.js +6 -5
- package/dist/user-tables/definitions-views.js +3 -2
- package/dist/user-tables/names.d.ts +1 -0
- package/dist/user-tables/names.js +8 -17
- package/dist/user-tables/schema-sql.js +2 -2
- package/package.json +6 -6
- package/dist/user-tables/reserved-words.js +0 -87
|
@@ -29,6 +29,7 @@ const commentsView = view("record_comments").names({
|
|
|
29
29
|
singular: "Comment",
|
|
30
30
|
plural: "Comments"
|
|
31
31
|
}).icon("message-square").title((r) => r.record_key).help("Every comment on the records whose resource takes comments, newest first. Delete one to take it down.").fields((r) => ({
|
|
32
|
+
resource: r.resource.label("Resource"),
|
|
32
33
|
record_key: r.record_key.label("Record"),
|
|
33
34
|
author_id: r.author_id.label("By"),
|
|
34
35
|
body: r.body.label("Comment"),
|
package/dist/history/activity.js
CHANGED
|
@@ -39,11 +39,21 @@ const activityView = view("record_history").names({
|
|
|
39
39
|
singular: "Change",
|
|
40
40
|
plural: "Activity"
|
|
41
41
|
}).icon("history").title((r) => r.record_key).help("Every change of the records whose resource keeps history, from the app or straight in the database, newest first.").fields((r) => ({
|
|
42
|
+
resource: r.resource.label("Resource"),
|
|
42
43
|
record_key: r.record_key.label("Record"),
|
|
44
|
+
operation: r.operation.label("Operation").valueLabels({
|
|
45
|
+
create: "Created",
|
|
46
|
+
update: "Updated",
|
|
47
|
+
delete: "Deleted",
|
|
48
|
+
undelete: "Restored"
|
|
49
|
+
}),
|
|
50
|
+
before: r.before.label("Before"),
|
|
51
|
+
after: r.after.label("After"),
|
|
43
52
|
actor_id: r.actor_id.label("By"),
|
|
44
53
|
actor_roles: r.actor_roles.label("Acting as"),
|
|
45
54
|
database_role: r.database_role.label("Database role"),
|
|
46
55
|
changed: r.changed.label("Changed fields"),
|
|
56
|
+
transaction: r.transaction.label("Transaction"),
|
|
47
57
|
at: r.at.label("When")
|
|
48
58
|
})).list((r) => ({
|
|
49
59
|
columns: [
|
package/dist/jobs/failed-jobs.js
CHANGED
|
@@ -30,8 +30,11 @@ const failedJobsView = view("failed_jobs").names({
|
|
|
30
30
|
}).icon("triangle-alert").title((r) => r.name).help("Jobs that used up their attempts, or failed for good. Retry one once its cause is fixed: it gets one more attempt.").fields((r) => ({
|
|
31
31
|
name: r.name.label("Job"),
|
|
32
32
|
error: r.error.label("Last error"),
|
|
33
|
+
attempts: r.attempts.label("Attempts"),
|
|
33
34
|
failed_at: r.failed_at.label("Failed"),
|
|
34
35
|
created_at: r.created_at.label("Enqueued"),
|
|
36
|
+
key: r.key.label("Key"),
|
|
37
|
+
payload: r.payload.label("Payload"),
|
|
35
38
|
output: r.output.label("Error details")
|
|
36
39
|
})).list((r) => ({
|
|
37
40
|
columns: [
|
package/dist/package.js
CHANGED
|
@@ -4,7 +4,7 @@ const reservedFields = ["id", "etag"];
|
|
|
4
4
|
const isFieldType = (type) => definitionFieldTypes.includes(type);
|
|
5
5
|
const fieldProblem = (field, columns, organization) => {
|
|
6
6
|
if (!isUserTableName(field.name)) return `the field name ${JSON.stringify(field.name)} is not lowercase letters, digits and "_", starting with a letter, at most 63 bytes`;
|
|
7
|
-
if (reservedFields.includes(field.name) || organization && field.name ===
|
|
7
|
+
if (reservedFields.includes(field.name) || organization && field.name === organizationColumn) return `the field name "${field.name}" is reserved`;
|
|
8
8
|
if (!isFieldType(field.type)) return `the field "${field.name}" has the type ${JSON.stringify(field.type)}, which is not one of ${definitionFieldTypes.join(", ")}`;
|
|
9
9
|
if (field.type === "decimal" && (field.precision === null || field.scale === null)) return `the decimal field "${field.name}" has no precision and scale`;
|
|
10
10
|
const column = columns.get(field.name);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { definitionFieldTypes, definitionsSchema, nameProblem, organizationColumn } from "./names.js";
|
|
1
|
+
import { definitionFieldTypes, definitionsSchema, maxOrganizationNameBytes, nameProblem, organizationColumn } from "./names.js";
|
|
2
2
|
import { organizationAdmins } from "./organization-access.js";
|
|
3
|
+
import { maxLabelLength } from "@protobase/schema/internal";
|
|
3
4
|
import { f, resource } from "@protobase/schema";
|
|
4
5
|
//#region src/user-tables/definitions-resources.ts
|
|
5
6
|
/** The names of the resources over the definitions: a table users add takes neither. */
|
|
@@ -36,14 +37,14 @@ const issue = (field, problem) => problem ? [{
|
|
|
36
37
|
const definitionsResources = ({ roles, taken, organizations = false }) => {
|
|
37
38
|
const organization = organizations ? { [organizationColumn]: f.text() } : {};
|
|
38
39
|
const nameIssue = (name) => {
|
|
39
|
-
if (organizations) return nameProblem(name, "table",
|
|
40
|
+
if (organizations) return nameProblem(name, "table", maxOrganizationNameBytes);
|
|
40
41
|
return nameProblem(name, "table") ?? (taken.includes(name) || ["user_tables", "user_table_fields"].includes(name) ? `"${name}" is the name of a resource of the app` : void 0);
|
|
41
42
|
};
|
|
42
43
|
const tables = resource(tablesResourceName).table(`${definitionsSchema}.tables`).fields({
|
|
43
44
|
id: f.bigint().readOnly().dbDefault().filterable().sortable(),
|
|
44
45
|
name: f.text().access(createOnly).filterable().sortable(),
|
|
45
|
-
singular_label: f.text().max(
|
|
46
|
-
plural_label: f.text().max(
|
|
46
|
+
singular_label: f.text().max(maxLabelLength),
|
|
47
|
+
plural_label: f.text().max(maxLabelLength),
|
|
47
48
|
title_field: f.text().optional(),
|
|
48
49
|
version: f.bigint().readOnly().dbDefault(),
|
|
49
50
|
table_name: f.text().readOnly().dbDefault(),
|
|
@@ -52,7 +53,7 @@ const definitionsResources = ({ roles, taken, organizations = false }) => {
|
|
|
52
53
|
const fields = resource(fieldsResourceName).table(`${definitionsSchema}.fields`).fields({
|
|
53
54
|
table_id: f.relation(tablesResourceName).access(createOnly).filterable(),
|
|
54
55
|
name: f.text().access(createOnly),
|
|
55
|
-
label: f.text().max(
|
|
56
|
+
label: f.text().max(maxLabelLength),
|
|
56
57
|
type: f.enum(definitionFieldTypes).access(createOnly),
|
|
57
58
|
required: f.boolean().default(false).access(createOnly),
|
|
58
59
|
position: f.integer().sortable(),
|
|
@@ -14,7 +14,8 @@ const fieldTypeLabels = {
|
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
16
|
* The tables list of the admin app, for whoever may make tables: a table's page has its fields, a link to add one and a
|
|
17
|
-
* link to the table's own list, which shows once the app serves it. The title field is set once its field exists.
|
|
17
|
+
* link to the table's own list, which shows once the app serves it. The title field is set once its field exists. Its
|
|
18
|
+
* Import makes a new table from a file, with its fields and records.
|
|
18
19
|
*/
|
|
19
20
|
const tablesView = view(tablesResourceName).names({
|
|
20
21
|
singular: "Table",
|
|
@@ -22,7 +23,7 @@ const tablesView = view(tablesResourceName).names({
|
|
|
22
23
|
}).title((r) => r.plural_label).help("The tables people add to the app. Open one to give it fields or to go to its records.").nav({
|
|
23
24
|
creatorsOnly: true,
|
|
24
25
|
order: 0
|
|
25
|
-
}).fields((r) => ({
|
|
26
|
+
}).import("table").fields((r) => ({
|
|
26
27
|
name: r.name.label("Name").format("code").help("Its address and the name of its database table: lowercase letters, digits and _, starting with a letter. It cannot change later."),
|
|
27
28
|
plural_label: r.plural_label.label("Plural label").help("Its name in the sidebar and above its list, such as Notes."),
|
|
28
29
|
singular_label: r.singular_label.label("Singular label").help("What one record is called, such as Note."),
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isUserTableName, maxNameBytes, maxNameBytes as maxNameBytes$1, maxOrganizationNameBytes, organizationColumn, userTableName } from "@protobase/schema/internal";
|
|
2
|
+
import { userTableNameIssue } from "@protobase/schema";
|
|
2
3
|
//#region src/user-tables/names.ts
|
|
3
4
|
/** The schema the definitions of the tables users add live in: `tables`, and `fields` of each. */
|
|
4
5
|
const definitionsSchema = "table_definitions";
|
|
5
6
|
/** The schema the tables users add live in, apart from their definitions, so no name of one reaches a definitions table. */
|
|
6
7
|
const userTablesSchema = "user_tables";
|
|
7
|
-
/** What a name of a user table or field must be: it reaches SQL as an identifier, always quoted too. */
|
|
8
|
-
const userTableName = /^[a-z][a-z0-9_]*$/;
|
|
9
8
|
/** The field types a definition may give, each with the Postgres types its column may have. */
|
|
10
9
|
const definitionColumnTypes = {
|
|
11
10
|
text: ["text"],
|
|
@@ -18,20 +17,12 @@ const definitionColumnTypes = {
|
|
|
18
17
|
json: ["jsonb"]
|
|
19
18
|
};
|
|
20
19
|
const definitionFieldTypes = Object.keys(definitionColumnTypes);
|
|
21
|
-
const isUserTableName = (name) => userTableName.test(name) && new TextEncoder().encode(name).length <= 63;
|
|
22
|
-
/** The column that holds the organization of each record of a table users add with organizations, and of each definition. */
|
|
23
|
-
const organizationColumn = "organization_id";
|
|
24
|
-
/** The names a field never takes: the key's, the API's for the record version, and the organization's. */
|
|
25
|
-
const reservedFieldNames = [
|
|
26
|
-
"id",
|
|
27
|
-
"etag",
|
|
28
|
-
organizationColumn
|
|
29
|
-
];
|
|
30
20
|
/** What is wrong with a name a table or field users add is given, or `undefined` when it may have it; `maxBytes` defaults to 63. */
|
|
31
|
-
const nameProblem = (name, kind, maxBytes =
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
if (
|
|
21
|
+
const nameProblem = (name, kind, maxBytes = maxNameBytes) => {
|
|
22
|
+
const issue = userTableNameIssue(name, kind, maxBytes);
|
|
23
|
+
if (issue === "form") return `Use lowercase letters, digits and "_", starting with a letter, at most ${maxBytes} bytes`;
|
|
24
|
+
if (issue === "reserved_word") return `"${name}" is a word SQL reserves`;
|
|
25
|
+
if (issue === "reserved_field") return `"${name}" is the name of a field every table has`;
|
|
35
26
|
};
|
|
36
27
|
//#endregion
|
|
37
|
-
export { definitionColumnTypes, definitionFieldTypes, definitionsSchema, isUserTableName, nameProblem, organizationColumn,
|
|
28
|
+
export { definitionColumnTypes, definitionFieldTypes, definitionsSchema, isUserTableName, maxNameBytes$1 as maxNameBytes, maxOrganizationNameBytes, nameProblem, organizationColumn, userTableName, userTablesSchema };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { definitionFieldTypes, definitionsSchema, userTableName, userTablesSchema } from "./names.js";
|
|
1
|
+
import { definitionFieldTypes, definitionsSchema, maxNameBytes, userTableName, userTablesSchema } from "./names.js";
|
|
2
2
|
//#region src/user-tables/schema-sql.ts
|
|
3
|
-
const nameCheck = (column) => `check (${column} ~ '${userTableName.source}' and octet_length(${column}) <=
|
|
3
|
+
const nameCheck = (column) => `check (${column} ~ '${userTableName.source}' and octet_length(${column}) <= ${maxNameBytes})`;
|
|
4
4
|
/**
|
|
5
5
|
* The table of definitions: one row per table users add, its labels and a version, 1 when added, that each change of it
|
|
6
6
|
* raises. With organizations a definition belongs to the one it was made in (`organization_id`), and its table is named
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.68.
|
|
3
|
+
"version": "0.68.3",
|
|
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": {
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"@better-auth/sso": "^1.7.12",
|
|
45
45
|
"@livekit/protocol": "1.53.1",
|
|
46
46
|
"@modelcontextprotocol/server": "^2.3.1",
|
|
47
|
-
"@protobase/layout": "0.68.
|
|
48
|
-
"@protobase/query": "0.68.
|
|
49
|
-
"@protobase/schema": "0.68.
|
|
50
|
-
"@protobase/workflow-plugins": "0.68.
|
|
51
|
-
"@protobase/workflows": "0.68.
|
|
47
|
+
"@protobase/layout": "0.68.3",
|
|
48
|
+
"@protobase/query": "0.68.3",
|
|
49
|
+
"@protobase/schema": "0.68.3",
|
|
50
|
+
"@protobase/workflow-plugins": "0.68.3",
|
|
51
|
+
"@protobase/workflows": "0.68.3",
|
|
52
52
|
"@react-email/render": "^2.1.0",
|
|
53
53
|
"@react-pdf/primitives": "^4.4.0",
|
|
54
54
|
"@react-pdf/renderer": "^4.9.0",
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
//#region src/user-tables/reserved-words.ts
|
|
2
|
-
/**
|
|
3
|
-
* The key words Postgres 18 reserves, `select word from pg_get_keywords() where catcode = 'R'`: a table or field users
|
|
4
|
-
* add never takes one, so its name works unquoted in any SQL a developer writes against it.
|
|
5
|
-
*/
|
|
6
|
-
const reservedWords = /* @__PURE__ */ new Set([
|
|
7
|
-
"all",
|
|
8
|
-
"analyse",
|
|
9
|
-
"analyze",
|
|
10
|
-
"and",
|
|
11
|
-
"any",
|
|
12
|
-
"array",
|
|
13
|
-
"as",
|
|
14
|
-
"asc",
|
|
15
|
-
"asymmetric",
|
|
16
|
-
"both",
|
|
17
|
-
"case",
|
|
18
|
-
"cast",
|
|
19
|
-
"check",
|
|
20
|
-
"collate",
|
|
21
|
-
"column",
|
|
22
|
-
"constraint",
|
|
23
|
-
"create",
|
|
24
|
-
"current_catalog",
|
|
25
|
-
"current_date",
|
|
26
|
-
"current_role",
|
|
27
|
-
"current_time",
|
|
28
|
-
"current_timestamp",
|
|
29
|
-
"current_user",
|
|
30
|
-
"default",
|
|
31
|
-
"deferrable",
|
|
32
|
-
"desc",
|
|
33
|
-
"distinct",
|
|
34
|
-
"do",
|
|
35
|
-
"else",
|
|
36
|
-
"end",
|
|
37
|
-
"except",
|
|
38
|
-
"false",
|
|
39
|
-
"fetch",
|
|
40
|
-
"for",
|
|
41
|
-
"foreign",
|
|
42
|
-
"from",
|
|
43
|
-
"grant",
|
|
44
|
-
"group",
|
|
45
|
-
"having",
|
|
46
|
-
"in",
|
|
47
|
-
"initially",
|
|
48
|
-
"intersect",
|
|
49
|
-
"into",
|
|
50
|
-
"lateral",
|
|
51
|
-
"leading",
|
|
52
|
-
"limit",
|
|
53
|
-
"localtime",
|
|
54
|
-
"localtimestamp",
|
|
55
|
-
"not",
|
|
56
|
-
"null",
|
|
57
|
-
"offset",
|
|
58
|
-
"on",
|
|
59
|
-
"only",
|
|
60
|
-
"or",
|
|
61
|
-
"order",
|
|
62
|
-
"placing",
|
|
63
|
-
"primary",
|
|
64
|
-
"references",
|
|
65
|
-
"returning",
|
|
66
|
-
"select",
|
|
67
|
-
"session_user",
|
|
68
|
-
"some",
|
|
69
|
-
"symmetric",
|
|
70
|
-
"system_user",
|
|
71
|
-
"table",
|
|
72
|
-
"then",
|
|
73
|
-
"to",
|
|
74
|
-
"trailing",
|
|
75
|
-
"true",
|
|
76
|
-
"union",
|
|
77
|
-
"unique",
|
|
78
|
-
"user",
|
|
79
|
-
"using",
|
|
80
|
-
"variadic",
|
|
81
|
-
"when",
|
|
82
|
-
"where",
|
|
83
|
-
"window",
|
|
84
|
-
"with"
|
|
85
|
-
]);
|
|
86
|
-
//#endregion
|
|
87
|
-
export { reservedWords };
|