@pithy-sh/support 0.3.3 → 0.4.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/ai/classify.d.ts.map +1 -1
- package/dist/ai/classify.js +3 -3
- package/dist/http/handlers.d.ts.map +1 -1
- package/dist/http/handlers.js +2 -3
- package/dist/http/routes.d.ts +4 -1
- package/dist/http/routes.d.ts.map +1 -1
- package/dist/http/routes.js +60 -12
- package/dist/inbound/handler.d.ts.map +1 -1
- package/dist/inbound/handler.js +0 -1
- package/dist/inbound/ingest.d.ts +0 -2
- package/dist/inbound/ingest.d.ts.map +1 -1
- package/dist/inbound/ingest.js +0 -14
- package/dist/provision/provisionSupport.d.ts +25 -4
- package/dist/provision/provisionSupport.d.ts.map +1 -1
- package/dist/provision/provisionSupport.js +18 -3
- package/dist/reply/send.d.ts +0 -2
- package/dist/reply/send.d.ts.map +1 -1
- package/dist/reply/send.js +2 -15
- package/dist/store/search.d.ts +16 -9
- package/dist/store/search.d.ts.map +1 -1
- package/dist/store/search.js +16 -9
- package/dist/store/searchIndex.d.ts +131 -5
- package/dist/store/searchIndex.d.ts.map +1 -1
- package/dist/store/searchIndex.js +151 -6
- package/dist/store/threads.d.ts.map +1 -1
- package/dist/store/threads.js +3 -2
- package/dist/submission/submit.d.ts +0 -2
- package/dist/submission/submit.d.ts.map +1 -1
- package/dist/submission/submit.js +0 -14
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/package.json +10 -10
- package/src/ai/classify.ts +7 -3
- package/src/http/handlers.ts +2 -3
- package/src/http/routes.ts +96 -19
- package/src/inbound/handler.ts +0 -1
- package/src/inbound/ingest.ts +0 -14
- package/src/provision/provisionSupport.ts +33 -7
- package/src/reply/send.ts +2 -13
- package/src/store/search.ts +16 -9
- package/src/store/searchIndex.ts +176 -5
- package/src/store/threads.ts +2 -1
- package/src/submission/submit.ts +0 -11
- package/src/version.generated.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../src/ai/classify.ts"],"names":[],"mappings":"AAKA,OAAO,EAAoC,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAiB,MAAM,eAAe,CAAC;AAGjF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,0GAA0G;AAC1G,MAAM,WAAW,SAAS;IACxB,qGAAqG;IACrG,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtE;AAED,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B,kGAAkG;IAClG,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,QAAQ,EAAE,eAAe,CAAC;IAC1B,6BAA6B;IAC7B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;CACf;AA4BD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,iBAAiB,GAAG,MAAM,CAe1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAS3F;AAqBD;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,EAAE,EAAE,SAAS,EACb,OAAO,EAAE;IACP,8BAA8B;IAC9B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,WAAW,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../src/ai/classify.ts"],"names":[],"mappings":"AAKA,OAAO,EAAoC,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAiB,MAAM,eAAe,CAAC;AAGjF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,0GAA0G;AAC1G,MAAM,WAAW,SAAS;IACxB,qGAAqG;IACrG,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtE;AAED,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B,kGAAkG;IAClG,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,QAAQ,EAAE,eAAe,CAAC;IAC1B,6BAA6B;IAC7B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;CACf;AA4BD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,iBAAiB,GAAG,MAAM,CAe1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAS3F;AAqBD;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,EAAE,EAAE,SAAS,EACb,OAAO,EAAE;IACP,8BAA8B;IAC9B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,WAAW,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,cAAc,CAAC,CAsEzB"}
|
package/dist/ai/classify.js
CHANGED
|
@@ -108,11 +108,11 @@ function unsure(model) {
|
|
|
108
108
|
* The code is the whole of how the step can tell the two apart.
|
|
109
109
|
*/
|
|
110
110
|
async function classifyMessage(ai, options) {
|
|
111
|
-
const
|
|
112
|
-
if (typeof run !== "function") throw new SupportClassificationError({ detail: "the AI binding is absent from this worker's env; classification cannot run" });
|
|
111
|
+
const binding = ai;
|
|
112
|
+
if (typeof binding?.run !== "function") throw new SupportClassificationError({ detail: "the AI binding is absent from this worker's env; classification cannot run" });
|
|
113
113
|
let raw;
|
|
114
114
|
try {
|
|
115
|
-
raw = await run
|
|
115
|
+
raw = await binding.run(options.model, {
|
|
116
116
|
messages: [{
|
|
117
117
|
role: "system",
|
|
118
118
|
content: classificationPrompt(options.categories)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/http/handlers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/http/handlers.ts"],"names":[],"mappings":"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"}
|
package/dist/http/handlers.js
CHANGED
|
@@ -12,6 +12,7 @@ import { submitFeedback } from "../submission/submit.js";
|
|
|
12
12
|
import { latestInboundMessageId } from "../workflows/classify.js";
|
|
13
13
|
import { listedThreadView, messageView, myMessageView, myThreadView, senderView, threadView } from "./views.js";
|
|
14
14
|
import { ValidationError } from "@pithy-sh/core/src/error/pithyError";
|
|
15
|
+
import { pithyCommandOf } from "@pithy-sh/core/src/cli/command";
|
|
15
16
|
//#region src/http/handlers.ts
|
|
16
17
|
/** List the inbox. */
|
|
17
18
|
async function listInbox(deps, query, viewer) {
|
|
@@ -98,7 +99,6 @@ async function replyToConversation(deps, threadId, input, viewer) {
|
|
|
98
99
|
db: deps.db,
|
|
99
100
|
config: deps.config,
|
|
100
101
|
enqueue: deps.enqueue,
|
|
101
|
-
fts: deps.fts,
|
|
102
102
|
emit: deps.emit,
|
|
103
103
|
log: deps.log,
|
|
104
104
|
newId: deps.newId,
|
|
@@ -119,7 +119,7 @@ async function reclassifyConversation(deps, threadId, viewer) {
|
|
|
119
119
|
});
|
|
120
120
|
if (!await deps.dispatchClassify(messageId)) throw new SupportClassificationError({
|
|
121
121
|
message: "Classification is not available on this deployment.",
|
|
122
|
-
action:
|
|
122
|
+
action: `Run \`${pithyCommandOf("support provision", {}, { runtime: true })}\` to deploy the classification worker, then retry.`,
|
|
123
123
|
detail: `the SUPPORT_CLASSIFY workflow binding is absent, so no instance was started`
|
|
124
124
|
});
|
|
125
125
|
await deps.emit({
|
|
@@ -184,7 +184,6 @@ async function submitFeedbackRequest(deps, input, userId) {
|
|
|
184
184
|
config: deps.config,
|
|
185
185
|
categories: deps.categories,
|
|
186
186
|
bucket: deps.bucket,
|
|
187
|
-
fts: deps.fts,
|
|
188
187
|
resolveAccount: (id) => resolveSubmitterAccount(deps.d1, id, deps.peers),
|
|
189
188
|
dispatchClassify: deps.dispatchClassify,
|
|
190
189
|
emit: deps.emit,
|
package/dist/http/routes.d.ts
CHANGED
|
@@ -76,7 +76,10 @@ import { type HandlerDeps } from "./handlers";
|
|
|
76
76
|
* `createBackend` mounts **every** capability's middleware before **any** capability's routes, and the
|
|
77
77
|
* adopter's `app` capability composes last — so their middleware runs after auth has resolved the
|
|
78
78
|
* session and before this file's `requireAuth()`. `createBackend.workers.test.ts` pins that ordering,
|
|
79
|
-
* because a paragraph asserting it is not the same as a test failing when it changes.
|
|
79
|
+
* because a paragraph asserting it is not the same as a test failing when it changes. Readiness guards
|
|
80
|
+
* (`Capability.readiness`) mount between the two, after every middleware and before every route, and
|
|
81
|
+
* skip the admin surface; `createBackend.readiness.workers.test.ts` pins that. A check like this one
|
|
82
|
+
* refuses a caller, not a broken Worker, so it stays in `middleware`.
|
|
80
83
|
*
|
|
81
84
|
* Two consequences worth stating rather than discovering. Their middleware sees `c.var.auth` as **null**
|
|
82
85
|
* on an unauthenticated request, since it runs ahead of the route's own gate — so it should pass those
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/http/routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/http/routes.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAC;AAM7E,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,EAEL,KAAK,WAAW,EAUjB,MAAM,YAAY,CAAC;AA+BpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0FG;AAEH,2CAA2C;AAC3C,MAAM,WAAW,oBAAoB;IACnC,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qDAAqD;IACrD,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;CACjE;AAuCD,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,oBAAoB,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,KAAK,IAAI,CAoJtG"}
|
package/dist/http/routes.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { requireAuth } from "./guards.js";
|
|
4
4
|
import { archiveConversation, listInbox, listMyThreads, listReplies, readConversation, readMyThread, reclassifyConversation, replyToConversation, submitFeedbackRequest, updateFlags } from "./handlers.js";
|
|
5
|
+
import { SupportArchiveResponse, SupportFlagsResponse, SupportMyThreadResponse, SupportMyThreadsResponse, SupportReclassifiedResponse, SupportRepliesResponse, SupportReplySentResponse, SupportSubmissionResponse, SupportThreadResponse, SupportThreadsResponse } from "./responses.js";
|
|
5
6
|
import { ArchiveThreadInput, FlagsInput, ListThreadsQuery, MyThreadsQuery, RepliesQuery, ReplyInput, SubmitFeedbackInput, ThreadIdParam } from "./schemas.js";
|
|
6
7
|
import { SUPPORT_THREADS_ARCHIVE_SCOPE, SUPPORT_THREADS_FLAG_SCOPE, SUPPORT_THREADS_READ_SCOPE, SUPPORT_THREADS_RECLASSIFY_SCOPE, SUPPORT_THREADS_REPLY_SCOPE } from "./scopes.js";
|
|
7
8
|
import { InternalError } from "@pithy-sh/core/src/error/pithyError";
|
|
8
|
-
import { zValidator } from "@hono/zod-validator";
|
|
9
9
|
import { requireControlPlane } from "@pithy-sh/core/src/controlPlane/http/guard";
|
|
10
|
+
import { describeRoute } from "@pithy-sh/core/src/http/routeSpec";
|
|
10
11
|
import { requireSameOrigin } from "@pithy-sh/core/src/http/sameOrigin";
|
|
11
|
-
import {
|
|
12
|
+
import { validate } from "@pithy-sh/core/src/http/validation";
|
|
12
13
|
//#region src/http/routes.ts
|
|
13
14
|
/**
|
|
14
15
|
* The verified management client behind a control-plane call.
|
|
@@ -47,17 +48,64 @@ function registerSupportRoutes(options) {
|
|
|
47
48
|
const resolve = options.resolveDeps;
|
|
48
49
|
const submission = options.submission ?? true;
|
|
49
50
|
return (app) => {
|
|
50
|
-
app.get(`${base}/threads`,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
app.
|
|
51
|
+
app.get(`${base}/threads`, describeRoute({
|
|
52
|
+
summary: "List the support inbox.",
|
|
53
|
+
verification: "control-plane",
|
|
54
|
+
scopes: [SUPPORT_THREADS_READ_SCOPE],
|
|
55
|
+
responses: { 200: SupportThreadsResponse }
|
|
56
|
+
}), requireControlPlane(SUPPORT_THREADS_READ_SCOPE), validate("query", ListThreadsQuery), async (c) => c.json(await listInbox(await resolve(c), c.req.valid("query"), viewer(c))));
|
|
57
|
+
app.get(`${base}/replies`, describeRoute({
|
|
58
|
+
summary: "List the canned replies.",
|
|
59
|
+
verification: "control-plane",
|
|
60
|
+
scopes: [SUPPORT_THREADS_READ_SCOPE],
|
|
61
|
+
responses: { 200: SupportRepliesResponse }
|
|
62
|
+
}), requireControlPlane(SUPPORT_THREADS_READ_SCOPE), validate("query", RepliesQuery), async (c) => c.json({ replies: listReplies(await resolve(c), c.req.valid("query")) }));
|
|
63
|
+
app.get(`${base}/threads/:id`, describeRoute({
|
|
64
|
+
summary: "Read one conversation and its messages.",
|
|
65
|
+
verification: "control-plane",
|
|
66
|
+
scopes: [SUPPORT_THREADS_READ_SCOPE],
|
|
67
|
+
responses: { 200: SupportThreadResponse }
|
|
68
|
+
}), requireControlPlane(SUPPORT_THREADS_READ_SCOPE), validate("param", ThreadIdParam), async (c) => c.json(await readConversation(await resolve(c), c.req.valid("param").id)));
|
|
69
|
+
app.post(`${base}/threads/:id/archive`, describeRoute({
|
|
70
|
+
summary: "Mark a conversation done, or reopen it.",
|
|
71
|
+
verification: "control-plane",
|
|
72
|
+
scopes: [SUPPORT_THREADS_ARCHIVE_SCOPE],
|
|
73
|
+
responses: { 200: SupportArchiveResponse }
|
|
74
|
+
}), requireControlPlane(SUPPORT_THREADS_ARCHIVE_SCOPE), validate("param", ThreadIdParam), validate("json", ArchiveThreadInput), async (c) => c.json(await archiveConversation(await resolve(c), c.req.valid("param").id, c.req.valid("json"), viewer(c))));
|
|
75
|
+
app.post(`${base}/threads/:id/reply`, describeRoute({
|
|
76
|
+
summary: "Answer the customer.",
|
|
77
|
+
verification: "control-plane",
|
|
78
|
+
scopes: [SUPPORT_THREADS_REPLY_SCOPE],
|
|
79
|
+
responses: { 201: SupportReplySentResponse }
|
|
80
|
+
}), requireControlPlane(SUPPORT_THREADS_REPLY_SCOPE), validate("param", ThreadIdParam), validate("json", ReplyInput), async (c) => c.json(await replyToConversation(await resolve(c), c.req.valid("param").id, c.req.valid("json"), viewer(c)), 201));
|
|
81
|
+
app.post(`${base}/threads/:id/reclassify`, describeRoute({
|
|
82
|
+
summary: "Run the classifier again on the latest inbound message.",
|
|
83
|
+
verification: "control-plane",
|
|
84
|
+
scopes: [SUPPORT_THREADS_RECLASSIFY_SCOPE],
|
|
85
|
+
responses: { 200: SupportReclassifiedResponse }
|
|
86
|
+
}), requireControlPlane(SUPPORT_THREADS_RECLASSIFY_SCOPE), validate("param", ThreadIdParam), async (c) => c.json(await reclassifyConversation(await resolve(c), c.req.valid("param").id, viewer(c))));
|
|
87
|
+
app.post(`${base}/threads/:id/flags`, describeRoute({
|
|
88
|
+
summary: "Set the caller's private read and snooze flags.",
|
|
89
|
+
verification: "control-plane",
|
|
90
|
+
scopes: [SUPPORT_THREADS_FLAG_SCOPE],
|
|
91
|
+
responses: { 200: SupportFlagsResponse }
|
|
92
|
+
}), requireControlPlane(SUPPORT_THREADS_FLAG_SCOPE), validate("param", ThreadIdParam), validate("json", FlagsInput), async (c) => c.json(await updateFlags(await resolve(c), c.req.valid("param").id, c.req.valid("json"), viewer(c))));
|
|
57
93
|
if (!submission) return;
|
|
58
|
-
app.post(`${base}/feedback`,
|
|
59
|
-
|
|
60
|
-
|
|
94
|
+
app.post(`${base}/feedback`, describeRoute({
|
|
95
|
+
summary: "Write in to support from the app.",
|
|
96
|
+
verification: ["bearer", "session"],
|
|
97
|
+
responses: { 201: SupportSubmissionResponse }
|
|
98
|
+
}), requireAuth(), requireSameOrigin(), validate("json", SubmitFeedbackInput), async (c) => c.json(await submitFeedbackRequest(await resolve(c), c.req.valid("json"), submitter(c)), 201));
|
|
99
|
+
app.get(`${base}/feedback`, describeRoute({
|
|
100
|
+
summary: "List the caller's own support requests.",
|
|
101
|
+
verification: ["bearer", "session"],
|
|
102
|
+
responses: { 200: SupportMyThreadsResponse }
|
|
103
|
+
}), requireAuth(), validate("query", MyThreadsQuery), async (c) => c.json(await listMyThreads(await resolve(c), c.req.valid("query"), submitter(c))));
|
|
104
|
+
app.get(`${base}/feedback/:id`, describeRoute({
|
|
105
|
+
summary: "Read one of the caller's own support requests.",
|
|
106
|
+
verification: ["bearer", "session"],
|
|
107
|
+
responses: { 200: SupportMyThreadResponse }
|
|
108
|
+
}), requireAuth(), validate("param", ThreadIdParam), async (c) => c.json(await readMyThread(await resolve(c), c.req.valid("param").id, submitter(c))));
|
|
61
109
|
};
|
|
62
110
|
}
|
|
63
111
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/inbound/handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAC;AAEvF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAMnD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,aAAa,GAAG,sBAAsB,
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/inbound/handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAC;AAEvF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAMnD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,aAAa,GAAG,sBAAsB,CAiEvF"}
|
package/dist/inbound/handler.js
CHANGED
|
@@ -57,7 +57,6 @@ function createSupportEmailHandler(wiring) {
|
|
|
57
57
|
db: supportDatabase(d1),
|
|
58
58
|
config: wiring.config,
|
|
59
59
|
bucket: bindings.SUPPORT_BUCKET,
|
|
60
|
-
fts: wiring.config.search.fts,
|
|
61
60
|
dispatchClassify: makeClassifyDispatcher(env, log),
|
|
62
61
|
linkSender: (address) => resolveSenderUserId(d1, address, wiring.peers),
|
|
63
62
|
emit: wiring.emit,
|
package/dist/inbound/ingest.d.ts
CHANGED
|
@@ -72,8 +72,6 @@ export interface IngestDeps {
|
|
|
72
72
|
config: SupportConfig;
|
|
73
73
|
/** The R2 bucket raw messages and attachments are written to. Absent means neither is kept. */
|
|
74
74
|
bucket?: R2Bucket;
|
|
75
|
-
/** Whether the FTS5 index is composed. False means search runs as a `LIKE` scan and nothing is indexed. */
|
|
76
|
-
fts: boolean;
|
|
77
75
|
/**
|
|
78
76
|
* Start the classification Workflow for a stored message. Allowed to fail and allowed to decline —
|
|
79
77
|
* an unprovisioned project has no binding, and a thread that stays `uncategorized` is a legitimate
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/inbound/ingest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kCAAkC,CAAC;AAG/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/inbound/ingest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kCAAkC,CAAC;AAG/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAUxB;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,uBAAuB;AACvB,MAAM,MAAM,aAAa;AACvB,oFAAoF;AAClF;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE;AAC7C,wDAAwD;GACtD;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE;AAC3D,yEAAyE;GACvE;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE;AACzE,cAAc;GACZ;IACE,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,EAAE,KAAK,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,iGAAiG;IACjG,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEN,+CAA+C;AAC/C,MAAM,WAAW,UAAU;IACzB,0BAA0B;IAC1B,EAAE,EAAE,eAAe,CAAC;IACpB,2BAA2B;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,+FAA+F;IAC/F,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,oFAAoF;IACpF,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD,sBAAsB;IACtB,IAAI,EAAE,SAAS,CAAC;IAChB,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,yBAAyB;IACzB,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,WAAW;IACX,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAED,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,0BAA0B;IAC1B,GAAG,EAAE,WAAW,CAAC;IACjB,wGAAwG;IACxG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8FAA8F;IAC9F,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA0ID,kCAAkC;AAClC,wBAAsB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CA2RhG"}
|
package/dist/inbound/ingest.js
CHANGED
|
@@ -7,7 +7,6 @@ import { SUPPORT_ATTACHMENTS_TABLE, SUPPORT_MESSAGES_TABLE, SUPPORT_THREADS_TABL
|
|
|
7
7
|
import { attachmentKey, putAttachment, rawMessageKey, sha256Hex } from "../attachment/store.js";
|
|
8
8
|
import { SupportAuditActions } from "../audit/actions.js";
|
|
9
9
|
import { parentCandidates } from "../mime/threading.js";
|
|
10
|
-
import { indexMessage } from "../store/search.js";
|
|
11
10
|
import { truncateToBytes } from "../mime/truncate.js";
|
|
12
11
|
import { MAX_TEXT_BODY, parseInbound } from "../mime/parse.js";
|
|
13
12
|
import { checkRates, checkSize } from "./guard.js";
|
|
@@ -283,19 +282,6 @@ async function ingestInbound(deps, input) {
|
|
|
283
282
|
});
|
|
284
283
|
throw error;
|
|
285
284
|
}
|
|
286
|
-
if (deps.fts) try {
|
|
287
|
-
await indexMessage(deps.db, {
|
|
288
|
-
threadId,
|
|
289
|
-
messageId,
|
|
290
|
-
subject,
|
|
291
|
-
body: text
|
|
292
|
-
});
|
|
293
|
-
} catch (error) {
|
|
294
|
-
deps.log.warn("support message not indexed", {
|
|
295
|
-
messageId,
|
|
296
|
-
error
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
285
|
let attachments = 0;
|
|
300
286
|
try {
|
|
301
287
|
attachments = await storeAttachments(deps, parsed, threadId, messageId, now);
|
|
@@ -48,6 +48,13 @@ export declare function supportRoutingRuleName(project: string): string;
|
|
|
48
48
|
* audits and deletes under cannot drift from the name it deploys under.
|
|
49
49
|
*/
|
|
50
50
|
export declare function supportWorkerName(project: string, env: ManagedEnvironment): string;
|
|
51
|
+
/**
|
|
52
|
+
* The Workflows the classification worker is deployed with in one environment —
|
|
53
|
+
* `<project>-<env>-support-classify`. Derived by the call `resolveSupportConfig` deploys with, so teardown
|
|
54
|
+
* deletes the names provision created and no others (#789). Deleting a Worker does not delete the
|
|
55
|
+
* Workflows registered against it.
|
|
56
|
+
*/
|
|
57
|
+
export declare function supportWorkflowNames(project: string, env: ManagedEnvironment): string[];
|
|
51
58
|
/** The live Cloudflare/wrangler seam. Each step must be idempotent. */
|
|
52
59
|
export interface SupportProvisioner {
|
|
53
60
|
/**
|
|
@@ -69,17 +76,25 @@ export interface SupportProvisioner {
|
|
|
69
76
|
/** Deploy the prebuilt classification worker for this environment. */
|
|
70
77
|
deployWorker(env: ManagedEnvironment): Promise<void>;
|
|
71
78
|
/**
|
|
72
|
-
* Create or drop the full-text index in this environment's app database, to
|
|
79
|
+
* Create or drop the full-text index **and its triggers** in this environment's app database, to
|
|
80
|
+
* match `search.fts`.
|
|
73
81
|
*
|
|
74
82
|
* A provisioning step rather than a migration, because the index is **derived** — every row in it
|
|
75
83
|
* comes from `pithy_support_messages` and `reindexThread` rebuilds it on demand. That is the line a
|
|
76
84
|
* migration is for: schema whose loss loses data. Keeping it here is also what makes the flag safe
|
|
77
85
|
* to toggle at all, since a config-conditional migration removed from the set is corruption to
|
|
78
|
-
* Kysely and blocks `pithy migrate` for every capability sharing the database.
|
|
86
|
+
* Kysely and blocks `pithy migrate` for every capability sharing the database. The triggers inherit
|
|
87
|
+
* that constraint exactly: they exist only where the table does, so they are provisioning too.
|
|
88
|
+
*
|
|
89
|
+
* **Three outcomes, not two.** `repaired` is a database whose table was already there and whose
|
|
90
|
+
* triggers were not — an adopter who took the release that moved the index onto triggers and deployed
|
|
91
|
+
* without re-provisioning. Nothing in the kit maintains that index, so the run creates the triggers
|
|
92
|
+
* and backfills the gap, and says which of the three it did.
|
|
79
93
|
*/
|
|
80
94
|
ensureSearchIndex(env: ManagedEnvironment): Promise<{
|
|
81
95
|
created: boolean;
|
|
82
96
|
dropped: boolean;
|
|
97
|
+
repaired: boolean;
|
|
83
98
|
}>;
|
|
84
99
|
/**
|
|
85
100
|
* Ensure the inbound Email Routing rule that delivers the support address to the app worker.
|
|
@@ -100,11 +115,12 @@ export interface SupportProvisionResult {
|
|
|
100
115
|
};
|
|
101
116
|
/** The environments a classification worker was deployed for. */
|
|
102
117
|
environments: ManagedEnvironment[];
|
|
103
|
-
/** What the full-text index did, per environment — created, dropped, or already correct. */
|
|
118
|
+
/** What the full-text index did, per environment — created, dropped, repaired, or already correct. */
|
|
104
119
|
search: Array<{
|
|
105
120
|
env: ManagedEnvironment;
|
|
106
121
|
created: boolean;
|
|
107
122
|
dropped: boolean;
|
|
123
|
+
repaired: boolean;
|
|
108
124
|
}>;
|
|
109
125
|
/** Whether the inbound routing rule was created, already present, or skipped. */
|
|
110
126
|
routing: {
|
|
@@ -136,6 +152,11 @@ export interface SupportDeprovisioner {
|
|
|
136
152
|
* environment before it removes the bucket or the rule they share (#591).
|
|
137
153
|
*/
|
|
138
154
|
hasWorker(env: ManagedEnvironment): Promise<boolean>;
|
|
155
|
+
/**
|
|
156
|
+
* Delete the env's classification Workflows, by {@link supportWorkflowNames}. Idempotent (a Workflow already
|
|
157
|
+
* gone is a no-op). Deleting the worker leaves them registered, so they go first (#789).
|
|
158
|
+
*/
|
|
159
|
+
deleteWorkflows(env: ManagedEnvironment): Promise<void>;
|
|
139
160
|
/** Delete the env's classification worker. Idempotent. */
|
|
140
161
|
deleteWorker(env: ManagedEnvironment): Promise<void>;
|
|
141
162
|
/**
|
|
@@ -178,7 +199,7 @@ export interface SupportDeprovisionResult {
|
|
|
178
199
|
* is every environment's support history — so they go with the last environment, never a staging teardown.
|
|
179
200
|
* 3. Remove the routing rule, when asked, **first**: stop new mail arriving before removing the worker that would
|
|
180
201
|
* have handled it.
|
|
181
|
-
* 4. Delete the environment's worker, then — only when asked — the bucket and everything in it.
|
|
202
|
+
* 4. Delete the environment's Workflows, then its worker, then — only when asked — the bucket and everything in it.
|
|
182
203
|
*/
|
|
183
204
|
export declare function deprovisionSupport(deprovisioner: SupportDeprovisioner, target: DeprovisionTarget, options?: SupportDeprovisionOptions): Promise<SupportDeprovisionResult>;
|
|
184
205
|
//# sourceMappingURL=provisionSupport.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provisionSupport.d.ts","sourceRoot":"","sources":["../../src/provision/provisionSupport.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"provisionSupport.d.ts","sourceRoot":"","sources":["../../src/provision/provisionSupport.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
-
import { SUPPORT_CAPABILITY } from "../workflows/specs.js";
|
|
3
|
+
import { SUPPORT_CAPABILITY, supportWorkflowRegistry } from "../workflows/specs.js";
|
|
4
4
|
import { GLOBAL_SCOPE } from "@pithy-sh/core/src/naming/environment";
|
|
5
5
|
import { resourceName } from "@pithy-sh/core/src/naming/resource";
|
|
6
6
|
import { resourceNames } from "@pithy-sh/core/src/naming/resourceNames";
|
|
7
|
+
import { hostWorkflowsFor } from "@pithy-sh/core/src/workflow/host";
|
|
7
8
|
import { assertSharedLeavesLast, deprovisionTarget, managedEnvironments } from "@pithy-sh/secrets/src/scope";
|
|
8
9
|
//#region src/provision/provisionSupport.ts
|
|
9
10
|
/**
|
|
@@ -61,6 +62,19 @@ function supportWorkerName(project, env) {
|
|
|
61
62
|
return resourceNames(project).env(env).worker(SUPPORT_CAPABILITY);
|
|
62
63
|
}
|
|
63
64
|
/**
|
|
65
|
+
* The Workflows the classification worker is deployed with in one environment —
|
|
66
|
+
* `<project>-<env>-support-classify`. Derived by the call `resolveSupportConfig` deploys with, so teardown
|
|
67
|
+
* deletes the names provision created and no others (#789). Deleting a Worker does not delete the
|
|
68
|
+
* Workflows registered against it.
|
|
69
|
+
*/
|
|
70
|
+
function supportWorkflowNames(project, env) {
|
|
71
|
+
return hostWorkflowsFor(supportWorkflowRegistry, {
|
|
72
|
+
project,
|
|
73
|
+
capability: SUPPORT_CAPABILITY,
|
|
74
|
+
env
|
|
75
|
+
}).workflows.map((workflow) => workflow.name);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
64
78
|
* Provision the support infrastructure.
|
|
65
79
|
*
|
|
66
80
|
* The order matters and is the inverse of how it fails: the bucket exists before a worker that could
|
|
@@ -111,7 +125,7 @@ async function provisionSupport(provisioner, environments) {
|
|
|
111
125
|
* is every environment's support history — so they go with the last environment, never a staging teardown.
|
|
112
126
|
* 3. Remove the routing rule, when asked, **first**: stop new mail arriving before removing the worker that would
|
|
113
127
|
* have handled it.
|
|
114
|
-
* 4. Delete the environment's worker, then — only when asked — the bucket and everything in it.
|
|
128
|
+
* 4. Delete the environment's Workflows, then its worker, then — only when asked — the bucket and everything in it.
|
|
115
129
|
*/
|
|
116
130
|
async function deprovisionSupport(deprovisioner, target, options = {}) {
|
|
117
131
|
const env = deprovisionTarget(target);
|
|
@@ -123,6 +137,7 @@ async function deprovisionSupport(deprovisioner, target, options = {}) {
|
|
|
123
137
|
flag: "--routing-zone"
|
|
124
138
|
}] : []]);
|
|
125
139
|
const routingRuleRemoved = options.removeRouting ? (await deprovisioner.removeRoutingRule()).removed : false;
|
|
140
|
+
await deprovisioner.deleteWorkflows(env);
|
|
126
141
|
await deprovisioner.deleteWorker(env);
|
|
127
142
|
if (options.deleteStorage) await deprovisioner.deleteBucket();
|
|
128
143
|
return {
|
|
@@ -131,4 +146,4 @@ async function deprovisionSupport(deprovisioner, target, options = {}) {
|
|
|
131
146
|
};
|
|
132
147
|
}
|
|
133
148
|
//#endregion
|
|
134
|
-
export { deprovisionSupport, provisionSupport, supportRoutingRuleName, supportWorkerName };
|
|
149
|
+
export { deprovisionSupport, provisionSupport, supportRoutingRuleName, supportWorkerName, supportWorkflowNames };
|
package/dist/reply/send.d.ts
CHANGED
package/dist/reply/send.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/reply/send.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/reply/send.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kCAAkC,CAAC;AAE/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAiD,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAIrG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,0CAA0C;AAC1C,MAAM,WAAW,SAAS;IACxB,0BAA0B;IAC1B,EAAE,EAAE,eAAe,CAAC;IACpB,2BAA2B;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,+FAA+F;QAC/F,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjC,sBAAsB;IACtB,IAAI,EAAE,SAAS,CAAC;IAChB,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,yBAAyB;IACzB,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,WAAW;IACX,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GACnB;IACE,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IACE,0FAA0F;IAC1F,OAAO,EAAE,KAAK,CAAC;IACf,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEN,gCAAgC;AAChC,wBAAsB,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAkLxF"}
|
package/dist/reply/send.js
CHANGED
|
@@ -5,7 +5,7 @@ import { SupportMessage } from "../data/message.js";
|
|
|
5
5
|
import { SUPPORT_MESSAGES_TABLE, SUPPORT_THREADS_TABLE } from "../data/tables.js";
|
|
6
6
|
import { SupportAuditActions } from "../audit/actions.js";
|
|
7
7
|
import { buildReferencesHeader, replySubject } from "../mime/threading.js";
|
|
8
|
-
import {
|
|
8
|
+
import { commandName } from "@pithy-sh/core/src/cli/command";
|
|
9
9
|
//#region src/reply/send.ts
|
|
10
10
|
/** Send a reply on a thread. */
|
|
11
11
|
async function sendReply(deps, input) {
|
|
@@ -60,7 +60,7 @@ async function sendReply(deps, input) {
|
|
|
60
60
|
if (!inApp) {
|
|
61
61
|
if (!enqueue) throw new SupportReplyFailedError({
|
|
62
62
|
message: "This deployment cannot send mail.",
|
|
63
|
-
action:
|
|
63
|
+
action: `Compose the email capability into this Worker with \`${commandName("add")}\` and provision it, then retry.`,
|
|
64
64
|
detail: `thread ${input.threadId} needs a mailed reply and no email capability is composed`
|
|
65
65
|
});
|
|
66
66
|
if (!replyTo) throw new SupportReplyFailedError({
|
|
@@ -110,19 +110,6 @@ async function sendReply(deps, input) {
|
|
|
110
110
|
createdAt: now
|
|
111
111
|
};
|
|
112
112
|
await deps.db.insertInto(SUPPORT_MESSAGES_TABLE).values(SupportMessage.encode(message)).execute();
|
|
113
|
-
if (deps.fts) try {
|
|
114
|
-
await indexMessage(deps.db, {
|
|
115
|
-
threadId: input.threadId,
|
|
116
|
-
messageId,
|
|
117
|
-
subject,
|
|
118
|
-
body: input.body
|
|
119
|
-
});
|
|
120
|
-
} catch (error) {
|
|
121
|
-
deps.log.warn("support reply not indexed", {
|
|
122
|
-
messageId,
|
|
123
|
-
error
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
113
|
await deps.db.updateTable(SUPPORT_THREADS_TABLE).set((eb) => ({
|
|
127
114
|
messageCount: eb("messageCount", "+", 1),
|
|
128
115
|
lastMessageAt: now.getTime(),
|
package/dist/store/search.d.ts
CHANGED
|
@@ -26,16 +26,23 @@ export declare function searchPredicate(term: string, options: {
|
|
|
26
26
|
/**
|
|
27
27
|
* Add a message to the full-text index, replacing any row already there for it.
|
|
28
28
|
*
|
|
29
|
-
* **
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* write path in this repo is built on.
|
|
29
|
+
* **No write path calls this any more.** The index is maintained by the triggers `createSearchIndex`
|
|
30
|
+
* provisions (`store/searchIndex.ts`), so a message is indexed because it was written rather than
|
|
31
|
+
* because the code that wrote it remembered. This is the **repair** primitive now: {@link reindexThread}
|
|
32
|
+
* and {@link reindexAll} are its only callers in the kit, and it stays exported because
|
|
33
|
+
* `package.json` exports `./src/*`, so an adopter may be reaching it.
|
|
35
34
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
35
|
+
* **Remove-then-insert, not a bare insert** — the same shape the trigger bodies use, and for the
|
|
36
|
+
* reason an FTS5 table always needs it: it has no primary key and no unique constraint to lean on, so
|
|
37
|
+
* a second call for the same message would silently add a second copy and the thread would start
|
|
38
|
+
* appearing twice in its own search results. Making the write idempotent means a retry, a reindex, and
|
|
39
|
+
* a first index are all the same operation.
|
|
40
|
+
*
|
|
41
|
+
* **It no longer carries a swallow contract, because it is no longer on a message write.** A failure
|
|
42
|
+
* here is a failed repair, and the caller is a command or a route that can say so. The durability
|
|
43
|
+
* question moved with the index itself: a trigger runs inside the message's own statement, so a
|
|
44
|
+
* failure in its body takes the message write down with it — which is what `searchIndex.ts` documents
|
|
45
|
+
* and what the triggers' own tests hold.
|
|
39
46
|
*/
|
|
40
47
|
export declare function indexMessage(db: SupportDatabase, entry: {
|
|
41
48
|
threadId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/store/search.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,EAAO,MAAM,QAAQ,CAAC;AAC5D,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AA+B9E;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS7C;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAEhE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,GAAG,UAAU,CAAC,OAAO,CAAC,CAkB5F;AAED
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/store/search.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,EAAO,MAAM,QAAQ,CAAC;AAC5D,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AA+B9E;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS7C;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAEhE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,GAAG,UAAU,CAAC,OAAO,CAAC,CAkB5F;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5E,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkB1F;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,eAAe,EACnB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GAC1E,OAAO,CAAC,MAAM,CAAC,CA4CjB"}
|
package/dist/store/search.js
CHANGED
|
@@ -67,16 +67,23 @@ function searchPredicate(term, options) {
|
|
|
67
67
|
/**
|
|
68
68
|
* Add a message to the full-text index, replacing any row already there for it.
|
|
69
69
|
*
|
|
70
|
-
* **
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* write path in this repo is built on.
|
|
70
|
+
* **No write path calls this any more.** The index is maintained by the triggers `createSearchIndex`
|
|
71
|
+
* provisions (`store/searchIndex.ts`), so a message is indexed because it was written rather than
|
|
72
|
+
* because the code that wrote it remembered. This is the **repair** primitive now: {@link reindexThread}
|
|
73
|
+
* and {@link reindexAll} are its only callers in the kit, and it stays exported because
|
|
74
|
+
* `package.json` exports `./src/*`, so an adopter may be reaching it.
|
|
76
75
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
76
|
+
* **Remove-then-insert, not a bare insert** — the same shape the trigger bodies use, and for the
|
|
77
|
+
* reason an FTS5 table always needs it: it has no primary key and no unique constraint to lean on, so
|
|
78
|
+
* a second call for the same message would silently add a second copy and the thread would start
|
|
79
|
+
* appearing twice in its own search results. Making the write idempotent means a retry, a reindex, and
|
|
80
|
+
* a first index are all the same operation.
|
|
81
|
+
*
|
|
82
|
+
* **It no longer carries a swallow contract, because it is no longer on a message write.** A failure
|
|
83
|
+
* here is a failed repair, and the caller is a command or a route that can say so. The durability
|
|
84
|
+
* question moved with the index itself: a trigger runs inside the message's own statement, so a
|
|
85
|
+
* failure in its body takes the message write down with it — which is what `searchIndex.ts` documents
|
|
86
|
+
* and what the triggers' own tests hold.
|
|
80
87
|
*/
|
|
81
88
|
async function indexMessage(db, entry) {
|
|
82
89
|
await sql`delete from pithy_support_search where message_id = ${entry.messageId}`.execute(db);
|