@protobase/server 0.21.7 → 0.21.9

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 (84) hide show
  1. package/dist/aggregate-groups.js +1 -1
  2. package/dist/aggregate-service.js +3 -3
  3. package/dist/ai/ai-fields.js +9 -0
  4. package/dist/ai/ai-model.js +9 -0
  5. package/dist/ai/check-ai.d.ts +13 -0
  6. package/dist/ai/check-ai.js +11 -0
  7. package/dist/ai/fill-hook.js +47 -0
  8. package/dist/ai/fill-job.js +67 -0
  9. package/dist/ai/generate-route.js +57 -0
  10. package/dist/ai/generate-value.js +28 -0
  11. package/dist/ai/values.js +15 -0
  12. package/dist/assistant/assistant-settings.d.ts +2 -2
  13. package/dist/assistant/assistant-settings.js +7 -21
  14. package/dist/assistant/built-in-assistant.js +2 -1
  15. package/dist/assistant/model-settings.js +31 -0
  16. package/dist/assistant/turn.d.ts +1 -1
  17. package/dist/assistant/turn.js +2 -2
  18. package/dist/batch/reorder.js +2 -2
  19. package/dist/batch/run-batch.js +1 -1
  20. package/dist/better-auth/create-auth.d.ts +12 -4
  21. package/dist/better-auth/create-auth.js +0 -1
  22. package/dist/better-auth/organizations/invitation-endpoints.js +1 -1
  23. package/dist/better-auth/organizations/member-endpoints.js +1 -1
  24. package/dist/better-auth/passkey-plugin.js +1 -1
  25. package/dist/better-auth/policy-plugin.js +1 -1
  26. package/dist/better-auth/roles/roles-plugin.js +1 -1
  27. package/dist/better-auth/sign-up/sign-up-plugin.js +1 -1
  28. package/dist/better-auth/staff-sign-in-endpoints.js +1 -1
  29. package/dist/changes/capture-schema.d.ts +1 -1
  30. package/dist/changes/outbox.d.ts +1 -1
  31. package/dist/check-views.js +33 -5
  32. package/dist/comments/app-origin.js +11 -0
  33. package/dist/comments/comment-access.js +54 -0
  34. package/dist/comments/comment-input.js +26 -0
  35. package/dist/comments/comment-item.d.ts +16 -0
  36. package/dist/comments/comment-item.js +18 -0
  37. package/dist/comments/comment-users.d.ts +21 -0
  38. package/dist/comments/comment-users.js +35 -0
  39. package/dist/comments/comments-sql.d.ts +8 -0
  40. package/dist/comments/comments-sql.js +37 -0
  41. package/dist/comments/mention-email.js +37 -0
  42. package/dist/comments/mention-job.d.ts +3 -0
  43. package/dist/comments/mention-job.js +43 -0
  44. package/dist/comments/mentionable.js +38 -0
  45. package/dist/comments/post-comment.js +57 -0
  46. package/dist/comments/read-comments.js +45 -0
  47. package/dist/comments/record-comments.js +53 -0
  48. package/dist/comments/record-label.js +19 -0
  49. package/dist/create-admin.d.ts +7 -4
  50. package/dist/create-admin.js +43 -8
  51. package/dist/deps.d.ts +4 -2
  52. package/dist/error-handler.js +1 -1
  53. package/dist/files/attach.js +1 -1
  54. package/dist/files/cleanup-runner.d.ts +1 -1
  55. package/dist/files/runtime.d.ts +2 -2
  56. package/dist/files/upload.js +2 -2
  57. package/dist/history/read-history.js +1 -1
  58. package/dist/index.d.ts +7 -3
  59. package/dist/index.js +11 -9
  60. package/dist/jobs/runtime.js +1 -1
  61. package/dist/list-photos.js +3 -3
  62. package/dist/list-service.js +2 -2
  63. package/dist/map-service.js +1 -1
  64. package/dist/meta.js +2 -2
  65. package/dist/openapi/resource-paths.js +151 -0
  66. package/dist/project-config.d.ts +1 -1
  67. package/dist/read-service.js +2 -2
  68. package/dist/related-labels.js +2 -2
  69. package/dist/request-access.d.ts +2 -2
  70. package/dist/routes/record.js +17 -0
  71. package/dist/row-permissions.js +3 -3
  72. package/dist/scan-guard.d.ts +2 -2
  73. package/dist/sql-parts.js +1 -1
  74. package/dist/types.d.ts +3 -1
  75. package/dist/view-access.js +33 -5
  76. package/dist/view-related.js +15 -2
  77. package/dist/webhooks/subscription.d.ts +1 -1
  78. package/dist/webhooks/table/schema.js +1 -1
  79. package/dist/webhooks/table/webhook-table.js +1 -1
  80. package/dist/webhooks/webhooks.js +1 -1
  81. package/dist/write-pipeline.js +2 -2
  82. package/dist/write-shared.js +3 -2
  83. package/dist/write-update.js +1 -1
  84. package/package.json +4 -4
