@protobase/server 0.19.16 → 0.21.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/aggregate-groups.js +137 -0
- package/dist/aggregate-service.js +11 -10
- package/dist/changes/actor.js +1 -1
- package/dist/changes/capture-plan.js +13 -10
- package/dist/changes/capture-schema.d.ts +7 -5
- package/dist/changes/capture-schema.js +14 -12
- package/dist/changes/capture-sql.d.ts +5 -0
- package/dist/changes/capture-sql.js +14 -7
- package/dist/changes/coalesce.d.ts +4 -0
- package/dist/changes/coalesce.js +1 -0
- package/dist/changes/outbox.js +1 -1
- package/dist/changes/resource-change.d.ts +2 -0
- package/dist/changes/resource-change.js +1 -0
- package/dist/check-view-queries.js +2 -1
- package/dist/check-views.js +8 -3
- package/dist/create-admin.d.ts +5 -2
- package/dist/create-admin.js +28 -7
- package/dist/history/activity.js +75 -0
- package/dist/history/actor-names.js +9 -0
- package/dist/history/history-sql.js +31 -0
- package/dist/history/options.d.ts +7 -0
- package/dist/history/options.js +9 -0
- package/dist/history/prune-history.js +26 -0
- package/dist/history/read-history.js +72 -0
- package/dist/history/write-history.js +43 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -2
- package/dist/openapi/resource-paths.js +137 -0
- package/dist/project-config.d.ts +3 -0
- package/dist/project-config.js +3 -1
- package/dist/routes/collection.js +5 -0
- package/dist/routes/record.js +2 -0
- package/dist/view-access.js +9 -7
- package/dist/view-chart.js +33 -0
- package/dist/webhooks/webhooks.js +8 -5
- package/package.json +4 -4
package/dist/routes/record.js
CHANGED
|
@@ -8,12 +8,14 @@ import { deletedRows, selectedFields } from "../params.js";
|
|
|
8
8
|
import { returnPreference } from "./preferences.js";
|
|
9
9
|
import { resolveTarget } from "./target.js";
|
|
10
10
|
import { revealField } from "../reveal-service.js";
|
|
11
|
+
import { readHistory } from "../history/read-history.js";
|
|
11
12
|
import { Hono } from "hono";
|
|
12
13
|
//#region src/routes/record.ts
|
|
13
14
|
const recordRoutes = (deps) => {
|
|
14
15
|
const app = new Hono();
|
|
15
16
|
app.get("/:resource/:key", async (c) => {
|
|
16
17
|
const { entry } = resolveTarget(deps, c.req.param("resource"));
|
|
18
|
+
if (c.req.param("key").endsWith(":history")) return c.json(await readHistory(deps, entry, c.get("session"), keyFromPath(c, entry, ":history"), c.req.query()));
|
|
17
19
|
const deleted = deletedRows(entry.model, c.req.query("show_deleted") === "true");
|
|
18
20
|
const found = await readRecord(deps, entry, c.get("session"), keyFromPath(c, entry), deleted);
|
|
19
21
|
const columns = selectedFields({
|
package/dist/view-access.js
CHANGED
|
@@ -4,7 +4,7 @@ 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, hrefPlaceholders, navRecentErrors, pruneByShape, shapes, tableDisplay } from "@protobase/schema/internal";
|
|
7
|
+
import { addressErrors, chartFields, hrefPlaceholders, navRecentErrors, pruneByShape, shapes, tableDisplay } from "@protobase/schema/internal";
|
|
8
8
|
//#region src/view-access.ts
|
|
9
9
|
const pruneRelated = (item, resource, models) => {
|
|
10
10
|
const { columns, ...rest } = item;
|
|
@@ -172,7 +172,13 @@ const layoutShape = (resource, model, models) => shapes.oneOf({
|
|
|
172
172
|
sidebar: shapes.data,
|
|
173
173
|
when: shapes.data
|
|
174
174
|
}, { requires: ["latitude", "longitude"] }),
|
|
175
|
-
gallery: shapes.map((item) => pruneGallery(item, resource, model, models))
|
|
175
|
+
gallery: shapes.map((item) => pruneGallery(item, resource, model, models)),
|
|
176
|
+
history: shapes.object({
|
|
177
|
+
kind: shapes.data,
|
|
178
|
+
title: shapes.data,
|
|
179
|
+
limit: shapes.data,
|
|
180
|
+
when: shapes.data
|
|
181
|
+
})
|
|
176
182
|
});
|
|
177
183
|
const linkShape = (model, models) => shapes.map((run) => {
|
|
178
184
|
return ("to" in run ? addressErrors(run.to, {
|
|
@@ -218,11 +224,7 @@ const viewShape = (resource, model, models, prompted) => shapes.object({
|
|
|
218
224
|
list: listShape(resource, model, models, prompted),
|
|
219
225
|
filters: shapes.list(widgetShape),
|
|
220
226
|
layout: shapes.list(layoutShape(resource, model, models)),
|
|
221
|
-
chart: shapes.
|
|
222
|
-
field: shapes.field,
|
|
223
|
-
range: shapes.data,
|
|
224
|
-
granularity: shapes.data
|
|
225
|
-
}, { requires: ["field"] }),
|
|
227
|
+
chart: shapes.map((chart) => chartFields(chart).every((name) => Object.hasOwn(model.fields, name)) ? chart : void 0),
|
|
226
228
|
recordMode: shapes.data,
|
|
227
229
|
helpWhen: shapes.data,
|
|
228
230
|
saveFeedback: shapes.data,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { isGroupChart } from "@protobase/schema/internal";
|
|
2
|
+
//#region src/view-chart.ts
|
|
3
|
+
const numericTypes = [
|
|
4
|
+
"integer",
|
|
5
|
+
"bigint",
|
|
6
|
+
"decimal"
|
|
7
|
+
];
|
|
8
|
+
const ungroupable = [
|
|
9
|
+
"date",
|
|
10
|
+
"timestamp",
|
|
11
|
+
"json",
|
|
12
|
+
"file"
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* What is wrong with a list's chart per value of a field: a field to group by that has no values to share out, or one
|
|
16
|
+
* to add up that is no number. The filterable and sensitive checks of every chart are with the other fields'.
|
|
17
|
+
*/
|
|
18
|
+
const chartErrors = (chart, model) => {
|
|
19
|
+
if (!isGroupChart(chart)) return [];
|
|
20
|
+
const fieldOf = (name) => Object.hasOwn(model.fields, name) ? model.fields[name] : void 0;
|
|
21
|
+
const group = fieldOf(chart.groupBy);
|
|
22
|
+
const measured = chart.sum ?? chart.average;
|
|
23
|
+
const measure = measured === void 0 ? void 0 : fieldOf(measured);
|
|
24
|
+
return [
|
|
25
|
+
...!group ? [`groupBy: unknown field "${chart.groupBy}" on ${model.name}`] : [],
|
|
26
|
+
...group && ungroupable.includes(group.type) ? [`groupBy: "${chart.groupBy}" is a ${group.type} field; chart a date with { field, range, granularity }, and group by a field of values`] : [],
|
|
27
|
+
...measured !== void 0 && !measure ? [`unknown field "${measured}" on ${model.name} to add up`] : [],
|
|
28
|
+
...measure && !numericTypes.includes(measure.type) ? [`"${measured}" is ${measure.type}, and only a number can be added up`] : [],
|
|
29
|
+
...chart.type === "donut" && chart.average !== void 0 ? ["a donut shares out a total, and averages add up to none: count or sum instead"] : []
|
|
30
|
+
];
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { chartErrors };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { dispatchJobName } from "../changes/capture-sql.js";
|
|
1
|
+
import { dispatchJobName, isCaptured } from "../changes/capture-sql.js";
|
|
2
|
+
import { defineJob } from "../jobs/define-job.js";
|
|
2
3
|
import { coalesce } from "../changes/coalesce.js";
|
|
3
4
|
import { EventCodeError } from "../changes/event-code-error.js";
|
|
4
5
|
import { eventMatcher } from "../changes/match-events.js";
|
|
5
6
|
import { deleteOutbox, readOutbox } from "../changes/outbox.js";
|
|
6
7
|
import { resourceChange } from "../changes/resource-change.js";
|
|
7
8
|
import { callerRecords } from "../functions/records.js";
|
|
8
|
-
import {
|
|
9
|
+
import { writeHistory } from "../history/write-history.js";
|
|
9
10
|
import { FatalJobError } from "../jobs/run-job.js";
|
|
10
11
|
import { maxTries, nextRetryDelay } from "./retry-schedule.js";
|
|
11
12
|
import { subscriberEvents } from "./subscriber-view.js";
|
|
@@ -40,7 +41,8 @@ const deliveryGroup = ({ subscription, change }) => `${subscription.name}:${chan
|
|
|
40
41
|
*
|
|
41
42
|
* - `changes.dispatch`, which `capture()` enqueues once per writing transaction, in one ordered group so one runs at a
|
|
42
43
|
* time: it turns the committed outbox rows into events, makes the first try of every unordered delivery itself,
|
|
43
|
-
* queues the failures and the ordered deliveries,
|
|
44
|
+
* queues the failures and the ordered deliveries, keeps the changes of resources with history, and deletes the rows
|
|
45
|
+
* in that same transaction. A process that dies
|
|
44
46
|
* before that commits leaves the rows, and the job's retry delivers them again, under the same `webhook-id`.
|
|
45
47
|
* - `webhooks.dispatch-change`, one change whose app code threw in dispatch: an event's `match` or filter, a
|
|
46
48
|
* subscription's `when`, or an access rule of its roles. Kept as failed, so dispatch moves on to later changes; a
|
|
@@ -52,14 +54,14 @@ const deliveryGroup = ({ subscription, change }) => `${subscription.name}:${chan
|
|
|
52
54
|
*
|
|
53
55
|
* With `table`, also `webhooks.send-test`, which Send test event queues, and with its log `webhooks.prune-log`, daily.
|
|
54
56
|
*
|
|
55
|
-
* No jobs without a resource with events; the subscriptions must name declared events.
|
|
57
|
+
* No jobs without a resource with events or history; the subscriptions must name declared events.
|
|
56
58
|
*/
|
|
57
59
|
const createWebhooks = ({ deps, subscriptions, functions, schema, log, table }) => {
|
|
58
60
|
const entries = deps.registry.entries;
|
|
59
61
|
const models = entries.map((entry) => entry.model);
|
|
60
62
|
const problems = subscriptionProblems(subscriptions, models, Object.keys(functions));
|
|
61
63
|
if (problems.length > 0) throw new Error(problems.join("; "));
|
|
62
|
-
if (!models.some(
|
|
64
|
+
if (!models.some(isCaptured)) return { jobs: [] };
|
|
63
65
|
const matchers = new Map(entries.map((entry) => [entry.name, eventMatcher(entry.model, entry.source.eventMatches?.() ?? {})]));
|
|
64
66
|
const keyColumns = (resource) => {
|
|
65
67
|
const model = deps.registry.find(resource)?.model;
|
|
@@ -230,6 +232,7 @@ const createWebhooks = ({ deps, subscriptions, functions, schema, log, table })
|
|
|
230
232
|
}
|
|
231
233
|
await queueOrdered(trx, deliveries);
|
|
232
234
|
for (const { change } of failures) await dispatchChange.enqueue(trx, { change });
|
|
235
|
+
await writeHistory(trx, schema, changes.filter((change) => deps.registry.find(change.resource)?.model.history));
|
|
233
236
|
await deleteOutbox(trx, schema, rows.map((row) => row.id));
|
|
234
237
|
});
|
|
235
238
|
for (const [index, { subscription, event }] of now.entries()) report(subscription.name, event, 1, results[index]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.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.21.0",
|
|
34
|
+
"@protobase/query": "0.21.0",
|
|
35
|
+
"@protobase/schema": "0.21.0",
|
|
36
36
|
"@react-email/render": "^2.1.0",
|
|
37
37
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
38
38
|
"@sentry/core": "^11.7.0",
|