@wirecat/cli-core 0.418.0 → 0.420.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 +42 -0
- package/dist/domain/providers.d.ts +20 -2
- package/dist/domain/providers.js +23 -3
- package/dist/domain/tag-consolidation.d.ts +30 -1
- package/dist/entities/index.d.ts +4 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/meetings/calendar-text.d.ts +13 -0
- package/dist/meetings/calendar-text.js +57 -0
- package/dist/meetings/event-store.d.ts +202 -0
- package/dist/meetings/event-store.js +1 -0
- package/dist/meetings/events.js +5 -1
- package/dist/meetings/index.d.ts +3 -0
- package/dist/meetings/index.js +2 -0
- package/dist/meetings/join-key.d.ts +7 -0
- package/dist/meetings/join-key.js +38 -0
- package/dist/meetings/model.d.ts +8 -2
- package/dist/meetings/read-answers.d.ts +6 -2
- package/dist/meetings/read-answers.js +9 -2
- package/dist/meetings/store.d.ts +2 -0
- package/dist/meetings/testing/event-store-contract.d.ts +10 -0
- package/dist/meetings/testing/event-store-contract.js +161 -0
- package/dist/meetings/testing/index.d.ts +1 -0
- package/dist/meetings/testing/index.js +1 -0
- package/dist/parity/bin.js +3 -1
- package/dist/search/account-list.js +39 -7
- package/dist/services/agenda.js +5 -5
- package/dist/services/document-source.d.ts +7 -3
- package/dist/services/document-source.js +24 -12
- package/dist/services/documents/ingestion.d.ts +6 -1
- package/dist/services/documents/ingestion.js +4 -0
- package/dist/services/notes-search.js +3 -2
- package/dist/services/search-all.js +1 -0
- package/dist/services/task-tag-filters.d.ts +3 -0
- package/dist/services/task-tag-filters.js +8 -0
- package/dist/services/tasks.js +5 -9
- package/dist/store/document-transfer/index.js +12 -2
- package/dist/store/index.d.ts +2 -1
- package/dist/store/sqlite/accounts.js +1 -0
- package/dist/store/sqlite/calendar.d.ts +3 -0
- package/dist/store/sqlite/calendar.js +593 -0
- package/dist/store/sqlite/catalog.js +16 -5
- package/dist/store/sqlite/chat-rekey.js +1 -1
- package/dist/store/sqlite/chats.js +2 -2
- package/dist/store/sqlite/context-document-source.js +8 -4
- package/dist/store/sqlite/document-transfer/apply.js +24 -2
- package/dist/store/sqlite/document-transfer/collection.d.ts +19 -0
- package/dist/store/sqlite/document-transfer/collection.js +66 -0
- package/dist/store/sqlite/document-transfer/plan.js +15 -8
- package/dist/store/sqlite/document-transfer/snapshot.js +12 -7
- package/dist/store/sqlite/entity-types.d.ts +5 -1
- package/dist/store/sqlite/entity-types.js +5 -1
- package/dist/store/sqlite/events.d.ts +8 -2
- package/dist/store/sqlite/events.js +36 -12
- package/dist/store/sqlite/files.d.ts +93 -0
- package/dist/store/sqlite/files.js +293 -0
- package/dist/store/sqlite/folded.d.ts +3 -0
- package/dist/store/sqlite/folded.js +1 -0
- package/dist/store/sqlite/imported-documents.js +1 -1
- package/dist/store/sqlite/join-keys.d.ts +8 -0
- package/dist/store/sqlite/join-keys.js +27 -0
- package/dist/store/sqlite/knowledge.js +6 -6
- package/dist/store/sqlite/link-reviews.d.ts +9 -0
- package/dist/store/sqlite/link-reviews.js +12 -0
- package/dist/store/sqlite/lucene-fields.js +8 -6
- package/dist/store/sqlite/manifest.js +11 -0
- package/dist/store/sqlite/meeting-selection.js +1 -1
- package/dist/store/sqlite/meetings.js +6 -1
- package/dist/store/sqlite/migrations.generated.js +143 -0
- package/dist/store/sqlite/note-index.d.ts +8 -1
- package/dist/store/sqlite/note-index.js +14 -0
- package/dist/store/sqlite/note-search.js +6 -5
- package/dist/store/sqlite/notes.d.ts +18 -1
- package/dist/store/sqlite/notes.js +237 -40
- package/dist/store/sqlite/open-store.js +4 -0
- package/dist/store/sqlite/person-links.js +7 -2
- package/dist/store/sqlite/schema/canonical-tags.d.ts +87 -0
- package/dist/store/sqlite/schema/canonical-tags.js +8 -0
- package/dist/store/sqlite/schema/tag-consolidations.d.ts +270 -0
- package/dist/store/sqlite/schema/tag-consolidations.js +34 -0
- package/dist/store/sqlite/schema.d.ts +3589 -1634
- package/dist/store/sqlite/schema.js +191 -13
- package/dist/store/sqlite/tag-consolidation-apply.d.ts +3 -0
- package/dist/store/sqlite/tag-consolidation-apply.js +88 -0
- package/dist/store/sqlite/tag-consolidation-input.d.ts +16 -0
- package/dist/store/sqlite/tag-consolidation-input.js +52 -0
- package/dist/store/sqlite/tag-consolidation.js +51 -66
- package/dist/store/sqlite/tag-creation.js +17 -8
- package/dist/store/sqlite/tag-resolution.d.ts +5 -0
- package/dist/store/sqlite/tag-resolution.js +33 -0
- package/dist/store/sqlite/tags.d.ts +3 -1
- package/dist/store/sqlite/tags.js +70 -50
- package/dist/store/sqlite/tasks.d.ts +2 -0
- package/dist/store/sqlite/tasks.js +6 -1
- package/dist/store/sqlite/things.js +2 -1
- package/dist/store/sqlite/vocabulary.d.ts +1 -0
- package/dist/store/sqlite/vocabulary.js +30 -8
- package/dist/store/store.d.ts +6 -0
- package/dist/store/store.js +10 -0
- package/dist/store/tags.js +2 -2
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,48 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.420.0 — 25.10.2026
|
|
11
|
+
|
|
12
|
+
### Changed — may break callers
|
|
13
|
+
|
|
14
|
+
- **A meeting links to an event by its room, with exclusive ends, and two candidate events make no link** (`eventCandidates`, `linkEvent`; available from the cli-core release that carries store versions 30 to 33). Rooms are compared by `join_key`, never by the raw link with its passcode; an event a calendar entry holds is a candidate though it has no meeting yet; back-to-back events no longer both match. With two or more candidates `linkEvent` links none and creates none, and `MeetingStore.reviewMeetingLink` (optional; the store's own implements it) puts the meeting on the owner's review list. Before, it created a new automatic event: a second event for a meeting the calendar already held.
|
|
15
|
+
- **`Event` gains optional `status`, `allDay` and `sourceDeletedAt`, and `origin` on `Event` and `EventSeries` may be `calendar`** (store version 33; available from the cli-core release that carries store versions 30 to 33). The stored event answer (`events list`, its MCP tool; a strict schema) gains `status`, `all_day` and `source_deleted_at`, and its `origin` takes `calendar`. A caller that switches over `origin` or validates the answer against the old schema must take the new value and fields.
|
|
16
|
+
- **Notes import finds a file by its place in `files`, and a new file with one document's content is that document** (requirements 3.3, rule 2; available from the cli-core release that carries store versions 30 to 33). `saveFileNote`, `renameFileNote`, `deleteFileNotes` and `notes({ folderId })` work through each folder's `files` rows, so one document can live in several folders, each listing it under its own path. A new local file whose content hash equals one document's is a copy of it — a live one, else a deleted one no file holds any more, so a file moved between folders keeps its document in whichever order the folders sync; two such documents give a new document and one review entry; empty text never joins. A document stays live while any of its files is, and its own `account_id` and `external_id` follow the file it shows (the owner's pin, else the latest changed); Drive's documents keep theirs. A file renamed and edited in one sync is a new document, and the old one stays, deleted. `saveFileNote` and `renameFileNote` answer the note as the saving folder sees it: its `folderId` and `path` are that file's.
|
|
17
|
+
- **Reviewed tag consolidation (store version 30)** (available from the cli-core release that carries store versions 30 to 33). Tag and automatic-claim
|
|
18
|
+
tables move behind a structural compatibility boundary with `minCompatible: 30`: `tags` becomes `canonical_tags`
|
|
19
|
+
and `auto_tag_claims` becomes `canonical_auto_tag_claims`, so an older SDK refuses the migrated file, including a
|
|
20
|
+
handle it already holds open. Later migrations must keep that minimum; the manifest refuses one that omits or
|
|
21
|
+
lowers it. Version 30 ships in one breaking release with versions 31 to 33. Consolidated tag groups refuse
|
|
22
|
+
document annotation transfer before copying; existing destinations need current schema maintenance for a
|
|
23
|
+
read-only preview.
|
|
24
|
+
- **Drive documents move from their stand-in notes folder to the `google-drive` account (store version 31, `minCompatible` 31)** (available from the cli-core release that carries store versions 30 to 33). The store moves every Drive file of a `folder` named `google-drive:<principal>` to the `{ provider: "google-drive", account: <principal> }` account by `UPDATE`, so `document:<id>` references, chunks, tags, notes and links keep pointing where they did; the folder row goes, saved profiles `{ provider: "folder", account: "google-drive:<p>" }` become `{ provider: "google-drive", account: "<p>" }`, and group entries `notes:<folder id>` follow to the account's id. A CLI on an older cli-core refuses the store until it is upgraded. `notes.folders()` no longer lists Drive; `notes.documentsAccount(key, create)` and `notes.documentsAccounts()` find it, and `documentIngestion({ provider: "google-drive" })` writes into it (`collectionName` is now optional). `folder:<id>`, `--source drive:<profile>`, `--source google-drive:<principal>` and annotation transfers (`google-drive:<principal>`, from a store on either side of version 31) name the account. Ships in the same release as store version 30.
|
|
25
|
+
- **A deleted document keeps its tags** (store version 32; available from the cli-core release that carries store versions 30 to 33). The version-1 trigger that dropped a document's tags when it was marked deleted is gone; its tags go when the row itself is purged. `knowledge.labelled` and the vocabulary's `usageCount` leave out a deleted document's taggings, and search already did. A document revived by a later sync, such as a file moved to another folder, comes back with its tags.
|
|
26
|
+
- **Notes folders are accounts of provider `obsidian` (a vault) or `local` (a plain folder), no longer `folder`** (store version 32; available from the cli-core release that carries store versions 30 to 33). The migration renames each by its `settings.format`; A Drive stand-in folder (`google-drive:<principal>`) that version 31 left behind keeps `folder`. `FOLDER_PROVIDER` is replaced by `FOLDER_PROVIDERS` and `isFolderProvider`, both exported from the package root; `local` and `obsidian` are built-in providers of kind `notes`, and `--source obsidian` / `--source local` name the folders of that provider. Saved profile keys and annotation export files that say `folder` still resolve and import. **Version 32 raises `minCompatible` to 32**: a CLI on an older cli-core refuses the store and says to upgrade, instead of finding no folders and importing them a second time.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **`knowledge.applyTagConsolidation`** (available from the cli-core release that carries store versions 30 to 33) applies an owner-reviewed complete preview
|
|
31
|
+
atomically, retains independent assignment/claim/alias/link provenance, follows inherited names in reads and
|
|
32
|
+
search, and returns a text-free idempotent receipt. Label removal respects requested origin scope across retained
|
|
33
|
+
members. [The consolidation guide](docs/dev/TAG-CONSOLIDATION.md) describes review, compatibility and the explicit
|
|
34
|
+
transfer limitation.
|
|
35
|
+
- **Files: the source records of documents** (store version 32; available from the cli-core release that carries store versions 30 to 33). A new `files` table holds one row per item in one place: a file under a local folder or an Obsidian vault, a Drive file or folder. Each row has the item's `path` from its account's root, a `parent_id` to its folder's own row (local folders get rows made from the path), `path_folded` for prefix and substring search, and the `document_id` it is a copy of. A Drive item is keyed by its Drive file id and takes its link, type, size, edit time and version from the document's columns or, for rows saved before those columns, from its front matter; a Markdown file's front matter is `metadata.front_matter`. Every documents writer in cli-core keeps its row current (`saveFileNote`, `renameFileNote`, `deleteFileNotes`, `catalog.upsert`, `catalog.reconcile`, annotation transfers), and the first open after the upgrade writes the rows of every existing document in short batches, resuming where it stopped. `documents` stays the entity: no document is re-inserted and every `document:<id>` keeps working.
|
|
36
|
+
- **The owner pins the file a document shows** (store version 32; same release). `documents.pinned_file_id` (nullable) holds the owner's choice; `store.files.pin(document, fileId)` takes one of the document's own files and `store.files.unpin(document)` clears it, so the document shows its most recently changed file again. `FilesStore` from `@wirecat/cli-core/store`.
|
|
37
|
+
- **`EventStore`, the calendar's store** (`store.calendar`; types from `@wirecat/cli-core/meetings`; available from the cli-core release that carries store versions 30 to 33). `saveCalendars` (a calendar the owner owns syncs, others are listed), `calendars`, `setCalendarSynced`, `saveEntries` (one sync page in one transaction, matched by rules 1–5 of requirements 3.4: the same calendar and id; the same series slot, so a plain occurrence and its exception are one entry, in whichever order the series and its occurrences arrive; another calendar's copy by iCalUID; a Zoom meeting's automatic event by room and overlapping time, which the entry adopts as origin `calendar`; else a new event), `markUnseen` (after a full list; plain occurrences only inside the window fetched), `events(filter)` by time, account, calendar or person, `event(id)` with its entries, participants and revisions, `linkEntry` / `unlinkEntry` (the owner's, which no rule replaces), `reviews` and `resolveReview` (choose a candidate, or none of them). An event shows its owner-organized copy, else the latest edited; it is cancelled only when every live entry is. A deleted single event, occurrence or whole series keeps its past as history (`source_deleted_at`) and cancels its future. Each change of an entry's or meeting's event is logged in `entity_link_events`. Text is stored with HTML reduced to text, passcode, password and PIN lines dropped and every link without its query string; `metadata` keeps only `ENTRY_METADATA_KEYS`. `eventStoreContract` (`@wirecat/cli-core/meetings/testing`) runs the rules against any `EventStore`.
|
|
38
|
+
- **Calendars in the store** (store version 33; available from the cli-core release that carries store versions 30 to 33). `calendars` (an account's calendars, `synced` for those whose entries sync), `calendar_entries` (one calendar's copy of a single event, a series or one occurrence: the source records of `events` and `event_series`, with `ical_uid`, `original_starts_at`, `recurrence`, `recurrence_ends_at` — NULL for a series that repeats indefinitely —, `join_url` without its query string and `join_key`; a trigger refuses an entry that ends before it starts), `calendar_entry_revisions` (tracked fields before a change) and `calendar_entry_participants` (who an entry lists, by address). Events gain `status`, `all_day` and `source_deleted_at` (below). Events have a words index of their own (`event_words`, `event_stems`, queue `event_index_pending`) over title, description, location and attendee names, leaving out working-location, focus-time, out-of-office and birthday entries; every existing event is queued. Nothing writes calendars yet: `EventStore` comes next.
|
|
39
|
+
- **`meetings.join_key`** (store version 33; same release): the room a meeting is held in without its passcode — `zoom:<number>`, `meet:<code>`, else host and path — written with every meeting and filled on open for those stored before, so a calendar entry can be matched to its Zoom meeting by room rather than by a link carrying `pwd=`. `joinKeyOf` and `joinUrlWithoutQuery` (`src/meetings/join-key.ts`).
|
|
40
|
+
- **`store.files`, where each document is kept** (available from the cli-core release that carries store versions 30 to 33). `list(accountId, { under, pathContains, kind, includeDeleted, limit, offset })` reads one account's files by path: `under` is a subtree, one range over the folded path's index; `pathContains` is any case and accent. `byPath(accountId, path)` and `ofDocument(reference)`, which answers the most recently changed live file first: the one the document shows. Types `FilesStore`, `StoredFile` and `FileListQuery` from `@wirecat/cli-core/store`. The account is always named; there is no default place.
|
|
41
|
+
- **Search hits read a document's link and edit time from its file** (same release): `search notes`, `search all` and context evidence prefer the preferred file's `url` and `external_updated_at`, then the document's columns, then Drive's front matter. A document whose account's provider is registered with kind `documents` is a document with no front matter, labelled with the command whose profile names that account.
|
|
42
|
+
- **`identities.email`** (store version 32; same release). A mail contact's address, lower case, filled for every existing mail contact and by a trigger for every new one, whatever writes it. A contact saved with an address (`savePeople`'s `email`, a Drive owner or editor) carries it too, and the same-email join finds the mail contact by this column.
|
|
43
|
+
- **`entity_link_events` and `link_reviews`** (store version 32; same release): the log of every change of the entity a source record belongs to (each file's link to its document now, method `import`; calendar entries and meetings to events next), and the owner's review list of source records with two equally good entities, which nothing writes yet.
|
|
44
|
+
- **`NoteFolder.provider`**: `obsidian`, `local`, or `folder` for Drive's stand-in folders.
|
|
45
|
+
|
|
46
|
+
## 0.419.0 — 25.10.2026
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **`cli-core-parity` reads a large piped program again.** `<cli> commands --json | cli-core-parity <cli>` stopped with `EAGAIN: resource temporarily unavailable, read` once the program was larger than the pipe buffer and the writer was still writing: a module it loads makes a piped stdin non-blocking, and the tool read stdin with a call that cannot wait for more. It now reads stdin as a stream to the end.
|
|
51
|
+
|
|
10
52
|
## 0.418.0 — 25.10.2026
|
|
11
53
|
|
|
12
54
|
### Added
|
|
@@ -11,8 +11,26 @@ export interface ProviderEntry {
|
|
|
11
11
|
}
|
|
12
12
|
/** The seed: what cli-core knows before any CLI has written its entry into the store. */
|
|
13
13
|
export declare const PROVIDERS: readonly ProviderEntry[];
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* A notes folder is an `accounts` row of one of these providers; people call every one `notes`. `obsidian` is a vault,
|
|
16
|
+
* `local` a plain folder (store version 32). `folder` is what they were before, and what Drive's stand-in folders and
|
|
17
|
+
* profile keys saved before that still say.
|
|
18
|
+
*/
|
|
19
|
+
export declare const FOLDER_PROVIDERS: readonly ["local", "obsidian", "folder"];
|
|
20
|
+
export declare const isFolderProvider: (provider: string) => boolean;
|
|
21
|
+
/** The same test in SQL, over an `accounts` row's `provider` column. */
|
|
22
|
+
export declare const folderProviderSql: (column?: string) => string;
|
|
23
|
+
/** The provider a new folder of this format gets. */
|
|
24
|
+
export declare const folderProviderOf: (format: "obsidian" | "markdown") => "obsidian" | "local";
|
|
25
|
+
/**
|
|
26
|
+
* Providers whose account holds documents the way a notes folder does: one account per remote principal, its
|
|
27
|
+
* files as `documents` rows (store version 31 moved Drive here from a `folder` named `google-drive:<principal>`).
|
|
28
|
+
* The provider registry's `documents` kind replaces this list when Drive leaves `notes` (Wave 3 R2).
|
|
29
|
+
*/
|
|
30
|
+
export declare const DOCUMENT_ACCOUNT_PROVIDERS: readonly ["google-drive"];
|
|
31
|
+
export declare const isDocumentAccountProvider: (provider: string) => boolean;
|
|
32
|
+
/** A folder or a documents account, in SQL over an `accounts` row's `provider` column. */
|
|
33
|
+
export declare const filesAccountSql: (column?: string) => string;
|
|
16
34
|
/** Notes written in memo have no account row; this is their account. */
|
|
17
35
|
export declare const INTERNAL_NOTES = "internal";
|
|
18
36
|
/** The reason a search gives for an account whose provider no entry names. */
|
package/dist/domain/providers.js
CHANGED
|
@@ -11,10 +11,30 @@ export const PROVIDERS = [
|
|
|
11
11
|
{ name: "whatsapp-bot", kind: "messages", aliases: ["wa-bot"] },
|
|
12
12
|
{ name: "email", kind: "mail", aliases: [] },
|
|
13
13
|
{ name: "notes", kind: "notes", aliases: [] },
|
|
14
|
+
{ name: "local", kind: "notes", aliases: [] },
|
|
15
|
+
{ name: "obsidian", kind: "notes", aliases: [] },
|
|
14
16
|
{ name: "zoom", kind: "meetings", aliases: [] },
|
|
15
17
|
];
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
+
/**
|
|
19
|
+
* A notes folder is an `accounts` row of one of these providers; people call every one `notes`. `obsidian` is a vault,
|
|
20
|
+
* `local` a plain folder (store version 32). `folder` is what they were before, and what Drive's stand-in folders and
|
|
21
|
+
* profile keys saved before that still say.
|
|
22
|
+
*/
|
|
23
|
+
export const FOLDER_PROVIDERS = ["local", "obsidian", "folder"];
|
|
24
|
+
export const isFolderProvider = (provider) => FOLDER_PROVIDERS.includes(provider);
|
|
25
|
+
/** The same test in SQL, over an `accounts` row's `provider` column. */
|
|
26
|
+
export const folderProviderSql = (column = "provider") => `${column} IN (${FOLDER_PROVIDERS.map((name) => `'${name}'`).join(", ")})`;
|
|
27
|
+
/** The provider a new folder of this format gets. */
|
|
28
|
+
export const folderProviderOf = (format) => format === "markdown" ? "local" : "obsidian";
|
|
29
|
+
/**
|
|
30
|
+
* Providers whose account holds documents the way a notes folder does: one account per remote principal, its
|
|
31
|
+
* files as `documents` rows (store version 31 moved Drive here from a `folder` named `google-drive:<principal>`).
|
|
32
|
+
* The provider registry's `documents` kind replaces this list when Drive leaves `notes` (Wave 3 R2).
|
|
33
|
+
*/
|
|
34
|
+
export const DOCUMENT_ACCOUNT_PROVIDERS = ["google-drive"];
|
|
35
|
+
export const isDocumentAccountProvider = (provider) => DOCUMENT_ACCOUNT_PROVIDERS.includes(provider);
|
|
36
|
+
/** A folder or a documents account, in SQL over an `accounts` row's `provider` column. */
|
|
37
|
+
export const filesAccountSql = (column = "provider") => `${column} IN (${[...FOLDER_PROVIDERS, ...DOCUMENT_ACCOUNT_PROVIDERS].map((name) => `'${name}'`).join(", ")})`;
|
|
18
38
|
/** Notes written in memo have no account row; this is their account. */
|
|
19
39
|
export const INTERNAL_NOTES = "internal";
|
|
20
40
|
/** The reason a search gives for an account whose provider no entry names. */
|
|
@@ -105,6 +125,6 @@ export const providerNamed = (word) => {
|
|
|
105
125
|
return registry.find((entry) => wordsOf(entry).includes(wanted));
|
|
106
126
|
};
|
|
107
127
|
/** The kind of a provider the registry knows; `undefined` for one no entry names, which a search skips. */
|
|
108
|
-
export const kindIn = (provider) => provider
|
|
128
|
+
export const kindIn = (provider) => isFolderProvider(provider) ? "notes" : registry.find(({ name }) => name === provider)?.kind;
|
|
109
129
|
/** A provider the registry does not list counts as a messenger here; a search asks `kindIn` and skips it. */
|
|
110
130
|
export const kindOf = (provider) => kindIn(provider) ?? "messages";
|
|
@@ -16,6 +16,8 @@ export interface TagConsolidationPreview {
|
|
|
16
16
|
id: string;
|
|
17
17
|
name: string;
|
|
18
18
|
};
|
|
19
|
+
sourceMemberIds: string[];
|
|
20
|
+
survivorMemberIds: string[];
|
|
19
21
|
vocabularyFingerprint: string;
|
|
20
22
|
previewFingerprint: string;
|
|
21
23
|
assignments: {
|
|
@@ -69,9 +71,36 @@ export interface TagConsolidationPreview {
|
|
|
69
71
|
rows: number;
|
|
70
72
|
readBytes: number;
|
|
71
73
|
samplesComplete: boolean;
|
|
72
|
-
applySupported:
|
|
74
|
+
applySupported: true;
|
|
73
75
|
};
|
|
74
76
|
}
|
|
75
77
|
export interface TagConsolidationStore {
|
|
76
78
|
previewTagConsolidation(options: TagConsolidationOptions): Promise<TagConsolidationPreview>;
|
|
79
|
+
applyTagConsolidation(options: TagConsolidationApplyOptions): Promise<TagConsolidationReceipt>;
|
|
80
|
+
}
|
|
81
|
+
export interface TagConsolidationApplyOptions extends Omit<TagConsolidationOptions, "sampleLimit"> {
|
|
82
|
+
by: "owner";
|
|
83
|
+
expectedPreviewFingerprint: string;
|
|
84
|
+
idempotencyKey: string;
|
|
85
|
+
}
|
|
86
|
+
export interface TagConsolidationReceipt {
|
|
87
|
+
id: string;
|
|
88
|
+
sourceId: string;
|
|
89
|
+
survivorId: string;
|
|
90
|
+
sourceMemberIds: string[];
|
|
91
|
+
survivorMemberIds: string[];
|
|
92
|
+
actor: {
|
|
93
|
+
type: "person";
|
|
94
|
+
id: string;
|
|
95
|
+
};
|
|
96
|
+
createdAt: string;
|
|
97
|
+
idempotencyKey: string;
|
|
98
|
+
vocabularyFingerprint: string;
|
|
99
|
+
previewFingerprint: string;
|
|
100
|
+
counts: {
|
|
101
|
+
assignments: number;
|
|
102
|
+
aliases: number;
|
|
103
|
+
automaticClaims: number;
|
|
104
|
+
links: number;
|
|
105
|
+
};
|
|
77
106
|
}
|
package/dist/entities/index.d.ts
CHANGED
|
@@ -6783,7 +6783,10 @@ export declare const ENTITIES: {
|
|
|
6783
6783
|
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
6784
6784
|
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
6785
6785
|
readonly timezone: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
6786
|
-
readonly origin: v.PicklistSchema<["auto", "owner"], undefined>;
|
|
6786
|
+
readonly origin: v.PicklistSchema<["auto", "owner", "calendar"], undefined>;
|
|
6787
|
+
readonly status: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
6788
|
+
readonly all_day: v.BooleanSchema<undefined>;
|
|
6789
|
+
readonly source_deleted_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
6787
6790
|
readonly deleted_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
6788
6791
|
readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
6789
6792
|
readonly updated_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { parseMarkdownSpans } from "./domain/markdown-spans.js";
|
|
|
8
8
|
export type { MessageLink, MessagePermalink } from "./domain/message-link.js";
|
|
9
9
|
export type * from "./domain/models.js";
|
|
10
10
|
export { countedPoll } from "./domain/polls.js";
|
|
11
|
-
export { isKind, KINDS, type Kind, kindIn, kindOf, PROVIDERS, type ProviderEntry, providerNamed, providers, UNKNOWN_PROVIDER, useProviders, } from "./domain/providers.js";
|
|
11
|
+
export { FOLDER_PROVIDERS, isFolderProvider, isKind, KINDS, type Kind, kindIn, kindOf, PROVIDERS, type ProviderEntry, providerNamed, providers, UNKNOWN_PROVIDER, useProviders, } from "./domain/providers.js";
|
|
12
12
|
export type { RankingGraphEvidence, RankingGraphLink } from "./domain/rankings-graph.js";
|
|
13
13
|
export type { RankingComponent, RankingInput, RankingMeasure, RankingOptions, RankingTarget, RankingWeights, ScorePreset, } from "./domain/rankings-options.js";
|
|
14
14
|
export { rankingOptions } from "./domain/rankings-options.js";
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { formatLocator, isLocator, parseLocator } from "./domain/locator.js";
|
|
|
5
5
|
export { parseMarkdown } from "./domain/markdown.js";
|
|
6
6
|
export { parseMarkdownSpans } from "./domain/markdown-spans.js";
|
|
7
7
|
export { countedPoll } from "./domain/polls.js";
|
|
8
|
-
export { isKind, KINDS, kindIn, kindOf, PROVIDERS, providerNamed, providers, UNKNOWN_PROVIDER, useProviders, } from "./domain/providers.js";
|
|
8
|
+
export { FOLDER_PROVIDERS, isFolderProvider, isKind, KINDS, kindIn, kindOf, PROVIDERS, providerNamed, providers, UNKNOWN_PROVIDER, useProviders, } from "./domain/providers.js";
|
|
9
9
|
export { rankingOptions } from "./domain/rankings-options.js";
|
|
10
10
|
export { canonicalReference, formatReference, parseReference } from "./domain/references.js";
|
|
11
11
|
export { normalizeTag, TAG_TYPES } from "./domain/tags.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Every web link without its query string or fragment, which is where passcodes ride (`pwd=`). */
|
|
2
|
+
export declare const withoutLinkQueries: (text: string) => string;
|
|
3
|
+
/**
|
|
4
|
+
* A calendar's text as the store keeps it: HTML reduced to text, links without their query strings, and each line
|
|
5
|
+
* naming a passcode, password or PIN dropped — except from a title, where "PIN reset review" is a meeting's name.
|
|
6
|
+
* `null` when nothing is left.
|
|
7
|
+
*/
|
|
8
|
+
export declare const calendarText: (text: string | null | undefined, { dropSecretLines }?: {
|
|
9
|
+
dropSecretLines?: boolean;
|
|
10
|
+
}) => string | null;
|
|
11
|
+
/** The provider fields a calendar entry may keep in `metadata`: none of them can open a meeting. */
|
|
12
|
+
export declare const ENTRY_METADATA_KEYS: readonly ["colorId", "guestsCanInviteOthers", "guestsCanModify", "guestsCanSeeOtherGuests", "hangoutLink", "locked", "privateCopy", "recurring_event_id"];
|
|
13
|
+
export declare const entryMetadata: (metadata: Record<string, unknown> | null | undefined) => Record<string, unknown>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { joinUrlWithoutQuery } from "./join-key.js";
|
|
2
|
+
const ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', "#39": "'", apos: "'", nbsp: " " };
|
|
3
|
+
/**
|
|
4
|
+
* A line that gives a way into a meeting: a passcode, a password, a PIN or an access code. Bounded by letters and
|
|
5
|
+
* digits, not `\b`, which knows only ASCII and would never match a Cyrillic word.
|
|
6
|
+
*/
|
|
7
|
+
const SECRET_LINE = /(?<![\p{L}\p{N}])(pass ?codes?|passwords?|pwd|pins?|access ?codes?|meeting ?codes?|kennwort|пароль|пин-?код|код доступа)(?![\p{L}\p{N}])|pwd=/iu;
|
|
8
|
+
const htmlToText = (html) => html
|
|
9
|
+
.replace(/<\s*br\s*\/?>/giu, "\n")
|
|
10
|
+
.replace(/<\/\s*(p|div|li|tr|h[1-6])\s*>/giu, "\n")
|
|
11
|
+
.replace(/<a\s[^>]*href\s*=\s*"([^"]*)"[^>]*>/giu, " $1 ")
|
|
12
|
+
.replace(/<[^>]+>/gu, "")
|
|
13
|
+
.replace(/&(amp|lt|gt|quot|#39|apos|nbsp);/gu, (_, name) => ENTITIES[name] ?? "");
|
|
14
|
+
/** Every web link without its query string or fragment, which is where passcodes ride (`pwd=`). */
|
|
15
|
+
export const withoutLinkQueries = (text) => text.replace(/https?:\/\/[^\s<>"')\]]+/giu, (url) => joinUrlWithoutQuery(url) ?? "");
|
|
16
|
+
/**
|
|
17
|
+
* A calendar's text as the store keeps it: HTML reduced to text, links without their query strings, and each line
|
|
18
|
+
* naming a passcode, password or PIN dropped — except from a title, where "PIN reset review" is a meeting's name.
|
|
19
|
+
* `null` when nothing is left.
|
|
20
|
+
*/
|
|
21
|
+
export const calendarText = (text, { dropSecretLines = true } = {}) => {
|
|
22
|
+
if (!text)
|
|
23
|
+
return null;
|
|
24
|
+
const kept = htmlToText(text)
|
|
25
|
+
.split(/\r?\n/u)
|
|
26
|
+
.map((line) => withoutLinkQueries(line)
|
|
27
|
+
.replace(/[ \t]+/gu, " ")
|
|
28
|
+
.trim())
|
|
29
|
+
.filter((line) => !dropSecretLines || !SECRET_LINE.test(line));
|
|
30
|
+
const joined = kept
|
|
31
|
+
.join("\n")
|
|
32
|
+
.replace(/\n{3,}/gu, "\n\n")
|
|
33
|
+
.trim();
|
|
34
|
+
return joined || null;
|
|
35
|
+
};
|
|
36
|
+
/** The provider fields a calendar entry may keep in `metadata`: none of them can open a meeting. */
|
|
37
|
+
export const ENTRY_METADATA_KEYS = [
|
|
38
|
+
"colorId",
|
|
39
|
+
"guestsCanInviteOthers",
|
|
40
|
+
"guestsCanModify",
|
|
41
|
+
"guestsCanSeeOtherGuests",
|
|
42
|
+
"hangoutLink",
|
|
43
|
+
"locked",
|
|
44
|
+
"privateCopy",
|
|
45
|
+
"recurring_event_id",
|
|
46
|
+
];
|
|
47
|
+
export const entryMetadata = (metadata) => {
|
|
48
|
+
const kept = {};
|
|
49
|
+
for (const key of ENTRY_METADATA_KEYS) {
|
|
50
|
+
const value = metadata?.[key];
|
|
51
|
+
if (typeof value === "string")
|
|
52
|
+
kept[key] = withoutLinkQueries(value);
|
|
53
|
+
else if (typeof value === "number" || typeof value === "boolean")
|
|
54
|
+
kept[key] = value;
|
|
55
|
+
}
|
|
56
|
+
return kept;
|
|
57
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { Event } from "./model.js";
|
|
2
|
+
/** One calendar as a provider lists it. */
|
|
3
|
+
export interface CalendarInput {
|
|
4
|
+
externalId: string;
|
|
5
|
+
title: string | null;
|
|
6
|
+
timezone: string | null;
|
|
7
|
+
/** owner, writer, reader, freeBusyReader. A calendar the owner owns syncs by default. */
|
|
8
|
+
accessRole: string | null;
|
|
9
|
+
primary: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface StoredCalendar {
|
|
12
|
+
id: number;
|
|
13
|
+
accountId: number;
|
|
14
|
+
externalId: string;
|
|
15
|
+
title: string | null;
|
|
16
|
+
timezone: string | null;
|
|
17
|
+
accessRole: string | null;
|
|
18
|
+
primary: boolean;
|
|
19
|
+
synced: boolean;
|
|
20
|
+
seenAt: number | null;
|
|
21
|
+
deletedAt: number | null;
|
|
22
|
+
}
|
|
23
|
+
export interface EntryParticipantInput {
|
|
24
|
+
email: string;
|
|
25
|
+
name?: string | null;
|
|
26
|
+
response?: string | null;
|
|
27
|
+
optional?: boolean;
|
|
28
|
+
organizer?: boolean;
|
|
29
|
+
self?: boolean;
|
|
30
|
+
/** A room or a piece of equipment: listed, never a contact. */
|
|
31
|
+
resource?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export type EntryKind = "single" | "series" | "occurrence";
|
|
34
|
+
export type EntryStatus = "confirmed" | "tentative" | "cancelled";
|
|
35
|
+
/**
|
|
36
|
+
* One calendar entry as the provider returns it. Times are epoch milliseconds, `endsAt` exclusive. Text is cleaned
|
|
37
|
+
* before it is stored; there is no field for a conference's entry points, so no passcode or PIN can be given.
|
|
38
|
+
*/
|
|
39
|
+
export interface EntryInput {
|
|
40
|
+
externalId: string;
|
|
41
|
+
kind: EntryKind;
|
|
42
|
+
/** An occurrence's series, by the series entry's `externalId` (Google's `recurringEventId`). */
|
|
43
|
+
seriesExternalId?: string | null;
|
|
44
|
+
icalUid?: string | null;
|
|
45
|
+
/** An occurrence's slot in its series (Google's `originalStartTime`). */
|
|
46
|
+
originalStartsAt?: number | null;
|
|
47
|
+
exception?: boolean;
|
|
48
|
+
status: EntryStatus;
|
|
49
|
+
title?: string | null;
|
|
50
|
+
description?: string | null;
|
|
51
|
+
location?: string | null;
|
|
52
|
+
startsAt?: number | null;
|
|
53
|
+
endsAt?: number | null;
|
|
54
|
+
allDay?: boolean;
|
|
55
|
+
startDate?: string | null;
|
|
56
|
+
endDate?: string | null;
|
|
57
|
+
timezone?: string | null;
|
|
58
|
+
recurrence?: string[] | null;
|
|
59
|
+
/** A series' last slot; `null` repeats indefinitely. */
|
|
60
|
+
recurrenceEndsAt?: number | null;
|
|
61
|
+
eventType?: string | null;
|
|
62
|
+
transparency?: string | null;
|
|
63
|
+
visibility?: string | null;
|
|
64
|
+
ownResponse?: string | null;
|
|
65
|
+
organizer?: {
|
|
66
|
+
email: string;
|
|
67
|
+
name?: string | null;
|
|
68
|
+
} | null;
|
|
69
|
+
participants?: EntryParticipantInput[];
|
|
70
|
+
joinUrl?: string | null;
|
|
71
|
+
conference?: string | null;
|
|
72
|
+
url?: string | null;
|
|
73
|
+
sequence?: number | null;
|
|
74
|
+
etag?: string | null;
|
|
75
|
+
externalUpdatedAt?: number | null;
|
|
76
|
+
/** Only the keys `ENTRY_METADATA_KEYS` names are kept. */
|
|
77
|
+
metadata?: Record<string, unknown> | null;
|
|
78
|
+
}
|
|
79
|
+
export interface StoredEntry {
|
|
80
|
+
id: number;
|
|
81
|
+
calendarId: number;
|
|
82
|
+
kind: EntryKind;
|
|
83
|
+
seriesEntryId: number | null;
|
|
84
|
+
eventId: number | null;
|
|
85
|
+
eventSeriesId: number | null;
|
|
86
|
+
externalId: string;
|
|
87
|
+
icalUid: string | null;
|
|
88
|
+
originalStartsAt: number | null;
|
|
89
|
+
exception: boolean;
|
|
90
|
+
status: EntryStatus;
|
|
91
|
+
title: string | null;
|
|
92
|
+
description: string | null;
|
|
93
|
+
location: string | null;
|
|
94
|
+
startsAt: number | null;
|
|
95
|
+
endsAt: number | null;
|
|
96
|
+
allDay: boolean;
|
|
97
|
+
startDate: string | null;
|
|
98
|
+
endDate: string | null;
|
|
99
|
+
timezone: string | null;
|
|
100
|
+
recurrence: string[] | null;
|
|
101
|
+
recurrenceEndsAt: number | null;
|
|
102
|
+
eventType: string | null;
|
|
103
|
+
ownResponse: string | null;
|
|
104
|
+
organizerIdentityId: number | null;
|
|
105
|
+
joinUrl: string | null;
|
|
106
|
+
joinKey: string | null;
|
|
107
|
+
url: string | null;
|
|
108
|
+
externalUpdatedAt: number | null;
|
|
109
|
+
seenAt: number | null;
|
|
110
|
+
deletedAt: number | null;
|
|
111
|
+
}
|
|
112
|
+
export interface StoredParticipant {
|
|
113
|
+
entryId: number;
|
|
114
|
+
identityId: number | null;
|
|
115
|
+
email: string;
|
|
116
|
+
name: string | null;
|
|
117
|
+
response: string | null;
|
|
118
|
+
optional: boolean;
|
|
119
|
+
organizer: boolean;
|
|
120
|
+
self: boolean;
|
|
121
|
+
resource: boolean;
|
|
122
|
+
}
|
|
123
|
+
/** An entry's tracked fields as they were before one change. */
|
|
124
|
+
export interface EntryRevision {
|
|
125
|
+
entryId: number;
|
|
126
|
+
title: string | null;
|
|
127
|
+
startsAt: number | null;
|
|
128
|
+
endsAt: number | null;
|
|
129
|
+
location: string | null;
|
|
130
|
+
status: string | null;
|
|
131
|
+
recurrence: string[] | null;
|
|
132
|
+
recurrenceEndsAt: number | null;
|
|
133
|
+
externalUpdatedAt: number | null;
|
|
134
|
+
createdAt: number;
|
|
135
|
+
}
|
|
136
|
+
export interface EventDetails {
|
|
137
|
+
event: Event;
|
|
138
|
+
entries: StoredEntry[];
|
|
139
|
+
participants: StoredParticipant[];
|
|
140
|
+
revisions: EntryRevision[];
|
|
141
|
+
}
|
|
142
|
+
export interface EventFilter {
|
|
143
|
+
/** Events that end after this instant (exclusive end). */
|
|
144
|
+
from?: number;
|
|
145
|
+
/** Events that start before this instant. */
|
|
146
|
+
to?: number;
|
|
147
|
+
accountId?: number;
|
|
148
|
+
calendarId?: number;
|
|
149
|
+
/** Events whose entries list one of this person's contacts. */
|
|
150
|
+
personId?: number;
|
|
151
|
+
limit?: number;
|
|
152
|
+
offset?: number;
|
|
153
|
+
}
|
|
154
|
+
/** Pairs the rules left unlinked because two or more entities fit equally well (requirements 3.6). */
|
|
155
|
+
export interface LinkReview {
|
|
156
|
+
id: number;
|
|
157
|
+
linkableType: string;
|
|
158
|
+
linkableId: number;
|
|
159
|
+
entityType: string;
|
|
160
|
+
candidates: number[];
|
|
161
|
+
reason: string;
|
|
162
|
+
status: "open" | "linked" | "apart";
|
|
163
|
+
chosenId: number | null;
|
|
164
|
+
decidedBy: string | null;
|
|
165
|
+
decidedAt: number | null;
|
|
166
|
+
createdAt: number;
|
|
167
|
+
}
|
|
168
|
+
export type ReviewDecision = {
|
|
169
|
+
choose: number;
|
|
170
|
+
} | {
|
|
171
|
+
apart: true;
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Calendars and their entries, the source records of events (requirements 3.4). Apart from `MeetingStore`: a
|
|
175
|
+
* calendar CLI implements this one, a meeting app the other. Every write is one transaction; an owner's link is never
|
|
176
|
+
* replaced by a rule, and two equally good candidates make no link and one review entry.
|
|
177
|
+
*/
|
|
178
|
+
export interface EventStore {
|
|
179
|
+
saveCalendars(accountId: number, calendars: CalendarInput[], now: number): Promise<StoredCalendar[]>;
|
|
180
|
+
calendars(accountId: number): Promise<StoredCalendar[]>;
|
|
181
|
+
setCalendarSynced(calendarId: number, synced: boolean, now: number): Promise<void>;
|
|
182
|
+
/** One sync page, matched by rules 1–5, in one transaction. */
|
|
183
|
+
saveEntries(calendarId: number, entries: EntryInput[], now: number): Promise<StoredEntry[]>;
|
|
184
|
+
/**
|
|
185
|
+
* After a full list (`410 Gone`): entries not seen since `seenBefore` get `deleted_at`. Plain occurrences come only
|
|
186
|
+
* from the window fetched, so only those inside it count. Events, series and links stay.
|
|
187
|
+
*/
|
|
188
|
+
markUnseen(calendarId: number, seenBefore: number, window: {
|
|
189
|
+
from: number;
|
|
190
|
+
to: number;
|
|
191
|
+
}, now: number): Promise<number>;
|
|
192
|
+
events(filter?: EventFilter): Promise<Event[]>;
|
|
193
|
+
event(id: number): Promise<EventDetails | null>;
|
|
194
|
+
/** The owner's link of an entry to an event; no rule replaces it. */
|
|
195
|
+
linkEntry(entryId: number, eventId: number, now: number): Promise<void>;
|
|
196
|
+
/** The owner's unlink: the rules leave the entry alone after it. */
|
|
197
|
+
unlinkEntry(entryId: number, now: number): Promise<void>;
|
|
198
|
+
reviews(filter?: {
|
|
199
|
+
status?: LinkReview["status"];
|
|
200
|
+
}): Promise<LinkReview[]>;
|
|
201
|
+
resolveReview(id: number, decision: ReviewDecision, now: number): Promise<LinkReview>;
|
|
202
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/meetings/events.js
CHANGED
|
@@ -13,7 +13,11 @@ export const linkEvent = async (store, details, now = Date.now()) => {
|
|
|
13
13
|
startsAt: meeting.startedAt,
|
|
14
14
|
endsAt: meeting.endedAt ?? meeting.startedAt,
|
|
15
15
|
});
|
|
16
|
-
|
|
16
|
+
if (candidates.length > 1) {
|
|
17
|
+
await store.reviewMeetingLink?.(meeting.id, candidates.map(({ id }) => id), now);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
let event = candidates[0];
|
|
17
21
|
if (!event) {
|
|
18
22
|
let eventSeriesId = series?.eventSeriesId ?? null;
|
|
19
23
|
if (series?.kind === "recurring" && eventSeriesId === null) {
|
package/dist/meetings/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { MEETING_STEPS, type MeetingDetailsItem, type MeetingHitItem, type MeetingItemBody, type MeetingListBody, type MeetingSummaryItem, type MeetingTranscriptItem, meetingDetails, meetingHit, meetingHitsList, meetingSummary, meetingTranscript, } from "./answers.js";
|
|
2
2
|
export type { MeetingBatchReader } from "./batch.js";
|
|
3
|
+
export { calendarText, ENTRY_METADATA_KEYS, entryMetadata, withoutLinkQueries } from "./calendar-text.js";
|
|
3
4
|
export type { MeetingContextStore, MeetingEventLinkInput, MeetingEventLinkReceipt, ParticipantPerson, ParticipantPersonInput, ParticipantPersonLinkInput, ParticipantPersonLinkReceipt, } from "./context-store.js";
|
|
4
5
|
export { type MeetingCueIdentity, meetingCueFingerprint } from "./cue-fingerprint.js";
|
|
5
6
|
export { EVENT_QUERY } from "./event-query.js";
|
|
7
|
+
export type { CalendarInput, EntryInput, EntryKind, EntryParticipantInput, EntryRevision, EntryStatus, EventDetails, EventFilter, EventStore, LinkReview, ReviewDecision, StoredCalendar, StoredEntry, StoredParticipant, } from "./event-store.js";
|
|
6
8
|
export { linkEvent } from "./events.js";
|
|
7
9
|
export { exportMeeting, type MeetingExport, type MeetingExportFormat, type MeetingExportOptions, serializeVtt, } from "./export.js";
|
|
8
10
|
export { type FindingEvidence, type FindingOutcome, findingHash, MEETING_FINDINGS_KIND, type MeetingFactFinding, type MeetingFinding, type MeetingFindingsInput, type MeetingFindingsReceipt, type MeetingTaskFinding, meetingFindingFingerprint, validateMeetingFindings, } from "./findings.js";
|
|
@@ -10,6 +12,7 @@ export { type Finding, type FindingsInput, type FindingsReceipt, type FindingsRu
|
|
|
10
12
|
export { liftMeetingFields, MEETING_HIT_QUERY, meetingWordsQuery, parseMeetingQuery } from "./hit-query.js";
|
|
11
13
|
export { type ImportFile, importFiles } from "./import.js";
|
|
12
14
|
export { MEETING_ISSUES, MEETING_QUERY, type MeetingIssue, type MeetingItem, type MeetingOwner, meeting, meetingIssuesOf, meetingItem, } from "./item.js";
|
|
15
|
+
export { joinKeyOf, joinUrlWithoutQuery } from "./join-key.js";
|
|
13
16
|
export { CHAT_MESSAGES_READ, type MeetingChatLine, type MeetingChatLinesInput, type MeetingChatLinesStore, type MeetingLink, } from "./links.js";
|
|
14
17
|
export { createMeetingContext, MEETING_FACT_TYPES, type MeetingContextAction, type MeetingContextChange, type MeetingContextOperation, type MeetingContextPorts, type MeetingContextReceipt, validateMeetingContextChange, } from "./meeting-context.js";
|
|
15
18
|
export { createMeetingManagement, dispatchMeetingMutation, type ManagedMeeting, type ManagedMeetingChange, type MeetingManagement, type MeetingManagementPorts, type MeetingMutationOptions, type MeetingMutationProposal, type MeetingMutationReceipt, type MeetingOperationOptions, type MeetingProviderContext, validateMeetingChange, } from "./meeting-management.js";
|
package/dist/meetings/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { MEETING_STEPS, meetingDetails, meetingHit, meetingHitsList, meetingSummary, meetingTranscript, } from "./answers.js";
|
|
2
|
+
export { calendarText, ENTRY_METADATA_KEYS, entryMetadata, withoutLinkQueries } from "./calendar-text.js";
|
|
2
3
|
export { meetingCueFingerprint } from "./cue-fingerprint.js";
|
|
3
4
|
export { EVENT_QUERY } from "./event-query.js";
|
|
4
5
|
export { linkEvent } from "./events.js";
|
|
@@ -8,6 +9,7 @@ export { findingFingerprint, validateFindings, } from "./findings-rules.js";
|
|
|
8
9
|
export { liftMeetingFields, MEETING_HIT_QUERY, meetingWordsQuery, parseMeetingQuery } from "./hit-query.js";
|
|
9
10
|
export { importFiles } from "./import.js";
|
|
10
11
|
export { MEETING_ISSUES, MEETING_QUERY, meeting, meetingIssuesOf, meetingItem, } from "./item.js";
|
|
12
|
+
export { joinKeyOf, joinUrlWithoutQuery } from "./join-key.js";
|
|
11
13
|
export { CHAT_MESSAGES_READ, } from "./links.js";
|
|
12
14
|
export { createMeetingContext, MEETING_FACT_TYPES, validateMeetingContextChange, } from "./meeting-context.js";
|
|
13
15
|
export { createMeetingManagement, dispatchMeetingMutation, validateMeetingChange, } from "./meeting-management.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What one meeting room is, whatever link it was shared by: `zoom:<number>`, `meet:<code>`, else the host and path.
|
|
3
|
+
* A query string carries passcodes (`pwd=`), so it never reaches the key or a stored link.
|
|
4
|
+
*/
|
|
5
|
+
export declare const joinKeyOf: (url: string | null | undefined) => string | null;
|
|
6
|
+
/** The link without its query string or fragment, which may hold a passcode. */
|
|
7
|
+
export declare const joinUrlWithoutQuery: (url: string | null | undefined) => string | null;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What one meeting room is, whatever link it was shared by: `zoom:<number>`, `meet:<code>`, else the host and path.
|
|
3
|
+
* A query string carries passcodes (`pwd=`), so it never reaches the key or a stored link.
|
|
4
|
+
*/
|
|
5
|
+
export const joinKeyOf = (url) => {
|
|
6
|
+
const parsed = urlOf(url);
|
|
7
|
+
if (!parsed)
|
|
8
|
+
return null;
|
|
9
|
+
const host = parsed.hostname.toLowerCase().replace(/^www\./u, "");
|
|
10
|
+
const path = parsed.pathname.replace(/\/+$/u, "");
|
|
11
|
+
if (host === "zoom.us" || host.endsWith(".zoom.us") || host === "zoomgov.com" || host.endsWith(".zoomgov.com")) {
|
|
12
|
+
const number = /^\/(?:j|w|s|wc\/join|wc)\/(\d{9,12})(?:\/|$)/u.exec(path)?.[1];
|
|
13
|
+
if (number)
|
|
14
|
+
return `zoom:${number}`;
|
|
15
|
+
}
|
|
16
|
+
if (host === "meet.google.com") {
|
|
17
|
+
const code = /^\/([a-z]{3}-[a-z]{4}-[a-z]{3})$/iu.exec(path)?.[1];
|
|
18
|
+
if (code)
|
|
19
|
+
return `meet:${code.toLowerCase()}`;
|
|
20
|
+
}
|
|
21
|
+
return `${host}${path}`;
|
|
22
|
+
};
|
|
23
|
+
/** The link without its query string or fragment, which may hold a passcode. */
|
|
24
|
+
export const joinUrlWithoutQuery = (url) => {
|
|
25
|
+
const parsed = urlOf(url);
|
|
26
|
+
return parsed ? `${parsed.origin}${parsed.pathname}` : null;
|
|
27
|
+
};
|
|
28
|
+
const urlOf = (url) => {
|
|
29
|
+
if (!url?.trim())
|
|
30
|
+
return null;
|
|
31
|
+
try {
|
|
32
|
+
const parsed = new URL(url.trim());
|
|
33
|
+
return parsed.protocol === "https:" || parsed.protocol === "http:" ? parsed : null;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
};
|
package/dist/meetings/model.d.ts
CHANGED
|
@@ -13,7 +13,8 @@ export interface EventSeries extends RecordTimes {
|
|
|
13
13
|
id: number;
|
|
14
14
|
title: string | null;
|
|
15
15
|
recurrence: string | null;
|
|
16
|
-
|
|
16
|
+
/** `auto` from a meeting, `owner`, or `calendar` from a calendar's entries. */
|
|
17
|
+
origin: "auto" | "owner" | "calendar";
|
|
17
18
|
}
|
|
18
19
|
export interface Event extends RecordTimes {
|
|
19
20
|
id: number;
|
|
@@ -24,8 +25,13 @@ export interface Event extends RecordTimes {
|
|
|
24
25
|
startsAt: number | null;
|
|
25
26
|
endsAt: number | null;
|
|
26
27
|
timezone: string | null;
|
|
27
|
-
origin: "auto" | "owner";
|
|
28
|
+
origin: "auto" | "owner" | "calendar";
|
|
28
29
|
deletedAt: number | null;
|
|
30
|
+
/** confirmed, tentative, cancelled: cancelled only when every live calendar entry of it is. */
|
|
31
|
+
status?: string | null;
|
|
32
|
+
allDay?: boolean | null;
|
|
33
|
+
/** When its calendar deleted it; a past event stays as a meeting that happened. */
|
|
34
|
+
sourceDeletedAt?: number | null;
|
|
29
35
|
}
|
|
30
36
|
export interface MeetingSeries extends RecordTimes {
|
|
31
37
|
id: number;
|
|
@@ -176,8 +176,12 @@ export declare const event: v.StrictObjectSchema<{
|
|
|
176
176
|
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
177
177
|
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
178
178
|
readonly timezone: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
179
|
-
/** `auto`: made from a meeting; `owner`: made by hand. */
|
|
180
|
-
readonly origin: v.PicklistSchema<["auto", "owner"], undefined>;
|
|
179
|
+
/** `auto`: made from a meeting; `owner`: made by hand; `calendar`: from a calendar's entries. */
|
|
180
|
+
readonly origin: v.PicklistSchema<["auto", "owner", "calendar"], undefined>;
|
|
181
|
+
readonly status: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
182
|
+
readonly all_day: v.BooleanSchema<undefined>;
|
|
183
|
+
/** When its calendar deleted it: shown as "deleted in Google", kept as history. */
|
|
184
|
+
readonly source_deleted_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
181
185
|
readonly deleted_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
182
186
|
readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
183
187
|
readonly updated_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|