naro-mcp 0.19.0 → 0.20.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.
Files changed (2) hide show
  1. package/dist/index.js +193 -38
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -15334,6 +15334,7 @@ var NaroClient = class {
15334
15334
  db;
15335
15335
  backend;
15336
15336
  storage;
15337
+ realtime;
15337
15338
  insights;
15338
15339
  analytics;
15339
15340
  protection;
@@ -15353,6 +15354,7 @@ var NaroClient = class {
15353
15354
  this.db = new DbApi(this);
15354
15355
  this.backend = new BackendApi(this);
15355
15356
  this.storage = new StorageApi(this);
15357
+ this.realtime = new RealtimeApi(this);
15356
15358
  this.insights = new InsightsApi(this);
15357
15359
  this.analytics = new AnalyticsApi(this);
15358
15360
  this.protection = new ProtectionApi(this);
@@ -16378,9 +16380,9 @@ function storageInfo(body) {
16378
16380
  buckets: (body.buckets ?? []).map(storageBucket)
16379
16381
  };
16380
16382
  }
16381
- function storageRunFailure(error51) {
16383
+ function provisionFailure(error51, feature) {
16382
16384
  const details = isNaroError(error51) ? error51.details : void 0;
16383
- if (isNaroError(error51) && error51.code === "BACKEND_PROVISION_FAILED" && isRecord(details?.storage) && isRecord(details.backend))
16385
+ if (isNaroError(error51) && error51.code === "BACKEND_PROVISION_FAILED" && isRecord(details?.[feature]) && isRecord(details.backend))
16384
16386
  return new NaroError(error51.code, error51.message, {
16385
16387
  ...error51.status !== void 0 ? { status: error51.status } : {},
16386
16388
  details: {
@@ -16818,7 +16820,7 @@ var StorageApi = class {
16818
16820
  }
16819
16821
  );
16820
16822
  } catch (error51) {
16821
- throw storageRunFailure(error51);
16823
+ throw provisionFailure(error51, "storage");
16822
16824
  }
16823
16825
  return storageInfo(body.storage);
16824
16826
  }
@@ -16856,7 +16858,7 @@ var StorageApi = class {
16856
16858
  }
16857
16859
  );
16858
16860
  } catch (error51) {
16859
- throw storageRunFailure(error51);
16861
+ throw provisionFailure(error51, "storage");
16860
16862
  }
16861
16863
  return storageInfo(body.storage);
16862
16864
  }
@@ -17015,6 +17017,114 @@ var StorageApi = class {
17015
17017
  return body.objects;
17016
17018
  }
17017
17019
  };