@@ -1,8 +1,8 @@
1
1
  import { badRequest } from "./problem.js";
2
- import { exposeRow } from "./rows.js";
3
2
  import { readScope, requestAccess } from "./request-access.js";
4
- import { listQuery } from "@protobase/query";
3
+ import { exposeRow } from "./rows.js";
5
4
  import { checkFilter, createWhere } from "@protobase/schema";
5
+ import { listQuery } from "@protobase/query";
6
6
  //#region src/related-labels.ts
7
7
  const where = createWhere();
8
8
  const invalid = (entry, message) => badRequest("invalid-parameter", `Invalid labels entry "${entry}": ${message}`, { errors: [{
@@ -1,5 +1,5 @@
1
1
  import "./types.js";
2
2
  import "./registry.js";
3
3
  import "./deps.js";
4
- import "@protobase/query";
5
- import { ResourceModel } from "@protobase/schema";
4
+ import { ResourceModel } from "@protobase/schema";
5
+ import "@protobase/query";
@@ -9,6 +9,10 @@ import { returnPreference } from "./preferences.js";
9
9
  import { resolveTarget } from "./target.js";
10
10
  import { revealField } from "../reveal-service.js";
11
11
  import { readHistory } from "../history/read-history.js";
12
+ import { mentionable } from "../comments/mentionable.js";
13
+ import { postComment } from "../comments/post-comment.js";
14
+ import { readComments } from "../comments/read-comments.js";
15
+ import { generateField } from "../ai/generate-route.js";
12
16
  import { Hono } from "hono";
13
17
  //#region src/routes/record.ts
14
18
  const recordRoutes = (deps) => {
@@ -16,6 +20,8 @@ const recordRoutes = (deps) => {
16
20
  app.get("/:resource/:key", async (c) => {
17
21
  const { entry } = resolveTarget(deps, c.req.param("resource"));
18
22
  if (c.req.param("key").endsWith(":history")) return c.json(await readHistory(deps, entry, c.get("session"), keyFromPath(c, entry, ":history"), c.req.query()));
23
+ if (c.req.param("key").endsWith(":comments")) return c.json(await readComments(deps, entry, c.get("session"), keyFromPath(c, entry, ":comments"), c.req.query()));
24
+ if (c.req.param("key").endsWith(":mentionable")) return c.json(await mentionable(deps, entry, c.get("session"), keyFromPath(c, entry, ":mentionable"), c.req.query()));
19
25
  const deleted = deletedRows(entry.model, c.req.query("show_deleted") === "true");
20
26
  const found = await readRecord(deps, entry, c.get("session"), keyFromPath(c, entry), deleted);
21
27
  const columns = selectedFields({
@@ -40,6 +46,17 @@ const recordRoutes = (deps) => {
40
46
  app.post("/:resource/:key", async (c) => {
41
47
  const { entry } = resolveTarget(deps, c.req.param("resource"));
42
48
  if (c.req.param("key").endsWith(":reveal")) return reveal(c, entry);
49
+ if (c.req.param("key").endsWith(":comments")) {
50
+ const body = await c.req.json();
51
+ return c.json(await postComment(deps, entry, c.get("session"), keyFromPath(c, entry, ":comments"), body, c.req.url), 201);
52
+ }
53
+ if (c.req.param("key").endsWith(":generate")) {
54
+ const generated = await generateField(deps, entry, c.get("session"), keyFromPath(c, entry, ":generate"), await c.req.json(), c.req.raw.signal);
55
+ return c.json({
56
+ ...generated.record,
57
+ etag: generated.etag
58
+ }, 200, { ETag: generated.etag });
59
+ }
43
60
  if (!c.req.param("key").endsWith(":undelete")) throw notFound("There is no such method");
44
61
  const restored = await runWrite(deps, entry, c.get("session"), {
45
62
  operation: "undelete",
@@ -1,9 +1,9 @@
1
- import { aliasedColumn, columnId, scopeConditions, tableId, whereClause } from "./sql-parts.js";
2
- import { fetchRecord } from "./records.js";
3
1
  import { tenantScope } from "./tenant.js";
4
2
  import { recordDecision } from "./request-access.js";
5
- import { compileFilter } from "@protobase/query";
3
+ import { aliasedColumn, columnId, scopeConditions, tableId, whereClause } from "./sql-parts.js";
4
+ import { fetchRecord } from "./records.js";
6
5
  import { sql } from "kysely";
6
+ import { compileFilter } from "@protobase/query";
7
7
  //#region src/row-permissions.ts
8
8
  const keyOf = (access, row) => JSON.stringify(access.full.primaryKey.map((name) => String(row[name])));
9
9
  const matching = async (trx, access, rows, filter) => {
@@ -1,4 +1,4 @@
1
1
  import "./types.js";
2
2
  import "./registry.js";
3
- import { Db } from "@protobase/query";
4
- import { ResourceModel } from "@protobase/schema";
3
+ import { ResourceModel } from "@protobase/schema";
4
+ import { Db } from "@protobase/query";
package/dist/sql-parts.js CHANGED
@@ -1,5 +1,5 @@
1
- import { compileFilter } from "@protobase/query";
2
1
  import { sql } from "kysely";
2
+ import { compileFilter } from "@protobase/query";
3
3
  //#region src/sql-parts.ts
4
4
  const etagAlias = "__pb_etag";
5
5
  const tableId = (model) => {
package/dist/types.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { JobDefinition } from "./jobs/job-types.js";
2
2
  import { Hono } from "hono";
3
3
  import { PageModel } from "@protobase/layout";
4
- import { Db } from "@protobase/query";
5
4
  import { ResourceModel, ViewModel } from "@protobase/schema";
5
+ import { Db } from "@protobase/query";
6
6
  //#region src/types.d.ts
7
7
  export type TenantValue = string | number;
8
8
  export type User = {
@@ -35,6 +35,8 @@ export type WriteEvent = {
35
35
  resource: ResourceModel;
36
36
  user: User;
37
37
  tenant?: TenantValue;
38
+ /** The writer is a visitor without a token, through a public form. */
39
+ anonymous?: true;
38
40
  before?: Row;
39
41
  after?: Row;
40
42
  };
@@ -1,19 +1,26 @@
1
- import { relatedErrors } from "./view-related.js";
1
+ import { openErrors, relatedErrors } from "./view-related.js";
2
2
  import { cardPhotosErrors } from "./view-card-photos.js";
3
3
  import { cardFavoriteErrors } from "./view-card-favorite.js";
4
4
  import { searchesOfFor } from "./view-saved-search.js";
5
5
  import { relatedColumnErrors } from "./view-related-column.js";
6
6
  import { resolveFieldPath } from "@protobase/layout";
7
- import { addressErrors, chartFields, hrefPlaceholders, navRecentErrors, pruneByShape, shapes, tableDisplay } from "@protobase/schema/internal";
7
+ import { addressErrors, chartFields, hrefPlaceholders, navRecentErrors, promptFields, pruneByShape, shapes, tableDisplay } from "@protobase/schema/internal";
8
8
  //#region src/view-access.ts
9
9
  const pruneRelated = (item, resource, models) => {
10
- const { columns, ...rest } = item;
10
+ const { columns, open, ...rest } = item;
11
11
  const visible = models[item.resource] ? columns?.filter((column) => resolveFieldPath(models, item.resource, column).ok) : void 0;
12
12
  const narrowed = visible && visible.length > 0 ? {
13
13
  ...rest,
14
14
  columns: visible
15
15
  } : rest;
16
- return relatedErrors(narrowed, resource, models).length === 0 ? narrowed : void 0;
16
+ const opened = open !== void 0 && openErrors({
17
+ ...narrowed,
18
+ open
19
+ }, models).length === 0 ? {
20
+ ...narrowed,
21
+ open
22
+ } : narrowed;
23
+ return relatedErrors(opened, resource, models).length === 0 ? opened : void 0;
17
24
  };
18
25
  const pruneGallery = (item, resource, model, models) => {
19
26
  const image = item.image !== void 0 && Object.hasOwn(model.fields, item.image) ? item.image : void 0;
@@ -101,6 +108,15 @@ const listShape = (resource, model, models, prompted) => shapes.object({
101
108
  badge: shapes.field,
102
109
  meta: shapes.list(shapes.field)
103
110
  }, { requires: ["by"] }),
111
+ calendar: shapes.object({
112
+ kind: shapes.data,
113
+ name: shapes.data,
114
+ label: shapes.data,
115
+ icon: shapes.data,
116
+ start: shapes.field,
117
+ end: shapes.field,
118
+ title: shapes.field
119
+ }, { requires: ["start"] }),
104
120
  custom: shapes.object({
105
121
  kind: shapes.data,
106
122
  name: shapes.data,
@@ -178,6 +194,11 @@ const layoutShape = (resource, model, models) => shapes.oneOf({
178
194
  title: shapes.data,
179
195
  limit: shapes.data,
180
196
  when: shapes.data
197
+ }),
198
+ comments: shapes.object({
199
+ kind: shapes.data,
200
+ title: shapes.data,
201
+ when: shapes.data
181
202
  })
182
203
  });
183
204
  const linkShape = (model, models) => shapes.map((run) => {
@@ -186,6 +207,12 @@ const linkShape = (model, models) => shapes.map((run) => {
186
207
  source: model
187
208
  }).length === 0 : hrefPlaceholders(run.href).every((name) => Object.hasOwn(model.fields, name))) ? run : void 0;
188
209
  });
210
+ const aiShape = (model) => shapes.map((run) => {
211
+ return [run.writes, ...promptFields(run.prompt ?? "")].every((name) => Object.hasOwn(model.fields, name)) ? {
212
+ kind: "ai",
213
+ writes: run.writes
214
+ } : void 0;
215
+ });
189
216
  const inputsShape = (models) => shapes.map((inputs) => inputs.every((input) => input.resource === void 0 || Object.hasOwn(models, input.resource)) ? inputs : void 0);
190
217
  const actionShape = (model, models) => shapes.object({
191
218
  name: shapes.data,
@@ -199,7 +226,8 @@ const actionShape = (model, models) => shapes.object({
199
226
  values: shapes.keys({ all: true })
200
227
  }, { requires: ["values"] }),
201
228
  delete: shapes.data,
202
- link: linkShape(model, models)
229
+ link: linkShape(model, models),
230
+ ai: aiShape(model)
203
231
  }),
204
232
  inputs: inputsShape(models)
205
233
  }, { requires: ["run", "inputs"] });
@@ -34,6 +34,18 @@ const throughErrors = (through, resource, models) => {
34
34
  ...filterErrors(via, through.filter).map((error) => `filter: ${error}`)
35
35
  ].map((error) => `through: ${error}`);
36
36
  };
37
+ /** What is wrong with the relation a related section's rows open; empty without one. */
38
+ const openErrors = (item, models) => {
39
+ const target = models[item.resource];
40
+ if (item.open === void 0 || !target) return [];
41
+ const field = Object.hasOwn(target.fields, item.open) ? target.fields[item.open] : void 0;
42
+ if (!field) return [`open: unknown field "${item.open}" on ${target.name}`];
43
+ if (field.type !== "relation" || !field.relation) return [`open: "${item.open}" on ${target.name} is not a relation`];
44
+ if (item.open === item.field) return [`open: "${item.open}" is the field that matches this record`];
45
+ const far = models[field.relation.resource];
46
+ if (!far) return [`open: unknown resource "${field.relation.resource}"`];
47
+ return far.primaryKey.length === 1 ? [] : [`open: ${far.name} has a composite key, so its records cannot be opened from "${item.open}"`];
48
+ };
37
49
  /**
38
50
  * What is wrong with a related section on the record page of `resource`, against these models; empty when it can be
39
51
  * shown. Against the full models it checks the config; against a caller's models, whether that caller can use it.
@@ -52,8 +64,9 @@ const relatedErrors = (item, resource, models) => {
52
64
  if (!resolved.ok) return [`column: ${resolved.message}`];
53
65
  return resolved.steps.at(-1).field.sensitive ? [`column: "${column}" is sensitive, so it cannot be listed`] : [];
54
66
  }),
55
- ...item.limit === void 0 || Number.isInteger(item.limit) && item.limit >= 1 && item.limit <= 100 ? [] : ["limit must be a whole number from 1 to 100"]
67
+ ...item.limit === void 0 || Number.isInteger(item.limit) && item.limit >= 1 && item.limit <= 100 ? [] : ["limit must be a whole number from 1 to 100"],
68
+ ...openErrors(item, models)
56
69
  ];
57
70
  };
58
71
  //#endregion
59
- export { matchErrors, relatedErrors, sortErrors, throughKey };
72
+ export { matchErrors, openErrors, relatedErrors, sortErrors, throughKey };
@@ -1,6 +1,6 @@
1
1
  import { Row, TenantValue } from "../types.js";
2
- import { Db } from "@protobase/query";
3
2
  import { EventActor, EventOperation } from "@protobase/schema";
3
+ import { Db } from "@protobase/query";
4
4
  //#region src/webhooks/subscription.d.ts
5
5
  /** What happened, as a receiver gets it in the `data` of an event. Records use field names, as the API shows them. */
6
6
  export type EventData = {
@@ -1,8 +1,8 @@
1
1
  import { quoteIdent } from "../../changes/capture-sql.js";
2
2
  import { changesSchema } from "../../changes/events-options.js";
3
+ import { migrationCommand } from "../../changes/capture-schema.js";
3
4
  import { webhooksSchema } from "./options.js";
4
5
  import { commandsSql, deliveriesSql, deliveryLogSql, subscriptionsSql, webhooksComment } from "./schema-sql.js";
5
- import { migrationCommand } from "../../changes/capture-schema.js";
6
6
  import { sql } from "kysely";
7
7
  //#region src/webhooks/table/schema.ts
8
8
  const functionNames = ["subscription_commands", "deliveries_resend"];
@@ -1,6 +1,6 @@
1
+ import { betterAuthOwnerRoles } from "./owner-roles.js";
1
2
  import { webhooksSchema } from "./options.js";
2
3
  import { checkWebhookUrl } from "./address-check.js";
3
- import { betterAuthOwnerRoles } from "./owner-roles.js";
4
4
  import { tableSubscriptions } from "./table-subscriptions.js";
5
5
  //#region src/webhooks/table/webhook-table.ts
6
6
  /**
@@ -1,5 +1,6 @@
1
1
  import { dispatchJobName, isCaptured } from "../changes/capture-sql.js";
2
2
  import { defineJob } from "../jobs/define-job.js";
3
+ import { FatalJobError } from "../jobs/run-job.js";
3
4
  import { coalesce } from "../changes/coalesce.js";
4
5
  import { EventCodeError } from "../changes/event-code-error.js";
5
6
  import { eventMatcher } from "../changes/match-events.js";
@@ -7,7 +8,6 @@ import { deleteOutbox, readOutbox } from "../changes/outbox.js";
7
8
  import { resourceChange } from "../changes/resource-change.js";
8
9
  import { callerRecords } from "../functions/records.js";
9
10
  import { writeHistory } from "../history/write-history.js";
10
- import { FatalJobError } from "../jobs/run-job.js";
11
11
  import { maxTries, nextRetryDelay } from "./retry-schedule.js";
12
12
  import { subscriberEvents } from "./subscriber-view.js";
13
13
  import { attemptDelivery } from "./attempt.js";
@@ -1,10 +1,10 @@
1
1
  import { HttpProblem, badRequest } from "./problem.js";
2
- import { isDatabaseError } from "./error-handler.js";
3
- import { deleteRecord, fetchRecord, insertRecord, restoreRecord } from "./records.js";
4
2
  import { tenantScope } from "./tenant.js";
5
3
  import { requestAccess, requireOperation, targetFilter, writableFields } from "./request-access.js";
4
+ import { deleteRecord, fetchRecord, insertRecord, restoreRecord } from "./records.js";
6
5
  import { writeTransaction } from "./transactions.js";
7
6
  import { blockedDelete } from "./blocked-delete.js";
7
+ import { isDatabaseError } from "./error-handler.js";
8
8
  import { attachFiles } from "./files/attach.js";
9
9
  import { actorSettings, wakeDispatch } from "./changes/actor.js";
10
10
  import { validateBody } from "./validate-body.js";
@@ -1,10 +1,10 @@
1
1
  import { forbidden, notFound, preconditionFailed, preconditionRequired } from "./problem.js";
2
+ import { tenantScope } from "./tenant.js";
3
+ import { andFilters, recordDecision } from "./request-access.js";
2
4
  import { presentFiles } from "./files/present.js";
3
5
  import { ifMatchSatisfied } from "./etag.js";
4
6
  import { pickFields } from "./rows.js";
5
7
  import { fetchRecord } from "./records.js";
6
- import { tenantScope } from "./tenant.js";
7
- import { andFilters, recordDecision } from "./request-access.js";
8
8
  import { scheduleRemovedFiles } from "./files/attach.js";
9
9
  //#region src/write-shared.ts
10
10
  const missing = (entry) => notFound(`No ${entry.name} with this key`);
@@ -45,6 +45,7 @@ const notifier = (deps, access) => async (trx, kind, before, after) => {
45
45
  resource: access.full,
46
46
  user: session.user,
47
47
  ...session.tenant !== void 0 && { tenant: session.tenant },
48
+ ...session.anonymous && { anonymous: true },
48
49
  ...before && { before },
49
50
  ...after && { after }
50
51
  };
@@ -1,6 +1,6 @@
1
- import { fetchRecord, updateRecord } from "./records.js";
2
1
  import { tenantScope } from "./tenant.js";
3
2
  import { targetFilter } from "./request-access.js";
3
+ import { fetchRecord, updateRecord } from "./records.js";
4
4
  import { attachFiles } from "./files/attach.js";
5
5
  import { validateBody } from "./validate-body.js";
6
6
  import { assertWritten, checkPrecondition, decide, denied, firstKeyField, missing, notifier } from "./write-shared.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.21.7",
3
+ "version": "0.21.9",
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.21.7",
34
- "@protobase/query": "0.21.7",
35
- "@protobase/schema": "0.21.7",
33
+ "@protobase/layout": "0.21.9",
34
+ "@protobase/query": "0.21.9",
35
+ "@protobase/schema": "0.21.9",
36
36
  "@react-email/render": "^2.1.0",
37
37
  "@scalar/hono-api-reference": "^0.12.10",
38
38
  "@sentry/core": "^11.7.0",