@protobase/server 0.4.1 → 0.6.0
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/admin-options.d.ts +35 -0
- package/dist/aggregate-service.js +1 -1
- package/dist/assistant/assistant-settings.d.ts +4 -7
- package/dist/assistant/built-in-assistant.js +21 -5
- package/dist/assistant/chat-completions.d.ts +7 -1
- package/dist/assistant/conversations.d.ts +1 -1
- package/dist/assistant/records.d.ts +23 -0
- package/dist/assistant/records.js +53 -0
- package/dist/assistant/tools.d.ts +3 -0
- package/dist/assistant/turn.d.ts +4 -2
- package/dist/assistant/turn.js +3 -1
- package/dist/assistant/widget.d.ts +15 -0
- package/dist/assistant/widget.js +32 -0
- package/dist/batch/reorder.js +3 -3
- package/dist/better-auth/auth-schema.d.ts +14 -0
- package/dist/better-auth/auth-schema.js +35 -0
- package/dist/better-auth/create-auth.d.ts +41 -6
- package/dist/better-auth/create-auth.js +29 -8
- package/dist/better-auth/platform-sign-in.d.ts +30 -0
- package/dist/better-auth/platform-sign-in.js +88 -0
- package/dist/better-auth/policy-plugin.js +10 -5
- package/dist/better-auth/policy-store.js +5 -0
- package/dist/better-auth/sign-in-enforcement.js +11 -3
- package/dist/better-auth/sign-in-policy.d.ts +8 -1
- package/dist/better-auth/sign-in-policy.js +21 -7
- package/dist/better-auth/staff-plugin.js +1 -1
- package/dist/better-auth/status-route.js +10 -4
- package/dist/better-auth/users.d.ts +8 -1
- package/dist/better-auth/users.js +16 -1
- package/dist/create-admin.d.ts +2 -1
- package/dist/create-admin.js +15 -8
- package/dist/deps.d.ts +6 -0
- package/dist/index.d.ts +15 -9
- package/dist/index.js +5 -1
- package/dist/list-service.js +4 -4
- package/dist/project-config.d.ts +28 -0
- package/dist/project-config.js +111 -0
- package/dist/read-service.js +3 -3
- package/dist/registry.d.ts +2 -0
- package/dist/routes/record.js +2 -2
- package/dist/row-permissions.js +2 -2
- package/dist/scan-guard.d.ts +4 -0
- package/dist/types.d.ts +1 -32
- package/dist/write-pipeline.js +2 -2
- package/dist/write-shared.js +2 -2
- package/dist/write-update.js +2 -2
- package/package.json +4 -4
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { isPageDefinition } from "@protobase/layout";
|
|
2
|
+
//#region src/project-config.ts
|
|
3
|
+
/** Typed identity, for `export default defineConfig({ extends: [...], config, auth, ... })` in `protobase.config.ts`. */
|
|
4
|
+
const defineConfig = (config) => config;
|
|
5
|
+
const lastSet = (objects) => Object.fromEntries(objects.flatMap((object) => Object.entries(object)).filter(([, value]) => value !== void 0));
|
|
6
|
+
const flatten = (config, label) => [...(config.extends ?? []).flatMap((parent, index) => flatten(parent, parent.name ?? `${label} extends[${index}]`)), {
|
|
7
|
+
config,
|
|
8
|
+
label
|
|
9
|
+
}];
|
|
10
|
+
const checkUnique = (kind, entries) => {
|
|
11
|
+
const seen = /* @__PURE__ */ new Map();
|
|
12
|
+
for (const { name, value, label } of entries) {
|
|
13
|
+
const first = seen.get(name);
|
|
14
|
+
if (first && first.value !== value) throw new Error(`${kind} "${name}" is defined twice, ${first.label === label ? `in ${label}` : `by ${first.label} and by ${label}`}; rename one of them`);
|
|
15
|
+
if (!first) seen.set(name, {
|
|
16
|
+
value,
|
|
17
|
+
label
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const mergedModule = (named) => {
|
|
22
|
+
const withModule = named.filter(({ config }) => config.config);
|
|
23
|
+
if (withModule.length === 0) return void 0;
|
|
24
|
+
const exports = withModule.flatMap(({ config, label }) => Object.entries(config.config).map(([key, value]) => ({
|
|
25
|
+
key,
|
|
26
|
+
value,
|
|
27
|
+
label
|
|
28
|
+
})));
|
|
29
|
+
const values = exports.map((entry) => ({
|
|
30
|
+
...entry,
|
|
31
|
+
value: entry.value
|
|
32
|
+
}));
|
|
33
|
+
checkUnique("The resource", values.filter(({ value }) => typeof value?.recordSchema === "function" && typeof value.toModel === "function").map(({ value, label }) => ({
|
|
34
|
+
name: String(value.toModel().name),
|
|
35
|
+
value,
|
|
36
|
+
label
|
|
37
|
+
})));
|
|
38
|
+
checkUnique("The page", exports.filter(({ value }) => isPageDefinition(value)).map(({ value, label }) => ({
|
|
39
|
+
name: value.name,
|
|
40
|
+
value,
|
|
41
|
+
label
|
|
42
|
+
})));
|
|
43
|
+
const menus = values.filter(({ value }) => typeof value?.toUserMenuModel === "function");
|
|
44
|
+
if (new Set(menus.map(({ value }) => value)).size > 1) throw new Error(`Only one config may export a user menu; ${menus.map(({ label }) => label).join(" and ")} each do`);
|
|
45
|
+
const module = {};
|
|
46
|
+
const own = withModule.at(-1).label;
|
|
47
|
+
for (const { key, value, label } of exports) {
|
|
48
|
+
if (Object.values(module).includes(value)) continue;
|
|
49
|
+
module[label === own ? key : `${label}:${key}`] = value;
|
|
50
|
+
}
|
|
51
|
+
return module;
|
|
52
|
+
};
|
|
53
|
+
const mergedAssistant = (named) => {
|
|
54
|
+
const options = named.map(({ config, label }) => ({
|
|
55
|
+
assistant: config.options?.assistant,
|
|
56
|
+
label
|
|
57
|
+
})).filter(({ assistant }) => assistant !== void 0);
|
|
58
|
+
if (options.length === 0) return void 0;
|
|
59
|
+
if (options.at(-1).assistant === false) return false;
|
|
60
|
+
const after = options.slice(options.findLastIndex(({ assistant }) => assistant === false) + 1);
|
|
61
|
+
const tools = after.flatMap(({ assistant, label }) => (assistant.tools ?? []).map((tool) => ({
|
|
62
|
+
tool,
|
|
63
|
+
label
|
|
64
|
+
})));
|
|
65
|
+
checkUnique("The assistant tool", tools.map(({ tool, label }) => ({
|
|
66
|
+
name: tool.name,
|
|
67
|
+
value: tool,
|
|
68
|
+
label
|
|
69
|
+
})));
|
|
70
|
+
return {
|
|
71
|
+
...lastSet(after.map(({ assistant }) => assistant)),
|
|
72
|
+
...tools.length > 0 && { tools: tools.map(({ tool }) => tool) }
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
const mergedOptions = (named) => {
|
|
76
|
+
const all = named.flatMap(({ config }) => config.options ? [config.options] : []);
|
|
77
|
+
if (all.length === 0) return void 0;
|
|
78
|
+
const writeHooks = all.flatMap((options) => options.writeHooks ?? []);
|
|
79
|
+
const assistant = mergedAssistant(named);
|
|
80
|
+
return {
|
|
81
|
+
...lastSet(all),
|
|
82
|
+
...writeHooks.length > 0 && { writeHooks },
|
|
83
|
+
...assistant !== void 0 && { assistant }
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* One config from several, each after the ones it `extends`, the last being the app's own. Named things are all kept:
|
|
88
|
+
* resources, views and pages of the config modules, assistant tools; one name defined by two configs is an error naming
|
|
89
|
+
* both. Write hooks run in the same order. Every other value is the last config's that sets it, so the app's own wins.
|
|
90
|
+
*/
|
|
91
|
+
const mergeConfig = (...configs) => {
|
|
92
|
+
const named = configs.flatMap((config, index) => flatten(config, config.name ?? `config ${index + 1}`));
|
|
93
|
+
const { db, authenticate, auth } = lastSet(named.map(({ config }) => ({
|
|
94
|
+
db: config.db,
|
|
95
|
+
authenticate: config.authenticate,
|
|
96
|
+
auth: config.auth
|
|
97
|
+
})));
|
|
98
|
+
const module = mergedModule(named);
|
|
99
|
+
const options = mergedOptions(named);
|
|
100
|
+
const name = configs.at(-1)?.name;
|
|
101
|
+
return {
|
|
102
|
+
...name && { name },
|
|
103
|
+
...module && { config: module },
|
|
104
|
+
...db && { db },
|
|
105
|
+
...authenticate && { authenticate },
|
|
106
|
+
...auth && { auth },
|
|
107
|
+
...options && { options }
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
//#endregion
|
|
111
|
+
export { defineConfig, mergeConfig };
|
package/dist/read-service.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { notFound } from "./problem.js";
|
|
2
|
-
import { tenantScope } from "./tenant.js";
|
|
3
|
-
import { recordDecision, requestAccess, requireOperation, targetFilter, visibleFields, writableFields } from "./request-access.js";
|
|
4
|
-
import { readTransaction } from "./transactions.js";
|
|
5
2
|
import { pickFields } from "./rows.js";
|
|
6
3
|
import { fetchRecord } from "./records.js";
|
|
4
|
+
import { tenantScope } from "./tenant.js";
|
|
5
|
+
import { recordDecision, requestAccess, requireOperation, targetFilter, visibleFields, writableFields } from "./request-access.js";
|
|
7
6
|
import { rowPermissions } from "./row-permissions.js";
|
|
7
|
+
import { readTransaction } from "./transactions.js";
|
|
8
8
|
//#region src/read-service.ts
|
|
9
9
|
/** `edit` for fields the caller may change on this record, `read` for the rest (sensitive ones included); hidden fields are not listed at all. */
|
|
10
10
|
const fieldModes = (access, canUpdate) => Object.fromEntries(Object.keys(access.readable.fields).map((name) => [name, canUpdate && writableFields(access, "update").includes(name) ? "edit" : "read"]));
|
package/dist/routes/record.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { notFound } from "../problem.js";
|
|
2
|
+
import { keyFromPath } from "../key.js";
|
|
2
3
|
import { pickFields } from "../rows.js";
|
|
4
|
+
import { readRecord } from "../read-service.js";
|
|
3
5
|
import { runWrite } from "../write-pipeline.js";
|
|
4
|
-
import { keyFromPath } from "../key.js";
|
|
5
6
|
import { deletedRows, selectedFields } from "../params.js";
|
|
6
7
|
import { returnPreference } from "./preferences.js";
|
|
7
8
|
import { resolveTarget } from "./target.js";
|
|
8
|
-
import { readRecord } from "../read-service.js";
|
|
9
9
|
import { revealField } from "../reveal-service.js";
|
|
10
10
|
import { Hono } from "hono";
|
|
11
11
|
//#region src/routes/record.ts
|
package/dist/row-permissions.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { tenantScope } from "./tenant.js";
|
|
2
|
-
import { recordDecision } from "./request-access.js";
|
|
3
1
|
import { aliasedColumn, columnId, scopeConditions, tableId, whereClause } from "./sql-parts.js";
|
|
4
2
|
import { fetchRecord } from "./records.js";
|
|
3
|
+
import { tenantScope } from "./tenant.js";
|
|
4
|
+
import { recordDecision } from "./request-access.js";
|
|
5
5
|
import { compileFilter } from "@protobase/query";
|
|
6
6
|
import { sql } from "kysely";
|
|
7
7
|
//#region src/row-permissions.ts
|
package/dist/types.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { AssistantOptions } from "./assistant/assistant-settings.js";
|
|
2
|
-
import { RuntimeOptions } from "./runtime/runtime-settings.js";
|
|
3
1
|
import { Hono } from "hono";
|
|
4
2
|
import { PageModel } from "@protobase/layout";
|
|
5
|
-
import {
|
|
3
|
+
import { ResourceModel, ViewModel } from "@protobase/schema";
|
|
6
4
|
import { Db } from "@protobase/query";
|
|
7
5
|
//#region src/types.d.ts
|
|
8
6
|
export type TenantValue = string | number;
|
|
@@ -64,35 +62,6 @@ export type AuditQueue = {
|
|
|
64
62
|
publish(event: AuditEvent): Promise<void>;
|
|
65
63
|
};
|
|
66
64
|
export type ScanGuardMode = 'reject' | 'warn' | 'off';
|
|
67
|
-
export type AdminOptions = {
|
|
68
|
-
/** Default `/api/v1`. */
|
|
69
|
-
basePath?: string;
|
|
70
|
-
/** Applied to every request's transaction with SET LOCAL. Default 15000. */
|
|
71
|
-
statementTimeoutMs?: number;
|
|
72
|
-
/** List shapes that would sequentially scan a table of more than `seqScanRows` rows (default 10000). Default mode `reject`. */
|
|
73
|
-
scanGuard?: {
|
|
74
|
-
mode?: ScanGuardMode;
|
|
75
|
-
seqScanRows?: number;
|
|
76
|
-
};
|
|
77
|
-
/** Runs inside the write transaction after each create, update and delete. */
|
|
78
|
-
writeHooks?: PipelineHook[];
|
|
79
|
-
/** The project's roles (from `defineRoles`): an operation without an access rule needs the matching capability. */
|
|
80
|
-
roles?: ResolveOptions['roles'];
|
|
81
|
-
/** What an operation without a rule does; overrides the capability check. Without `roles` the default is allow. */
|
|
82
|
-
defaultAccess?: ResolveOptions['defaultAccess'];
|
|
83
|
-
/** Whether list items carry their `permissions` (update, delete): true by default, or `{ except: [...] }` for resources that opt out. */
|
|
84
|
-
rowPermissions?: boolean | {
|
|
85
|
-
except: string[];
|
|
86
|
-
};
|
|
87
|
-
/** Receives an event for every reveal of a sensitive field. Default: printed to the console, nothing kept. */
|
|
88
|
-
audit?: AuditQueue;
|
|
89
|
-
/** Called with every error the API turns into a 500, so the host can report it. */
|
|
90
|
-
onUnhandledError?: (error: unknown) => void;
|
|
91
|
-
/** The assistant in the admin app's dock: a backend elsewhere or the built-in one (see `AssistantOptions`); `false` turns it off. */
|
|
92
|
-
assistant?: AssistantOptions | false;
|
|
93
|
-
/** The runtime updates endpoint behind the top bar's update button (see `RuntimeOptions`); `false` turns it off. */
|
|
94
|
-
runtime?: RuntimeOptions | false;
|
|
95
|
-
};
|
|
96
65
|
export type ViewSource = {
|
|
97
66
|
toModel(): ViewModel;
|
|
98
67
|
};
|
package/dist/write-pipeline.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { HttpProblem, badRequest } from "./problem.js";
|
|
2
|
+
import { deleteRecord, fetchRecord, insertRecord, restoreRecord } from "./records.js";
|
|
2
3
|
import { tenantScope } from "./tenant.js";
|
|
3
4
|
import { requestAccess, requireOperation, targetFilter, writableFields } from "./request-access.js";
|
|
4
5
|
import { writeTransaction } from "./transactions.js";
|
|
5
|
-
import { deleteRecord, fetchRecord, insertRecord, restoreRecord } from "./records.js";
|
|
6
|
-
import { assertWritten, checkPrecondition, decide, denied, firstKeyField, missing, notifier, shown } from "./write-shared.js";
|
|
7
6
|
import { validateBody } from "./validate-body.js";
|
|
7
|
+
import { assertWritten, checkPrecondition, decide, denied, firstKeyField, missing, notifier, shown } from "./write-shared.js";
|
|
8
8
|
import { applyUpdate, planUpdate } from "./write-update.js";
|
|
9
9
|
//#region src/write-pipeline.ts
|
|
10
10
|
const withOwner = (access, values) => {
|
package/dist/write-shared.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { forbidden, notFound, preconditionFailed, preconditionRequired } from "./problem.js";
|
|
2
|
-
import { tenantScope } from "./tenant.js";
|
|
3
|
-
import { andFilters, recordDecision } from "./request-access.js";
|
|
4
2
|
import { ifMatchSatisfied } from "./etag.js";
|
|
5
3
|
import { pickFields } from "./rows.js";
|
|
6
4
|
import { fetchRecord } from "./records.js";
|
|
5
|
+
import { tenantScope } from "./tenant.js";
|
|
6
|
+
import { andFilters, recordDecision } from "./request-access.js";
|
|
7
7
|
//#region src/write-shared.ts
|
|
8
8
|
const missing = (entry) => notFound(`No ${entry.name} with this key`);
|
|
9
9
|
const checkPrecondition = (current, ifMatch, required) => {
|
package/dist/write-update.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { fetchRecord, updateRecord } from "./records.js";
|
|
1
2
|
import { tenantScope } from "./tenant.js";
|
|
2
3
|
import { targetFilter } from "./request-access.js";
|
|
3
|
-
import { fetchRecord, updateRecord } from "./records.js";
|
|
4
|
-
import { assertWritten, checkPrecondition, decide, denied, firstKeyField, missing, notifier } from "./write-shared.js";
|
|
5
4
|
import { validateBody } from "./validate-body.js";
|
|
5
|
+
import { assertWritten, checkPrecondition, decide, denied, firstKeyField, missing, notifier } from "./write-shared.js";
|
|
6
6
|
//#region src/write-update.ts
|
|
7
7
|
/**
|
|
8
8
|
* Everything an update needs before it writes: the row (locked, inside the caller's tenant and row filters), the ETag
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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": {
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@better-auth/passkey": "^1.7.7",
|
|
33
|
-
"@protobase/layout": "0.
|
|
34
|
-
"@protobase/query": "0.
|
|
35
|
-
"@protobase/schema": "0.
|
|
33
|
+
"@protobase/layout": "0.6.0",
|
|
34
|
+
"@protobase/query": "0.6.0",
|
|
35
|
+
"@protobase/schema": "0.6.0",
|
|
36
36
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
37
37
|
"@simplewebauthn/server": "^13.3.1",
|
|
38
38
|
"better-auth": "^1.7.7",
|