@wirecat/cli-core 0.410.0 → 0.412.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 +44 -0
- package/dist/cli/bot/command.js +4 -3
- package/dist/cli/bot/messages-sync.d.ts +7 -0
- package/dist/cli/bot/{store.js → messages-sync.js} +4 -5
- package/dist/cli/bot/port.d.ts +4 -4
- package/dist/cli/bot/setup.js +11 -5
- package/dist/cli/commands-command.js +18 -12
- package/dist/cli/config-command.js +1 -0
- package/dist/cli/contract-evidence.d.ts +26 -0
- package/dist/cli/contract-evidence.js +58 -0
- package/dist/cli/contract-templates.d.ts +4 -0
- package/dist/cli/contract-templates.js +83 -0
- package/dist/cli/discovery-descriptions.d.ts +4 -0
- package/dist/cli/discovery-descriptions.js +4 -0
- package/dist/cli/index.d.ts +21 -55
- package/dist/cli/index.js +18 -55
- package/dist/cli/messenger/chat-answers.d.ts +1 -1
- package/dist/cli/messenger/chat-answers.js +1 -1
- package/dist/cli/messenger/chat-lists.d.ts +1 -1
- package/dist/cli/messenger/chats-command.js +3 -0
- package/dist/cli/messenger/contacts-command.js +23 -15
- package/dist/cli/messenger/guard-commands.d.ts +5 -1
- package/dist/cli/messenger/guard-commands.js +5 -3
- package/dist/cli/messenger/messages-press-command.js +4 -1
- package/dist/cli/messenger/models-command.js +3 -11
- package/dist/cli/messenger/records-command.js +1 -1
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/messenger/tasks-command.js +14 -4
- package/dist/cli/model-catalogue-command.d.ts +15 -0
- package/dist/cli/model-catalogue-command.js +110 -0
- package/dist/cli/moved.js +1 -0
- package/dist/cli/program.js +2 -0
- package/dist/cli/runs/search-command.js +2 -3
- package/dist/cli/runs/search-query.d.ts +1 -1
- package/dist/cli/runs/search-query.js +18 -2
- package/dist/cli/runs/search.d.ts +2 -1
- package/dist/cli/runs/search.js +4 -3
- package/dist/cli/settings.d.ts +2 -0
- package/dist/cli/settings.js +5 -1
- package/dist/cli/shared-command-exports.d.ts +644 -0
- package/dist/cli/shared-command-exports.js +114 -0
- package/dist/cli/shared-contracts.d.ts +1 -0
- package/dist/cli/shared-contracts.js +1 -0
- package/dist/domain/providers.d.ts +25 -3
- package/dist/domain/providers.js +89 -5
- package/dist/entities/chat.d.ts +3 -3
- package/dist/entities/chat.js +2 -2
- package/dist/entities/contact-context.d.ts +8 -8
- package/dist/entities/contact.d.ts +4 -4
- package/dist/entities/index.d.ts +40 -13
- package/dist/entities/index.js +5 -1
- package/dist/entities/knowledge-lists.d.ts +28 -0
- package/dist/entities/knowledge-lists.js +29 -0
- package/dist/entities/list-results.d.ts +1 -1
- package/dist/entities/list-results.js +1 -1
- package/dist/entities/send-attempt.d.ts +0 -1
- package/dist/entities/send-attempt.js +0 -1
- package/dist/errors/reasons.d.ts +1 -1
- package/dist/errors/reasons.js +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/meetings/cli/index.d.ts +1 -1
- package/dist/meetings/cli/index.js +25 -9
- package/dist/meetings/cli/passage.d.ts +0 -1
- package/dist/meetings/cli/passage.js +5 -3
- package/dist/meetings/cli/project-evidence.js +3 -2
- package/dist/meetings/cli/search.d.ts +2 -2
- package/dist/meetings/cli/search.js +9 -7
- package/dist/meetings/event-query.d.ts +1 -0
- package/dist/meetings/event-query.js +15 -0
- package/dist/meetings/index.d.ts +1 -0
- package/dist/meetings/index.js +1 -0
- package/dist/meetings/store.d.ts +2 -0
- package/dist/meetings/testing/memory.js +5 -0
- package/dist/search/account-list.d.ts +5 -2
- package/dist/search/account-list.js +27 -11
- package/dist/search/query.js +3 -1
- package/dist/sends/permission-aliases.js +1 -0
- package/dist/services/documents/knowledge.d.ts +5 -3
- package/dist/services/documents/knowledge.js +4 -4
- package/dist/services/meeting-links.d.ts +3 -1
- package/dist/services/meeting-links.js +2 -2
- package/dist/services/messages-search.js +15 -3
- package/dist/services/search-all.js +5 -2
- package/dist/services/tasks.d.ts +2 -0
- package/dist/services/tasks.js +6 -4
- package/dist/store/accounts.d.ts +3 -0
- package/dist/store/accounts.js +2 -0
- package/dist/store/exports.d.ts +1 -0
- package/dist/store/exports.js +1 -0
- package/dist/store/index.d.ts +3 -1
- package/dist/store/index.js +2 -0
- package/dist/store/knowledge-query.d.ts +6 -0
- package/dist/store/knowledge-query.js +34 -0
- package/dist/store/providers.d.ts +7 -0
- package/dist/store/providers.js +5 -0
- package/dist/store/sqlite/events.d.ts +2 -1
- package/dist/store/sqlite/events.js +16 -1
- package/dist/store/sqlite/knowledge.d.ts +5 -4
- package/dist/store/sqlite/knowledge.js +50 -17
- package/dist/store/sqlite/mail-rules.d.ts +2 -1
- package/dist/store/sqlite/mail-rules.js +12 -2
- package/dist/store/sqlite/meetings.js +3 -0
- package/dist/store/sqlite/notes.d.ts +11 -5
- package/dist/store/sqlite/notes.js +9 -2
- package/dist/store/sqlite/open-store.js +2 -0
- package/dist/store/sqlite/providers.d.ts +12 -0
- package/dist/store/sqlite/providers.js +45 -0
- package/dist/store/sqlite/tags.d.ts +1 -1
- package/dist/store/sqlite/tags.js +3 -3
- package/package.json +3 -3
- package/parity.json +92 -111
- package/dist/cli/bot/store.d.ts +0 -7
|
@@ -47,18 +47,18 @@ export const documentKnowledge = (store, folder) => {
|
|
|
47
47
|
return folderId ? store.notes.importedDocuments(folderId, query, page) : { items: [], hasMore: false };
|
|
48
48
|
},
|
|
49
49
|
document,
|
|
50
|
-
tags: async (principal, ref, action, tags = []) => {
|
|
50
|
+
tags: async (principal, ref, action, tags = [], filter) => {
|
|
51
51
|
const note = await document(principal, ref);
|
|
52
52
|
const target = { type: "document", id: note.id };
|
|
53
53
|
if (action === "add")
|
|
54
54
|
return store.knowledge.addTags(null, target, tags);
|
|
55
55
|
if (action === "remove")
|
|
56
56
|
return store.knowledge.removeTags(null, target, tags);
|
|
57
|
-
return store.knowledge.tags(null, target);
|
|
57
|
+
return store.knowledge.tags(null, target, filter?.tag);
|
|
58
58
|
},
|
|
59
|
-
links: async (principal, ref) => {
|
|
59
|
+
links: async (principal, ref, filter) => {
|
|
60
60
|
await document(principal, ref);
|
|
61
|
-
return store.notes.links({ from: ref });
|
|
61
|
+
return store.notes.links({ ...filter, from: ref });
|
|
62
62
|
},
|
|
63
63
|
addLink: async (principal, ref, input) => {
|
|
64
64
|
await document(principal, ref);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { MeetingChatLinesInput, MeetingHitSearchRequest, MeetingLink } from "../meetings/index.js";
|
|
2
2
|
import type { MessageStore } from "../store/store.js";
|
|
3
3
|
export type { MeetingLink };
|
|
4
|
-
export type MeetingLinksRequest = Omit<MeetingHitSearchRequest, "limit" | "after" | "maxReadBytes"> & Pick<MeetingChatLinesInput, "maxMessages"
|
|
4
|
+
export type MeetingLinksRequest = Omit<MeetingHitSearchRequest, "limit" | "after" | "maxReadBytes"> & Pick<MeetingChatLinesInput, "maxMessages"> & {
|
|
5
|
+
url?: string;
|
|
6
|
+
};
|
|
5
7
|
/** Links shared in the chat of the selected meetings, most shared first; found when read, not stored. */
|
|
6
8
|
export declare const readMeetingLinks: (store: Pick<MessageStore, "meetings"> & {
|
|
7
9
|
knowledge?: MessageStore["knowledge"];
|
|
@@ -3,7 +3,7 @@ import { findMentions } from "../domain/mentions.js";
|
|
|
3
3
|
import { resolveMeetingSelection } from "./meeting-hit-search.js";
|
|
4
4
|
/** Links shared in the chat of the selected meetings, most shared first; found when read, not stored. */
|
|
5
5
|
export const readMeetingLinks = async (store, request) => {
|
|
6
|
-
const { maxMessages, ...selecting } = request;
|
|
6
|
+
const { maxMessages, url, ...selecting } = request;
|
|
7
7
|
const { input, selection } = await resolveMeetingSelection(store, selecting);
|
|
8
8
|
const lines = await store.meetings.meetingChatLines({
|
|
9
9
|
...input,
|
|
@@ -12,7 +12,7 @@ export const readMeetingLinks = async (store, request) => {
|
|
|
12
12
|
const links = new Map();
|
|
13
13
|
for (const line of lines)
|
|
14
14
|
for (const found of findMentions(line.text)) {
|
|
15
|
-
if (found.kind !== "url")
|
|
15
|
+
if (found.kind !== "url" || (url !== undefined && found.value !== url))
|
|
16
16
|
continue;
|
|
17
17
|
const link = links.get(found.value) ?? {
|
|
18
18
|
url: found.value,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { calendarKey } from "../domain/calendar.js";
|
|
3
3
|
import { parseLocator } from "../domain/locator.js";
|
|
4
|
+
import { kindIn, UNKNOWN_PROVIDER } from "../domain/providers.js";
|
|
4
5
|
import { syncsOneChat } from "../ports/traits.js";
|
|
5
6
|
import { PRESET_VERSION } from "../search/lucene/presets.js";
|
|
6
7
|
import { FIELD_VERSION, validateAst, validateFields } from "../search/lucene/registry.js";
|
|
@@ -69,13 +70,24 @@ export const prepareLucene = async (store, account, request, messenger) => {
|
|
|
69
70
|
throw new CliError("validation_error", "a search names at least one account");
|
|
70
71
|
if (request.senders !== undefined && leaves.some(({ field }) => field === "from"))
|
|
71
72
|
throw new CliError("validation_error", "--from and from: together — name the people once");
|
|
72
|
-
const
|
|
73
|
+
const chosen = request.accounts ??
|
|
73
74
|
listed?.accounts ??
|
|
74
75
|
(source !== undefined
|
|
75
76
|
? held.filter(({ provider }) => inSource(source, provider))
|
|
76
77
|
: sources.length
|
|
77
78
|
? held.filter(({ provider }) => sources.some((source) => inSource(source, provider)))
|
|
78
|
-
: [account])
|
|
79
|
+
: [account]);
|
|
80
|
+
// The account running is the app's own: its provider is a messenger whether or not an entry names it.
|
|
81
|
+
const unknown = chosen.filter(({ provider }) => provider !== account.provider && kindIn(provider) === undefined);
|
|
82
|
+
const unknownSkipped = unknown.map(({ provider, account }) => ({
|
|
83
|
+
source: `${provider}:${account}`,
|
|
84
|
+
code: "not_found",
|
|
85
|
+
reason: UNKNOWN_PROVIDER,
|
|
86
|
+
}));
|
|
87
|
+
if (unknown.length > 0 && unknown.length === chosen.length)
|
|
88
|
+
throw new CliError("not_found", `this search reads nothing: ${unknownSkipped.map(({ source }) => source).join(", ")} (${UNKNOWN_PROVIDER})`);
|
|
89
|
+
const skipped = [...(listed?.skipped ?? []), ...unknownSkipped];
|
|
90
|
+
const accounts = accountsOfKind(request.kind, chosen.filter((one) => !unknown.includes(one)), held, source ?? (sources.includes("email") ? "email" : sources[0]), messenger.app?.command, request.mailAccount, listed !== undefined || request.accounts !== undefined);
|
|
79
91
|
const scopeAccounts = accounts.map(({ provider, account }) => ({ provider, account }));
|
|
80
92
|
const chatLookup = scopeAccounts.some(({ provider }) => provider === MAIL) ? withMailThreads(store) : store;
|
|
81
93
|
const mailField = leaves.find(({ field, value }) => MAIL_FIELDS.has(field) &&
|
|
@@ -141,7 +153,7 @@ export const prepareLucene = async (store, account, request, messenger) => {
|
|
|
141
153
|
stemsReady,
|
|
142
154
|
...(stemmer ? { stemming: stemmingOf(ast.root, stemmer) } : unstemmed ? { stemming: unstemmed } : {}),
|
|
143
155
|
scopeAccounts,
|
|
144
|
-
...(
|
|
156
|
+
...(skipped.length ? { skipped } : {}),
|
|
145
157
|
...(selectedChat ? { selectedChat } : {}),
|
|
146
158
|
};
|
|
147
159
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { parseLocator } from "../domain/locator.js";
|
|
3
|
-
import { INTERNAL_NOTES,
|
|
3
|
+
import { INTERNAL_NOTES, kindIn } from "../domain/providers.js";
|
|
4
4
|
import { excerpt } from "./excerpt.js";
|
|
5
5
|
import { MAX_SEARCH_OFFSET, searchStore } from "./messages-archive-search.js";
|
|
6
6
|
import { notesScopeOf, RRF_K, searchNotes } from "./notes-search.js";
|
|
@@ -55,7 +55,10 @@ export const searchAll = async (store, account, request, messenger = {}, searchM
|
|
|
55
55
|
hasMore ||= found.hasMore;
|
|
56
56
|
const read = found.coverage?.accounts ??
|
|
57
57
|
given ??
|
|
58
|
-
(await store.accounts.accounts()).filter(({ provider }) =>
|
|
58
|
+
(await store.accounts.accounts()).filter(({ provider }) => kindIn(provider) === kind);
|
|
59
|
+
for (const one of found.skipped ?? [])
|
|
60
|
+
if (!skipped.some(({ source }) => source === one.source))
|
|
61
|
+
skipped.push(one);
|
|
59
62
|
accounts.push(...read.map(({ provider, account }) => ({ resource: kind, provider, account })));
|
|
60
63
|
return found.items.map((hit) => {
|
|
61
64
|
const at = parseLocator(hit.locator);
|
package/dist/services/tasks.d.ts
CHANGED
|
@@ -60,6 +60,8 @@ export interface TaskList {
|
|
|
60
60
|
* from a message: the message stays a link, so deleting it leaves no copy here.
|
|
61
61
|
*/
|
|
62
62
|
export interface TaskPlan {
|
|
63
|
+
priority?: number | null;
|
|
64
|
+
parent?: string | null;
|
|
63
65
|
title?: string;
|
|
64
66
|
description?: string | null;
|
|
65
67
|
dueAt?: Date | null;
|
package/dist/services/tasks.js
CHANGED
|
@@ -170,7 +170,7 @@ export const tasksService = (deps, categories) => {
|
|
|
170
170
|
const planOf = async (store, id, plan, by) => {
|
|
171
171
|
if (plan.category !== undefined)
|
|
172
172
|
plan = { ...plan, category: await categoryOf(store, plan.category) };
|
|
173
|
-
const open = (await store.tasks.
|
|
173
|
+
const open = (await store.tasks.details(id)).status === "open";
|
|
174
174
|
if (!open && (plan.dueAt != null || plan.nextCheckAt != null))
|
|
175
175
|
throw new CliError("validation_error", "a closed task takes no due date or next check; it stays closed");
|
|
176
176
|
const { assignees, ...fields } = plan;
|
|
@@ -405,10 +405,12 @@ export const tasksService = (deps, categories) => {
|
|
|
405
405
|
await store.tasks.suggestClose(id, null, "owner");
|
|
406
406
|
return cardOf(store, id);
|
|
407
407
|
}),
|
|
408
|
-
plan: (id, plan, by) =>
|
|
409
|
-
|
|
408
|
+
plan: async (id, plan, by) => {
|
|
409
|
+
const store = await deps.store();
|
|
410
|
+
if (!(await store.tasks.projectTask(id)))
|
|
411
|
+
await owned(store, await deps.account(), id);
|
|
410
412
|
return planOf(store, id, plan, by);
|
|
411
|
-
}
|
|
413
|
+
},
|
|
412
414
|
link: async (id, { references = [], chats = [] }, { kind, by }) => {
|
|
413
415
|
const written = references.map((one) => one.trim()).filter(Boolean);
|
|
414
416
|
const relationKind = TASK_RELATION_KINDS.includes(kind);
|
package/dist/store/accounts.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProviderEntry } from "../domain/providers.js";
|
|
1
2
|
import { type AccountGroups, type Profiles } from "../search/account-list.js";
|
|
2
3
|
import type { StoreContext } from "./sqlite/open.js";
|
|
3
4
|
import type { AccountKey, StoredAccount } from "./types.js";
|
|
@@ -29,6 +30,8 @@ export interface AccountsStore extends AccountAccounts {
|
|
|
29
30
|
profiles(): Promise<Profiles>;
|
|
30
31
|
/** Records which account a profile of `app` is; a no-op when the store already says so. */
|
|
31
32
|
saveProfile(app: string, profile: string, key: AccountKey): Promise<void>;
|
|
33
|
+
/** Writes a CLI's provider entry for every tool on this store to read; see `registerProvider`. */
|
|
34
|
+
registerProvider(entry: ProviderEntry): Promise<void>;
|
|
32
35
|
}
|
|
33
36
|
export declare const accountAccountsOver: (context: StoreContext) => AccountAccounts;
|
|
34
37
|
export declare const accountsStoreOver: (context: StoreContext, keyed: AccountAccounts) => AccountsStore;
|
package/dist/store/accounts.js
CHANGED
|
@@ -3,6 +3,7 @@ import { keysOver } from "./keys.js";
|
|
|
3
3
|
import { accountGroupsOf, profilesOf, saveProfile } from "./sqlite/account-groups.js";
|
|
4
4
|
import * as accounts from "./sqlite/accounts.js";
|
|
5
5
|
import { linkOwnIdentities } from "./sqlite/own-identities.js";
|
|
6
|
+
import { registerProvider } from "./sqlite/providers.js";
|
|
6
7
|
import { linkInbox } from "./sqlite/tasks.js";
|
|
7
8
|
import { write } from "./sqlite/transaction.js";
|
|
8
9
|
export const accountAccountsOver = (context) => {
|
|
@@ -34,6 +35,7 @@ export const accountsStoreOver = (context, keyed) => {
|
|
|
34
35
|
accountGroups: async () => accountGroupsOf(database),
|
|
35
36
|
profiles: async () => profilesOf(database),
|
|
36
37
|
saveProfile: async (app, profile, key) => write(database, () => saveProfile(database, app, profile, key, context.now())),
|
|
38
|
+
registerProvider: async (entry) => registerProvider(database, entry, context.now()),
|
|
37
39
|
saveAccountGroup: async (name, members) => write(database, () => {
|
|
38
40
|
const groups = accountGroupsOf(database);
|
|
39
41
|
if (members === undefined)
|
package/dist/store/exports.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type { AccountCounters, CountersStore } from "./counters.js";
|
|
|
8
8
|
export type { AccountMentions, MentionsStore } from "./mentions.js";
|
|
9
9
|
export type { AccountMessages, MessagesStore } from "./messages.js";
|
|
10
10
|
export type { AccountPeople, PeopleStore } from "./people.js";
|
|
11
|
+
export { registerProvider } from "./providers.js";
|
|
11
12
|
export type { QueriesStore } from "./queries.js";
|
|
12
13
|
export type { RosterStore } from "./roster.js";
|
|
13
14
|
export type { SearchIndexStore } from "./search-index.js";
|
package/dist/store/exports.js
CHANGED
package/dist/store/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { FACT_STATUSES, FACT_TYPES, MEMORY_KINDS, MEMORY_SCOPES, MEMORY_STATUSES
|
|
|
3
3
|
export type { TagConsolidationOptions, TagConsolidationPreview, TagConsolidationStore, } from "../domain/tag-consolidation.js";
|
|
4
4
|
export type { TagRedirectIndex, TagRedirectResolution, TagRedirectSnapshot } from "../domain/tag-redirects.js";
|
|
5
5
|
export { createTagRedirectIndex } from "../domain/tag-redirects.js";
|
|
6
|
+
export { note } from "../entities/note.js";
|
|
6
7
|
export type { FindingEvidence, FindingOutcome, MeetingFactFinding, MeetingFinding, MeetingFindingsInput, MeetingFindingsReceipt, MeetingTaskFinding, } from "../meetings/index.js";
|
|
7
8
|
export type { AccountMail, AccountMeetings, AccountStore } from "./account-store.js";
|
|
8
9
|
export type { DocumentTransferOptions, DocumentTransferPreview, DocumentTransferReceipt, } from "./document-transfer/index.js";
|
|
@@ -10,6 +11,7 @@ export { applyDocumentTransfer, previewDocumentTransfer } from "./document-trans
|
|
|
10
11
|
export type { CacheDatabase, CacheStatement, OpenDatabase, SqlValue } from "./driver.js";
|
|
11
12
|
export { PRAGMAS } from "./driver.js";
|
|
12
13
|
export { EVIDENCE_FINDINGS_KIND, type EvidenceFindingsBatch, type EvidenceFindingsDecider, type EvidenceFindingsReceipt, type EvidenceFindingsStore, validateEvidenceFindings, } from "./evidence-findings.js";
|
|
14
|
+
export { ORGANIZATIONS_QUERY, organization, PROJECTS_QUERY, project, REMINDERS_QUERY, reminder, } from "./knowledge-query.js";
|
|
13
15
|
export type { MeetingFindingsBatch, MeetingFindingsStore } from "./meeting-findings.js";
|
|
14
16
|
export { MIGRATIONS, type Migration, migrate } from "./migrations.js";
|
|
15
17
|
export { openCache } from "./open.js";
|
|
@@ -41,7 +43,7 @@ export type { AttachmentInput } from "./sqlite/meetings.js";
|
|
|
41
43
|
export type { Author, MemoriesStore, Memory, MemoryInput } from "./sqlite/memories.js";
|
|
42
44
|
export type { NoteIndexState } from "./sqlite/note-index.js";
|
|
43
45
|
export { type NearestNote, NOTE_FIELDS, type NoteChunkToEmbed, type NoteHit, type NoteQuery, type NoteSearch, } from "./sqlite/note-search.js";
|
|
44
|
-
export type { FileNoteInput, Link, LinkInput, Note, NoteFolder, NotesStore, NoteTag, } from "./sqlite/notes.js";
|
|
46
|
+
export type { FileNoteInput, Link, LinkInput, LinkListFilter, Note, NoteFolder, NotesStore, NoteTag, } from "./sqlite/notes.js";
|
|
45
47
|
export type { PrivateContact, PrivateContactNote } from "./sqlite/private-people.js";
|
|
46
48
|
export type { ProposalInput, ProposedAction, ProposedActionsStore } from "./sqlite/proposed-actions.js";
|
|
47
49
|
export { PROPOSAL_STATUSES } from "./sqlite/proposed-actions.js";
|
package/dist/store/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { FACT_STATUSES, FACT_TYPES, MEMORY_KINDS, MEMORY_SCOPES, MEMORY_STATUSES, TASK_TYPES, WORK_TASK_TYPES, } from "../domain/knowledge.js";
|
|
2
2
|
export { createTagRedirectIndex } from "../domain/tag-redirects.js";
|
|
3
|
+
export { note } from "../entities/note.js";
|
|
3
4
|
export { applyDocumentTransfer, previewDocumentTransfer } from "./document-transfer/index.js";
|
|
4
5
|
export { PRAGMAS } from "./driver.js";
|
|
5
6
|
export { EVIDENCE_FINDINGS_KIND, validateEvidenceFindings, } from "./evidence-findings.js";
|
|
7
|
+
export { ORGANIZATIONS_QUERY, organization, PROJECTS_QUERY, project, REMINDERS_QUERY, reminder, } from "./knowledge-query.js";
|
|
6
8
|
export { MIGRATIONS, migrate } from "./migrations.js";
|
|
7
9
|
export { openCache } from "./open.js";
|
|
8
10
|
export { storePath } from "./path.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type QueryRegistry } from "@wirecat/cli-base/query";
|
|
2
|
+
import { organization, project, reminder } from "../entities/knowledge-lists.js";
|
|
3
|
+
export { organization, project, reminder };
|
|
4
|
+
export declare const ORGANIZATIONS_QUERY: QueryRegistry;
|
|
5
|
+
export declare const PROJECTS_QUERY: QueryRegistry;
|
|
6
|
+
export declare const REMINDERS_QUERY: QueryRegistry;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { queryFieldsOf } from "@wirecat/cli-base/query";
|
|
2
|
+
import { organization, project, reminder } from "../entities/knowledge-lists.js";
|
|
3
|
+
export { organization, project, reminder };
|
|
4
|
+
export const ORGANIZATIONS_QUERY = {
|
|
5
|
+
entity: "organization",
|
|
6
|
+
backend: "sql",
|
|
7
|
+
fields: queryFieldsOf(organization, {
|
|
8
|
+
native: ["id", "kind", "scope", "created_at"],
|
|
9
|
+
examples: { id: "id:1", kind: "kind:company", scope: "scope:work", created_at: "created_at>=2026-01-01" },
|
|
10
|
+
}),
|
|
11
|
+
};
|
|
12
|
+
export const PROJECTS_QUERY = {
|
|
13
|
+
entity: "project",
|
|
14
|
+
backend: "sql",
|
|
15
|
+
fields: queryFieldsOf(project, {
|
|
16
|
+
native: ["id", "key", "type", "organization_id", "scope", "created_at"],
|
|
17
|
+
examples: {
|
|
18
|
+
id: "id:1",
|
|
19
|
+
key: "key:SMP",
|
|
20
|
+
type: "type:work",
|
|
21
|
+
organization_id: "organization_id:1",
|
|
22
|
+
scope: "scope:work",
|
|
23
|
+
created_at: "created_at>=2026-01-01",
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
export const REMINDERS_QUERY = {
|
|
28
|
+
entity: "reminder",
|
|
29
|
+
backend: "sql",
|
|
30
|
+
fields: queryFieldsOf(reminder, {
|
|
31
|
+
native: ["id", "state", "due_at", "revision"],
|
|
32
|
+
examples: { id: "id:1", state: "state:pending", due_at: "due_at>=2026-01-01", revision: "revision>1" },
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProviderEntry } from "../domain/providers.js";
|
|
2
|
+
import type { MessageStore } from "./store.js";
|
|
3
|
+
/**
|
|
4
|
+
* A CLI's entry, written into the store on every run so cli-core in every other CLI knows its provider's kind and
|
|
5
|
+
* aliases. Two entries that disagree on a kind, or an alias another provider already has, are an error.
|
|
6
|
+
*/
|
|
7
|
+
export declare const registerProvider: (store: Pick<MessageStore, "accounts">, entry: ProviderEntry) => Promise<void>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A CLI's entry, written into the store on every run so cli-core in every other CLI knows its provider's kind and
|
|
3
|
+
* aliases. Two entries that disagree on a kind, or an alias another provider already has, are an error.
|
|
4
|
+
*/
|
|
5
|
+
export const registerProvider = (store, entry) => store.accounts.registerProvider(entry);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { QueryAst } from "@wirecat/cli-base/query";
|
|
1
2
|
import type { Event, EventCandidateFilter, EventSeries, NewRecord } from "../../meetings/index.js";
|
|
2
3
|
import type { CacheDatabase } from "../driver.js";
|
|
3
4
|
/** JSON columns keep a missing value as SQL NULL, never the text `null`, so a read gives back what was saved. */
|
|
@@ -12,7 +13,7 @@ export declare const page: (filter: {
|
|
|
12
13
|
limit?: number;
|
|
13
14
|
offset?: number;
|
|
14
15
|
}) => [number, number];
|
|
15
|
-
export declare const events: (database: CacheDatabase) => Event[];
|
|
16
|
+
export declare const events: (database: CacheDatabase, query?: QueryAst) => Event[];
|
|
16
17
|
/** An event without an end occupies its start instant. */
|
|
17
18
|
export declare const eventCandidates: (database: CacheDatabase, filter: EventCandidateFilter) => Event[];
|
|
18
19
|
export declare const createEvent: (database: CacheDatabase, input: NewRecord<Event>, now: number) => Event;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { queryFilter } from "./query-filter.js";
|
|
1
2
|
/** JSON columns keep a missing value as SQL NULL, never the text `null`, so a read gives back what was saved. */
|
|
2
3
|
export const toJson = (value) => value === null || value === undefined ? null : JSON.stringify(value);
|
|
3
4
|
export const fromJson = (value) => (value === null || value === undefined ? null : JSON.parse(String(value)));
|
|
@@ -36,7 +37,21 @@ const seriesOf = (row) => ({
|
|
|
36
37
|
updatedAt: Number(row.updated_at),
|
|
37
38
|
});
|
|
38
39
|
const exists = (database, table, id) => database.prepare(`SELECT 1 FROM ${table} WHERE id = ?`).get(id) !== undefined;
|
|
39
|
-
export const events = (database) =>
|
|
40
|
+
export const events = (database, query) => {
|
|
41
|
+
const where = query &&
|
|
42
|
+
queryFilter(query, {
|
|
43
|
+
id: { type: "num", sql: "id" },
|
|
44
|
+
origin: { type: "enum", sql: "origin" },
|
|
45
|
+
starts_at: { type: "time", sql: "starts_at" },
|
|
46
|
+
ends_at: { type: "time", sql: "ends_at" },
|
|
47
|
+
created_at: { type: "time", sql: "created_at" },
|
|
48
|
+
updated_at: { type: "time", sql: "updated_at" },
|
|
49
|
+
});
|
|
50
|
+
return database
|
|
51
|
+
.prepare(`SELECT * FROM events WHERE deleted_at IS NULL${where ? ` AND (${where.sql})` : ""} ORDER BY id`)
|
|
52
|
+
.all(...(where?.params ?? []))
|
|
53
|
+
.map(eventOf);
|
|
54
|
+
};
|
|
40
55
|
/** An event without an end occupies its start instant. */
|
|
41
56
|
export const eventCandidates = (database, filter) => database
|
|
42
57
|
.prepare(`SELECT e.* FROM events e
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { QueryAst } from "@wirecat/cli-base/query";
|
|
1
2
|
import type { AccountKey } from "../types.js";
|
|
2
3
|
import { RELATION_KINDS } from "./link-kinds.js";
|
|
3
4
|
import { type MeetingProjectsStore } from "./meeting-projects.js";
|
|
@@ -130,7 +131,7 @@ export interface KnowledgeStore extends VocabularyStore, MeetingProjectsStore {
|
|
|
130
131
|
id: string;
|
|
131
132
|
removed: true;
|
|
132
133
|
}>;
|
|
133
|
-
tags(key: KnowledgeScope, target: KnowledgeTarget): Promise<string[]>;
|
|
134
|
+
tags(key: KnowledgeScope, target: KnowledgeTarget, tag?: string): Promise<string[]>;
|
|
134
135
|
addTags(key: KnowledgeScope, target: KnowledgeTarget, tags: string[]): Promise<string[]>;
|
|
135
136
|
removeTags(key: KnowledgeScope, target: KnowledgeTarget, tags: string[]): Promise<string[]>;
|
|
136
137
|
/**
|
|
@@ -163,7 +164,7 @@ export interface KnowledgeStore extends VocabularyStore, MeetingProjectsStore {
|
|
|
163
164
|
name: string;
|
|
164
165
|
scope?: Organization["scope"];
|
|
165
166
|
}): Promise<Organization>;
|
|
166
|
-
organizations(): Promise<Organization[]>;
|
|
167
|
+
organizations(query?: QueryAst): Promise<Organization[]>;
|
|
167
168
|
/** A project; its key is made from the name when none is given. */
|
|
168
169
|
addProject(input: {
|
|
169
170
|
name: string;
|
|
@@ -173,7 +174,7 @@ export interface KnowledgeStore extends VocabularyStore, MeetingProjectsStore {
|
|
|
173
174
|
scope?: Project["scope"];
|
|
174
175
|
description?: string;
|
|
175
176
|
}): Promise<Project>;
|
|
176
|
-
projects(): Promise<Project[]>;
|
|
177
|
+
projects(query?: QueryAst): Promise<Project[]>;
|
|
177
178
|
relate(key: KnowledgeScope, input: {
|
|
178
179
|
from: string;
|
|
179
180
|
to: string;
|
|
@@ -190,7 +191,7 @@ export interface KnowledgeStore extends VocabularyStore, MeetingProjectsStore {
|
|
|
190
191
|
}>;
|
|
191
192
|
confirmRelation(key: KnowledgeScope, id: string): Promise<KnowledgeRelation>;
|
|
192
193
|
schedule(key: AccountKey, task: string, dueAt: string, timezone: string): Promise<Reminder>;
|
|
193
|
-
reminders(key: AccountKey): Promise<Reminder[]>;
|
|
194
|
+
reminders(key: AccountKey, query?: QueryAst): Promise<Reminder[]>;
|
|
194
195
|
cancelReminder(key: AccountKey, id: string): Promise<Reminder>;
|
|
195
196
|
snoozeReminder(key: AccountKey, id: string, dueAt: string, revision: number): Promise<Reminder>;
|
|
196
197
|
claimReminders(key: AccountKey, options?: {
|
|
@@ -9,6 +9,7 @@ import { ownerPerson } from "./actors.js";
|
|
|
9
9
|
import { linkKind, RELATION_KINDS } from "./link-kinds.js";
|
|
10
10
|
import { meetingProjectsStoreOver } from "./meeting-projects.js";
|
|
11
11
|
import { insertLink, linkOf } from "./notes.js";
|
|
12
|
+
import { queryFilter } from "./query-filter.js";
|
|
12
13
|
import { addTags as addThingTags, canonicalTagNamed, createTag, labelsOf, removeTags as removeThingTags, setMainTopic, } from "./tags.js";
|
|
13
14
|
import { TASK_KEY_PREFIXES } from "./task-keys.js";
|
|
14
15
|
import { inboxKey, taskAccountOf, taskIdOf, taskRowOf } from "./tasks.js";
|
|
@@ -153,10 +154,10 @@ export const knowledgeStoreOver = (context) => {
|
|
|
153
154
|
? parseReference(formatReference({ type: "folder", id: target.id, path: target.path ?? null })).path
|
|
154
155
|
: null;
|
|
155
156
|
/** A subfolder's labels: `labelled` links from the folder, anchored at the path, to each tag. */
|
|
156
|
-
const subfolderLabels = (folder, path) => database
|
|
157
|
+
const subfolderLabels = (folder, path, tag) => database
|
|
157
158
|
.prepare("SELECT t.name FROM links l JOIN tags t ON t.id = l.to_id WHERE l.from_type = 'account' AND l.from_id = ? " +
|
|
158
|
-
`AND l.kind = ${linkKind("labelled")} AND l.to_type = 'tag' AND l.anchor = ? ORDER BY t.name`)
|
|
159
|
-
.all(folder, path)
|
|
159
|
+
`AND l.kind = ${linkKind("labelled")} AND l.to_type = 'tag' AND l.anchor = ?${tag === undefined ? "" : " AND t.name = ?"} ORDER BY t.name`)
|
|
160
|
+
.all(folder, path, ...(tag === undefined ? [] : [tag]))
|
|
160
161
|
.map((row) => String(row.name));
|
|
161
162
|
const annotationOf = (row) => {
|
|
162
163
|
const thing = storedThing(row.notable_type, row.notable_id);
|
|
@@ -335,12 +336,15 @@ export const knowledgeStoreOver = (context) => {
|
|
|
335
336
|
database.prepare("DELETE FROM notes WHERE id=?").run(Number(id));
|
|
336
337
|
return { id, removed: true };
|
|
337
338
|
},
|
|
338
|
-
tags: async (key, target) => {
|
|
339
|
+
tags: async (key, target, tag) => {
|
|
339
340
|
const { thing, state } = resolve(key, target);
|
|
340
341
|
if (!thing || state !== "available")
|
|
341
342
|
return [];
|
|
342
343
|
const path = subfolder(target);
|
|
343
|
-
|
|
344
|
+
const name = tag === undefined ? undefined : canonicalTagName(tag);
|
|
345
|
+
return path === null
|
|
346
|
+
? labelsOf(database, thing, name).map(({ tag }) => tag)
|
|
347
|
+
: subfolderLabels(thing.id, path, name);
|
|
344
348
|
},
|
|
345
349
|
addTags: async (key, target, tags) => {
|
|
346
350
|
const thing = available(key, target);
|
|
@@ -418,10 +422,19 @@ export const knowledgeStoreOver = (context) => {
|
|
|
418
422
|
.get(oneOf(kind, ORGANIZATION_KINDS, "an organization's kind"), textOf(name, 200), oneOf(scope, SCOPES, "a scope"), at, at);
|
|
419
423
|
return organizationOf(row);
|
|
420
424
|
},
|
|
421
|
-
organizations: async () =>
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
+
organizations: async (query) => {
|
|
426
|
+
const where = query &&
|
|
427
|
+
queryFilter(query, {
|
|
428
|
+
id: { type: "kw", sql: "CAST(id AS TEXT)" },
|
|
429
|
+
kind: { type: "enum", sql: "kind" },
|
|
430
|
+
scope: { type: "enum", sql: "scope" },
|
|
431
|
+
created_at: { type: "time", sql: "created_at" },
|
|
432
|
+
});
|
|
433
|
+
return database
|
|
434
|
+
.prepare(`SELECT * FROM organizations WHERE deleted_at IS NULL${where ? ` AND (${where.sql})` : ""} ORDER BY name, id LIMIT 500`)
|
|
435
|
+
.all(...(where?.params ?? []))
|
|
436
|
+
.map(organizationOf);
|
|
437
|
+
},
|
|
425
438
|
addProject: async ({ name, key, type = "other", organization, scope = "personal", description }) => write(database, () => {
|
|
426
439
|
const title = textOf(name, 200);
|
|
427
440
|
const organizationId = organization === undefined
|
|
@@ -475,10 +488,21 @@ export const knowledgeStoreOver = (context) => {
|
|
|
475
488
|
.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);
|
|
476
489
|
return projectOf(row);
|
|
477
490
|
}),
|
|
478
|
-
projects: async () =>
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
491
|
+
projects: async (query) => {
|
|
492
|
+
const where = query &&
|
|
493
|
+
queryFilter(query, {
|
|
494
|
+
id: { type: "kw", sql: "CAST(id AS TEXT)" },
|
|
495
|
+
key: { type: "kw", sql: "key" },
|
|
496
|
+
type: { type: "enum", sql: "type" },
|
|
497
|
+
organization_id: { type: "kw", sql: "CAST(organization_id AS TEXT)" },
|
|
498
|
+
scope: { type: "enum", sql: "scope" },
|
|
499
|
+
created_at: { type: "time", sql: "created_at" },
|
|
500
|
+
});
|
|
501
|
+
return database
|
|
502
|
+
.prepare(`SELECT * FROM projects WHERE deleted_at IS NULL${where ? ` AND (${where.sql})` : ""} ORDER BY name, id LIMIT 500`)
|
|
503
|
+
.all(...(where?.params ?? []))
|
|
504
|
+
.map(projectOf);
|
|
505
|
+
},
|
|
482
506
|
relate: async (key, input) => {
|
|
483
507
|
const from = related(key, input.from, true);
|
|
484
508
|
const to = related(key, input.to);
|
|
@@ -553,10 +577,19 @@ export const knowledgeStoreOver = (context) => {
|
|
|
553
577
|
.get(row, accountId, timestamp, timezone, at, at);
|
|
554
578
|
return reminder(key, String(made?.id));
|
|
555
579
|
},
|
|
556
|
-
reminders: async (key) =>
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
580
|
+
reminders: async (key, query) => {
|
|
581
|
+
const where = query &&
|
|
582
|
+
queryFilter(query, {
|
|
583
|
+
id: { type: "kw", sql: "CAST(id AS TEXT)" },
|
|
584
|
+
state: { type: "enum", sql: "state" },
|
|
585
|
+
due_at: { type: "time", sql: "due_at" },
|
|
586
|
+
revision: { type: "num", sql: "revision" },
|
|
587
|
+
});
|
|
588
|
+
return database
|
|
589
|
+
.prepare(`SELECT * FROM reminders WHERE account_id=?${where ? ` AND (${where.sql})` : ""} ORDER BY due_at, id LIMIT 500`)
|
|
590
|
+
.all(account(key), ...(where?.params ?? []))
|
|
591
|
+
.map(reminderOf);
|
|
592
|
+
},
|
|
560
593
|
cancelReminder: async (key, id) => {
|
|
561
594
|
reminder(key, id);
|
|
562
595
|
database
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { QueryAst } from "@wirecat/cli-base/query";
|
|
1
2
|
import type { JsonValue } from "../../meetings/index.js";
|
|
2
3
|
import type { CacheDatabase } from "../driver.js";
|
|
3
4
|
type JsonObject = {
|
|
@@ -36,7 +37,7 @@ export interface MailRuleCount {
|
|
|
36
37
|
}
|
|
37
38
|
export interface MailRulesStore {
|
|
38
39
|
/** Oldest first. */
|
|
39
|
-
list(accountId: number): Promise<MailRule[]>;
|
|
40
|
+
list(accountId: number, query?: QueryAst): Promise<MailRule[]>;
|
|
40
41
|
/** A rule with the same conditions is replaced, and keeps its id and the emails it decided. */
|
|
41
42
|
upsert(accountId: number, rule: MailRuleInput, now: number): Promise<MailRule>;
|
|
42
43
|
/** Clears the rule from the emails it decided. Answers false when no rule has these conditions. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { queryFilter } from "./query-filter.js";
|
|
1
2
|
import { write } from "./transaction.js";
|
|
2
3
|
const sorted = (value) => Array.isArray(value)
|
|
3
4
|
? value.map(sorted)
|
|
@@ -23,8 +24,17 @@ const ruleOf = (row) => ({
|
|
|
23
24
|
// SQLite allows 32766 parameters; two are taken by the account and the rule.
|
|
24
25
|
const CHUNK = 30_000;
|
|
25
26
|
export const mailRulesOver = (database) => ({
|
|
26
|
-
async list(accountId) {
|
|
27
|
-
|
|
27
|
+
async list(accountId, query) {
|
|
28
|
+
const where = query &&
|
|
29
|
+
queryFilter(query, {
|
|
30
|
+
id: { type: "num", sql: "id" },
|
|
31
|
+
protected: { type: "bool", sql: "protected" },
|
|
32
|
+
source: { type: "kw", sql: "source" },
|
|
33
|
+
});
|
|
34
|
+
return database
|
|
35
|
+
.prepare(`SELECT * FROM mail_rules WHERE account_id = ?${where ? ` AND (${where.sql})` : ""} ORDER BY id`)
|
|
36
|
+
.all(accountId, ...(where?.params ?? []))
|
|
37
|
+
.map(ruleOf);
|
|
28
38
|
},
|
|
29
39
|
async upsert(accountId, rule, now) {
|
|
30
40
|
const row = database
|
|
@@ -620,6 +620,9 @@ export const meetingStoreOver = (context) => {
|
|
|
620
620
|
async events() {
|
|
621
621
|
return eventQueries.events(database);
|
|
622
622
|
},
|
|
623
|
+
async queryEvents(query) {
|
|
624
|
+
return eventQueries.events(database, query);
|
|
625
|
+
},
|
|
623
626
|
async eventCandidates(filter) {
|
|
624
627
|
return eventQueries.eventCandidates(database, filter);
|
|
625
628
|
},
|
|
@@ -51,6 +51,16 @@ export interface Link {
|
|
|
51
51
|
confirmed: boolean;
|
|
52
52
|
createdAt: string;
|
|
53
53
|
}
|
|
54
|
+
export interface LinkListFilter {
|
|
55
|
+
from?: string;
|
|
56
|
+
to?: string;
|
|
57
|
+
unresolved?: boolean;
|
|
58
|
+
id?: string;
|
|
59
|
+
kind?: Link["kind"];
|
|
60
|
+
role?: string;
|
|
61
|
+
origin?: Link["origin"];
|
|
62
|
+
confirmed?: boolean;
|
|
63
|
+
}
|
|
54
64
|
export interface LinkInput {
|
|
55
65
|
from: string;
|
|
56
66
|
to?: string;
|
|
@@ -123,11 +133,7 @@ export interface NotesStore extends NoteSearch, ImportedDocuments {
|
|
|
123
133
|
removed: true;
|
|
124
134
|
}>;
|
|
125
135
|
addLink(input: LinkInput): Promise<Link>;
|
|
126
|
-
links(options?:
|
|
127
|
-
from?: string;
|
|
128
|
-
to?: string;
|
|
129
|
-
unresolved?: boolean;
|
|
130
|
-
}): Promise<Link[]>;
|
|
136
|
+
links(options?: LinkListFilter): Promise<Link[]>;
|
|
131
137
|
removeLink(id: string): Promise<{
|
|
132
138
|
id: string;
|
|
133
139
|
removed: true;
|
|
@@ -404,10 +404,17 @@ export const notesStoreOver = (context) => {
|
|
|
404
404
|
const to = pair(options.to);
|
|
405
405
|
if (!from.known || !to.known)
|
|
406
406
|
return [];
|
|
407
|
+
const filters = [
|
|
408
|
+
...(options.id === undefined ? [] : [{ sql: "CAST(id AS TEXT) = ?", value: options.id }]),
|
|
409
|
+
...(options.kind === undefined ? [] : [{ sql: "kind = ?", value: options.kind }]),
|
|
410
|
+
...(options.role === undefined ? [] : [{ sql: "role = ?", value: options.role }]),
|
|
411
|
+
...(options.origin === undefined ? [] : [{ sql: "source = ?", value: options.origin }]),
|
|
412
|
+
...(options.confirmed === undefined ? [] : [{ sql: "confirmed = ?", value: options.confirmed ? 1 : 0 }]),
|
|
413
|
+
];
|
|
407
414
|
return database
|
|
408
415
|
.prepare("SELECT * FROM links WHERE (? IS NULL OR (from_type = ? AND from_id = ?)) AND (? IS NULL OR (to_type = ? AND to_id = ?)) " +
|
|
409
|
-
|
|
410
|
-
.all(from.type, from.type, from.id, to.type, to.type, to.id, options.unresolved ? 1 : 0)
|
|
416
|
+
`AND (? = 0 OR to_id IS NULL)${filters.map(({ sql }) => ` AND (${sql})`).join("")} ORDER BY created_at, id LIMIT 5000`)
|
|
417
|
+
.all(from.type, from.type, from.id, to.type, to.type, to.id, options.unresolved ? 1 : 0, ...filters.map(({ value }) => value))
|
|
411
418
|
.map((row) => linkOf(database, row));
|
|
412
419
|
},
|
|
413
420
|
removeLink: async (id) => write(database, () => {
|
|
@@ -9,6 +9,7 @@ import { backfillNormalized, pendingNormalization } from "./backfill.js";
|
|
|
9
9
|
import { fillFolded } from "./folded.js";
|
|
10
10
|
import { logPruner } from "./log-pruning.js";
|
|
11
11
|
import { openSqlite } from "./open.js";
|
|
12
|
+
import { loadProviders } from "./providers.js";
|
|
12
13
|
import { assertReadCompatible } from "./read-compatibility.js";
|
|
13
14
|
import { stemsState } from "./stems.js";
|
|
14
15
|
import { fillStems, followDefaultStemmers } from "./stems-fill.js";
|
|
@@ -56,6 +57,7 @@ export const openStoreContext = async ({ path, env, now = Date.now, command, rea
|
|
|
56
57
|
fillStems(database, { now });
|
|
57
58
|
logPruner(database, now)();
|
|
58
59
|
}
|
|
60
|
+
loadProviders(database);
|
|
59
61
|
return { ...opened, now, sharedConfig: sharedConfigPath(file, env ?? process.env) };
|
|
60
62
|
}
|
|
61
63
|
catch (error) {
|