17020
+ var RealtimeApi = class {
17021
+ constructor(client) {
17022
+ this.client = client;
17023
+ }
17024
+ path(projectId, suffix = "") {
17025
+ return `/api/projects/${encodeURIComponent(projectId)}/backend/realtime${suffix}`;
17026
+ }
17027
+ /**
17028
+ * Like `StorageApi.bucketPath`: URL parsing folds `.` and `..`, an empty
17029
+ * name is the collection, and `encodeURIComponent` throws on a lone
17030
+ * surrogate — so a name no URL can carry is refused here, before a
17031
+ * request. SQLite does make tables named `.` and `..`, and one with the
17032
+ * empty name (`CREATE TABLE "" (a)`); such a table cannot be published
17033
+ * or unpublished through this API.
17034
+ */
17035
+ tablePath(projectId, table) {
17036
+ if (table === "" || table === "." || table === ".." || LONE_SURROGATE.test(table))
17037
+ throw new NaroError("INVALID_PATH", "The table name is not valid.");
17038
+ return this.path(projectId, `/tables/${encodeURIComponent(table)}`);
17039
+ }
17040
+ /**
17041
+ * Whether Realtime is on, the limits the hub enforces, the published
17042
+ * tables and the hub's own numbers. A project that never turned it on
17043
+ * answers `enabled: false` with no tables.
17044
+ */
17045
+ async info(projectId) {
17046
+ const body = await this.client.request(
17047
+ this.path(projectId),
17048
+ { context: "Read realtime" }
17049
+ );
17050
+ return body.realtime;
17051
+ }
17052
+ /**
17053
+ * Turns Realtime on: the request and the plan's limits are recorded, and
17054
+ * the Naro worker is uploaded again with its hub (a Durable Object). Safe
17055
+ * to repeat, and a repair — also after a plan change. The project must have
17056
+ * a backend, turned on, with its database. A backend in `error` or with a
17057
+ * run that stalled is not refused: its provisioning is resumed on the way.
17058
+ * Two minutes, like `backend.enable`.
17059
+ *
17060
+ * Refusals, in the order they are checked: `BACKEND_DISABLED` (the control
17061
+ * plane's backend switch), `FORBIDDEN` (`database.write`),
17062
+ * `REALTIME_DISABLED` (the control plane's Realtime switch),
17063
+ * `DATABASE_NOT_ENABLED` (the project's database is external),
17064
+ * `BACKEND_NOT_READY` (409, only when the project has no backend at all),
17065
+ * `BACKEND_OFF`, `BACKEND_BUSY`, `REALTIME_ENABLE_FAILED` (502: the request
17066
+ * could not be recorded; nothing changed), and `BACKEND_PROVISION_FAILED`
17067
+ * (502) when Realtime is recorded as on but the worker's upload stopped.
17068
+ * The last one's `details` hold a `RealtimeProvisionFailure` — `{ realtime:
17069
+ * { applied: false, enabled: true }, backend }`, the backend read like
17070
+ * `backend.info` — and `enable` again finishes.
17071
+ */
17072
+ async enable(projectId) {
17073
+ let body;
17074
+ try {
17075
+ body = await this.client.request(
17076
+ this.path(projectId),
17077
+ {
17078
+ method: "POST",
17079
+ context: "Turn on Realtime",
17080
+ timeoutMs: PROVISION_TIMEOUT_MS
17081
+ }
17082
+ );
17083
+ } catch (error51) {
17084
+ throw provisionFailure(error51, "realtime");
17085
+ }
17086
+ return body.realtime;
17087
+ }
17088
+ /**
17089
+ * The tables whose Data API writes are published, in the order they were
17090
+ * published. `exists: false` is a published name whose table was dropped.
17091
+ * `REALTIME_NOT_ENABLED` (409) before `enable`; it answers while the
17092
+ * backend is off, since the list is in the project's database.
17093
+ */
17094
+ async listTables(projectId) {
17095
+ const body = await this.client.request(
17096
+ this.path(projectId, "/tables"),
17097
+ { context: "List realtime tables" }
17098
+ );
17099
+ return body.tables;
17100
+ }
17101
+ /**
17102
+ * Publishes a table's changes to the subscribers of its channel. Safe to
17103
+ * repeat. Only changes made through the Data API are published — writes
17104
+ * the app makes with `env.DB` are not (`warnings` says so).
17105
+ * `TABLE_NOT_FOUND` (404) for a table that does not exist;
17106
+ * `VALIDATION_FAILED` (400) for a Naro system table, a virtual table, a
17107
+ * name over 256 characters, or the 100-table cap; `REALTIME_NOT_ENABLED`
17108
+ * (409) before `enable`.
17109
+ */
17110
+ async publishTable(projectId, table) {
17111
+ return this.client.request(
17112
+ this.tablePath(projectId, table),
17113
+ { method: "PUT", context: "Publish a realtime table" }
17114
+ );
17115
+ }
17116
+ /**
17117
+ * Stops publishing a table's changes; a table dropped since can still be
17118
+ * unpublished. `TABLE_NOT_PUBLISHED` (404) for a table that does not
17119
+ * publish, `REALTIME_NOT_ENABLED` (409) before `enable`.
17120
+ */
17121
+ async unpublishTable(projectId, table) {
17122
+ return this.client.request(
17123
+ this.tablePath(projectId, table),
17124
+ { method: "DELETE", context: "Stop publishing a realtime table" }
17125
+ );
17126
+ }
17127
+ };
17018
17128
 
