@vibes.diy/api-svc 14.1.21 → 14.1.22

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 (61) hide show
  1. package/create-handler.d.ts +2 -1
  2. package/create-handler.js +1 -0
  3. package/create-handler.js.map +1 -1
  4. package/index.d.ts +2 -0
  5. package/index.js +2 -0
  6. package/index.js.map +1 -1
  7. package/intern/agent-belt.js +15 -4
  8. package/intern/agent-belt.js.map +1 -1
  9. package/intern/agent-build-tools.js +5 -2
  10. package/intern/agent-build-tools.js.map +1 -1
  11. package/intern/agent-debug-tools.d.ts +33 -0
  12. package/intern/agent-debug-tools.js +676 -0
  13. package/intern/agent-debug-tools.js.map +1 -0
  14. package/intern/agent-file-tools.js +8 -2
  15. package/intern/agent-file-tools.js.map +1 -1
  16. package/intern/agent-instruction.d.ts +1 -1
  17. package/intern/agent-instruction.js +2 -2
  18. package/intern/agent-instruction.js.map +1 -1
  19. package/intern/agent-session-instruction.d.ts +2 -2
  20. package/intern/agent-session-instruction.js +15 -2
  21. package/intern/agent-session-instruction.js.map +1 -1
  22. package/intern/agent-session-runtime.d.ts +1 -0
  23. package/intern/agent-session-runtime.js +8 -3
  24. package/intern/agent-session-runtime.js.map +1 -1
  25. package/intern/attempt-backend-fetch.js +7 -1
  26. package/intern/attempt-backend-fetch.js.map +1 -1
  27. package/intern/backend-do-addr.d.ts +2 -0
  28. package/intern/backend-do-addr.js +2 -0
  29. package/intern/backend-do-addr.js.map +1 -1
  30. package/intern/backend-do-control-plane.d.ts +18 -0
  31. package/intern/backend-do-control-plane.js +90 -1
  32. package/intern/backend-do-control-plane.js.map +1 -1
  33. package/intern/mint-decision.d.ts +8 -0
  34. package/intern/mint-decision.js +15 -3
  35. package/intern/mint-decision.js.map +1 -1
  36. package/intern/resolve-backend-session-user.d.ts +8 -0
  37. package/intern/resolve-backend-session-user.js +10 -0
  38. package/intern/resolve-backend-session-user.js.map +1 -1
  39. package/package.json +11 -11
  40. package/public/app-documents-broadcast.d.ts +2 -0
  41. package/public/app-documents-broadcast.js +7 -0
  42. package/public/app-documents-broadcast.js.map +1 -1
  43. package/public/app-documents-read-eventos.d.ts +52 -1
  44. package/public/app-documents-read-eventos.js +250 -222
  45. package/public/app-documents-read-eventos.js.map +1 -1
  46. package/public/app-documents-write-eventos.d.ts +42 -0
  47. package/public/app-documents-write-eventos.js +334 -334
  48. package/public/app-documents-write-eventos.js.map +1 -1
  49. package/public/app-logs.d.ts +14 -2
  50. package/public/app-logs.js +60 -45
  51. package/public/app-logs.js.map +1 -1
  52. package/public/app-setting-items.d.ts +4 -0
  53. package/public/app-setting-items.js +6 -3
  54. package/public/app-setting-items.js.map +1 -1
  55. package/public/app-settings-sql-upsert.d.ts +4 -0
  56. package/public/app-settings-sql-upsert.js +1 -1
  57. package/public/app-settings-sql-upsert.js.map +1 -1
  58. package/public/prompt-chat-section.js +1 -0
  59. package/public/prompt-chat-section.js.map +1 -1
  60. package/types.d.ts +2 -1
  61. package/types.js.map +1 -1
@@ -14,6 +14,7 @@ import { filterDocsByChannel } from "./channel-read-filter.js";
14
14
  import { mintFilesUrls } from "./files-url-mint.js";
15
15
  import { applyQueryFilter } from "./app-documents-query-filter.js";
16
16
  import { readAllowed, clientWsSend, connectionAdminMode } from "./app-documents-shared.js";
17
+ import { addressesDirectChannel } from "./dm-lane.js";
17
18
  import { resolveActingHandle, resolveActiveHandle } from "./resolve-active-handle.js";
