datanotes 3.3.1 → 3.4.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/SECURITY.md +5 -3
- package/dist/api/extensions.cjs +141 -5
- package/dist/api/extensions.js +141 -5
- package/dist/api/index.cjs +141 -5
- package/dist/api/index.js +141 -5
- package/dist/api/mcp.cjs +141 -5
- package/dist/api/mcp.js +141 -5
- package/dist/datanotes.cjs +1672 -600
- package/dist/docs/administration/extensions.md +2 -2
- package/dist/docs/api/operations/add_column.md +1 -0
- package/dist/docs/api/operations/check_table.md +1 -0
- package/dist/docs/api/operations/create_table.md +1 -0
- package/dist/docs/api/operations/describe_database.md +2 -0
- package/dist/docs/api/operations/describe_history.md +1 -0
- package/dist/docs/api/operations/describe_semantic_index.md +1 -0
- package/dist/docs/api/operations/describe_table.md +1 -0
- package/dist/docs/api/operations/graph_neighbors.md +1 -0
- package/dist/docs/api/operations/graph_path.md +1 -0
- package/dist/docs/api/operations/graph_traverse.md +1 -0
- package/dist/docs/api/operations/infer_table.md +1 -0
- package/dist/docs/api/operations/list_changes.md +1 -0
- package/dist/docs/api/operations/list_folder.md +1 -0
- package/dist/docs/api/operations/list_link_targets.md +1 -0
- package/dist/docs/api/operations/list_migrations.md +1 -0
- package/dist/docs/api/operations/list_tables.md +2 -0
- package/dist/docs/api/operations/list_webhooks.md +1 -0
- package/dist/docs/api/operations/read_note.md +1 -0
- package/dist/docs/api/operations/search.md +39 -0
- package/dist/docs/api/operations/search_notes.md +1 -0
- package/dist/docs/api/operations/select_rows.md +1 -0
- package/dist/docs/api/operations/semantic_search.md +1 -0
- package/dist/docs/api/operations/table_changes.md +1 -0
- package/dist/docs/api/operations/tags/queries.md +1 -0
- package/dist/docs/api/operations/tags/tokens.md +1 -1
- package/dist/docs/api/operations/tokens_create.md +1 -0
- package/dist/docs/api/operations/tokens_update.md +2 -1
- package/dist/docs/api.md +5 -4
- package/dist/docs/develop/build-an-app.md +2 -1
- package/dist/docs/develop/change-feed-and-webhooks.md +1 -1
- package/dist/docs/develop/http-api.md +1 -1
- package/dist/docs/guides/data-quality/check-constraints.md +10 -10
- package/dist/docs/guides/data-quality/hand-edits.md +8 -8
- package/dist/docs/guides/database-objects/bases.md +11 -11
- package/dist/docs/guides/database-objects/catalogs-and-schemas.md +10 -10
- package/dist/docs/guides/database-objects/columns.md +241 -38
- package/dist/docs/guides/database-objects/relations.md +28 -28
- package/dist/docs/guides/database-objects/tables.md +26 -26
- package/dist/docs/guides/database-objects/views.md +19 -19
- package/dist/docs/guides/history/change-data-feed.md +34 -34
- package/dist/docs/guides/history/journal-and-undo.md +12 -12
- package/dist/docs/guides/history/restore.md +20 -20
- package/dist/docs/guides/history/table-history.md +31 -31
- package/dist/docs/guides/read-and-write/automatic-links.md +15 -15
- package/dist/docs/guides/read-and-write/import-notes.md +20 -20
- package/dist/docs/guides/read-and-write/write-rows.md +10 -10
- package/dist/docs/guides/search/hybrid.md +162 -0
- package/dist/docs/guides/search/text.md +1 -1
- package/dist/docs/guides/security/security-model.md +5 -3
- package/dist/docs/guides/security/tokens.md +121 -8
- package/dist/docs/reference/cli.md +1 -1
- package/dist/docs/reference/glossary.md +1 -1
- package/dist/docs/reference/operations/auxiliary.md +2 -2
- package/dist/docs/reference/operations/queries.md +16 -0
- package/dist/docs/reference/operations/schema.md +2 -2
- package/dist/docs/reference/operations.md +2 -1
- package/dist/docs/release-notes.md +43 -0
- package/dist/docs/start/connect-agents.md +10 -3
- package/package.json +1 -1
package/dist/datanotes.cjs
CHANGED
|
@@ -264,7 +264,7 @@ var init_catalog = __esm({
|
|
|
264
264
|
},
|
|
265
265
|
{
|
|
266
266
|
"name": "list_tables",
|
|
267
|
-
"description": "List every table: `name`, `catalog`, `schema`, `qualified` (`personal.metrics.mood` \u2014 use it\nwherever a table is expected; `metrics.mood` or `mood` also work when unambiguous), `path` and,\nwhen declared, `description`, `aliases` (other words for what it records) and `tags`. Tables are\ndefined in `metastore.yaml` (catalog \u2192 schema \u2192 table): read it freely, but it is read-only for\nagents and changes only through the schema operations (create_table, add_column, \u2026). Returns `{tables, views}`: `views` lists the stored views (same fields, no\npath): select them like tables. `system.information_schema` is read with `select_rows`.\n\nCall this first to find the table for what the user reports (match their words against\nname, description and aliases), and before creating tables or columns so you reuse existing\nones instead of creating duplicates.",
|
|
267
|
+
"description": "List every table: `name`, `catalog`, `schema`, `qualified` (`personal.metrics.mood` \u2014 use it\nwherever a table is expected; `metrics.mood` or `mood` also work when unambiguous), `path` and,\nwhen declared, `description`, `aliases` (other words for what it records) and `tags`. Tables are\ndefined in `metastore.yaml` (catalog \u2192 schema \u2192 table): read it freely, but it is read-only for\nagents and changes only through the schema operations (create_table, add_column, \u2026). Returns `{tables, views}`: `views` lists the stored views (same fields, no\npath): select them like tables. `system.information_schema` is read with `select_rows`.\nA token with `read` patterns sees only the tables and views it may read.\n\nCall this first to find the table for what the user reports (match their words against\nname, description and aliases), and before creating tables or columns so you reuse existing\nones instead of creating duplicates.",
|
|
268
268
|
"inputSchema": {
|
|
269
269
|
"additionalProperties": false,
|
|
270
270
|
"properties": {},
|
|
@@ -529,7 +529,7 @@ var init_catalog = __esm({
|
|
|
529
529
|
},
|
|
530
530
|
"generated": {
|
|
531
531
|
"type": "string",
|
|
532
|
-
"description":
|
|
532
|
+
"description": 'Expression of a generated column: a column, `date(col)\xB1N`, a template like `{name} {surname}`, `link(<template>)`, or an aggregate of a json list of objects like `count(feeds, side = "left")` or `sum(feeds.min)`.'
|
|
533
533
|
},
|
|
534
534
|
"dense": {
|
|
535
535
|
"type": "boolean",
|
|
@@ -650,7 +650,7 @@ var init_catalog = __esm({
|
|
|
650
650
|
},
|
|
651
651
|
{
|
|
652
652
|
"name": "create_table",
|
|
653
|
-
"description": 'Create a table: `name`, in `catalog` and `schema` (default: the database\'s defaults); the definition\ngoes in metastore.yaml, the rows in `<root>/<catalog>/<schema>/<name>/`. Names: lowercase letters,\ndigits, `_` (no dots: pass the catalog and the schema as their own arguments).\n- `description` (required): what a row of the table records. Every column needs its own `description`\n too, what it holds: the table is refused otherwise, with every missing one listed. Descriptions are how\n agents and people understand the schema, so write them for a reader who never saw the notes. The\n engine writes those of `id` and of `identity` "uuid" / "created" columns.\n- `columns`: specs like `{"name": "kind", "type": "enum", "values": ["a","b"], "required": true,\n "description": "What kind of entry the row is"}`.\n Types: string, integer, number, boolean, date, datetime, enum, link, list (+ `items`), json.\n Attributes: required, default (`"{now}"`/`"{today}"`), min, max, step, values, `ordered` (enum in\n order: filters/avg use the position), ref (link target, full `catalog.schema.table` or "self"),\n unique, label, description, aliases, `generated` (e.g. `"date(occurred_at)"`, `"{name} {surname}"`),\n `identity` ("filename" | "created"), `vocabulary` (allowed values = names of another table\'s rows),\n `hierarchy: true` (one parent link to its own table), `acyclic: true` (links to rows of its own table that never loop back, such as tasks blocked by tasks; with `inverse`, the rule holds on both sides), `dense` (written on every row),\n `inverse` on a link column: the column of the `ref` table that links back (`friends` \u2194\n `friends`, `children` \u2194 `parents`, `partner` \u2194 `partner`); writes keep both sides in step.\n- `time_column`: rows named by that date/datetime; otherwise `filename` (e.g. `"{name}"`).\n- The table\'s options, as alter_table names them: `aliases` (how agents find it, with the\n description), `partition`, `exclude`, `archive`, `history` (keep its versions; default: the\n database\'s setting), `autolink_target` (true / false: may automatic links link its rows? default:\n yes, unless its rows are named by a date or a time, as with `time_column`).\n- `row_template`: markdown body of new rows. Shared columns come from bases: `set_table_bases`.\n- `instructions`: how to fill a row, for agents (Markdown); describe_table returns them.\n- `tags`: key/value labels of the table, strings (`{"domain": "health"}`); list_tables and\n describe_table return them, alter_table changes them.\nEnum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", \u2026}`): describe_table shows them and a refused value lists them.\nGenerated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet).\nFiltered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row.\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
|
|
653
|
+
"description": 'Create a table: `name`, in `catalog` and `schema` (default: the database\'s defaults); the definition\ngoes in metastore.yaml, the rows in `<root>/<catalog>/<schema>/<name>/`. Names: lowercase letters,\ndigits, `_` (no dots: pass the catalog and the schema as their own arguments).\n- `description` (required): what a row of the table records. Every column needs its own `description`\n too, what it holds: the table is refused otherwise, with every missing one listed. Descriptions are how\n agents and people understand the schema, so write them for a reader who never saw the notes. The\n engine writes those of `id` and of `identity` "uuid" / "created" columns.\n- `columns`: specs like `{"name": "kind", "type": "enum", "values": ["a","b"], "required": true,\n "description": "What kind of entry the row is"}`.\n Types: string, integer, number, boolean, date, datetime, enum, link, list (+ `items`), json.\n Attributes: required, default (`"{now}"`/`"{today}"`), min, max, step, values, `ordered` (enum in\n order: filters/avg use the position), ref (link target, full `catalog.schema.table` or "self"),\n unique, label, description, aliases, `generated` (e.g. `"date(occurred_at)"`, `"{name} {surname}"`),\n `identity` ("filename" | "created"), `vocabulary` (allowed values = names of another table\'s rows),\n `hierarchy: true` (one parent link to its own table), `acyclic: true` (links to rows of its own table that never loop back, such as tasks blocked by tasks; with `inverse`, the rule holds on both sides), `dense` (written on every row),\n `inverse` on a link column: the column of the `ref` table that links back (`friends` \u2194\n `friends`, `children` \u2194 `parents`, `partner` \u2194 `partner`); writes keep both sides in step.\n- `time_column`: rows named by that date/datetime; otherwise `filename` (e.g. `"{name}"`).\n- The table\'s options, as alter_table names them: `aliases` (how agents find it, with the\n description), `partition`, `exclude`, `archive`, `history` (keep its versions; default: the\n database\'s setting), `autolink_target` (true / false: may automatic links link its rows? default:\n yes, unless its rows are named by a date or a time, as with `time_column`).\n- `row_template`: markdown body of new rows. Shared columns come from bases: `set_table_bases`.\n- `instructions`: how to fill a row, for agents (Markdown); describe_table returns them.\n- `tags`: key/value labels of the table, strings (`{"domain": "health"}`); list_tables and\n describe_table return them, alter_table changes them.\nEnum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", \u2026}`): describe_table shows them and a refused value lists them.\nGenerated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet).\nGenerated aggregates of a json column holding a list of objects (integer or number columns; avg: number): `count(feeds)`, `count(feeds.min)` (the items with a min), `sum(feeds.min)`, `avg(\u2026)`, `min(\u2026)`, `max(\u2026)`, with an optional filter on one field: `count(feeds, side = "left")`, `sum(feeds.min, min > 0)` (=, !=, <, <=, >, >=; "text", a number, true or false). Recomputed on every write; no list: count and sum 0, avg/min/max empty; a field that is not a number: empty.\nFiltered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row.\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
|
|
654
654
|
"inputSchema": {
|
|
655
655
|
"additionalProperties": false,
|
|
656
656
|
"properties": {
|
|
@@ -1744,7 +1744,7 @@ var init_catalog = __esm({
|
|
|
1744
1744
|
},
|
|
1745
1745
|
{
|
|
1746
1746
|
"name": "add_column",
|
|
1747
|
-
"description": 'Add a typed column. Sparse by default (written only on rows that have a value \u2014 use for\nmetrics, e.g. `{"name": "metric_mood", "type": "integer", "min": 1, "max": 5, "label":\n"Mood", "aliases": ["mood"], "description": "Mood of the day, 1 = bad, 5 = great"}`).\n`description` is required: what the column holds, for the agents and people who read the schema\n(the engine writes it for `identity` "uuid" / "created" columns).\n`dense: true` in the spec adds it to every row with its `default`. Call `describe_table` first: never add\na column whose meaning an existing column (name/label/aliases/description) already covers.\nColumn names are permanent identifiers; only `rename_column` changes them.\nEnum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", \u2026}`): describe_table shows them and a refused value lists them.\nGenerated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet).\nFiltered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row.\nAcyclic links: `acyclic: true` on a link (or list of links) to the table itself refuses every write that closes a cycle, on either side of an `inverse`; refused while existing rows form one (they are listed).\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
|
|
1747
|
+
"description": 'Add a typed column. Sparse by default (written only on rows that have a value \u2014 use for\nmetrics, e.g. `{"name": "metric_mood", "type": "integer", "min": 1, "max": 5, "label":\n"Mood", "aliases": ["mood"], "description": "Mood of the day, 1 = bad, 5 = great"}`).\n`description` is required: what the column holds, for the agents and people who read the schema\n(the engine writes it for `identity` "uuid" / "created" columns).\n`dense: true` in the spec adds it to every row with its `default`. Call `describe_table` first: never add\na column whose meaning an existing column (name/label/aliases/description) already covers.\nColumn names are permanent identifiers; only `rename_column` changes them.\nEnum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", \u2026}`): describe_table shows them and a refused value lists them.\nGenerated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet).\nGenerated aggregates of a json column holding a list of objects (integer or number columns; avg: number): `count(feeds)`, `count(feeds.min)` (the items with a min), `sum(feeds.min)`, `avg(\u2026)`, `min(\u2026)`, `max(\u2026)`, with an optional filter on one field: `count(feeds, side = "left")`, `sum(feeds.min, min > 0)` (=, !=, <, <=, >, >=; "text", a number, true or false). Recomputed on every write; no list: count and sum 0, avg/min/max empty; a field that is not a number: empty.\nFiltered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row.\nAcyclic links: `acyclic: true` on a link (or list of links) to the table itself refuses every write that closes a cycle, on either side of an `inverse`; refused while existing rows form one (they are listed).\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
|
|
1748
1748
|
"inputSchema": {
|
|
1749
1749
|
"additionalProperties": false,
|
|
1750
1750
|
"properties": {
|
|
@@ -5143,7 +5143,7 @@ var init_catalog = __esm({
|
|
|
5143
5143
|
},
|
|
5144
5144
|
{
|
|
5145
5145
|
"name": "describe_database",
|
|
5146
|
-
"description": "Describe the database: its folder (`root`), `metastore` (`metastore.yaml`: read it freely,\nbut it is read-only for agents and changes only through the schema operations), configuration\n(default catalog and schema, row partition and file-name formats, archive folder, time zone,\nhistory, automatic links),\njournal folder, `bases` (shared column sets and the tables using them), `catalogs` \u2192 `schemas` \u2192 `tables` (with `row_count`, `tags`, and `unreadable_count` when some rows' front matter cannot be read) and `views`;\ncatalogs and schemas show their `comment` (set with `comment_on`). A table or base defined before\ndescriptions were required has `missing_descriptions`: what lacks one, for alter_table / alter_column\n(define_base for a base) to fill, so every table and column says what it holds.",
|
|
5146
|
+
"description": "Describe the database: its folder (`root`), `metastore` (`metastore.yaml`: read it freely,\nbut it is read-only for agents and changes only through the schema operations), configuration\n(default catalog and schema, row partition and file-name formats, archive folder, time zone,\nhistory, automatic links),\njournal folder, `bases` (shared column sets and the tables using them), `catalogs` \u2192 `schemas` \u2192 `tables` (with `row_count`, `tags`, and `unreadable_count` when some rows' front matter cannot be read) and `views`;\ncatalogs and schemas show their `comment` (set with `comment_on`). A table or base defined before\ndescriptions were required has `missing_descriptions`: what lacks one, for alter_table / alter_column\n(define_base for a base) to fill, so every table and column says what it holds.\nA token with `read` patterns sees only the tables, views and bases it may read.",
|
|
5147
5147
|
"inputSchema": {
|
|
5148
5148
|
"additionalProperties": false,
|
|
5149
5149
|
"properties": {},
|
|
@@ -11707,6 +11707,141 @@ var init_catalog = __esm({
|
|
|
11707
11707
|
]
|
|
11708
11708
|
}
|
|
11709
11709
|
},
|
|
11710
|
+
{
|
|
11711
|
+
"name": "search",
|
|
11712
|
+
"description": "Search the notes by their words and, when semantic search is on, by their meaning:\n`{query, results: [{path, table?, score, heading, snippet, matched_by}], meaning}`, one result per\nnote, best first. It always works: with semantic search off it ranks by words alone (BM25:\nrare words count more; a word in the note's name or properties counts more), with it on it fuses\nboth rankings (Reciprocal Rank Fusion), so a note found only by its words or only by its meaning\nis still there. `matched_by` says which (`words`, `meaning`); `meaning` is `on`, `off` or `failed`\n(the embedding provider failed: words only, `meaning_error` says why). Query syntax as search_docs:\n`\"a phrase\"` and `+word` must appear, `-word` must not (both rankings); words in any language,\ncase and accents ignored, no stemming. Filters: `tables` (qualified or short names), `folder`,\n`min_score` (the meaning ranking's cosine floor). `limit`: default 10, max 100. Then read_note /\nselect_rows for the details.",
|
|
11713
|
+
"inputSchema": {
|
|
11714
|
+
"additionalProperties": false,
|
|
11715
|
+
"properties": {
|
|
11716
|
+
"query": {
|
|
11717
|
+
"type": "string"
|
|
11718
|
+
},
|
|
11719
|
+
"limit": {
|
|
11720
|
+
"default": 10,
|
|
11721
|
+
"type": "integer"
|
|
11722
|
+
},
|
|
11723
|
+
"tables": {
|
|
11724
|
+
"anyOf": [
|
|
11725
|
+
{
|
|
11726
|
+
"items": {
|
|
11727
|
+
"type": "string"
|
|
11728
|
+
},
|
|
11729
|
+
"type": "array"
|
|
11730
|
+
},
|
|
11731
|
+
{
|
|
11732
|
+
"type": "null"
|
|
11733
|
+
}
|
|
11734
|
+
],
|
|
11735
|
+
"default": null
|
|
11736
|
+
},
|
|
11737
|
+
"folder": {
|
|
11738
|
+
"anyOf": [
|
|
11739
|
+
{
|
|
11740
|
+
"type": "string"
|
|
11741
|
+
},
|
|
11742
|
+
{
|
|
11743
|
+
"type": "null"
|
|
11744
|
+
}
|
|
11745
|
+
],
|
|
11746
|
+
"default": null
|
|
11747
|
+
},
|
|
11748
|
+
"min_score": {
|
|
11749
|
+
"anyOf": [
|
|
11750
|
+
{
|
|
11751
|
+
"type": "number"
|
|
11752
|
+
},
|
|
11753
|
+
{
|
|
11754
|
+
"type": "null"
|
|
11755
|
+
}
|
|
11756
|
+
],
|
|
11757
|
+
"default": null
|
|
11758
|
+
}
|
|
11759
|
+
},
|
|
11760
|
+
"required": [
|
|
11761
|
+
"query"
|
|
11762
|
+
],
|
|
11763
|
+
"type": "object"
|
|
11764
|
+
},
|
|
11765
|
+
"annotations": {
|
|
11766
|
+
"title": "Search",
|
|
11767
|
+
"readOnlyHint": true,
|
|
11768
|
+
"destructiveHint": false,
|
|
11769
|
+
"idempotentHint": true,
|
|
11770
|
+
"openWorldHint": false
|
|
11771
|
+
},
|
|
11772
|
+
"responseSchema": {
|
|
11773
|
+
"type": "object",
|
|
11774
|
+
"properties": {
|
|
11775
|
+
"query": {
|
|
11776
|
+
"type": "string"
|
|
11777
|
+
},
|
|
11778
|
+
"results": {
|
|
11779
|
+
"type": "array",
|
|
11780
|
+
"items": {
|
|
11781
|
+
"type": "object",
|
|
11782
|
+
"properties": {
|
|
11783
|
+
"path": {
|
|
11784
|
+
"type": "string"
|
|
11785
|
+
},
|
|
11786
|
+
"table": {
|
|
11787
|
+
"type": "string",
|
|
11788
|
+
"description": "Qualified table of a row note."
|
|
11789
|
+
},
|
|
11790
|
+
"score": {
|
|
11791
|
+
"type": "number",
|
|
11792
|
+
"description": "BM25 when ranked by words alone; the Reciprocal Rank Fusion score when fused."
|
|
11793
|
+
},
|
|
11794
|
+
"heading": {
|
|
11795
|
+
"type": "string",
|
|
11796
|
+
"description": "Heading above the match (empty before the first heading)."
|
|
11797
|
+
},
|
|
11798
|
+
"snippet": {
|
|
11799
|
+
"type": "string",
|
|
11800
|
+
"description": "About 200 characters around the first match, or the best passage by meaning."
|
|
11801
|
+
},
|
|
11802
|
+
"matched_by": {
|
|
11803
|
+
"type": "array",
|
|
11804
|
+
"items": {
|
|
11805
|
+
"type": "string",
|
|
11806
|
+
"enum": [
|
|
11807
|
+
"words",
|
|
11808
|
+
"meaning"
|
|
11809
|
+
]
|
|
11810
|
+
},
|
|
11811
|
+
"description": "The rankings the note is in."
|
|
11812
|
+
}
|
|
11813
|
+
},
|
|
11814
|
+
"required": [
|
|
11815
|
+
"path",
|
|
11816
|
+
"score",
|
|
11817
|
+
"heading",
|
|
11818
|
+
"snippet",
|
|
11819
|
+
"matched_by"
|
|
11820
|
+
]
|
|
11821
|
+
},
|
|
11822
|
+
"description": "One result per note, best first."
|
|
11823
|
+
},
|
|
11824
|
+
"meaning": {
|
|
11825
|
+
"type": "string",
|
|
11826
|
+
"enum": [
|
|
11827
|
+
"on",
|
|
11828
|
+
"off",
|
|
11829
|
+
"failed"
|
|
11830
|
+
],
|
|
11831
|
+
"description": "on: words and meaning fused; off: semantic search is off, words only; failed: the embedding provider failed, words only."
|
|
11832
|
+
},
|
|
11833
|
+
"meaning_error": {
|
|
11834
|
+
"type": "string",
|
|
11835
|
+
"description": "Why the meaning ranking failed (with meaning: failed)."
|
|
11836
|
+
}
|
|
11837
|
+
},
|
|
11838
|
+
"required": [
|
|
11839
|
+
"query",
|
|
11840
|
+
"results",
|
|
11841
|
+
"meaning"
|
|
11842
|
+
]
|
|
11843
|
+
}
|
|
11844
|
+
},
|
|
11710
11845
|
{
|
|
11711
11846
|
"name": "describe_semantic_index",
|
|
11712
11847
|
"description": "The semantic search index: `enabled`, `provider`, `model`, `files`, `passages`, `pending`,\n`running`, `last_sync`, `last_error`. sync_semantic_index brings it up to date.",
|
|
@@ -17839,6 +17974,7 @@ var init_catalog = __esm({
|
|
|
17839
17974
|
list_folder: "queries",
|
|
17840
17975
|
search_notes: "queries",
|
|
17841
17976
|
semantic_search: "queries",
|
|
17977
|
+
search: "queries",
|
|
17842
17978
|
graph_neighbors: "queries",
|
|
17843
17979
|
graph_traverse: "queries",
|
|
17844
17980
|
graph_path: "queries",
|
|
@@ -18558,14 +18694,14 @@ var require_identity = __commonJS({
|
|
|
18558
18694
|
var ALIAS = Symbol.for("yaml.alias");
|
|
18559
18695
|
var DOC = Symbol.for("yaml.document");
|
|
18560
18696
|
var MAP = Symbol.for("yaml.map");
|
|
18561
|
-
var
|
|
18697
|
+
var PAIR2 = Symbol.for("yaml.pair");
|
|
18562
18698
|
var SCALAR = Symbol.for("yaml.scalar");
|
|
18563
18699
|
var SEQ = Symbol.for("yaml.seq");
|
|
18564
18700
|
var NODE_TYPE = Symbol.for("yaml.node.type");
|
|
18565
18701
|
var isAlias = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === ALIAS;
|
|
18566
18702
|
var isDocument = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === DOC;
|
|
18567
18703
|
var isMap2 = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === MAP;
|
|
18568
|
-
var isPair = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] ===
|
|
18704
|
+
var isPair = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === PAIR2;
|
|
18569
18705
|
var isScalar2 = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === SCALAR;
|
|
18570
18706
|
var isSeq = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === SEQ;
|
|
18571
18707
|
function isCollection(node2) {
|
|
@@ -18593,7 +18729,7 @@ var require_identity = __commonJS({
|
|
|
18593
18729
|
exports2.DOC = DOC;
|
|
18594
18730
|
exports2.MAP = MAP;
|
|
18595
18731
|
exports2.NODE_TYPE = NODE_TYPE;
|
|
18596
|
-
exports2.PAIR =
|
|
18732
|
+
exports2.PAIR = PAIR2;
|
|
18597
18733
|
exports2.SCALAR = SCALAR;
|
|
18598
18734
|
exports2.SEQ = SEQ;
|
|
18599
18735
|
exports2.hasAnchor = hasAnchor;
|
|
@@ -24080,8 +24216,8 @@ var require_cst_visit = __commonJS({
|
|
|
24080
24216
|
visit.REMOVE = REMOVE;
|
|
24081
24217
|
visit.itemAtPath = (cst, path23) => {
|
|
24082
24218
|
let item = cst;
|
|
24083
|
-
for (const [
|
|
24084
|
-
const tok = item?.[
|
|
24219
|
+
for (const [field3, index] of path23) {
|
|
24220
|
+
const tok = item?.[field3];
|
|
24085
24221
|
if (tok && "items" in tok) {
|
|
24086
24222
|
item = tok.items[index];
|
|
24087
24223
|
} else
|
|
@@ -24091,8 +24227,8 @@ var require_cst_visit = __commonJS({
|
|
|
24091
24227
|
};
|
|
24092
24228
|
visit.parentCollection = (cst, path23) => {
|
|
24093
24229
|
const parent2 = visit.itemAtPath(cst, path23.slice(0, -1));
|
|
24094
|
-
const
|
|
24095
|
-
const coll = parent2?.[
|
|
24230
|
+
const field3 = path23[path23.length - 1][0];
|
|
24231
|
+
const coll = parent2?.[field3];
|
|
24096
24232
|
if (coll && "items" in coll)
|
|
24097
24233
|
return coll;
|
|
24098
24234
|
throw new Error("Parent collection not found");
|
|
@@ -24101,11 +24237,11 @@ var require_cst_visit = __commonJS({
|
|
|
24101
24237
|
let ctrl = visitor(item, path23);
|
|
24102
24238
|
if (typeof ctrl === "symbol")
|
|
24103
24239
|
return ctrl;
|
|
24104
|
-
for (const
|
|
24105
|
-
const token = item[
|
|
24240
|
+
for (const field3 of ["key", "value"]) {
|
|
24241
|
+
const token = item[field3];
|
|
24106
24242
|
if (token && "items" in token) {
|
|
24107
24243
|
for (let i = 0; i < token.items.length; ++i) {
|
|
24108
|
-
const ci = _visit(Object.freeze(path23.concat([[
|
|
24244
|
+
const ci = _visit(Object.freeze(path23.concat([[field3, i]])), token.items[i], visitor);
|
|
24109
24245
|
if (typeof ci === "number")
|
|
24110
24246
|
i = ci - 1;
|
|
24111
24247
|
else if (ci === BREAK)
|
|
@@ -24115,7 +24251,7 @@ var require_cst_visit = __commonJS({
|
|
|
24115
24251
|
i -= 1;
|
|
24116
24252
|
}
|
|
24117
24253
|
}
|
|
24118
|
-
if (typeof ctrl === "function" &&
|
|
24254
|
+
if (typeof ctrl === "function" && field3 === "key")
|
|
24119
24255
|
ctrl = ctrl(item, path23);
|
|
24120
24256
|
}
|
|
24121
24257
|
}
|
|
@@ -26239,6 +26375,7 @@ var init_hints = __esm({
|
|
|
26239
26375
|
renameColumn: "/guides/database-objects/columns.md#rename-a-column",
|
|
26240
26376
|
generated: "/guides/data-quality/hand-edits.md#recompute-generated-columns-after-a-hand-edit",
|
|
26241
26377
|
computed: "/guides/database-objects/columns.md#compute-a-column-from-other-columns",
|
|
26378
|
+
jsonAggregates: "/guides/database-objects/columns.md#count-and-total-the-items-of-a-json-list",
|
|
26242
26379
|
partition: "/guides/database-objects/columns.md#partition-rows-into-folders",
|
|
26243
26380
|
filters: "/guides/read-and-write/read-and-filter.md#compare-values-with-operators",
|
|
26244
26381
|
logical: "/guides/read-and-write/read-and-filter.md#combine-filters-with-or-and-and-not",
|
|
@@ -26297,10 +26434,12 @@ var init_hints = __esm({
|
|
|
26297
26434
|
graph: "/guides/search/link-graph.md#walk-the-links-from-a-note",
|
|
26298
26435
|
graphTables: "/guides/search/link-graph.md#read-the-whole-graph-in-one-call",
|
|
26299
26436
|
semantic: "/guides/search/semantic.md#turn-on-semantic-search",
|
|
26437
|
+
search: "/guides/search/hybrid.md#search-by-words-and-meaning",
|
|
26300
26438
|
docsSearch: "/guides/agents/docs-offline.md#search-by-words-and-by-meaning",
|
|
26301
26439
|
tokens: "/guides/security/tokens.md#choose-what-a-token-can-do",
|
|
26302
26440
|
manageTokens: "/guides/security/tokens.md#manage-your-tokens",
|
|
26303
26441
|
tokenAutolink: "/guides/security/tokens.md#turn-automatic-links-off-for-a-sync-program",
|
|
26442
|
+
tokenRead: "/guides/security/tokens.md#limit-what-a-token-can-read",
|
|
26304
26443
|
reportAgents: "/guides/security/who-did-what.md#report-what-an-agent-does",
|
|
26305
26444
|
auth: "/develop/http-api.md#authenticate-your-requests",
|
|
26306
26445
|
routes: "/develop/http-api.md#call-the-engines-routes",
|
|
@@ -26703,6 +26842,64 @@ var init_datetime = __esm({
|
|
|
26703
26842
|
});
|
|
26704
26843
|
|
|
26705
26844
|
// src/core/generated.ts
|
|
26845
|
+
function filterValue(text5) {
|
|
26846
|
+
if (text5 === "true" || text5 === "false") return text5 === "true";
|
|
26847
|
+
if (NUMBER_LITERAL.test(text5)) return Number(text5);
|
|
26848
|
+
if (/^'[^']*'$/.test(text5)) return text5.slice(1, -1);
|
|
26849
|
+
if (/^"[\s\S]*"$/.test(text5)) {
|
|
26850
|
+
try {
|
|
26851
|
+
return JSON.parse(text5);
|
|
26852
|
+
} catch {
|
|
26853
|
+
return void 0;
|
|
26854
|
+
}
|
|
26855
|
+
}
|
|
26856
|
+
return void 0;
|
|
26857
|
+
}
|
|
26858
|
+
function parseAggregate(expr) {
|
|
26859
|
+
const e = expr.trim();
|
|
26860
|
+
if (!AGGREGATE_START.test(e)) return null;
|
|
26861
|
+
const fail2 = (message) => new GeneratedExpressionError(message, AGGREGATE_GUIDANCE);
|
|
26862
|
+
const m = e.match(AGGREGATE_EXPR);
|
|
26863
|
+
if (!m) throw fail2(`"${expr}" is not an aggregate: write count(column), count(column, field = value) or sum|avg|min|max(column.field), with an optional filter`);
|
|
26864
|
+
const fn = m[1];
|
|
26865
|
+
const column = m[2];
|
|
26866
|
+
if (fn !== "count" && m[3] === void 0) throw fail2(`"${expr}": ${fn}() needs a field of the items, like ${fn}(${column}.minutes)`);
|
|
26867
|
+
const out2 = { fn, column, ...m[3] !== void 0 ? { field: m[3] } : {} };
|
|
26868
|
+
if (m[4] === void 0) return out2;
|
|
26869
|
+
const f = m[4].match(FILTER_EXPR);
|
|
26870
|
+
if (!f) throw fail2(`"${expr}": the filter "${m[4]}" must compare a field with a value, like side = "left"`);
|
|
26871
|
+
const op2 = f[2];
|
|
26872
|
+
const raw = f[3].trim();
|
|
26873
|
+
const value = filterValue(raw);
|
|
26874
|
+
if (value === void 0) throw fail2(`"${expr}": the filter value ${raw} is not "text", a number, true or false`);
|
|
26875
|
+
if (op2 !== "=" && op2 !== "!=" && typeof value !== "number") throw fail2(`"${expr}": ${op2} compares numbers: the filter value must be a number, like min ${op2} 5`);
|
|
26876
|
+
return { ...out2, filter: { field: f[1], op: op2, value } };
|
|
26877
|
+
}
|
|
26878
|
+
function passes(item, filter) {
|
|
26879
|
+
const v = fieldOf(item, filter.field);
|
|
26880
|
+
if (filter.op === "=") return v === filter.value;
|
|
26881
|
+
if (filter.op === "!=") return v !== filter.value;
|
|
26882
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return false;
|
|
26883
|
+
const n = filter.value;
|
|
26884
|
+
if (filter.op === "<") return v < n;
|
|
26885
|
+
if (filter.op === "<=") return v <= n;
|
|
26886
|
+
if (filter.op === ">") return v > n;
|
|
26887
|
+
return v >= n;
|
|
26888
|
+
}
|
|
26889
|
+
function evalAggregate(a, value) {
|
|
26890
|
+
const list4 = value === void 0 || value === null ? [] : value;
|
|
26891
|
+
if (!Array.isArray(list4)) return null;
|
|
26892
|
+
const items3 = a.filter ? list4.filter((x) => passes(x, a.filter)) : list4;
|
|
26893
|
+
if (a.field === void 0) return items3.length;
|
|
26894
|
+
const values = items3.map((x) => fieldOf(x, a.field)).filter((v) => v !== void 0 && v !== null);
|
|
26895
|
+
if (a.fn === "count") return values.length;
|
|
26896
|
+
if (values.some((v) => typeof v !== "number" || !Number.isFinite(v))) return null;
|
|
26897
|
+
const nums = values;
|
|
26898
|
+
if (a.fn === "sum") return tidy(nums.reduce((s, n) => s + n, 0));
|
|
26899
|
+
if (nums.length === 0) return null;
|
|
26900
|
+
if (a.fn === "avg") return tidy(nums.reduce((s, n) => s + n, 0) / nums.length);
|
|
26901
|
+
return a.fn === "min" ? Math.min(...nums) : Math.max(...nums);
|
|
26902
|
+
}
|
|
26706
26903
|
function placeholderCols(m) {
|
|
26707
26904
|
return m[2] ? m[2].split(",").map((c) => c.trim()) : [m[3]];
|
|
26708
26905
|
}
|
|
@@ -26714,6 +26911,8 @@ function isTemplateExpression(expr) {
|
|
|
26714
26911
|
}
|
|
26715
26912
|
function generatedRefs(expr) {
|
|
26716
26913
|
const e = expr.trim();
|
|
26914
|
+
const agg = parseAggregate(expr);
|
|
26915
|
+
if (agg) return [agg.column];
|
|
26717
26916
|
const lm = e.match(LINK_EXPR);
|
|
26718
26917
|
if (lm) {
|
|
26719
26918
|
if (!lm[1].includes("{")) throw new GeneratedExpressionError(`"${expr}": link() needs a name template with at least one {column}`);
|
|
@@ -26755,6 +26954,8 @@ function display(value) {
|
|
|
26755
26954
|
function evalGenerated(expr, get, path23, linkFolder) {
|
|
26756
26955
|
const e = expr.trim();
|
|
26757
26956
|
const read3 = (col) => col === "path" ? path23 : get(col);
|
|
26957
|
+
const agg = parseAggregate(e);
|
|
26958
|
+
if (agg) return evalAggregate(agg, read3(agg.column));
|
|
26758
26959
|
const lm = e.match(LINK_EXPR);
|
|
26759
26960
|
if (lm) {
|
|
26760
26961
|
const tpl = lm[1];
|
|
@@ -26766,8 +26967,8 @@ function evalGenerated(expr, get, path23, linkFolder) {
|
|
|
26766
26967
|
}
|
|
26767
26968
|
if (isTemplateExpression(e)) {
|
|
26768
26969
|
const out2 = e.replace(PLACEHOLDER_RE, (...args) => placeholderText(args, read3));
|
|
26769
|
-
const
|
|
26770
|
-
return
|
|
26970
|
+
const tidy2 = out2.replace(/\s+/g, " ").replace(/\(\s*\)|\[\s*\]/g, "").replace(/\s+/g, " ").trim();
|
|
26971
|
+
return tidy2 === "" ? null : tidy2;
|
|
26771
26972
|
}
|
|
26772
26973
|
const m = e.match(DATE_EXPR);
|
|
26773
26974
|
if (m) {
|
|
@@ -26785,18 +26986,30 @@ function placeholderText(m, read3) {
|
|
|
26785
26986
|
if (m[1] === "max") return [...texts].sort().pop();
|
|
26786
26987
|
return texts[0];
|
|
26787
26988
|
}
|
|
26788
|
-
var GeneratedExpressionError, PLACEHOLDER_RE, LINK_EXPR, DATE_EXPR, COLUMN_EXPR;
|
|
26989
|
+
var GeneratedExpressionError, AGGREGATE_GUIDANCE, AGGREGATE_START, AGGREGATE_EXPR, FILTER_EXPR, NUMBER_LITERAL, isObject, fieldOf, tidy, PLACEHOLDER_RE, LINK_EXPR, DATE_EXPR, COLUMN_EXPR;
|
|
26789
26990
|
var init_generated = __esm({
|
|
26790
26991
|
"src/core/generated.ts"() {
|
|
26791
26992
|
"use strict";
|
|
26792
26993
|
init_validate();
|
|
26793
26994
|
init_datetime();
|
|
26794
|
-
|
|
26995
|
+
init_hints();
|
|
26996
|
+
GeneratedExpressionError = class extends GuidedError {
|
|
26795
26997
|
constructor() {
|
|
26796
26998
|
super(...arguments);
|
|
26797
26999
|
this.name = "GeneratedExpressionError";
|
|
26798
27000
|
}
|
|
26799
27001
|
};
|
|
27002
|
+
AGGREGATE_GUIDANCE = guide(
|
|
27003
|
+
`An aggregate reads a json column holding a list of objects: count(feeds), count(feeds, side = "left"), count(feeds.min) (the items with a min), sum(feeds.min), avg|min|max(feeds.min, side != "right"). A filter compares one field of the item with "text", a number, true or false, using =, !=, <, <=, > or >=.`,
|
|
27004
|
+
"jsonAggregates"
|
|
27005
|
+
);
|
|
27006
|
+
AGGREGATE_START = /^(count|sum|avg|min|max)\s*\(/;
|
|
27007
|
+
AGGREGATE_EXPR = /^(count|sum|avg|min|max)\s*\(\s*([A-Za-z][A-Za-z0-9_-]*)(?:\.([A-Za-z_][A-Za-z0-9_-]*))?\s*(?:,\s*([\s\S]*?))?\s*\)$/;
|
|
27008
|
+
FILTER_EXPR = /^([A-Za-z_][A-Za-z0-9_-]*)\s*(!=|<=|>=|=|<|>)\s*([\s\S]*)$/;
|
|
27009
|
+
NUMBER_LITERAL = /^-?\d+(?:\.\d+)?$/;
|
|
27010
|
+
isObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
27011
|
+
fieldOf = (item, field3) => isObject(item) ? item[field3] : void 0;
|
|
27012
|
+
tidy = (n) => Number(n.toPrecision(15));
|
|
26800
27013
|
PLACEHOLDER_RE = /\{(?:(min|max|coalesce)\(\s*([A-Za-z][A-Za-z0-9_-]*(?:\s*,\s*[A-Za-z][A-Za-z0-9_-]*)*)\s*\)|([A-Za-z][A-Za-z0-9_-]*))(?::([^}]+))?\}/g;
|
|
26801
27014
|
LINK_EXPR = /^link\(\s*([\s\S]+?)\s*\)$/;
|
|
26802
27015
|
DATE_EXPR = /^date\(\s*([A-Za-z][A-Za-z0-9_-]*)\s*\)\s*(?:([+-])\s*(\d+))?$/;
|
|
@@ -27147,7 +27360,7 @@ function checkGeneratedColumns(specs, known) {
|
|
|
27147
27360
|
try {
|
|
27148
27361
|
refs = generatedRefs(c.generated);
|
|
27149
27362
|
} catch (e) {
|
|
27150
|
-
throw new MalformedColumnError(index, messageOf(e));
|
|
27363
|
+
throw new MalformedColumnError(index, messageOf(e), guidanceOf(e) ?? void 0);
|
|
27151
27364
|
}
|
|
27152
27365
|
if (isTemplateExpression(c.generated) && c.type !== "string") {
|
|
27153
27366
|
throw new MalformedColumnError(index, `a template expression makes text: "${c.name}" must be a string column`);
|
|
@@ -27164,8 +27377,37 @@ function checkGeneratedColumns(specs, known) {
|
|
|
27164
27377
|
throw new MalformedColumnError(index, `"generated" reads generated column "${ref2}", declared after it`);
|
|
27165
27378
|
}
|
|
27166
27379
|
}
|
|
27380
|
+
const agg = parseAggregate(c.generated);
|
|
27381
|
+
if (agg) checkAggregate(c, index, agg, specs);
|
|
27167
27382
|
});
|
|
27168
27383
|
}
|
|
27384
|
+
function checkAggregate(c, index, agg, specs) {
|
|
27385
|
+
const source = specs.find((s) => s.name === agg.column);
|
|
27386
|
+
if (source?.type !== "json" && !(source?.type === "list" && source.items === "json")) {
|
|
27387
|
+
const what = !source ? `"${agg.column}" is ${agg.column === "path" ? "the row's path" : "an untyped field"}` : source.type === "list" ? (
|
|
27388
|
+
// parseColumn gives every list its items ("string" when the spec has none)
|
|
27389
|
+
`"${agg.column}" is a list of ${source.items}`
|
|
27390
|
+
) : `"${agg.column}" has type ${source.type}`;
|
|
27391
|
+
throw new MalformedColumnError(
|
|
27392
|
+
index,
|
|
27393
|
+
`"generated" aggregates "${agg.column}", which is not a json column`,
|
|
27394
|
+
guide(`${agg.fn}() reads a json column (type json, or a list of json) holding a list of objects, such as [{side: left, min: 12}]; ${what}. describe_table shows the type of each column.`, "jsonAggregates")
|
|
27395
|
+
);
|
|
27396
|
+
}
|
|
27397
|
+
const fits = agg.fn === "avg" ? c.type === "number" : c.type === "number" || c.type === "integer";
|
|
27398
|
+
if (fits) return;
|
|
27399
|
+
if (agg.fn === "count") {
|
|
27400
|
+
throw new MalformedColumnError(index, `count() makes a whole number: "${c.name}" must be an integer or number column`, guide(`Give "${c.name}" type "integer" or "number".`, "jsonAggregates"));
|
|
27401
|
+
}
|
|
27402
|
+
if (agg.fn === "avg") {
|
|
27403
|
+
throw new MalformedColumnError(index, `avg() makes a number: "${c.name}" must be a number column`, guide(`Give "${c.name}" type "number": an average is seldom a whole number.`, "jsonAggregates"));
|
|
27404
|
+
}
|
|
27405
|
+
throw new MalformedColumnError(
|
|
27406
|
+
index,
|
|
27407
|
+
`${agg.fn}() makes a number: "${c.name}" must be a number or integer column`,
|
|
27408
|
+
guide(`Give "${c.name}" type "number", or "integer" when the field holds only whole numbers.`, "jsonAggregates")
|
|
27409
|
+
);
|
|
27410
|
+
}
|
|
27169
27411
|
function tableDefinitionFromOrdered(ref2, schemaPath, ordered, settings) {
|
|
27170
27412
|
const reserved = new Set(TABLE_META_KEYS);
|
|
27171
27413
|
const options = parseTableOptions(fmGet(ordered, TABLE_KEY));
|
|
@@ -28817,6 +29059,89 @@ var init_database_move = __esm({
|
|
|
28817
29059
|
}
|
|
28818
29060
|
});
|
|
28819
29061
|
|
|
29062
|
+
// src/core/read-scope.ts
|
|
29063
|
+
function runWithReadScope(scope, fn) {
|
|
29064
|
+
if (!storage2) {
|
|
29065
|
+
if (scope) throw new Error("read permissions need Node's AsyncLocalStorage");
|
|
29066
|
+
return fn();
|
|
29067
|
+
}
|
|
29068
|
+
if (!scope && !storage2.getStore()) return fn();
|
|
29069
|
+
return storage2.run({ scope }, fn);
|
|
29070
|
+
}
|
|
29071
|
+
function currentReadScope() {
|
|
29072
|
+
return storage2?.getStore()?.scope ?? null;
|
|
29073
|
+
}
|
|
29074
|
+
function matchesTablePattern(full, patterns) {
|
|
29075
|
+
return patterns.some((p) => {
|
|
29076
|
+
if (p === "*") return true;
|
|
29077
|
+
if (p.endsWith(".*")) return full.startsWith(p.slice(0, -1));
|
|
29078
|
+
return p === full;
|
|
29079
|
+
});
|
|
29080
|
+
}
|
|
29081
|
+
function tableReadable(full, scope = currentReadScope()) {
|
|
29082
|
+
return scope === null || matchesTablePattern(full, scope.patterns);
|
|
29083
|
+
}
|
|
29084
|
+
function tableReadDenied(token, full) {
|
|
29085
|
+
return new ReadDeniedError(`token ${token} may not read ${full}`);
|
|
29086
|
+
}
|
|
29087
|
+
function pathReadDenied(token, path23) {
|
|
29088
|
+
return new ReadDeniedError(`token ${token} may not read the note ${path23}`);
|
|
29089
|
+
}
|
|
29090
|
+
function assertTableReadable(full) {
|
|
29091
|
+
const scope = currentReadScope();
|
|
29092
|
+
if (scope && !matchesTablePattern(full, scope.patterns)) throw tableReadDenied(scope.token, full);
|
|
29093
|
+
}
|
|
29094
|
+
function tableOfPath(app, settings, path23) {
|
|
29095
|
+
const idx = vaultIndex(app, settings);
|
|
29096
|
+
if (idx && !idx.hasFailures) return idx.note(path23)?.table ?? null;
|
|
29097
|
+
const model = cachedMetastore(app, settings);
|
|
29098
|
+
const file = app.vault.getAbstractFileByPath(path23);
|
|
29099
|
+
if (!model || !file || !("extension" in file)) return null;
|
|
29100
|
+
let value;
|
|
29101
|
+
try {
|
|
29102
|
+
value = app.metadataCache.getFileCache(file)?.frontmatter?.[settings.tableKey];
|
|
29103
|
+
} catch {
|
|
29104
|
+
return null;
|
|
29105
|
+
}
|
|
29106
|
+
return rowTableFull(model, value);
|
|
29107
|
+
}
|
|
29108
|
+
function pathReadable(app, settings, path23, scope = currentReadScope()) {
|
|
29109
|
+
if (scope === null) return true;
|
|
29110
|
+
const table = tableOfPath(app, settings, path23);
|
|
29111
|
+
return table !== null && matchesTablePattern(table, scope.patterns);
|
|
29112
|
+
}
|
|
29113
|
+
function assertPathReadable(app, settings, path23) {
|
|
29114
|
+
const scope = currentReadScope();
|
|
29115
|
+
if (scope && !pathReadable(app, settings, path23, scope)) throw pathReadDenied(scope.token, path23);
|
|
29116
|
+
}
|
|
29117
|
+
var storage2, READ_SCOPE_SUPPORTED, READ_DENIED_GUIDANCE, ReadDeniedError;
|
|
29118
|
+
var init_read_scope = __esm({
|
|
29119
|
+
"src/core/read-scope.ts"() {
|
|
29120
|
+
"use strict";
|
|
29121
|
+
init_hints();
|
|
29122
|
+
init_metastore();
|
|
29123
|
+
init_vault_index();
|
|
29124
|
+
storage2 = null;
|
|
29125
|
+
try {
|
|
29126
|
+
const { AsyncLocalStorage: AsyncLocalStorage2 } = require("node:async_hooks");
|
|
29127
|
+
storage2 = new AsyncLocalStorage2();
|
|
29128
|
+
} catch {
|
|
29129
|
+
storage2 = null;
|
|
29130
|
+
}
|
|
29131
|
+
READ_SCOPE_SUPPORTED = storage2 !== null;
|
|
29132
|
+
READ_DENIED_GUIDANCE = guide(
|
|
29133
|
+
`The owner grants reads with tokens/update/ and "read" patterns (catalog.schema.table, catalog.schema.*, catalog.* or *). list_tables lists the tables this token may read.`,
|
|
29134
|
+
"tokenRead"
|
|
29135
|
+
);
|
|
29136
|
+
ReadDeniedError = class extends GuidedError {
|
|
29137
|
+
constructor(message) {
|
|
29138
|
+
super(message, READ_DENIED_GUIDANCE);
|
|
29139
|
+
this.name = "ReadDeniedError";
|
|
29140
|
+
}
|
|
29141
|
+
};
|
|
29142
|
+
}
|
|
29143
|
+
});
|
|
29144
|
+
|
|
28820
29145
|
// src/core/journal.ts
|
|
28821
29146
|
function pad(n) {
|
|
28822
29147
|
return String(n).padStart(2, "0");
|
|
@@ -28984,7 +29309,11 @@ async function readJournal(app, settings, migrationId) {
|
|
|
28984
29309
|
async function markUndone(app, settings, migrationId, undoneBy, now) {
|
|
28985
29310
|
await appendLine(app, journalFileFor(settings, migrationId), { type: "undone", migration_id: migrationId, undone_by: undoneBy, at: now.toISOString() });
|
|
28986
29311
|
}
|
|
29312
|
+
function tableOfEntry(e) {
|
|
29313
|
+
return e.schema?.includes("#") ? e.schema.slice(e.schema.indexOf("#") + 1) : void 0;
|
|
29314
|
+
}
|
|
28987
29315
|
async function listMigrations(app, settings, input = {}) {
|
|
29316
|
+
const scope = currentReadScope();
|
|
28988
29317
|
const files = (await storeList(app, journalDir(settings))).files.filter((f) => f.endsWith(".jsonl")).sort().reverse();
|
|
28989
29318
|
const limit = Math.max(1, Math.min(input.limit ?? 50, 1e3));
|
|
28990
29319
|
const since = input.since ? Date.parse(input.since) : NaN;
|
|
@@ -29000,13 +29329,17 @@ async function listMigrations(app, settings, input = {}) {
|
|
|
29000
29329
|
if (input.op && e.op !== input.op) continue;
|
|
29001
29330
|
if (input.table && !(e.schema ?? "").includes(input.table) && !e.summary.includes(input.table)) continue;
|
|
29002
29331
|
if (!Number.isNaN(since) && Date.parse(e.executed_at) < since) continue;
|
|
29332
|
+
if (scope) {
|
|
29333
|
+
const table = tableOfEntry(e);
|
|
29334
|
+
if (table === void 0 || !tableReadable(table, scope)) continue;
|
|
29335
|
+
}
|
|
29003
29336
|
total++;
|
|
29004
29337
|
if (out2.length >= limit) continue;
|
|
29005
29338
|
out2.push({
|
|
29006
29339
|
migration_id: e.migration_id,
|
|
29007
29340
|
op: e.op,
|
|
29008
29341
|
summary: e.summary,
|
|
29009
|
-
...e
|
|
29342
|
+
...tableOfEntry(e) !== void 0 ? { table: tableOfEntry(e) } : {},
|
|
29010
29343
|
executed_at: e.executed_at,
|
|
29011
29344
|
notes_changed: e.rows.length,
|
|
29012
29345
|
files_changed: e.creates.length + e.moves.length + e.references.length + (e.deleted?.length ?? 0),
|
|
@@ -29116,6 +29449,7 @@ var init_journal = __esm({
|
|
|
29116
29449
|
init_store();
|
|
29117
29450
|
init_database_move();
|
|
29118
29451
|
init_tracing();
|
|
29452
|
+
init_read_scope();
|
|
29119
29453
|
BLOB_MIN = 2048;
|
|
29120
29454
|
MigrationNotFoundError = class extends Error {
|
|
29121
29455
|
constructor(migrationId) {
|
|
@@ -31624,10 +31958,146 @@ var init_link_paths = __esm({
|
|
|
31624
31958
|
}
|
|
31625
31959
|
});
|
|
31626
31960
|
|
|
31961
|
+
// src/core/query-tables.ts
|
|
31962
|
+
function refOf2(model, name, kind) {
|
|
31963
|
+
try {
|
|
31964
|
+
return resolveCatalogRef(model, name, kind)?.full ?? null;
|
|
31965
|
+
} catch {
|
|
31966
|
+
return null;
|
|
31967
|
+
}
|
|
31968
|
+
}
|
|
31969
|
+
function isSystemTable(name) {
|
|
31970
|
+
const t = name.trim();
|
|
31971
|
+
if (t.startsWith(`${SYSTEM_CATALOG}.`)) return true;
|
|
31972
|
+
const parts = t.split(".");
|
|
31973
|
+
return parts.length === 2 && parts[0] === "information_schema";
|
|
31974
|
+
}
|
|
31975
|
+
function queryTables(q, model, out2, depth) {
|
|
31976
|
+
const text5 = JSON.stringify(q);
|
|
31977
|
+
const r = { vault: false, today: text5.includes("{today"), now: text5.includes("{now") };
|
|
31978
|
+
if (depth > 8) return { ...r, vault: true };
|
|
31979
|
+
if (text5.includes("within") || text5.includes("{this")) r.vault = true;
|
|
31980
|
+
if (DOTTED_KEY.test(text5) && !linkedTables(q, model, out2)) r.vault = true;
|
|
31981
|
+
if (text5.includes("file_backlink_count")) r.links = true;
|
|
31982
|
+
if (Object.keys(q).some((k) => !PLAIN_SELECT_KEYS.has(k))) r.vault = true;
|
|
31983
|
+
const sources = [q.table];
|
|
31984
|
+
if (q.join && typeof q.join === "object") sources.push(q.join.table);
|
|
31985
|
+
if (Array.isArray(q.union)) for (const b of q.union) sources.push(b && typeof b === "object" ? b.table : void 0);
|
|
31986
|
+
for (const name of sources) {
|
|
31987
|
+
if (typeof name === "string" && isSystemTable(name)) {
|
|
31988
|
+
r.store = true;
|
|
31989
|
+
continue;
|
|
31990
|
+
}
|
|
31991
|
+
if (typeof name !== "string" || name.startsWith("base:")) {
|
|
31992
|
+
r.vault = true;
|
|
31993
|
+
continue;
|
|
31994
|
+
}
|
|
31995
|
+
const table = refOf2(model, name, "table");
|
|
31996
|
+
const view = refOf2(model, name, "view");
|
|
31997
|
+
if (!table && !view) r.vault = true;
|
|
31998
|
+
if (table) out2.add(table);
|
|
31999
|
+
const v = view ? viewQuery(model, view) : null;
|
|
32000
|
+
if (view && !v) r.vault = true;
|
|
32001
|
+
if (v) {
|
|
32002
|
+
const sub = queryTables(v, model, out2, depth + 1);
|
|
32003
|
+
r.vault ||= sub.vault;
|
|
32004
|
+
r.links ||= sub.links;
|
|
32005
|
+
r.store ||= sub.store;
|
|
32006
|
+
r.today ||= sub.today;
|
|
32007
|
+
r.now ||= sub.now;
|
|
32008
|
+
}
|
|
32009
|
+
}
|
|
32010
|
+
return r;
|
|
32011
|
+
}
|
|
32012
|
+
function plainTable(model, name) {
|
|
32013
|
+
return typeof name === "string" && !refOf2(model, name, "view") ? refOf2(model, name, "table") : null;
|
|
32014
|
+
}
|
|
32015
|
+
function linkedTables(q, model, out2) {
|
|
32016
|
+
const join = isRecord2(q.join) ? q.join : null;
|
|
32017
|
+
const branches = Array.isArray(q.union) ? q.union : null;
|
|
32018
|
+
const rest = {
|
|
32019
|
+
...q,
|
|
32020
|
+
where: void 0,
|
|
32021
|
+
join: join ? { ...join, where: void 0 } : q.join,
|
|
32022
|
+
union: branches ? branches.map((b) => isRecord2(b) ? { ...b, where: void 0 } : b) : q.union
|
|
32023
|
+
};
|
|
32024
|
+
if (DOTTED_KEY.test(JSON.stringify(rest))) return false;
|
|
32025
|
+
const roots = branches ? branches.map((b) => isRecord2(b) ? plainTable(model, b.table) : null) : [plainTable(model, q.table)];
|
|
32026
|
+
if (roots.length === 0) return false;
|
|
32027
|
+
for (const table of roots) if (!followKeys(q.where, table, model, out2)) return false;
|
|
32028
|
+
if (join && !followKeys(join.where, plainTable(model, join.table), model, out2)) return false;
|
|
32029
|
+
for (const b of branches ?? []) if (isRecord2(b) && !followKeys(b.where, plainTable(model, b.table), model, out2)) return false;
|
|
32030
|
+
return true;
|
|
32031
|
+
}
|
|
32032
|
+
function followKeys(where, table, model, out2) {
|
|
32033
|
+
if (where === null || typeof where !== "object") return true;
|
|
32034
|
+
if (Array.isArray(where)) return where.every((w) => followKeys(w, table, model, out2));
|
|
32035
|
+
for (const [key2, cond] of Object.entries(where)) {
|
|
32036
|
+
if (key2 === "and" || key2 === "or" || key2 === "not") {
|
|
32037
|
+
if (!followKeys(cond, table, model, out2)) return false;
|
|
32038
|
+
} else if (key2.indexOf(".") > 0 && (table === null || !followPath(key2, table, model, out2))) return false;
|
|
32039
|
+
}
|
|
32040
|
+
return true;
|
|
32041
|
+
}
|
|
32042
|
+
function followPath(key2, table, model, out2) {
|
|
32043
|
+
let current2 = table;
|
|
32044
|
+
for (const column of key2.split(".").slice(0, -1)) {
|
|
32045
|
+
const next = linkTarget(model, current2, column);
|
|
32046
|
+
if (!next) return false;
|
|
32047
|
+
out2.add(next);
|
|
32048
|
+
current2 = next;
|
|
32049
|
+
}
|
|
32050
|
+
return true;
|
|
32051
|
+
}
|
|
32052
|
+
function linkTarget(model, full, column) {
|
|
32053
|
+
const t = catalogTables(model).find((x) => x.ref.full === full);
|
|
32054
|
+
if (!t) return null;
|
|
32055
|
+
let columns;
|
|
32056
|
+
try {
|
|
32057
|
+
columns = effectiveColumns(model, t.ref, t.entry);
|
|
32058
|
+
} catch {
|
|
32059
|
+
return null;
|
|
32060
|
+
}
|
|
32061
|
+
const c = columns.find((x) => x.name === column);
|
|
32062
|
+
const link = c !== void 0 && (c.type === "link" || c.type === "list" && c.items === "link");
|
|
32063
|
+
return link && typeof c.ref === "string" ? refOf2(model, c.ref, "table") : null;
|
|
32064
|
+
}
|
|
32065
|
+
function viewQuery(model, full) {
|
|
32066
|
+
const [catalog, schema, name] = full.split(".");
|
|
32067
|
+
const q = model.catalogs[catalog]?.schemas[schema]?.views[name]?.query;
|
|
32068
|
+
return q && typeof q === "object" ? q : null;
|
|
32069
|
+
}
|
|
32070
|
+
function tablesReadByQuery(q, model) {
|
|
32071
|
+
const found = /* @__PURE__ */ new Set();
|
|
32072
|
+
const r = queryTables(q, model, found, 0);
|
|
32073
|
+
return { tables: [...found].sort(), complete: !r.vault };
|
|
32074
|
+
}
|
|
32075
|
+
function tablesReadByView(model, viewFull) {
|
|
32076
|
+
const q = viewQuery(model, viewFull);
|
|
32077
|
+
return q ? tablesReadByQuery(q, model) : { tables: [], complete: false };
|
|
32078
|
+
}
|
|
32079
|
+
var PLAIN_SELECT_KEYS, DOTTED_KEY, isRecord2;
|
|
32080
|
+
var init_query_tables = __esm({
|
|
32081
|
+
"src/core/query-tables.ts"() {
|
|
32082
|
+
"use strict";
|
|
32083
|
+
init_metastore();
|
|
32084
|
+
PLAIN_SELECT_KEYS = /* @__PURE__ */ new Set(["table", "columns", "where", "order_by", "limit", "offset", "group_by", "aggregates", "include_body", "join", "union"]);
|
|
32085
|
+
DOTTED_KEY = /"[^"]*\.[^"]*"\s*:/;
|
|
32086
|
+
isRecord2 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
32087
|
+
}
|
|
32088
|
+
});
|
|
32089
|
+
|
|
31627
32090
|
// src/core/table-registry.ts
|
|
32091
|
+
function viewReadable(model, viewFull, scope = currentReadScope()) {
|
|
32092
|
+
if (scope === null) return true;
|
|
32093
|
+
if (!tableReadable(viewFull, scope)) return false;
|
|
32094
|
+
const reads = tablesReadByView(model, viewFull);
|
|
32095
|
+
return reads.complete && reads.tables.every((t) => tableReadable(t, scope));
|
|
32096
|
+
}
|
|
31628
32097
|
function tableNotFound(ref2, model, kind = "table") {
|
|
31629
|
-
const
|
|
31630
|
-
const
|
|
32098
|
+
const scope = currentReadScope();
|
|
32099
|
+
const tables = model ? catalogTables(model).filter(({ ref: r }) => tableReadable(r.full, scope)).map(({ ref: r, entry }) => ({ r, aliases: entry.aliases })) : [];
|
|
32100
|
+
const views = model ? catalogViews(model).filter(({ ref: r }) => viewReadable(model, r.full, scope)).map(({ ref: r, view }) => ({ r, aliases: view.aliases })) : [];
|
|
31631
32101
|
if (model && kind === "table") {
|
|
31632
32102
|
const view = views.find(({ r }) => r.full === ref2.trim() || r.table === ref2.trim() || `${r.schema}.${r.table}` === ref2.trim());
|
|
31633
32103
|
if (view) {
|
|
@@ -31654,7 +32124,7 @@ function tableNotFound(ref2, model, kind = "table") {
|
|
|
31654
32124
|
guide(
|
|
31655
32125
|
sentences(
|
|
31656
32126
|
near.length > 0 ? `Did you mean ${near.join(" or ")}?` : "",
|
|
31657
|
-
kind !== "view" ? tableNames.length > 0 ? `Tables: ${nameList(tableNames)}.` : "There are no tables yet (create_table makes one)." : "",
|
|
32127
|
+
kind !== "view" ? tableNames.length > 0 ? `Tables: ${nameList(tableNames)}.` : scope ? `Token ${scope.token} may read no table.` : "There are no tables yet (create_table makes one)." : "",
|
|
31658
32128
|
kind !== "table" && viewNames.length > 0 ? `Views: ${nameList(viewNames)}.` : kind === "view" ? "There are no views." : "",
|
|
31659
32129
|
"list_tables lists them with their descriptions and aliases."
|
|
31660
32130
|
),
|
|
@@ -31791,6 +32261,8 @@ var init_table_registry = __esm({
|
|
|
31791
32261
|
init_vault_index();
|
|
31792
32262
|
init_hints();
|
|
31793
32263
|
init_db_paths();
|
|
32264
|
+
init_read_scope();
|
|
32265
|
+
init_query_tables();
|
|
31794
32266
|
TableNotFoundError = class extends GuidedError {
|
|
31795
32267
|
constructor(ref2, guidance) {
|
|
31796
32268
|
super(`Table not found: ${ref2}`, guidance);
|
|
@@ -32099,57 +32571,68 @@ async function describeDatabase(app, settings) {
|
|
|
32099
32571
|
const config2 = await readDatabaseConfig(app, settings);
|
|
32100
32572
|
const ms = await loadMetastore(app, settings);
|
|
32101
32573
|
const catalogs = [];
|
|
32574
|
+
const scope = currentReadScope();
|
|
32575
|
+
const readable = (full) => tableReadable(full, scope);
|
|
32102
32576
|
if (ms) {
|
|
32103
|
-
const byTable = new Map(catalogTables(ms.model).map((x) => [x.ref.full, catalogLoadedTable(settings, ms, x.ref)]));
|
|
32577
|
+
const byTable = new Map(catalogTables(ms.model).filter((x) => readable(x.ref.full)).map((x) => [x.ref.full, catalogLoadedTable(settings, ms, x.ref)]));
|
|
32104
32578
|
const rowFiles = catalogRowFilesOf(app, settings, [...byTable].map(([full, t]) => ({ full, def: t.def })));
|
|
32105
32579
|
const unreadable = /* @__PURE__ */ new Map();
|
|
32106
32580
|
for (const u of await unreadableRows(app, settings, ms.model)) unreadable.set(u.table, (unreadable.get(u.table) ?? 0) + 1);
|
|
32107
32581
|
for (const [catalog, cat] of Object.entries(ms.model.catalogs)) {
|
|
32582
|
+
const schemas = Object.entries(cat.schemas).map(([schema, sch]) => ({
|
|
32583
|
+
schema,
|
|
32584
|
+
...sch.description ? { description: sch.description } : {},
|
|
32585
|
+
tables: Object.entries(sch.tables).filter(([name]) => readable(`${catalog}.${schema}.${name}`)).map(([name, entry]) => {
|
|
32586
|
+
const full = `${catalog}.${schema}.${name}`;
|
|
32587
|
+
const t = byTable.get(full);
|
|
32588
|
+
const tags = tableTags(entry);
|
|
32589
|
+
const missing = missingDescriptions(t.def.options.description, t.def.columns);
|
|
32590
|
+
return {
|
|
32591
|
+
name,
|
|
32592
|
+
qualified: full,
|
|
32593
|
+
row_count: rowFiles.get(full).length,
|
|
32594
|
+
...entry.description ? { description: entry.description } : {},
|
|
32595
|
+
...tags ? { tags } : {},
|
|
32596
|
+
...missing ? { missing_descriptions: missing } : {},
|
|
32597
|
+
...unreadable.has(full) ? { unreadable_count: unreadable.get(full) } : {}
|
|
32598
|
+
};
|
|
32599
|
+
}),
|
|
32600
|
+
views: Object.entries(sch.views).filter(([name]) => viewReadable(ms.model, `${catalog}.${schema}.${name}`, scope)).map(([name, v]) => ({
|
|
32601
|
+
name,
|
|
32602
|
+
qualified: `${catalog}.${schema}.${name}`,
|
|
32603
|
+
...v.description ? { description: v.description } : {}
|
|
32604
|
+
}))
|
|
32605
|
+
})).filter((x) => !scope || x.tables.length > 0 || x.views.length > 0);
|
|
32606
|
+
if (scope && schemas.length === 0) continue;
|
|
32108
32607
|
catalogs.push({
|
|
32109
32608
|
catalog,
|
|
32110
32609
|
...cat.description ? { description: cat.description } : {},
|
|
32111
32610
|
...catalog === config2.default_catalog ? { default: true } : {},
|
|
32112
|
-
schemas
|
|
32113
|
-
schema,
|
|
32114
|
-
...sch.description ? { description: sch.description } : {},
|
|
32115
|
-
tables: Object.entries(sch.tables).map(([name, entry]) => {
|
|
32116
|
-
const full = `${catalog}.${schema}.${name}`;
|
|
32117
|
-
const t = byTable.get(full);
|
|
32118
|
-
const tags = tableTags(entry);
|
|
32119
|
-
const missing = missingDescriptions(t.def.options.description, t.def.columns);
|
|
32120
|
-
return {
|
|
32121
|
-
name,
|
|
32122
|
-
qualified: full,
|
|
32123
|
-
row_count: rowFiles.get(full).length,
|
|
32124
|
-
...entry.description ? { description: entry.description } : {},
|
|
32125
|
-
...tags ? { tags } : {},
|
|
32126
|
-
...missing ? { missing_descriptions: missing } : {},
|
|
32127
|
-
...unreadable.has(full) ? { unreadable_count: unreadable.get(full) } : {}
|
|
32128
|
-
};
|
|
32129
|
-
}),
|
|
32130
|
-
views: Object.entries(sch.views).map(([name, v]) => ({
|
|
32131
|
-
name,
|
|
32132
|
-
qualified: `${catalog}.${schema}.${name}`,
|
|
32133
|
-
...v.description ? { description: v.description } : {}
|
|
32134
|
-
}))
|
|
32135
|
-
}))
|
|
32611
|
+
schemas
|
|
32136
32612
|
});
|
|
32137
32613
|
}
|
|
32138
32614
|
}
|
|
32615
|
+
const extending = (name, seen = /* @__PURE__ */ new Set()) => {
|
|
32616
|
+
if (!ms || seen.has(name)) return [];
|
|
32617
|
+
seen.add(name);
|
|
32618
|
+
const direct = catalogTables(ms.model).filter(({ entry }) => (entry.bases ?? []).includes(name)).map(({ ref: ref2 }) => ref2.full);
|
|
32619
|
+
const via = Object.entries(ms.model.bases).filter(([, b]) => (b.bases ?? []).includes(name)).flatMap(([n]) => extending(n, seen));
|
|
32620
|
+
return [...direct, ...via];
|
|
32621
|
+
};
|
|
32139
32622
|
return {
|
|
32140
32623
|
root,
|
|
32141
32624
|
metastore: metastorePath(settings),
|
|
32142
32625
|
journal: storePath("journal"),
|
|
32143
32626
|
config: config2,
|
|
32144
32627
|
catalogs,
|
|
32145
|
-
bases: ms ? Object.entries(ms.model.bases).map(([name, b]) => {
|
|
32628
|
+
bases: ms ? Object.entries(ms.model.bases).filter(([name]) => !scope || extending(name).some(readable)).map(([name, b]) => {
|
|
32146
32629
|
const missing = missingDescriptions(b.description, b.columns);
|
|
32147
32630
|
return {
|
|
32148
32631
|
name,
|
|
32149
32632
|
...b.description ? { description: b.description } : {},
|
|
32150
32633
|
...b.bases ? { bases: b.bases } : {},
|
|
32151
32634
|
columns: b.columns.map((c) => String(c.name)),
|
|
32152
|
-
tables: catalogTables(ms.model).filter(({ entry }) => (entry.bases ?? []).includes(name)).map(({ ref: ref2 }) => ref2.full),
|
|
32635
|
+
tables: catalogTables(ms.model).filter(({ ref: ref2, entry }) => (entry.bases ?? []).includes(name) && readable(ref2.full)).map(({ ref: ref2 }) => ref2.full),
|
|
32153
32636
|
...missing ? { missing_descriptions: { base: missing.table, columns: missing.columns } } : {}
|
|
32154
32637
|
};
|
|
32155
32638
|
}) : []
|
|
@@ -32392,6 +32875,8 @@ var init_database = __esm({
|
|
|
32392
32875
|
init_descriptions();
|
|
32393
32876
|
init_db_paths();
|
|
32394
32877
|
init_unreadable();
|
|
32878
|
+
init_read_scope();
|
|
32879
|
+
init_table_registry();
|
|
32395
32880
|
DatabaseConfigError = class extends GuidedError {
|
|
32396
32881
|
constructor() {
|
|
32397
32882
|
super(...arguments);
|
|
@@ -32419,7 +32904,7 @@ var init_database = __esm({
|
|
|
32419
32904
|
|
|
32420
32905
|
// src/core/write-check.ts
|
|
32421
32906
|
function staleWrite(app) {
|
|
32422
|
-
const ctx =
|
|
32907
|
+
const ctx = storage3?.getStore();
|
|
32423
32908
|
if (ctx === void 0 || ctx.gen === generation(app)) return false;
|
|
32424
32909
|
ctx.stale = true;
|
|
32425
32910
|
return true;
|
|
@@ -32427,12 +32912,12 @@ function staleWrite(app) {
|
|
|
32427
32912
|
function wrote(app) {
|
|
32428
32913
|
const next = generation(app) + 1;
|
|
32429
32914
|
GENERATION.set(app, next);
|
|
32430
|
-
const ctx =
|
|
32915
|
+
const ctx = storage3?.getStore();
|
|
32431
32916
|
if (ctx) ctx.gen = next;
|
|
32432
32917
|
}
|
|
32433
32918
|
async function runWrite(app, fn, isStale) {
|
|
32434
|
-
if (!
|
|
32435
|
-
const s =
|
|
32919
|
+
if (!storage3) return fn();
|
|
32920
|
+
const s = storage3;
|
|
32436
32921
|
let ctx = { gen: 0, stale: false };
|
|
32437
32922
|
const attempt = () => s.run(ctx = { gen: generation(app), stale: false }, fn);
|
|
32438
32923
|
let result = await attempt();
|
|
@@ -32442,19 +32927,19 @@ async function runWrite(app, fn, isStale) {
|
|
|
32442
32927
|
function holdsStaleWrite(value) {
|
|
32443
32928
|
return JSON.stringify(value ?? null).includes(STALE_WRITE);
|
|
32444
32929
|
}
|
|
32445
|
-
var STALE_WRITE, WRITE_ATTEMPTS, GENERATION,
|
|
32930
|
+
var STALE_WRITE, WRITE_ATTEMPTS, GENERATION, storage3, generation;
|
|
32446
32931
|
var init_write_check = __esm({
|
|
32447
32932
|
"src/core/write-check.ts"() {
|
|
32448
32933
|
"use strict";
|
|
32449
32934
|
STALE_WRITE = "the database changed while this write was being checked";
|
|
32450
32935
|
WRITE_ATTEMPTS = 5;
|
|
32451
32936
|
GENERATION = /* @__PURE__ */ new WeakMap();
|
|
32452
|
-
|
|
32937
|
+
storage3 = null;
|
|
32453
32938
|
try {
|
|
32454
32939
|
const { AsyncLocalStorage: AsyncLocalStorage2 } = require("node:async_hooks");
|
|
32455
|
-
|
|
32940
|
+
storage3 = new AsyncLocalStorage2();
|
|
32456
32941
|
} catch {
|
|
32457
|
-
|
|
32942
|
+
storage3 = null;
|
|
32458
32943
|
}
|
|
32459
32944
|
generation = (app) => GENERATION.get(app) ?? 0;
|
|
32460
32945
|
}
|
|
@@ -32591,10 +33076,10 @@ var init_query = __esm({
|
|
|
32591
33076
|
});
|
|
32592
33077
|
|
|
32593
33078
|
// src/core/this-tokens.ts
|
|
32594
|
-
function hostValue(host2,
|
|
32595
|
-
const own2 = THIS_NOTE_FIELDS.get(
|
|
33079
|
+
function hostValue(host2, field3) {
|
|
33080
|
+
const own2 = THIS_NOTE_FIELDS.get(field3);
|
|
32596
33081
|
if (own2) return own2(host2);
|
|
32597
|
-
return
|
|
33082
|
+
return field3.split(".").reduce((v, k) => v && typeof v === "object" ? v[k] : void 0, host2.frontmatter);
|
|
32598
33083
|
}
|
|
32599
33084
|
function shiftDate3(value, sign, n, unit) {
|
|
32600
33085
|
const s = typeof value === "string" ? value : value instanceof Date ? value.toISOString() : null;
|
|
@@ -32815,6 +33300,7 @@ async function resolveWithin(app, settings, t, where) {
|
|
|
32815
33300
|
const viaLink = via && (via.type === "link" || via.type === "list" && via.items === "link");
|
|
32816
33301
|
if (!via || !viaLink || !via.ref) throw new OperationInputError(`where "${col}": "${head}" is not a link column with ref`, notALink(head, t));
|
|
32817
33302
|
const target2 = via.ref === t.catalog.ref.full ? t : await loadTable(app, settings, via.ref);
|
|
33303
|
+
assertTableReadable(target2.catalog.ref.full);
|
|
32818
33304
|
const sub = await resolveWithin(app, settings, target2, { [tail]: cond });
|
|
32819
33305
|
const pred = compileWhere(sub, target2, settings, rowGetter(app, target2, settings));
|
|
32820
33306
|
const hits = (await loadRows(app, settings, target2)).filter(pred).map((r) => `[[${basenameOf(r.file.path)}]]`);
|
|
@@ -32830,6 +33316,7 @@ async function resolveWithin(app, settings, t, where) {
|
|
|
32830
33316
|
const isLink2 = spec2 && (spec2.type === "link" || spec2.type === "list" && spec2.items === "link");
|
|
32831
33317
|
if (!spec2 || !isLink2 || !spec2.ref) throw new OperationInputError(`"within" needs a link column with ref: "${col}" is not one`, notALink(col, t));
|
|
32832
33318
|
const target = spec2.ref === t.catalog.ref.full ? t : await loadTable(app, settings, spec2.ref);
|
|
33319
|
+
assertTableReadable(target.catalog.ref.full);
|
|
32833
33320
|
const h = target.def.columns.find((c) => c.hierarchy);
|
|
32834
33321
|
if (!h) throw new OperationInputError(`"within": ${spec2.ref} has no hierarchy column (e.g. parent with hierarchy: true)`, guide(`"within" follows a tree: give ${spec2.ref} a parent link with hierarchy: true (add_column), or filter with "eq" / "in" instead.`, "tree"));
|
|
32835
33322
|
const raw = cond.within;
|
|
@@ -32932,6 +33419,7 @@ var init_where = __esm({
|
|
|
32932
33419
|
init_path_templates();
|
|
32933
33420
|
init_this_tokens();
|
|
32934
33421
|
init_hints();
|
|
33422
|
+
init_read_scope();
|
|
32935
33423
|
LOGICAL_HINT = guide(`"and" and "or" take a list of where objects, "not" one: {"or": [{"status": "watched"}, {"rating": {"gte": 4}}]}.`, "logical");
|
|
32936
33424
|
LOGICAL_KEYS = /* @__PURE__ */ new Set(["or", "and", "not"]);
|
|
32937
33425
|
OPERATORS = /* @__PURE__ */ new Set(["eq", "ne", "gt", "gte", "lt", "lte", "in", "contains", "icontains", "exists", "within"]);
|
|
@@ -33297,12 +33785,16 @@ async function linkTargetRule(t, settings, links, c, loadSource = true) {
|
|
|
33297
33785
|
}
|
|
33298
33786
|
const out2 = [];
|
|
33299
33787
|
const shown = () => {
|
|
33788
|
+
const scope = currentReadScope();
|
|
33789
|
+
if (scope && [target.catalog.ref.full, ...links.reads(target, c.ref_where)].some((full) => !tableReadable(full, scope))) {
|
|
33790
|
+
return `the link must name a row of ${c.name}'s target table`;
|
|
33791
|
+
}
|
|
33300
33792
|
const ok2 = targets(d).filter((p) => p !== row.file.path).map(basenameOf);
|
|
33301
|
-
return ok2.length === 0 ? "none" : `${ok2.slice(0, VALID_TARGETS_SHOWN).join(", ")}${ok2.length > VALID_TARGETS_SHOWN ? ` (+${ok2.length - VALID_TARGETS_SHOWN} more)` : ""}`;
|
|
33793
|
+
return `valid: ${ok2.length === 0 ? "none" : `${ok2.slice(0, VALID_TARGETS_SHOWN).join(", ")}${ok2.length > VALID_TARGETS_SHOWN ? ` (+${ok2.length - VALID_TARGETS_SHOWN} more)` : ""}`}`;
|
|
33302
33794
|
};
|
|
33303
33795
|
const missing = given.filter((_, i) => paths[i] == null || !rows.has(paths[i])).map((x) => `[[${basenameOf(parseWikilink(x) ?? String(x))}]]`);
|
|
33304
33796
|
if (missing.length > 0) {
|
|
33305
|
-
out2.push({ column: c.name, code: "broken_link", message: `${missing.join(", ")} ${missing.length > 1 ? "are not rows" : "is not a row"} of ${target.catalog.ref.full};
|
|
33797
|
+
out2.push({ column: c.name, code: "broken_link", message: `${missing.join(", ")} ${missing.length > 1 ? "are not rows" : "is not a row"} of ${target.catalog.ref.full}; ${shown()}`, value: v });
|
|
33306
33798
|
}
|
|
33307
33799
|
if (paths.includes(row.file.path)) {
|
|
33308
33800
|
out2.push({ column: c.name, code: "ref", message: `${list4 ? `${c.name} cannot hold` : `a ${c.name} cannot be`} the row itself`, value: v });
|
|
@@ -33313,7 +33805,7 @@ async function linkTargetRule(t, settings, links, c, loadSource = true) {
|
|
|
33313
33805
|
});
|
|
33314
33806
|
if (bad.length > 0) {
|
|
33315
33807
|
const what = list4 ? `${bad.length > 1 ? "are not valid targets" : "is not a valid target"} in ${c.name}` : `is not a valid ${c.name}`;
|
|
33316
|
-
out2.push({ column: c.name, code: "ref", message: `${bad.map((p) => `[[${basenameOf(p)}]]`).join(", ")} ${what}${of} (needs ${needs(d)});
|
|
33808
|
+
out2.push({ column: c.name, code: "ref", message: `${bad.map((p) => `[[${basenameOf(p)}]]`).join(", ")} ${what}${of} (needs ${needs(d)}); ${shown()}`, value: v });
|
|
33317
33809
|
}
|
|
33318
33810
|
return out2;
|
|
33319
33811
|
};
|
|
@@ -33408,6 +33900,7 @@ function findRow(rows, ref2, table) {
|
|
|
33408
33900
|
}
|
|
33409
33901
|
async function linkOptionsImpl(app, settings, input) {
|
|
33410
33902
|
const t = await loadTable(app, settings, input.table);
|
|
33903
|
+
assertTableReadable(t.catalog.ref.full);
|
|
33411
33904
|
const c = refLinkColumn(t, input.column);
|
|
33412
33905
|
if (!c) {
|
|
33413
33906
|
const links2 = t.def.columns.filter((x) => refLinkColumn(t, x.name)).map((x) => x.name);
|
|
@@ -33426,6 +33919,9 @@ async function linkOptionsImpl(app, settings, input) {
|
|
|
33426
33919
|
const links = new CheckLinks(app, settings);
|
|
33427
33920
|
const given = input.row !== void 0 && input.row !== null && String(input.row).trim() !== "";
|
|
33428
33921
|
const rule = await linkTargetRule(t, settings, links, c, given);
|
|
33922
|
+
if (currentReadScope()) {
|
|
33923
|
+
for (const full of [rule.target.catalog.ref.full, ...links.reads(rule.target, c.ref_where)]) assertTableReadable(full);
|
|
33924
|
+
}
|
|
33429
33925
|
let ordered = [];
|
|
33430
33926
|
let self;
|
|
33431
33927
|
if (given) {
|
|
@@ -33484,6 +33980,7 @@ var init_link_targets = __esm({
|
|
|
33484
33980
|
init_where();
|
|
33485
33981
|
init_check_links();
|
|
33486
33982
|
init_hints();
|
|
33983
|
+
init_read_scope();
|
|
33487
33984
|
VALID_TARGETS_SHOWN = 20;
|
|
33488
33985
|
}
|
|
33489
33986
|
});
|
|
@@ -33491,9 +33988,51 @@ var init_link_targets = __esm({
|
|
|
33491
33988
|
// src/core/changeset-checks.ts
|
|
33492
33989
|
var changeset_checks_exports = {};
|
|
33493
33990
|
__export(changeset_checks_exports, {
|
|
33991
|
+
HIDDEN_CHECK_MESSAGE: () => HIDDEN_CHECK_MESSAGE,
|
|
33494
33992
|
linkedCheckViolations: () => linkedCheckViolations,
|
|
33495
|
-
metastoreLeftBy: () => metastoreLeftBy
|
|
33993
|
+
metastoreLeftBy: () => metastoreLeftBy,
|
|
33994
|
+
pathHidden: () => pathHidden
|
|
33496
33995
|
});
|
|
33996
|
+
function pathHidden(app, settings, path23, scope = currentReadScope()) {
|
|
33997
|
+
if (scope === null || path23 === "" || app.vault.getAbstractFileByPath(path23) === null) return false;
|
|
33998
|
+
return !pathReadable(app, settings, path23, scope);
|
|
33999
|
+
}
|
|
34000
|
+
function movedOffHiddenRows(app, settings, cs, written2, consequential, out2, scope) {
|
|
34001
|
+
const reported = new Map(written2);
|
|
34002
|
+
for (const m of cs.moves) reported.set(m.from, written2.get(m.to) ?? m.from);
|
|
34003
|
+
for (const p of cs.deletes) reported.set(p, p);
|
|
34004
|
+
const visible = (p) => !pathHidden(app, settings, p, scope);
|
|
34005
|
+
const moved = /* @__PURE__ */ new Map();
|
|
34006
|
+
const add = (path23, v) => {
|
|
34007
|
+
const list4 = moved.get(path23) ?? [];
|
|
34008
|
+
if (!list4.some((x) => x.code === v.code && x.column === v.column && x.message === v.message)) list4.push(v);
|
|
34009
|
+
moved.set(path23, list4);
|
|
34010
|
+
};
|
|
34011
|
+
for (const [path23, violations] of out2) {
|
|
34012
|
+
if (visible(path23)) {
|
|
34013
|
+
for (const v of violations) add(path23, v);
|
|
34014
|
+
continue;
|
|
34015
|
+
}
|
|
34016
|
+
const file = app.vault.getAbstractFileByPath(path23);
|
|
34017
|
+
const fm = app.metadataCache.getFileCache(file)?.frontmatter ?? {};
|
|
34018
|
+
const targets = [];
|
|
34019
|
+
for (const [key2, value] of Object.entries(fm)) {
|
|
34020
|
+
for (const x of Array.isArray(value) ? value : [value]) {
|
|
34021
|
+
const lp = parseWikilink(x);
|
|
34022
|
+
const dest = lp == null ? null : app.metadataCache.getFirstLinkpathDest(lp, path23);
|
|
34023
|
+
const to = dest ? reported.get(dest.path) : void 0;
|
|
34024
|
+
if (to !== void 0 && visible(to)) targets.push([to, key2]);
|
|
34025
|
+
}
|
|
34026
|
+
}
|
|
34027
|
+
const own2 = [...new Set(written2.values())].filter((p) => !consequential.has(p) && visible(p)).sort();
|
|
34028
|
+
for (const v of violations) {
|
|
34029
|
+
for (const [to, column] of targets) add(to, { column, code: v.code, message: HIDDEN_CHECK_MESSAGE });
|
|
34030
|
+
if (targets.length > 0) continue;
|
|
34031
|
+
for (const to of own2.length > 0 ? own2 : [""]) add(to, { code: v.code, message: HIDDEN_CHECK_MESSAGE });
|
|
34032
|
+
}
|
|
34033
|
+
}
|
|
34034
|
+
return moved;
|
|
34035
|
+
}
|
|
33497
34036
|
async function metastoreLeftBy(app, settings, cs) {
|
|
33498
34037
|
const path23 = metastorePath(settings);
|
|
33499
34038
|
const text5 = cs.texts.filter((t) => t.path === path23).pop()?.after;
|
|
@@ -33593,9 +34132,9 @@ async function linkedCheckViolationsImpl(app, settings, cs, consequential = /* @
|
|
|
33593
34132
|
const affected = restored || [...after.reads(s, where)].some((full) => after.changes(full));
|
|
33594
34133
|
if (!affected && !rows.some((r) => written2.has(r.file.path))) continue;
|
|
33595
34134
|
const heads = [...linkHeads(where)];
|
|
33596
|
-
let
|
|
34135
|
+
let passes2;
|
|
33597
34136
|
try {
|
|
33598
|
-
|
|
34137
|
+
passes2 = compileCheck(where, s, settings, after);
|
|
33599
34138
|
} catch (e) {
|
|
33600
34139
|
for (const r of rows) {
|
|
33601
34140
|
const p = written2.get(r.file.path);
|
|
@@ -33607,7 +34146,7 @@ async function linkedCheckViolationsImpl(app, settings, cs, consequential = /* @
|
|
|
33607
34146
|
for (const r of rows) {
|
|
33608
34147
|
const own2 = written2.get(r.file.path);
|
|
33609
34148
|
if (own2 === void 0 && !affected) continue;
|
|
33610
|
-
if (
|
|
34149
|
+
if (passes2(r)) continue;
|
|
33611
34150
|
const message = `violates CHECK ${name}: ${JSON.stringify(where)}`;
|
|
33612
34151
|
if (own2 === void 0 && restored) {
|
|
33613
34152
|
add(r.file.path, { constraint: name, code: "check", message: `${message} (CHECK ${name} comes back with this change)`, ...ownHint });
|
|
@@ -33625,7 +34164,9 @@ async function linkedCheckViolationsImpl(app, settings, cs, consequential = /* @
|
|
|
33625
34164
|
}
|
|
33626
34165
|
}
|
|
33627
34166
|
}
|
|
33628
|
-
|
|
34167
|
+
const scope = currentReadScope();
|
|
34168
|
+
const answer = scope ? movedOffHiddenRows(app, settings, cs, written2, consequential, out2, scope) : out2;
|
|
34169
|
+
return [...answer].map(([path23, violations]) => ({ path: path23, violations }));
|
|
33629
34170
|
}
|
|
33630
34171
|
function linkedCheckViolations(...args) {
|
|
33631
34172
|
return phase("engine.checks", async (span) => {
|
|
@@ -33634,6 +34175,7 @@ function linkedCheckViolations(...args) {
|
|
|
33634
34175
|
return v;
|
|
33635
34176
|
}, { "datanotes.checks.kind": "linked" });
|
|
33636
34177
|
}
|
|
34178
|
+
var HIDDEN_CHECK_MESSAGE;
|
|
33637
34179
|
var init_changeset_checks = __esm({
|
|
33638
34180
|
"src/core/changeset-checks.ts"() {
|
|
33639
34181
|
"use strict";
|
|
@@ -33645,6 +34187,9 @@ var init_changeset_checks = __esm({
|
|
|
33645
34187
|
init_link_targets();
|
|
33646
34188
|
init_message();
|
|
33647
34189
|
init_tracing();
|
|
34190
|
+
init_read_scope();
|
|
34191
|
+
init_validate();
|
|
34192
|
+
HIDDEN_CHECK_MESSAGE = "a linked row of a table this token may not read breaks one of its CHECKs";
|
|
33648
34193
|
}
|
|
33649
34194
|
});
|
|
33650
34195
|
|
|
@@ -33897,10 +34442,13 @@ async function checkOne(app, settings, table, everyUnreadable) {
|
|
|
33897
34442
|
async function checkImpl(app, settings, input) {
|
|
33898
34443
|
const limit = Math.min(Math.max(Math.trunc(input.limit ?? CHECK_LIMIT), 1), CHECK_MAX);
|
|
33899
34444
|
const offset = Math.max(Math.trunc(input.offset ?? 0), 0);
|
|
33900
|
-
const
|
|
34445
|
+
const scope = currentReadScope();
|
|
34446
|
+
const rowsOf = (list4, table) => list4.filter((r) => scope === null || pathReadable(app, settings, r.path, scope) || r.violations.every((v) => v.code === "unreadable")).map((r) => ({ path: r.path, ...table ? { table } : {}, violations: r.violations.map(checkViolation) }));
|
|
33901
34447
|
if (input.table) {
|
|
34448
|
+
if (scope) assertTableReadable((await loadTable(app, settings, input.table)).catalog.ref.full);
|
|
33902
34449
|
const r = await checkOne(app, settings, input.table);
|
|
33903
|
-
|
|
34450
|
+
const shown = rowsOf(r.violations);
|
|
34451
|
+
return { table: r.table, rows_checked: r.rows, total: shown.length, violations: shown.slice(offset, offset + limit) };
|
|
33904
34452
|
}
|
|
33905
34453
|
const model = (await loadMetastore(app, settings))?.model;
|
|
33906
34454
|
const all = [];
|
|
@@ -33908,7 +34456,7 @@ async function checkImpl(app, settings, input) {
|
|
|
33908
34456
|
let rows = 0;
|
|
33909
34457
|
let tables = 0;
|
|
33910
34458
|
const everyUnreadable = model ? { model, rows: await unreadableRows(app, settings, model) } : void 0;
|
|
33911
|
-
for (const full of (model ? catalogTables(model).map(({ ref: ref2 }) => ref2.full) : []).sort()) {
|
|
34459
|
+
for (const full of (model ? catalogTables(model).map(({ ref: ref2 }) => ref2.full) : []).filter((full2) => tableReadable(full2, scope)).sort()) {
|
|
33912
34460
|
let r;
|
|
33913
34461
|
try {
|
|
33914
34462
|
r = await checkOne(app, settings, full, everyUnreadable);
|
|
@@ -33965,6 +34513,7 @@ var init_row_checks = __esm({
|
|
|
33965
34513
|
init_acyclic();
|
|
33966
34514
|
init_unreadable();
|
|
33967
34515
|
init_tracing();
|
|
34516
|
+
init_read_scope();
|
|
33968
34517
|
CHECK_LIMIT = 500;
|
|
33969
34518
|
CHECK_MAX = 5e3;
|
|
33970
34519
|
}
|
|
@@ -34198,9 +34747,9 @@ async function checkExternalEdits(app, settings, input) {
|
|
|
34198
34747
|
const s = await table(ref2.full);
|
|
34199
34748
|
for (const [name, where] of checks) {
|
|
34200
34749
|
await after.prepare(s, where);
|
|
34201
|
-
let
|
|
34750
|
+
let passes2;
|
|
34202
34751
|
try {
|
|
34203
|
-
|
|
34752
|
+
passes2 = compileCheck(where, s, settings, after);
|
|
34204
34753
|
} catch {
|
|
34205
34754
|
continue;
|
|
34206
34755
|
}
|
|
@@ -34218,7 +34767,7 @@ async function checkExternalEdits(app, settings, input) {
|
|
|
34218
34767
|
return failure2(passed(r));
|
|
34219
34768
|
};
|
|
34220
34769
|
const chains = linkChains(where);
|
|
34221
|
-
for (const r of after.rowsOf(s)) await report(s, chains, r, () => failure2(
|
|
34770
|
+
for (const r of after.rowsOf(s)) await report(s, chains, r, () => failure2(passes2(r)), before ? prior(r) : void 0);
|
|
34222
34771
|
}
|
|
34223
34772
|
for (const c of filteredLinkColumns(s).filter((x) => filtered.has(x.name))) {
|
|
34224
34773
|
const check2 = await linkTargetCheck(s, settings, after, c);
|
|
@@ -36555,10 +37104,10 @@ function pbValue(v) {
|
|
|
36555
37104
|
if (typeof v === "boolean") return new Pb().uint(2, v ? 1 : 0);
|
|
36556
37105
|
return Number.isInteger(v) ? new Pb().uint(3, BigInt(v)) : new Pb().double(4, v);
|
|
36557
37106
|
}
|
|
36558
|
-
function pbAttrs(m,
|
|
37107
|
+
function pbAttrs(m, field3, a) {
|
|
36559
37108
|
for (const [k, v] of Object.entries(a)) {
|
|
36560
37109
|
if (v === void 0 || v === null) continue;
|
|
36561
|
-
m.message(
|
|
37110
|
+
m.message(field3, new Pb().string(1, k).message(2, pbValue(v)));
|
|
36562
37111
|
}
|
|
36563
37112
|
return m;
|
|
36564
37113
|
}
|
|
@@ -36650,48 +37199,48 @@ var init_otlp = __esm({
|
|
|
36650
37199
|
}
|
|
36651
37200
|
this.parts.push(Number(x));
|
|
36652
37201
|
}
|
|
36653
|
-
tag(
|
|
36654
|
-
this.varint(
|
|
37202
|
+
tag(field3, wire) {
|
|
37203
|
+
this.varint(field3 << 3 | wire);
|
|
36655
37204
|
}
|
|
36656
|
-
uint(
|
|
36657
|
-
this.tag(
|
|
37205
|
+
uint(field3, v) {
|
|
37206
|
+
this.tag(field3, 0);
|
|
36658
37207
|
this.varint(v);
|
|
36659
37208
|
return this;
|
|
36660
37209
|
}
|
|
36661
|
-
fixed32(
|
|
36662
|
-
this.tag(
|
|
37210
|
+
fixed32(field3, v) {
|
|
37211
|
+
this.tag(field3, 5);
|
|
36663
37212
|
for (let i = 0; i < 4; i++) this.parts.push(v >>> 8 * i & 255);
|
|
36664
37213
|
return this;
|
|
36665
37214
|
}
|
|
36666
|
-
fixed64(
|
|
36667
|
-
this.tag(
|
|
37215
|
+
fixed64(field3, v) {
|
|
37216
|
+
this.tag(field3, 1);
|
|
36668
37217
|
for (let i = 0; i < 8; i++) this.parts.push(Number(v >> BigInt(8 * i) & BigInt(255)));
|
|
36669
37218
|
return this;
|
|
36670
37219
|
}
|
|
36671
|
-
double(
|
|
36672
|
-
this.tag(
|
|
37220
|
+
double(field3, v) {
|
|
37221
|
+
this.tag(field3, 1);
|
|
36673
37222
|
const b = new DataView(new ArrayBuffer(8));
|
|
36674
37223
|
b.setFloat64(0, v, true);
|
|
36675
37224
|
for (let i = 0; i < 8; i++) this.parts.push(b.getUint8(i));
|
|
36676
37225
|
return this;
|
|
36677
37226
|
}
|
|
36678
|
-
bytes(
|
|
36679
|
-
this.tag(
|
|
37227
|
+
bytes(field3, data) {
|
|
37228
|
+
this.tag(field3, 2);
|
|
36680
37229
|
this.varint(data.length);
|
|
36681
37230
|
for (const x of data) this.parts.push(x);
|
|
36682
37231
|
return this;
|
|
36683
37232
|
}
|
|
36684
37233
|
/** An id given in hex (trace and span ids), as bytes. */
|
|
36685
|
-
hex(
|
|
37234
|
+
hex(field3, hex) {
|
|
36686
37235
|
const out2 = [];
|
|
36687
37236
|
for (let i = 0; i < hex.length; i += 2) out2.push(parseInt(hex.slice(i, i + 2), 16));
|
|
36688
|
-
return this.bytes(
|
|
37237
|
+
return this.bytes(field3, out2);
|
|
36689
37238
|
}
|
|
36690
|
-
string(
|
|
36691
|
-
return this.bytes(
|
|
37239
|
+
string(field3, s) {
|
|
37240
|
+
return this.bytes(field3, new TextEncoder().encode(s));
|
|
36692
37241
|
}
|
|
36693
|
-
message(
|
|
36694
|
-
return this.bytes(
|
|
37242
|
+
message(field3, m) {
|
|
37243
|
+
return this.bytes(field3, m.finish());
|
|
36695
37244
|
}
|
|
36696
37245
|
finish() {
|
|
36697
37246
|
return Uint8Array.from(this.parts);
|
|
@@ -36939,23 +37488,23 @@ var init_redact = __esm({
|
|
|
36939
37488
|
|
|
36940
37489
|
// src/api/extension-record.ts
|
|
36941
37490
|
function recordProblems(v, prefix = "") {
|
|
36942
|
-
if (!
|
|
37491
|
+
if (!isObject2(v)) return [`"${prefix.replace(/\.$/, "") || "record"}" must be an object`];
|
|
36943
37492
|
const out2 = [];
|
|
36944
|
-
const bad = (
|
|
37493
|
+
const bad = (field3, what) => out2.push(`"${prefix}${field3}" must be ${what}`);
|
|
36945
37494
|
if (typeof v.name !== "string" || !RECORD_NAME.test(v.name)) bad("name", "2 to 40 lowercase letters, digits or -, starting with a letter");
|
|
36946
37495
|
for (const k of ["version", "commit", "manifest_sha256", "entry_sha256", "token", "installed_at", "updated_at"]) if (!nonEmpty(v[k])) bad(k, "a non-empty string");
|
|
36947
37496
|
if (typeof v.description !== "string") bad("description", "a string");
|
|
36948
37497
|
if (typeof v.api !== "number" || !Number.isInteger(v.api) || v.api < 1) bad("api", "an integer of 1 or more");
|
|
36949
|
-
if (!
|
|
37498
|
+
if (!isObject2(v.source)) bad("source", "an object");
|
|
36950
37499
|
else if (v.source.kind !== "git" && v.source.kind !== "local") bad("source.kind", '"git" or "local"');
|
|
36951
|
-
if (!
|
|
37500
|
+
if (!isObject2(v.tables) || Object.values(v.tables).some((t) => !isObject2(t) || typeof t.table !== "string" || typeof t.owned !== "boolean")) bad("tables", "an object of roles");
|
|
36952
37501
|
if (!isStrings(v.tools)) bad("tools", "a list of strings");
|
|
36953
37502
|
if (!isStrings(v.write)) bad("write", "a list of strings");
|
|
36954
37503
|
if (v.migration_id !== null && typeof v.migration_id !== "string") bad("migration_id", "a string or null");
|
|
36955
37504
|
if (v.previous !== null) out2.push(...recordProblems(v.previous, `${prefix}previous.`));
|
|
36956
37505
|
return out2;
|
|
36957
37506
|
}
|
|
36958
|
-
var RECORD_NAME, RECORD_COMMIT, DATABASE_ID, MAX_RECORD_CHARS,
|
|
37507
|
+
var RECORD_NAME, RECORD_COMMIT, DATABASE_ID, MAX_RECORD_CHARS, isObject2, isStrings, nonEmpty;
|
|
36959
37508
|
var init_extension_record = __esm({
|
|
36960
37509
|
"src/api/extension-record.ts"() {
|
|
36961
37510
|
"use strict";
|
|
@@ -36963,7 +37512,7 @@ var init_extension_record = __esm({
|
|
|
36963
37512
|
RECORD_COMMIT = /^(?:[0-9a-f]{40}|local-[0-9a-z.-]+)$/;
|
|
36964
37513
|
DATABASE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
36965
37514
|
MAX_RECORD_CHARS = 1e6;
|
|
36966
|
-
|
|
37515
|
+
isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
36967
37516
|
isStrings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
36968
37517
|
nonEmpty = (v) => typeof v === "string" && v !== "";
|
|
36969
37518
|
}
|
|
@@ -37566,7 +38115,7 @@ function satisfiesEngine(range, engineVersion) {
|
|
|
37566
38115
|
}
|
|
37567
38116
|
return true;
|
|
37568
38117
|
}
|
|
37569
|
-
function
|
|
38118
|
+
function isObject3(v) {
|
|
37570
38119
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
37571
38120
|
}
|
|
37572
38121
|
function bindRoles(value, tables) {
|
|
@@ -37580,7 +38129,7 @@ function bindRoles(value, tables) {
|
|
|
37580
38129
|
return exact ? table(exact[1]) : v.replace(new RegExp(ROLE_TEXT.source, "g"), (_m, role) => table(role));
|
|
37581
38130
|
}
|
|
37582
38131
|
if (Array.isArray(v)) return v.map(walk);
|
|
37583
|
-
if (
|
|
38132
|
+
if (isObject3(v)) {
|
|
37584
38133
|
const out2 = {};
|
|
37585
38134
|
for (const k of Object.keys(v)) out2[k] = walk(v[k]);
|
|
37586
38135
|
return out2;
|
|
@@ -37601,7 +38150,7 @@ function rolesNamed(value) {
|
|
|
37601
38150
|
const re = new RegExp(ROLE_TEXT.source, "g");
|
|
37602
38151
|
for (let m = re.exec(v); m; m = re.exec(v)) add(m[1]);
|
|
37603
38152
|
} else if (Array.isArray(v)) v.forEach(walk);
|
|
37604
|
-
else if (
|
|
38153
|
+
else if (isObject3(v)) Object.keys(v).forEach((k) => walk(v[k]));
|
|
37605
38154
|
};
|
|
37606
38155
|
walk(value);
|
|
37607
38156
|
return out2;
|
|
@@ -37616,7 +38165,7 @@ function parseManifest(text5, file = MANIFEST_FILE) {
|
|
|
37616
38165
|
return validateManifest(raw, file);
|
|
37617
38166
|
}
|
|
37618
38167
|
function validateManifest(raw, file = MANIFEST_FILE) {
|
|
37619
|
-
if (!
|
|
38168
|
+
if (!isObject3(raw)) throw new ExtensionManifestError(["the manifest must be a JSON object"], file);
|
|
37620
38169
|
const problems = [];
|
|
37621
38170
|
const m = JSON.parse(JSON.stringify(raw));
|
|
37622
38171
|
const add = (path23, text5) => {
|
|
@@ -37634,10 +38183,10 @@ function validateManifest(raw, file = MANIFEST_FILE) {
|
|
|
37634
38183
|
const needDescription = (o, path23) => {
|
|
37635
38184
|
if (!nonEmpty2(o.description)) add(`${path23}.description`, "is required");
|
|
37636
38185
|
};
|
|
37637
|
-
const tablesRaw =
|
|
38186
|
+
const tablesRaw = isObject3(m.tables) ? m.tables : {};
|
|
37638
38187
|
const roles = Object.keys(tablesRaw);
|
|
37639
|
-
const basesRaw =
|
|
37640
|
-
const permWrite =
|
|
38188
|
+
const basesRaw = isObject3(m.bases) ? m.bases : {};
|
|
38189
|
+
const permWrite = isObject3(m.permissions) && Array.isArray(m.permissions.write) ? m.permissions.write : [];
|
|
37641
38190
|
const roleList = (v, path23, allowed, notText) => {
|
|
37642
38191
|
if (!Array.isArray(v)) return add(path23, "must be a list of roles");
|
|
37643
38192
|
v.forEach((r, i) => {
|
|
@@ -37645,7 +38194,7 @@ function validateManifest(raw, file = MANIFEST_FILE) {
|
|
|
37645
38194
|
});
|
|
37646
38195
|
};
|
|
37647
38196
|
const column = (c, path23, refs, inBase) => {
|
|
37648
|
-
if (!
|
|
38197
|
+
if (!isObject3(c)) return add(path23, "must be an object");
|
|
37649
38198
|
keys(c, COLUMN_KEYS, path23);
|
|
37650
38199
|
if (!ident2(c.name)) add(`${path23}.name`, `must be ${IDENT_TEXT}`);
|
|
37651
38200
|
else if (c.name === "id") add(path23, `"id" is the engine's own column: leave it out`);
|
|
@@ -37665,7 +38214,7 @@ function validateManifest(raw, file = MANIFEST_FILE) {
|
|
|
37665
38214
|
const seen = /* @__PURE__ */ new Set();
|
|
37666
38215
|
v.forEach((c, i) => {
|
|
37667
38216
|
column(c, `${path23}[${i}]`, refs, inBase);
|
|
37668
|
-
const name =
|
|
38217
|
+
const name = isObject3(c) ? c.name : void 0;
|
|
37669
38218
|
if (typeof name !== "string") return;
|
|
37670
38219
|
if (seen.has(name)) add(path23, `column "${name}" twice`);
|
|
37671
38220
|
seen.add(name);
|
|
@@ -37690,7 +38239,7 @@ function validateManifest(raw, file = MANIFEST_FILE) {
|
|
|
37690
38239
|
const path23 = `tables.${role}`;
|
|
37691
38240
|
const t = tablesRaw[role];
|
|
37692
38241
|
if (!IDENT3.test(role)) add(path23, `a role is ${IDENT_TEXT}`);
|
|
37693
|
-
if (!
|
|
38242
|
+
if (!isObject3(t)) return add(path23, "must be an object");
|
|
37694
38243
|
keys(t, TABLE_KEYS, path23);
|
|
37695
38244
|
if (typeof t.owned !== "boolean") add(`${path23}.owned`, "must be true or false");
|
|
37696
38245
|
for (const k of ["name", "schema"]) if (t[k] !== void 0 && !ident2(t[k])) add(`${path23}.${k}`, `must be ${IDENT_TEXT}`);
|
|
@@ -37710,14 +38259,14 @@ function validateManifest(raw, file = MANIFEST_FILE) {
|
|
|
37710
38259
|
const seen = /* @__PURE__ */ new Set();
|
|
37711
38260
|
t.constraints.forEach((c, i) => {
|
|
37712
38261
|
const cp = `${path23}.constraints[${i}]`;
|
|
37713
|
-
if (!
|
|
38262
|
+
if (!isObject3(c)) return add(cp, "must be an object");
|
|
37714
38263
|
keys(c, CONSTRAINT_KEYS2, cp);
|
|
37715
38264
|
if (!ident2(c.name)) add(`${cp}.name`, `must be ${IDENT_TEXT}`);
|
|
37716
38265
|
else {
|
|
37717
38266
|
if (seen.has(c.name)) add(`${path23}.constraints`, `constraint "${c.name}" twice`);
|
|
37718
38267
|
seen.add(c.name);
|
|
37719
38268
|
}
|
|
37720
|
-
if (!
|
|
38269
|
+
if (!isObject3(c.check)) add(`${cp}.check`, "must be an object");
|
|
37721
38270
|
if (typeof c.hint !== "string") add(`${cp}.hint`, "must be a string");
|
|
37722
38271
|
});
|
|
37723
38272
|
}
|
|
@@ -37732,12 +38281,12 @@ function validateManifest(raw, file = MANIFEST_FILE) {
|
|
|
37732
38281
|
}
|
|
37733
38282
|
}
|
|
37734
38283
|
});
|
|
37735
|
-
if (m.bases !== void 0 && !
|
|
38284
|
+
if (m.bases !== void 0 && !isObject3(m.bases)) add("bases", "must be an object");
|
|
37736
38285
|
for (const b of Object.keys(basesRaw)) {
|
|
37737
38286
|
const path23 = `bases.${b}`;
|
|
37738
38287
|
const base = basesRaw[b];
|
|
37739
38288
|
if (!IDENT3.test(b)) add(path23, `a base name is ${IDENT_TEXT}`);
|
|
37740
|
-
if (!
|
|
38289
|
+
if (!isObject3(base)) {
|
|
37741
38290
|
add(path23, "must be an object");
|
|
37742
38291
|
continue;
|
|
37743
38292
|
}
|
|
@@ -37745,21 +38294,21 @@ function validateManifest(raw, file = MANIFEST_FILE) {
|
|
|
37745
38294
|
needDescription(base, path23);
|
|
37746
38295
|
columns(base.columns, `${path23}.columns`, [], true);
|
|
37747
38296
|
}
|
|
37748
|
-
if (m.views !== void 0 && !
|
|
37749
|
-
const viewsRaw =
|
|
38297
|
+
if (m.views !== void 0 && !isObject3(m.views)) add("views", "must be an object");
|
|
38298
|
+
const viewsRaw = isObject3(m.views) ? m.views : {};
|
|
37750
38299
|
for (const v of Object.keys(viewsRaw)) {
|
|
37751
38300
|
const path23 = `views.${v}`;
|
|
37752
38301
|
const view = viewsRaw[v];
|
|
37753
38302
|
if (!/^vw_[a-z0-9_]+$/.test(v)) add(path23, `a view name starts with vw_ and is ${IDENT_TEXT}`);
|
|
37754
|
-
if (!
|
|
38303
|
+
if (!isObject3(view)) {
|
|
37755
38304
|
add(path23, "must be an object");
|
|
37756
38305
|
continue;
|
|
37757
38306
|
}
|
|
37758
38307
|
keys(view, VIEW_KEYS, path23);
|
|
37759
38308
|
needDescription(view, path23);
|
|
37760
|
-
if (!
|
|
38309
|
+
if (!isObject3(view.query)) add(`${path23}.query`, "must be an object");
|
|
37761
38310
|
}
|
|
37762
|
-
if (!
|
|
38311
|
+
if (!isObject3(m.permissions)) add("permissions", `must be an object with a "write" list of roles`);
|
|
37763
38312
|
else {
|
|
37764
38313
|
keys(m.permissions, ["write"], "permissions");
|
|
37765
38314
|
roleList(m.permissions.write, "permissions.write", roles, (r) => `"${r}" is not a role`);
|
|
@@ -37769,7 +38318,7 @@ function validateManifest(raw, file = MANIFEST_FILE) {
|
|
|
37769
38318
|
const seen = /* @__PURE__ */ new Set();
|
|
37770
38319
|
m.tools.forEach((t, i) => {
|
|
37771
38320
|
const path23 = `tools[${i}]`;
|
|
37772
|
-
if (!
|
|
38321
|
+
if (!isObject3(t)) return add(path23, "must be an object");
|
|
37773
38322
|
keys(t, TOOL_KEYS, path23);
|
|
37774
38323
|
if (typeof t.name !== "string" || !TOOL_NAME.test(t.name)) add(`${path23}.name`, "must be snake_case, 1 to 64 characters");
|
|
37775
38324
|
else {
|
|
@@ -37777,10 +38326,10 @@ function validateManifest(raw, file = MANIFEST_FILE) {
|
|
|
37777
38326
|
seen.add(t.name);
|
|
37778
38327
|
}
|
|
37779
38328
|
if (!nonEmpty2(t.description)) add(`${path23}.description`, "is required");
|
|
37780
|
-
if (!
|
|
38329
|
+
if (!isObject3(t.inputSchema) || t.inputSchema.type !== "object" || t.inputSchema.additionalProperties !== false) {
|
|
37781
38330
|
add(`${path23}.inputSchema`, `must be an object schema with "type": "object" and "additionalProperties": false`);
|
|
37782
38331
|
}
|
|
37783
|
-
const readOnly =
|
|
38332
|
+
const readOnly = isObject3(t.annotations) ? t.annotations.readOnlyHint : void 0;
|
|
37784
38333
|
if (readOnly !== void 0 && typeof readOnly !== "boolean") add(`${path23}.annotations.readOnlyHint`, "must be true or false");
|
|
37785
38334
|
if (t.tables !== void 0) roleList(t.tables, `${path23}.tables`, roles, (r) => `"${r}" is not a role`);
|
|
37786
38335
|
if (t.writes !== void 0) roleList(t.writes, `${path23}.writes`, permWrite, (r) => `"${r}" is not in permissions.write`);
|
|
@@ -37793,7 +38342,7 @@ function validateManifest(raw, file = MANIFEST_FILE) {
|
|
|
37793
38342
|
let previous = null;
|
|
37794
38343
|
(Array.isArray(m.migrations) ? m.migrations : []).forEach((g, i) => {
|
|
37795
38344
|
const path23 = `migrations[${i}]`;
|
|
37796
|
-
if (!
|
|
38345
|
+
if (!isObject3(g)) return add(path23, "must be an object");
|
|
37797
38346
|
keys(g, MIGRATION_KEYS, path23);
|
|
37798
38347
|
const at = g.version;
|
|
37799
38348
|
if (typeof at !== "string" || !parseVersion(at)) add(`${path23}.version`, `"${String(at)}" is not a version`);
|
|
@@ -37806,7 +38355,7 @@ function validateManifest(raw, file = MANIFEST_FILE) {
|
|
|
37806
38355
|
if (!Array.isArray(g.operations)) add(`${path23}.operations`, "must be a list");
|
|
37807
38356
|
else {
|
|
37808
38357
|
g.operations.forEach((o, j) => {
|
|
37809
|
-
const op2 =
|
|
38358
|
+
const op2 = isObject3(o) ? o.op : void 0;
|
|
37810
38359
|
if (typeof op2 !== "string" || !BATCH_OPERATIONS2.includes(op2)) add(`${path23}.operations[${j}].op`, `"${String(op2)}" is not an operation a batch takes`);
|
|
37811
38360
|
});
|
|
37812
38361
|
}
|
|
@@ -37822,7 +38371,7 @@ function validateManifest(raw, file = MANIFEST_FILE) {
|
|
|
37822
38371
|
const re = new RegExp(ROLE_TEXT.source, "g");
|
|
37823
38372
|
for (let r = re.exec(v); r; r = re.exec(v)) if (!known.has(r[1])) add(path23, `"@{${r[1]}}" names no role`);
|
|
37824
38373
|
} else if (Array.isArray(v)) v.forEach((x, i) => names2(x, `${path23}[${i}]`));
|
|
37825
|
-
else if (
|
|
38374
|
+
else if (isObject3(v)) for (const k of Object.keys(v)) names2(v[k], `${path23}.${k}`);
|
|
37826
38375
|
};
|
|
37827
38376
|
for (const part of ["tables", "bases", "views", "migrations"]) if (m[part] !== void 0) names2(m[part], part);
|
|
37828
38377
|
if (problems.length > 0) throw new ExtensionManifestError(problems, file);
|
|
@@ -38079,7 +38628,7 @@ async function loadExtensions(o) {
|
|
|
38079
38628
|
let manifest;
|
|
38080
38629
|
try {
|
|
38081
38630
|
manifest = parseManifest(import_node_fs17.default.readFileSync(manifestFile, "utf8"));
|
|
38082
|
-
const odd = manifest.tools.findIndex((t) => !
|
|
38631
|
+
const odd = manifest.tools.findIndex((t) => !isObject4(t.annotations));
|
|
38083
38632
|
if (odd >= 0) throw new Error(`tools[${odd}].annotations: must be an object`);
|
|
38084
38633
|
} catch (e) {
|
|
38085
38634
|
skip("entry", `the code of ${r.name} did not load: ${e.message}`);
|
|
@@ -38240,7 +38789,7 @@ Docs: read_docs(${JSON.stringify(`extensions/${name}`)})`);
|
|
|
38240
38789
|
}
|
|
38241
38790
|
return { name, version: r.version, servedBy, tools, instructions, has: (tool) => defs.has(tool), call };
|
|
38242
38791
|
}
|
|
38243
|
-
var import_node_fs17, import_node_path19, import_node_url2, noLog, ClientTokens,
|
|
38792
|
+
var import_node_fs17, import_node_path19, import_node_url2, noLog, ClientTokens, isObject4, matches2, cut;
|
|
38244
38793
|
var init_extension_host = __esm({
|
|
38245
38794
|
"src/api/extension-host.ts"() {
|
|
38246
38795
|
"use strict";
|
|
@@ -38285,7 +38834,7 @@ var init_extension_host = __esm({
|
|
|
38285
38834
|
return value;
|
|
38286
38835
|
}
|
|
38287
38836
|
};
|
|
38288
|
-
|
|
38837
|
+
isObject4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
38289
38838
|
matches2 = (v, rule) => typeof v === "string" && rule.test(v);
|
|
38290
38839
|
cut = (s, n) => s.length > n ? s.slice(0, n) : s;
|
|
38291
38840
|
}
|
|
@@ -38295,7 +38844,7 @@ var init_extension_host = __esm({
|
|
|
38295
38844
|
function stable(v) {
|
|
38296
38845
|
const walk = (x) => {
|
|
38297
38846
|
if (Array.isArray(x)) return x.map(walk);
|
|
38298
|
-
if (
|
|
38847
|
+
if (isObject5(x)) {
|
|
38299
38848
|
const out2 = {};
|
|
38300
38849
|
for (const k of Object.keys(x).sort()) out2[k] = walk(x[k]);
|
|
38301
38850
|
return out2;
|
|
@@ -38915,7 +39464,7 @@ function renderPlan(plan) {
|
|
|
38915
39464
|
lines.push("", plan.agent_message, plan.summary);
|
|
38916
39465
|
return lines.join("\n");
|
|
38917
39466
|
}
|
|
38918
|
-
var AGENT_MESSAGE, MAX_OPERATIONS,
|
|
39467
|
+
var AGENT_MESSAGE, MAX_OPERATIONS, isObject5, obj3, list2, tableName, isLink;
|
|
38919
39468
|
var init_extension_plan = __esm({
|
|
38920
39469
|
"src/api/extension-plan.ts"() {
|
|
38921
39470
|
"use strict";
|
|
@@ -38923,8 +39472,8 @@ var init_extension_plan = __esm({
|
|
|
38923
39472
|
init_guidance();
|
|
38924
39473
|
AGENT_MESSAGE = `For the agent installing this extension: show this plan to the user before anything is written. Where a table says "proposed", the installer chose it: ask the user to confirm it, to pick another table (--map <role>=<catalog.schema.table>, --map <role>.<column>=<column>), or to create it (--create <role>). Add a missing column only after the user agrees (--add <role>.<column>). Solve each problem the same way. Then run the same command again with the user's choices and --yes, only once the user approved the tables it creates or changes, the tools and the token's permissions.`;
|
|
38925
39474
|
MAX_OPERATIONS = 200;
|
|
38926
|
-
|
|
38927
|
-
obj3 = (v) =>
|
|
39475
|
+
isObject5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
39476
|
+
obj3 = (v) => isObject5(v) ? v : {};
|
|
38928
39477
|
list2 = (v) => Array.isArray(v) ? v : [];
|
|
38929
39478
|
tableName = (t, role) => t.name ?? role;
|
|
38930
39479
|
isLink = (c) => c.type === "link" || c.type === "list" && c.items === "link";
|
|
@@ -40051,7 +40600,7 @@ __export(util_exports, {
|
|
|
40051
40600
|
hexToUint8Array: () => hexToUint8Array,
|
|
40052
40601
|
hide: () => hide,
|
|
40053
40602
|
installLazyProp: () => installLazyProp,
|
|
40054
|
-
isObject: () =>
|
|
40603
|
+
isObject: () => isObject6,
|
|
40055
40604
|
isPlainObject: () => isPlainObject2,
|
|
40056
40605
|
issue: () => issue,
|
|
40057
40606
|
joinValues: () => joinValues,
|
|
@@ -40258,11 +40807,11 @@ function esc(str3) {
|
|
|
40258
40807
|
function slugify(input) {
|
|
40259
40808
|
return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
40260
40809
|
}
|
|
40261
|
-
function
|
|
40810
|
+
function isObject6(data) {
|
|
40262
40811
|
return typeof data === "object" && data !== null && !Array.isArray(data);
|
|
40263
40812
|
}
|
|
40264
40813
|
function isPlainObject2(o) {
|
|
40265
|
-
if (
|
|
40814
|
+
if (isObject6(o) === false)
|
|
40266
40815
|
return false;
|
|
40267
40816
|
const ctor = o.constructor;
|
|
40268
40817
|
if (ctor === void 0)
|
|
@@ -40270,7 +40819,7 @@ function isPlainObject2(o) {
|
|
|
40270
40819
|
if (typeof ctor !== "function")
|
|
40271
40820
|
return true;
|
|
40272
40821
|
const prot = ctor.prototype;
|
|
40273
|
-
if (
|
|
40822
|
+
if (isObject6(prot) === false)
|
|
40274
40823
|
return false;
|
|
40275
40824
|
if (Object.prototype.hasOwnProperty.call(prot, "isPrototypeOf") === false) {
|
|
40276
40825
|
return false;
|
|
@@ -42656,20 +43205,20 @@ var init_schemas = __esm({
|
|
|
42656
43205
|
const shape = zod.def.shape;
|
|
42657
43206
|
const propValues = {};
|
|
42658
43207
|
for (const key2 in shape) {
|
|
42659
|
-
const
|
|
42660
|
-
if (
|
|
43208
|
+
const field3 = shape[key2]._zod;
|
|
43209
|
+
if (field3.values) {
|
|
42661
43210
|
if (!Object.prototype.hasOwnProperty.call(propValues, key2)) {
|
|
42662
43211
|
assignProp(propValues, key2, /* @__PURE__ */ new Set());
|
|
42663
43212
|
}
|
|
42664
|
-
for (const v of
|
|
43213
|
+
for (const v of field3.values)
|
|
42665
43214
|
propValues[key2].add(v);
|
|
42666
|
-
if (
|
|
43215
|
+
if (field3.optin !== void 0)
|
|
42667
43216
|
propValues[key2].add(void 0);
|
|
42668
43217
|
}
|
|
42669
43218
|
}
|
|
42670
43219
|
return propValues;
|
|
42671
43220
|
});
|
|
42672
|
-
const
|
|
43221
|
+
const isObject7 = isObject6;
|
|
42673
43222
|
const catchall = def.catchall;
|
|
42674
43223
|
let value;
|
|
42675
43224
|
const memo2 = globalConfig.memoizer;
|
|
@@ -42677,7 +43226,7 @@ var init_schemas = __esm({
|
|
|
42677
43226
|
inst._zod.parse = (payload, ctx) => {
|
|
42678
43227
|
value ?? (value = _normalized.value);
|
|
42679
43228
|
const input = payload.value;
|
|
42680
|
-
if (!
|
|
43229
|
+
if (!isObject7(input)) {
|
|
42681
43230
|
payload.issues.push({
|
|
42682
43231
|
expected: "object",
|
|
42683
43232
|
code: "invalid_type",
|
|
@@ -42813,7 +43362,7 @@ var init_schemas = __esm({
|
|
|
42813
43362
|
return doc.compile();
|
|
42814
43363
|
};
|
|
42815
43364
|
let fastpass;
|
|
42816
|
-
const
|
|
43365
|
+
const isObject7 = isObject6;
|
|
42817
43366
|
const jit = !globalConfig.jitless;
|
|
42818
43367
|
const allowsEval2 = allowsEval;
|
|
42819
43368
|
const fastEnabled = jit && allowsEval2.value;
|
|
@@ -42822,7 +43371,7 @@ var init_schemas = __esm({
|
|
|
42822
43371
|
inst._zod.parse = (payload, ctx) => {
|
|
42823
43372
|
value ?? (value = _normalized.value);
|
|
42824
43373
|
const input = payload.value;
|
|
42825
|
-
if (!
|
|
43374
|
+
if (!isObject7(input)) {
|
|
42826
43375
|
payload.issues.push({
|
|
42827
43376
|
expected: "object",
|
|
42828
43377
|
code: "invalid_type",
|
|
@@ -42922,7 +43471,7 @@ var init_schemas = __esm({
|
|
|
42922
43471
|
const disc = cached(() => discriminatorMap(def));
|
|
42923
43472
|
inst._zod.parse = (payload, ctx) => {
|
|
42924
43473
|
const input = payload.value;
|
|
42925
|
-
if (!
|
|
43474
|
+
if (!isObject6(input)) {
|
|
42926
43475
|
payload.issues.push({
|
|
42927
43476
|
code: "invalid_type",
|
|
42928
43477
|
expected: "object",
|
|
@@ -45285,8 +45834,8 @@ var init_json_schema_processors = __esm({
|
|
|
45285
45834
|
}
|
|
45286
45835
|
const requiredKeys = [];
|
|
45287
45836
|
for (const key2 of Object.keys(shape)) {
|
|
45288
|
-
const
|
|
45289
|
-
if (ctx.io === "input" ? inputOptin(
|
|
45837
|
+
const field3 = def.shape[key2];
|
|
45838
|
+
if (ctx.io === "input" ? inputOptin(field3) === void 0 : field3._zod.optout === void 0) {
|
|
45290
45839
|
requiredKeys.push(key2);
|
|
45291
45840
|
}
|
|
45292
45841
|
}
|
|
@@ -61187,6 +61736,7 @@ init_metastore();
|
|
|
61187
61736
|
init_table_registry();
|
|
61188
61737
|
init_hints();
|
|
61189
61738
|
init_tracing();
|
|
61739
|
+
init_read_scope();
|
|
61190
61740
|
var HISTORY_OFF = (full) => guide(`Turn history on for it with alter_table("${full}", history: true), or for every table with configure_database(history: true); versions are kept from then on.`, "history");
|
|
61191
61741
|
var ASOF_HINT = guide(`Pass "version" (an integer from describe_history) or "timestamp" (an ISO date-time), not both.`, "readAsOf");
|
|
61192
61742
|
async function historyTableName(app, settings, table) {
|
|
@@ -61268,6 +61818,7 @@ async function newestHistory(app, settings, full, limit) {
|
|
|
61268
61818
|
}
|
|
61269
61819
|
async function describeHistory(app, settings, input) {
|
|
61270
61820
|
const ref2 = await historyTableName(app, settings, input.table);
|
|
61821
|
+
assertTableReadable(ref2.full);
|
|
61271
61822
|
const limit = input.limit ?? 50;
|
|
61272
61823
|
if (input.since === void 0) return { table: ref2.full, history: await newestHistory(app, settings, ref2.full, limit) };
|
|
61273
61824
|
const from = sinceMs(input.since);
|
|
@@ -61383,6 +61934,7 @@ function rowValues(content, settings) {
|
|
|
61383
61934
|
}
|
|
61384
61935
|
async function tableChanges(app, settings, input) {
|
|
61385
61936
|
const ref2 = await historyTableName(app, settings, input.table);
|
|
61937
|
+
assertTableReadable(ref2.full);
|
|
61386
61938
|
const versions = await listVersions(app, settings, ref2.full);
|
|
61387
61939
|
if (versions.length === 0) throw new OperationInputError(`${ref2.full} has no history`, HISTORY_OFF(ref2.full));
|
|
61388
61940
|
const latest = versions[versions.length - 1];
|
|
@@ -61449,6 +62001,8 @@ init_check_links();
|
|
|
61449
62001
|
init_changeset_checks();
|
|
61450
62002
|
init_computed_columns();
|
|
61451
62003
|
init_path_templates();
|
|
62004
|
+
init_read_scope();
|
|
62005
|
+
init_query_tables();
|
|
61452
62006
|
init_where();
|
|
61453
62007
|
init_link_paths();
|
|
61454
62008
|
init_validate();
|
|
@@ -61507,10 +62061,10 @@ async function addConstraintImpl(app, settings, input, opts = {}) {
|
|
|
61507
62061
|
if (cat.entry.constraints?.[input.name]) throw new OperationInputError(`constraint "${input.name}" already exists; drop it first`, guide(`Choose another name, or drop_constraint "${input.name}" first to replace it; describe_table lists the constraints.`, "check"));
|
|
61508
62062
|
const links = new CheckLinks(app, settings);
|
|
61509
62063
|
await links.prepare(t, input.check);
|
|
61510
|
-
const
|
|
62064
|
+
const passes2 = compileCheck(input.check, t, settings, links);
|
|
61511
62065
|
const conflicts = [];
|
|
61512
62066
|
for (const r of await loadRows(app, settings, t)) {
|
|
61513
|
-
if (!
|
|
62067
|
+
if (!passes2(r)) conflicts.push({ path: r.file.path, message: `violates CHECK ${input.name}` });
|
|
61514
62068
|
}
|
|
61515
62069
|
const hints = { ...constraintHints(cat.entry), ...hint ? { [input.name]: hint } : {} };
|
|
61516
62070
|
const { constraint_hints: _h, ...withoutHints } = cat.entry;
|
|
@@ -61620,6 +62174,7 @@ async function resolveViewSelect(app, settings, input) {
|
|
|
61620
62174
|
if (tableRef) return null;
|
|
61621
62175
|
const ref2 = resolveCatalogRef(ms.model, input.table, "view");
|
|
61622
62176
|
if (!ref2) return null;
|
|
62177
|
+
assertTableReadable(ref2.full);
|
|
61623
62178
|
const view = ms.model.catalogs[ref2.catalog].schemas[ref2.schema].views[ref2.table];
|
|
61624
62179
|
const base = viewQueryToSelect(input.host ? resolveThisTokens(view.query, input.host) : view.query);
|
|
61625
62180
|
let where = { ...base.where ?? {} };
|
|
@@ -61719,13 +62274,17 @@ async function informationRows(app, settings, name) {
|
|
|
61719
62274
|
const ms = await loadMetastore(app, settings);
|
|
61720
62275
|
const model = ms?.model ?? parseMetastore("");
|
|
61721
62276
|
const cfg = await readDatabaseConfig(app, settings);
|
|
61722
|
-
const
|
|
62277
|
+
const scope = currentReadScope();
|
|
62278
|
+
const tables = catalogTables(model).filter(({ ref: ref2 }) => tableReadable(ref2.full, scope));
|
|
62279
|
+
const views = catalogViews(model).filter(({ ref: ref2 }) => viewReadable2(model, ref2.full, scope));
|
|
62280
|
+
const visible = new Set([...tables, ...views].map(({ ref: ref2 }) => `${ref2.catalog}.${ref2.schema}`));
|
|
62281
|
+
const shown = (catalog, schema) => scope === null || (schema !== void 0 ? visible.has(`${catalog}.${schema}`) : [...visible].some((k) => k.startsWith(`${catalog}.`)));
|
|
61723
62282
|
switch (name) {
|
|
61724
62283
|
case "catalogs":
|
|
61725
|
-
return Object.entries(model.catalogs).map(([c, cat]) => ({ catalog_name: c, comment: cat.description ?? null, is_default: c === cfg.default_catalog }));
|
|
62284
|
+
return Object.entries(model.catalogs).filter(([c]) => shown(c)).map(([c, cat]) => ({ catalog_name: c, comment: cat.description ?? null, is_default: c === cfg.default_catalog }));
|
|
61726
62285
|
case "schemata":
|
|
61727
62286
|
return Object.entries(model.catalogs).flatMap(
|
|
61728
|
-
([c, cat]) => Object.entries(cat.schemas).map(([s, sch]) => ({ catalog_name: c, schema_name: s, comment: sch.description ?? null }))
|
|
62287
|
+
([c, cat]) => Object.entries(cat.schemas).filter(([s]) => shown(c, s)).map(([s, sch]) => ({ catalog_name: c, schema_name: s, comment: sch.description ?? null }))
|
|
61729
62288
|
);
|
|
61730
62289
|
case "tables": {
|
|
61731
62290
|
const out2 = [];
|
|
@@ -61746,7 +62305,7 @@ async function informationRows(app, settings, name) {
|
|
|
61746
62305
|
instructions: tableInstructions(entry) ?? null
|
|
61747
62306
|
});
|
|
61748
62307
|
}
|
|
61749
|
-
for (const { ref: ref2, view } of
|
|
62308
|
+
for (const { ref: ref2, view } of views) {
|
|
61750
62309
|
out2.push({
|
|
61751
62310
|
table_catalog: ref2.catalog,
|
|
61752
62311
|
table_schema: ref2.schema,
|
|
@@ -61784,7 +62343,7 @@ async function informationRows(app, settings, name) {
|
|
|
61784
62343
|
}));
|
|
61785
62344
|
});
|
|
61786
62345
|
case "views":
|
|
61787
|
-
return
|
|
62346
|
+
return views.map(({ ref: ref2, view }) => ({
|
|
61788
62347
|
table_catalog: ref2.catalog,
|
|
61789
62348
|
table_schema: ref2.schema,
|
|
61790
62349
|
table_name: ref2.table,
|
|
@@ -61807,6 +62366,12 @@ async function informationRows(app, settings, name) {
|
|
|
61807
62366
|
});
|
|
61808
62367
|
}
|
|
61809
62368
|
}
|
|
62369
|
+
function viewReadable2(model, full, scope) {
|
|
62370
|
+
if (scope === null) return true;
|
|
62371
|
+
if (!tableReadable(full, scope)) return false;
|
|
62372
|
+
const r = tablesReadByView(model, full);
|
|
62373
|
+
return r.complete && r.tables.every((t) => tableReadable(t, scope));
|
|
62374
|
+
}
|
|
61810
62375
|
async function informationSchemaTableData(app, settings, name) {
|
|
61811
62376
|
const ref2 = refOf(SYSTEM_CATALOG, "information_schema", name);
|
|
61812
62377
|
const entry = { columns: INFO_COLUMNS[name].map(([n, type]) => ({ name: n, type })) };
|
|
@@ -62069,6 +62634,8 @@ var setTableBases = instrumented("set_table_bases", setTableBasesImpl, (i) => ({
|
|
|
62069
62634
|
// src/core/select.ts
|
|
62070
62635
|
init_this_tokens();
|
|
62071
62636
|
init_metastore();
|
|
62637
|
+
init_query_tables();
|
|
62638
|
+
init_read_scope();
|
|
62072
62639
|
init_engine();
|
|
62073
62640
|
init_file_cache();
|
|
62074
62641
|
init_frontmatter();
|
|
@@ -62167,9 +62734,11 @@ async function loadJoin(app, settings, t, left, join, env, asOf2) {
|
|
|
62167
62734
|
if (pairs.length !== 1) throw new OperationInputError(`join.on must contain exactly one "left": "right" pair`, JOIN_HINT);
|
|
62168
62735
|
const [leftText, rightText] = pairs[0];
|
|
62169
62736
|
if (typeof rightText !== "string") throw new OperationInputError(`join.on values must be strings`, JOIN_HINT);
|
|
62737
|
+
if (asOf2.timestamp !== void 0) await assertRefReadable(app, settings, join.table);
|
|
62170
62738
|
const hist = asOf2.timestamp !== void 0 ? await loadTableAsOf(app, settings, join.table, { timestamp: asOf2.timestamp }) : null;
|
|
62171
62739
|
if (hist) asOf2.read.set(hist.table.catalog.ref.full, hist.version);
|
|
62172
62740
|
const rt = hist?.table ?? await loadTable(app, settings, join.table);
|
|
62741
|
+
assertTableReadable(rt.catalog.ref.full);
|
|
62173
62742
|
const rKnown = readableKeys(rt, settings);
|
|
62174
62743
|
const lGet = rowGetter(app, t, settings, env.tz);
|
|
62175
62744
|
const rGet = rowGetter(app, rt, settings, env.tz);
|
|
@@ -62197,6 +62766,12 @@ async function loadJoin(app, settings, t, left, join, env, asOf2) {
|
|
|
62197
62766
|
};
|
|
62198
62767
|
}
|
|
62199
62768
|
var MAX_VIEW_DEPTH = 8;
|
|
62769
|
+
async function assertRefReadable(app, settings, ref2) {
|
|
62770
|
+
if (!currentReadScope()) return;
|
|
62771
|
+
const model = (await loadMetastore(app, settings))?.model;
|
|
62772
|
+
const full = model ? refOf2(model, ref2, "table") : null;
|
|
62773
|
+
if (full) assertTableReadable(full);
|
|
62774
|
+
}
|
|
62200
62775
|
var UNION_BRANCH_MAX = 1e3;
|
|
62201
62776
|
function perTableVersion(what) {
|
|
62202
62777
|
return new OperationInputError(
|
|
@@ -62214,7 +62789,7 @@ async function selectBase(app, settings, input, depth, read3) {
|
|
|
62214
62789
|
const tables = catalogTables(model).filter(({ entry }) => {
|
|
62215
62790
|
const ext = entry.bases ?? [];
|
|
62216
62791
|
return ext.length > 0 && baseChain(model.bases, ext).includes(name);
|
|
62217
|
-
}).map(({ ref: ref2 }) => ref2.full);
|
|
62792
|
+
}).map(({ ref: ref2 }) => ref2.full).filter((full) => tableReadable(full));
|
|
62218
62793
|
const own2 = /* @__PURE__ */ new Set([SYSTEM_ID_COLUMN, ...baseChain(model.bases, [name]).flatMap((b) => model.bases[b].columns.map((c) => String(c.name)))]);
|
|
62219
62794
|
const columns = input.columns ?? [...own2];
|
|
62220
62795
|
for (const c of [...columns, ...Object.keys(input.where ?? {}).filter((k) => !["and", "or", "not"].includes(k))]) {
|
|
@@ -62299,10 +62874,12 @@ async function selectImpl(app, settings, input, depth = 0, read3 = /* @__PURE__
|
|
|
62299
62874
|
}
|
|
62300
62875
|
if (input.version === void 0 && input.timestamp === void 0) return selectOn(app, settings, input, null, read3);
|
|
62301
62876
|
if (input.join && input.version !== void 0) throw perTableVersion("a join reads two tables");
|
|
62877
|
+
await assertRefReadable(app, settings, input.table);
|
|
62302
62878
|
const hist = await loadTableAsOf(app, settings, input.table, {
|
|
62303
62879
|
...input.version !== void 0 ? { version: input.version } : {},
|
|
62304
62880
|
...input.timestamp !== void 0 ? { timestamp: input.timestamp } : {}
|
|
62305
62881
|
});
|
|
62882
|
+
assertTableReadable(hist.table.catalog.ref.full);
|
|
62306
62883
|
read3.set(hist.table.catalog.ref.full, hist.version);
|
|
62307
62884
|
const r = await selectOn(app, settings, input, hist, read3);
|
|
62308
62885
|
return { ...r, version: hist.version, timestamp: hist.timestamp, ...input.join ? { versions: sortedVersions(read3) } : {} };
|
|
@@ -62321,6 +62898,7 @@ async function selectOn(app, settings, input, hist, read3) {
|
|
|
62321
62898
|
const t = hist?.table ?? await loadTable(app, settings, input.table).catch(async (e) => {
|
|
62322
62899
|
throw e instanceof TableNotFoundError ? tableNotFound(input.table, (await loadMetastore(app, settings))?.model, "any") : e;
|
|
62323
62900
|
});
|
|
62901
|
+
if (!hist) assertTableReadable(t.catalog.ref.full);
|
|
62324
62902
|
const known = readableKeys(t, settings);
|
|
62325
62903
|
const tz = (await readDatabaseConfig(app, settings)).timezone;
|
|
62326
62904
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -62352,7 +62930,7 @@ async function selectOn(app, settings, input, hist, read3) {
|
|
|
62352
62930
|
const linkpath = parseWikilink(v);
|
|
62353
62931
|
if (linkpath == null) return v;
|
|
62354
62932
|
const dest = app.metadataCache.getFirstLinkpathDest(linkpath, sourcePath);
|
|
62355
|
-
if (!dest || isArchivedPath(app, settings, dest.path)) return v;
|
|
62933
|
+
if (!dest || isArchivedPath(app, settings, dest.path) || !pathReadable(app, settings, dest.path)) return v;
|
|
62356
62934
|
const fm = app.metadataCache.getFileCache(dest)?.frontmatter ?? {};
|
|
62357
62935
|
return { path: dest.path, ...fm };
|
|
62358
62936
|
};
|
|
@@ -62454,15 +63032,16 @@ function select(app, settings, input) {
|
|
|
62454
63032
|
return traced(
|
|
62455
63033
|
"datanotes select",
|
|
62456
63034
|
{ "datanotes.op": "select", table: input.table },
|
|
62457
|
-
async () => selectImpl(app, settings, input.thisNote === void 0 ? input : { ...input, host: await noteHost(app, input.thisNote) }),
|
|
63035
|
+
async () => selectImpl(app, settings, input.thisNote === void 0 ? input : { ...input, host: await noteHost(app, settings, input.thisNote) }),
|
|
62458
63036
|
(r) => ({ attributes: { total: r.total, returned: r.rows.length } })
|
|
62459
63037
|
);
|
|
62460
63038
|
}
|
|
62461
63039
|
var THIS_NOTE_HINT = guide(`Give "this_note" the vault path of an existing note, with ".md" (list_folder or search_notes find it).`, "thisNote");
|
|
62462
|
-
async function noteHost(app, path23) {
|
|
63040
|
+
async function noteHost(app, settings, path23) {
|
|
62463
63041
|
const why = unsafeVaultPath(path23);
|
|
62464
63042
|
if (why) throw new OperationInputError(`this_note: ${why}`, THIS_NOTE_HINT);
|
|
62465
63043
|
const file = app.vault.getAbstractFileByPath(path23);
|
|
63044
|
+
assertPathReadable(app, settings, file?.path ?? path23);
|
|
62466
63045
|
if (file == null || !("extension" in file)) throw new OperationInputError(`this_note: no note at "${path23}" (give its vault path, e.g. "Journal/2026-10-06.md")`, THIS_NOTE_HINT);
|
|
62467
63046
|
const { ordered } = readOrderedFromMarkdown(await app.vault.read(file));
|
|
62468
63047
|
return { path: file.path, frontmatter: Object.fromEntries(ordered) };
|
|
@@ -62551,6 +63130,7 @@ init_file_cache();
|
|
|
62551
63130
|
init_inverse();
|
|
62552
63131
|
init_metastore();
|
|
62553
63132
|
init_tracing();
|
|
63133
|
+
init_read_scope();
|
|
62554
63134
|
init_vault_index();
|
|
62555
63135
|
var MIN_NAME_LENGTH = 3;
|
|
62556
63136
|
var MAX_NAME_WORDS = 12;
|
|
@@ -62921,7 +63501,7 @@ function standsAlone(text5, start, end) {
|
|
|
62921
63501
|
if (after === "." || after === ":") return !/[\p{L}\p{N}_/\\]/u.test(text5[end + 1] ?? "");
|
|
62922
63502
|
return true;
|
|
62923
63503
|
}
|
|
62924
|
-
function matchesIn(text5, tokens, ix, self) {
|
|
63504
|
+
function matchesIn(text5, tokens, ix, self, visible) {
|
|
62925
63505
|
const out2 = [];
|
|
62926
63506
|
for (let i = 0; i < tokens.length; i++) {
|
|
62927
63507
|
let key2 = tokens[i].norm;
|
|
@@ -62935,9 +63515,10 @@ function matchesIn(text5, tokens, ix, self) {
|
|
|
62935
63515
|
let any = false;
|
|
62936
63516
|
let isSelf = false;
|
|
62937
63517
|
for (const p of paths.keys()) {
|
|
62938
|
-
any = true;
|
|
62939
63518
|
if (self.has(p)) isSelf = true;
|
|
62940
|
-
else cands.push(p);
|
|
63519
|
+
else if (visible(p)) cands.push(p);
|
|
63520
|
+
else continue;
|
|
63521
|
+
any = true;
|
|
62941
63522
|
}
|
|
62942
63523
|
if (any) out2.push({ ti: i, tj: j, start: tokens[i].start, end: tokens[j].end, key: key2, cands: isSelf ? [] : cands.sort(), self: isSelf });
|
|
62943
63524
|
}
|
|
@@ -62950,7 +63531,7 @@ function matchesIn(text5, tokens, ix, self) {
|
|
|
62950
63531
|
}
|
|
62951
63532
|
return out2.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
62952
63533
|
}
|
|
62953
|
-
function mentionsIn(text5, frag, ix, self) {
|
|
63534
|
+
function mentionsIn(text5, frag, ix, self, visible) {
|
|
62954
63535
|
const out2 = [];
|
|
62955
63536
|
for (const [a, b] of freeSegments(text5)) {
|
|
62956
63537
|
const tokens = [...text5.slice(a, b).matchAll(WORD_RE)].map((m) => ({
|
|
@@ -62958,7 +63539,7 @@ function mentionsIn(text5, frag, ix, self) {
|
|
|
62958
63539
|
end: a + m.index + m[0].length,
|
|
62959
63540
|
norm: m[0].normalize("NFC").toLowerCase()
|
|
62960
63541
|
}));
|
|
62961
|
-
const matches3 = matchesIn(text5, tokens, ix, self);
|
|
63542
|
+
const matches3 = matchesIn(text5, tokens, ix, self, visible);
|
|
62962
63543
|
if (matches3.length === 0) continue;
|
|
62963
63544
|
const inMatch = /* @__PURE__ */ new Set();
|
|
62964
63545
|
for (const m of matches3) for (let t = m.ti; t <= m.tj; t++) inMatch.add(t);
|
|
@@ -63064,7 +63645,15 @@ async function autolinkFragmentsImpl(app, settings, input) {
|
|
|
63064
63645
|
if (input.fragments.every((f) => f.trim() === "")) return { fragments: [...input.fragments], report };
|
|
63065
63646
|
const ix = await nameIndex(app, settings);
|
|
63066
63647
|
const self = /* @__PURE__ */ new Set([input.path, ...input.self ?? []]);
|
|
63067
|
-
const
|
|
63648
|
+
const scope = currentReadScope();
|
|
63649
|
+
const seenVisible = /* @__PURE__ */ new Map();
|
|
63650
|
+
const visible = (p) => {
|
|
63651
|
+
if (!scope) return true;
|
|
63652
|
+
let v = seenVisible.get(p);
|
|
63653
|
+
if (v === void 0) seenVisible.set(p, v = pathReadable(app, settings, p, scope));
|
|
63654
|
+
return v;
|
|
63655
|
+
};
|
|
63656
|
+
const mentions = input.fragments.flatMap((text5, i) => mentionsIn(text5, i, ix, self, visible));
|
|
63068
63657
|
const doubtful = new Set(mentions.filter((m) => m.kind !== "clear" && m.kind !== "self" && m.kind !== "sentence_start").flatMap((m) => m.keys));
|
|
63069
63658
|
const linked = linkedTargets(app, input.body, input.path);
|
|
63070
63659
|
const edits = [];
|
|
@@ -63708,6 +64297,13 @@ function clearLinksTo(app, sourcePath, ordered, targets) {
|
|
|
63708
64297
|
return pointsAt(v) ? [k, null] : [k, v];
|
|
63709
64298
|
});
|
|
63710
64299
|
}
|
|
64300
|
+
function referencedBy(app, settings, sources) {
|
|
64301
|
+
const shown = sources.filter((p) => !pathHidden(app, settings, p));
|
|
64302
|
+
const hidden2 = sources.length - shown.length;
|
|
64303
|
+
if (hidden2 === 0) return `referenced by ${sources.join(", ")}`;
|
|
64304
|
+
const rest = `${hidden2} ${hidden2 === 1 ? "row" : "rows"} this token may not read`;
|
|
64305
|
+
return `referenced by ${shown.length > 0 ? `${shown.join(", ")} and ${rest}` : rest}`;
|
|
64306
|
+
}
|
|
63711
64307
|
async function deleteRowsImpl(app, settings, input, opts = {}) {
|
|
63712
64308
|
const t = await loadTable(app, settings, input.table);
|
|
63713
64309
|
const pred = compileWhere(await resolveWithin(app, settings, t, input.where), t, settings);
|
|
@@ -63722,7 +64318,7 @@ async function deleteRowsImpl(app, settings, input, opts = {}) {
|
|
|
63722
64318
|
for (const r of rows) {
|
|
63723
64319
|
const sources = inbound.get(r.file.path) ?? [];
|
|
63724
64320
|
if (sources.length > 0 && policy === "restrict") {
|
|
63725
|
-
conflicts.push({ path: r.file.path, message:
|
|
64321
|
+
conflicts.push({ path: r.file.path, message: referencedBy(app, settings, sources) });
|
|
63726
64322
|
}
|
|
63727
64323
|
sources.forEach((s) => referrers.add(s));
|
|
63728
64324
|
}
|
|
@@ -63795,6 +64391,7 @@ init_where();
|
|
|
63795
64391
|
init_row_checks();
|
|
63796
64392
|
init_changeset_checks();
|
|
63797
64393
|
init_message();
|
|
64394
|
+
init_read_scope();
|
|
63798
64395
|
init_link_paths();
|
|
63799
64396
|
function checkRetarget(dst, settings, r) {
|
|
63800
64397
|
const dstKnown = knownKeys(dst, settings);
|
|
@@ -63970,7 +64567,7 @@ async function adoptRowsImpl(app, settings, input, opts = {}) {
|
|
|
63970
64567
|
/* @__PURE__ */ new Set([settings.tableKey])
|
|
63971
64568
|
);
|
|
63972
64569
|
const owner = model ? rowTableFull(model, fmGet(ordered, settings.tableKey)) : null;
|
|
63973
|
-
if (owner) conflicts.push({ path: p, message: `already a row of ${owner}: use move_rows` });
|
|
64570
|
+
if (owner) conflicts.push({ path: p, message: `already a row of ${tableReadable(owner) ? owner : "another table"}: use move_rows` });
|
|
63974
64571
|
notes.push({ path: p, ordered });
|
|
63975
64572
|
}
|
|
63976
64573
|
const cs = emptyChangeSet("adopt_rows", `adopt ${notes.length} notes into ${dst.def.name}`, dst.def.schemaPath);
|
|
@@ -64593,13 +65190,36 @@ init_engine();
|
|
|
64593
65190
|
init_hints();
|
|
64594
65191
|
init_journal();
|
|
64595
65192
|
init_metastore();
|
|
65193
|
+
init_read_scope();
|
|
64596
65194
|
async function describeDatabaseHistory(app, settings, input = {}) {
|
|
64597
65195
|
const from = input.since === void 0 ? void 0 : sinceMs(input.since);
|
|
64598
65196
|
const tables = await loggedTables(app, settings);
|
|
64599
65197
|
const limit = Math.max(input.limit ?? 50, 0);
|
|
65198
|
+
const batchOf = await batchMembership(app, settings);
|
|
65199
|
+
const scope = currentReadScope();
|
|
65200
|
+
if (!scope) return databaseCommits(tables, await newestOf(app, settings, tables, limit, from), batchOf, limit);
|
|
65201
|
+
const readable = tables.filter((t) => tableReadable(t, scope));
|
|
65202
|
+
if (!(limit > 0)) return { tables: readable, history: [] };
|
|
65203
|
+
for (let depth = limit; ; depth *= 2) {
|
|
65204
|
+
const mine = await newestOf(app, settings, readable, depth, from);
|
|
65205
|
+
const oldest = mine.reduce((m, c) => Math.min(m, Date.parse(c.timestamp)), Infinity);
|
|
65206
|
+
const others = oldest === Infinity ? [] : await newestOf(app, settings, tables.filter((t) => !readable.includes(t)), Infinity, Math.max(from ?? -Infinity, oldest - 6e4));
|
|
65207
|
+
const all = databaseCommits(tables, [...mine, ...others], batchOf, Infinity);
|
|
65208
|
+
const shown = all.history.filter((c) => tableReadable(c.table, scope) && (c.tables ?? []).every((t) => tableReadable(t, scope)));
|
|
65209
|
+
const last = shown.length >= limit ? shown[limit - 1].timestamp : null;
|
|
65210
|
+
const complete = readable.every((t) => {
|
|
65211
|
+
const read3 = mine.filter((c) => c.table === t);
|
|
65212
|
+
return read3.length < depth || last !== null && read3[read3.length - 1].timestamp < last;
|
|
65213
|
+
});
|
|
65214
|
+
if (complete) return { tables: readable, history: shown.slice(0, limit) };
|
|
65215
|
+
}
|
|
65216
|
+
}
|
|
65217
|
+
async function newestOf(app, settings, tables, limit, from) {
|
|
64600
65218
|
const all = [];
|
|
64601
65219
|
for (const table of tables) for (const c of await newestCommitInfos(app, settings, table, limit, from)) all.push({ table, ...c });
|
|
64602
|
-
|
|
65220
|
+
return all;
|
|
65221
|
+
}
|
|
65222
|
+
function databaseCommits(tables, all, batchOf, limit) {
|
|
64603
65223
|
const groupOf = (c) => c.migrationId === void 0 ? void 0 : batchOf.get(c.migrationId) ?? c.migrationId;
|
|
64604
65224
|
const groups = /* @__PURE__ */ new Map();
|
|
64605
65225
|
for (const c of all) {
|
|
@@ -64757,6 +65377,8 @@ init_metastore();
|
|
|
64757
65377
|
init_descriptions();
|
|
64758
65378
|
init_row_files();
|
|
64759
65379
|
init_table_registry();
|
|
65380
|
+
init_read_scope();
|
|
65381
|
+
init_query_tables();
|
|
64760
65382
|
|
|
64761
65383
|
// src/core/graph.ts
|
|
64762
65384
|
init_engine();
|
|
@@ -64812,6 +65434,7 @@ init_table_registry();
|
|
|
64812
65434
|
init_validate();
|
|
64813
65435
|
init_hints();
|
|
64814
65436
|
init_tracing();
|
|
65437
|
+
init_read_scope();
|
|
64815
65438
|
var LINK_LABEL = "link";
|
|
64816
65439
|
var MAX_GRAPH_DEPTH = 10;
|
|
64817
65440
|
var MAX_GRAPH_NODES = 5e3;
|
|
@@ -64833,7 +65456,10 @@ function addEdge(g, keys, from, to, label) {
|
|
|
64833
65456
|
back.push({ to: from, label });
|
|
64834
65457
|
g.in.set(to, back);
|
|
64835
65458
|
}
|
|
64836
|
-
|
|
65459
|
+
function graphIndex(app, settings) {
|
|
65460
|
+
return runWithReadScope(null, () => sharedGraph(app, settings));
|
|
65461
|
+
}
|
|
65462
|
+
async function sharedGraph(app, settings) {
|
|
64837
65463
|
const generation2 = vaultGeneration(app);
|
|
64838
65464
|
let metastore = cachedMetastore(app, settings);
|
|
64839
65465
|
const attempt = `${generation2}\0${metastorePath(settings)}`;
|
|
@@ -65110,7 +65736,7 @@ function tablesOf(app, settings, names2, what) {
|
|
|
65110
65736
|
for (const n of names2) {
|
|
65111
65737
|
if (TABLE_PATTERN.test(n)) {
|
|
65112
65738
|
const prefix = n.slice(0, -1);
|
|
65113
|
-
for (const t of all) if (t.qualified.startsWith(prefix)) out2.add(t.qualified);
|
|
65739
|
+
for (const t of all) if (t.qualified.startsWith(prefix) && tableReadable(t.qualified)) out2.add(t.qualified);
|
|
65114
65740
|
continue;
|
|
65115
65741
|
}
|
|
65116
65742
|
if (n.includes("*")) throw new OperationInputError(`${what}: "${n}" is not a table pattern`, NODE_SET_HINT);
|
|
@@ -65124,10 +65750,12 @@ async function resolveNodes(app, settings, set, what) {
|
|
|
65124
65750
|
for (const p of set.paths ?? []) {
|
|
65125
65751
|
const path23 = p.endsWith(".md") ? p : `${p}.md`;
|
|
65126
65752
|
if (app.vault.getAbstractFileByPath(path23) == null) throw new OperationInputError(`${what}: note not found: ${path23}`);
|
|
65753
|
+
assertPathReadable(app, settings, path23);
|
|
65127
65754
|
out2.add(path23);
|
|
65128
65755
|
}
|
|
65129
65756
|
if (set.table !== void 0) {
|
|
65130
65757
|
const t = await loadTable(app, settings, set.table);
|
|
65758
|
+
assertTableReadable(t.catalog.ref.full);
|
|
65131
65759
|
const pred = compileWhere(await resolveWithin(app, settings, t, set.where), t, settings);
|
|
65132
65760
|
for (const r of await loadRows(app, settings, t)) if (pred(r)) out2.add(r.file.path);
|
|
65133
65761
|
} else if (set.where !== void 0) {
|
|
@@ -65141,13 +65769,24 @@ async function resolveNodes(app, settings, set, what) {
|
|
|
65141
65769
|
if (!set.paths && set.table === void 0 && set.tables === void 0) throw new OperationInputError(`"${what}" needs "paths", "table" or "tables"`, NODE_SET_HINT);
|
|
65142
65770
|
return [...out2].sort();
|
|
65143
65771
|
}
|
|
65144
|
-
function
|
|
65772
|
+
function visibleNodes(app, settings) {
|
|
65773
|
+
const scope = currentReadScope();
|
|
65774
|
+
if (!scope) return null;
|
|
65775
|
+
const known = /* @__PURE__ */ new Map();
|
|
65776
|
+
return (path23) => {
|
|
65777
|
+
let ok2 = known.get(path23);
|
|
65778
|
+
if (ok2 === void 0) known.set(path23, ok2 = pathReadable(app, settings, path23, scope));
|
|
65779
|
+
return ok2;
|
|
65780
|
+
};
|
|
65781
|
+
}
|
|
65782
|
+
function stepsFrom(g, node2, direction, labels, visible) {
|
|
65145
65783
|
const steps = [];
|
|
65784
|
+
const follow = (e) => (!labels || labels.has(e.label)) && (!visible || visible(e.to));
|
|
65146
65785
|
if (direction !== "in") {
|
|
65147
|
-
for (const e of g.out.get(node2) ?? []) if (
|
|
65786
|
+
for (const e of g.out.get(node2) ?? []) if (follow(e)) steps.push({ ...e, dir: "out" });
|
|
65148
65787
|
}
|
|
65149
65788
|
if (direction !== "out") {
|
|
65150
|
-
for (const e of g.in.get(node2) ?? []) if (
|
|
65789
|
+
for (const e of g.in.get(node2) ?? []) if (follow(e)) steps.push({ ...e, dir: "in" });
|
|
65151
65790
|
}
|
|
65152
65791
|
return steps;
|
|
65153
65792
|
}
|
|
@@ -65163,11 +65802,12 @@ async function graphNeighbors(app, settings, input) {
|
|
|
65163
65802
|
const direction = checkDirection(input.direction);
|
|
65164
65803
|
const g = await graphIndex(app, settings);
|
|
65165
65804
|
const labels = labelSet(input.edges);
|
|
65805
|
+
const visible = visibleNodes(app, settings);
|
|
65166
65806
|
const nodes = await resolveNodes(app, settings, input.from, "from");
|
|
65167
65807
|
if (nodes.length > MAX_GRAPH_NODES) throw new OperationInputError(`"from" matches ${nodes.length} notes (max ${MAX_GRAPH_NODES}): narrow it`);
|
|
65168
65808
|
return {
|
|
65169
65809
|
nodes: nodes.map((path23) => {
|
|
65170
|
-
const edges = stepsFrom(g, path23, direction, labels).map((s) => ({
|
|
65810
|
+
const edges = stepsFrom(g, path23, direction, labels, visible).map((s) => ({
|
|
65171
65811
|
path: s.to,
|
|
65172
65812
|
...g.tableOf.has(s.to) ? { table: g.tableOf.get(s.to) } : {},
|
|
65173
65813
|
label: s.label,
|
|
@@ -65182,11 +65822,18 @@ async function graphNeighbors(app, settings, input) {
|
|
|
65182
65822
|
function qualifiedTables(app, settings, names2, what) {
|
|
65183
65823
|
if (!names2 || names2.length === 0) return null;
|
|
65184
65824
|
const all = listTables(app, settings.databaseFolder);
|
|
65825
|
+
const scope = currentReadScope();
|
|
65826
|
+
const known = scope ? all.filter((t) => tableReadable(t.qualified, scope)) : all;
|
|
65185
65827
|
const out2 = /* @__PURE__ */ new Set();
|
|
65186
65828
|
for (const n of names2) {
|
|
65187
|
-
|
|
65829
|
+
let hits = all.filter((t) => t.qualified === n || t.qualified.endsWith(`.${n}`));
|
|
65830
|
+
if (scope) {
|
|
65831
|
+
const readable = hits.filter((t) => tableReadable(t.qualified, scope));
|
|
65832
|
+
if (readable.length === 0 && hits.length > 0) throw tableReadDenied(scope.token, hits[0].qualified);
|
|
65833
|
+
hits = readable;
|
|
65834
|
+
}
|
|
65188
65835
|
if (hits.length === 0) {
|
|
65189
|
-
throw new OperationInputError(`${what}: unknown table "${n}"`, unknownName(n.split(".").pop(),
|
|
65836
|
+
throw new OperationInputError(`${what}: unknown table "${n}"`, unknownName(n.split(".").pop(), known.map((t) => t.name), "tables", "list_tables lists them.", "graph"));
|
|
65190
65837
|
}
|
|
65191
65838
|
if (hits.length > 1) {
|
|
65192
65839
|
throw new OperationInputError(`${what}: "${n}" matches ${hits.map((h) => h.qualified).join(", ")}`, guide(`Name the table in full: ${hits.map((h) => h.qualified).join(" or ")}.`, "graph"));
|
|
@@ -65207,6 +65854,7 @@ async function graphTraverse(app, settings, input) {
|
|
|
65207
65854
|
const labels = labelSet(input.edges);
|
|
65208
65855
|
const want = qualifiedTables(app, settings, input.tables, "tables");
|
|
65209
65856
|
const through = qualifiedTables(app, settings, input.through, "through");
|
|
65857
|
+
const visible = visibleNodes(app, settings);
|
|
65210
65858
|
const starts = await resolveNodes(app, settings, input.from, "from");
|
|
65211
65859
|
const via = new Map(starts.map((s) => [s, []]));
|
|
65212
65860
|
let frontier = starts;
|
|
@@ -65227,7 +65875,7 @@ async function graphTraverse(app, settings, input) {
|
|
|
65227
65875
|
const next = [];
|
|
65228
65876
|
for (const node2 of frontier) {
|
|
65229
65877
|
if (depth > 1 && through && !through.has(g.tableOf.get(node2) ?? "")) continue;
|
|
65230
|
-
for (const s of stepsFrom(g, node2, direction, labels)) {
|
|
65878
|
+
for (const s of stepsFrom(g, node2, direction, labels, visible)) {
|
|
65231
65879
|
if (via.has(s.to)) continue;
|
|
65232
65880
|
via.set(s.to, [...via.get(node2), { from: node2, label: s.label, direction: s.dir, to: s.to }]);
|
|
65233
65881
|
next.push(s.to);
|
|
@@ -65247,6 +65895,7 @@ async function graphPath(app, settings, input) {
|
|
|
65247
65895
|
}
|
|
65248
65896
|
const g = await graphIndex(app, settings);
|
|
65249
65897
|
const labels = labelSet(input.edges);
|
|
65898
|
+
const visible = visibleNodes(app, settings);
|
|
65250
65899
|
const sources = await resolveNodes(app, settings, input.from, "from");
|
|
65251
65900
|
const targets = new Set(await resolveNodes(app, settings, input.to, "to"));
|
|
65252
65901
|
const via = new Map(sources.map((s) => [s, []]));
|
|
@@ -65256,7 +65905,7 @@ async function graphPath(app, settings, input) {
|
|
|
65256
65905
|
for (let depth = 1; depth <= maxDepth && frontier.length > 0; depth++) {
|
|
65257
65906
|
const next = [];
|
|
65258
65907
|
for (const node2 of frontier) {
|
|
65259
|
-
for (const s of stepsFrom(g, node2, direction, labels)) {
|
|
65908
|
+
for (const s of stepsFrom(g, node2, direction, labels, visible)) {
|
|
65260
65909
|
if (via.has(s.to)) continue;
|
|
65261
65910
|
const steps = [...via.get(node2), { from: node2, label: s.label, direction: s.dir, to: s.to }];
|
|
65262
65911
|
if (targets.has(s.to)) return { found: true, length: depth, via: steps, from: steps[0].from, to: s.to };
|
|
@@ -65273,6 +65922,7 @@ async function graphPath(app, settings, input) {
|
|
|
65273
65922
|
init_engine();
|
|
65274
65923
|
init_metastore();
|
|
65275
65924
|
init_table_registry();
|
|
65925
|
+
init_read_scope();
|
|
65276
65926
|
var DATE_RE2 = /^\d{4}-\d{2}-\d{2}$/;
|
|
65277
65927
|
var DATETIME_RE2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?$/;
|
|
65278
65928
|
var LINK_RE2 = /^\[\[([^\]|#]+)(?:#[^\]|]*)?(?:\|[^\]]*)?\]\]$/;
|
|
@@ -65310,14 +65960,16 @@ function tableNameFor(folder) {
|
|
|
65310
65960
|
}
|
|
65311
65961
|
function filesOf(app, settings, input) {
|
|
65312
65962
|
const out2 = /* @__PURE__ */ new Map();
|
|
65963
|
+
const scope = currentReadScope();
|
|
65313
65964
|
if (input.folder !== void 0) {
|
|
65314
65965
|
const prefix = input.folder.replace(/^\/+|\/+$/g, "");
|
|
65315
65966
|
for (const f of app.vault.getMarkdownFiles()) {
|
|
65316
|
-
if (prefix === "" || f.path.startsWith(`${prefix}/`)) out2.set(f.path, f);
|
|
65967
|
+
if ((prefix === "" || f.path.startsWith(`${prefix}/`)) && pathReadable(app, settings, f.path, scope)) out2.set(f.path, f);
|
|
65317
65968
|
}
|
|
65318
65969
|
}
|
|
65319
65970
|
for (const p of input.paths ?? []) {
|
|
65320
65971
|
const path23 = p.endsWith(".md") ? p : `${p}.md`;
|
|
65972
|
+
assertPathReadable(app, settings, path23);
|
|
65321
65973
|
const f = app.vault.getAbstractFileByPath(path23);
|
|
65322
65974
|
if (f && "extension" in f) out2.set(path23, f);
|
|
65323
65975
|
}
|
|
@@ -65355,7 +66007,7 @@ function inferColumns(app, settings, observed, files, maxEnum) {
|
|
|
65355
66007
|
col.items = itemKind === "list" ? "string" : itemKind;
|
|
65356
66008
|
}
|
|
65357
66009
|
if (col.items === "link") {
|
|
65358
|
-
const ref2 =
|
|
66010
|
+
const ref2 = refOf3(app, settings, filled.flatMap((x) => Array.isArray(x.value) ? x.value.map((v) => ({ path: x.path, value: v })) : [x]));
|
|
65359
66011
|
if (ref2) col.ref = ref2;
|
|
65360
66012
|
}
|
|
65361
66013
|
} else if (kind === "string") {
|
|
@@ -65370,7 +66022,7 @@ function inferColumns(app, settings, observed, files, maxEnum) {
|
|
|
65370
66022
|
} else {
|
|
65371
66023
|
col.type = kind;
|
|
65372
66024
|
if (kind === "link") {
|
|
65373
|
-
const ref2 =
|
|
66025
|
+
const ref2 = refOf3(app, settings, filled);
|
|
65374
66026
|
if (ref2) col.ref = ref2;
|
|
65375
66027
|
}
|
|
65376
66028
|
}
|
|
@@ -65386,7 +66038,7 @@ function frequencyOrder(values) {
|
|
|
65386
66038
|
for (const v of values) n.set(v, (n.get(v) ?? 0) + 1);
|
|
65387
66039
|
return [...n.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([v]) => v);
|
|
65388
66040
|
}
|
|
65389
|
-
function
|
|
66041
|
+
function refOf3(app, settings, links) {
|
|
65390
66042
|
const counts = /* @__PURE__ */ new Map();
|
|
65391
66043
|
let resolved = 0;
|
|
65392
66044
|
for (const { path: path23, value } of links) {
|
|
@@ -65400,7 +66052,7 @@ function refOf2(app, settings, links) {
|
|
|
65400
66052
|
if (owner) counts.set(owner, (counts.get(owner) ?? 0) + 1);
|
|
65401
66053
|
}
|
|
65402
66054
|
const best = [...counts.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
65403
|
-
return best && resolved > 0 && best[1] >= resolved * 0.8 ? best[0] : void 0;
|
|
66055
|
+
return best && resolved > 0 && best[1] >= resolved * 0.8 && tableReadable(best[0]) ? best[0] : void 0;
|
|
65404
66056
|
}
|
|
65405
66057
|
function frontmatter(app, f) {
|
|
65406
66058
|
const fm = app.metadataCache.getFileCache(f)?.frontmatter;
|
|
@@ -65412,6 +66064,7 @@ async function inferTable(app, settings, input) {
|
|
|
65412
66064
|
const notes = [];
|
|
65413
66065
|
if (input.table !== void 0) {
|
|
65414
66066
|
const t = await loadTable(app, settings, input.table);
|
|
66067
|
+
assertTableReadable(t.catalog.ref.full);
|
|
65415
66068
|
const rows = await loadRows(app, settings, t);
|
|
65416
66069
|
const declared = /* @__PURE__ */ new Set([...t.def.columns.map((c) => c.name), ...t.def.untypedFields]);
|
|
65417
66070
|
const observed2 = /* @__PURE__ */ new Map();
|
|
@@ -65575,7 +66228,8 @@ init_hints();
|
|
|
65575
66228
|
var FOLLOW_HINT = (topic) => guide("They run while the engine follows the vault: start it with datanotes serve --vault <folder>.", topic);
|
|
65576
66229
|
async function listTablesAndViews(app, settings) {
|
|
65577
66230
|
const ms = await loadMetastore(app, settings);
|
|
65578
|
-
const
|
|
66231
|
+
const scope = currentReadScope();
|
|
66232
|
+
const views = ms ? catalogViews(ms.model).filter(({ ref: ref2 }) => viewReadable(ms.model, ref2.full, scope)).map(({ ref: ref2, view }) => ({
|
|
65579
66233
|
name: ref2.table,
|
|
65580
66234
|
catalog: ref2.catalog,
|
|
65581
66235
|
schema: ref2.schema,
|
|
@@ -65583,10 +66237,17 @@ async function listTablesAndViews(app, settings) {
|
|
|
65583
66237
|
...view.description ? { description: view.description } : {},
|
|
65584
66238
|
...view.aliases && view.aliases.length > 0 ? { aliases: view.aliases } : {}
|
|
65585
66239
|
})) : [];
|
|
65586
|
-
return { tables: listTables(app, settings.databaseFolder), views };
|
|
66240
|
+
return { tables: listTables(app, settings.databaseFolder).filter((t) => tableReadable(t.qualified, scope)), views };
|
|
65587
66241
|
}
|
|
65588
66242
|
async function describeTable(app, settings, table) {
|
|
66243
|
+
const scope = currentReadScope();
|
|
66244
|
+
if (scope) {
|
|
66245
|
+
const model = (await loadMetastore(app, settings))?.model;
|
|
66246
|
+
const view = model && typeof table === "string" && refOf2(model, table, "table") === null ? refOf2(model, table, "view") : null;
|
|
66247
|
+
if (model && view && !viewReadable(model, view, scope)) throw tableReadDenied(scope.token, view);
|
|
66248
|
+
}
|
|
65589
66249
|
const t = await loadTable(app, settings, table);
|
|
66250
|
+
assertTableReadable(t.catalog.ref.full);
|
|
65590
66251
|
const { def } = t;
|
|
65591
66252
|
const o = def.options;
|
|
65592
66253
|
const e = t.catalog.entry;
|
|
@@ -65831,6 +66492,7 @@ init_metastore();
|
|
|
65831
66492
|
init_store();
|
|
65832
66493
|
init_engine();
|
|
65833
66494
|
init_message();
|
|
66495
|
+
init_read_scope();
|
|
65834
66496
|
var SHARDS = 16;
|
|
65835
66497
|
var FORMAT = 1;
|
|
65836
66498
|
function quantizeValues(v) {
|
|
@@ -66151,13 +66813,23 @@ var SemanticIndex = class {
|
|
|
66151
66813
|
const q = await this.queryVector(input.query.trim());
|
|
66152
66814
|
const limit = Math.min(Math.max(1, input.limit ?? 10), 100);
|
|
66153
66815
|
const folder = input.folder?.replace(/^\/+|\/+$/g, "");
|
|
66154
|
-
const
|
|
66816
|
+
const scope = currentReadScope();
|
|
66817
|
+
const settings = this.getSettings();
|
|
66818
|
+
if (scope) {
|
|
66819
|
+
const model = cachedMetastore(this.app, settings);
|
|
66820
|
+
for (const name of input.tables ?? []) {
|
|
66821
|
+
const full = model ? rowTableFull(model, name) : null;
|
|
66822
|
+
if (full !== null) assertTableReadable(full);
|
|
66823
|
+
}
|
|
66824
|
+
}
|
|
66825
|
+
const wantTable2 = (t) => !input.tables || input.tables.length === 0 || t !== void 0 && input.tables.some((x) => t === x || t.endsWith(`.${x}`));
|
|
66155
66826
|
const min = input.minScore;
|
|
66156
66827
|
const passing = (f) => f.passages.map((p) => ({ p, estimate: estimateOf(q, p) })).filter(({ p, estimate }) => min === void 0 || estimate >= min + SCORE_SLACK || estimate >= min - SCORE_SLACK && scoreOf(q, p) >= min);
|
|
66157
66828
|
const notes = [];
|
|
66158
66829
|
for (const [path23, f] of this.files) {
|
|
66159
66830
|
if (folder && !path23.startsWith(`${folder}/`)) continue;
|
|
66160
|
-
if (!
|
|
66831
|
+
if (!wantTable2(f.table)) continue;
|
|
66832
|
+
if (scope && !pathReadable(this.app, settings, path23, scope)) continue;
|
|
66161
66833
|
let estimate = -Infinity;
|
|
66162
66834
|
for (const x of passing(f)) estimate = Math.max(estimate, x.estimate);
|
|
66163
66835
|
if (estimate > -Infinity) notes.push({ path: path23, f, estimate });
|
|
@@ -66613,6 +67285,536 @@ init_actor();
|
|
|
66613
67285
|
init_changeset();
|
|
66614
67286
|
init_engine();
|
|
66615
67287
|
init_vault_settle();
|
|
67288
|
+
|
|
67289
|
+
// src/core/note-search.ts
|
|
67290
|
+
init_file_cache();
|
|
67291
|
+
init_hints();
|
|
67292
|
+
init_engine();
|
|
67293
|
+
init_metastore();
|
|
67294
|
+
init_read_scope();
|
|
67295
|
+
|
|
67296
|
+
// src/core/vault-read.ts
|
|
67297
|
+
init_file_cache();
|
|
67298
|
+
init_database();
|
|
67299
|
+
init_datetime();
|
|
67300
|
+
init_hints();
|
|
67301
|
+
init_read_scope();
|
|
67302
|
+
var VaultPathNotFoundError = class extends GuidedError {
|
|
67303
|
+
constructor() {
|
|
67304
|
+
super(...arguments);
|
|
67305
|
+
this.name = "VaultPathNotFoundError";
|
|
67306
|
+
}
|
|
67307
|
+
};
|
|
67308
|
+
var VaultPathError = class extends GuidedError {
|
|
67309
|
+
constructor() {
|
|
67310
|
+
super(...arguments);
|
|
67311
|
+
this.name = "VaultPathError";
|
|
67312
|
+
}
|
|
67313
|
+
};
|
|
67314
|
+
var FIND_NOTE = guide("Give the vault path of the note, with .md: list_folder lists a folder, search_notes finds notes by their words.", "readNote");
|
|
67315
|
+
function clean(path23) {
|
|
67316
|
+
return path23.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
67317
|
+
}
|
|
67318
|
+
function readableFilter(app, settings) {
|
|
67319
|
+
const scope = currentReadScope();
|
|
67320
|
+
if (scope === null) return null;
|
|
67321
|
+
if (!settings) return () => false;
|
|
67322
|
+
return (path23) => pathReadable(app, settings, path23, scope);
|
|
67323
|
+
}
|
|
67324
|
+
async function readNote(app, settings, path23) {
|
|
67325
|
+
const rel = clean(path23);
|
|
67326
|
+
if (rel === "" || /\/$/.test(path23.trim())) {
|
|
67327
|
+
throw new VaultPathError("path must name a file, not a folder", guide('Pass the vault path of a note, e.g. "Films/Metropolis.md"; list_folder lists a folder.', "readNote"));
|
|
67328
|
+
}
|
|
67329
|
+
const f = app.vault.getAbstractFileByPath(rel);
|
|
67330
|
+
if (!f || !("extension" in f)) {
|
|
67331
|
+
if (f) throw new VaultPathError(`${rel} is a folder: list_folder lists it`, FIND_NOTE);
|
|
67332
|
+
throw new VaultPathNotFoundError(`no file ${rel} in the vault`, FIND_NOTE);
|
|
67333
|
+
}
|
|
67334
|
+
const file = f;
|
|
67335
|
+
assertPathReadable(app, settings, file.path);
|
|
67336
|
+
const { content } = await readHashed(app, file);
|
|
67337
|
+
const cache = app.metadataCache.getFileCache(file);
|
|
67338
|
+
const { position: _position, ...frontMatter } = cache?.frontmatter ?? {};
|
|
67339
|
+
const fmTags = frontMatter.tags;
|
|
67340
|
+
const tags = [
|
|
67341
|
+
...(cache?.tags ?? []).map((t) => t.tag.replace(/^#/, "")),
|
|
67342
|
+
...Array.isArray(fmTags) ? fmTags.map(String) : typeof fmTags === "string" ? [fmTags] : []
|
|
67343
|
+
];
|
|
67344
|
+
const stat = file.stat;
|
|
67345
|
+
const tz = (await readDatabaseConfig(app, settings)).timezone;
|
|
67346
|
+
const at = (ms) => typeof ms === "number" && ms > 0 ? isoAt(ms, tz) : null;
|
|
67347
|
+
return {
|
|
67348
|
+
path: file.path,
|
|
67349
|
+
content,
|
|
67350
|
+
front_matter: frontMatter,
|
|
67351
|
+
tags: [...new Set(tags)],
|
|
67352
|
+
modified_at: at(stat?.mtime),
|
|
67353
|
+
created_at: at(stat?.ctime),
|
|
67354
|
+
size: typeof stat?.size === "number" ? stat.size : null
|
|
67355
|
+
};
|
|
67356
|
+
}
|
|
67357
|
+
function listFolder(app, folder, settings) {
|
|
67358
|
+
const rel = clean(folder);
|
|
67359
|
+
const node2 = rel === "" ? app.vault.getRoot?.() ?? app.vault.getAbstractFileByPath("/") : app.vault.getAbstractFileByPath(rel);
|
|
67360
|
+
if (!node2 || !("children" in node2)) {
|
|
67361
|
+
if (node2) throw new VaultPathError(`${rel} is a file: read_note reads it`, guide('Pass a folder of the vault ("" is the vault root); read_note reads a file.', "readNote"));
|
|
67362
|
+
throw new VaultPathNotFoundError(`no folder ${rel} in the vault`, guide('list_folder with folder "" lists the vault root; pass one of its folders.', "readNote"));
|
|
67363
|
+
}
|
|
67364
|
+
const readable = readableFilter(app, settings);
|
|
67365
|
+
let shown = null;
|
|
67366
|
+
if (readable) {
|
|
67367
|
+
shown = /* @__PURE__ */ new Set();
|
|
67368
|
+
const prefix = rel === "" ? "" : `${rel}/`;
|
|
67369
|
+
for (const f of app.vault.getMarkdownFiles()) {
|
|
67370
|
+
if (!f.path.startsWith(prefix) || !readable(f.path)) continue;
|
|
67371
|
+
shown.add(f.path);
|
|
67372
|
+
for (let i = f.path.indexOf("/", prefix.length); i >= 0; i = f.path.indexOf("/", i + 1)) shown.add(f.path.slice(0, i));
|
|
67373
|
+
}
|
|
67374
|
+
}
|
|
67375
|
+
const folders = [];
|
|
67376
|
+
const files = [];
|
|
67377
|
+
for (const child of node2.children) {
|
|
67378
|
+
const name = child.name ?? child.path.split("/").pop();
|
|
67379
|
+
if (name.startsWith(".")) continue;
|
|
67380
|
+
const path23 = rel === "" ? name : `${rel}/${name}`;
|
|
67381
|
+
if (shown && !shown.has(path23)) continue;
|
|
67382
|
+
("children" in child ? folders : files).push(path23);
|
|
67383
|
+
}
|
|
67384
|
+
return { folder: rel, folders: folders.sort(), files: files.sort() };
|
|
67385
|
+
}
|
|
67386
|
+
function fold5(s) {
|
|
67387
|
+
return s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
|
|
67388
|
+
}
|
|
67389
|
+
var searchFold = fold5;
|
|
67390
|
+
var ONE_TO_ONE = /^[\u0000-˿]*$/;
|
|
67391
|
+
var BMP_FOLDED = new Uint8Array(65536).fill(255);
|
|
67392
|
+
function foldedLength(cp, ch) {
|
|
67393
|
+
if (cp > 65535) return fold5(ch).length;
|
|
67394
|
+
let n = BMP_FOLDED[cp];
|
|
67395
|
+
if (n === 255) BMP_FOLDED[cp] = n = fold5(ch).length;
|
|
67396
|
+
return n;
|
|
67397
|
+
}
|
|
67398
|
+
function foldedOffsets(content) {
|
|
67399
|
+
if (ONE_TO_ONE.test(content)) return null;
|
|
67400
|
+
const starts = [];
|
|
67401
|
+
const ends = [];
|
|
67402
|
+
let aligned = true;
|
|
67403
|
+
for (let o = 0; o < content.length; ) {
|
|
67404
|
+
const cp = content.codePointAt(o);
|
|
67405
|
+
const width = cp > 65535 ? 2 : 1;
|
|
67406
|
+
const n = cp < 768 ? 1 : foldedLength(cp, content.slice(o, o + width));
|
|
67407
|
+
if (n !== width) aligned = false;
|
|
67408
|
+
if (n === 0 && ends.length > 0) {
|
|
67409
|
+
for (let k = ends.length - 1; k >= 0 && ends[k] === o; k--) ends[k] = o + width;
|
|
67410
|
+
}
|
|
67411
|
+
for (let k = 0; k < n; k++) {
|
|
67412
|
+
starts.push(o);
|
|
67413
|
+
ends.push(o + width);
|
|
67414
|
+
}
|
|
67415
|
+
o += width;
|
|
67416
|
+
}
|
|
67417
|
+
if (aligned) return null;
|
|
67418
|
+
return {
|
|
67419
|
+
// a match covers at least one folded character: its start and its last character are in the text
|
|
67420
|
+
start: (i) => starts[i],
|
|
67421
|
+
end: (i) => ends[i - 1]
|
|
67422
|
+
};
|
|
67423
|
+
}
|
|
67424
|
+
var SEARCH_CACHE = { maxChars: 64 * 1024 * 1024 };
|
|
67425
|
+
var FOLDED = /* @__PURE__ */ new WeakMap();
|
|
67426
|
+
function foldedCache(app) {
|
|
67427
|
+
let cache = FOLDED.get(app);
|
|
67428
|
+
if (!cache) FOLDED.set(app, cache = { notes: /* @__PURE__ */ new Map(), chars: 0 });
|
|
67429
|
+
return cache;
|
|
67430
|
+
}
|
|
67431
|
+
function keptFolded(app, path23, hash) {
|
|
67432
|
+
const kept2 = foldedCache(app).notes.get(path23);
|
|
67433
|
+
return kept2 && kept2.hash === hash ? kept2 : void 0;
|
|
67434
|
+
}
|
|
67435
|
+
function foldedNote(app, path23, hash, content, keep) {
|
|
67436
|
+
const note = { hash, text: fold5(content), name: fold5(path23) };
|
|
67437
|
+
if (!keep) return note;
|
|
67438
|
+
const cache = foldedCache(app);
|
|
67439
|
+
const kept2 = cache.notes.get(path23);
|
|
67440
|
+
const size = note.text.length + note.name.length;
|
|
67441
|
+
if (kept2) {
|
|
67442
|
+
cache.chars += size - kept2.text.length - kept2.name.length;
|
|
67443
|
+
cache.notes.set(path23, note);
|
|
67444
|
+
} else if (cache.chars + size <= SEARCH_CACHE.maxChars) {
|
|
67445
|
+
cache.chars += size;
|
|
67446
|
+
cache.notes.set(path23, note);
|
|
67447
|
+
}
|
|
67448
|
+
return note;
|
|
67449
|
+
}
|
|
67450
|
+
function forgetFolded(app, present2) {
|
|
67451
|
+
const cache = foldedCache(app);
|
|
67452
|
+
for (const [path23, note] of cache.notes) {
|
|
67453
|
+
if (present2.has(path23)) continue;
|
|
67454
|
+
cache.notes.delete(path23);
|
|
67455
|
+
cache.chars -= note.text.length + note.name.length;
|
|
67456
|
+
}
|
|
67457
|
+
}
|
|
67458
|
+
var SEARCH_SOURCES = /* @__PURE__ */ new WeakMap();
|
|
67459
|
+
function setSearchSource(app, source) {
|
|
67460
|
+
if (source) SEARCH_SOURCES.set(app, source);
|
|
67461
|
+
else SEARCH_SOURCES.delete(app);
|
|
67462
|
+
}
|
|
67463
|
+
function searchCandidates(app, words) {
|
|
67464
|
+
return SEARCH_SOURCES.get(app)?.candidates(words) ?? null;
|
|
67465
|
+
}
|
|
67466
|
+
var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
67467
|
+
function indexableWord(w) {
|
|
67468
|
+
if (w.indexOf("\0") >= 0 || LONE_SURROGATE.test(w)) return false;
|
|
67469
|
+
let n = 0;
|
|
67470
|
+
for (const _ of w) if (++n >= 3) return true;
|
|
67471
|
+
return false;
|
|
67472
|
+
}
|
|
67473
|
+
var HIDDEN = /(^|\/)\./;
|
|
67474
|
+
var MAX_RESULTS = 200;
|
|
67475
|
+
function scoreOf2(n, words) {
|
|
67476
|
+
let hits = 0;
|
|
67477
|
+
for (const w of words) {
|
|
67478
|
+
for (let i = n.text.indexOf(w); i >= 0 && hits < 20; i = n.text.indexOf(w, i + w.length)) hits++;
|
|
67479
|
+
if (hits >= 20) break;
|
|
67480
|
+
}
|
|
67481
|
+
return hits + words.filter((w) => n.name.includes(w)).length * 5;
|
|
67482
|
+
}
|
|
67483
|
+
function matchesOf(text5, content, words, ctx) {
|
|
67484
|
+
const spans = [];
|
|
67485
|
+
for (const w of words) {
|
|
67486
|
+
for (let i = text5.indexOf(w); i >= 0; i = text5.indexOf(w, i + w.length)) spans.push({ start: i, end: i + w.length });
|
|
67487
|
+
}
|
|
67488
|
+
spans.sort((x, y) => x.start - y.start || y.end - x.end);
|
|
67489
|
+
const merged = [];
|
|
67490
|
+
for (const sp of spans) {
|
|
67491
|
+
const last = merged[merged.length - 1];
|
|
67492
|
+
if (last && (sp.start <= last.end || !/[\p{L}\p{N}]/u.test(text5.slice(last.end, sp.start)))) last.end = Math.max(last.end, sp.end);
|
|
67493
|
+
else merged.push({ ...sp });
|
|
67494
|
+
}
|
|
67495
|
+
const at = merged.length > 0 ? foldedOffsets(content) : null;
|
|
67496
|
+
return merged.slice(0, 20).map((m) => {
|
|
67497
|
+
const start = at ? at.start(m.start) : m.start;
|
|
67498
|
+
const end = at ? at.end(m.end) : m.end;
|
|
67499
|
+
return { start, end, context: content.slice(Math.max(0, start - ctx), end + ctx) };
|
|
67500
|
+
});
|
|
67501
|
+
}
|
|
67502
|
+
function candidateFiles(app, c) {
|
|
67503
|
+
const out2 = [];
|
|
67504
|
+
const seen = /* @__PURE__ */ new Set();
|
|
67505
|
+
for (const list4 of [c.hits, c.unsure]) {
|
|
67506
|
+
for (const p of list4) {
|
|
67507
|
+
if (seen.has(p)) continue;
|
|
67508
|
+
seen.add(p);
|
|
67509
|
+
const f = app.vault.getAbstractFileByPath(p);
|
|
67510
|
+
if (f && "extension" in f && f.extension === "md") out2.push(f);
|
|
67511
|
+
}
|
|
67512
|
+
}
|
|
67513
|
+
return out2;
|
|
67514
|
+
}
|
|
67515
|
+
async function searchNotes(app, query, contextLength = 100, settings) {
|
|
67516
|
+
const words = [...new Set(fold5(query).split(/\s+/).filter(Boolean))];
|
|
67517
|
+
if (words.length === 0) return { query, results: [], total: 0 };
|
|
67518
|
+
const ctx = Number.isFinite(contextLength) && contextLength >= 0 ? Math.min(Math.floor(contextLength), 1e3) : 100;
|
|
67519
|
+
const indexable = words.filter(indexableWord);
|
|
67520
|
+
const narrowed = indexable.length > 0 ? searchCandidates(app, indexable) : null;
|
|
67521
|
+
const files = narrowed ? candidateFiles(app, narrowed) : app.vault.getMarkdownFiles();
|
|
67522
|
+
const keep = narrowed === null;
|
|
67523
|
+
const present2 = keep ? /* @__PURE__ */ new Set() : null;
|
|
67524
|
+
const has2 = (n) => words.every((w) => n.text.includes(w) || n.name.includes(w));
|
|
67525
|
+
const readable = readableFilter(app, settings);
|
|
67526
|
+
const found = [];
|
|
67527
|
+
for (const f of files) {
|
|
67528
|
+
if (HIDDEN.test(f.path)) continue;
|
|
67529
|
+
present2?.add(f.path);
|
|
67530
|
+
if (readable && !readable(f.path)) continue;
|
|
67531
|
+
let content;
|
|
67532
|
+
let folded = keptFolded(app, f.path, await readHash(app, f));
|
|
67533
|
+
if (!folded) {
|
|
67534
|
+
const r = await readHashed(app, f);
|
|
67535
|
+
content = r.content;
|
|
67536
|
+
folded = foldedNote(app, f.path, r.hash, content, keep);
|
|
67537
|
+
}
|
|
67538
|
+
if (!has2(folded)) continue;
|
|
67539
|
+
if (content === void 0) {
|
|
67540
|
+
const r = await readHashed(app, f);
|
|
67541
|
+
content = r.content;
|
|
67542
|
+
if (r.hash !== folded.hash && !has2(folded = foldedNote(app, f.path, r.hash, content, keep))) continue;
|
|
67543
|
+
}
|
|
67544
|
+
found.push({ path: f.path, score: scoreOf2(folded, words), folded, content });
|
|
67545
|
+
}
|
|
67546
|
+
if (present2) forgetFolded(app, present2);
|
|
67547
|
+
found.sort((x, y) => y.score - x.score || x.path.localeCompare(y.path));
|
|
67548
|
+
const results = found.slice(0, MAX_RESULTS).map((r) => ({ path: r.path, score: r.score, matches: matchesOf(r.folded.text, r.content, words, ctx) }));
|
|
67549
|
+
return { query, results, total: found.length };
|
|
67550
|
+
}
|
|
67551
|
+
|
|
67552
|
+
// src/core/note-search.ts
|
|
67553
|
+
init_message();
|
|
67554
|
+
var K12 = 1.2;
|
|
67555
|
+
var B2 = 0.75;
|
|
67556
|
+
var NAME = 3;
|
|
67557
|
+
var FRONT_MATTER = 1;
|
|
67558
|
+
var PAIR = 0.75;
|
|
67559
|
+
var RRF_K2 = 60;
|
|
67560
|
+
var FUSION_DEPTH2 = 50;
|
|
67561
|
+
var MEANING_DEPTH = 100;
|
|
67562
|
+
var SNIPPET = 200;
|
|
67563
|
+
var SEARCH_GUIDE = (hint) => guide(hint, "search");
|
|
67564
|
+
function searchTerms(text5) {
|
|
67565
|
+
return (searchFold(text5).match(/[\p{L}\p{N}]+/gu) ?? []).filter((w) => w.length > 1 || new RegExp("\\p{N}", "u").test(w));
|
|
67566
|
+
}
|
|
67567
|
+
function parseSearchQuery(query) {
|
|
67568
|
+
const words = [];
|
|
67569
|
+
const required2 = [];
|
|
67570
|
+
const excluded = [];
|
|
67571
|
+
const quoted3 = query.replace(/[“”„«»]/g, '"');
|
|
67572
|
+
for (const m of quoted3.matchAll(/([+-]?)(?:"([^"]*)"?|([^\s"]+))/g)) {
|
|
67573
|
+
const [, sign, phrase, word] = m;
|
|
67574
|
+
if (word !== void 0 && (sign === "" || word.startsWith("-"))) {
|
|
67575
|
+
words.push(`${sign}${word}`);
|
|
67576
|
+
continue;
|
|
67577
|
+
}
|
|
67578
|
+
const content = phrase ?? word;
|
|
67579
|
+
const terms = searchTerms(content);
|
|
67580
|
+
if (sign === "-") {
|
|
67581
|
+
if (terms.length > 0) excluded.push(terms);
|
|
67582
|
+
continue;
|
|
67583
|
+
}
|
|
67584
|
+
words.push(content);
|
|
67585
|
+
if (terms.length > 0) required2.push(terms);
|
|
67586
|
+
}
|
|
67587
|
+
return { text: words.join(" "), required: required2, excluded };
|
|
67588
|
+
}
|
|
67589
|
+
function field2(text5) {
|
|
67590
|
+
const terms = searchTerms(text5);
|
|
67591
|
+
const counts = /* @__PURE__ */ new Map();
|
|
67592
|
+
for (const t of terms) counts.set(t, (counts.get(t) ?? 0) + 1);
|
|
67593
|
+
return { terms, counts };
|
|
67594
|
+
}
|
|
67595
|
+
function occurrences2(f, terms) {
|
|
67596
|
+
if (terms.length === 1) return f.counts.get(terms[0]) ?? 0;
|
|
67597
|
+
let n = 0;
|
|
67598
|
+
for (let i = 0; i + terms.length <= f.terms.length; i++) if (terms.every((s, j) => f.terms[i + j] === s)) n++;
|
|
67599
|
+
return n;
|
|
67600
|
+
}
|
|
67601
|
+
function adjacent2(f, a, b) {
|
|
67602
|
+
for (let i = 0; i + 1 < f.terms.length; i++) if (f.terms[i] === a && f.terms[i + 1] === b) return true;
|
|
67603
|
+
return false;
|
|
67604
|
+
}
|
|
67605
|
+
function linkText(text5) {
|
|
67606
|
+
return text5.replace(/\[\[([^\]|#]*)(?:#[^\]|]*)?(?:\|([^\]]*))?\]\]/g, (_, target, alias) => alias ?? target.split("/").pop());
|
|
67607
|
+
}
|
|
67608
|
+
function splitNote(content) {
|
|
67609
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(content);
|
|
67610
|
+
return m ? { frontMatter: m[1], body: content.slice(m[0].length) } : { frontMatter: null, body: content };
|
|
67611
|
+
}
|
|
67612
|
+
function frontMatterValues(yaml, tableKey) {
|
|
67613
|
+
const skip = /* @__PURE__ */ new Set(["id", tableKey]);
|
|
67614
|
+
const out2 = [];
|
|
67615
|
+
let key2 = null;
|
|
67616
|
+
for (const line of yaml.split(/\r?\n/)) {
|
|
67617
|
+
const entry = /^([^\s:#-][^:]*):\s*(.*)$/.exec(line);
|
|
67618
|
+
if (entry) {
|
|
67619
|
+
key2 = entry[1].trim().replace(/^["']|["']$/g, "");
|
|
67620
|
+
if (!skip.has(key2) && entry[2].trim() !== "") out2.push(linkText(entry[2].trim()));
|
|
67621
|
+
continue;
|
|
67622
|
+
}
|
|
67623
|
+
if (key2 !== null && !skip.has(key2) && line.trim() !== "") out2.push(linkText(line.trim().replace(/^-\s*/, "")));
|
|
67624
|
+
}
|
|
67625
|
+
return out2;
|
|
67626
|
+
}
|
|
67627
|
+
function fieldsOf(path23, hash, content, tableKey) {
|
|
67628
|
+
const { frontMatter, body } = splitNote(content);
|
|
67629
|
+
const lines = frontMatter === null ? [] : frontMatterValues(frontMatter, tableKey);
|
|
67630
|
+
const name = path23.split("/").pop().replace(/\.md$/i, "");
|
|
67631
|
+
return { hash, name: field2(name), frontMatter: field2(lines.join("\n")), body: field2(linkText(body)), frontMatterLines: lines, bodyText: body };
|
|
67632
|
+
}
|
|
67633
|
+
var SEARCH_TERMS_CACHE = { maxTerms: 8e6 };
|
|
67634
|
+
var KEPT2 = /* @__PURE__ */ new WeakMap();
|
|
67635
|
+
var sizeOf = (n) => n.name.terms.length + n.frontMatter.terms.length + n.body.terms.length;
|
|
67636
|
+
function keptNotes(app) {
|
|
67637
|
+
let kept2 = KEPT2.get(app);
|
|
67638
|
+
if (!kept2) KEPT2.set(app, kept2 = { notes: /* @__PURE__ */ new Map(), terms: 0 });
|
|
67639
|
+
return kept2;
|
|
67640
|
+
}
|
|
67641
|
+
async function noteFields(app, f, tableKey) {
|
|
67642
|
+
const kept2 = keptNotes(app);
|
|
67643
|
+
const old = kept2.notes.get(f.path);
|
|
67644
|
+
if (old && old.hash === await readHash(app, f)) return old;
|
|
67645
|
+
const { content, hash } = await readHashed(app, f);
|
|
67646
|
+
const n = fieldsOf(f.path, hash, content, tableKey);
|
|
67647
|
+
if (old) {
|
|
67648
|
+
kept2.terms += sizeOf(n) - sizeOf(old);
|
|
67649
|
+
kept2.notes.set(f.path, n);
|
|
67650
|
+
} else if (kept2.terms + sizeOf(n) <= SEARCH_TERMS_CACHE.maxTerms) {
|
|
67651
|
+
kept2.terms += sizeOf(n);
|
|
67652
|
+
kept2.notes.set(f.path, n);
|
|
67653
|
+
}
|
|
67654
|
+
return n;
|
|
67655
|
+
}
|
|
67656
|
+
function forgetGone(app, present2) {
|
|
67657
|
+
const kept2 = keptNotes(app);
|
|
67658
|
+
for (const [path23, n] of kept2.notes) {
|
|
67659
|
+
if (present2.has(path23)) continue;
|
|
67660
|
+
kept2.notes.delete(path23);
|
|
67661
|
+
kept2.terms -= sizeOf(n);
|
|
67662
|
+
}
|
|
67663
|
+
}
|
|
67664
|
+
function allows2(n, q) {
|
|
67665
|
+
const holds = (terms) => occurrences2(n.name, terms) + occurrences2(n.frontMatter, terms) + occurrences2(n.body, terms) > 0;
|
|
67666
|
+
return q.required.every(holds) && !q.excluded.some(holds);
|
|
67667
|
+
}
|
|
67668
|
+
var wantTable = (tables, table) => !tables || tables.length === 0 || table !== null && tables.some((x) => table === x || table.endsWith(`.${x}`));
|
|
67669
|
+
function headingAt(body, at) {
|
|
67670
|
+
let heading = "";
|
|
67671
|
+
for (const m of body.slice(0, at).matchAll(/^#{1,6}[ \t]+(.+?)[ \t#]*$/gm)) heading = m[1];
|
|
67672
|
+
return heading;
|
|
67673
|
+
}
|
|
67674
|
+
function around(text5, at) {
|
|
67675
|
+
const start = Math.max(0, at - 60);
|
|
67676
|
+
const cut2 = text5.slice(start, start + SNIPPET).replace(/\s+/g, " ").trim();
|
|
67677
|
+
return `${start > 0 ? "\u2026" : ""}${cut2}${start + SNIPPET < text5.length ? "\u2026" : ""}`;
|
|
67678
|
+
}
|
|
67679
|
+
function firstMatch(text5, terms) {
|
|
67680
|
+
for (const m of text5.matchAll(/[\p{L}\p{M}\p{N}]+/gu)) if (searchTerms(m[0]).some((t) => terms.has(t))) return m.index;
|
|
67681
|
+
return -1;
|
|
67682
|
+
}
|
|
67683
|
+
function excerptOf(n, terms) {
|
|
67684
|
+
const prose = n.bodyText.split("\n").filter((l) => !/^(#{1,6}\s|\s*```|\s*~~~)/.test(l)).join(" ").replace(/\s+/g, " ").trim();
|
|
67685
|
+
const at = firstMatch(n.bodyText, terms);
|
|
67686
|
+
if (at >= 0) return { heading: headingAt(n.bodyText, at), snippet: around(prose, Math.max(0, firstMatch(prose, terms))) };
|
|
67687
|
+
const line = n.frontMatterLines.find((l) => firstMatch(l, terms) >= 0);
|
|
67688
|
+
if (line !== void 0) return { heading: "", snippet: around(line, 0) };
|
|
67689
|
+
return { heading: "", snippet: around(prose, 0) };
|
|
67690
|
+
}
|
|
67691
|
+
function rankByWords(notes, corpus, q, terms) {
|
|
67692
|
+
const df = terms.map((t) => notes.filter((n) => occurrences2(n.fields.name, [t]) + occurrences2(n.fields.frontMatter, [t]) + occurrences2(n.fields.body, [t]) > 0).length);
|
|
67693
|
+
const idf = df.map((d) => Math.log(1 + (corpus - d + 0.5) / (d + 0.5)));
|
|
67694
|
+
const avg = notes.length === 0 ? 1 : Math.max(1, notes.reduce((s, n) => s + n.fields.body.terms.length, 0) / notes.length);
|
|
67695
|
+
const out2 = [];
|
|
67696
|
+
for (const n of notes) {
|
|
67697
|
+
if (!allows2(n.fields, q)) continue;
|
|
67698
|
+
const { name, frontMatter, body } = n.fields;
|
|
67699
|
+
let score = 0;
|
|
67700
|
+
let matched = 0;
|
|
67701
|
+
terms.forEach((t, i) => {
|
|
67702
|
+
const w = idf[i];
|
|
67703
|
+
const count2 = body.counts.get(t) ?? 0;
|
|
67704
|
+
const s = (count2 > 0 ? w * count2 * (K12 + 1) / (count2 + K12 * (1 - B2 + B2 * body.terms.length / avg)) : 0) + (name.counts.has(t) ? NAME * w : 0) + (frontMatter.counts.has(t) ? FRONT_MATTER * w : 0);
|
|
67705
|
+
if (s > 0) matched++;
|
|
67706
|
+
score += s;
|
|
67707
|
+
});
|
|
67708
|
+
if (matched === 0) continue;
|
|
67709
|
+
for (let i = 0; i + 1 < terms.length; i++) {
|
|
67710
|
+
const [a, b] = [terms[i], terms[i + 1]];
|
|
67711
|
+
if (adjacent2(name, a, b) || adjacent2(frontMatter, a, b) || adjacent2(body, a, b)) score += PAIR * Math.min(idf[i], idf[i + 1]);
|
|
67712
|
+
}
|
|
67713
|
+
score *= (matched / terms.length) ** 2;
|
|
67714
|
+
out2.push({ path: n.path, score, fields: n.fields });
|
|
67715
|
+
}
|
|
67716
|
+
return out2.sort((x, y) => y.score - x.score || x.path.localeCompare(y.path));
|
|
67717
|
+
}
|
|
67718
|
+
var HIDDEN2 = /(^|\/)\./;
|
|
67719
|
+
async function searchVault(app, settings, input, semantic) {
|
|
67720
|
+
if (typeof input.query !== "string" || input.query.trim() === "") throw new OperationInputError(`"query" is required`, SEARCH_GUIDE(`Give a few words, for example search({query: "pasta al pomodoro"}).`));
|
|
67721
|
+
await loadMetastore(app, settings);
|
|
67722
|
+
const q = parseSearchQuery(input.query);
|
|
67723
|
+
const terms = [...new Set(searchTerms(q.text))];
|
|
67724
|
+
if (terms.length === 0) {
|
|
67725
|
+
throw new OperationInputError(
|
|
67726
|
+
"search: the query has no words to look for",
|
|
67727
|
+
SEARCH_GUIDE(`Give at least one word to rank by: "-word" only leaves notes out, and single letters are not searched. Example: search({query: "uovo -latte"}).`)
|
|
67728
|
+
);
|
|
67729
|
+
}
|
|
67730
|
+
const limit = Math.min(Math.max(1, Math.floor(input.limit ?? 10)), 100);
|
|
67731
|
+
const folder = input.folder?.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
67732
|
+
const scope = currentReadScope();
|
|
67733
|
+
if (scope) {
|
|
67734
|
+
const model = cachedMetastore(app, settings);
|
|
67735
|
+
for (const name of input.tables ?? []) {
|
|
67736
|
+
const full = model ? rowTableFull(model, name) : null;
|
|
67737
|
+
if (full !== null) assertTableReadable(full);
|
|
67738
|
+
}
|
|
67739
|
+
}
|
|
67740
|
+
const files = app.vault.getMarkdownFiles();
|
|
67741
|
+
const corpus = /* @__PURE__ */ new Map();
|
|
67742
|
+
for (const f of files) {
|
|
67743
|
+
if (HIDDEN2.test(f.path)) continue;
|
|
67744
|
+
if (folder && !f.path.startsWith(`${folder}/`)) continue;
|
|
67745
|
+
if (scope && !pathReadable(app, settings, f.path, scope)) continue;
|
|
67746
|
+
const table = input.tables && input.tables.length > 0 ? tableOfPath(app, settings, f.path) : null;
|
|
67747
|
+
if (!wantTable(input.tables, table)) continue;
|
|
67748
|
+
corpus.set(f.path, { file: f, table });
|
|
67749
|
+
}
|
|
67750
|
+
let read3 = corpus.values();
|
|
67751
|
+
if (terms.every(indexableWord)) {
|
|
67752
|
+
const named = /* @__PURE__ */ new Set();
|
|
67753
|
+
let all = false;
|
|
67754
|
+
for (const t of terms) {
|
|
67755
|
+
const c = searchCandidates(app, [t]);
|
|
67756
|
+
if (c === null) {
|
|
67757
|
+
all = true;
|
|
67758
|
+
break;
|
|
67759
|
+
}
|
|
67760
|
+
for (const list4 of [c.hits, c.unsure]) for (const p of list4) named.add(p);
|
|
67761
|
+
}
|
|
67762
|
+
if (!all) read3 = [...named].map((p) => corpus.get(p)).filter((c) => c !== void 0);
|
|
67763
|
+
}
|
|
67764
|
+
const notes = [];
|
|
67765
|
+
for (const c of read3) notes.push({ path: c.file.path, fields: await noteFields(app, c.file, settings.tableKey) });
|
|
67766
|
+
forgetGone(app, new Set(files.map((f) => f.path)));
|
|
67767
|
+
const byWords = rankByWords(notes, corpus.size, q, terms);
|
|
67768
|
+
const termSet = new Set(terms);
|
|
67769
|
+
const tableOf = (path23) => corpus.get(path23)?.table ?? tableOfPath(app, settings, path23);
|
|
67770
|
+
const words = (r, score, matchedBy) => {
|
|
67771
|
+
const table = tableOf(r.path);
|
|
67772
|
+
return { path: r.path, ...table ? { table } : {}, score, ...excerptOf(r.fields, termSet), matched_by: matchedBy };
|
|
67773
|
+
};
|
|
67774
|
+
if (!semantic || !settings.semanticEnabled) {
|
|
67775
|
+
return { query: input.query, results: byWords.slice(0, limit).map((r) => words(r, Math.round(r.score * 100) / 100, ["words"])), meaning: "off" };
|
|
67776
|
+
}
|
|
67777
|
+
let meaningHits;
|
|
67778
|
+
try {
|
|
67779
|
+
const r = await semantic.search({
|
|
67780
|
+
query: q.text,
|
|
67781
|
+
limit: MEANING_DEPTH,
|
|
67782
|
+
...input.tables && input.tables.length > 0 ? { tables: input.tables } : {},
|
|
67783
|
+
...folder ? { folder } : {},
|
|
67784
|
+
...input.minScore !== void 0 ? { minScore: input.minScore } : {}
|
|
67785
|
+
});
|
|
67786
|
+
meaningHits = r.results;
|
|
67787
|
+
} catch (e) {
|
|
67788
|
+
if (e instanceof ReadDeniedError || e instanceof OperationInputError) throw e;
|
|
67789
|
+
return { query: input.query, results: byWords.slice(0, limit).map((r) => words(r, Math.round(r.score * 100) / 100, ["words"])), meaning: "failed", meaning_error: messageOf(e) };
|
|
67790
|
+
}
|
|
67791
|
+
const meaning = [];
|
|
67792
|
+
for (const h of meaningHits) {
|
|
67793
|
+
const c = corpus.get(h.path);
|
|
67794
|
+
if (!c) continue;
|
|
67795
|
+
const fields = await noteFields(app, c.file, settings.tableKey);
|
|
67796
|
+
if (!allows2(fields, q)) continue;
|
|
67797
|
+
meaning.push({ path: h.path, heading: h.heading, snippet: h.snippet, fields });
|
|
67798
|
+
if (meaning.length === FUSION_DEPTH2) break;
|
|
67799
|
+
}
|
|
67800
|
+
const fused = /* @__PURE__ */ new Map();
|
|
67801
|
+
byWords.slice(0, FUSION_DEPTH2).forEach((r, i) => fused.set(r.path, { score: 1 / (RRF_K2 + i + 1), wordsRank: i + 1, ranked: r }));
|
|
67802
|
+
meaning.forEach((m, i) => {
|
|
67803
|
+
const f = fused.get(m.path) ?? { score: 0, wordsRank: Infinity };
|
|
67804
|
+
f.score += 1 / (RRF_K2 + i + 1);
|
|
67805
|
+
f.meaning = m;
|
|
67806
|
+
fused.set(m.path, f);
|
|
67807
|
+
});
|
|
67808
|
+
const results = [...fused.entries()].sort(([pa, a], [pb, b]) => b.score - a.score || a.wordsRank - b.wordsRank || pa.localeCompare(pb)).slice(0, limit).map(([path23, f]) => {
|
|
67809
|
+
const score = Math.round(f.score * 1e4) / 1e4;
|
|
67810
|
+
if (f.ranked) return words(f.ranked, score, f.meaning ? ["words", "meaning"] : ["words"]);
|
|
67811
|
+
const table = tableOf(path23);
|
|
67812
|
+
return { path: path23, ...table ? { table } : {}, score, heading: f.meaning.heading, snippet: f.meaning.snippet, matched_by: ["meaning"] };
|
|
67813
|
+
});
|
|
67814
|
+
return { query: input.query, results, meaning: "on" };
|
|
67815
|
+
}
|
|
67816
|
+
|
|
67817
|
+
// src/server/rest.ts
|
|
66616
67818
|
init_write_check();
|
|
66617
67819
|
init_query();
|
|
66618
67820
|
init_journal();
|
|
@@ -66890,6 +68092,7 @@ async function rowMovesSince(app, rows) {
|
|
|
66890
68092
|
// src/core/change-feed.ts
|
|
66891
68093
|
init_table_loader();
|
|
66892
68094
|
init_types_table();
|
|
68095
|
+
init_read_scope();
|
|
66893
68096
|
init_store();
|
|
66894
68097
|
init_vault_index();
|
|
66895
68098
|
init_telemetry();
|
|
@@ -67046,6 +68249,24 @@ function matches(ev, q) {
|
|
|
67046
68249
|
if (q.excludeActor && ev.actor?.harness === q.excludeActor) return false;
|
|
67047
68250
|
return true;
|
|
67048
68251
|
}
|
|
68252
|
+
function eventTable(app, settings, table) {
|
|
68253
|
+
if (table === null) return null;
|
|
68254
|
+
const model = cachedMetastore(app, settings);
|
|
68255
|
+
return (model ? rowTableFull(model, table) : null) ?? table;
|
|
68256
|
+
}
|
|
68257
|
+
function eventForScope(ev, scope, app, settings) {
|
|
68258
|
+
if (scope === null) return ev;
|
|
68259
|
+
if (ev.op === "tool_call") return ev.actor?.auth === scope.token ? ev : null;
|
|
68260
|
+
const table = eventTable(app, settings, ev.table);
|
|
68261
|
+
if (table === null || !tableReadable(table, scope)) return null;
|
|
68262
|
+
const shown = (path23) => path23 === ev.path || path23 === ev.oldPath || pathReadable(app, settings, path23, scope);
|
|
68263
|
+
const violations = ev.violations?.filter((v) => shown(v.path));
|
|
68264
|
+
const cause = ev.cause?.filter((c) => pathReadable(app, settings, c.path, scope));
|
|
68265
|
+
const cut2 = violations !== void 0 && violations.length < ev.violations.length;
|
|
68266
|
+
if (!cut2 && cause?.length === ev.cause?.length) return ev;
|
|
68267
|
+
const { violationsTotal, ...rest } = ev;
|
|
68268
|
+
return { ...rest, ...violations !== void 0 ? { violations } : {}, ...!cut2 && violationsTotal !== void 0 ? { violationsTotal } : {}, ...cause !== void 0 ? { cause } : {} };
|
|
68269
|
+
}
|
|
67049
68270
|
var STATE_SLICE = 2e3;
|
|
67050
68271
|
var ChangeFeed = class {
|
|
67051
68272
|
constructor(app, getSettings, retentionDays = 30) {
|
|
@@ -67715,9 +68936,14 @@ var ChangeFeed = class {
|
|
|
67715
68936
|
latest: broken.slice(0, limit).map((e) => ({ path: e.path, table: e.table, at: e.at, seq: e.seq, violations: count2(e), message: e.violations[0].message }))
|
|
67716
68937
|
};
|
|
67717
68938
|
}
|
|
67718
|
-
/**
|
|
67719
|
-
|
|
68939
|
+
/**
|
|
68940
|
+
* Events after `since` that match the query, as the read scope sees them ({@link eventForScope}:
|
|
68941
|
+
* by default the scope of the request that reads; delivery that runs on its own passes its own).
|
|
68942
|
+
*/
|
|
68943
|
+
async read(q = {}, scope = currentReadScope()) {
|
|
67720
68944
|
const since = q.since ?? 0;
|
|
68945
|
+
const s = this.getSettings();
|
|
68946
|
+
const visible = (ev) => eventForScope(ev, scope, this.app, s);
|
|
67721
68947
|
const limit = Math.min(Math.max(q.limit ?? 500, 1), 5e3);
|
|
67722
68948
|
let source;
|
|
67723
68949
|
if (this.buffer.length > 0 && since >= this.buffer[0].seq - 1) source = this.buffer.slice(firstAfter(this.buffer, since));
|
|
@@ -67735,7 +68961,7 @@ var ChangeFeed = class {
|
|
|
67735
68961
|
if (ev.seq > max) max = ev.seq;
|
|
67736
68962
|
if (ev.seq <= since) continue;
|
|
67737
68963
|
source.push(ev);
|
|
67738
|
-
if (plain && matches(ev, q)) matched++;
|
|
68964
|
+
if (plain && matches(ev, q) && (scope === null || visible(ev) !== null)) matched++;
|
|
67739
68965
|
}
|
|
67740
68966
|
if (events2.length > 0) this.fileMaxSeq.set(f, max);
|
|
67741
68967
|
if (plain && matched >= limit) break;
|
|
@@ -67751,7 +68977,10 @@ var ChangeFeed = class {
|
|
|
67751
68977
|
for (let i = 0; i < source.length; i++) {
|
|
67752
68978
|
const ev = source[i];
|
|
67753
68979
|
last = ev.seq;
|
|
67754
|
-
if (kept2[i] && rowMatches(ev))
|
|
68980
|
+
if (kept2[i] && rowMatches(ev)) {
|
|
68981
|
+
const shown = visible(ev);
|
|
68982
|
+
if (shown) events.push(shown);
|
|
68983
|
+
}
|
|
67755
68984
|
if (events.length >= limit) break;
|
|
67756
68985
|
}
|
|
67757
68986
|
if (events.length < limit) last = Math.max(last, Math.min(this.seq, source.length ? source[source.length - 1].seq : since));
|
|
@@ -67795,10 +69024,10 @@ var ChangeFeed = class {
|
|
|
67795
69024
|
for (const w of [...this.waiters]) w();
|
|
67796
69025
|
}
|
|
67797
69026
|
/** Like `read`, but waits up to `waitMs` for a matching event when there is none yet (long poll). */
|
|
67798
|
-
async wait(q, waitMs) {
|
|
69027
|
+
async wait(q, waitMs, scope = currentReadScope()) {
|
|
67799
69028
|
const started = Date.now();
|
|
67800
69029
|
const deadline = started + Math.min(Math.max(waitMs, 0), 6e4);
|
|
67801
|
-
let page2 = await this.read(q);
|
|
69030
|
+
let page2 = await this.read(q, scope);
|
|
67802
69031
|
let since = page2.last;
|
|
67803
69032
|
while (page2.events.length === 0 && Date.now() < deadline && this.waitsEnded < started) {
|
|
67804
69033
|
await new Promise((resolve) => {
|
|
@@ -67811,7 +69040,7 @@ var ChangeFeed = class {
|
|
|
67811
69040
|
}
|
|
67812
69041
|
this.waiters.add(done);
|
|
67813
69042
|
});
|
|
67814
|
-
page2 = await this.read({ ...q, since });
|
|
69043
|
+
page2 = await this.read({ ...q, since }, scope);
|
|
67815
69044
|
since = page2.last;
|
|
67816
69045
|
}
|
|
67817
69046
|
return page2;
|
|
@@ -67842,246 +69071,18 @@ function changedTables2(prev, next) {
|
|
|
67842
69071
|
return catalogTables(after).filter(({ ref: ref2, entry }) => was.get(ref2.full) !== JSON.stringify(entry)).map(({ ref: ref2 }) => ref2.full).sort();
|
|
67843
69072
|
}
|
|
67844
69073
|
|
|
67845
|
-
// src/core/vault-read.ts
|
|
67846
|
-
init_file_cache();
|
|
67847
|
-
init_database();
|
|
67848
|
-
init_datetime();
|
|
67849
|
-
init_hints();
|
|
67850
|
-
var VaultPathNotFoundError = class extends GuidedError {
|
|
67851
|
-
constructor() {
|
|
67852
|
-
super(...arguments);
|
|
67853
|
-
this.name = "VaultPathNotFoundError";
|
|
67854
|
-
}
|
|
67855
|
-
};
|
|
67856
|
-
var VaultPathError = class extends GuidedError {
|
|
67857
|
-
constructor() {
|
|
67858
|
-
super(...arguments);
|
|
67859
|
-
this.name = "VaultPathError";
|
|
67860
|
-
}
|
|
67861
|
-
};
|
|
67862
|
-
var FIND_NOTE = guide("Give the vault path of the note, with .md: list_folder lists a folder, search_notes finds notes by their words.", "readNote");
|
|
67863
|
-
function clean(path23) {
|
|
67864
|
-
return path23.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
67865
|
-
}
|
|
67866
|
-
async function readNote(app, settings, path23) {
|
|
67867
|
-
const rel = clean(path23);
|
|
67868
|
-
if (rel === "" || /\/$/.test(path23.trim())) {
|
|
67869
|
-
throw new VaultPathError("path must name a file, not a folder", guide('Pass the vault path of a note, e.g. "Films/Metropolis.md"; list_folder lists a folder.', "readNote"));
|
|
67870
|
-
}
|
|
67871
|
-
const f = app.vault.getAbstractFileByPath(rel);
|
|
67872
|
-
if (!f || !("extension" in f)) {
|
|
67873
|
-
if (f) throw new VaultPathError(`${rel} is a folder: list_folder lists it`, FIND_NOTE);
|
|
67874
|
-
throw new VaultPathNotFoundError(`no file ${rel} in the vault`, FIND_NOTE);
|
|
67875
|
-
}
|
|
67876
|
-
const file = f;
|
|
67877
|
-
const { content } = await readHashed(app, file);
|
|
67878
|
-
const cache = app.metadataCache.getFileCache(file);
|
|
67879
|
-
const { position: _position, ...frontMatter } = cache?.frontmatter ?? {};
|
|
67880
|
-
const fmTags = frontMatter.tags;
|
|
67881
|
-
const tags = [
|
|
67882
|
-
...(cache?.tags ?? []).map((t) => t.tag.replace(/^#/, "")),
|
|
67883
|
-
...Array.isArray(fmTags) ? fmTags.map(String) : typeof fmTags === "string" ? [fmTags] : []
|
|
67884
|
-
];
|
|
67885
|
-
const stat = file.stat;
|
|
67886
|
-
const tz = (await readDatabaseConfig(app, settings)).timezone;
|
|
67887
|
-
const at = (ms) => typeof ms === "number" && ms > 0 ? isoAt(ms, tz) : null;
|
|
67888
|
-
return {
|
|
67889
|
-
path: file.path,
|
|
67890
|
-
content,
|
|
67891
|
-
front_matter: frontMatter,
|
|
67892
|
-
tags: [...new Set(tags)],
|
|
67893
|
-
modified_at: at(stat?.mtime),
|
|
67894
|
-
created_at: at(stat?.ctime),
|
|
67895
|
-
size: typeof stat?.size === "number" ? stat.size : null
|
|
67896
|
-
};
|
|
67897
|
-
}
|
|
67898
|
-
function listFolder(app, folder) {
|
|
67899
|
-
const rel = clean(folder);
|
|
67900
|
-
const node2 = rel === "" ? app.vault.getRoot?.() ?? app.vault.getAbstractFileByPath("/") : app.vault.getAbstractFileByPath(rel);
|
|
67901
|
-
if (!node2 || !("children" in node2)) {
|
|
67902
|
-
if (node2) throw new VaultPathError(`${rel} is a file: read_note reads it`, guide('Pass a folder of the vault ("" is the vault root); read_note reads a file.', "readNote"));
|
|
67903
|
-
throw new VaultPathNotFoundError(`no folder ${rel} in the vault`, guide('list_folder with folder "" lists the vault root; pass one of its folders.', "readNote"));
|
|
67904
|
-
}
|
|
67905
|
-
const folders = [];
|
|
67906
|
-
const files = [];
|
|
67907
|
-
for (const child of node2.children) {
|
|
67908
|
-
const name = child.name ?? child.path.split("/").pop();
|
|
67909
|
-
if (name.startsWith(".")) continue;
|
|
67910
|
-
const path23 = rel === "" ? name : `${rel}/${name}`;
|
|
67911
|
-
("children" in child ? folders : files).push(path23);
|
|
67912
|
-
}
|
|
67913
|
-
return { folder: rel, folders: folders.sort(), files: files.sort() };
|
|
67914
|
-
}
|
|
67915
|
-
function fold5(s) {
|
|
67916
|
-
return s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
|
|
67917
|
-
}
|
|
67918
|
-
var searchFold = fold5;
|
|
67919
|
-
var ONE_TO_ONE = /^[\u0000-˿]*$/;
|
|
67920
|
-
var BMP_FOLDED = new Uint8Array(65536).fill(255);
|
|
67921
|
-
function foldedLength(cp, ch) {
|
|
67922
|
-
if (cp > 65535) return fold5(ch).length;
|
|
67923
|
-
let n = BMP_FOLDED[cp];
|
|
67924
|
-
if (n === 255) BMP_FOLDED[cp] = n = fold5(ch).length;
|
|
67925
|
-
return n;
|
|
67926
|
-
}
|
|
67927
|
-
function foldedOffsets(content) {
|
|
67928
|
-
if (ONE_TO_ONE.test(content)) return null;
|
|
67929
|
-
const starts = [];
|
|
67930
|
-
const ends = [];
|
|
67931
|
-
let aligned = true;
|
|
67932
|
-
for (let o = 0; o < content.length; ) {
|
|
67933
|
-
const cp = content.codePointAt(o);
|
|
67934
|
-
const width = cp > 65535 ? 2 : 1;
|
|
67935
|
-
const n = cp < 768 ? 1 : foldedLength(cp, content.slice(o, o + width));
|
|
67936
|
-
if (n !== width) aligned = false;
|
|
67937
|
-
if (n === 0 && ends.length > 0) {
|
|
67938
|
-
for (let k = ends.length - 1; k >= 0 && ends[k] === o; k--) ends[k] = o + width;
|
|
67939
|
-
}
|
|
67940
|
-
for (let k = 0; k < n; k++) {
|
|
67941
|
-
starts.push(o);
|
|
67942
|
-
ends.push(o + width);
|
|
67943
|
-
}
|
|
67944
|
-
o += width;
|
|
67945
|
-
}
|
|
67946
|
-
if (aligned) return null;
|
|
67947
|
-
return {
|
|
67948
|
-
// a match covers at least one folded character: its start and its last character are in the text
|
|
67949
|
-
start: (i) => starts[i],
|
|
67950
|
-
end: (i) => ends[i - 1]
|
|
67951
|
-
};
|
|
67952
|
-
}
|
|
67953
|
-
var SEARCH_CACHE = { maxChars: 64 * 1024 * 1024 };
|
|
67954
|
-
var FOLDED = /* @__PURE__ */ new WeakMap();
|
|
67955
|
-
function foldedCache(app) {
|
|
67956
|
-
let cache = FOLDED.get(app);
|
|
67957
|
-
if (!cache) FOLDED.set(app, cache = { notes: /* @__PURE__ */ new Map(), chars: 0 });
|
|
67958
|
-
return cache;
|
|
67959
|
-
}
|
|
67960
|
-
function keptFolded(app, path23, hash) {
|
|
67961
|
-
const kept2 = foldedCache(app).notes.get(path23);
|
|
67962
|
-
return kept2 && kept2.hash === hash ? kept2 : void 0;
|
|
67963
|
-
}
|
|
67964
|
-
function foldedNote(app, path23, hash, content, keep) {
|
|
67965
|
-
const note = { hash, text: fold5(content), name: fold5(path23) };
|
|
67966
|
-
if (!keep) return note;
|
|
67967
|
-
const cache = foldedCache(app);
|
|
67968
|
-
const kept2 = cache.notes.get(path23);
|
|
67969
|
-
const size = note.text.length + note.name.length;
|
|
67970
|
-
if (kept2) {
|
|
67971
|
-
cache.chars += size - kept2.text.length - kept2.name.length;
|
|
67972
|
-
cache.notes.set(path23, note);
|
|
67973
|
-
} else if (cache.chars + size <= SEARCH_CACHE.maxChars) {
|
|
67974
|
-
cache.chars += size;
|
|
67975
|
-
cache.notes.set(path23, note);
|
|
67976
|
-
}
|
|
67977
|
-
return note;
|
|
67978
|
-
}
|
|
67979
|
-
function forgetFolded(app, present2) {
|
|
67980
|
-
const cache = foldedCache(app);
|
|
67981
|
-
for (const [path23, note] of cache.notes) {
|
|
67982
|
-
if (present2.has(path23)) continue;
|
|
67983
|
-
cache.notes.delete(path23);
|
|
67984
|
-
cache.chars -= note.text.length + note.name.length;
|
|
67985
|
-
}
|
|
67986
|
-
}
|
|
67987
|
-
var SEARCH_SOURCES = /* @__PURE__ */ new WeakMap();
|
|
67988
|
-
function setSearchSource(app, source) {
|
|
67989
|
-
if (source) SEARCH_SOURCES.set(app, source);
|
|
67990
|
-
else SEARCH_SOURCES.delete(app);
|
|
67991
|
-
}
|
|
67992
|
-
var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
67993
|
-
function indexableWord(w) {
|
|
67994
|
-
if (w.indexOf("\0") >= 0 || LONE_SURROGATE.test(w)) return false;
|
|
67995
|
-
let n = 0;
|
|
67996
|
-
for (const _ of w) if (++n >= 3) return true;
|
|
67997
|
-
return false;
|
|
67998
|
-
}
|
|
67999
|
-
var HIDDEN = /(^|\/)\./;
|
|
68000
|
-
var MAX_RESULTS = 200;
|
|
68001
|
-
function scoreOf2(n, words) {
|
|
68002
|
-
let hits = 0;
|
|
68003
|
-
for (const w of words) {
|
|
68004
|
-
for (let i = n.text.indexOf(w); i >= 0 && hits < 20; i = n.text.indexOf(w, i + w.length)) hits++;
|
|
68005
|
-
if (hits >= 20) break;
|
|
68006
|
-
}
|
|
68007
|
-
return hits + words.filter((w) => n.name.includes(w)).length * 5;
|
|
68008
|
-
}
|
|
68009
|
-
function matchesOf(text5, content, words, ctx) {
|
|
68010
|
-
const spans = [];
|
|
68011
|
-
for (const w of words) {
|
|
68012
|
-
for (let i = text5.indexOf(w); i >= 0; i = text5.indexOf(w, i + w.length)) spans.push({ start: i, end: i + w.length });
|
|
68013
|
-
}
|
|
68014
|
-
spans.sort((x, y) => x.start - y.start || y.end - x.end);
|
|
68015
|
-
const merged = [];
|
|
68016
|
-
for (const sp of spans) {
|
|
68017
|
-
const last = merged[merged.length - 1];
|
|
68018
|
-
if (last && (sp.start <= last.end || !/[\p{L}\p{N}]/u.test(text5.slice(last.end, sp.start)))) last.end = Math.max(last.end, sp.end);
|
|
68019
|
-
else merged.push({ ...sp });
|
|
68020
|
-
}
|
|
68021
|
-
const at = merged.length > 0 ? foldedOffsets(content) : null;
|
|
68022
|
-
return merged.slice(0, 20).map((m) => {
|
|
68023
|
-
const start = at ? at.start(m.start) : m.start;
|
|
68024
|
-
const end = at ? at.end(m.end) : m.end;
|
|
68025
|
-
return { start, end, context: content.slice(Math.max(0, start - ctx), end + ctx) };
|
|
68026
|
-
});
|
|
68027
|
-
}
|
|
68028
|
-
function candidateFiles(app, c) {
|
|
68029
|
-
const out2 = [];
|
|
68030
|
-
const seen = /* @__PURE__ */ new Set();
|
|
68031
|
-
for (const list4 of [c.hits, c.unsure]) {
|
|
68032
|
-
for (const p of list4) {
|
|
68033
|
-
if (seen.has(p)) continue;
|
|
68034
|
-
seen.add(p);
|
|
68035
|
-
const f = app.vault.getAbstractFileByPath(p);
|
|
68036
|
-
if (f && "extension" in f && f.extension === "md") out2.push(f);
|
|
68037
|
-
}
|
|
68038
|
-
}
|
|
68039
|
-
return out2;
|
|
68040
|
-
}
|
|
68041
|
-
async function searchNotes(app, query, contextLength = 100) {
|
|
68042
|
-
const words = [...new Set(fold5(query).split(/\s+/).filter(Boolean))];
|
|
68043
|
-
if (words.length === 0) return { query, results: [], total: 0 };
|
|
68044
|
-
const ctx = Number.isFinite(contextLength) && contextLength >= 0 ? Math.min(Math.floor(contextLength), 1e3) : 100;
|
|
68045
|
-
const indexable = words.filter(indexableWord);
|
|
68046
|
-
const narrowed = indexable.length > 0 ? SEARCH_SOURCES.get(app)?.candidates(indexable) ?? null : null;
|
|
68047
|
-
const files = narrowed ? candidateFiles(app, narrowed) : app.vault.getMarkdownFiles();
|
|
68048
|
-
const keep = narrowed === null;
|
|
68049
|
-
const present2 = keep ? /* @__PURE__ */ new Set() : null;
|
|
68050
|
-
const has2 = (n) => words.every((w) => n.text.includes(w) || n.name.includes(w));
|
|
68051
|
-
const found = [];
|
|
68052
|
-
for (const f of files) {
|
|
68053
|
-
if (HIDDEN.test(f.path)) continue;
|
|
68054
|
-
present2?.add(f.path);
|
|
68055
|
-
let content;
|
|
68056
|
-
let folded = keptFolded(app, f.path, await readHash(app, f));
|
|
68057
|
-
if (!folded) {
|
|
68058
|
-
const r = await readHashed(app, f);
|
|
68059
|
-
content = r.content;
|
|
68060
|
-
folded = foldedNote(app, f.path, r.hash, content, keep);
|
|
68061
|
-
}
|
|
68062
|
-
if (!has2(folded)) continue;
|
|
68063
|
-
if (content === void 0) {
|
|
68064
|
-
const r = await readHashed(app, f);
|
|
68065
|
-
content = r.content;
|
|
68066
|
-
if (r.hash !== folded.hash && !has2(folded = foldedNote(app, f.path, r.hash, content, keep))) continue;
|
|
68067
|
-
}
|
|
68068
|
-
found.push({ path: f.path, score: scoreOf2(folded, words), folded, content });
|
|
68069
|
-
}
|
|
68070
|
-
if (present2) forgetFolded(app, present2);
|
|
68071
|
-
found.sort((x, y) => y.score - x.score || x.path.localeCompare(y.path));
|
|
68072
|
-
const results = found.slice(0, MAX_RESULTS).map((r) => ({ path: r.path, score: r.score, matches: matchesOf(r.folded.text, r.content, words, ctx) }));
|
|
68073
|
-
return { query, results, total: found.length };
|
|
68074
|
-
}
|
|
68075
|
-
|
|
68076
69074
|
// src/server/rest.ts
|
|
68077
69075
|
init_message();
|
|
68078
69076
|
init_hints();
|
|
68079
69077
|
init_guidance();
|
|
68080
69078
|
init_requests();
|
|
68081
69079
|
init_actor();
|
|
69080
|
+
init_read_scope();
|
|
69081
|
+
init_changeset_checks();
|
|
68082
69082
|
|
|
68083
69083
|
// src/server/token-routes.ts
|
|
68084
69084
|
init_catalog();
|
|
69085
|
+
init_hints();
|
|
68085
69086
|
var READ_OPERATIONS = new Set(OPERATIONS.filter((o) => o.annotations.readOnlyHint === true).map((o) => o.name));
|
|
68086
69087
|
var SCHEMA_OPERATIONS = new Set(Object.keys(OPERATION_KIND).filter((op2) => OPERATION_KIND[op2] === "schema"));
|
|
68087
69088
|
var ROW_WRITES = {
|
|
@@ -68095,14 +69096,25 @@ var ROW_WRITES = {
|
|
|
68095
69096
|
recompute_columns: ["table"],
|
|
68096
69097
|
align_relations: ["table"]
|
|
68097
69098
|
};
|
|
69099
|
+
var READ_TABLE_ARGS = {
|
|
69100
|
+
select_rows: ["table"],
|
|
69101
|
+
describe_table: ["table"],
|
|
69102
|
+
check_table: ["table"],
|
|
69103
|
+
infer_table: ["table"],
|
|
69104
|
+
list_link_targets: ["table"],
|
|
69105
|
+
table_changes: ["table"],
|
|
69106
|
+
describe_history: ["table"]
|
|
69107
|
+
};
|
|
68098
69108
|
var OWNER_OPERATIONS = /* @__PURE__ */ new Set(["create_webhook", "delete_webhook"]);
|
|
68099
69109
|
var READ_ROUTES = /* @__PURE__ */ new Set(["/manual/search/", "/tokens/me/", "/tokens/update/", "/extensions/", "/openapi/", "/openapi.json/", "/sessions/identify/", "/tool-calls/record/"]);
|
|
68100
69110
|
var OWNER_ADMIN_ROUTES = /* @__PURE__ */ new Set(["/tokens/", "/tokens/create/", "/tokens/update/", "/tokens/delete/", "/tokens/rotate/", "/extensions/record/", "/extensions/delete/", "/status/", "/stop/"]);
|
|
69111
|
+
var READ_SCOPE_UNSUPPORTED = "read permissions need Node's AsyncLocalStorage";
|
|
69112
|
+
var READ_SCOPE_UNSUPPORTED_HINT = guide(`This token has "read" patterns, which this runtime cannot enforce: run the engine on Node (datanotes serve), or have the owner pass "read": null to tokens/update/ for this token.`, "tokenRead");
|
|
68101
69113
|
|
|
68102
69114
|
// src/server/scheduler.ts
|
|
68103
69115
|
init_telemetry();
|
|
68104
69116
|
var FREE_WRITES = /* @__PURE__ */ new Set(["sync_semantic_index"]);
|
|
68105
|
-
var FREE_READS = /* @__PURE__ */ new Set(["semantic_search"]);
|
|
69117
|
+
var FREE_READS = /* @__PURE__ */ new Set(["semantic_search", "search"]);
|
|
68106
69118
|
var MAX_WRITES_AHEAD = 8;
|
|
68107
69119
|
var WRITE_SLOT = { maxMs: 6e4 };
|
|
68108
69120
|
function operationClass(op2, body = {}) {
|
|
@@ -68222,7 +69234,7 @@ var Forbidden = class extends GuidedError {
|
|
|
68222
69234
|
}
|
|
68223
69235
|
};
|
|
68224
69236
|
var FORBIDDEN_HINT = guide(
|
|
68225
|
-
'Named tokens read
|
|
69237
|
+
'Named tokens read the tables their "read" patterns name (every table without "read"); writes need a matching "write" pattern, schema changes "ddl": true, maintenance "maintain": true. Ask the owner to update the token (tokens/update/), or use a token that has the permission.',
|
|
68226
69238
|
"tokens"
|
|
68227
69239
|
);
|
|
68228
69240
|
function operationOf(route) {
|
|
@@ -68232,7 +69244,7 @@ function operationOf(route) {
|
|
|
68232
69244
|
function argumentsGuidance(op2, route) {
|
|
68233
69245
|
if (op2) return { hint: `Check the arguments of ${op2}: ${argumentsOf(op2)}.`, docs: toolDocs(op2) };
|
|
68234
69246
|
if (route?.startsWith("/tokens/")) {
|
|
68235
|
-
return guide(`The token routes take {"name"} and, to create or update one, "description" (text), "write" (table patterns), "ddl" and "maintain" (true or false), "autolink" (true or false: the default of its writes for automatic links).`, "manageTokens");
|
|
69247
|
+
return guide(`The token routes take {"name"} and, to create or update one, "description" (text), "read" (table patterns it may read; on update, null for every table), "write" (table patterns it may write, each one readable), "ddl" and "maintain" (true or false), "autolink" (true or false: the default of its writes for automatic links).`, "manageTokens");
|
|
68236
69248
|
}
|
|
68237
69249
|
return guide("Check the request's fields against the route in GET /datanotes/openapi.json.", "routes");
|
|
68238
69250
|
}
|
|
@@ -68286,9 +69298,54 @@ function runOpts(body) {
|
|
|
68286
69298
|
function kindsArg(body) {
|
|
68287
69299
|
return strings(body, "kinds")?.map(opOfKind);
|
|
68288
69300
|
}
|
|
68289
|
-
function
|
|
68290
|
-
|
|
68291
|
-
return { status:
|
|
69301
|
+
function writeResponse(r, app, settings) {
|
|
69302
|
+
const answer = redactWriteAnswer(r, app, settings);
|
|
69303
|
+
if (r.ok) return { status: 200, json: answer };
|
|
69304
|
+
return { status: writeStatus(r.failed.length > 0, r.rolled_back), json: { ...answer, ...refusalGuidance(answer) } };
|
|
69305
|
+
}
|
|
69306
|
+
function redactWriteAnswer(r, app, settings) {
|
|
69307
|
+
const scope = currentReadScope();
|
|
69308
|
+
if (!scope) return r;
|
|
69309
|
+
const hidden2 = (path23) => pathHidden(app, settings, path23, scope);
|
|
69310
|
+
const blocked = /* @__PURE__ */ new Set();
|
|
69311
|
+
const keep = (list4, pathOf) => list4.filter((x) => {
|
|
69312
|
+
const p = pathOf(x);
|
|
69313
|
+
if (!hidden2(p)) return true;
|
|
69314
|
+
blocked.add(p);
|
|
69315
|
+
return false;
|
|
69316
|
+
});
|
|
69317
|
+
const conflicts = keep(r.conflicts, (c) => c.path).map((c) => ({ path: c.path, message: c.message }));
|
|
69318
|
+
const violations = keep(r.violations, (v) => v.path).map((v) => ({ path: v.path, violations: v.violations }));
|
|
69319
|
+
const failed2 = keep(r.failed, (f) => f.path).map((f) => ({ path: f.path, error: f.error }));
|
|
69320
|
+
if (blocked.size > 0) {
|
|
69321
|
+
const n = blocked.size;
|
|
69322
|
+
conflicts.push({ path: "", message: `${n} ${n === 1 ? "row" : "rows"} this token may not read ${n === 1 ? "blocks" : "block"} this write` });
|
|
69323
|
+
}
|
|
69324
|
+
const links = r.links_added?.filter((l) => !hidden2(l.path) && !hidden2(l.target)).map((l) => ({ path: l.path, text: l.text, target: l.target, link: l.link }));
|
|
69325
|
+
const mentions = r.ambiguous_mentions?.filter((m) => !hidden2(m.path)).map((m) => ({ path: m.path, text: m.text, reason: m.reason, candidates: m.candidates.filter((c) => !hidden2(c)), context: m.context, hint: m.hint })).filter((m) => m.candidates.length > 0);
|
|
69326
|
+
return {
|
|
69327
|
+
ok: r.ok,
|
|
69328
|
+
op: r.op,
|
|
69329
|
+
dry_run: r.dry_run,
|
|
69330
|
+
...r.migration_id !== void 0 ? { migration_id: r.migration_id } : {},
|
|
69331
|
+
notes_changed: r.notes_changed,
|
|
69332
|
+
files_created: r.files_created,
|
|
69333
|
+
files_moved: r.files_moved,
|
|
69334
|
+
files_deleted: r.files_deleted,
|
|
69335
|
+
references_updated: r.references_updated,
|
|
69336
|
+
conflicts,
|
|
69337
|
+
violations,
|
|
69338
|
+
failed: failed2,
|
|
69339
|
+
...r.rolled_back !== void 0 ? { rolled_back: r.rolled_back } : {},
|
|
69340
|
+
planned_edits: r.planned_edits.filter((e) => !hidden2(e.path)).map((e) => ({ path: e.path, set: e.set, remove: e.remove, ...e.body !== void 0 ? { body: e.body } : {} })),
|
|
69341
|
+
planned_creates: r.planned_creates.filter((p) => !hidden2(p)),
|
|
69342
|
+
planned_moves: r.planned_moves.filter((m) => !hidden2(m.from) && !hidden2(m.to)).map((m) => ({ from: m.from, to: m.to })),
|
|
69343
|
+
planned_deletes: r.planned_deletes.filter((p) => !hidden2(p)),
|
|
69344
|
+
planned_texts: [...r.planned_texts],
|
|
69345
|
+
...r.commits ? { commits: r.commits.filter((c) => tableReadable(c.table, scope)).map((c) => ({ table: c.table, version: c.version, operation: c.operation })) } : {},
|
|
69346
|
+
...links && links.length > 0 ? { links_added: links } : {},
|
|
69347
|
+
...mentions && mentions.length > 0 ? { ambiguous_mentions: mentions } : {}
|
|
69348
|
+
};
|
|
68292
69349
|
}
|
|
68293
69350
|
function writeStatus(failed2, rolledBack) {
|
|
68294
69351
|
return !failed2 ? 422 : rolledBack === true ? 503 : 500;
|
|
@@ -68300,6 +69357,7 @@ function errorResponse(e, op2, route) {
|
|
|
68300
69357
|
if (e instanceof TableNotFoundError) return { status: 404, json: body(guide("list_tables lists the tables and views, with their descriptions and aliases.", "findTable")) };
|
|
68301
69358
|
if (e instanceof MigrationNotFoundError) return { status: 404, json: body(guide("list_migrations lists the journal ids (migration_id) of the writes, newest first.", "undo")) };
|
|
68302
69359
|
if (e instanceof VaultPathNotFoundError) return { status: 404, json: body(guide("list_folder lists a folder, search_notes finds notes by their words.", "readNote")) };
|
|
69360
|
+
if (e instanceof ReadDeniedError) return { status: 403, json: body(READ_DENIED_GUIDANCE) };
|
|
68303
69361
|
if (e instanceof Forbidden) return { status: 403, json: body(FORBIDDEN_HINT) };
|
|
68304
69362
|
if (e instanceof AmbiguousCatalogRefError) {
|
|
68305
69363
|
return { status: 409, json: { ...body(guide(`Name the table in full (catalog.schema.table): ${e.candidates.join(" or ")}.`, "catalogs")), tables: e.candidates } };
|
|
@@ -68312,6 +69370,26 @@ function errorResponse(e, op2, route) {
|
|
|
68312
69370
|
function stepFailure(json3) {
|
|
68313
69371
|
return Object.assign(new Error(json3.error), guidanceOf(json3));
|
|
68314
69372
|
}
|
|
69373
|
+
function redactBatchRollback(r, app, settings) {
|
|
69374
|
+
const scope = currentReadScope();
|
|
69375
|
+
if (!scope || !r.rollback) return r;
|
|
69376
|
+
const shown = (path23) => !pathHidden(app, settings, path23, scope);
|
|
69377
|
+
return {
|
|
69378
|
+
ok: r.ok,
|
|
69379
|
+
op: r.op,
|
|
69380
|
+
dry_run: r.dry_run,
|
|
69381
|
+
...r.migration_id !== void 0 ? { migration_id: r.migration_id } : {},
|
|
69382
|
+
steps: r.steps,
|
|
69383
|
+
...r.failed_at !== void 0 ? { failed_at: r.failed_at } : {},
|
|
69384
|
+
...r.rolled_back !== void 0 ? { rolled_back: r.rolled_back } : {},
|
|
69385
|
+
rollback: r.rollback.map((u) => ({
|
|
69386
|
+
migration_id: u.migration_id,
|
|
69387
|
+
ok: u.ok,
|
|
69388
|
+
conflicts: u.conflicts.filter((c) => shown(c.path)).map((c) => ({ path: c.path, message: c.message })),
|
|
69389
|
+
failed: u.failed.filter((f) => shown(f.path)).map((f) => ({ path: f.path, error: f.error }))
|
|
69390
|
+
}))
|
|
69391
|
+
};
|
|
69392
|
+
}
|
|
68315
69393
|
function batchGuidance(r) {
|
|
68316
69394
|
if (r.dry_run) return guide("Fix the operations marked refused, then send the batch again (dry_run checks each one against the vault as it is now).", "batch");
|
|
68317
69395
|
if (r.rolled_back === false) return guide('Some operations could not be undone: "rollback" lists them; undo them with undo_migration, or repair the rows, before you retry.', "batch");
|
|
@@ -68356,6 +69434,7 @@ function checkArgs(op2, args) {
|
|
|
68356
69434
|
function createHandlers(app, getSettings, saveSettings = async () => {
|
|
68357
69435
|
}, semantic, api, options = {}) {
|
|
68358
69436
|
const s = getSettings;
|
|
69437
|
+
const opResponse = (r) => writeResponse(r, app, s());
|
|
68359
69438
|
const pub = api ?? createPublicApi(app, getSettings, saveSettings, semantic);
|
|
68360
69439
|
const needSemantic = () => {
|
|
68361
69440
|
if (!semantic) throw new BadRequest("semantic search is not available", SEMANTIC_OFF);
|
|
@@ -68713,7 +69792,8 @@ function createHandlers(app, getSettings, saveSettings = async () => {
|
|
|
68713
69792
|
});
|
|
68714
69793
|
const failedStep = r.failed_at !== void 0 && (r.steps[r.failed_at]?.result?.failed.length ?? 0) > 0;
|
|
68715
69794
|
const status = r.ok ? 200 : r.rolled_back === false ? 500 : writeStatus(failedStep, true);
|
|
68716
|
-
|
|
69795
|
+
const answer = redactBatchRollback(r, app, s());
|
|
69796
|
+
return { status, json: answer.ok ? answer : { ...answer, ...batchGuidance(answer) } };
|
|
68717
69797
|
},
|
|
68718
69798
|
// --- queries ---
|
|
68719
69799
|
select_rows: async (b) => {
|
|
@@ -68758,10 +69838,10 @@ function createHandlers(app, getSettings, saveSettings = async () => {
|
|
|
68758
69838
|
};
|
|
68759
69839
|
},
|
|
68760
69840
|
read_note: async (b) => ({ status: 200, json: await readNote(app, s(), text3(b, "path")) }),
|
|
68761
|
-
list_folder: async (b) => ({ status: 200, json: listFolder(app, text3(b, "folder") ?? "") }),
|
|
69841
|
+
list_folder: async (b) => ({ status: 200, json: listFolder(app, text3(b, "folder") ?? "", s()) }),
|
|
68762
69842
|
search_notes: async (b) => {
|
|
68763
69843
|
const ctx = optInt(b, "context_length");
|
|
68764
|
-
return { status: 200, json: await searchNotes(app, str(b, "query"), ctx ?? 100) };
|
|
69844
|
+
return { status: 200, json: await searchNotes(app, str(b, "query"), ctx ?? 100, s()) };
|
|
68765
69845
|
},
|
|
68766
69846
|
semantic_search: async (b) => {
|
|
68767
69847
|
const sem = needSemantic();
|
|
@@ -68778,6 +69858,20 @@ function createHandlers(app, getSettings, saveSettings = async () => {
|
|
|
68778
69858
|
})
|
|
68779
69859
|
};
|
|
68780
69860
|
},
|
|
69861
|
+
search: async (b) => {
|
|
69862
|
+
const limit = optInt(b, "limit");
|
|
69863
|
+
const minScore = b.min_score;
|
|
69864
|
+
return {
|
|
69865
|
+
status: 200,
|
|
69866
|
+
json: await searchVault(app, s(), {
|
|
69867
|
+
query: str(b, "query"),
|
|
69868
|
+
...limit !== void 0 ? { limit } : {},
|
|
69869
|
+
...strings(b, "tables") ? { tables: strings(b, "tables") } : {},
|
|
69870
|
+
...str(b, "folder") !== void 0 ? { folder: str(b, "folder") } : {},
|
|
69871
|
+
...typeof minScore === "number" ? { minScore } : {}
|
|
69872
|
+
}, semantic)
|
|
69873
|
+
};
|
|
69874
|
+
},
|
|
68781
69875
|
graph_neighbors: async (b) => ({
|
|
68782
69876
|
status: 200,
|
|
68783
69877
|
json: await graphNeighbors(app, s(), {
|
|
@@ -69161,10 +70255,12 @@ var STATUS_TEXT = {
|
|
|
69161
70255
|
404: "Unknown table, view, migration, note or folder.",
|
|
69162
70256
|
409: "Ambiguous table name: it matches several tables (`paths` or `tables` lists them)."
|
|
69163
70257
|
};
|
|
70258
|
+
var READ_DENIED_TEXT = "A named token whose `read` patterns do not cover the table, view or note the request names (such as `token cook may not read personal.health.weight`). Sets it did not name (listings, bases, search, the graph, the change feed) are filtered instead.";
|
|
69164
70259
|
var ERRORS = {
|
|
69165
70260
|
400: { description: STATUS_TEXT[400], content: json(ref("Error")) },
|
|
69166
70261
|
401: { $ref: "#/components/responses/Unauthorized" },
|
|
69167
70262
|
403: { description: STATUS_TEXT[403], content: json(ref("Error")) },
|
|
70263
|
+
readDenied: { description: READ_DENIED_TEXT, content: json(ref("Error")) },
|
|
69168
70264
|
404: { description: STATUS_TEXT[404], content: json(ref("Error")) },
|
|
69169
70265
|
409: { description: STATUS_TEXT[409], content: json(ref("AmbiguousName")) },
|
|
69170
70266
|
500: { $ref: "#/components/responses/ServerError" }
|
|
@@ -69174,6 +70270,7 @@ function postRoute(d) {
|
|
|
69174
70270
|
const op2 = operationOf(d.route);
|
|
69175
70271
|
const read3 = op2 !== void 0 ? READ_OPERATIONS.has(op2) : READ_ROUTES.has(d.route) && d.route !== "/tokens/update/";
|
|
69176
70272
|
if (!read3) responses["403"] = ERRORS[403];
|
|
70273
|
+
else if (op2 !== void 0) responses["403"] = ERRORS.readDenied;
|
|
69177
70274
|
if (d.namesTable) responses["404"] = ERRORS[404];
|
|
69178
70275
|
const refused2 = op2 !== void 0 ? refusedStatus(op2) : void 0;
|
|
69179
70276
|
if (refused2 === 409) {
|
|
@@ -69213,6 +70310,7 @@ var TOKEN_ROUTES = {
|
|
|
69213
70310
|
properties: {
|
|
69214
70311
|
name: { type: "string", pattern: "^[a-z0-9][a-z0-9-]*$", description: "Kebab-case; the journal records it as `auth`. `owner` is reserved." },
|
|
69215
70312
|
description: { type: "string" },
|
|
70313
|
+
read: { type: "array", items: { type: "string" }, description: "Tables the token may read, as patterns like `write`'s. Absent (or with `*`): every table. It must cover every `write` pattern, and stay absent with `ddl` or `maintain` (400 otherwise)." },
|
|
69216
70314
|
write: { type: "array", items: { type: "string" }, description: "Tables the token may write: catalog.schema.table, catalog.schema.*, catalog.* or *." },
|
|
69217
70315
|
ddl: { type: "boolean", description: "May the token change the schema?" },
|
|
69218
70316
|
maintain: { type: "boolean", description: "May the token also undo, restore, vacuum, configure and rebuild the semantic index? (For an agent's own token.)" },
|
|
@@ -69223,12 +70321,13 @@ var TOKEN_ROUTES = {
|
|
|
69223
70321
|
writes: true
|
|
69224
70322
|
},
|
|
69225
70323
|
"/tokens/update/": {
|
|
69226
|
-
summary: "Change a named token. A token may change its own description and autolink default; permissions are the owner's.",
|
|
70324
|
+
summary: "Change a named token. A token may change its own description and autolink default; permissions (`read`, `write`, `ddl`, `maintain`) are the owner's.",
|
|
69227
70325
|
body: {
|
|
69228
70326
|
type: "object",
|
|
69229
70327
|
properties: {
|
|
69230
70328
|
name: { type: "string", description: "Owner only (a named token updates itself)." },
|
|
69231
70329
|
description: { type: "string", description: "An empty string removes it." },
|
|
70330
|
+
read: { type: ["array", "null"], items: { type: "string" }, description: "The tables the token may read; null reads every table again. Checked against `write`, `ddl` and `maintain` as on create." },
|
|
69232
70331
|
write: { type: "array", items: { type: "string" } },
|
|
69233
70332
|
ddl: { type: "boolean" },
|
|
69234
70333
|
maintain: { type: "boolean" },
|
|
@@ -69402,11 +70501,12 @@ var SCHEMAS = {
|
|
|
69402
70501
|
Token: {
|
|
69403
70502
|
type: "object",
|
|
69404
70503
|
description: "A named token as the registry shows it (never its value).",
|
|
69405
|
-
required: ["name", "write", "ddl", "created_at", "updated_at"],
|
|
70504
|
+
required: ["name", "write", "read", "ddl", "created_at", "updated_at"],
|
|
69406
70505
|
properties: {
|
|
69407
70506
|
name: { type: "string" },
|
|
69408
70507
|
description: { type: "string" },
|
|
69409
70508
|
write: { type: "array", items: { type: "string" } },
|
|
70509
|
+
read: { type: "array", items: { type: "string" }, description: 'The tables it may read (patterns like `write`\'s); `["*"]` when it reads every table.' },
|
|
69410
70510
|
ddl: { type: "boolean" },
|
|
69411
70511
|
maintain: { type: "boolean" },
|
|
69412
70512
|
autolink: { type: "boolean", description: "The default of its writes for automatic links (absent: the database's setting)." },
|
|
@@ -69657,7 +70757,7 @@ function openApiDocument(version2) {
|
|
|
69657
70757
|
info: {
|
|
69658
70758
|
title: "Datanotes",
|
|
69659
70759
|
version: version2,
|
|
69660
|
-
description: "Database of Markdown notes: tables of notes, rows, queries, history, a change feed and the vault's notes.\n\n**Operations**: every operation is `POST /datanotes/operations/<name>/`, its body the operation's arguments (the same names as the MCP tool of the same name). An unknown argument is refused (400) with the closest real one.\n\n**Authentication**: every route but `GET /`, this document and `/datanotes/docs` needs `Authorization: Bearer <token>`: the owner's token (`httpServerToken` in the settings: everything) or a named token (`/datanotes/tokens/create/`: reads
|
|
70760
|
+
description: "Database of Markdown notes: tables of notes, rows, queries, history, a change feed and the vault's notes.\n\n**Operations**: every operation is `POST /datanotes/operations/<name>/`, its body the operation's arguments (the same names as the MCP tool of the same name). An unknown argument is refused (400) with the closest real one.\n\n**Authentication**: every route but `GET /`, this document and `/datanotes/docs` needs `Authorization: Bearer <token>`: the owner's token (`httpServerToken` in the settings: everything) or a named token (`/datanotes/tokens/create/`: reads the tables in its `read` patterns, every table without them; writes the tables in its `write` patterns, changes the schema with `ddl`; the engine's administration stays the owner's: 403 otherwise).\n\n**Actor**: send `X-Datanotes-Actor: {\"harness\": \u2026, \"model\": \u2026, \"session\": \u2026}` (JSON) to record on whose behalf a write is made; the journal adds the credential (`auth`: `owner`, or the named token's name), which the client cannot claim.\n\n**Errors**: `{error}` with 400 (bad input), 401 (missing or wrong token), 403 (a named token beyond its permissions, or a read of a table outside its `read` patterns), 404 (unknown table, view, migration or note), 409 (ambiguous table name), 500. A refused write answers 422 (409 for undo) with the operation's result, `ok: false`: nothing was written."
|
|
69661
70761
|
},
|
|
69662
70762
|
servers: [{ url: "/" }],
|
|
69663
70763
|
tags: [
|
|
@@ -69691,6 +70791,7 @@ function openApiDocument(version2) {
|
|
|
69691
70791
|
init_message();
|
|
69692
70792
|
init_hints();
|
|
69693
70793
|
init_docs();
|
|
70794
|
+
init_read_scope();
|
|
69694
70795
|
var ListenError = class extends GuidedError {
|
|
69695
70796
|
constructor(message, code, g) {
|
|
69696
70797
|
super(message, g);
|
|
@@ -70000,7 +71101,7 @@ var HttpApiServer = class {
|
|
|
70000
71101
|
"gen_ai.tool.name": actor.tool
|
|
70001
71102
|
});
|
|
70002
71103
|
if (method === "GET" && (path23 === `${ROUTE_PREFIX2}/changes/stream` || path23 === `${ROUTE_PREFIX2}/changes/stream/`)) {
|
|
70003
|
-
await this.stream(req, res, url);
|
|
71104
|
+
await this.stream(req, res, url, named ? this.opts.readScopeOf?.(named.name) ?? null : null);
|
|
70004
71105
|
return;
|
|
70005
71106
|
}
|
|
70006
71107
|
if (method === "GET" && (path23 === MANUAL_PATH || path23.startsWith(`${MANUAL_PATH}/`))) {
|
|
@@ -70061,7 +71162,11 @@ var HttpApiServer = class {
|
|
|
70061
71162
|
* `where`: JSON).
|
|
70062
71163
|
* A comment line every 25 s keeps proxies from closing the connection.
|
|
70063
71164
|
*/
|
|
70064
|
-
async stream(req, res, url) {
|
|
71165
|
+
async stream(req, res, url, scope) {
|
|
71166
|
+
if (scope && !(this.opts.readScopeSupported ?? READ_SCOPE_SUPPORTED)) {
|
|
71167
|
+
send2(res, 503, { error: READ_SCOPE_UNSUPPORTED, ...READ_SCOPE_UNSUPPORTED_HINT });
|
|
71168
|
+
return;
|
|
71169
|
+
}
|
|
70065
71170
|
const feed = this.opts.changeFeed?.();
|
|
70066
71171
|
if (!feed || !res.write) {
|
|
70067
71172
|
send2(res, 503, { error: "the change feed is not available", ...guide("The change feed runs while the engine follows the vault (datanotes serve); list_changes reads it.", "changeFeed") });
|
|
@@ -70099,7 +71204,7 @@ var HttpApiServer = class {
|
|
|
70099
71204
|
`);
|
|
70100
71205
|
try {
|
|
70101
71206
|
while (open2 && this.server && !this.draining) {
|
|
70102
|
-
const page2 = await feed.wait({ ...q, since: cursor, limit: 500 }, 2e4);
|
|
71207
|
+
const page2 = await feed.wait({ ...q, since: cursor, limit: 500 }, 2e4, scope);
|
|
70103
71208
|
if (!open2) break;
|
|
70104
71209
|
if (page2.gap) res.write(`event: gap
|
|
70105
71210
|
data: {"since":${cursor}}
|
|
@@ -70265,6 +71370,8 @@ init_actor();
|
|
|
70265
71370
|
init_redact();
|
|
70266
71371
|
|
|
70267
71372
|
// src/server/webhooks.ts
|
|
71373
|
+
init_metastore();
|
|
71374
|
+
init_read_scope();
|
|
70268
71375
|
init_store();
|
|
70269
71376
|
init_telemetry();
|
|
70270
71377
|
init_message();
|
|
@@ -70360,8 +71467,16 @@ var WebhookDispatcher = class {
|
|
|
70360
71467
|
if (this.saveTimer) await this.save();
|
|
70361
71468
|
await this.saving;
|
|
70362
71469
|
}
|
|
71470
|
+
/**
|
|
71471
|
+
* The webhooks, as the read scope of the request sees them: a restricted token (one with `read`
|
|
71472
|
+
* patterns) sees only the webhooks on tables it may all read, never one on every table.
|
|
71473
|
+
*/
|
|
70363
71474
|
list() {
|
|
70364
|
-
|
|
71475
|
+
const scope = currentReadScope();
|
|
71476
|
+
if (!scope) return this.hooks.map(publicView);
|
|
71477
|
+
const model = cachedMetastore(this.app, this.getSettings());
|
|
71478
|
+
const full = (t) => (model ? rowTableFull(model, t) : null) ?? t;
|
|
71479
|
+
return this.hooks.filter((h) => (h.tables ?? []).length > 0 && h.tables.every((t) => tableReadable(full(t), scope))).map(publicView);
|
|
70365
71480
|
}
|
|
70366
71481
|
async create(input) {
|
|
70367
71482
|
if (!/^https?:\/\//i.test(input.url)) throw new Error(`"url" must be an http(s) URL`);
|
|
@@ -70451,7 +71566,7 @@ var WebhookDispatcher = class {
|
|
|
70451
71566
|
continue;
|
|
70452
71567
|
}
|
|
70453
71568
|
const q = { since: h.cursor, limit: BATCH, ...h.tables ? { tables: h.tables } : {}, ...h.ops ? { ops: h.ops } : {}, ...h.columns ? { columns: h.columns } : {}, ...h.excludeActor ? { excludeActor: h.excludeActor } : {}, ...h.where ? { where: h.where } : {} };
|
|
70454
|
-
const page2 = await feed.wait(q, WAIT_MS);
|
|
71569
|
+
const page2 = await feed.wait(q, WAIT_MS, null);
|
|
70455
71570
|
if (!this.alive(h)) return;
|
|
70456
71571
|
if (page2.events.length === 0 && !page2.gap) {
|
|
70457
71572
|
if (page2.last !== h.cursor) {
|
|
@@ -70621,7 +71736,7 @@ async function engineStatus(i) {
|
|
|
70621
71736
|
cursor: w.cursor
|
|
70622
71737
|
}))
|
|
70623
71738
|
},
|
|
70624
|
-
tokens: i.tokens.map((t) => ({ name: t.name, write: t.write, ddl: t.ddl, maintain: t.maintain === true, last_seen: t.last_seen ?? null })),
|
|
71739
|
+
tokens: i.tokens.map((t) => ({ name: t.name, ...t.read.includes("*") ? {} : { read: t.read }, write: t.write, ddl: t.ddl, maintain: t.maintain === true, last_seen: t.last_seen ?? null })),
|
|
70625
71740
|
hand_edits: handEdits,
|
|
70626
71741
|
logs: {
|
|
70627
71742
|
folder: s.fileLogEnabled ? i.host?.store ? `${i.host.store}${i.host.store.includes("\\") ? "\\" : "/"}logs` : storePath("logs") : null,
|
|
@@ -70633,13 +71748,14 @@ async function engineStatus(i) {
|
|
|
70633
71748
|
|
|
70634
71749
|
// src/server/tokens.ts
|
|
70635
71750
|
init_actor();
|
|
71751
|
+
init_read_scope();
|
|
70636
71752
|
init_metastore();
|
|
70637
71753
|
init_store();
|
|
70638
71754
|
init_telemetry();
|
|
70639
71755
|
init_message();
|
|
70640
71756
|
init_hints();
|
|
70641
71757
|
var FILE = "tokens.json";
|
|
70642
|
-
var
|
|
71758
|
+
var NAME2 = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
70643
71759
|
var RESERVED_NAMES = /* @__PURE__ */ new Set(["owner"]);
|
|
70644
71760
|
var SEEN_SAVE_MS = 6e4;
|
|
70645
71761
|
async function sha256Hex(text5) {
|
|
@@ -70647,11 +71763,13 @@ async function sha256Hex(text5) {
|
|
|
70647
71763
|
return [...d].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
70648
71764
|
}
|
|
70649
71765
|
function tableAllowed(full, patterns) {
|
|
70650
|
-
return patterns
|
|
70651
|
-
|
|
70652
|
-
|
|
70653
|
-
|
|
70654
|
-
|
|
71766
|
+
return matchesTablePattern(full, patterns);
|
|
71767
|
+
}
|
|
71768
|
+
function scopeOf(t) {
|
|
71769
|
+
return t.read === void 0 || t.read.includes("*") ? null : { token: t.name, patterns: t.read };
|
|
71770
|
+
}
|
|
71771
|
+
function readCovers(r, w) {
|
|
71772
|
+
return r === "*" || r === w || r.endsWith(".*") && w.startsWith(r.slice(0, -1));
|
|
70655
71773
|
}
|
|
70656
71774
|
function tokenNameOf(actor) {
|
|
70657
71775
|
const auth = actor?.auth;
|
|
@@ -70711,6 +71829,7 @@ var TokenRegistry = class {
|
|
|
70711
71829
|
return {
|
|
70712
71830
|
name: t.name,
|
|
70713
71831
|
...t.description !== void 0 ? { description: t.description } : {},
|
|
71832
|
+
read: t.read ?? ["*"],
|
|
70714
71833
|
write: t.write,
|
|
70715
71834
|
ddl: t.ddl,
|
|
70716
71835
|
...t.maintain !== void 0 ? { maintain: t.maintain } : {},
|
|
@@ -70741,7 +71860,7 @@ var TokenRegistry = class {
|
|
|
70741
71860
|
}
|
|
70742
71861
|
async create(input) {
|
|
70743
71862
|
await this.load();
|
|
70744
|
-
if (!
|
|
71863
|
+
if (!NAME2.test(input.name)) throw new BadRequest(`"name" must be kebab-case (a-z, 0-9, -)`);
|
|
70745
71864
|
if (RESERVED_NAMES.has(input.name)) throw new BadRequest(`"${input.name}" is reserved (the owner's token)`);
|
|
70746
71865
|
if (this.get(input.name)) throw new BadRequest(`token ${input.name} already exists (update it, or delete it first)`);
|
|
70747
71866
|
const token = newToken();
|
|
@@ -70750,13 +71869,15 @@ var TokenRegistry = class {
|
|
|
70750
71869
|
name: input.name,
|
|
70751
71870
|
...input.description ? { description: input.description } : {},
|
|
70752
71871
|
tokenHash: await sha256Hex(token),
|
|
70753
|
-
|
|
71872
|
+
...input.read !== void 0 ? { read: readPatterns(input.read) } : {},
|
|
71873
|
+
write: checkPatterns(input.write ?? [], "write"),
|
|
70754
71874
|
ddl: input.ddl === true,
|
|
70755
71875
|
...input.maintain === true ? { maintain: true } : {},
|
|
70756
71876
|
...input.autolink !== void 0 ? { autolink: input.autolink } : {},
|
|
70757
71877
|
createdAt: now,
|
|
70758
71878
|
updatedAt: now
|
|
70759
71879
|
};
|
|
71880
|
+
checkReadCoversPermissions(t);
|
|
70760
71881
|
this.tokens.push(t);
|
|
70761
71882
|
this.reindex();
|
|
70762
71883
|
await this.save();
|
|
@@ -70765,24 +71886,31 @@ var TokenRegistry = class {
|
|
|
70765
71886
|
}
|
|
70766
71887
|
/**
|
|
70767
71888
|
* Change a token. `self` (a token updating its own entry) may change its description and its
|
|
70768
|
-
* `autolink` default; permissions are the owner's. `autolink: null` follows the database again
|
|
71889
|
+
* `autolink` default; permissions are the owner's. `autolink: null` follows the database again,
|
|
71890
|
+
* `read: null` reads every table again. Nothing changes when the token as changed would be refused.
|
|
70769
71891
|
*/
|
|
70770
71892
|
async update(name, patch, self) {
|
|
70771
71893
|
await this.load();
|
|
70772
71894
|
const t = this.get(name);
|
|
70773
71895
|
if (!t) throw new BadRequest(`no token ${name}`);
|
|
70774
|
-
if (self && (patch.write !== void 0 || patch.ddl !== void 0 || patch.maintain !== void 0)) throw new Forbidden("a token cannot change its own permissions");
|
|
71896
|
+
if (self && (patch.read !== void 0 || patch.write !== void 0 || patch.ddl !== void 0 || patch.maintain !== void 0)) throw new Forbidden("a token cannot change its own permissions");
|
|
71897
|
+
const next = { ...t };
|
|
70775
71898
|
if (patch.description !== void 0) {
|
|
70776
|
-
if (patch.description === "") delete
|
|
70777
|
-
else
|
|
70778
|
-
}
|
|
70779
|
-
if (patch.
|
|
70780
|
-
if (patch.
|
|
70781
|
-
if (patch.
|
|
70782
|
-
|
|
70783
|
-
if (patch.
|
|
70784
|
-
else if (patch.
|
|
70785
|
-
|
|
71899
|
+
if (patch.description === "") delete next.description;
|
|
71900
|
+
else next.description = patch.description;
|
|
71901
|
+
}
|
|
71902
|
+
if (patch.read === null) delete next.read;
|
|
71903
|
+
else if (patch.read !== void 0) next.read = readPatterns(patch.read);
|
|
71904
|
+
if (patch.write !== void 0) next.write = checkPatterns(patch.write, "write");
|
|
71905
|
+
if (patch.ddl !== void 0) next.ddl = patch.ddl;
|
|
71906
|
+
if (patch.maintain === true) next.maintain = true;
|
|
71907
|
+
else if (patch.maintain === false) delete next.maintain;
|
|
71908
|
+
if (patch.autolink === null) delete next.autolink;
|
|
71909
|
+
else if (patch.autolink !== void 0) next.autolink = patch.autolink;
|
|
71910
|
+
checkReadCoversPermissions(next);
|
|
71911
|
+
next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
71912
|
+
for (const k of Object.keys(t)) if (!(k in next)) delete t[k];
|
|
71913
|
+
Object.assign(t, next);
|
|
70786
71914
|
await this.save();
|
|
70787
71915
|
return this.publicView(t);
|
|
70788
71916
|
}
|
|
@@ -70807,7 +71935,10 @@ var TokenRegistry = class {
|
|
|
70807
71935
|
return token;
|
|
70808
71936
|
}
|
|
70809
71937
|
// --- permissions ---------------------------------------------------------------------------
|
|
70810
|
-
/**
|
|
71938
|
+
/**
|
|
71939
|
+
* Why this token may not run this route with this body, or null when it may: the reason of a
|
|
71940
|
+
* refused write or route, or the refusal of a read (the same error the engine's read sites throw).
|
|
71941
|
+
*/
|
|
70811
71942
|
async check(name, route, body) {
|
|
70812
71943
|
const t = this.get(name);
|
|
70813
71944
|
if (!t) return "unknown token";
|
|
@@ -70818,8 +71949,8 @@ var TokenRegistry = class {
|
|
|
70818
71949
|
}
|
|
70819
71950
|
/** Why this token may not run this operation with these arguments, or null when it may. */
|
|
70820
71951
|
async checkOperation(t, op2, body) {
|
|
70821
|
-
if (READ_OPERATIONS.has(op2)) return null;
|
|
70822
71952
|
const b = body && typeof body === "object" ? body : {};
|
|
71953
|
+
if (READ_OPERATIONS.has(op2)) return this.checkRead(t, op2, b);
|
|
70823
71954
|
if (op2 === "batch") {
|
|
70824
71955
|
const steps = Array.isArray(b.operations) ? b.operations : [];
|
|
70825
71956
|
for (const [i, raw] of steps.entries()) {
|
|
@@ -70842,20 +71973,44 @@ var TokenRegistry = class {
|
|
|
70842
71973
|
}
|
|
70843
71974
|
return null;
|
|
70844
71975
|
}
|
|
70845
|
-
/**
|
|
71976
|
+
/**
|
|
71977
|
+
* The refusal of a read whose arguments name a table the token may not read, or null. Only names
|
|
71978
|
+
* that resolve to a table are checked here (a view, `base:x`, the system catalog or an unknown
|
|
71979
|
+
* name pass): the engine's read sites decide the rest while the request runs in the token's scope.
|
|
71980
|
+
*/
|
|
71981
|
+
async checkRead(t, op2, b) {
|
|
71982
|
+
const scope = scopeOf(t);
|
|
71983
|
+
if (!scope) return null;
|
|
71984
|
+
const names2 = (Object.prototype.hasOwnProperty.call(READ_TABLE_ARGS, op2) ? READ_TABLE_ARGS[op2] : []).map((k) => b[k]);
|
|
71985
|
+
if ((op2 === "semantic_search" || op2 === "search") && Array.isArray(b.tables)) names2.push(...b.tables.filter((x) => typeof x === "string" && !x.includes("*")));
|
|
71986
|
+
if (!names2.some((n) => typeof n === "string")) return null;
|
|
71987
|
+
const model = (await loadMetastore(this.deps.app, this.deps.getSettings()).catch(() => null))?.model ?? null;
|
|
71988
|
+
if (!model) return null;
|
|
71989
|
+
for (const ref2 of names2) {
|
|
71990
|
+
const full = rowTableFull(model, ref2);
|
|
71991
|
+
if (full !== null && !matchesTablePattern(full, scope.patterns)) return tableReadDenied(t.name, full);
|
|
71992
|
+
}
|
|
71993
|
+
return null;
|
|
71994
|
+
}
|
|
71995
|
+
/** Wrap a dispatcher: calls made with a named token are checked first, and run in its read scope. */
|
|
70846
71996
|
guard(dispatch2) {
|
|
70847
71997
|
return async (route, handler, body) => {
|
|
70848
71998
|
const name = tokenNameOf(currentActor());
|
|
70849
|
-
if (name)
|
|
70850
|
-
|
|
70851
|
-
|
|
70852
|
-
|
|
70853
|
-
|
|
70854
|
-
}
|
|
70855
|
-
|
|
70856
|
-
|
|
70857
|
-
|
|
70858
|
-
|
|
71999
|
+
if (!name) return dispatch2(route, handler, body);
|
|
72000
|
+
const why = await this.check(name, route, body);
|
|
72001
|
+
if (why) {
|
|
72002
|
+
const reason = typeof why === "string" ? why : why.message;
|
|
72003
|
+
telemetry().log("WARN", "datanotes token refused", { token_name: name, "http.route": route, reason });
|
|
72004
|
+
return typeof why === "string" ? { status: 403, json: { error: why, ...FORBIDDEN_HINT } } : errorResponse(why);
|
|
72005
|
+
}
|
|
72006
|
+
const token = this.get(name);
|
|
72007
|
+
if (token.autolink !== void 0) body = withAutolinkDefault(route, body, token.autolink);
|
|
72008
|
+
const scope = scopeOf(token);
|
|
72009
|
+
if (scope && !(this.deps.readScopeSupported ?? READ_SCOPE_SUPPORTED)) {
|
|
72010
|
+
telemetry().log("WARN", "datanotes token refused", { token_name: name, "http.route": route, reason: READ_SCOPE_UNSUPPORTED });
|
|
72011
|
+
return { status: 503, json: { error: READ_SCOPE_UNSUPPORTED, ...READ_SCOPE_UNSUPPORTED_HINT } };
|
|
72012
|
+
}
|
|
72013
|
+
return runWithReadScope(scope, () => dispatch2(route, handler, body));
|
|
70859
72014
|
};
|
|
70860
72015
|
}
|
|
70861
72016
|
// --- routes --------------------------------------------------------------------------------
|
|
@@ -70871,6 +72026,7 @@ var TokenRegistry = class {
|
|
|
70871
72026
|
if (!Array.isArray(v) || v.some((x) => typeof x !== "string")) throw new BadRequest(`"${k}" must be a list of strings`);
|
|
70872
72027
|
return v;
|
|
70873
72028
|
};
|
|
72029
|
+
const strListOrNull = (b, k) => b[k] === null ? null : strList(b, k);
|
|
70874
72030
|
const optStr = (b, k) => {
|
|
70875
72031
|
const v = b[k];
|
|
70876
72032
|
if (v === void 0 || v === null) return void 0;
|
|
@@ -70900,10 +72056,12 @@ var TokenRegistry = class {
|
|
|
70900
72056
|
ownerOnly();
|
|
70901
72057
|
const autolink = optAutolink(b, false);
|
|
70902
72058
|
const description = optStr(b, "description");
|
|
72059
|
+
const read3 = strList(b, "read");
|
|
70903
72060
|
const write = strList(b, "write");
|
|
70904
72061
|
const r = await this.create({
|
|
70905
72062
|
name: optStr(b, "name") ?? "",
|
|
70906
72063
|
...description !== void 0 ? { description } : {},
|
|
72064
|
+
...read3 ? { read: read3 } : {},
|
|
70907
72065
|
...write ? { write } : {},
|
|
70908
72066
|
...b.ddl === true ? { ddl: true } : {},
|
|
70909
72067
|
...b.maintain === true ? { maintain: true } : {},
|
|
@@ -70915,10 +72073,12 @@ var TokenRegistry = class {
|
|
|
70915
72073
|
const self = !!caller();
|
|
70916
72074
|
for (const k of ["ddl", "maintain"]) if (b[k] !== void 0 && typeof b[k] !== "boolean") throw new BadRequest(`"${k}" must be true or false`);
|
|
70917
72075
|
const description = optStr(b, "description");
|
|
72076
|
+
const read3 = strListOrNull(b, "read");
|
|
70918
72077
|
const write = strList(b, "write");
|
|
70919
72078
|
const autolink = optAutolink(b, true);
|
|
70920
72079
|
const patch = {
|
|
70921
72080
|
...description !== void 0 ? { description } : {},
|
|
72081
|
+
...read3 !== void 0 ? { read: read3 } : {},
|
|
70922
72082
|
...write !== void 0 ? { write } : {},
|
|
70923
72083
|
...typeof b.ddl === "boolean" ? { ddl: b.ddl } : {},
|
|
70924
72084
|
...typeof b.maintain === "boolean" ? { maintain: b.maintain } : {},
|
|
@@ -70966,12 +72126,28 @@ function optAutolink(b, nullable2) {
|
|
|
70966
72126
|
)
|
|
70967
72127
|
);
|
|
70968
72128
|
}
|
|
70969
|
-
function checkPatterns(patterns) {
|
|
72129
|
+
function checkPatterns(patterns, field3) {
|
|
70970
72130
|
for (const p of patterns) {
|
|
70971
|
-
if (!/^(\*|[\w-]+(\.[\w-]+){0,2}(\.\*)?)$/.test(p))
|
|
72131
|
+
if (!/^(\*|[\w-]+(\.[\w-]+){0,2}(\.\*)?)$/.test(p)) {
|
|
72132
|
+
throw new BadRequest(`${field3} pattern "${p}" must be a table name, catalog.schema.*, catalog.* or *`, field3 === "read" ? guide(`Write each read pattern as catalog.schema.table, catalog.schema.*, catalog.* or *; list_tables gives the full names.`, "tokenRead") : void 0);
|
|
72133
|
+
}
|
|
70972
72134
|
}
|
|
70973
72135
|
return [...new Set(patterns)];
|
|
70974
72136
|
}
|
|
72137
|
+
function readPatterns(patterns) {
|
|
72138
|
+
const read3 = checkPatterns(patterns, "read");
|
|
72139
|
+
return read3.includes("*") ? ["*"] : read3;
|
|
72140
|
+
}
|
|
72141
|
+
function checkReadCoversPermissions(t) {
|
|
72142
|
+
const read3 = t.read;
|
|
72143
|
+
if (read3 === void 0 || read3.includes("*")) return;
|
|
72144
|
+
if (t.ddl || t.maintain === true) {
|
|
72145
|
+
throw new BadRequest(`token ${t.name}: "ddl" and "maintain" need read on every table`, guide(`Leave "read" out (or pass ["*"]) for a token with ddl or maintain, or turn them off.`, "tokenRead"));
|
|
72146
|
+
}
|
|
72147
|
+
for (const w of t.write) {
|
|
72148
|
+
if (!read3.some((r) => readCovers(r, w))) throw new BadRequest(`token ${t.name} writes ${w} but may not read it`, guide(`Add "${w}" (or a wider pattern) to "read": a write answers with the rows it touches.`, "tokenRead"));
|
|
72149
|
+
}
|
|
72150
|
+
}
|
|
70975
72151
|
|
|
70976
72152
|
// src/server/extensions.ts
|
|
70977
72153
|
init_actor();
|
|
@@ -71076,6 +72252,7 @@ var ExtensionRegistry = class {
|
|
|
71076
72252
|
|
|
71077
72253
|
// src/server/read-cache.ts
|
|
71078
72254
|
init_metastore();
|
|
72255
|
+
init_query_tables();
|
|
71079
72256
|
init_file_cache();
|
|
71080
72257
|
init_vault_index();
|
|
71081
72258
|
var CACHED_READS = /* @__PURE__ */ new Set([
|
|
@@ -71094,7 +72271,6 @@ var CACHED_READS = /* @__PURE__ */ new Set([
|
|
|
71094
72271
|
]);
|
|
71095
72272
|
var GRAPH_READS = /* @__PURE__ */ new Set(["graph_neighbors", "graph_traverse", "graph_path"]);
|
|
71096
72273
|
var SECRET_SETTINGS = /* @__PURE__ */ new Set(["httpServerToken", "telemetryHeaders", "storeBackupRemote"]);
|
|
71097
|
-
var PLAIN_SELECT_KEYS = /* @__PURE__ */ new Set(["table", "columns", "where", "order_by", "limit", "offset", "group_by", "aggregates", "include_body", "join", "union"]);
|
|
71098
72274
|
var TODAY_BUCKET_MS = 15 * 6e4;
|
|
71099
72275
|
function pathKey(path23) {
|
|
71100
72276
|
return path23.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "").normalize("NFC").toLowerCase();
|
|
@@ -71376,8 +72552,8 @@ function readDeps(op2, body, model) {
|
|
|
71376
72552
|
return { tables: "vault", today: today || r.today || views.today };
|
|
71377
72553
|
}
|
|
71378
72554
|
if (op2 === "describe_table" && model && typeof body.table === "string") {
|
|
71379
|
-
const ref2 =
|
|
71380
|
-
if (ref2 && !
|
|
72555
|
+
const ref2 = refOf2(model, body.table, "table");
|
|
72556
|
+
if (ref2 && !refOf2(model, body.table, "view")) return { tables: [ref2], today };
|
|
71381
72557
|
}
|
|
71382
72558
|
if (op2 === "list_tables") return { tables: [], today };
|
|
71383
72559
|
if (op2 === "describe_database" && model) return { tables: catalogTables(model).map(({ ref: ref2 }) => ref2.full), today, loose: true };
|
|
@@ -71386,111 +72562,6 @@ function readDeps(op2, body, model) {
|
|
|
71386
72562
|
if (GRAPH_READS.has(op2) && model) return { tables: catalogTables(model).map(({ ref: ref2 }) => ref2.full).sort(), today, links: true };
|
|
71387
72563
|
return { tables: "vault", today };
|
|
71388
72564
|
}
|
|
71389
|
-
function refOf3(model, name, kind) {
|
|
71390
|
-
try {
|
|
71391
|
-
return resolveCatalogRef(model, name, kind)?.full ?? null;
|
|
71392
|
-
} catch {
|
|
71393
|
-
return null;
|
|
71394
|
-
}
|
|
71395
|
-
}
|
|
71396
|
-
function isSystemTable(name) {
|
|
71397
|
-
const t = name.trim();
|
|
71398
|
-
if (t.startsWith(`${SYSTEM_CATALOG}.`)) return true;
|
|
71399
|
-
const parts = t.split(".");
|
|
71400
|
-
return parts.length === 2 && parts[0] === "information_schema";
|
|
71401
|
-
}
|
|
71402
|
-
var DOTTED_KEY = /"[^"]*\.[^"]*"\s*:/;
|
|
71403
|
-
function queryTables(q, model, out2, depth) {
|
|
71404
|
-
const text5 = JSON.stringify(q);
|
|
71405
|
-
const r = { vault: false, today: text5.includes("{today"), now: text5.includes("{now") };
|
|
71406
|
-
if (depth > 8) return { ...r, vault: true };
|
|
71407
|
-
if (text5.includes("within") || text5.includes("{this")) r.vault = true;
|
|
71408
|
-
if (DOTTED_KEY.test(text5) && !linkedTables(q, model, out2)) r.vault = true;
|
|
71409
|
-
if (text5.includes("file_backlink_count")) r.links = true;
|
|
71410
|
-
if (Object.keys(q).some((k) => !PLAIN_SELECT_KEYS.has(k))) r.vault = true;
|
|
71411
|
-
const sources = [q.table];
|
|
71412
|
-
if (q.join && typeof q.join === "object") sources.push(q.join.table);
|
|
71413
|
-
if (Array.isArray(q.union)) for (const b of q.union) sources.push(b && typeof b === "object" ? b.table : void 0);
|
|
71414
|
-
for (const name of sources) {
|
|
71415
|
-
if (typeof name === "string" && isSystemTable(name)) {
|
|
71416
|
-
r.store = true;
|
|
71417
|
-
continue;
|
|
71418
|
-
}
|
|
71419
|
-
if (typeof name !== "string" || name.startsWith("base:")) {
|
|
71420
|
-
r.vault = true;
|
|
71421
|
-
continue;
|
|
71422
|
-
}
|
|
71423
|
-
const table = refOf3(model, name, "table");
|
|
71424
|
-
const view = refOf3(model, name, "view");
|
|
71425
|
-
if (!table && !view) r.vault = true;
|
|
71426
|
-
if (table) out2.add(table);
|
|
71427
|
-
const v = view ? viewQuery(model, view) : null;
|
|
71428
|
-
if (view && !v) r.vault = true;
|
|
71429
|
-
if (v) {
|
|
71430
|
-
const sub = queryTables(v, model, out2, depth + 1);
|
|
71431
|
-
r.vault ||= sub.vault;
|
|
71432
|
-
r.links ||= sub.links;
|
|
71433
|
-
r.store ||= sub.store;
|
|
71434
|
-
r.today ||= sub.today;
|
|
71435
|
-
r.now ||= sub.now;
|
|
71436
|
-
}
|
|
71437
|
-
}
|
|
71438
|
-
return r;
|
|
71439
|
-
}
|
|
71440
|
-
var isRecord2 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
71441
|
-
function plainTable(model, name) {
|
|
71442
|
-
return typeof name === "string" && !refOf3(model, name, "view") ? refOf3(model, name, "table") : null;
|
|
71443
|
-
}
|
|
71444
|
-
function linkedTables(q, model, out2) {
|
|
71445
|
-
const join = isRecord2(q.join) ? q.join : null;
|
|
71446
|
-
const branches = Array.isArray(q.union) ? q.union : null;
|
|
71447
|
-
const rest = {
|
|
71448
|
-
...q,
|
|
71449
|
-
where: void 0,
|
|
71450
|
-
join: join ? { ...join, where: void 0 } : q.join,
|
|
71451
|
-
union: branches ? branches.map((b) => isRecord2(b) ? { ...b, where: void 0 } : b) : q.union
|
|
71452
|
-
};
|
|
71453
|
-
if (DOTTED_KEY.test(JSON.stringify(rest))) return false;
|
|
71454
|
-
const roots = branches ? branches.map((b) => isRecord2(b) ? plainTable(model, b.table) : null) : [plainTable(model, q.table)];
|
|
71455
|
-
if (roots.length === 0) return false;
|
|
71456
|
-
for (const table of roots) if (!followKeys(q.where, table, model, out2)) return false;
|
|
71457
|
-
if (join && !followKeys(join.where, plainTable(model, join.table), model, out2)) return false;
|
|
71458
|
-
for (const b of branches ?? []) if (isRecord2(b) && !followKeys(b.where, plainTable(model, b.table), model, out2)) return false;
|
|
71459
|
-
return true;
|
|
71460
|
-
}
|
|
71461
|
-
function followKeys(where, table, model, out2) {
|
|
71462
|
-
if (where === null || typeof where !== "object") return true;
|
|
71463
|
-
if (Array.isArray(where)) return where.every((w) => followKeys(w, table, model, out2));
|
|
71464
|
-
for (const [key2, cond] of Object.entries(where)) {
|
|
71465
|
-
if (key2 === "and" || key2 === "or" || key2 === "not") {
|
|
71466
|
-
if (!followKeys(cond, table, model, out2)) return false;
|
|
71467
|
-
} else if (key2.indexOf(".") > 0 && (table === null || !followPath(key2, table, model, out2))) return false;
|
|
71468
|
-
}
|
|
71469
|
-
return true;
|
|
71470
|
-
}
|
|
71471
|
-
function followPath(key2, table, model, out2) {
|
|
71472
|
-
let current2 = table;
|
|
71473
|
-
for (const column of key2.split(".").slice(0, -1)) {
|
|
71474
|
-
const next = linkTarget(model, current2, column);
|
|
71475
|
-
if (!next) return false;
|
|
71476
|
-
out2.add(next);
|
|
71477
|
-
current2 = next;
|
|
71478
|
-
}
|
|
71479
|
-
return true;
|
|
71480
|
-
}
|
|
71481
|
-
function linkTarget(model, full, column) {
|
|
71482
|
-
const t = catalogTables(model).find((x) => x.ref.full === full);
|
|
71483
|
-
if (!t) return null;
|
|
71484
|
-
let columns;
|
|
71485
|
-
try {
|
|
71486
|
-
columns = effectiveColumns(model, t.ref, t.entry);
|
|
71487
|
-
} catch {
|
|
71488
|
-
return null;
|
|
71489
|
-
}
|
|
71490
|
-
const c = columns.find((x) => x.name === column);
|
|
71491
|
-
const link = c !== void 0 && (c.type === "link" || c.type === "list" && c.items === "link");
|
|
71492
|
-
return link && typeof c.ref === "string" ? refOf3(model, c.ref, "table") : null;
|
|
71493
|
-
}
|
|
71494
72565
|
var VIEW_TIMES = /* @__PURE__ */ new WeakMap();
|
|
71495
72566
|
function viewTimes(model) {
|
|
71496
72567
|
let t = VIEW_TIMES.get(model);
|
|
@@ -71500,11 +72571,6 @@ function viewTimes(model) {
|
|
|
71500
72571
|
}
|
|
71501
72572
|
return t;
|
|
71502
72573
|
}
|
|
71503
|
-
function viewQuery(model, full) {
|
|
71504
|
-
const [catalog, schema, name] = full.split(".");
|
|
71505
|
-
const q = model.catalogs[catalog]?.schemas[schema]?.views[name]?.query;
|
|
71506
|
-
return q && typeof q === "object" ? q : null;
|
|
71507
|
-
}
|
|
71508
72574
|
|
|
71509
72575
|
// src/server/runtime.ts
|
|
71510
72576
|
init_message();
|
|
@@ -71848,6 +72914,11 @@ var EngineRuntime = class {
|
|
|
71848
72914
|
...this.o.httpHost ? { host: this.o.httpHost } : {},
|
|
71849
72915
|
changeFeed: () => this.feed,
|
|
71850
72916
|
tokens: this.tokens,
|
|
72917
|
+
// the change stream's events, as the token may read them (a token gone since: nothing)
|
|
72918
|
+
readScopeOf: (name) => {
|
|
72919
|
+
const t = this.tokens.get(name);
|
|
72920
|
+
return t ? scopeOf(t) : { token: name, patterns: [] };
|
|
72921
|
+
},
|
|
71851
72922
|
dispatch: guarded,
|
|
71852
72923
|
...this.o.docs ? { docs: this.o.docs } : {},
|
|
71853
72924
|
compression: () => this.o.getSettings().httpCompression
|
|
@@ -71899,7 +72970,7 @@ var EngineRuntime = class {
|
|
|
71899
72970
|
const name = tokenNameOf(currentActor());
|
|
71900
72971
|
if (!name) return "owner";
|
|
71901
72972
|
const t = this.tokens.get(name);
|
|
71902
|
-
return JSON.stringify([name, t?.write ?? null, t?.ddl ?? null, t?.maintain ?? null, t?.autolink ?? null]);
|
|
72973
|
+
return JSON.stringify([name, t?.write ?? null, t?.ddl ?? null, t?.maintain ?? null, t?.autolink ?? null, t?.read ?? null]);
|
|
71903
72974
|
}
|
|
71904
72975
|
/** Follow the vault (call once the vault is indexed: Obsidian's layout-ready, or after opening). */
|
|
71905
72976
|
async start(opts = {}) {
|
|
@@ -72987,7 +74058,7 @@ function relativePath(fromDir2, to) {
|
|
|
72987
74058
|
while (i < a.length && i < b.length - 1 && a[i] === b[i]) i++;
|
|
72988
74059
|
return [...a.slice(i).map(() => ".."), ...b.slice(i)].join("/");
|
|
72989
74060
|
}
|
|
72990
|
-
function
|
|
74061
|
+
function linkText2(resolver, path23, sourcePath, format, omitMd = true) {
|
|
72991
74062
|
const target = omitMd && /\.md$/i.test(path23) ? stripMd(path23) : path23;
|
|
72992
74063
|
if (format === "absolute") return target;
|
|
72993
74064
|
if (format === "relative") return relativePath(dirname2(sourcePath), target);
|
|
@@ -75149,7 +76220,7 @@ var NodeMetadataCache = class {
|
|
|
75149
76220
|
return f && "extension" in f ? f : null;
|
|
75150
76221
|
}
|
|
75151
76222
|
fileToLinktext(file, sourcePath, omitMdExtension = true) {
|
|
75152
|
-
return
|
|
76223
|
+
return linkText2(this.resolver, file.path, sourcePath, this.vault.config.newLinkFormat, omitMdExtension);
|
|
75153
76224
|
}
|
|
75154
76225
|
on(name, cb) {
|
|
75155
76226
|
this.events.on(name, cb);
|
|
@@ -75251,7 +76322,7 @@ var NodeFileManager = class {
|
|
|
75251
76322
|
src,
|
|
75252
76323
|
resolveOld,
|
|
75253
76324
|
moved,
|
|
75254
|
-
(dest, s) =>
|
|
76325
|
+
(dest, s) => linkText2(newResolver, dest, s, format, true),
|
|
75255
76326
|
oldSrc,
|
|
75256
76327
|
(lp, s) => newResolver.resolve(lp, s)
|
|
75257
76328
|
).text;
|
|
@@ -76145,7 +77216,7 @@ function setUp(o, vault, home, explicit, self) {
|
|
|
76145
77216
|
// src/node/service.ts
|
|
76146
77217
|
init_local();
|
|
76147
77218
|
init_hints();
|
|
76148
|
-
var VERSION = true ? "3.
|
|
77219
|
+
var VERSION = true ? "3.4.0" : "dev";
|
|
76149
77220
|
function parsePort(raw, from = "--port") {
|
|
76150
77221
|
const n = /^\s*\d+\s*$/.test(raw) ? Number(raw) : NaN;
|
|
76151
77222
|
if (n === 0 || Number.isInteger(n) && n >= 1024 && n <= 65535) return n;
|
|
@@ -76660,7 +77731,8 @@ ${"error".padEnd(11)}${r.error}`;
|
|
|
76660
77731
|
line("changes", f.running ? `feed running, last seq ${f.head}` : `feed NOT RUNNING: ${f.reason}`);
|
|
76661
77732
|
line("webhooks", `${r.webhooks.count}${r.webhooks.failing.length ? ` (${r.webhooks.failing.length} failing)` : ""}`);
|
|
76662
77733
|
for (const w of r.webhooks.failing) out2.push(` ! ${w.id} ${w.url_host}: ${plural(w.failures, "failed attempt")}, retried with backoff${w.last_error ? `; last error: ${w.last_error}` : ""}`);
|
|
76663
|
-
const
|
|
77734
|
+
const reads = (t) => t.read ? [`read ${t.read.length ? t.read.join(", ") : "nothing"}`] : [];
|
|
77735
|
+
const perms = (t) => [...reads(t), t.write.length ? `write ${t.write.join(", ")}` : t.read ? "no write" : "read only", ...t.ddl ? ["ddl"] : [], ...t.maintain ? ["maintain"] : []].join(", ");
|
|
76664
77736
|
line("tokens", r.tokens.length ? r.tokens.map((t) => `${t.name} (${perms(t)})`).join("; ") : "owner only");
|
|
76665
77737
|
const h = r.hand_edits;
|
|
76666
77738
|
line(
|
|
@@ -77209,7 +78281,7 @@ Docs: https://datanotes.dev`;
|
|
|
77209
78281
|
|
|
77210
78282
|
// src/node/cli.ts
|
|
77211
78283
|
init_message();
|
|
77212
|
-
var VERSION2 = true ? "3.
|
|
78284
|
+
var VERSION2 = true ? "3.4.0" : "dev";
|
|
77213
78285
|
var consoleError = console.error;
|
|
77214
78286
|
async function main5(argv = process.argv.slice(2)) {
|
|
77215
78287
|
const [command, ...rest] = argv;
|