@vibes.diy/api-svc 2.4.7 → 2.4.10
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/cf-serve.js +16 -0
- package/cf-serve.js.map +1 -1
- package/create-handler.d.ts +10 -1
- package/create-handler.js +1 -0
- package/create-handler.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/intern/prompt-assembly.d.ts +21 -0
- package/intern/prompt-assembly.js +194 -0
- package/intern/prompt-assembly.js.map +1 -0
- package/intern/prompt-asset-fetch.d.ts +6 -0
- package/intern/prompt-asset-fetch.js +18 -0
- package/intern/prompt-asset-fetch.js.map +1 -0
- package/intern/prompt-streaming.d.ts +48 -0
- package/intern/prompt-streaming.js +139 -0
- package/intern/prompt-streaming.js.map +1 -0
- package/package.json +11 -11
- package/public/access-function.d.ts +1 -0
- package/public/access-function.js +32 -0
- package/public/access-function.js.map +1 -1
- package/public/app-documents.js +316 -17
- package/public/app-documents.js.map +1 -1
- package/public/channel-read-filter.d.ts +9 -0
- package/public/channel-read-filter.js +22 -0
- package/public/channel-read-filter.js.map +1 -0
- package/public/ensure-app-slug-item.js +226 -1
- package/public/ensure-app-slug-item.js.map +1 -1
- package/public/grant-reduce.d.ts +25 -0
- package/public/grant-reduce.js +130 -0
- package/public/grant-reduce.js.map +1 -0
- package/public/prompt-chat-section.d.ts +11 -70
- package/public/prompt-chat-section.js +12 -343
- package/public/prompt-chat-section.js.map +1 -1
- package/public/report-top-vibes-by-members.js +9 -2
- package/public/report-top-vibes-by-members.js.map +1 -1
- package/types.d.ts +15 -1
- package/types.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/api-svc",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,18 +25,18 @@
|
|
|
25
25
|
"@libsql/client": "~0.17.3",
|
|
26
26
|
"@neondatabase/serverless": "~1.1.0",
|
|
27
27
|
"@noble/hashes": "~2.2.0",
|
|
28
|
-
"@vibes.diy/api-impl": "2.4.
|
|
29
|
-
"@vibes.diy/api-pkg": "2.4.
|
|
30
|
-
"@vibes.diy/api-sql": "2.4.
|
|
31
|
-
"@vibes.diy/api-types": "2.4.
|
|
32
|
-
"@vibes.diy/call-ai-v2": "2.4.
|
|
33
|
-
"@vibes.diy/prompts": "2.4.
|
|
34
|
-
"@vibes.diy/use-vibes-base": "2.4.
|
|
35
|
-
"@vibes.diy/vibe-db-explorer": "2.4.
|
|
36
|
-
"@vibes.diy/vibe-types": "2.4.
|
|
28
|
+
"@vibes.diy/api-impl": "2.4.10",
|
|
29
|
+
"@vibes.diy/api-pkg": "2.4.10",
|
|
30
|
+
"@vibes.diy/api-sql": "2.4.10",
|
|
31
|
+
"@vibes.diy/api-types": "2.4.10",
|
|
32
|
+
"@vibes.diy/call-ai-v2": "2.4.10",
|
|
33
|
+
"@vibes.diy/prompts": "2.4.10",
|
|
34
|
+
"@vibes.diy/use-vibes-base": "2.4.10",
|
|
35
|
+
"@vibes.diy/vibe-db-explorer": "2.4.10",
|
|
36
|
+
"@vibes.diy/vibe-types": "2.4.10",
|
|
37
37
|
"acorn": "~8.16.0",
|
|
38
38
|
"arktype": "~2.2.0",
|
|
39
|
-
"call-ai": "2.4.
|
|
39
|
+
"call-ai": "2.4.10",
|
|
40
40
|
"cookie": "~1.1.1",
|
|
41
41
|
"drizzle-orm": "~0.45.2",
|
|
42
42
|
"jose": "~6.2.2",
|
|
@@ -2,6 +2,7 @@ import type { AccessDescriptor, AccessFunction, Helpers, UserContext } from "@vi
|
|
|
2
2
|
export type { AccessDescriptor, AccessFunction, Helpers, UserContext };
|
|
3
3
|
export declare function enforceAllowAnonymous(result: AccessDescriptor, user: UserContext | null): void;
|
|
4
4
|
export declare function makeHelpers(user: UserContext | null): Helpers;
|
|
5
|
+
export declare function extractExportSource(fullSource: string, bindingDbName: string): string | undefined;
|
|
5
6
|
export declare class ForbiddenError extends Error {
|
|
6
7
|
readonly forbidden: string;
|
|
7
8
|
constructor(reason: string);
|
|
@@ -17,6 +17,38 @@ export function makeHelpers(user) {
|
|
|
17
17
|
},
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
+
export function extractExportSource(fullSource, bindingDbName) {
|
|
21
|
+
let pattern;
|
|
22
|
+
if (bindingDbName === "*") {
|
|
23
|
+
pattern = /export\s+default\s+(?:function\s*(?:\w+\s*)?\([^)]*\)\s*\{|\([^)]*\)\s*=>\s*\{|\w+\s*=>\s*\{)/;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
pattern = new RegExp(`export\\s+function\\s+${bindingDbName}\\s*\\([^)]*\\)\\s*\\{`);
|
|
27
|
+
}
|
|
28
|
+
const match = fullSource.match(pattern);
|
|
29
|
+
if (!match)
|
|
30
|
+
return undefined;
|
|
31
|
+
const start = match.index;
|
|
32
|
+
if (start === undefined)
|
|
33
|
+
return undefined;
|
|
34
|
+
let depth = 0;
|
|
35
|
+
let end = start;
|
|
36
|
+
for (let i = start; i < fullSource.length; i++) {
|
|
37
|
+
if (fullSource[i] === "{")
|
|
38
|
+
depth++;
|
|
39
|
+
if (fullSource[i] === "}") {
|
|
40
|
+
depth--;
|
|
41
|
+
if (depth === 0) {
|
|
42
|
+
end = i + 1;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
let extracted = fullSource.slice(start, end).replace(/^export\s+/, "");
|
|
48
|
+
if (bindingDbName === "*")
|
|
49
|
+
extracted = extracted.replace(/^default\s+/, "");
|
|
50
|
+
return extracted;
|
|
51
|
+
}
|
|
20
52
|
export class ForbiddenError extends Error {
|
|
21
53
|
forbidden;
|
|
22
54
|
constructor(reason) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-function.js","sourceRoot":"","sources":["../../jsr/public/access-function.ts"],"names":[],"mappings":"AA4BA,MAAM,UAAU,qBAAqB,CAAC,MAAwB,EAAE,IAAwB;IACtF,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;QAC5C,MAAM,IAAI,cAAc,CAAC,yBAAyB,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAQD,MAAM,UAAU,WAAW,CAAC,IAAwB;IAClD,OAAO;QACL,aAAa,CAAC,SAAiB;YAC7B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAClB,MAAM,IAAI,cAAc,CAAC,mBAAmB,SAAS,EAAE,CAAC,CAAC;YAC3D,CAAC;QAGH,CAAC;QACD,WAAW,CAAC,QAAgB;YAC1B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAClB,MAAM,IAAI,cAAc,CAAC,gBAAgB,QAAQ,EAAE,CAAC,CAAC;YACvD,CAAC;QAGH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,SAAS,CAAS;IAE3B,YAAY,MAAc;QACxB,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC;IAC1B,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"access-function.js","sourceRoot":"","sources":["../../jsr/public/access-function.ts"],"names":[],"mappings":"AA4BA,MAAM,UAAU,qBAAqB,CAAC,MAAwB,EAAE,IAAwB;IACtF,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;QAC5C,MAAM,IAAI,cAAc,CAAC,yBAAyB,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAQD,MAAM,UAAU,WAAW,CAAC,IAAwB;IAClD,OAAO;QACL,aAAa,CAAC,SAAiB;YAC7B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAClB,MAAM,IAAI,cAAc,CAAC,mBAAmB,SAAS,EAAE,CAAC,CAAC;YAC3D,CAAC;QAGH,CAAC;QACD,WAAW,CAAC,QAAgB;YAC1B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAClB,MAAM,IAAI,cAAc,CAAC,gBAAgB,QAAQ,EAAE,CAAC,CAAC;YACvD,CAAC;QAGH,CAAC;KACF,CAAC;AACJ,CAAC;AAQD,MAAM,UAAU,mBAAmB,CAAC,UAAkB,EAAE,aAAqB;IAC3E,IAAI,OAAe,CAAC;IACpB,IAAI,aAAa,KAAK,GAAG,EAAE,CAAC;QAC1B,OAAO,GAAG,+FAA+F,CAAC;IAC5G,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,IAAI,MAAM,CAAC,yBAAyB,aAAa,wBAAwB,CAAC,CAAC;IACvF,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAC1B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,GAAG,GAAG,KAAK,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/C,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;QACnC,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YAC1B,KAAK,EAAE,CAAC;YACR,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChB,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;gBACZ,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IACvE,IAAI,aAAa,KAAK,GAAG;QAAE,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IAC5E,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,SAAS,CAAS;IAE3B,YAAY,MAAc;QACxB,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC;IAC1B,CAAC;CACF"}
|
package/public/app-documents.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { Result, Option, EventoResult } from "@adviser/cement";
|
|
1
|
+
import { Result, Option, EventoResult, exception2Result } from "@adviser/cement";
|
|
2
2
|
import { reqPutDoc, reqGetDoc, reqQueryDocs, reqDeleteDoc, reqSubscribeDocs, reqListDbNames, reqListDmThreads, reqMarkDmRead, COMMENTS_DB_NAME, isDirectChannel, directChannelParticipants, } from "@vibes.diy/api-types";
|
|
3
3
|
import { unwrapMsgBase } from "../unwrap-msg-base.js";
|
|
4
4
|
import { checkAuth, optAuth } from "../check-auth.js";
|
|
5
|
-
import { eq, and, sql, inArray } from "drizzle-orm";
|
|
5
|
+
import { eq, and, sql, inArray, desc } from "drizzle-orm";
|
|
6
6
|
import { max } from "drizzle-orm/sql";
|
|
7
7
|
import { type } from "arktype";
|
|
8
8
|
import { checkDocAccess, canRead, isPublicReadable } from "./access-helpers.js";
|
|
9
|
+
import { enforceAllowAnonymous, ForbiddenError, extractExportSource } from "./access-function.js";
|
|
9
10
|
import { aclAllows, resolveDbAcl, checkDirectChannelAccess } from "./db-acl-resolver.js";
|
|
11
|
+
import { GrantReduce, extractContribution } from "./grant-reduce.js";
|
|
12
|
+
import { filterDocsByChannel } from "./channel-read-filter.js";
|
|
10
13
|
import { mintFilesUrls, isFileMeta } from "./files-url-mint.js";
|
|
11
14
|
async function readAllowed(vctx, acl, access, appSlug, ownerHandle) {
|
|
12
15
|
if (acl?.read !== undefined)
|
|
@@ -54,18 +57,22 @@ export const putDocEvento = {
|
|
|
54
57
|
}
|
|
55
58
|
return Result.Ok(Option.Some({ ...msg, payload: ret }));
|
|
56
59
|
}),
|
|
57
|
-
handle:
|
|
60
|
+
handle: optAuth(async (ctx) => {
|
|
58
61
|
const req = ctx.validated.payload;
|
|
59
62
|
const vctx = ctx.ctx.getOrThrow("vibesApiCtx");
|
|
60
|
-
const userId = req._auth
|
|
63
|
+
const userId = req._auth?.verifiedAuth.claims.userId ?? null;
|
|
61
64
|
if (isDirectChannel(req.ownerHandle)) {
|
|
65
|
+
if (!userId) {
|
|
66
|
+
await ctx.send.send(ctx, { type: "vibes.diy.res-error", error: { message: "Access denied" } });
|
|
67
|
+
return Result.Ok(EventoResult.Continue);
|
|
68
|
+
}
|
|
62
69
|
const rAccess = await checkDirectChannelAccess(vctx, req.ownerHandle, userId);
|
|
63
70
|
if (rAccess.isErr() || !rAccess.Ok()) {
|
|
64
71
|
await ctx.send.send(ctx, { type: "vibes.diy.res-error", error: { message: "Access denied" } });
|
|
65
72
|
return Result.Ok(EventoResult.Continue);
|
|
66
73
|
}
|
|
67
74
|
}
|
|
68
|
-
else {
|
|
75
|
+
else if (userId) {
|
|
69
76
|
const access = await checkDocAccess(vctx, userId, req.appSlug, req.ownerHandle);
|
|
70
77
|
const rAcl = await resolveDbAcl(vctx, req.ownerHandle, req.appSlug, req.dbName);
|
|
71
78
|
if (rAcl.isErr()) {
|
|
@@ -86,8 +93,114 @@ export const putDocEvento = {
|
|
|
86
93
|
});
|
|
87
94
|
return Result.Ok(EventoResult.Continue);
|
|
88
95
|
}
|
|
89
|
-
|
|
96
|
+
let accessResult;
|
|
97
|
+
const tAfb = vctx.sql.tables.accessFunctionBindings;
|
|
98
|
+
const afbRow = await vctx.sql.db
|
|
99
|
+
.select({ accessFnCid: tAfb.accessFnCid, accessFnAssetUri: tAfb.accessFnAssetUri, dbName: tAfb.dbName })
|
|
100
|
+
.from(tAfb)
|
|
101
|
+
.where(and(eq(tAfb.userSlug, req.ownerHandle), eq(tAfb.appSlug, req.appSlug), inArray(tAfb.dbName, [req.dbName, "*"])))
|
|
102
|
+
.orderBy(sql `CASE WHEN ${tAfb.dbName} = ${req.dbName} THEN 0 ELSE 1 END`)
|
|
103
|
+
.limit(1)
|
|
104
|
+
.then((r) => r[0]);
|
|
105
|
+
if (!userId && !afbRow?.accessFnCid) {
|
|
106
|
+
await ctx.send.send(ctx, {
|
|
107
|
+
type: "vibes.diy.res-error",
|
|
108
|
+
error: { message: "Access denied" },
|
|
109
|
+
});
|
|
110
|
+
return Result.Ok(EventoResult.Continue);
|
|
111
|
+
}
|
|
90
112
|
const docId = req.docId ?? vctx.sthis.timeOrderedNextId().str;
|
|
113
|
+
if (afbRow?.accessFnCid && vctx.invokeAccessFn) {
|
|
114
|
+
const fnCid = afbRow.accessFnCid;
|
|
115
|
+
const t_usb = vctx.sql.tables.handleBinding;
|
|
116
|
+
const writerRow = userId
|
|
117
|
+
? await vctx.sql.db
|
|
118
|
+
.select({ handle: t_usb.handle })
|
|
119
|
+
.from(t_usb)
|
|
120
|
+
.where(eq(t_usb.userId, userId))
|
|
121
|
+
.limit(1)
|
|
122
|
+
.then((r) => r[0])
|
|
123
|
+
: undefined;
|
|
124
|
+
const userContext = writerRow?.handle ? { userHandle: writerRow.handle } : null;
|
|
125
|
+
let oldDoc = null;
|
|
126
|
+
if (req.docId) {
|
|
127
|
+
const tDocs = vctx.sql.tables.appDocuments;
|
|
128
|
+
const existing = await vctx.sql.db
|
|
129
|
+
.select({ data: tDocs.data })
|
|
130
|
+
.from(tDocs)
|
|
131
|
+
.where(and(eq(tDocs.ownerHandle, req.ownerHandle), eq(tDocs.appSlug, req.appSlug), eq(tDocs.dbName, req.dbName), eq(tDocs.docId, req.docId)))
|
|
132
|
+
.orderBy(desc(tDocs.seq))
|
|
133
|
+
.limit(1)
|
|
134
|
+
.then((r) => r[0]);
|
|
135
|
+
oldDoc = existing?.data ?? null;
|
|
136
|
+
}
|
|
137
|
+
let accessFnSource;
|
|
138
|
+
if (afbRow.accessFnAssetUri) {
|
|
139
|
+
const rFetch = await vctx.storage.fetch(afbRow.accessFnAssetUri);
|
|
140
|
+
if (rFetch.type === "fetch.ok") {
|
|
141
|
+
const reader = rFetch.data.getReader();
|
|
142
|
+
const chunks = [];
|
|
143
|
+
for (;;) {
|
|
144
|
+
const { done, value } = await reader.read();
|
|
145
|
+
if (done)
|
|
146
|
+
break;
|
|
147
|
+
if (value)
|
|
148
|
+
chunks.push(value);
|
|
149
|
+
}
|
|
150
|
+
const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
151
|
+
const merged = new Uint8Array(totalLength);
|
|
152
|
+
let offset = 0;
|
|
153
|
+
for (const chunk of chunks) {
|
|
154
|
+
merged.set(chunk, offset);
|
|
155
|
+
offset += chunk.length;
|
|
156
|
+
}
|
|
157
|
+
const rawSource = new TextDecoder().decode(merged);
|
|
158
|
+
accessFnSource = extractExportSource(rawSource, afbRow.dbName) ?? rawSource;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const tOutputs = vctx.sql.tables.accessFnOutputs;
|
|
162
|
+
const storedOutputs = await vctx.sql.db
|
|
163
|
+
.select({ docId: tOutputs.docId, output: tOutputs.output })
|
|
164
|
+
.from(tOutputs)
|
|
165
|
+
.where(and(eq(tOutputs.userSlug, req.ownerHandle), eq(tOutputs.appSlug, req.appSlug), eq(tOutputs.dbName, req.dbName), eq(tOutputs.fnCid, fnCid), eq(tOutputs.hasGrants, 1)));
|
|
166
|
+
const reduce = new GrantReduce();
|
|
167
|
+
for (const row of storedOutputs) {
|
|
168
|
+
reduce.addDoc(row.docId, extractContribution(JSON.parse(row.output)));
|
|
169
|
+
}
|
|
170
|
+
const grantState = {
|
|
171
|
+
members: Object.fromEntries(Array.from(reduce.effectiveMembers).map(([k, v]) => [k, Array.from(v)])),
|
|
172
|
+
roleGrants: Object.fromEntries(Array.from(reduce.roleGrants).map(([k, v]) => [k, Array.from(v)])),
|
|
173
|
+
userGrants: Object.fromEntries(Array.from(reduce.userGrants).map(([k, v]) => [k, Array.from(v)])),
|
|
174
|
+
};
|
|
175
|
+
const invokeResult = await vctx.invokeAccessFn({
|
|
176
|
+
cid: fnCid,
|
|
177
|
+
doc: { ...req.doc, _id: docId },
|
|
178
|
+
oldDoc,
|
|
179
|
+
user: userContext,
|
|
180
|
+
source: accessFnSource,
|
|
181
|
+
grantState,
|
|
182
|
+
});
|
|
183
|
+
if ("forbidden" in invokeResult) {
|
|
184
|
+
await ctx.send.send(ctx, {
|
|
185
|
+
type: "vibes.diy.res-error",
|
|
186
|
+
error: { message: invokeResult.forbidden },
|
|
187
|
+
});
|
|
188
|
+
return Result.Ok(EventoResult.Continue);
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
enforceAllowAnonymous(invokeResult, userContext);
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
const reason = err instanceof ForbiddenError ? err.forbidden : String(err);
|
|
195
|
+
await ctx.send.send(ctx, {
|
|
196
|
+
type: "vibes.diy.res-error",
|
|
197
|
+
error: { message: reason },
|
|
198
|
+
});
|
|
199
|
+
return Result.Ok(EventoResult.Continue);
|
|
200
|
+
}
|
|
201
|
+
accessResult = invokeResult;
|
|
202
|
+
}
|
|
203
|
+
const now = new Date().toISOString();
|
|
91
204
|
const dbName = req.dbName;
|
|
92
205
|
const t = vctx.sql.tables.appDocuments;
|
|
93
206
|
const maxSeqResult = await vctx.sql.db
|
|
@@ -102,7 +215,7 @@ export const putDocEvento = {
|
|
|
102
215
|
dbName,
|
|
103
216
|
docId,
|
|
104
217
|
seq: nextSeq,
|
|
105
|
-
userId,
|
|
218
|
+
userId: userId ?? "unknown",
|
|
106
219
|
data: req.doc,
|
|
107
220
|
deleted: 0,
|
|
108
221
|
created: now,
|
|
@@ -122,14 +235,14 @@ export const putDocEvento = {
|
|
|
122
235
|
const senderRow = await vctx.sql.db
|
|
123
236
|
.select({ handle: t_usb.handle })
|
|
124
237
|
.from(t_usb)
|
|
125
|
-
.where(and(eq(t_usb.userId, userId), inArray(t_usb.handle, participants)))
|
|
238
|
+
.where(and(eq(t_usb.userId, userId ?? ""), inArray(t_usb.handle, participants)))
|
|
126
239
|
.then((r) => r[0]);
|
|
127
240
|
const senderUserSlug = senderRow?.handle ?? "";
|
|
128
241
|
const recipientUserSlug = participants.find((h) => h !== senderUserSlug) ?? participants[1];
|
|
129
242
|
await vctx.postQueue({
|
|
130
243
|
payload: {
|
|
131
244
|
type: "vibes.diy.evt-dm-received",
|
|
132
|
-
senderUserId: userId,
|
|
245
|
+
senderUserId: userId ?? "",
|
|
133
246
|
senderUserSlug,
|
|
134
247
|
recipientUserSlug,
|
|
135
248
|
channelUserSlug: req.ownerHandle,
|
|
@@ -160,12 +273,12 @@ export const putDocEvento = {
|
|
|
160
273
|
await vctx.postQueue({
|
|
161
274
|
payload: {
|
|
162
275
|
type: "vibes.diy.evt-comment-posted",
|
|
163
|
-
userId,
|
|
276
|
+
userId: userId ?? "unknown",
|
|
164
277
|
ownerHandle: req.ownerHandle,
|
|
165
278
|
appSlug: req.appSlug,
|
|
166
279
|
docId,
|
|
167
280
|
created: now,
|
|
168
|
-
email: req._auth
|
|
281
|
+
email: req._auth?.verifiedAuth.claims.params.email ?? "unknown",
|
|
169
282
|
},
|
|
170
283
|
tid: "queue-event",
|
|
171
284
|
src: "putDoc",
|
|
@@ -174,9 +287,56 @@ export const putDocEvento = {
|
|
|
174
287
|
});
|
|
175
288
|
}
|
|
176
289
|
if (vctx.notifyDocChanged) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
290
|
+
if (accessResult?.channels?.length) {
|
|
291
|
+
for (const channel of accessResult.channels) {
|
|
292
|
+
vctx
|
|
293
|
+
.notifyDocChanged({ ownerHandle: req.ownerHandle, appSlug: req.appSlug, dbName: channel, docId }, clientWsSend(ctx).connId)
|
|
294
|
+
.catch((e) => console.error("DocNotify channel error:", e));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
vctx
|
|
299
|
+
.notifyDocChanged({ ownerHandle: req.ownerHandle, appSlug: req.appSlug, dbName, docId }, clientWsSend(ctx).connId)
|
|
300
|
+
.catch((e) => console.error("DocNotify error:", e));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (accessResult && !("forbidden" in accessResult) && afbRow?.accessFnCid) {
|
|
304
|
+
const tOutputs = vctx.sql.tables.accessFnOutputs;
|
|
305
|
+
const outputHasGrants = (accessResult.members && Object.keys(accessResult.members).length > 0) ||
|
|
306
|
+
(accessResult.grant?.users && Object.keys(accessResult.grant.users).length > 0) ||
|
|
307
|
+
(accessResult.grant?.roles && Object.keys(accessResult.grant.roles).length > 0) ||
|
|
308
|
+
(accessResult.grant?.public && accessResult.grant.public.length > 0)
|
|
309
|
+
? 1
|
|
310
|
+
: 0;
|
|
311
|
+
const rUpsert = await exception2Result(() => vctx.sql.db
|
|
312
|
+
.insert(tOutputs)
|
|
313
|
+
.values({
|
|
314
|
+
userSlug: req.ownerHandle,
|
|
315
|
+
appSlug: req.appSlug,
|
|
316
|
+
dbName: req.dbName,
|
|
317
|
+
docId,
|
|
318
|
+
fnCid: afbRow.accessFnCid,
|
|
319
|
+
output: JSON.stringify(accessResult),
|
|
320
|
+
hasGrants: outputHasGrants,
|
|
321
|
+
})
|
|
322
|
+
.onConflictDoUpdate({
|
|
323
|
+
target: [tOutputs.userSlug, tOutputs.appSlug, tOutputs.dbName, tOutputs.docId],
|
|
324
|
+
set: {
|
|
325
|
+
fnCid: afbRow.accessFnCid,
|
|
326
|
+
output: JSON.stringify(accessResult),
|
|
327
|
+
hasGrants: outputHasGrants,
|
|
328
|
+
},
|
|
329
|
+
}));
|
|
330
|
+
if (rUpsert.isErr()) {
|
|
331
|
+
console.error("AccessFnOutputs upsert failed:", rUpsert.Err());
|
|
332
|
+
if (outputHasGrants === 1) {
|
|
333
|
+
await ctx.send.send(ctx, {
|
|
334
|
+
type: "vibes.diy.res-error",
|
|
335
|
+
error: { message: "grant storage failed — retry the write" },
|
|
336
|
+
});
|
|
337
|
+
return Result.Ok(EventoResult.Continue);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
180
340
|
}
|
|
181
341
|
await ctx.send.send(ctx, {
|
|
182
342
|
type: "vibes.diy.res-put-doc",
|
|
@@ -225,6 +385,59 @@ export const getDocEvento = {
|
|
|
225
385
|
});
|
|
226
386
|
return Result.Ok(EventoResult.Continue);
|
|
227
387
|
}
|
|
388
|
+
const tAfbG = vctx.sql.tables.accessFunctionBindings;
|
|
389
|
+
const afbRowG = await vctx.sql.db
|
|
390
|
+
.select({ accessFnCid: tAfbG.accessFnCid })
|
|
391
|
+
.from(tAfbG)
|
|
392
|
+
.where(and(eq(tAfbG.userSlug, req.ownerHandle), eq(tAfbG.appSlug, req.appSlug), inArray(tAfbG.dbName, [req.dbName, "*"])))
|
|
393
|
+
.orderBy(sql `CASE WHEN ${tAfbG.dbName} = ${req.dbName} THEN 0 ELSE 1 END`)
|
|
394
|
+
.limit(1)
|
|
395
|
+
.then((r) => r[0]);
|
|
396
|
+
if (afbRowG?.accessFnCid) {
|
|
397
|
+
const tOutputsG = vctx.sql.tables.accessFnOutputs;
|
|
398
|
+
const docOutput = await vctx.sql.db
|
|
399
|
+
.select({ output: tOutputsG.output })
|
|
400
|
+
.from(tOutputsG)
|
|
401
|
+
.where(and(eq(tOutputsG.userSlug, req.ownerHandle), eq(tOutputsG.appSlug, req.appSlug), eq(tOutputsG.dbName, req.dbName), eq(tOutputsG.docId, req.docId), eq(tOutputsG.fnCid, afbRowG.accessFnCid)))
|
|
402
|
+
.limit(1)
|
|
403
|
+
.then((r) => r[0]);
|
|
404
|
+
const parsed = docOutput ? JSON.parse(docOutput.output) : undefined;
|
|
405
|
+
const docChannels = parsed?.channels;
|
|
406
|
+
if (docChannels === undefined || docChannels.length === 0) {
|
|
407
|
+
await ctx.send.send(ctx, {
|
|
408
|
+
type: "vibes.diy.res-get-doc",
|
|
409
|
+
status: "not-found",
|
|
410
|
+
id: req.docId,
|
|
411
|
+
});
|
|
412
|
+
return Result.Ok(EventoResult.Continue);
|
|
413
|
+
}
|
|
414
|
+
const grantOutputs = await vctx.sql.db
|
|
415
|
+
.select({ docId: tOutputsG.docId, output: tOutputsG.output })
|
|
416
|
+
.from(tOutputsG)
|
|
417
|
+
.where(and(eq(tOutputsG.userSlug, req.ownerHandle), eq(tOutputsG.appSlug, req.appSlug), eq(tOutputsG.dbName, req.dbName), eq(tOutputsG.fnCid, afbRowG.accessFnCid), eq(tOutputsG.hasGrants, 1)));
|
|
418
|
+
const reduce = new GrantReduce();
|
|
419
|
+
for (const r of grantOutputs) {
|
|
420
|
+
reduce.addDoc(r.docId, extractContribution(JSON.parse(r.output)));
|
|
421
|
+
}
|
|
422
|
+
const userHandle = req._auth
|
|
423
|
+
? await vctx.sql.db
|
|
424
|
+
.select({ handle: vctx.sql.tables.handleBinding.handle })
|
|
425
|
+
.from(vctx.sql.tables.handleBinding)
|
|
426
|
+
.where(eq(vctx.sql.tables.handleBinding.userId, req._auth.verifiedAuth.claims.userId))
|
|
427
|
+
.limit(1)
|
|
428
|
+
.then((r) => r[0]?.handle ?? null)
|
|
429
|
+
: null;
|
|
430
|
+
const effectiveChannels = userHandle !== null ? reduce.resolveEffectiveChannels(userHandle) : new Set();
|
|
431
|
+
const hasAccess = docChannels.some((ch) => effectiveChannels.has(ch) || reduce.publicChannels.has(ch));
|
|
432
|
+
if (!hasAccess) {
|
|
433
|
+
await ctx.send.send(ctx, {
|
|
434
|
+
type: "vibes.diy.res-get-doc",
|
|
435
|
+
status: "not-found",
|
|
436
|
+
id: req.docId,
|
|
437
|
+
});
|
|
438
|
+
return Result.Ok(EventoResult.Continue);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
228
441
|
const doc = mintFilesUrls(row.data, {
|
|
229
442
|
ownerHandle: req.ownerHandle,
|
|
230
443
|
appSlug: req.appSlug,
|
|
@@ -335,7 +548,44 @@ export const queryDocsEvento = {
|
|
|
335
548
|
...doc,
|
|
336
549
|
});
|
|
337
550
|
}
|
|
338
|
-
const
|
|
551
|
+
const tAfbQ = vctx.sql.tables.accessFunctionBindings;
|
|
552
|
+
const afbRowQ = await vctx.sql.db
|
|
553
|
+
.select({ accessFnCid: tAfbQ.accessFnCid })
|
|
554
|
+
.from(tAfbQ)
|
|
555
|
+
.where(and(eq(tAfbQ.userSlug, req.ownerHandle), eq(tAfbQ.appSlug, req.appSlug), inArray(tAfbQ.dbName, [req.dbName, "*"])))
|
|
556
|
+
.orderBy(sql `CASE WHEN ${tAfbQ.dbName} = ${req.dbName} THEN 0 ELSE 1 END`)
|
|
557
|
+
.limit(1)
|
|
558
|
+
.then((r) => r[0]);
|
|
559
|
+
let channelFilteredDocs = docs;
|
|
560
|
+
if (afbRowQ?.accessFnCid) {
|
|
561
|
+
const tOutputsQ = vctx.sql.tables.accessFnOutputs;
|
|
562
|
+
const allOutputs = await vctx.sql.db
|
|
563
|
+
.select({ docId: tOutputsQ.docId, output: tOutputsQ.output })
|
|
564
|
+
.from(tOutputsQ)
|
|
565
|
+
.where(and(eq(tOutputsQ.userSlug, req.ownerHandle), eq(tOutputsQ.appSlug, req.appSlug), eq(tOutputsQ.dbName, req.dbName), eq(tOutputsQ.fnCid, afbRowQ.accessFnCid)));
|
|
566
|
+
const grantOutputs = allOutputs.filter((r) => {
|
|
567
|
+
const parsed = JSON.parse(r.output);
|
|
568
|
+
return ((parsed.members !== undefined && Object.keys(parsed.members).length > 0) ||
|
|
569
|
+
(parsed.grant?.users !== undefined && Object.keys(parsed.grant.users).length > 0) ||
|
|
570
|
+
(parsed.grant?.roles !== undefined && Object.keys(parsed.grant.roles).length > 0) ||
|
|
571
|
+
(parsed.grant?.public !== undefined && parsed.grant.public.length > 0));
|
|
572
|
+
});
|
|
573
|
+
const reduce = new GrantReduce();
|
|
574
|
+
for (const row of grantOutputs) {
|
|
575
|
+
reduce.addDoc(row.docId, extractContribution(JSON.parse(row.output)));
|
|
576
|
+
}
|
|
577
|
+
const userHandle = req._auth
|
|
578
|
+
? await vctx.sql.db
|
|
579
|
+
.select({ handle: vctx.sql.tables.handleBinding.handle })
|
|
580
|
+
.from(vctx.sql.tables.handleBinding)
|
|
581
|
+
.where(eq(vctx.sql.tables.handleBinding.userId, req._auth.verifiedAuth.claims.userId))
|
|
582
|
+
.limit(1)
|
|
583
|
+
.then((r) => r[0]?.handle ?? null)
|
|
584
|
+
: null;
|
|
585
|
+
const effectiveChannels = userHandle !== null ? reduce.resolveEffectiveChannels(userHandle) : new Set();
|
|
586
|
+
channelFilteredDocs = filterDocsByChannel(docs, allOutputs, userHandle, effectiveChannels, reduce.publicChannels);
|
|
587
|
+
}
|
|
588
|
+
const filteredDocs = applyQueryFilter(channelFilteredDocs, req.filter);
|
|
339
589
|
await ctx.send.send(ctx, {
|
|
340
590
|
type: "vibes.diy.res-query-docs",
|
|
341
591
|
status: "ok",
|
|
@@ -443,9 +693,58 @@ export const subscribeDocsEvento = {
|
|
|
443
693
|
}
|
|
444
694
|
const wsSend = clientWsSend(ctx);
|
|
445
695
|
const subscriptionKey = `${req.ownerHandle}/${req.appSlug}/${req.dbName}`;
|
|
446
|
-
|
|
696
|
+
const tAfbS = vctx.sql.tables.accessFunctionBindings;
|
|
697
|
+
const afbRowS = await vctx.sql.db
|
|
698
|
+
.select({ accessFnCid: tAfbS.accessFnCid })
|
|
699
|
+
.from(tAfbS)
|
|
700
|
+
.where(and(eq(tAfbS.userSlug, req.ownerHandle), eq(tAfbS.appSlug, req.appSlug), inArray(tAfbS.dbName, [req.dbName, "*"])))
|
|
701
|
+
.orderBy(sql `CASE WHEN ${tAfbS.dbName} = ${req.dbName} THEN 0 ELSE 1 END`)
|
|
702
|
+
.limit(1)
|
|
703
|
+
.then((r) => r[0]);
|
|
704
|
+
const channelKeys = [];
|
|
705
|
+
if (afbRowS?.accessFnCid) {
|
|
706
|
+
const tOutputsS = vctx.sql.tables.accessFnOutputs;
|
|
707
|
+
const grantOutputs = await vctx.sql.db
|
|
708
|
+
.select({ docId: tOutputsS.docId, output: tOutputsS.output })
|
|
709
|
+
.from(tOutputsS)
|
|
710
|
+
.where(and(eq(tOutputsS.userSlug, req.ownerHandle), eq(tOutputsS.appSlug, req.appSlug), eq(tOutputsS.dbName, req.dbName), eq(tOutputsS.fnCid, afbRowS.accessFnCid), eq(tOutputsS.hasGrants, 1)));
|
|
711
|
+
const reduce = new GrantReduce();
|
|
712
|
+
for (const row of grantOutputs) {
|
|
713
|
+
reduce.addDoc(row.docId, extractContribution(JSON.parse(row.output)));
|
|
714
|
+
}
|
|
715
|
+
const userHandle = req._auth
|
|
716
|
+
? await vctx.sql.db
|
|
717
|
+
.select({ handle: vctx.sql.tables.handleBinding.handle })
|
|
718
|
+
.from(vctx.sql.tables.handleBinding)
|
|
719
|
+
.where(eq(vctx.sql.tables.handleBinding.userId, req._auth.verifiedAuth.claims.userId))
|
|
720
|
+
.limit(1)
|
|
721
|
+
.then((r) => r[0]?.handle ?? null)
|
|
722
|
+
: null;
|
|
723
|
+
const effectiveChannels = userHandle !== null ? reduce.resolveEffectiveChannels(userHandle) : new Set();
|
|
724
|
+
for (const ch of effectiveChannels) {
|
|
725
|
+
channelKeys.push(`${req.ownerHandle}/${req.appSlug}/${ch}`);
|
|
726
|
+
}
|
|
727
|
+
for (const ch of reduce.publicChannels) {
|
|
728
|
+
channelKeys.push(`${req.ownerHandle}/${req.appSlug}/${ch}`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (channelKeys.length > 0) {
|
|
732
|
+
for (const key of channelKeys) {
|
|
733
|
+
wsSend.subscribedDocKeys.add(key);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
wsSend.subscribedDocKeys.add(subscriptionKey);
|
|
738
|
+
}
|
|
447
739
|
if (vctx.registerDocSubscription) {
|
|
448
|
-
|
|
740
|
+
if (channelKeys.length > 0) {
|
|
741
|
+
for (const key of channelKeys) {
|
|
742
|
+
vctx.registerDocSubscription(key).catch((e) => console.error("DocNotify error:", e));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
vctx.registerDocSubscription(subscriptionKey).catch((e) => console.error("DocNotify error:", e));
|
|
747
|
+
}
|
|
449
748
|
}
|
|
450
749
|
await ctx.send.send(ctx, {
|
|
451
750
|
type: "vibes.diy.res-subscribe-docs",
|