@protobase/server 0.51.3 → 0.52.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.
- package/dist/aggregate-groups.js +2 -2
- package/dist/aggregate-service.js +4 -4
- package/dist/aggregate-splits.js +1 -1
- package/dist/assistant/batch-approval.js +49 -51
- package/dist/assistant/batch-before.js +38 -0
- package/dist/assistant/batch-echo.js +36 -0
- package/dist/assistant/batch-write-args.js +14 -5
- package/dist/assistant/batch-write-tool.js +30 -11
- package/dist/assistant/budgeted-page.js +30 -0
- package/dist/assistant/comment-tools.d.ts +20 -0
- package/dist/assistant/comment-tools.js +120 -0
- package/dist/assistant/count-records-tool.d.ts +5 -0
- package/dist/assistant/count-records-tool.js +84 -0
- package/dist/assistant/query-result-budget.js +32 -0
- package/dist/assistant/query-tools.js +7 -7
- package/dist/assistant/record-before.js +2 -1
- package/dist/assistant/record-field-piece.js +54 -0
- package/dist/assistant/record-tool-args.js +6 -3
- package/dist/assistant/record-tools.d.ts +4 -1
- package/dist/assistant/record-tools.js +17 -6
- package/dist/assistant/records.d.ts +19 -0
- package/dist/assistant/records.js +20 -13
- package/dist/assistant/result-budget.js +72 -0
- package/dist/assistant/tools.d.ts +3 -0
- package/dist/assistant/write-echo.js +41 -0
- package/dist/assistant/write-messages.js +26 -6
- package/dist/assistant/write-prompts.js +37 -12
- package/dist/assistant/write-record-tools.js +15 -6
- package/dist/batch/run-batch.js +1 -1
- package/dist/better-auth/auth-settings.d.ts +1 -1
- package/dist/better-auth/status-route.js +2 -2
- package/dist/changes/capture-schema.js +1 -1
- package/dist/changes/resource-change.d.ts +4 -1
- package/dist/changes/resource-change.js +27 -3
- package/dist/comments/post-comment.js +1 -1
- package/dist/comments/read-comments.js +1 -1
- package/dist/comments/tool-comment-types.d.ts +28 -0
- package/dist/comments/tool-comments.js +30 -0
- package/dist/create-admin.d.ts +5 -2
- package/dist/create-admin.js +20 -10
- package/dist/deps.d.ts +1 -1
- package/dist/encryption/scan.js +1 -1
- package/dist/error-handler.js +3 -2
- package/dist/etag.js +1 -1
- package/dist/files/download.js +1 -1
- package/dist/files/file-cleanup.d.ts +1 -1
- package/dist/functions/records.d.ts +1 -1
- package/dist/functions/records.js +2 -2
- package/dist/history/read-history.js +2 -2
- package/dist/index.d.ts +9 -3
- package/dist/index.js +9 -4
- package/dist/jobs/run-job.d.ts +1 -1
- package/dist/list-photos.js +2 -2
- package/dist/list-service.js +6 -6
- package/dist/map-service.js +2 -2
- package/dist/mcp/mcp-context.d.ts +7 -4
- package/dist/mcp/mcp-context.js +6 -4
- package/dist/mcp/mcp-server.d.ts +3 -2
- package/dist/mcp/mcp-tools.d.ts +6 -1
- package/dist/mcp/mcp-tools.js +8 -4
- package/dist/package.js +1 -1
- package/dist/read-service.js +2 -2
- package/dist/realtime/broadcast-api.js +3 -3
- package/dist/realtime/broadcast-hub.js +15 -5
- package/dist/realtime/change-checker.js +1 -1
- package/dist/realtime/channel-access.js +1 -1
- package/dist/realtime/database-broadcasts.js +1 -1
- package/dist/realtime/presence-hub.js +74 -39
- package/dist/realtime/realtime-app.js +28 -4
- package/dist/realtime/realtime-routes.js +3 -5
- package/dist/realtime/realtime-socket.js +3 -1
- package/dist/realtime/relay/frame-cursor.js +38 -0
- package/dist/realtime/relay/presence-replica.js +57 -0
- package/dist/realtime/relay/realtime-options.d.ts +13 -0
- package/dist/realtime/relay/realtime-options.js +9 -0
- package/dist/realtime/relay/realtime-relay.js +104 -0
- package/dist/realtime/relay/relay-frame-sql.js +82 -0
- package/dist/realtime/relay/relay-presence-sql.js +71 -0
- package/dist/realtime/relay/relay-schema-sql.js +55 -0
- package/dist/realtime/relay/relay-schema.d.ts +7 -0
- package/dist/realtime/relay/relay-schema.js +18 -0
- package/dist/realtime/relay/relayed-hubs.js +61 -0
- package/dist/records.js +1 -1
- package/dist/related-labels.js +1 -1
- package/dist/rest/rest-context.js +1 -1
- package/dist/routes/batch.js +1 -1
- package/dist/routes/collection.js +2 -2
- package/dist/routes/record.js +3 -3
- package/dist/row-permissions.js +1 -1
- package/dist/sql-parts.js +1 -1
- package/dist/web-app/mcp-icons.d.ts +9 -0
- package/dist/web-app/mcp-icons.js +26 -0
- package/dist/webhooks/subscriber-view.js +27 -2
- package/dist/webhooks/webhooks.js +41 -12
- package/dist/write-pipeline.js +1 -1
- package/dist/write-shared.js +1 -1
- package/package.json +4 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { requestAccess } from "../request-access.js";
|
|
2
2
|
import { EventCodeError } from "../changes/event-code-error.js";
|
|
3
|
+
import { currentRecord } from "../changes/resource-change.js";
|
|
3
4
|
import { checkRecord, evaluateFilter } from "@protobase/schema";
|
|
4
5
|
//#region src/webhooks/subscriber-view.ts
|
|
5
6
|
/**
|
|
@@ -40,6 +41,28 @@ const subscriptionEvent = (change, event, { fields, thin = false } = {}) => ({
|
|
|
40
41
|
transaction: change.transaction
|
|
41
42
|
}
|
|
42
43
|
});
|
|
44
|
+
/**
|
|
45
|
+
* A delivery's stored event as a change again, with its records as the model has them now, to shape it anew for the
|
|
46
|
+
* subscription as it is now: an event stored before a field became sensitive holds it. The database role is unknown.
|
|
47
|
+
*/
|
|
48
|
+
const eventChange = (model, event) => {
|
|
49
|
+
const { data } = event;
|
|
50
|
+
return {
|
|
51
|
+
resource: data.resource,
|
|
52
|
+
key: data.key,
|
|
53
|
+
operation: data.operation,
|
|
54
|
+
...data.before && { before: currentRecord(model, data.before) },
|
|
55
|
+
...data.after && { after: currentRecord(model, data.after) },
|
|
56
|
+
changed: data.changed,
|
|
57
|
+
actor: data.actor,
|
|
58
|
+
source: data.source,
|
|
59
|
+
databaseRole: null,
|
|
60
|
+
...data.tenant !== void 0 && { tenant: data.tenant },
|
|
61
|
+
transaction: data.transaction,
|
|
62
|
+
sequence: data.sequence,
|
|
63
|
+
at: event.timestamp
|
|
64
|
+
};
|
|
65
|
+
};
|
|
43
66
|
const withUnknownFields = (model, record) => {
|
|
44
67
|
const fields = Object.values(model.fields).filter((field) => field.sensitive || !Object.hasOwn(record, field.name));
|
|
45
68
|
const unknown = { ...record };
|
|
@@ -96,7 +119,9 @@ const subscriberEvents = (deps) => {
|
|
|
96
119
|
if (!accesses.has(cacheKey)) accesses.set(cacheKey, requestAccess(deps, entry, session));
|
|
97
120
|
const readable = async () => {
|
|
98
121
|
const access = await accesses.get(cacheKey);
|
|
99
|
-
|
|
122
|
+
const record = change.after ?? change.before;
|
|
123
|
+
if (!record) return access.resolved.operations.read ? access : void 0;
|
|
124
|
+
return await mayRead(access, record) ? access : void 0;
|
|
100
125
|
};
|
|
101
126
|
const access = await readable().catch((error) => {
|
|
102
127
|
throw new EventCodeError(`The access check of subscription "${subscription.name}" on ${change.resource} ${change.key}`, error);
|
|
@@ -109,4 +134,4 @@ const subscriberEvents = (deps) => {
|
|
|
109
134
|
};
|
|
110
135
|
};
|
|
111
136
|
//#endregion
|
|
112
|
-
export { subscriberEvents, subscriberSession, subscriptionEvent };
|
|
137
|
+
export { eventChange, subscriberEvents, subscriberSession, subscriptionEvent };
|
|
@@ -6,11 +6,11 @@ import { coalesce } from "../changes/coalesce.js";
|
|
|
6
6
|
import { EventCodeError } from "../changes/event-code-error.js";
|
|
7
7
|
import { eventMatcher } from "../changes/match-events.js";
|
|
8
8
|
import { deleteOutbox, readOutbox } from "../changes/outbox.js";
|
|
9
|
-
import { resourceChange } from "../changes/resource-change.js";
|
|
9
|
+
import { currentChange, resourceChange } from "../changes/resource-change.js";
|
|
10
10
|
import { callerRecords } from "../functions/records.js";
|
|
11
11
|
import { writeHistory } from "../history/write-history.js";
|
|
12
12
|
import { maxTries, nextRetryDelay } from "./retry-schedule.js";
|
|
13
|
-
import { subscriberEvents } from "./subscriber-view.js";
|
|
13
|
+
import { eventChange, subscriberEvents } from "./subscriber-view.js";
|
|
14
14
|
import { attemptDelivery } from "./attempt.js";
|
|
15
15
|
import { subscriptionProblems, subscriptionRouter } from "./route-events.js";
|
|
16
16
|
import { tableSubscriptionName } from "./table/options.js";
|
|
@@ -51,7 +51,8 @@ const deliveryGroup = ({ subscription, change }) => `${subscription.name}:${chan
|
|
|
51
51
|
* only: the others still receive it, and the retry delivers to that subscription alone. Any other error fails the
|
|
52
52
|
* whole dispatch, which is retried.
|
|
53
53
|
* - `webhooks.deliver`, one try of an unordered delivery; a failure queues the next try on the Standard Webhooks
|
|
54
|
-
* schedule, or Retry-After, and the last one is kept as failed.
|
|
54
|
+
* schedule, or Retry-After, and the last one is kept as failed. Each try, and each of the job below, shapes its event
|
|
55
|
+
* again for the subscription as it is now, and sends nothing when it may no longer read the record.
|
|
55
56
|
* - `webhooks.deliver-ordered`, the ordered deliveries of one dispatch for one subscription and record, retried with
|
|
56
57
|
* doubling waits up to a day, holding back the record's later deliveries to that subscription meanwhile.
|
|
57
58
|
*
|
|
@@ -103,8 +104,25 @@ const createWebhooks = ({ deps, subscriptions, functions, schema, log, table })
|
|
|
103
104
|
const found = id === void 0 ? byName.get(name) : await table?.subscriptions.find(db, id);
|
|
104
105
|
if (!found) throw new FatalJobError(`The subscription "${name}" no longer exists`);
|
|
105
106
|
if (found.options.enabled === false) throw new FatalJobError(`The subscription "${name}" is disabled`);
|
|
107
|
+
const problems = id === void 0 ? [] : subscriptionProblems([found], models, []);
|
|
108
|
+
if (problems.length > 0) throw new FatalJobError(problems.join("; "));
|
|
106
109
|
return found;
|
|
107
110
|
};
|
|
111
|
+
const modelOf = (resource) => {
|
|
112
|
+
const model = deps.registry.find(resource)?.model;
|
|
113
|
+
if (!model) throw new FatalJobError(`The resource "${resource}" no longer exists`);
|
|
114
|
+
return model;
|
|
115
|
+
};
|
|
116
|
+
const reshaper = () => {
|
|
117
|
+
const eventOf = subscriberEvents(deps);
|
|
118
|
+
return async (subscription, stored) => {
|
|
119
|
+
const shaped = await eventOf(subscription, eventChange(modelOf(stored.data.resource), stored), stored.type);
|
|
120
|
+
return shaped && {
|
|
121
|
+
...shaped,
|
|
122
|
+
id: stored.id
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
};
|
|
108
126
|
const deliver = defineJob({
|
|
109
127
|
name: "webhooks.deliver",
|
|
110
128
|
maxAttempts: 3,
|
|
@@ -113,13 +131,16 @@ const createWebhooks = ({ deps, subscriptions, functions, schema, log, table })
|
|
|
113
131
|
concurrency: parallelAttempts,
|
|
114
132
|
run: async (payload, { db, signal }) => {
|
|
115
133
|
const subscription = await subscriptionOf(payload.subscription, db);
|
|
116
|
-
const
|
|
117
|
-
|
|
134
|
+
const event = await reshaper()(subscription, payload.event);
|
|
135
|
+
if (!event) return;
|
|
136
|
+
const result = await attempt(subscription, event, db, payload.attempt, signal);
|
|
137
|
+
report(payload.subscription, event, payload.attempt, result);
|
|
118
138
|
if (result.ok) return;
|
|
119
139
|
const delay = result.final ? void 0 : nextRetryDelay(payload.attempt, result.retryAfterMs);
|
|
120
140
|
if (delay === void 0) throw new FatalJobError(result.error);
|
|
121
141
|
await deliver.enqueue(db, {
|
|
122
142
|
...payload,
|
|
143
|
+
event,
|
|
123
144
|
attempt: payload.attempt + 1,
|
|
124
145
|
lastError: result.error
|
|
125
146
|
}, {
|
|
@@ -138,7 +159,13 @@ const createWebhooks = ({ deps, subscriptions, functions, schema, log, table })
|
|
|
138
159
|
concurrency: parallelAttempts,
|
|
139
160
|
run: async (payload, { db, signal, job }) => {
|
|
140
161
|
const subscription = await subscriptionOf(payload.subscription, db);
|
|
141
|
-
|
|
162
|
+
const reshape = reshaper();
|
|
163
|
+
const events = [];
|
|
164
|
+
for (const stored of payload.events) {
|
|
165
|
+
const event = await reshape(subscription, stored);
|
|
166
|
+
if (event) events.push(event);
|
|
167
|
+
}
|
|
168
|
+
for (const event of events) {
|
|
142
169
|
const result = await attempt(subscription, event, db, job.attempt, signal);
|
|
143
170
|
report(payload.subscription, event, job.attempt, result);
|
|
144
171
|
if (result.ok) continue;
|
|
@@ -147,11 +174,12 @@ const createWebhooks = ({ deps, subscriptions, functions, schema, log, table })
|
|
|
147
174
|
}
|
|
148
175
|
}
|
|
149
176
|
});
|
|
150
|
-
const changeDeliveries = async (db) => {
|
|
151
|
-
const
|
|
177
|
+
const changeDeliveries = async (db, only) => {
|
|
178
|
+
const all = await current(db);
|
|
179
|
+
const route = subscriptionRouter(only === void 0 ? all : all.filter(({ name }) => name === only), models);
|
|
152
180
|
const eventOf = subscriberEvents(deps);
|
|
153
|
-
return async (change
|
|
154
|
-
const routes = route(change, matchers.get(change.resource)(change))
|
|
181
|
+
return async (change) => {
|
|
182
|
+
const routes = route(change, matchers.get(change.resource)(change));
|
|
155
183
|
const deliveries = [];
|
|
156
184
|
const failures = [];
|
|
157
185
|
const failed = /* @__PURE__ */ new Set();
|
|
@@ -195,8 +223,9 @@ const createWebhooks = ({ deps, subscriptions, functions, schema, log, table })
|
|
|
195
223
|
name: "webhooks.dispatch-change",
|
|
196
224
|
maxAttempts: 1,
|
|
197
225
|
timeout: "5 minutes",
|
|
198
|
-
run: async ({ change, subscription: only }, { db }) => {
|
|
199
|
-
const
|
|
226
|
+
run: async ({ change: kept, subscription: only }, { db }) => {
|
|
227
|
+
const change = currentChange(modelOf(kept.resource), kept);
|
|
228
|
+
const { deliveries, failures } = await (await changeDeliveries(db, only))(change);
|
|
200
229
|
if (only !== void 0 && failures.length > 0) throw failures[0].error;
|
|
201
230
|
await db.transaction().execute(async (trx) => {
|
|
202
231
|
for (const { subscription, event } of deliveries.filter(({ subscription }) => !subscription.options.ordered)) {
|
package/dist/write-pipeline.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { HttpProblem, badRequest } from "./problem.js";
|
|
2
2
|
import { tenantScope } from "./tenant.js";
|
|
3
3
|
import { requestAccess, requireOperation, targetFilter, writableFields } from "./request-access.js";
|
|
4
|
+
import { writeTransaction } from "./transactions.js";
|
|
4
5
|
import { actorSettings, wakeDispatch } from "./changes/actor.js";
|
|
5
6
|
import { isDatabaseError } from "./error-handler.js";
|
|
6
7
|
import { attachFiles, scheduleRemovedFiles } from "./files/attach.js";
|
|
7
|
-
import { writeTransaction } from "./transactions.js";
|
|
8
8
|
import { bindingOf, pendingBinding } from "./encryption/binding.js";
|
|
9
9
|
import { deleteRecord, fetchRecord, insertRecord, restoreRecord, updateRecord } from "./records.js";
|
|
10
10
|
import { assertWritten, checkParents, checkPrecondition, decide, denied, firstKeyField, keyOf, missing, notifier, shown } from "./write-shared.js";
|
package/dist/write-shared.js
CHANGED
|
@@ -2,9 +2,9 @@ import { maskEncrypted } from "./encryption/mask-stored.js";
|
|
|
2
2
|
import { forbidden, notFound, preconditionFailed, preconditionRequired } from "./problem.js";
|
|
3
3
|
import { tenantScope } from "./tenant.js";
|
|
4
4
|
import { andFilters, recordDecision } from "./request-access.js";
|
|
5
|
+
import { pickFields } from "./rows.js";
|
|
5
6
|
import { removedFiles } from "./files/attach.js";
|
|
6
7
|
import { ifMatchSatisfied } from "./etag.js";
|
|
7
|
-
import { pickFields } from "./rows.js";
|
|
8
8
|
import { fetchRecord } from "./records.js";
|
|
9
9
|
import { parentGuard } from "./parent-access.js";
|
|
10
10
|
import { presentFiles } from "./files/present.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.1",
|
|
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": {
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"@better-auth/passkey": "^1.7.12",
|
|
41
41
|
"@better-auth/sso": "^1.7.12",
|
|
42
42
|
"@modelcontextprotocol/server": "^2.3.1",
|
|
43
|
-
"@protobase/layout": "0.
|
|
44
|
-
"@protobase/query": "0.
|
|
45
|
-
"@protobase/schema": "0.
|
|
43
|
+
"@protobase/layout": "0.52.1",
|
|
44
|
+
"@protobase/query": "0.52.1",
|
|
45
|
+
"@protobase/schema": "0.52.1",
|
|
46
46
|
"@react-email/render": "^2.1.0",
|
|
47
47
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
48
48
|
"@sentry/core": "^11.7.0",
|