@protobase/server 0.0.0-stage → 0.1.1

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.
Files changed (76) hide show
  1. package/LICENSE +202 -0
  2. package/dist/aggregate-service.js +135 -0
  3. package/dist/audit/console-queue.d.ts +5 -0
  4. package/dist/audit/console-queue.js +5 -0
  5. package/dist/auth/jwt.d.ts +21 -0
  6. package/dist/auth/jwt.js +49 -0
  7. package/dist/batch/refs.js +31 -0
  8. package/dist/batch/reorder.js +79 -0
  9. package/dist/batch/run-batch.js +105 -0
  10. package/dist/batch/schema.js +51 -0
  11. package/dist/better-auth/authenticator.d.ts +22 -0
  12. package/dist/better-auth/authenticator.js +21 -0
  13. package/dist/better-auth/create-auth.d.ts +1252 -0
  14. package/dist/better-auth/create-auth.js +104 -0
  15. package/dist/better-auth/jwks-cache.d.ts +9 -0
  16. package/dist/better-auth/jwks-cache.js +31 -0
  17. package/dist/better-auth/password-reset.js +32 -0
  18. package/dist/better-auth/status-route.js +19 -0
  19. package/dist/better-auth/tokens.d.ts +18 -0
  20. package/dist/better-auth/tokens.js +37 -0
  21. package/dist/better-auth/users.d.ts +50 -0
  22. package/dist/better-auth/users.js +111 -0
  23. package/dist/check-shell.js +17 -0
  24. package/dist/check-views.js +45 -0
  25. package/dist/create-admin.d.ts +23 -0
  26. package/dist/create-admin.js +85 -0
  27. package/dist/error-handler.js +44 -0
  28. package/dist/etag.js +22 -0
  29. package/dist/filter-input.js +28 -0
  30. package/dist/filter-shape.js +25 -0
  31. package/dist/hash.js +10 -0
  32. package/dist/index.d.ts +11 -0
  33. package/dist/index.js +10 -0
  34. package/dist/key.js +22 -0
  35. package/dist/list-service.js +96 -0
  36. package/dist/mail/mail-settings.js +26 -0
  37. package/dist/mail/smtp-mailer.d.ts +11 -0
  38. package/dist/mail/smtp-mailer.js +17 -0
  39. package/dist/meta.js +63 -0
  40. package/dist/openapi/components.js +331 -0
  41. package/dist/openapi/document.js +74 -0
  42. package/dist/openapi/naming.js +4 -0
  43. package/dist/openapi/resource-paths.js +417 -0
  44. package/dist/openapi/schemas.js +93 -0
  45. package/dist/params.js +48 -0
  46. package/dist/problem.d.ts +17 -0
  47. package/dist/problem.js +43 -0
  48. package/dist/read-service.js +65 -0
  49. package/dist/records.js +66 -0
  50. package/dist/registry.js +44 -0
  51. package/dist/request-access.js +97 -0
  52. package/dist/resource-source.d.ts +35 -0
  53. package/dist/resource-source.js +22 -0
  54. package/dist/reveal-service.js +43 -0
  55. package/dist/routes/batch.js +21 -0
  56. package/dist/routes/collection.js +63 -0
  57. package/dist/routes/docs.js +20 -0
  58. package/dist/routes/meta.js +22 -0
  59. package/dist/routes/preferences.js +12 -0
  60. package/dist/routes/record.js +97 -0
  61. package/dist/routes/target.js +18 -0
  62. package/dist/row-permissions.js +69 -0
  63. package/dist/rows.js +12 -0
  64. package/dist/scan-guard.js +72 -0
  65. package/dist/sql-parts.js +32 -0
  66. package/dist/tenant.js +10 -0
  67. package/dist/transactions.js +14 -0
  68. package/dist/types.d.ts +97 -0
  69. package/dist/validate-body.js +73 -0
  70. package/dist/view-access.js +54 -0
  71. package/dist/view-related.js +59 -0
  72. package/dist/write-pipeline.js +94 -0
  73. package/dist/write-shared.js +52 -0
  74. package/dist/write-update.js +45 -0
  75. package/package.json +39 -3
  76. 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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.1",
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.1",
36
+ "@protobase/query": "0.1.1",
37
+ "@protobase/schema": "0.1.1"
38
+ },
39
+ "scripts": {
40
+ "build": "tsdown"
41
+ }
6
42
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.