18
19
  import { audienceChannelsOf, audienceChannelsForReader, collectAudienceSubjects, inheritChannelOf, INHERIT_CAPABILITY_CHANNEL, RESERVED_CHANNEL_PREFIX, } from "./audience-channels.js";
19
20
  import { resolveInheritReadChannels } from "./inherit-read-resolver.js";
@@ -64,6 +65,128 @@ async function validateReadActingPin(vctx, userId, pin) {
64
65
  return { ok: false };
65
66
  return { ok: true, actingHandle: pin };
66
67
  }
68
+ export async function getDocForCaller(vctx, args) {
69
+ if (addressesDirectChannel(args.ownerHandle))
70
+ return { kind: "denied", message: "Access denied" };
71
+ const authenticated = args.userId !== null;
72
+ const getPin = await validateReadActingPin(vctx, args.userId, args.actingHandle);
73
+ if (!getPin.ok)
74
+ return { kind: "denied", message: "Acting handle not bound" };
75
+ const { access, blocked } = args.userId !== null
76
+ ? await checkDocAccess(vctx, args.userId, args.appSlug, args.ownerHandle, args.adminMode, "get-doc")
77
+ : { access: "none", blocked: false };
78
+ const rEnv = await resolveDbAccess({
79
+ vctx,
80
+ ownerHandle: args.ownerHandle,
81
+ appSlug: args.appSlug,
82
+ dbName: args.dbName,
83
+ });
84
+ if (rEnv.isErr() ||
85
+ !(await readAllowed({
86
+ vctx,
87
+ env: rEnv.Ok(),
88
+ caller: { access, authenticated, blocked },
89
+ appSlug: args.appSlug,
90
+ ownerHandle: args.ownerHandle,
91
+ }))) {
92
+ return { kind: "denied", message: "Access denied" };
93
+ }
94
+ const t = vctx.sql.tables.appDocuments;
95
+ const row = await vctx.sql.db
96
+ .select()
97
+ .from(t)
98
+ .where(and(eq(t.ownerHandle, args.ownerHandle), eq(t.appSlug, args.appSlug), eq(t.dbName, args.dbName), eq(t.docId, args.docId)))
99
+ .orderBy(sql `${t.seq} desc`)
100
+ .limit(1)
101
+ .then((r) => r[0]);
102
+ if (!row || row.deleted === 1)
103
+ return { kind: "not-found", docId: args.docId };
104
+ const afbRowG = await resolveAccessBinding(vctx, args.ownerHandle, args.appSlug, args.dbName);
105
+ if (afbRowG?.accessFnCid && access !== "override") {
106
+ const tOutputsG = vctx.sql.tables.accessFnOutputs;
107
+ const docOutput = await vctx.sql.db
108
+ .select({ docId: tOutputsG.docId, output: tOutputsG.output })
109
+ .from(tOutputsG)
110
+ .where(and(eq(tOutputsG.ownerHandle, args.ownerHandle), eq(tOutputsG.appSlug, args.appSlug), eq(tOutputsG.dbName, args.dbName), eq(tOutputsG.docId, args.docId)))
111
+ .limit(1)
112
+ .then((r) => r[0]);
113
+ const decodedDocOutput = docOutput === undefined ? undefined : decodeAccessOutputRow(docOutput);
114
+ const parsed = decodedDocOutput?.descriptor;
115
+ const docChannels = parsed?.channels;
116
+ const docAudienceChannels = parsed === undefined ? [] : audienceChannelsOf(parsed);
117
+ const docInheritChannels = parsed === undefined ? [] : inheritChannelOf(parsed);
118
+ if ((docChannels === undefined || docChannels.length === 0) &&
119
+ docAudienceChannels.length === 0 &&
120
+ docInheritChannels.length === 0) {
121
+ return { kind: "not-found", docId: args.docId };
122
+ }
123
+ const grantOutputs = await vctx.sql.db
124
+ .select({ docId: tOutputsG.docId, output: tOutputsG.output })
125
+ .from(tOutputsG)
126
+ .where(and(eq(tOutputsG.ownerHandle, args.ownerHandle), eq(tOutputsG.appSlug, args.appSlug), eq(tOutputsG.dbName, args.dbName), eq(tOutputsG.hasGrants, 1)));
127
+ const grantRowsExcludingDoc = docOutput === undefined ? grantOutputs : grantOutputs.filter((r) => r.docId !== docOutput.docId);
128
+ const decodedGrantOutputs = decodeAccessOutputRows(grantRowsExcludingDoc);
129
+ const devHandles = authenticated
130
+ ? await resolveDeveloperHandles(vctx, { ownerHandle: args.ownerHandle, appSlug: args.appSlug })
131
+ : [];
132
+ const reduce = newSeededReduce(args.ownerHandle, devHandles);
133
+ if (decodedDocOutput !== undefined && grantOutputs.some((grantOutput) => grantOutput.docId === args.docId)) {
134
+ reduce.addDoc(args.docId, extractContribution(decodedDocOutput.descriptor));
135
+ }
136
+ for (const { row: grantRow, descriptor } of decodedGrantOutputs) {
137
+ reduce.addDoc(grantRow.docId, extractContribution(descriptor));
138
+ }
139
+ const userHandle = args.userId !== null ? (getPin.actingHandle ?? (await resolveActiveHandle(vctx, args.userId)) ?? null) : null;
140
+ const effectiveChannels = userHandle !== null ? reduce.resolveEffectiveChannels(userHandle) : new Set();
141
+ if (parsed !== undefined && docAudienceChannels.length > 0) {
142
+ const subjects = collectAudienceSubjects([parsed]);
143
+ if (userHandle !== null) {
144
+ const membership = await resolveAudienceMembershipFailClosed(vctx, {
145
+ handle: userHandle,
146
+ subjects,
147
+ ownerHandle: args.ownerHandle,
148
+ appSlug: args.appSlug,
149
+ });
150
+ for (const ch of audienceChannelsForReader(membership))
151
+ effectiveChannels.add(ch);
152
+ }
153
+ const publicChannels = await resolvePublicAudienceChannelsFailClosed(vctx, {
154
+ handle: userHandle,
155
+ subjects,
156
+ ownerHandle: args.ownerHandle,
157
+ appSlug: args.appSlug,
158
+ });
159
+ for (const ch of publicChannels)
160
+ effectiveChannels.add(ch);
161
+ }
162
+ if (docInheritChannels.length > 0 && decodedDocOutput !== undefined) {
163
+ const inheritCh = await resolveInheritReadChannels(vctx, {
164
+ ownerHandle: args.ownerHandle,
165
+ appSlug: args.appSlug,
166
+ outputs: [decodedDocOutput],
167
+ userHandle,
168
+ authenticated,
169
+ blocked,
170
+ access,
171
+ devHandles,
172
+ });
173
+ for (const ch of inheritCh)
174
+ effectiveChannels.add(ch);
175
+ }
176
+ const docChannelsWithAudience = [...(docChannels ?? []), ...docAudienceChannels, ...docInheritChannels];
177
+ const hasAccess = docChannelsWithAudience.some((ch) => effectiveChannels.has(ch) || reduce.publicChannels.has(ch));
178
+ if (!hasAccess)
179
+ return { kind: "not-found", docId: args.docId };
180
+ }
181
+ const doc = mintFilesUrls(row.data, {
182
+ ownerHandle: args.ownerHandle,
183
+ appSlug: args.appSlug,
184
+ dbName: args.dbName,
185
+ docId: row.docId,
186
+ svc: vctx.params.vibes.svc,
187
+ });
188
+ return { kind: "ok", docId: row.docId, doc };
189
+ }
67
190
  export const getDocEvento = {
68
191
  hash: "get-doc",
69
192
  validate: unwrapMsgBase(async (msg) => {
@@ -76,160 +199,122 @@ export const getDocEvento = {
76
199
  handle: optAuth(async (ctx) => {
77
200
  const req = ctx.validated.payload;
78
201
  const vctx = ctx.ctx.getOrThrow("vibesApiCtx");
79
- const getPin = await validateReadActingPin(vctx, req._auth?.verifiedAuth.claims.userId ?? null, req.actingHandle);
80
- if (!getPin.ok) {
81
- await ctx.send.send(ctx, {
82
- type: "vibes.diy.res-error",
83
- error: { message: "Acting handle not bound" },
84
- });
85
- return Result.Ok(EventoResult.Continue);
86
- }
87
- const reqAdmin = req.adminMode === true;
88
- const { access, blocked } = req._auth
89
- ? await checkDocAccess(vctx, req._auth.verifiedAuth.claims.userId, req.appSlug, req.ownerHandle, connectionAdminMode(ctx) || reqAdmin, "get-doc")
90
- : { access: "none", blocked: false };
91
- const rEnv = await resolveDbAccess({
92
- vctx,
202
+ const answer = await getDocForCaller(vctx, {
203
+ userId: req._auth?.verifiedAuth.claims.userId ?? null,
93
204
  ownerHandle: req.ownerHandle,
94
205
  appSlug: req.appSlug,
95
206
  dbName: req.dbName,
207
+ docId: req.docId,
208
+ adminMode: connectionAdminMode(ctx) || req.adminMode === true,
209
+ ...(req.actingHandle === undefined ? {} : { actingHandle: req.actingHandle }),
96
210
  });
97
- if (rEnv.isErr() ||
98
- !(await readAllowed({
99
- vctx,
100
- env: rEnv.Ok(),
101
- caller: { access, authenticated: req._auth !== undefined, blocked },
102
- appSlug: req.appSlug,
103
- ownerHandle: req.ownerHandle,
104
- }))) {
211
+ if (answer.kind === "denied") {
105
212
  await ctx.send.send(ctx, {
106
213
  type: "vibes.diy.res-error",
107
- error: { message: "Access denied" },
214
+ error: { message: answer.message },
108
215
  });
109
216
  return Result.Ok(EventoResult.Continue);
110
217
  }
111
- const t = vctx.sql.tables.appDocuments;
112
- const row = await vctx.sql.db
113
- .select()
114
- .from(t)
115
- .where(and(eq(t.ownerHandle, req.ownerHandle), eq(t.appSlug, req.appSlug), eq(t.dbName, req.dbName), eq(t.docId, req.docId)))
116
- .orderBy(sql `${t.seq} desc`)
117
- .limit(1)
118
- .then((r) => r[0]);
119
- if (!row || row.deleted === 1) {
218
+ if (answer.kind === "not-found") {
120
219
  await ctx.send.send(ctx, {
121
220
  type: "vibes.diy.res-get-doc",
122
221
  status: "not-found",
123
- id: req.docId,
222
+ id: answer.docId,
124
223
  });
125
224
  return Result.Ok(EventoResult.Continue);
126
225
  }
127
- const afbRowG = await resolveAccessBinding(vctx, req.ownerHandle, req.appSlug, req.dbName);
128
- if (afbRowG?.accessFnCid && access !== "override") {
129
- const tOutputsG = vctx.sql.tables.accessFnOutputs;
130
- const docOutput = await vctx.sql.db
131
- .select({ docId: tOutputsG.docId, output: tOutputsG.output })
132
- .from(tOutputsG)
133
- .where(and(eq(tOutputsG.ownerHandle, req.ownerHandle), eq(tOutputsG.appSlug, req.appSlug), eq(tOutputsG.dbName, req.dbName), eq(tOutputsG.docId, req.docId)))
134
- .limit(1)
135
- .then((r) => r[0]);
136
- const decodedDocOutput = docOutput === undefined ? undefined : decodeAccessOutputRow(docOutput);
137
- const parsed = decodedDocOutput?.descriptor;
138
- const docChannels = parsed?.channels;
139
- const docAudienceChannels = parsed === undefined ? [] : audienceChannelsOf(parsed);
140
- const docInheritChannels = parsed === undefined ? [] : inheritChannelOf(parsed);
141
- if ((docChannels === undefined || docChannels.length === 0) &&
142
- docAudienceChannels.length === 0 &&
143
- docInheritChannels.length === 0) {
144
- await ctx.send.send(ctx, {
145
- type: "vibes.diy.res-get-doc",
146
- status: "not-found",
147
- id: req.docId,
148
- });
149
- return Result.Ok(EventoResult.Continue);
150
- }
151
- const grantOutputs = await vctx.sql.db
152
- .select({ docId: tOutputsG.docId, output: tOutputsG.output })
153
- .from(tOutputsG)
154
- .where(and(eq(tOutputsG.ownerHandle, req.ownerHandle), eq(tOutputsG.appSlug, req.appSlug), eq(tOutputsG.dbName, req.dbName), eq(tOutputsG.hasGrants, 1)));
155
- const grantRowsExcludingDoc = docOutput === undefined ? grantOutputs : grantOutputs.filter((r) => r.docId !== docOutput.docId);
156
- const decodedGrantOutputs = decodeAccessOutputRows(grantRowsExcludingDoc);
157
- const devHandles = req._auth !== undefined
158
- ? await resolveDeveloperHandles(vctx, { ownerHandle: req.ownerHandle, appSlug: req.appSlug })
159
- : [];
160
- const reduce = newSeededReduce(req.ownerHandle, devHandles);
161
- if (decodedDocOutput !== undefined && grantOutputs.some((grantOutput) => grantOutput.docId === req.docId)) {
162
- reduce.addDoc(req.docId, extractContribution(decodedDocOutput.descriptor));
163
- }
164
- for (const { row, descriptor } of decodedGrantOutputs) {
165
- reduce.addDoc(row.docId, extractContribution(descriptor));
166
- }
167
- const userHandle = req._auth
168
- ? (getPin.actingHandle ?? (await resolveActiveHandle(vctx, req._auth.verifiedAuth.claims.userId)) ?? null)
169
- : null;
170
- const effectiveChannels = userHandle !== null ? reduce.resolveEffectiveChannels(userHandle) : new Set();
171
- if (parsed !== undefined && docAudienceChannels.length > 0) {
172
- const subjects = collectAudienceSubjects([parsed]);
173
- if (userHandle !== null) {
174
- const membership = await resolveAudienceMembershipFailClosed(vctx, {
175
- handle: userHandle,
176
- subjects,
177
- ownerHandle: req.ownerHandle,
178
- appSlug: req.appSlug,
179
- });
180
- for (const ch of audienceChannelsForReader(membership))
181
- effectiveChannels.add(ch);
182
- }
183
- const publicChannels = await resolvePublicAudienceChannelsFailClosed(vctx, {
184
- handle: userHandle,
185
- subjects,
186
- ownerHandle: req.ownerHandle,
187
- appSlug: req.appSlug,
188
- });
189
- for (const ch of publicChannels)
190
- effectiveChannels.add(ch);
191
- }
192
- if (docInheritChannels.length > 0 && decodedDocOutput !== undefined) {
193
- const inheritCh = await resolveInheritReadChannels(vctx, {
194
- ownerHandle: req.ownerHandle,
195
- appSlug: req.appSlug,
196
- outputs: [decodedDocOutput],
197
- userHandle,
198
- authenticated: req._auth !== undefined,
199
- blocked,
200
- access,
201
- devHandles,
202
- });
203
- for (const ch of inheritCh)
204
- effectiveChannels.add(ch);
205
- }
206
- const docChannelsWithAudience = [...(docChannels ?? []), ...docAudienceChannels, ...docInheritChannels];
207
- const hasAccess = docChannelsWithAudience.some((ch) => effectiveChannels.has(ch) || reduce.publicChannels.has(ch));
208
- if (!hasAccess) {
209
- await ctx.send.send(ctx, {
210
- type: "vibes.diy.res-get-doc",
211
- status: "not-found",
212
- id: req.docId,
213
- });
214
- return Result.Ok(EventoResult.Continue);
215
- }
216
- }
217
- const doc = mintFilesUrls(row.data, {
218
- ownerHandle: req.ownerHandle,
219
- appSlug: req.appSlug,
220
- dbName: req.dbName,
221
- docId: row.docId,
222
- svc: vctx.params.vibes.svc,
223
- });
224
226
  await ctx.send.send(ctx, {
225
227
  type: "vibes.diy.res-get-doc",
226
228
  status: "ok",
227
- id: row.docId,
228
- doc,
229
+ id: answer.docId,
230
+ doc: answer.doc,
229
231
  });
230
232
  return Result.Ok(EventoResult.Continue);
231
233
  }),
232
234
  };
235
+ export async function queryDocsForCaller(vctx, args) {
236
+ if (addressesDirectChannel(args.ownerHandle))
237
+ return { kind: "denied", message: "Access denied" };
238
+ const authenticated = args.userId !== null;
239
+ const queryPin = await validateReadActingPin(vctx, args.userId, args.actingHandle);
240
+ if (!queryPin.ok)
241
+ return { kind: "denied", message: "Acting handle not bound" };
242
+ const afbRowQ = await resolveAccessBinding(vctx, args.ownerHandle, args.appSlug, args.dbName);
243
+ const { access, blocked = false } = args.userId !== null
244
+ ? await checkDocAccess(vctx, args.userId, args.appSlug, args.ownerHandle, args.adminMode, "query-docs")
245
+ : { access: "none", blocked: false };
246
+ const rEnv = await resolveDbAccess({
247
+ vctx,
248
+ ownerHandle: args.ownerHandle,
249
+ appSlug: args.appSlug,
250
+ dbName: args.dbName,
251
+ });
252
+ if (rEnv.isErr() ||
253
+ !(await readAllowed({
254
+ vctx,
255
+ env: rEnv.Ok(),
256
+ caller: { access, authenticated, blocked },
257
+ appSlug: args.appSlug,
258
+ ownerHandle: args.ownerHandle,
259
+ }))) {
260
+ return { kind: "denied", message: "Access denied" };
261
+ }
262
+ const pageReq = resolveQueryPage(args);
263
+ const rows = pageReq.paged
264
+ ? await loadHeadDocsPage(vctx, { ownerHandle: args.ownerHandle, appSlug: args.appSlug, dbName: args.dbName }, { limit: pageReq.limit, ...(pageReq.after !== undefined ? { after: pageReq.after } : {}) })
265
+ : await loadHeadDocs(vctx, { ownerHandle: args.ownerHandle, appSlug: args.appSlug, dbName: args.dbName });
266
+ const next = pageReq.paged && rows.length === pageReq.limit ? rows[rows.length - 1].docId : undefined;
267
+ const docs = [];
268
+ for (const row of rows) {
269
+ const doc = mintFilesUrls(row.data, {
270
+ ownerHandle: args.ownerHandle,
271
+ appSlug: args.appSlug,
272
+ dbName: args.dbName,
273
+ docId: row.docId,
274
+ svc: vctx.params.vibes.svc,
275
+ });
276
+ docs.push({
277
+ _id: row.docId,
278
+ ...doc,
279
+ });
280
+ }
281
+ let channelFilteredDocs = docs;
282
+ if (afbRowQ?.accessFnCid) {
283
+ const paged = pageReq.paged
284
+ ? await readPagedOutputInputs(vctx, { ownerHandle: args.ownerHandle, appSlug: args.appSlug, dbName: args.dbName }, { pageDocIds: rows.map((r) => r.docId), wantDigest: access !== "override" })
285
+ : undefined;
286
+ const tOutputsQ = vctx.sql.tables.accessFnOutputs;
287
+ const decodedOutputs = paged?.decodedOutputs ??
288
+ decodeAccessOutputRows(await vctx.sql.db
289
+ .select({ docId: tOutputsQ.docId, output: tOutputsQ.output })
290
+ .from(tOutputsQ)
291
+ .where(and(eq(tOutputsQ.ownerHandle, args.ownerHandle), eq(tOutputsQ.appSlug, args.appSlug), eq(tOutputsQ.dbName, args.dbName))));
292
+ if (paged === undefined)
293
+ await runQueryReadInterleaveHookForTests();
294
+ const { userHandle, effectiveChannels, publicChannels } = await resolveViewerChannels(vctx, {
295
+ ownerHandle: args.ownerHandle,
296
+ appSlug: args.appSlug,
297
+ dbName: args.dbName,
298
+ userId: args.userId,
299
+ blocked,
300
+ access,
301
+ decodedOutputs,
302
+ derivation: "readable",
303
+ ...(queryPin.actingHandle !== undefined ? { actingHandle: queryPin.actingHandle } : {}),
304
+ ...(paged !== undefined ? { boundedInputs: paged.boundedInputs } : {}),
305
+ });
306
+ channelFilteredDocs = filterDocsByChannel({
307
+ docs,
308
+ outputRows: decodedOutputs,
309
+ userHandle,
310
+ effectiveChannels,
311
+ publicChannels,
312
+ adminOverride: access === "override",
313
+ });
314
+ }
315
+ const filteredDocs = applyQueryFilter(channelFilteredDocs, args.filter);
316
+ return { kind: "ok", docs: filteredDocs, ...(next !== undefined ? { next } : {}) };
317
+ }
233
318
  export const queryDocsEvento = {
234
319
  hash: "query-docs",
235
320
  validate: unwrapMsgBase(async (msg) => {
@@ -242,98 +327,29 @@ export const queryDocsEvento = {
242
327
  handle: optAuth(async (ctx) => {
243
328
  const req = ctx.validated.payload;
244
329
  const vctx = ctx.ctx.getOrThrow("vibesApiCtx");
245
- const queryPin = await validateReadActingPin(vctx, req._auth?.verifiedAuth.claims.userId ?? null, req.actingHandle);
246
- if (!queryPin.ok) {
247
- await ctx.send.send(ctx, {
248
- type: "vibes.diy.res-error",
249
- error: { message: "Acting handle not bound" },
250
- });
251
- return Result.Ok(EventoResult.Continue);
252
- }
253
- const afbRowQ = await resolveAccessBinding(vctx, req.ownerHandle, req.appSlug, req.dbName);
254
- const reqAdmin = req.adminMode === true;
255
- const { access, blocked = false } = req._auth
256
- ? await checkDocAccess(vctx, req._auth.verifiedAuth.claims.userId, req.appSlug, req.ownerHandle, connectionAdminMode(ctx) || reqAdmin, "query-docs")
257
- : { access: "none", blocked: false };
258
- const rEnv = await resolveDbAccess({
259
- vctx,
330
+ const answer = await queryDocsForCaller(vctx, {
331
+ userId: req._auth?.verifiedAuth.claims.userId ?? null,
260
332
  ownerHandle: req.ownerHandle,
261
333
  appSlug: req.appSlug,
262
334
  dbName: req.dbName,
335
+ adminMode: connectionAdminMode(ctx) || req.adminMode === true,
336
+ ...(req.actingHandle === undefined ? {} : { actingHandle: req.actingHandle }),
337
+ ...(req.filter === undefined ? {} : { filter: req.filter }),
338
+ ...(req.limit === undefined ? {} : { limit: req.limit }),
339
+ ...(req.after === undefined ? {} : { after: req.after }),
263
340
  });
264
- if (rEnv.isErr() ||
265
- !(await readAllowed({
266
- vctx,
267
- env: rEnv.Ok(),
268
- caller: { access, authenticated: req._auth !== undefined, blocked },
269
- appSlug: req.appSlug,
270
- ownerHandle: req.ownerHandle,
271
- }))) {
341
+ if (answer.kind === "denied") {
272
342
  await ctx.send.send(ctx, {
273
343
  type: "vibes.diy.res-error",
274
- error: { message: "Access denied" },
344
+ error: { message: answer.message },
275
345
  });
276
346
  return Result.Ok(EventoResult.Continue);
277
347
  }
278
- const pageReq = resolveQueryPage(req);
279
- const rows = pageReq.paged
280
- ? await loadHeadDocsPage(vctx, { ownerHandle: req.ownerHandle, appSlug: req.appSlug, dbName: req.dbName }, { limit: pageReq.limit, ...(pageReq.after !== undefined ? { after: pageReq.after } : {}) })
281
- : await loadHeadDocs(vctx, { ownerHandle: req.ownerHandle, appSlug: req.appSlug, dbName: req.dbName });
282
- const next = pageReq.paged && rows.length === pageReq.limit ? rows[rows.length - 1].docId : undefined;
283
- const docs = [];
284
- for (const row of rows) {
285
- const doc = mintFilesUrls(row.data, {
286
- ownerHandle: req.ownerHandle,
287
- appSlug: req.appSlug,
288
- dbName: req.dbName,
289
- docId: row.docId,
290
- svc: vctx.params.vibes.svc,
291
- });
292
- docs.push({
293
- _id: row.docId,
294
- ...doc,
295
- });
296
- }
297
- let channelFilteredDocs = docs;
298
- if (afbRowQ?.accessFnCid) {
299
- const paged = pageReq.paged
300
- ? await readPagedOutputInputs(vctx, { ownerHandle: req.ownerHandle, appSlug: req.appSlug, dbName: req.dbName }, { pageDocIds: rows.map((r) => r.docId), wantDigest: access !== "override" })
301
- : undefined;
302
- const tOutputsQ = vctx.sql.tables.accessFnOutputs;
303
- const decodedOutputs = paged?.decodedOutputs ??
304
- decodeAccessOutputRows(await vctx.sql.db
305
- .select({ docId: tOutputsQ.docId, output: tOutputsQ.output })
306
- .from(tOutputsQ)
307
- .where(and(eq(tOutputsQ.ownerHandle, req.ownerHandle), eq(tOutputsQ.appSlug, req.appSlug), eq(tOutputsQ.dbName, req.dbName))));
308
- if (paged === undefined)
309
- await runQueryReadInterleaveHookForTests();
310
- const { userHandle, effectiveChannels, publicChannels } = await resolveViewerChannels(vctx, {
311
- ownerHandle: req.ownerHandle,
312
- appSlug: req.appSlug,
313
- dbName: req.dbName,
314
- userId: req._auth?.verifiedAuth.claims.userId ?? null,
315
- blocked,
316
- access,
317
- decodedOutputs,
318
- derivation: "readable",
319
- ...(queryPin.actingHandle !== undefined ? { actingHandle: queryPin.actingHandle } : {}),
320
- ...(paged !== undefined ? { boundedInputs: paged.boundedInputs } : {}),
321
- });
322
- channelFilteredDocs = filterDocsByChannel({
323
- docs,
324
- outputRows: decodedOutputs,
325
- userHandle,
326
- effectiveChannels,
327
- publicChannels,
328
- adminOverride: access === "override",
329
- });
330
- }
331
- const filteredDocs = applyQueryFilter(channelFilteredDocs, req.filter);
332
348
  await ctx.send.send(ctx, {
333
349
  type: "vibes.diy.res-query-docs",
334
350
  status: "ok",
335
- docs: filteredDocs,
336
- ...(next !== undefined ? { next } : {}),
351
+ docs: [...answer.docs],
352
+ ...(answer.next !== undefined ? { next: answer.next } : {}),
337
353
  });
338
354
  return Result.Ok(EventoResult.Continue);
339
355
  }),
@@ -568,6 +584,19 @@ export const subscribeViewerGrantsEvento = {
568
584
  return Result.Ok(EventoResult.Continue);
569
585
  }),
570
586
  };
587
+ export async function listDbNamesForCaller(vctx, args) {
588
+ if (addressesDirectChannel(args.ownerHandle))
589
+ return { kind: "denied", message: "Access denied" };
590
+ const { access, isOwner } = await checkDocAccess(vctx, args.userId, args.appSlug, args.ownerHandle, args.adminMode, "list-db-names");
591
+ if (isOwner === false && access !== "override")
592
+ return { kind: "denied", message: "Access denied" };
593
+ const t = vctx.sql.tables.appDocuments;
594
+ const rows = await vctx.sql.db
595
+ .selectDistinct({ dbName: t.dbName })
596
+ .from(t)
597
+ .where(and(eq(t.ownerHandle, args.ownerHandle), eq(t.appSlug, args.appSlug)));
598
+ return { kind: "ok", dbNames: rows.map((r) => r.dbName) };
599
+ }
571
600
  export const listDbNamesEvento = {
572
601
  hash: "list-db-names",
573
602
  validate: unwrapMsgBase(async (msg) => {
@@ -580,21 +609,20 @@ export const listDbNamesEvento = {
580
609
  handle: checkAuth(async (ctx) => {
581
610
  const req = ctx.validated.payload;
582
611
  const vctx = ctx.ctx.getOrThrow("vibesApiCtx");
583
- const userId = req._auth.verifiedAuth.claims.userId;
584
- const { access, isOwner } = await checkDocAccess(vctx, userId, req.appSlug, req.ownerHandle, connectionAdminMode(ctx) || req.adminMode === true, "list-db-names");
585
- if (isOwner === false && access !== "override") {
586
- await ctx.send.send(ctx, { type: "vibes.diy.res-error", error: { message: "Access denied" } });
612
+ const answer = await listDbNamesForCaller(vctx, {
613
+ userId: req._auth.verifiedAuth.claims.userId,
614
+ ownerHandle: req.ownerHandle,
615
+ appSlug: req.appSlug,
616
+ adminMode: connectionAdminMode(ctx) || req.adminMode === true,
617
+ });
618
+ if (answer.kind === "denied") {
619
+ await ctx.send.send(ctx, { type: "vibes.diy.res-error", error: { message: answer.message } });
587
620
  return Result.Ok(EventoResult.Continue);
588
621
  }
589
- const t = vctx.sql.tables.appDocuments;
590
- const rows = await vctx.sql.db
591
- .selectDistinct({ dbName: t.dbName })
592
- .from(t)
593
- .where(and(eq(t.ownerHandle, req.ownerHandle), eq(t.appSlug, req.appSlug)));
594
622
  await ctx.send.send(ctx, {
595
623
  type: "vibes.diy.res-list-db-names",
596
624
  status: "ok",
597
- dbNames: rows.map((r) => r.dbName),
625
+ dbNames: [...answer.dbNames],
598
626
  });
599
627
  return Result.Ok(EventoResult.Continue);
600
628
  }),