@wirecat/cli-core 0.483.0 → 0.484.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -1
- package/dist/search/lucene/registry.js +1 -1
- package/dist/services/tags.js +1 -1
- package/dist/store/sqlite/chats.js +13 -4
- package/dist/store/sqlite/document-transfer/people.js +3 -2
- package/dist/store/sqlite/emails.js +16 -10
- package/dist/store/sqlite/folded.d.ts +20 -1
- package/dist/store/sqlite/folded.js +10 -1
- package/dist/store/sqlite/identities.js +3 -1
- package/dist/store/sqlite/knowledge.js +5 -4
- package/dist/store/sqlite/lucene-fields.js +2 -2
- package/dist/store/sqlite/manifest.js +2 -0
- package/dist/store/sqlite/migrations.generated.js +33 -0
- package/dist/store/sqlite/schema.d.ts +80 -0
- package/dist/store/sqlite/schema.js +18 -2
- package/dist/store/sqlite/tags.js +7 -7
- package/dist/store/sqlite/tasks.js +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.484.0 — 30.10.2026
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Every name filter ignores case in any script, not only Latin** — SQLite's `lower()` folds A–Z only, so
|
|
15
|
+
`projects list 'name:*ТАРТ*'` missed «Тартус». Store version 38 keeps a folded copy, with an index where a filter
|
|
16
|
+
reads it, of project names, chat titles, contact names, mail thread subjects and mailbox names, beside the copies
|
|
17
|
+
tasks, meetings, attachments and files already had. `projects list name:`, `tags list name:` and mail search
|
|
18
|
+
`mailbox:`/`label:` compare those copies. A trigger empties a copy whose name an older build changed, and the next
|
|
19
|
+
open refills it, as it fills the copies of rows stored before version 38.
|
|
20
|
+
|
|
10
21
|
## 0.483.0 — 30.10.2026
|
|
11
22
|
|
|
12
23
|
### Fixed
|
|
@@ -2057,7 +2068,6 @@ shape of this file.
|
|
|
2057
2068
|
- **An import says the zone it read file times in, and a host can explain its file names.** `RunReport.timezone` is
|
|
2058
2069
|
set when every imported file's meeting carries one zone; `MeetingCommandDeps.importHelp` adds the host's note
|
|
2059
2070
|
(file name patterns, how their time is read) under `meetings import --help`.
|
|
2060
|
-
||||||| Stash base
|
|
2061
2071
|
- **Meeting search finds words spread over neighbouring lines.** `"циан риелтор"~3` in a meeting query finds a
|
|
2062
2072
|
transcript line with one of the words when the others are at most three lines away in the same transcript; `~0`
|
|
2063
2073
|
is one line, any order; at most 20. The words widen to garbled spellings as single words do. It needs
|
|
@@ -112,7 +112,7 @@ export const QUERY_FIELDS = [
|
|
|
112
112
|
aliases: [],
|
|
113
113
|
type: "kw",
|
|
114
114
|
operators: terms,
|
|
115
|
-
normalization: "a folder or label by its id or name
|
|
115
|
+
normalization: "a folder or label by its id (ASCII case folded) or name (case folded in any script); mail only",
|
|
116
116
|
support: "A1",
|
|
117
117
|
},
|
|
118
118
|
{
|
package/dist/services/tags.js
CHANGED
|
@@ -18,7 +18,7 @@ export const TAGGING_QUERY = {
|
|
|
18
18
|
const TAGGING_COLUMNS = {
|
|
19
19
|
tag: { type: "kw", sql: "x.tag" },
|
|
20
20
|
type: { type: "enum", sql: "x.tag_type" },
|
|
21
|
-
name: { type: "kw", sql: "
|
|
21
|
+
name: { type: "kw", sql: "x.name_folded", folded: true },
|
|
22
22
|
sources: {
|
|
23
23
|
where: (node) => node.operator === "wildcard"
|
|
24
24
|
? { sql: "1", params: [] }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { and, eq, inArray, or, sql } from "./drizzle/core.js";
|
|
3
|
+
import { folded } from "./folded.js";
|
|
3
4
|
import { identityPk } from "./identities.js";
|
|
4
5
|
import { bound } from "./query-filter.js";
|
|
5
6
|
import { chatEventPeople, chatEvents, chatMembers, chats, chunks, fetchLeases, identities, involvements, memberCounts, memberObservationMembers, memberObservations, memberStays, messageReactions, messageRevisions, messages, messageTranscripts, pollVotes, syncRanges, } from "./schema.js";
|
|
@@ -112,7 +113,14 @@ export const answeredChatPk = (context, accountKey, chatId, typed) => {
|
|
|
112
113
|
}
|
|
113
114
|
return Number(orm
|
|
114
115
|
.insert(chats)
|
|
115
|
-
.values({
|
|
116
|
+
.values({
|
|
117
|
+
accountId: accountKey,
|
|
118
|
+
externalId: chatId,
|
|
119
|
+
...like,
|
|
120
|
+
titleFolded: folded(like.title),
|
|
121
|
+
createdAt: now(),
|
|
122
|
+
updatedAt: now(),
|
|
123
|
+
})
|
|
116
124
|
.returning({ pk: chats.id })
|
|
117
125
|
.get()?.pk);
|
|
118
126
|
};
|
|
@@ -133,6 +141,7 @@ export const upsertChat = (context, accountKey, chat) => {
|
|
|
133
141
|
parentChatId: parent,
|
|
134
142
|
scope: chat.scope ?? null,
|
|
135
143
|
title: chat.title ?? null,
|
|
144
|
+
titleFolded: folded(chat.title),
|
|
136
145
|
unreadCount: chat.unreadCount ?? null,
|
|
137
146
|
lastMessageAt: toMs(chat.lastMessageAt),
|
|
138
147
|
participantsCount: chat.participantsCount ?? null,
|
|
@@ -148,7 +157,7 @@ export const upsertChat = (context, accountKey, chat) => {
|
|
|
148
157
|
kind: sql `excluded.kind`,
|
|
149
158
|
...(chat.parentChatId === undefined ? {} : { parentChatId: sql `excluded.parent_chat_id` }),
|
|
150
159
|
scope: sql `coalesce(excluded.scope, ${chats.scope})`,
|
|
151
|
-
...(chat.title === undefined ? {} : { title: sql `excluded.title` }),
|
|
160
|
+
...(chat.title === undefined ? {} : { title: sql `excluded.title`, titleFolded: sql `excluded.title_folded` }),
|
|
152
161
|
...(chat.unreadCount === undefined
|
|
153
162
|
? {}
|
|
154
163
|
: { unreadCount: sql `excluded.unread_count`, unreadCountedAt: sql `excluded.unread_counted_at` }),
|
|
@@ -423,8 +432,8 @@ export const metadata = (context, key, chatId) => {
|
|
|
423
432
|
};
|
|
424
433
|
export const saveMetadata = (context, key, entry) => {
|
|
425
434
|
revisedBy(context, "details", () => context.database
|
|
426
|
-
.prepare("UPDATE chats SET title=?, username=?, description=?, details_fetched_at=?, updated_at=? WHERE id=?")
|
|
427
|
-
.run(entry.title, entry.username, entry.description, context.now(), context.now(), metadataChatPk(context, key, entry.chatId)));
|
|
435
|
+
.prepare("UPDATE chats SET title=?, title_folded=?, username=?, description=?, details_fetched_at=?, updated_at=? WHERE id=?")
|
|
436
|
+
.run(entry.title, folded(entry.title), entry.username, entry.description, context.now(), context.now(), metadataChatPk(context, key, entry.chatId)));
|
|
428
437
|
return metadata(context, key, entry.chatId);
|
|
429
438
|
};
|
|
430
439
|
/** Called inside the store's transaction: claims and effective search tags change together. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { fold } from "../../normalize.js";
|
|
2
3
|
import { ownerPerson } from "../actors.js";
|
|
3
4
|
import { stateOfThing, thingOf } from "../things.js";
|
|
4
5
|
export const planPeople = (database, snapshot, mappings, bindings, conflicts) => {
|
|
@@ -62,8 +63,8 @@ export const copyPeople = (database, snapshot, mappings, now) => {
|
|
|
62
63
|
throw new CliError("configuration_error", "an identity belongs to another destination person; the transfer was rolled back");
|
|
63
64
|
if (!row)
|
|
64
65
|
row = database
|
|
65
|
-
.prepare("INSERT INTO identities (provider,external_id,name,username,bot,created_at,updated_at) VALUES (
|
|
66
|
-
.get(String(identity.provider), String(identity.external_id), identity.name ?? null, identity.username ?? null, identity.bot ?? null, identity.created_at ?? now(), identity.updated_at ?? now());
|
|
66
|
+
.prepare("INSERT INTO identities (provider,external_id,name,name_folded,username,bot,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?) RETURNING id")
|
|
67
|
+
.get(String(identity.provider), String(identity.external_id), identity.name ?? null, identity.name == null ? null : fold(String(identity.name)), identity.username ?? null, identity.bot ?? null, identity.created_at ?? now(), identity.updated_at ?? now());
|
|
67
68
|
if (!database.prepare("SELECT identity_id FROM identity_links WHERE identity_id=?").get(Number(row?.id)))
|
|
68
69
|
database
|
|
69
70
|
.prepare("INSERT INTO identity_links (identity_id,person_id,method,confidence,author,source,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)")
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { formatLocator } from "../../domain/locator.js";
|
|
2
2
|
import { formatReference } from "../../domain/references.js";
|
|
3
|
-
import { normalize } from "../normalize.js";
|
|
3
|
+
import { fold, normalize } from "../normalize.js";
|
|
4
4
|
import { chunkAttachments, drainEmailIndex, dropAttachmentChunks, MAIL_PIECES, queueThreads, threadPieces, titleOf, } from "./email-index.js";
|
|
5
5
|
import { recountEmailThread as recount, setEmailFlags } from "./email-state.js";
|
|
6
6
|
import { bool, flag, fromJson, int, page, str, toJson } from "./events.js";
|
|
7
|
+
import { folded } from "./folded.js";
|
|
7
8
|
import { ensurePerson } from "./identities.js";
|
|
8
9
|
import { mailRulesOver } from "./mail-rules.js";
|
|
9
10
|
import { attachmentOf, saveAttachment, wordsQuery } from "./meetings.js";
|
|
@@ -144,22 +145,24 @@ const identityOf = (database, { address, name }, now) => {
|
|
|
144
145
|
.get(key);
|
|
145
146
|
if (found) {
|
|
146
147
|
if (found.name === null && name)
|
|
147
|
-
database
|
|
148
|
+
database
|
|
149
|
+
.prepare("UPDATE identities SET name = ?, name_folded = ?, updated_at = ? WHERE id = ?")
|
|
150
|
+
.run(name, fold(name), now, Number(found.id));
|
|
148
151
|
return Number(found.id);
|
|
149
152
|
}
|
|
150
153
|
return Number(database
|
|
151
|
-
.prepare("INSERT INTO identities (provider, external_id, name, created_at, updated_at) VALUES ('email', ?, ?, ?, ?) RETURNING id")
|
|
152
|
-
.get(key, name, now, now)?.id);
|
|
154
|
+
.prepare("INSERT INTO identities (provider, external_id, name, name_folded, created_at, updated_at) VALUES ('email', ?, ?, ?, ?, ?) RETURNING id")
|
|
155
|
+
.get(key, name, folded(name), now, now)?.id);
|
|
153
156
|
};
|
|
154
157
|
const mailboxPk = (database, accountId, box, now) => {
|
|
155
158
|
if (!box.externalId)
|
|
156
159
|
throw new Error("Invalid mailbox key");
|
|
157
160
|
return Number(database
|
|
158
|
-
.prepare(`INSERT INTO mailboxes (account_id, external_id, name, kind, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)
|
|
159
|
-
ON CONFLICT (account_id, external_id) DO UPDATE SET name = excluded.name,
|
|
161
|
+
.prepare(`INSERT INTO mailboxes (account_id, external_id, name, name_folded, kind, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
162
|
+
ON CONFLICT (account_id, external_id) DO UPDATE SET name = excluded.name, name_folded = excluded.name_folded,
|
|
160
163
|
kind = coalesce(excluded.kind, kind), updated_at = excluded.updated_at
|
|
161
164
|
RETURNING id`)
|
|
162
|
-
.get(accountId, box.externalId, box.name, box.kind, now, now)?.id);
|
|
165
|
+
.get(accountId, box.externalId, box.name, fold(box.name), box.kind, now, now)?.id);
|
|
163
166
|
};
|
|
164
167
|
const join = (database, emailId, mailboxId, now) => database
|
|
165
168
|
.prepare("INSERT OR IGNORE INTO email_mailboxes (email_id, mailbox_id, created_at) VALUES (?, ?, ?)")
|
|
@@ -253,7 +256,9 @@ const saveThread = (database, orm, input) => {
|
|
|
253
256
|
RETURNING id`)
|
|
254
257
|
.get(accountId, externalId, now, now)?.id);
|
|
255
258
|
if (input.subject !== undefined)
|
|
256
|
-
database
|
|
259
|
+
database
|
|
260
|
+
.prepare("UPDATE email_threads SET subject = ?, subject_folded = ? WHERE id = ?")
|
|
261
|
+
.run(input.subject, folded(input.subject), threadId);
|
|
257
262
|
if (input.metadata !== undefined)
|
|
258
263
|
database.prepare("UPDATE email_threads SET metadata = ? WHERE id = ?").run(toJson(input.metadata), threadId);
|
|
259
264
|
const moved = new Set();
|
|
@@ -265,9 +270,10 @@ const saveThread = (database, orm, input) => {
|
|
|
265
270
|
const first = database
|
|
266
271
|
.prepare("SELECT subject FROM emails WHERE email_thread_id = ? ORDER BY coalesce(sent_at, received_at), id LIMIT 1")
|
|
267
272
|
.get(threadId);
|
|
273
|
+
const subject = titleOf(str(first?.subject));
|
|
268
274
|
database
|
|
269
|
-
.prepare("UPDATE email_threads SET subject = ? WHERE id = ? AND subject IS NULL")
|
|
270
|
-
.run(
|
|
275
|
+
.prepare("UPDATE email_threads SET subject = ?, subject_folded = ? WHERE id = ? AND subject IS NULL")
|
|
276
|
+
.run(subject, folded(subject), threadId);
|
|
271
277
|
for (const id of [threadId, ...moved])
|
|
272
278
|
recount(database, id, now);
|
|
273
279
|
queueThreads(database, moved);
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { CacheDatabase } from "../driver.js";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Every name a filter compares, beside its `fold()`ed copy: SQLite's `lower()`, `NOCASE` and `LIKE` fold ASCII only,
|
|
4
|
+
* and Bun cannot register a function that folds the rest. A writer sets the copy; a trigger empties a copy whose name
|
|
5
|
+
* changed without it (version 38), and `fillFolded` refills it on the next open.
|
|
6
|
+
*/
|
|
3
7
|
export declare const FOLDED: readonly [{
|
|
4
8
|
readonly table: "tasks";
|
|
5
9
|
readonly column: "title";
|
|
@@ -18,6 +22,21 @@ export declare const FOLDED: readonly [{
|
|
|
18
22
|
}, {
|
|
19
23
|
readonly table: "files";
|
|
20
24
|
readonly column: "path";
|
|
25
|
+
}, {
|
|
26
|
+
readonly table: "projects";
|
|
27
|
+
readonly column: "name";
|
|
28
|
+
}, {
|
|
29
|
+
readonly table: "chats";
|
|
30
|
+
readonly column: "title";
|
|
31
|
+
}, {
|
|
32
|
+
readonly table: "identities";
|
|
33
|
+
readonly column: "name";
|
|
34
|
+
}, {
|
|
35
|
+
readonly table: "email_threads";
|
|
36
|
+
readonly column: "subject";
|
|
37
|
+
}, {
|
|
38
|
+
readonly table: "mailboxes";
|
|
39
|
+
readonly column: "name";
|
|
21
40
|
}];
|
|
22
41
|
export declare const folded: (value: string | null | undefined) => string | null;
|
|
23
42
|
/**
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { fold } from "../normalize.js";
|
|
2
2
|
import { writeBatch } from "./transaction.js";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Every name a filter compares, beside its `fold()`ed copy: SQLite's `lower()`, `NOCASE` and `LIKE` fold ASCII only,
|
|
5
|
+
* and Bun cannot register a function that folds the rest. A writer sets the copy; a trigger empties a copy whose name
|
|
6
|
+
* changed without it (version 38), and `fillFolded` refills it on the next open.
|
|
7
|
+
*/
|
|
4
8
|
export const FOLDED = [
|
|
5
9
|
{ table: "tasks", column: "title" },
|
|
6
10
|
{ table: "tasks", column: "close_reason" },
|
|
@@ -8,6 +12,11 @@ export const FOLDED = [
|
|
|
8
12
|
{ table: "attachments", column: "local_path" },
|
|
9
13
|
{ table: "meetings", column: "title" },
|
|
10
14
|
{ table: "files", column: "path" },
|
|
15
|
+
{ table: "projects", column: "name" },
|
|
16
|
+
{ table: "chats", column: "title" },
|
|
17
|
+
{ table: "identities", column: "name" },
|
|
18
|
+
{ table: "email_threads", column: "subject" },
|
|
19
|
+
{ table: "mailboxes", column: "name" },
|
|
11
20
|
];
|
|
12
21
|
export const folded = (value) => value === null || value === undefined ? null : fold(value);
|
|
13
22
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { fold } from "../normalize.js";
|
|
3
3
|
import { and, asc, desc, eq, inArray, isNotNull, isNull, ne, sql } from "./drizzle/core.js";
|
|
4
|
+
import { folded } from "./folded.js";
|
|
4
5
|
import { resolvePersonLinks } from "./person-resolution.js";
|
|
5
6
|
import { accountIdentities, accounts, aliases, chatMembers, chats, identities, identityLinkEvents, identityLinks, identityRevisions, messages, persons, } from "./schema.js";
|
|
6
7
|
import { toIso } from "./values.js";
|
|
@@ -129,7 +130,7 @@ const saveIdentity = ({ orm, now, database }, provider, nativeId, name, facts, r
|
|
|
129
130
|
changed.phone !== found.phone) {
|
|
130
131
|
orm
|
|
131
132
|
.update(identities)
|
|
132
|
-
.set({ ...changed, updatedAt: now() })
|
|
133
|
+
.set({ ...changed, nameFolded: folded(changed.name), updatedAt: now() })
|
|
133
134
|
.where(eq(identities.id, found.pk))
|
|
134
135
|
.run();
|
|
135
136
|
}
|
|
@@ -149,6 +150,7 @@ const saveIdentity = ({ orm, now, database }, provider, nativeId, name, facts, r
|
|
|
149
150
|
provider,
|
|
150
151
|
externalId: nativeId,
|
|
151
152
|
name,
|
|
153
|
+
nameFolded: folded(name),
|
|
152
154
|
metadata: facts.metadata == null ? null : JSON.stringify(facts.metadata),
|
|
153
155
|
username: facts.username ?? null,
|
|
154
156
|
bot: flag(facts.isBot),
|
|
@@ -5,6 +5,7 @@ import { formatReference, parseReference } from "../../domain/references.js";
|
|
|
5
5
|
import { normalizeTag } from "../../domain/tags.js";
|
|
6
6
|
import { normalizeVocabularyName } from "../../domain/vocabulary.js";
|
|
7
7
|
import { formatMeetingReference } from "../../meetings/index.js";
|
|
8
|
+
import { fold } from "../normalize.js";
|
|
8
9
|
import { ulid } from "../ulid.js";
|
|
9
10
|
import { ownerPerson } from "./actors.js";
|
|
10
11
|
import { linkKind, RELATION_KINDS } from "./link-kinds.js";
|
|
@@ -485,9 +486,9 @@ export const knowledgeStoreOver = (context) => {
|
|
|
485
486
|
}
|
|
486
487
|
const at = now();
|
|
487
488
|
const row = database
|
|
488
|
-
.prepare("INSERT INTO projects (key, name, description, type, organization_id, scope, tasks_count, status, created_at, updated_at) " +
|
|
489
|
-
"VALUES (?, ?, ?, ?, ?, ?, 0, 'active', ?, ?) RETURNING *")
|
|
490
|
-
.get(chosen, title, description === undefined ? null : textOf(description, 2000), oneOf(type, PROJECT_TYPES, "a project's type"), organizationId, oneOf(scope, SCOPES, "a scope"), at, at);
|
|
489
|
+
.prepare("INSERT INTO projects (key, name, name_folded, description, type, organization_id, scope, tasks_count, status, created_at, updated_at) " +
|
|
490
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, 0, 'active', ?, ?) RETURNING *")
|
|
491
|
+
.get(chosen, title, fold(title), description === undefined ? null : textOf(description, 2000), oneOf(type, PROJECT_TYPES, "a project's type"), organizationId, oneOf(scope, SCOPES, "a scope"), at, at);
|
|
491
492
|
return projectOf(row);
|
|
492
493
|
}),
|
|
493
494
|
projects: async (query) => {
|
|
@@ -496,7 +497,7 @@ export const knowledgeStoreOver = (context) => {
|
|
|
496
497
|
queryFilter(query, {
|
|
497
498
|
id: { type: "kw", sql: "CAST(id AS TEXT)" },
|
|
498
499
|
key: { type: "kw", sql: "key" },
|
|
499
|
-
name: { type: "kw", sql: "
|
|
500
|
+
name: { type: "kw", sql: "name_folded", folded: true },
|
|
500
501
|
kind: { type: "enum", sql: `CASE WHEN length(key) = ${INBOX_KEY_LENGTH} THEN 'inbox' ELSE 'project' END` },
|
|
501
502
|
type: { type: "enum", sql: "type" },
|
|
502
503
|
organization_id: { type: "kw", sql: "CAST(organization_id AS TEXT)" },
|
|
@@ -9,7 +9,7 @@ import { isStemmed } from "../../search/lucene/resolved.js";
|
|
|
9
9
|
import { exhausted, QUERY_LIMITS, searchError } from "../../search/lucene/types.js";
|
|
10
10
|
import { inSource } from "../../search/query.js";
|
|
11
11
|
import { STEM_SEPARATOR } from "../../search/stem.js";
|
|
12
|
-
import { normalize } from "../normalize.js";
|
|
12
|
+
import { fold, normalize } from "../normalize.js";
|
|
13
13
|
import { linkMatch } from "./mention-links.js";
|
|
14
14
|
import { valueMatch } from "./phone-countries.js";
|
|
15
15
|
import { canonicalTagNamed } from "./tags.js";
|
|
@@ -379,7 +379,7 @@ const mailbox = ({ value }, { mail }) => always(mail
|
|
|
379
379
|
// Not tied to m.account_id: a correlated subquery re-reads every mailbox link for each email (minutes on
|
|
380
380
|
// 880k links), and an email only ever links to its own account's mailboxes anyway.
|
|
381
381
|
`m.id IN (SELECT em.email_id FROM email_mailboxes em WHERE em.mailbox_id IN
|
|
382
|
-
(SELECT b.id FROM mailboxes b WHERE lower(b.external_id) = lower(?) OR
|
|
382
|
+
(SELECT b.id FROM mailboxes b WHERE lower(b.external_id) = lower(?) OR b.name_folded = ?))`, value, fold(value))
|
|
383
383
|
: bound("0"));
|
|
384
384
|
const is = ({ value }, { mail }) => {
|
|
385
385
|
const state = value.toLowerCase();
|
|
@@ -50,6 +50,8 @@ export const MANIFEST = [
|
|
|
50
50
|
{ name: "20261029162435_version-36-attachment-contact", version: 36, minCompatible: 34 },
|
|
51
51
|
{ name: "20261029180959_version-37-identity-phone", version: 37, minCompatible: 34 },
|
|
52
52
|
{ name: "20261029181000_version-37-contact-mention-triggers", version: 37, minCompatible: 34 },
|
|
53
|
+
{ name: "20261029224106_version-38-folded-names", version: 38, minCompatible: 34 },
|
|
54
|
+
{ name: "20261029224107_version-38-folded-triggers", version: 38, minCompatible: 34 },
|
|
53
55
|
];
|
|
54
56
|
export const generatedMigrations = (manifest = MANIFEST, generated = GENERATED) => {
|
|
55
57
|
const migrations = [];
|
|
@@ -797,5 +797,38 @@ export const GENERATED = [
|
|
|
797
797
|
"-- A shared contact card has no text, only a phone: queue it for mentions like an attachment's text.\n-- Cards stored before version 37 wait for `mentions rebuild`.\nCREATE TRIGGER mention_pending_attachment_contact_ai AFTER INSERT ON attachments\n WHEN new.attachable_type = 'message' AND new.contact IS NOT NULL BEGIN\n INSERT OR IGNORE INTO mention_pending (source_type, source_id) VALUES ('attachment', new.id);\nEND;",
|
|
798
798
|
"CREATE TRIGGER mention_pending_attachment_contact_au AFTER UPDATE OF contact ON attachments\n WHEN new.attachable_type = 'message' AND old.contact IS NOT new.contact BEGIN\n INSERT OR IGNORE INTO mention_pending (source_type, source_id) VALUES ('attachment', new.id);\nEND;"
|
|
799
799
|
]
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
"name": "20261029224106_version-38-folded-names",
|
|
803
|
+
"statements": [
|
|
804
|
+
"ALTER TABLE `chats` ADD `title_folded` text;",
|
|
805
|
+
"ALTER TABLE `email_threads` ADD `subject_folded` text;",
|
|
806
|
+
"ALTER TABLE `identities` ADD `name_folded` text;",
|
|
807
|
+
"ALTER TABLE `mailboxes` ADD `name_folded` text;",
|
|
808
|
+
"ALTER TABLE `projects` ADD `name_folded` text;",
|
|
809
|
+
"CREATE INDEX `chats_title_to_fold` ON `chats` (`id`) WHERE title IS NOT NULL AND title_folded IS NULL;",
|
|
810
|
+
"CREATE INDEX `email_threads_subject_to_fold` ON `email_threads` (`id`) WHERE subject IS NOT NULL AND subject_folded IS NULL;",
|
|
811
|
+
"CREATE INDEX `identities_name_to_fold` ON `identities` (`id`) WHERE name IS NOT NULL AND name_folded IS NULL;",
|
|
812
|
+
"CREATE INDEX `mailboxes_by_name_folded` ON `mailboxes` (`name_folded`);",
|
|
813
|
+
"CREATE INDEX `mailboxes_name_to_fold` ON `mailboxes` (`id`) WHERE name_folded IS NULL;",
|
|
814
|
+
"CREATE INDEX `projects_by_name_folded` ON `projects` (`name_folded`);",
|
|
815
|
+
"CREATE INDEX `projects_name_to_fold` ON `projects` (`id`) WHERE name_folded IS NULL;"
|
|
816
|
+
]
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
"name": "20261029224107_version-38-folded-triggers",
|
|
820
|
+
"statements": [
|
|
821
|
+
"-- A build older than 38, or a write that forgets the copy, changes a name without its folded copy: empty the copy\n-- so the next open refills it (fillFolded) rather than leave a stale one that filters would trust.\nCREATE TRIGGER tasks_title_folded_au AFTER UPDATE OF title ON tasks\n WHEN new.title IS NOT old.title AND new.title_folded IS old.title_folded BEGIN\n UPDATE tasks SET title_folded = NULL WHERE id = new.id;\nEND;",
|
|
822
|
+
"CREATE TRIGGER tasks_close_reason_folded_au AFTER UPDATE OF close_reason ON tasks\n WHEN new.close_reason IS NOT old.close_reason AND new.close_reason_folded IS old.close_reason_folded BEGIN\n UPDATE tasks SET close_reason_folded = NULL WHERE id = new.id;\nEND;",
|
|
823
|
+
"CREATE TRIGGER attachments_name_folded_au AFTER UPDATE OF name ON attachments\n WHEN new.name IS NOT old.name AND new.name_folded IS old.name_folded BEGIN\n UPDATE attachments SET name_folded = NULL WHERE id = new.id;\nEND;",
|
|
824
|
+
"CREATE TRIGGER attachments_local_path_folded_au AFTER UPDATE OF local_path ON attachments\n WHEN new.local_path IS NOT old.local_path AND new.local_path_folded IS old.local_path_folded BEGIN\n UPDATE attachments SET local_path_folded = NULL WHERE id = new.id;\nEND;",
|
|
825
|
+
"CREATE TRIGGER meetings_title_folded_au AFTER UPDATE OF title ON meetings\n WHEN new.title IS NOT old.title AND new.title_folded IS old.title_folded BEGIN\n UPDATE meetings SET title_folded = NULL WHERE id = new.id;\nEND;",
|
|
826
|
+
"CREATE TRIGGER files_path_folded_au AFTER UPDATE OF path ON files\n WHEN new.path IS NOT old.path AND new.path_folded IS old.path_folded BEGIN\n UPDATE files SET path_folded = NULL WHERE id = new.id;\nEND;",
|
|
827
|
+
"CREATE TRIGGER projects_name_folded_au AFTER UPDATE OF name ON projects\n WHEN new.name IS NOT old.name AND new.name_folded IS old.name_folded BEGIN\n UPDATE projects SET name_folded = NULL WHERE id = new.id;\nEND;",
|
|
828
|
+
"CREATE TRIGGER chats_title_folded_au AFTER UPDATE OF title ON chats\n WHEN new.title IS NOT old.title AND new.title_folded IS old.title_folded BEGIN\n UPDATE chats SET title_folded = NULL WHERE id = new.id;\nEND;",
|
|
829
|
+
"CREATE TRIGGER identities_name_folded_au AFTER UPDATE OF name ON identities\n WHEN new.name IS NOT old.name AND new.name_folded IS old.name_folded BEGIN\n UPDATE identities SET name_folded = NULL WHERE id = new.id;\nEND;",
|
|
830
|
+
"CREATE TRIGGER email_threads_subject_folded_au AFTER UPDATE OF subject ON email_threads\n WHEN new.subject IS NOT old.subject AND new.subject_folded IS old.subject_folded BEGIN\n UPDATE email_threads SET subject_folded = NULL WHERE id = new.id;\nEND;",
|
|
831
|
+
"CREATE TRIGGER mailboxes_name_folded_au AFTER UPDATE OF name ON mailboxes\n WHEN new.name IS NOT old.name AND new.name_folded IS old.name_folded BEGIN\n UPDATE mailboxes SET name_folded = NULL WHERE id = new.id;\nEND;"
|
|
832
|
+
]
|
|
800
833
|
}
|
|
801
834
|
];
|
|
@@ -1058,6 +1058,22 @@ export declare const identities: import("drizzle-orm/sqlite-core").SQLiteTableWi
|
|
|
1058
1058
|
identity: undefined;
|
|
1059
1059
|
generated: undefined;
|
|
1060
1060
|
}, {}>;
|
|
1061
|
+
nameFolded: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
1062
|
+
name: string;
|
|
1063
|
+
tableName: "identities";
|
|
1064
|
+
dataType: "string";
|
|
1065
|
+
data: string;
|
|
1066
|
+
driverParam: string;
|
|
1067
|
+
notNull: false;
|
|
1068
|
+
hasDefault: false;
|
|
1069
|
+
isPrimaryKey: false;
|
|
1070
|
+
isAutoincrement: false;
|
|
1071
|
+
hasRuntimeDefault: false;
|
|
1072
|
+
enumValues: [string, ...string[]];
|
|
1073
|
+
baseColumn: never;
|
|
1074
|
+
identity: undefined;
|
|
1075
|
+
generated: undefined;
|
|
1076
|
+
}, {}>;
|
|
1061
1077
|
};
|
|
1062
1078
|
dialect: 'sqlite';
|
|
1063
1079
|
isAlias: false;
|
|
@@ -2725,6 +2741,22 @@ export declare const chats: import("drizzle-orm/sqlite-core").SQLiteTableWithCol
|
|
|
2725
2741
|
identity: undefined;
|
|
2726
2742
|
generated: undefined;
|
|
2727
2743
|
}, {}>;
|
|
2744
|
+
titleFolded: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
2745
|
+
name: string;
|
|
2746
|
+
tableName: "chats";
|
|
2747
|
+
dataType: "string";
|
|
2748
|
+
data: string;
|
|
2749
|
+
driverParam: string;
|
|
2750
|
+
notNull: false;
|
|
2751
|
+
hasDefault: false;
|
|
2752
|
+
isPrimaryKey: false;
|
|
2753
|
+
isAutoincrement: false;
|
|
2754
|
+
hasRuntimeDefault: false;
|
|
2755
|
+
enumValues: [string, ...string[]];
|
|
2756
|
+
baseColumn: never;
|
|
2757
|
+
identity: undefined;
|
|
2758
|
+
generated: undefined;
|
|
2759
|
+
}, {}>;
|
|
2728
2760
|
};
|
|
2729
2761
|
dialect: 'sqlite';
|
|
2730
2762
|
isAlias: false;
|
|
@@ -5184,6 +5216,22 @@ export declare const emailThreads: import("drizzle-orm/sqlite-core").SQLiteTable
|
|
|
5184
5216
|
identity: undefined;
|
|
5185
5217
|
generated: undefined;
|
|
5186
5218
|
}, {}>;
|
|
5219
|
+
subjectFolded: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
5220
|
+
name: string;
|
|
5221
|
+
tableName: "email_threads";
|
|
5222
|
+
dataType: "string";
|
|
5223
|
+
data: string;
|
|
5224
|
+
driverParam: string;
|
|
5225
|
+
notNull: false;
|
|
5226
|
+
hasDefault: false;
|
|
5227
|
+
isPrimaryKey: false;
|
|
5228
|
+
isAutoincrement: false;
|
|
5229
|
+
hasRuntimeDefault: false;
|
|
5230
|
+
enumValues: [string, ...string[]];
|
|
5231
|
+
baseColumn: never;
|
|
5232
|
+
identity: undefined;
|
|
5233
|
+
generated: undefined;
|
|
5234
|
+
}, {}>;
|
|
5187
5235
|
};
|
|
5188
5236
|
dialect: 'sqlite';
|
|
5189
5237
|
isAlias: false;
|
|
@@ -6048,6 +6096,22 @@ export declare const mailboxes: import("drizzle-orm/sqlite-core").SQLiteTableWit
|
|
|
6048
6096
|
identity: undefined;
|
|
6049
6097
|
generated: undefined;
|
|
6050
6098
|
}, {}>;
|
|
6099
|
+
nameFolded: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
6100
|
+
name: string;
|
|
6101
|
+
tableName: "mailboxes";
|
|
6102
|
+
dataType: "string";
|
|
6103
|
+
data: string;
|
|
6104
|
+
driverParam: string;
|
|
6105
|
+
notNull: false;
|
|
6106
|
+
hasDefault: false;
|
|
6107
|
+
isPrimaryKey: false;
|
|
6108
|
+
isAutoincrement: false;
|
|
6109
|
+
hasRuntimeDefault: false;
|
|
6110
|
+
enumValues: [string, ...string[]];
|
|
6111
|
+
baseColumn: never;
|
|
6112
|
+
identity: undefined;
|
|
6113
|
+
generated: undefined;
|
|
6114
|
+
}, {}>;
|
|
6051
6115
|
};
|
|
6052
6116
|
dialect: 'sqlite';
|
|
6053
6117
|
isAlias: false;
|
|
@@ -11851,6 +11915,22 @@ export declare const projects: import("drizzle-orm/sqlite-core").SQLiteTableWith
|
|
|
11851
11915
|
identity: undefined;
|
|
11852
11916
|
generated: undefined;
|
|
11853
11917
|
}, {}>;
|
|
11918
|
+
nameFolded: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
11919
|
+
name: string;
|
|
11920
|
+
tableName: "projects";
|
|
11921
|
+
dataType: "string";
|
|
11922
|
+
data: string;
|
|
11923
|
+
driverParam: string;
|
|
11924
|
+
notNull: false;
|
|
11925
|
+
hasDefault: false;
|
|
11926
|
+
isPrimaryKey: false;
|
|
11927
|
+
isAutoincrement: false;
|
|
11928
|
+
hasRuntimeDefault: false;
|
|
11929
|
+
enumValues: [string, ...string[]];
|
|
11930
|
+
baseColumn: never;
|
|
11931
|
+
identity: undefined;
|
|
11932
|
+
generated: undefined;
|
|
11933
|
+
}, {}>;
|
|
11854
11934
|
};
|
|
11855
11935
|
dialect: 'sqlite';
|
|
11856
11936
|
isAlias: false;
|
|
@@ -101,9 +101,11 @@ export const identities = sqliteTable("identities", {
|
|
|
101
101
|
description: text("description"),
|
|
102
102
|
email: text("email"),
|
|
103
103
|
phone: text("phone"),
|
|
104
|
+
nameFolded: text("name_folded"),
|
|
104
105
|
}, (table) => [
|
|
105
106
|
unique().on(table.provider, table.externalId),
|
|
106
107
|
index("identities_by_email").on(table.email).where(sql `email IS NOT NULL`),
|
|
108
|
+
index("identities_name_to_fold").on(table.id).where(sql `name IS NOT NULL AND name_folded IS NULL`),
|
|
107
109
|
]);
|
|
108
110
|
export const identityLinks = sqliteTable("identity_links", {
|
|
109
111
|
identityId: integer("identity_id")
|
|
@@ -272,9 +274,11 @@ export const chats = sqliteTable("chats", {
|
|
|
272
274
|
archived: integer("archived"),
|
|
273
275
|
markedUnread: integer("marked_unread"),
|
|
274
276
|
unreadCountedAt: integer("unread_counted_at"),
|
|
277
|
+
titleFolded: text("title_folded"),
|
|
275
278
|
}, (table) => [
|
|
276
279
|
unique().on(table.accountId, table.externalId),
|
|
277
280
|
index("chats_by_recency").on(table.accountId, desc(table.lastMessageAt)),
|
|
281
|
+
index("chats_title_to_fold").on(table.id).where(sql `title IS NOT NULL AND title_folded IS NULL`),
|
|
278
282
|
index("chats_by_parent_chat_id").on(table.parentChatId),
|
|
279
283
|
]);
|
|
280
284
|
/** Who is in a chat, as the account last saw it: a list replaces the chat's membership whole. */
|
|
@@ -589,7 +593,11 @@ export const emailThreads = sqliteTable("email_threads", {
|
|
|
589
593
|
createdAt: integer("created_at").notNull(),
|
|
590
594
|
updatedAt: integer("updated_at").notNull(),
|
|
591
595
|
deletedAt: integer("deleted_at"),
|
|
592
|
-
|
|
596
|
+
subjectFolded: text("subject_folded"),
|
|
597
|
+
}, (table) => [
|
|
598
|
+
unique().on(table.accountId, table.externalId),
|
|
599
|
+
index("email_threads_subject_to_fold").on(table.id).where(sql `subject IS NOT NULL AND subject_folded IS NULL`),
|
|
600
|
+
]);
|
|
593
601
|
export const emails = sqliteTable("emails", {
|
|
594
602
|
id: integer("id").primaryKey(),
|
|
595
603
|
accountId: integer("account_id")
|
|
@@ -669,7 +677,12 @@ export const mailboxes = sqliteTable("mailboxes", {
|
|
|
669
677
|
kind: text("kind"),
|
|
670
678
|
createdAt: integer("created_at").notNull(),
|
|
671
679
|
updatedAt: integer("updated_at").notNull(),
|
|
672
|
-
|
|
680
|
+
nameFolded: text("name_folded"),
|
|
681
|
+
}, (table) => [
|
|
682
|
+
unique().on(table.accountId, table.externalId),
|
|
683
|
+
index("mailboxes_by_name_folded").on(table.nameFolded),
|
|
684
|
+
index("mailboxes_name_to_fold").on(table.id).where(sql `name_folded IS NULL`),
|
|
685
|
+
]);
|
|
673
686
|
export const emailMailboxes = sqliteTable("email_mailboxes", {
|
|
674
687
|
emailId: integer("email_id")
|
|
675
688
|
.notNull()
|
|
@@ -1186,7 +1199,10 @@ export const projects = sqliteTable("projects", {
|
|
|
1186
1199
|
createdAt: integer("created_at").notNull(),
|
|
1187
1200
|
updatedAt: integer("updated_at").notNull(),
|
|
1188
1201
|
deletedAt: integer("deleted_at"),
|
|
1202
|
+
nameFolded: text("name_folded"),
|
|
1189
1203
|
}, (table) => [
|
|
1204
|
+
index("projects_by_name_folded").on(table.nameFolded),
|
|
1205
|
+
index("projects_name_to_fold").on(table.id).where(sql `name_folded IS NULL`),
|
|
1190
1206
|
index("projects_by_organization_id").on(table.organizationId),
|
|
1191
1207
|
index("projects_by_account_id").on(table.accountId),
|
|
1192
1208
|
index("projects_by_owner_type_owner_id").on(table.ownerType, table.ownerId),
|
|
@@ -205,7 +205,7 @@ export const tagsOf = ({ database }, key, filter) => {
|
|
|
205
205
|
const parts = [
|
|
206
206
|
{
|
|
207
207
|
type: "chat",
|
|
208
|
-
sql: "SELECT t.name AS tag, g.taggable_type AS type, g.created_at, c.external_id AS chat_id, c.title AS chat_title, " +
|
|
208
|
+
sql: "SELECT t.name AS tag, g.taggable_type AS type, g.created_at, c.external_id AS chat_id, c.title AS chat_title, c.title_folded AS name_folded, " +
|
|
209
209
|
"NULL AS person_id, NULL AS name, NULL AS message_id, g.source, g.taggable_id AS target_id, t.canonical_id AS tag_id, NULL AS task_id " +
|
|
210
210
|
"FROM taggings g JOIN resolved_tags t ON t.id=g.tag_id JOIN chats c ON c.id=g.taggable_id " +
|
|
211
211
|
"JOIN accounts a ON a.id=c.account_id WHERE g.taggable_type='chat' AND a.provider=? AND a.external_id=?",
|
|
@@ -213,7 +213,7 @@ export const tagsOf = ({ database }, key, filter) => {
|
|
|
213
213
|
},
|
|
214
214
|
{
|
|
215
215
|
type: "contact",
|
|
216
|
-
sql: "SELECT t.name AS tag, g.taggable_type AS type, g.created_at, NULL AS chat_id, NULL AS chat_title, " +
|
|
216
|
+
sql: "SELECT t.name AS tag, g.taggable_type AS type, g.created_at, NULL AS chat_id, NULL AS chat_title, i.name_folded AS name_folded, " +
|
|
217
217
|
"i.external_id AS person_id, i.name AS name, NULL AS message_id, g.source, g.taggable_id AS target_id, t.canonical_id AS tag_id, NULL AS task_id " +
|
|
218
218
|
"FROM taggings g JOIN resolved_tags t ON t.id=g.tag_id JOIN identities i ON i.id=g.taggable_id " +
|
|
219
219
|
"WHERE g.taggable_type='identity' AND i.provider=?",
|
|
@@ -221,7 +221,7 @@ export const tagsOf = ({ database }, key, filter) => {
|
|
|
221
221
|
},
|
|
222
222
|
{
|
|
223
223
|
type: "message",
|
|
224
|
-
sql: "SELECT t.name AS tag, g.taggable_type AS type, g.created_at, c.external_id AS chat_id, c.title AS chat_title, " +
|
|
224
|
+
sql: "SELECT t.name AS tag, g.taggable_type AS type, g.created_at, c.external_id AS chat_id, c.title AS chat_title, c.title_folded AS name_folded, " +
|
|
225
225
|
"NULL AS person_id, NULL AS name, m.external_id AS message_id, g.source, g.taggable_id AS target_id, t.canonical_id AS tag_id, NULL AS task_id " +
|
|
226
226
|
"FROM taggings g JOIN resolved_tags t ON t.id=g.tag_id JOIN messages m ON m.id=g.taggable_id JOIN chats c ON c.id=m.chat_id " +
|
|
227
227
|
"JOIN accounts a ON a.id=c.account_id WHERE g.taggable_type='message' AND a.provider=? AND a.external_id=?",
|
|
@@ -229,7 +229,7 @@ export const tagsOf = ({ database }, key, filter) => {
|
|
|
229
229
|
},
|
|
230
230
|
{
|
|
231
231
|
type: "chat",
|
|
232
|
-
sql: "SELECT t.name AS tag, g.taggable_type AS type, g.created_at, th.external_id AS chat_id, th.subject AS chat_title, " +
|
|
232
|
+
sql: "SELECT t.name AS tag, g.taggable_type AS type, g.created_at, th.external_id AS chat_id, th.subject AS chat_title, th.subject_folded AS name_folded, " +
|
|
233
233
|
"NULL AS person_id, NULL AS name, NULL AS message_id, g.source, g.taggable_id AS target_id, t.canonical_id AS tag_id, NULL AS task_id " +
|
|
234
234
|
"FROM taggings g JOIN resolved_tags t ON t.id=g.tag_id JOIN email_threads th ON th.id=g.taggable_id " +
|
|
235
235
|
"JOIN accounts a ON a.id=th.account_id WHERE g.taggable_type='email_thread' AND a.provider=? AND a.external_id=?",
|
|
@@ -237,7 +237,7 @@ export const tagsOf = ({ database }, key, filter) => {
|
|
|
237
237
|
},
|
|
238
238
|
{
|
|
239
239
|
type: "message",
|
|
240
|
-
sql: "SELECT t.name AS tag, g.taggable_type AS type, g.created_at, th.external_id AS chat_id, th.subject AS chat_title, " +
|
|
240
|
+
sql: "SELECT t.name AS tag, g.taggable_type AS type, g.created_at, th.external_id AS chat_id, th.subject AS chat_title, th.subject_folded AS name_folded, " +
|
|
241
241
|
"NULL AS person_id, NULL AS name, e.external_id AS message_id, g.source, g.taggable_id AS target_id, t.canonical_id AS tag_id, NULL AS task_id " +
|
|
242
242
|
"FROM taggings g JOIN resolved_tags t ON t.id=g.tag_id JOIN emails e ON e.id=g.taggable_id " +
|
|
243
243
|
"JOIN email_threads th ON th.id=e.email_thread_id JOIN accounts a ON a.id=e.account_id " +
|
|
@@ -246,7 +246,7 @@ export const tagsOf = ({ database }, key, filter) => {
|
|
|
246
246
|
},
|
|
247
247
|
{
|
|
248
248
|
type: "task",
|
|
249
|
-
sql: "SELECT t.name AS tag, g.taggable_type AS type, g.created_at, NULL AS chat_id, k.title AS chat_title, " +
|
|
249
|
+
sql: "SELECT t.name AS tag, g.taggable_type AS type, g.created_at, NULL AS chat_id, k.title AS chat_title, k.title_folded AS name_folded, " +
|
|
250
250
|
"NULL AS person_id, NULL AS name, NULL AS message_id, g.source, g.taggable_id AS target_id, t.canonical_id AS tag_id, " +
|
|
251
251
|
"COALESCE(k.package_id, k.key) AS task_id " +
|
|
252
252
|
"FROM taggings g JOIN resolved_tags t ON t.id=g.tag_id JOIN tasks k ON k.id=g.taggable_id JOIN projects p ON p.id=k.project_id " +
|
|
@@ -263,7 +263,7 @@ export const tagsOf = ({ database }, key, filter) => {
|
|
|
263
263
|
: [{ sql: filter.source === "auto" ? "x.auto = 1" : "(x.auto = 0 OR x.source = 'owner')", params: [] }]),
|
|
264
264
|
];
|
|
265
265
|
const rows = database
|
|
266
|
-
.prepare("SELECT * FROM (SELECT tag,type,min(created_at) AS created_at,chat_id,chat_title,person_id,name,message_id,CASE WHEN max(source='owner') THEN 'owner' ELSE min(source) END AS source,target_id,tag_id,task_id,tag_type,max(auto) AS auto FROM (SELECT u.*, CASE u.type WHEN 'identity' THEN 'contact' WHEN 'email_thread' THEN 'chat' " +
|
|
266
|
+
.prepare("SELECT * FROM (SELECT tag,type,min(created_at) AS created_at,chat_id,chat_title,person_id,name,name_folded,message_id,CASE WHEN max(source='owner') THEN 'owner' ELSE min(source) END AS source,target_id,tag_id,task_id,tag_type,max(auto) AS auto FROM (SELECT u.*, CASE u.type WHEN 'identity' THEN 'contact' WHEN 'email_thread' THEN 'chat' " +
|
|
267
267
|
"WHEN 'email' THEN 'message' ELSE u.type END AS tag_type, (u.type = 'chat' AND EXISTS (SELECT 1 FROM " +
|
|
268
268
|
`canonical_auto_tag_claims ac JOIN resolved_tags rt ON rt.id=ac.tag_id WHERE ac.chat_id = u.target_id AND rt.canonical_id = u.tag_id)) AS auto FROM (${union}) u) GROUP BY tag,type,target_id) x ` +
|
|
269
269
|
`WHERE ${where.length === 0 ? "1" : where.map(({ sql }) => `(${sql})`).join(" AND ")} ` +
|
|
@@ -40,9 +40,9 @@ export const taskStoreOver = (context) => {
|
|
|
40
40
|
}
|
|
41
41
|
const at = now();
|
|
42
42
|
return Number(database
|
|
43
|
-
.prepare("INSERT INTO projects (key, name, description, type, account_id, scope, tasks_count, status, created_at, updated_at) " +
|
|
44
|
-
"VALUES (?, ?, ?, 'personal', ?, 'personal', 0, 'active', ?, ?) RETURNING id")
|
|
45
|
-
.get(key, account, `What waits on the owner in ${account}`, accountId, at, at)?.id);
|
|
43
|
+
.prepare("INSERT INTO projects (key, name, name_folded, description, type, account_id, scope, tasks_count, status, created_at, updated_at) " +
|
|
44
|
+
"VALUES (?, ?, ?, ?, 'personal', ?, 'personal', 0, 'active', ?, ?) RETURNING id")
|
|
45
|
+
.get(key, account, folded(account), `What waits on the owner in ${account}`, accountId, at, at)?.id);
|
|
46
46
|
};
|
|
47
47
|
// A task made here has no package id and may be a type the package does not know; the package never sees it.
|
|
48
48
|
const select = (where) => database.prepare(`SELECT ${COLUMNS} FROM tasks t JOIN projects p ON p.id = t.project_id WHERE t.package_id IS NOT NULL AND ${where}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.484.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|