17019
17129
  // ../../packages/sdk/src/database.ts
17020
17130
  var BOOKMARK_RE = /^[0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{32}$/;
@@ -17041,6 +17151,39 @@ var MAX_FILE_BYTES2 = 1024 * 1024;
17041
17151
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
17042
17152
  import { join as join4 } from "path";
17043
17153
 
17154
+ // ../../packages/data-core/src/system-tables.ts
17155
+ var SYSTEM_TABLE_PREFIXES = [
17156
+ "_naro_",
17157
+ "_cf_",
17158
+ "sqlite_"
17159
+ ];
17160
+ var SYSTEM_TABLE_NAMES = ["d1_migrations"];
17161
+ function asciiLower(text) {
17162
+ return text.replace(/[A-Z]+/g, (run) => run.toLowerCase());
17163
+ }
17164
+ function isSystemTable(name) {
17165
+ const lower = asciiLower(name);
17166
+ return SYSTEM_TABLE_PREFIXES.some((prefix) => lower.startsWith(prefix)) || SYSTEM_TABLE_NAMES.includes(lower);
17167
+ }
17168
+ function likeLiteral(text) {
17169
+ return text.replace(/[\\%_]/g, (char) => `\\${char}`).replace(/'/g, "''");
17170
+ }
17171
+ var COLUMN_REFERENCE = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)?$/;
17172
+ function systemTablePredicate(column = "name") {
17173
+ if (!COLUMN_REFERENCE.test(column))
17174
+ throw new Error(`Not a column reference: ${column}`);
17175
+ return [
17176
+ ...SYSTEM_TABLE_PREFIXES.map(
17177
+ (prefix) => `${column} NOT LIKE '${likeLiteral(prefix)}%' ESCAPE '\\'`
17178
+ ),
17179
+ ...SYSTEM_TABLE_NAMES.map(
17180
+ (name) => `${column} NOT LIKE '${likeLiteral(name)}' ESCAPE '\\'`
17181
+ ),
17182
+ `${column} NOT IN (SELECT name FROM pragma_table_list WHERE schema = 'main' AND type = 'shadow')`
17183
+ ].join(" AND ");
17184
+ }
17185
+ var SYSTEM_TABLE_SQL_PREDICATE = systemTablePredicate("name");
17186
+
17044
17187
  // ../../packages/data-core/src/storage-path.ts
17045
17188
  var STORAGE_RESERVED_WORDS = [
17046
17189
  "authenticated",
@@ -17118,39 +17261,6 @@ var STORAGE_BUCKET_DELETING_COLUMN = {
17118
17261
  definition: "TEXT"
17119
17262
  };
17120
17263
 
17121
- // ../../packages/data-core/src/system-tables.ts
17122
- var SYSTEM_TABLE_PREFIXES = [
17123
- "_naro_",
17124
- "_cf_",
17125
- "sqlite_"
17126
- ];
17127
- var SYSTEM_TABLE_NAMES = ["d1_migrations"];
17128
- function asciiLower(text) {
17129
- return text.replace(/[A-Z]+/g, (run) => run.toLowerCase());
17130
- }
17131
- function isSystemTable(name) {
17132
- const lower = asciiLower(name);
17133
- return SYSTEM_TABLE_PREFIXES.some((prefix) => lower.startsWith(prefix)) || SYSTEM_TABLE_NAMES.includes(lower);
17134
- }
17135
- function likeLiteral(text) {
17136
- return text.replace(/[\\%_]/g, (char) => `\\${char}`).replace(/'/g, "''");
17137
- }
17138
- var COLUMN_REFERENCE = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)?$/;
17139
- function systemTablePredicate(column = "name") {
17140
- if (!COLUMN_REFERENCE.test(column))
17141
- throw new Error(`Not a column reference: ${column}`);
17142
- return [
17143
- ...SYSTEM_TABLE_PREFIXES.map(
17144
- (prefix) => `${column} NOT LIKE '${likeLiteral(prefix)}%' ESCAPE '\\'`
17145
- ),
17146
- ...SYSTEM_TABLE_NAMES.map(
17147
- (name) => `${column} NOT LIKE '${likeLiteral(name)}' ESCAPE '\\'`
17148
- ),
17149
- `${column} NOT IN (SELECT name FROM pragma_table_list WHERE schema = 'main' AND type = 'shadow')`
17150
- ].join(" AND ");
17151
- }
17152
- var SYSTEM_TABLE_SQL_PREDICATE = systemTablePredicate("name");
17153
-
17154
17264
  // ../../packages/data-core/src/errors.ts
17155
17265
  var STORAGE_MIGRATION_PENDING = new RegExp(
17156
17266
  `^no such column: (?:b\\.|${STORAGE_BUCKET_DELETING_COLUMN.table}\\.)?${STORAGE_BUCKET_DELETING_COLUMN.column}\\b`
@@ -19396,8 +19506,34 @@ var MAX_STATEMENT_BYTES = 1e5;
19396
19506
  // ../../packages/data-core/src/policy-index.ts
19397
19507
  var INDEX_SQL = `SELECT t.name AS table_name, il.name AS index_name, il."partial" AS partial, ix.name AS column_name, ix.coll AS collation FROM pragma_table_list AS t JOIN pragma_index_list(t.name, t.schema) AS il JOIN pragma_index_xinfo(il.name, t.schema) AS ix WHERE t.schema = 'main' AND t.type = 'table' AND ${systemTablePredicate("t.name")} AND ix.seqno = 0`;
19398
19508
 
19509
+ // ../../packages/data-core/src/realtime-filter.ts
19510
+ var FILTER_REASONS = {
19511
+ bytes: "must be a string of up to 1024 bytes in UTF-8",
19512
+ control: "must have no control character",
19513
+ syntax: "supports eq, neq, lt, lte, gt, gte and in, one condition",
19514
+ list: "needs in.(a,b) with 1 to 100 values"
19515
+ };
19516
+
19399
19517
  // ../../packages/data-core/src/realtime-protocol.ts
19400
19518
  var REALTIME_PROTOCOL_VERSION = "1.0.0";
19519
+ var MAX_CHANGE_CHANNELS_PER_TABLE = 4;
19520
+ var JOIN_CONFIG_REASONS = {
19521
+ object: "must be an object",
19522
+ unsupported: "is not supported",
19523
+ boolean: "must be true or false",
19524
+ presenceKey: "must be 1 to 128 characters",
19525
+ changes: "must be an array of up to 16 subscriptions",
19526
+ changeEvent: "must be *, INSERT, UPDATE or DELETE",
19527
+ schema: "must be public",
19528
+ table: "must be a table name of 1 to 128 characters, or *",
19529
+ filter: FILTER_REASONS.bytes,
19530
+ filterControl: FILTER_REASONS.control,
19531
+ filterSyntax: FILTER_REASONS.syntax,
19532
+ filterList: FILTER_REASONS.list,
19533
+ filterTable: "needs a table: a filter cannot go with *",
19534
+ /** The hub's, after `config.postgres_changes[<i>].table` (3-2c-1): `MAX_CHANGE_CHANNELS_PER_TABLE` against the connection's joins. */
19535
+ changeChannels: `would put more than ${String(MAX_CHANGE_CHANNELS_PER_TABLE)} channels of this connection on one table's changes (* counts for every table): add the subscription to a channel it already has`
19536
+ };
19401
19537
  var REALTIME_CLOSE = {
19402
19538
  UNSUPPORTED_PROTOCOL: {
19403
19539
  code: 4e3,
@@ -19486,6 +19622,12 @@ var LIVE_BUCKET = `${STORAGE_BUCKET_DELETING_COLUMN.column} IS NULL`;
19486
19622
  var STORAGE_PROBE_NONCE = "f".repeat(STORAGE_NONCE_MAX_DIGITS);
19487
19623
  var REALTIME_PROBE_NONCE = "f".repeat(64);
19488
19624
 
19625
+ // ../../packages/data-core/src/realtime-publication.ts
19626
+ var REALTIME_TABLES = "_naro_realtime_tables";
19627
+ var REALTIME_TABLES_SQL = `CREATE TABLE IF NOT EXISTS ${REALTIME_TABLES} (table_name TEXT NOT NULL PRIMARY KEY COLLATE NOCASE, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')))`;
19628
+ var MAX_PUBLISHED_TABLES = 100;
19629
+ var PUBLISHED_TABLES_SQL = `SELECT table_name FROM ${REALTIME_TABLES} ORDER BY rowid LIMIT ${String(MAX_PUBLISHED_TABLES)}`;
19630
+
19489
19631
  // ../../packages/data-core/src/system-sql.ts
19490
19632
  async function convertLegacyPolicies(db, found) {
19491
19633
  const rows = rowsOf(await db.prepare(POLICY_SQL.list).all());
@@ -19519,6 +19661,7 @@ var MIGRATIONS_TABLE = "CREATE TABLE IF NOT EXISTS _naro_migrations (version INT
19519
19661
  var STORAGE_SUBQUERIES_VERSION = 7;
19520
19662
  var STORAGE_SUBQUERIES_NAME = "storage_subquery_policies";
19521
19663
  var STORAGE_SUBQUERIES_SQL = `SELECT version FROM _naro_migrations WHERE version = ${String(STORAGE_SUBQUERIES_VERSION)} AND name = '${STORAGE_SUBQUERIES_NAME}'`;
19664
+ var REALTIME_TABLES_VERSION = 8;
19522
19665
  var SYSTEM_MIGRATIONS = [
19523
19666
  {
19524
19667
  version: 1,
@@ -19607,6 +19750,14 @@ var SYSTEM_MIGRATIONS = [
19607
19750
  version: STORAGE_SUBQUERIES_VERSION,
19608
19751
  name: STORAGE_SUBQUERIES_NAME,
19609
19752
  statements: []
19753
+ },
19754
+ {
19755
+ // Realtime 3-2b (original spec §9.1, design §7.1): the tables whose
19756
+ // Data API writes reach the hub. Empty until the publish API (3-2c)
19757
+ // writes it. A worker from before it never reads it.
19758
+ version: REALTIME_TABLES_VERSION,
19759
+ name: "realtime",
19760
+ statements: [REALTIME_TABLES_SQL]
19610
19761
  }
19611
19762
  ];
19612
19763
 
@@ -19648,6 +19799,10 @@ var ENTERPRISE_REALTIME_LIMITS = Object.freeze({
19648
19799
  restRequestsPerSecond: 100
19649
19800
  });
19650
19801
 
19802
+ // ../../packages/data-core/src/realtime-changes.ts
19803
+ var CHANGE_TYPES = Object.freeze({ insert: "INSERT", update: "UPDATE", delete: "DELETE" });
19804
+ var CHANGE_EVENT_CODES = Object.freeze({ "*": 0, INSERT: 1, UPDATE: 2, DELETE: 3 });
19805
+
19651
19806
  // ../../packages/data-core/src/storage-multipart.ts
19652
19807
  var MiB = 1024 * 1024;
19653
19808
  var MULTIPART_PART_SIZES = [16 * MiB, 32 * MiB, 64 * MiB];
@@ -23010,7 +23165,7 @@ async function openClaims(client, projectId) {
23010
23165
  }
23011
23166
 
23012
23167
  // src/server.ts
23013
- var SERVER_VERSION = "0.19.0";
23168
+ var SERVER_VERSION = "0.20.0";
23014
23169
  function visibleTools(options2) {
23015
23170
  const { features, readOnly } = options2;
23016
23171
  return TOOLS.flatMap((tool) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "naro-mcp",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "naro-mcp": "./dist/index.js"
@@ -18,7 +18,7 @@
18
18
  "dependencies": {
19
19
  "@modelcontextprotocol/sdk": "^1.0.0",
20
20
  "@supabase/supabase-js": "2.112.3",
21
- "naro-cli": "0.20.0"
21
+ "naro-cli": "0.21.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "tsup": "8.5.0",