@protobase/server 0.0.0-stage → 0.1.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/LICENSE +202 -0
- package/dist/aggregate-service.js +135 -0
- package/dist/audit/console-queue.d.ts +5 -0
- package/dist/audit/console-queue.js +5 -0
- package/dist/auth/jwt.d.ts +21 -0
- package/dist/auth/jwt.js +49 -0
- package/dist/batch/refs.js +31 -0
- package/dist/batch/reorder.js +79 -0
- package/dist/batch/run-batch.js +105 -0
- package/dist/batch/schema.js +51 -0
- package/dist/better-auth/authenticator.d.ts +22 -0
- package/dist/better-auth/authenticator.js +21 -0
- package/dist/better-auth/create-auth.d.ts +1244 -0
- package/dist/better-auth/create-auth.js +93 -0
- package/dist/better-auth/jwks-cache.d.ts +9 -0
- package/dist/better-auth/jwks-cache.js +31 -0
- package/dist/better-auth/password-reset.js +32 -0
- package/dist/better-auth/status-route.js +17 -0
- package/dist/better-auth/tokens.d.ts +18 -0
- package/dist/better-auth/tokens.js +37 -0
- package/dist/better-auth/users.d.ts +50 -0
- package/dist/better-auth/users.js +111 -0
- package/dist/check-shell.js +17 -0
- package/dist/check-views.js +45 -0
- package/dist/create-admin.d.ts +23 -0
- package/dist/create-admin.js +85 -0
- package/dist/error-handler.js +44 -0
- package/dist/etag.js +22 -0
- package/dist/filter-input.js +28 -0
- package/dist/filter-shape.js +25 -0
- package/dist/hash.js +10 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +10 -0
- package/dist/key.js +22 -0
- package/dist/list-service.js +96 -0
- package/dist/mail/mail-settings.js +26 -0
- package/dist/mail/smtp-mailer.d.ts +11 -0
- package/dist/mail/smtp-mailer.js +17 -0
- package/dist/meta.js +63 -0
- package/dist/openapi/components.js +331 -0
- package/dist/openapi/document.js +74 -0
- package/dist/openapi/naming.js +4 -0
- package/dist/openapi/resource-paths.js +417 -0
- package/dist/openapi/schemas.js +93 -0
- package/dist/params.js +48 -0
- package/dist/problem.d.ts +17 -0
- package/dist/problem.js +43 -0
- package/dist/read-service.js +65 -0
- package/dist/records.js +66 -0
- package/dist/registry.js +44 -0
- package/dist/request-access.js +97 -0
- package/dist/resource-source.d.ts +35 -0
- package/dist/resource-source.js +22 -0
- package/dist/reveal-service.js +43 -0
- package/dist/routes/batch.js +21 -0
- package/dist/routes/collection.js +63 -0
- package/dist/routes/docs.js +20 -0
- package/dist/routes/meta.js +22 -0
- package/dist/routes/preferences.js +12 -0
- package/dist/routes/record.js +97 -0
- package/dist/routes/target.js +18 -0
- package/dist/row-permissions.js +69 -0
- package/dist/rows.js +12 -0
- package/dist/scan-guard.js +72 -0
- package/dist/sql-parts.js +32 -0
- package/dist/tenant.js +10 -0
- package/dist/transactions.js +14 -0
- package/dist/types.d.ts +97 -0
- package/dist/validate-body.js +73 -0
- package/dist/view-access.js +54 -0
- package/dist/view-related.js +59 -0
- package/dist/write-pipeline.js +94 -0
- package/dist/write-shared.js +52 -0
- package/dist/write-update.js +45 -0
- package/package.json +39 -3
- package/README.md +0 -3
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { forbidden, notFound, preconditionFailed, preconditionRequired } from "./problem.js";
|
|
2
|
+
import { tenantScope } from "./tenant.js";
|
|
3
|
+
import { andFilters, recordDecision } from "./request-access.js";
|
|
4
|
+
import { ifMatchSatisfied } from "./etag.js";
|
|
5
|
+
import { pickFields } from "./rows.js";
|
|
6
|
+
import { fetchRecord } from "./records.js";
|
|
7
|
+
//#region src/write-shared.ts
|
|
8
|
+
const missing = (entry) => notFound(`No ${entry.name} with this key`);
|
|
9
|
+
const checkPrecondition = (current, ifMatch, required) => {
|
|
10
|
+
if (ifMatch === null) {
|
|
11
|
+
if (required) throw preconditionRequired("Send the ETag of the record you read in an If-Match header (or * for any version)");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!ifMatchSatisfied(ifMatch, current.etag)) throw preconditionFailed("The record changed since it was read; fetch it again");
|
|
15
|
+
};
|
|
16
|
+
const denied = (entry, operation) => forbidden("access-denied", `You may not ${operation} this ${entry.name} record`);
|
|
17
|
+
const decide = async (access, operation, record, input) => {
|
|
18
|
+
if (!access.resolved.recordChecks.includes(operation)) return void 0;
|
|
19
|
+
const decision = await recordDecision(access, operation, record, input);
|
|
20
|
+
if (decision === false) throw denied(access.entry, operation);
|
|
21
|
+
return decision === true ? void 0 : decision;
|
|
22
|
+
};
|
|
23
|
+
const keyOf = (access, record) => access.full.primaryKey.map((name) => record[name]);
|
|
24
|
+
const firstKeyField = (access) => access.full.fields[access.full.primaryKey[0]];
|
|
25
|
+
const assertWritten = async (trx, access, written, operation, ...filters) => {
|
|
26
|
+
const filter = andFilters(filters);
|
|
27
|
+
if (!filter) return;
|
|
28
|
+
if (!await fetchRecord(trx, access.entry, keyOf(access, written.record), tenantScope(access.entry, access.session)?.tenantValue, {
|
|
29
|
+
filter,
|
|
30
|
+
fields: [firstKeyField(access)]
|
|
31
|
+
})) throw denied(access.entry, `leave in that state (${operation})`);
|
|
32
|
+
};
|
|
33
|
+
/** The caller sees the readable fields only; hooks get the whole record. */
|
|
34
|
+
const shown = (access, written) => ({
|
|
35
|
+
record: pickFields(written.record, Object.keys(access.model.fields)),
|
|
36
|
+
etag: written.etag
|
|
37
|
+
});
|
|
38
|
+
/** Runs the pipeline hooks for one write, inside the transaction. */
|
|
39
|
+
const notifier = (deps, access) => async (trx, kind, before, after) => {
|
|
40
|
+
const { session } = access;
|
|
41
|
+
const event = {
|
|
42
|
+
operation: kind,
|
|
43
|
+
resource: access.full,
|
|
44
|
+
user: session.user,
|
|
45
|
+
...session.tenant !== void 0 && { tenant: session.tenant },
|
|
46
|
+
...before && { before },
|
|
47
|
+
...after && { after }
|
|
48
|
+
};
|
|
49
|
+
for (const hook of deps.hooks) await hook(event, trx);
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { assertWritten, checkPrecondition, decide, denied, firstKeyField, keyOf, missing, notifier, shown };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { tenantScope } from "./tenant.js";
|
|
2
|
+
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
|
+
import { validateBody } from "./validate-body.js";
|
|
6
|
+
//#region src/write-update.ts
|
|
7
|
+
/**
|
|
8
|
+
* Everything an update needs before it writes: the row (locked, inside the caller's tenant and row filters), the ETag
|
|
9
|
+
* precondition, validation against the fields the caller may write, and the record-level rule with its filter.
|
|
10
|
+
*/
|
|
11
|
+
const planUpdate = async (trx, access, key, body, ifMatch, required = true) => {
|
|
12
|
+
const { entry, full } = access;
|
|
13
|
+
const tenant = tenantScope(entry, access.session)?.tenantValue;
|
|
14
|
+
const target = targetFilter(access, "update");
|
|
15
|
+
const current = await fetchRecord(trx, entry, key, tenant, {
|
|
16
|
+
deleted: "hide",
|
|
17
|
+
lock: true,
|
|
18
|
+
...target && { filter: target },
|
|
19
|
+
etagFields: Object.keys(access.model.fields)
|
|
20
|
+
});
|
|
21
|
+
if (!current) throw missing(entry);
|
|
22
|
+
checkPrecondition(current, ifMatch, required);
|
|
23
|
+
const values = validateBody(access, "update", body, current.record);
|
|
24
|
+
const filter = Object.keys(values).length === 0 ? void 0 : await decide(access, "update", current.record, values);
|
|
25
|
+
if (filter && !await fetchRecord(trx, entry, key, tenant, {
|
|
26
|
+
filter,
|
|
27
|
+
fields: [firstKeyField(access)]
|
|
28
|
+
})) throw denied(entry, "update");
|
|
29
|
+
return {
|
|
30
|
+
current,
|
|
31
|
+
values,
|
|
32
|
+
...filter && { filter }
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
/** Writes a planned update, checks the row as written against the filters, and runs the hooks. */
|
|
36
|
+
const applyUpdate = async (trx, deps, access, key, plan) => {
|
|
37
|
+
const { entry } = access;
|
|
38
|
+
const tenant = tenantScope(entry, access.session)?.tenantValue;
|
|
39
|
+
const updated = await updateRecord(trx, entry, key, tenant, plan.values, { etagFields: Object.keys(access.model.fields) });
|
|
40
|
+
await assertWritten(trx, access, updated, "update", access.resolved.rowFilter.update, plan.filter);
|
|
41
|
+
await notifier(deps, access)(trx, "update", plan.current.record, updated.record);
|
|
42
|
+
return updated;
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
export { applyUpdate, planUpdate };
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/PrototeamHQ/protobase.git",
|
|
9
|
+
"directory": "packages/server"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://docs.protobase.net",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=22.12.0"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@scalar/hono-api-reference": "^0.12.10",
|
|
29
|
+
"better-auth": "^1.7.7",
|
|
30
|
+
"hono": "^4.13.13",
|
|
31
|
+
"jose": "^6.2.12",
|
|
32
|
+
"kysely": "^0.29.6",
|
|
33
|
+
"nodemailer": "^10.0.16",
|
|
34
|
+
"zod": "^4.6.5",
|
|
35
|
+
"@protobase/layout": "0.1.0",
|
|
36
|
+
"@protobase/schema": "0.1.0",
|
|
37
|
+
"@protobase/query": "0.1.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsdown"
|
|
41
|
+
}
|
|
6
42
|
}
|
package/README.md
DELETED