tina4-nodejs 3.13.110 → 3.13.112

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.
@@ -16918,6 +16918,775 @@ var init_docstore = __esm({
16918
16918
  }
16919
16919
  });
16920
16920
 
16921
+ // ../orm/src/graph/graphUrl.ts
16922
+ var SCHEME_ENGINE, ENGINE_DEFAULT_PORT, GraphUrl;
16923
+ var init_graphUrl = __esm({
16924
+ "../orm/src/graph/graphUrl.ts"() {
16925
+ "use strict";
16926
+ SCHEME_ENGINE = {
16927
+ ultipa: "ultipa",
16928
+ ultipas: "ultipa",
16929
+ // TLS variant
16930
+ neo4j: "bolt",
16931
+ "neo4j+s": "bolt",
16932
+ bolt: "bolt",
16933
+ "bolt+s": "bolt",
16934
+ memgraph: "bolt",
16935
+ arango: "arango",
16936
+ arangodb: "arango"
16937
+ };
16938
+ ENGINE_DEFAULT_PORT = {
16939
+ ultipa: 60061,
16940
+ bolt: 7687,
16941
+ arango: 8529
16942
+ };
16943
+ GraphUrl = class _GraphUrl {
16944
+ raw;
16945
+ scheme;
16946
+ engine;
16947
+ host;
16948
+ port;
16949
+ /** The graph/database name (leading slash stripped), or null when absent. */
16950
+ graph;
16951
+ username;
16952
+ password;
16953
+ params;
16954
+ useTls;
16955
+ constructor(url) {
16956
+ this.raw = url;
16957
+ let parsed;
16958
+ try {
16959
+ parsed = new URL(url);
16960
+ } catch {
16961
+ throw new Error(
16962
+ `Unsupported graph URL '${url}' \u2014 expected scheme://[user[:password]@]host[:port]/graph (e.g. ultipa://host:60061/mygraph).`
16963
+ );
16964
+ }
16965
+ const scheme = parsed.protocol.replace(/:$/, "").toLowerCase();
16966
+ const engine = SCHEME_ENGINE[scheme];
16967
+ if (engine === void 0) {
16968
+ throw new Error(
16969
+ `Unsupported graph URL scheme '${scheme}'. Supported: ${Object.keys(SCHEME_ENGINE).sort().join(", ")} (e.g. ultipa://host:60061/mygraph).`
16970
+ );
16971
+ }
16972
+ this.scheme = scheme;
16973
+ this.engine = engine;
16974
+ this.host = parsed.hostname || "localhost";
16975
+ this.port = parsed.port ? parseInt(parsed.port, 10) : ENGINE_DEFAULT_PORT[engine];
16976
+ const path8 = (parsed.pathname || "").replace(/^\//, "");
16977
+ this.graph = path8 === "" ? null : path8;
16978
+ this.username = parsed.username ? decodeURIComponent(parsed.username) : null;
16979
+ this.password = parsed.password ? decodeURIComponent(parsed.password) : null;
16980
+ this.params = {};
16981
+ for (const [key, value] of parsed.searchParams) {
16982
+ if (!(key in this.params)) this.params[key] = value;
16983
+ }
16984
+ this.useTls = scheme.endsWith("s") || this.params.tls === "1" || this.params.tls === "true";
16985
+ }
16986
+ /** host:port/graph — for messages, never carrying credentials. */
16987
+ getDsn() {
16988
+ const target = this.port ? `${this.host}:${this.port}` : this.host;
16989
+ return this.graph ? `${target}/${this.graph}` : target;
16990
+ }
16991
+ static fromEnv(envKey = "TINA4_GRAPH_URL") {
16992
+ const url = (process.env[envKey] ?? "").trim();
16993
+ return url === "" ? null : new _GraphUrl(url);
16994
+ }
16995
+ };
16996
+ }
16997
+ });
16998
+
16999
+ // ../orm/src/graph/errors.ts
17000
+ var GraphError, GraphConnectTimeout;
17001
+ var init_errors = __esm({
17002
+ "../orm/src/graph/errors.ts"() {
17003
+ "use strict";
17004
+ GraphError = class extends Error {
17005
+ constructor(message, cause) {
17006
+ super(message, cause === void 0 ? void 0 : { cause });
17007
+ this.name = "GraphError";
17008
+ }
17009
+ };
17010
+ GraphConnectTimeout = class extends GraphError {
17011
+ constructor(message, cause) {
17012
+ super(message, cause);
17013
+ this.name = "GraphConnectTimeout";
17014
+ }
17015
+ };
17016
+ }
17017
+ });
17018
+
17019
+ // ../orm/src/graph/shapes.ts
17020
+ var GraphNode, GraphEdge, GraphResult;
17021
+ var init_shapes = __esm({
17022
+ "../orm/src/graph/shapes.ts"() {
17023
+ "use strict";
17024
+ GraphNode = class {
17025
+ id;
17026
+ labels;
17027
+ properties;
17028
+ constructor(id, labels = null, properties = null) {
17029
+ this.id = id;
17030
+ this.labels = [...labels ?? []];
17031
+ this.properties = { ...properties ?? {} };
17032
+ }
17033
+ toDict() {
17034
+ return { id: this.id, labels: this.labels, properties: this.properties };
17035
+ }
17036
+ };
17037
+ GraphEdge = class {
17038
+ id;
17039
+ type;
17040
+ from;
17041
+ to;
17042
+ properties;
17043
+ constructor(id, type2, from, to, properties = null) {
17044
+ this.id = id;
17045
+ this.type = type2;
17046
+ this.from = from;
17047
+ this.to = to;
17048
+ this.properties = { ...properties ?? {} };
17049
+ }
17050
+ toDict() {
17051
+ return {
17052
+ id: this.id,
17053
+ type: this.type,
17054
+ from: this.from,
17055
+ to: this.to,
17056
+ properties: this.properties
17057
+ };
17058
+ }
17059
+ };
17060
+ GraphResult = class {
17061
+ records;
17062
+ columns;
17063
+ constructor(records = null, columns = null) {
17064
+ this.records = [...records ?? []];
17065
+ this.columns = [...columns ?? []];
17066
+ }
17067
+ toArray() {
17068
+ return this.records;
17069
+ }
17070
+ /** The first value of the first record, or null. */
17071
+ scalar() {
17072
+ if (this.records.length === 0) return null;
17073
+ const values = Object.values(this.records[0]);
17074
+ return values.length ? values[0] : null;
17075
+ }
17076
+ [Symbol.iterator]() {
17077
+ return this.records[Symbol.iterator]();
17078
+ }
17079
+ get length() {
17080
+ return this.records.length;
17081
+ }
17082
+ };
17083
+ }
17084
+ });
17085
+
17086
+ // ../orm/src/graph/connectTimeout.ts
17087
+ function resolveGraphConnectTimeout() {
17088
+ const raw = (process.env[GRAPH_CONNECT_TIMEOUT_VARIABLE] ?? "").trim();
17089
+ if (raw === "") {
17090
+ return DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS;
17091
+ }
17092
+ const seconds = Number(raw);
17093
+ if (!Number.isFinite(seconds)) {
17094
+ Log.warning(
17095
+ `${GRAPH_CONNECT_TIMEOUT_VARIABLE}="${raw}" is not a number of seconds \u2014 bounding graph connects at the ${DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS}s default instead`
17096
+ );
17097
+ return DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS;
17098
+ }
17099
+ return seconds <= 0 ? null : seconds;
17100
+ }
17101
+ var GRAPH_CONNECT_TIMEOUT_VARIABLE, DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS;
17102
+ var init_connectTimeout2 = __esm({
17103
+ "../orm/src/graph/connectTimeout.ts"() {
17104
+ "use strict";
17105
+ init_src3();
17106
+ GRAPH_CONNECT_TIMEOUT_VARIABLE = "TINA4_GRAPH_CONNECT_TIMEOUT";
17107
+ DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS = 10;
17108
+ }
17109
+ });
17110
+
17111
+ // ../orm/src/graph/adapters/ultipa.ts
17112
+ var ultipa_exports = {};
17113
+ __export(ultipa_exports, {
17114
+ UltipaGraphAdapter: () => UltipaGraphAdapter
17115
+ });
17116
+ function propClause(properties) {
17117
+ const props = properties ?? {};
17118
+ const keys = Object.keys(props);
17119
+ if (keys.length === 0) return { clause: "{}", params: {} };
17120
+ const pairs = keys.map((key) => `${key}: $p_${key}`).join(", ");
17121
+ const params = {};
17122
+ for (const key of keys) params[`p_${key}`] = props[key];
17123
+ return { clause: `{${pairs}}`, params };
17124
+ }
17125
+ function errorMessage(exc) {
17126
+ if (exc instanceof Error) return exc.message;
17127
+ return String(exc);
17128
+ }
17129
+ var DRIVER_PACKAGE, driver, UltipaClient, UltipaConnectError, UNBOUNDED_CONNECT_SECONDS, UltipaGraphAdapter;
17130
+ var init_ultipa = __esm({
17131
+ async "../orm/src/graph/adapters/ultipa.ts"() {
17132
+ "use strict";
17133
+ init_shapes();
17134
+ init_errors();
17135
+ init_connectTimeout2();
17136
+ DRIVER_PACKAGE = "tina4-ultipa";
17137
+ driver = await import(DRIVER_PACKAGE);
17138
+ UltipaClient = driver.UltipaClient;
17139
+ UltipaConnectError = driver.UltipaConnectError;
17140
+ UNBOUNDED_CONNECT_SECONDS = 31536e4;
17141
+ UltipaGraphAdapter = class {
17142
+ url;
17143
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17144
+ client;
17145
+ lastError = null;
17146
+ constructor(graphUrl, credentials = {}) {
17147
+ this.url = graphUrl;
17148
+ const timeout = resolveGraphConnectTimeout();
17149
+ this.client = new UltipaClient({
17150
+ host: graphUrl.host,
17151
+ port: graphUrl.port,
17152
+ username: graphUrl.username || credentials.username || null,
17153
+ password: graphUrl.password || credentials.password || null,
17154
+ graph: graphUrl.graph,
17155
+ connectTimeout: timeout ?? UNBOUNDED_CONNECT_SECONDS,
17156
+ useTls: graphUrl.useTls
17157
+ });
17158
+ }
17159
+ // -- connection + raw pass-through -------------------------------------
17160
+ async run(gql, params = null, readOnly = true) {
17161
+ try {
17162
+ await this.client.connect();
17163
+ } catch (exc) {
17164
+ this.lastError = errorMessage(exc);
17165
+ if (exc instanceof UltipaConnectError || exc?.name === "UltipaConnectError") {
17166
+ const elapsed = typeof exc?.elapsed === "number" ? exc.elapsed : 0;
17167
+ throw new GraphConnectTimeout(
17168
+ `Graph connect to ${this.url.host}:${this.url.port} timed out after ${elapsed.toFixed(1)}s (${GRAPH_CONNECT_TIMEOUT_VARIABLE}). Raise ${GRAPH_CONNECT_TIMEOUT_VARIABLE} if the server is simply slow, or set it to 0 to wait indefinitely.`,
17169
+ exc
17170
+ );
17171
+ }
17172
+ throw new GraphError(this.lastError, exc);
17173
+ }
17174
+ try {
17175
+ return await this.client.query(gql, { params: params ?? null, readOnly });
17176
+ } catch (exc) {
17177
+ this.lastError = errorMessage(exc);
17178
+ throw new GraphError(this.lastError, exc);
17179
+ }
17180
+ }
17181
+ async query(text, params = null) {
17182
+ const result = await this.run(text, params, true);
17183
+ return new GraphResult(result.dicts(), result.columns);
17184
+ }
17185
+ async execute(text, params = null) {
17186
+ const result = await this.run(text, params, false);
17187
+ return new GraphResult(result.dicts(), result.columns);
17188
+ }
17189
+ // -- portable node/edge/traverse core (GQL) ----------------------------
17190
+ nodeFromRow(row) {
17191
+ if (row === null || row === void 0) return null;
17192
+ return new GraphNode(
17193
+ String(row.id),
17194
+ row.labels ?? [],
17195
+ row.props ?? {}
17196
+ );
17197
+ }
17198
+ async addNode(label, properties = null) {
17199
+ const { clause, params } = propClause(properties);
17200
+ const gql = `INSERT (n:\`${label}\` ${clause}) RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
17201
+ const rows = (await this.run(gql, params, false)).dicts();
17202
+ return rows.length ? this.nodeFromRow(rows[0]) : null;
17203
+ }
17204
+ async addEdge(fromId, toId, type2, properties = null) {
17205
+ const { clause, params } = propClause(properties);
17206
+ params.from_id = fromId;
17207
+ params.to_id = toId;
17208
+ const gql = `MATCH (a), (b) WHERE id(a) = $from_id AND id(b) = $to_id INSERT (a)-[e:\`${type2}\` ${clause}]->(b) RETURN id(e) AS id, type(e) AS type, id(a) AS f, id(b) AS t, properties(e) AS props`;
17209
+ const rows = (await this.run(gql, params, false)).dicts();
17210
+ if (rows.length === 0) return null;
17211
+ const row = rows[0];
17212
+ return new GraphEdge(
17213
+ String(row.id),
17214
+ String(row.type),
17215
+ String(row.f),
17216
+ String(row.t),
17217
+ row.props ?? {}
17218
+ );
17219
+ }
17220
+ async getNode(nodeId) {
17221
+ const gql = `MATCH (n) WHERE id(n) = $id RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
17222
+ const rows = (await this.run(gql, { id: nodeId }, true)).dicts();
17223
+ return rows.length ? this.nodeFromRow(rows[0]) : null;
17224
+ }
17225
+ async updateNode(nodeId, properties) {
17226
+ const props = properties ?? {};
17227
+ const keys = Object.keys(props);
17228
+ const sets = keys.map((key) => `n.${key} = $p_${key}`).join(", ");
17229
+ const params = { id: nodeId };
17230
+ for (const key of keys) params[`p_${key}`] = props[key];
17231
+ const gql = `MATCH (n) WHERE id(n) = $id SET ${sets} RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
17232
+ const rows = (await this.run(gql, params, false)).dicts();
17233
+ return rows.length ? this.nodeFromRow(rows[0]) : null;
17234
+ }
17235
+ async deleteNode(nodeId) {
17236
+ const gql = "MATCH (n) WHERE id(n) = $id DETACH DELETE n";
17237
+ await this.run(gql, { id: nodeId }, false);
17238
+ return true;
17239
+ }
17240
+ async neighbors(nodeId, options = {}) {
17241
+ const direction = options.direction ?? "both";
17242
+ const limit = options.limit ?? 100;
17243
+ const edge = options.edgeType ? `:\`${options.edgeType}\`` : "";
17244
+ const pattern = {
17245
+ out: `(n)-[${edge}]->(m)`,
17246
+ in: `(n)<-[${edge}]-(m)`,
17247
+ both: `(n)-[${edge}]-(m)`
17248
+ }[direction];
17249
+ const gql = `MATCH ${pattern} WHERE id(n) = $id RETURN DISTINCT id(m) AS id, labels(m) AS labels, properties(m) AS props LIMIT ${Math.trunc(limit)}`;
17250
+ const rows = (await this.run(gql, { id: nodeId }, true)).dicts();
17251
+ return rows.map((row) => this.nodeFromRow(row)).filter((node) => node !== null);
17252
+ }
17253
+ async traverse(startId, options = {}) {
17254
+ const depth = options.depth ?? 1;
17255
+ const direction = options.direction ?? "both";
17256
+ const limit = options.limit ?? 1e3;
17257
+ const edge = options.edgeType ? `:\`${options.edgeType}\`` : "";
17258
+ const quant = `{1,${Math.trunc(depth)}}`;
17259
+ const pattern = {
17260
+ out: `(n)-[${edge}]->${quant}(m)`,
17261
+ in: `(n)<-[${edge}]-${quant}(m)`,
17262
+ both: `(n)-[${edge}]-${quant}(m)`
17263
+ }[direction];
17264
+ const gql = `MATCH ${pattern} WHERE id(n) = $start RETURN DISTINCT id(m) AS id, labels(m) AS labels, properties(m) AS props LIMIT ${Math.trunc(limit)}`;
17265
+ const rows = (await this.run(gql, { start: startId }, true)).dicts();
17266
+ return rows.map((row) => this.nodeFromRow(row)).filter((node) => node !== null);
17267
+ }
17268
+ close() {
17269
+ this.client.close();
17270
+ }
17271
+ getError() {
17272
+ return this.lastError;
17273
+ }
17274
+ };
17275
+ }
17276
+ });
17277
+
17278
+ // ../orm/src/graph/adapters/bolt.ts
17279
+ var bolt_exports = {};
17280
+ __export(bolt_exports, {
17281
+ BoltGraphAdapter: () => BoltGraphAdapter
17282
+ });
17283
+ function errorMessage2(exc) {
17284
+ if (exc instanceof Error) return exc.message;
17285
+ return String(exc);
17286
+ }
17287
+ function boltId(id) {
17288
+ const value = Number(id);
17289
+ return Number.isNaN(value) ? -1 : value;
17290
+ }
17291
+ var DRIVER_PACKAGE2, driverModule, neo4j, BoltGraphAdapter;
17292
+ var init_bolt = __esm({
17293
+ async "../orm/src/graph/adapters/bolt.ts"() {
17294
+ "use strict";
17295
+ init_shapes();
17296
+ init_errors();
17297
+ init_connectTimeout2();
17298
+ DRIVER_PACKAGE2 = "neo4j-driver";
17299
+ driverModule = await import(DRIVER_PACKAGE2);
17300
+ neo4j = driverModule.default ?? driverModule;
17301
+ BoltGraphAdapter = class {
17302
+ url;
17303
+ database;
17304
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17305
+ driver;
17306
+ lastError = null;
17307
+ constructor(graphUrl, credentials = {}) {
17308
+ this.url = graphUrl;
17309
+ this.database = graphUrl.graph || null;
17310
+ const user = graphUrl.username || credentials.username || "neo4j";
17311
+ const pwd = graphUrl.password || credentials.password || "";
17312
+ const scheme = graphUrl.useTls ? "bolt+s" : "bolt";
17313
+ const uri = `${scheme}://${graphUrl.host}:${graphUrl.port}`;
17314
+ const timeout = resolveGraphConnectTimeout();
17315
+ const config = { disableLosslessIntegers: true };
17316
+ if (timeout !== null) {
17317
+ const ms = Math.max(1, Math.ceil(timeout * 1e3));
17318
+ config.connectionTimeout = ms;
17319
+ config.connectionAcquisitionTimeout = ms;
17320
+ config.maxTransactionRetryTime = ms;
17321
+ }
17322
+ this.driver = neo4j.driver(uri, neo4j.auth.basic(user, pwd), config);
17323
+ }
17324
+ // -- connection + raw pass-through -------------------------------------
17325
+ async run(cypher, params = null) {
17326
+ const session = this.database ? this.driver.session({ database: this.database }) : this.driver.session();
17327
+ try {
17328
+ const result = await session.run(cypher, params ?? {});
17329
+ return result.records.map((record) => record.toObject());
17330
+ } catch (exc) {
17331
+ this.lastError = errorMessage2(exc);
17332
+ const code = exc?.code ?? "";
17333
+ const message = this.lastError.toLowerCase();
17334
+ if (code === "ServiceUnavailable" || code === neo4j.error?.SERVICE_UNAVAILABLE || message.includes("timed out") || message.includes("timeout")) {
17335
+ throw new GraphConnectTimeout(
17336
+ `Graph connect to ${this.url.host}:${this.url.port} timed out (${GRAPH_CONNECT_TIMEOUT_VARIABLE}). Raise ${GRAPH_CONNECT_TIMEOUT_VARIABLE} if the server is simply slow, or set it to 0 to wait indefinitely.`,
17337
+ exc
17338
+ );
17339
+ }
17340
+ throw new GraphError(this.lastError, exc);
17341
+ } finally {
17342
+ await session.close();
17343
+ }
17344
+ }
17345
+ async query(text, params = null) {
17346
+ const rows = await this.run(text, params);
17347
+ const columns = rows.length ? Object.keys(rows[0]) : [];
17348
+ return new GraphResult(rows, columns);
17349
+ }
17350
+ async execute(text, params = null) {
17351
+ return this.query(text, params);
17352
+ }
17353
+ // -- portable node/edge/traverse core (Cypher) -------------------------
17354
+ nodeFromRow(row) {
17355
+ if (row === null || row === void 0) return null;
17356
+ return new GraphNode(
17357
+ String(row.id),
17358
+ row.labels ?? [],
17359
+ row.props ?? {}
17360
+ );
17361
+ }
17362
+ async addNode(label, properties = null) {
17363
+ const cypher = `CREATE (n:\`${label}\` $props) RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
17364
+ const rows = await this.run(cypher, { props: properties ?? {} });
17365
+ return rows.length ? this.nodeFromRow(rows[0]) : null;
17366
+ }
17367
+ async addEdge(fromId, toId, type2, properties = null) {
17368
+ const cypher = `MATCH (a), (b) WHERE id(a) = $from_id AND id(b) = $to_id CREATE (a)-[e:\`${type2}\` $props]->(b) RETURN id(e) AS id, type(e) AS type, id(a) AS f, id(b) AS t, properties(e) AS props`;
17369
+ const rows = await this.run(cypher, {
17370
+ from_id: boltId(fromId),
17371
+ to_id: boltId(toId),
17372
+ props: properties ?? {}
17373
+ });
17374
+ if (rows.length === 0) return null;
17375
+ const row = rows[0];
17376
+ return new GraphEdge(
17377
+ String(row.id),
17378
+ String(row.type),
17379
+ String(row.f),
17380
+ String(row.t),
17381
+ row.props ?? {}
17382
+ );
17383
+ }
17384
+ async getNode(nodeId) {
17385
+ const cypher = `MATCH (n) WHERE id(n) = $id RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
17386
+ const rows = await this.run(cypher, { id: boltId(nodeId) });
17387
+ return rows.length ? this.nodeFromRow(rows[0]) : null;
17388
+ }
17389
+ async updateNode(nodeId, properties) {
17390
+ const cypher = `MATCH (n) WHERE id(n) = $id SET n += $props RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
17391
+ const rows = await this.run(cypher, { id: boltId(nodeId), props: properties ?? {} });
17392
+ return rows.length ? this.nodeFromRow(rows[0]) : null;
17393
+ }
17394
+ async deleteNode(nodeId) {
17395
+ await this.run("MATCH (n) WHERE id(n) = $id DETACH DELETE n", { id: boltId(nodeId) });
17396
+ return true;
17397
+ }
17398
+ async neighbors(nodeId, options = {}) {
17399
+ const direction = options.direction ?? "both";
17400
+ const limit = options.limit ?? 100;
17401
+ const edge = options.edgeType ? `:\`${options.edgeType}\`` : "";
17402
+ const pattern = {
17403
+ out: `(n)-[${edge}]->(m)`,
17404
+ in: `(n)<-[${edge}]-(m)`,
17405
+ both: `(n)-[${edge}]-(m)`
17406
+ }[direction];
17407
+ const cypher = `MATCH ${pattern} WHERE id(n) = $id RETURN DISTINCT id(m) AS id, labels(m) AS labels, properties(m) AS props LIMIT ${Math.trunc(limit)}`;
17408
+ const rows = await this.run(cypher, { id: boltId(nodeId) });
17409
+ return rows.map((row) => this.nodeFromRow(row)).filter((node) => node !== null);
17410
+ }
17411
+ async traverse(startId, options = {}) {
17412
+ const depth = options.depth ?? 1;
17413
+ const direction = options.direction ?? "both";
17414
+ const limit = options.limit ?? 1e3;
17415
+ const edge = options.edgeType ? `:\`${options.edgeType}\`` : "";
17416
+ const range = `*1..${Math.trunc(depth)}`;
17417
+ const arrow = {
17418
+ out: `-[${edge}${range}]->`,
17419
+ in: `<-[${edge}${range}]-`,
17420
+ both: `-[${edge}${range}]-`
17421
+ }[direction];
17422
+ const cypher = `MATCH (n)${arrow}(m) WHERE id(n) = $start RETURN DISTINCT id(m) AS id, labels(m) AS labels, properties(m) AS props LIMIT ${Math.trunc(limit)}`;
17423
+ const rows = await this.run(cypher, { start: boltId(startId) });
17424
+ return rows.map((row) => this.nodeFromRow(row)).filter((node) => node !== null);
17425
+ }
17426
+ async close() {
17427
+ await this.driver.close();
17428
+ }
17429
+ getError() {
17430
+ return this.lastError;
17431
+ }
17432
+ };
17433
+ }
17434
+ });
17435
+
17436
+ // ../orm/src/graph/adapters/arango.ts
17437
+ var arango_exports = {};
17438
+ __export(arango_exports, {
17439
+ ArangoGraphAdapter: () => ArangoGraphAdapter
17440
+ });
17441
+ function cleanProps(doc) {
17442
+ const props = {};
17443
+ for (const [key, value] of Object.entries(doc)) {
17444
+ if (!RESERVED.has(key)) props[key] = value;
17445
+ }
17446
+ return props;
17447
+ }
17448
+ function errorMessage3(exc) {
17449
+ if (exc instanceof Error) return exc.message;
17450
+ return String(exc);
17451
+ }
17452
+ var DRIVER_PACKAGE3, driverModule2, ArangoDatabase, VERTEX_COLLECTION, EDGE_COLLECTION, RESERVED, ArangoGraphAdapter;
17453
+ var init_arango = __esm({
17454
+ async "../orm/src/graph/adapters/arango.ts"() {
17455
+ "use strict";
17456
+ init_shapes();
17457
+ init_errors();
17458
+ init_connectTimeout2();
17459
+ DRIVER_PACKAGE3 = "arangojs";
17460
+ driverModule2 = await import(DRIVER_PACKAGE3);
17461
+ ArangoDatabase = driverModule2.Database ?? driverModule2.default?.Database;
17462
+ VERTEX_COLLECTION = "tina4_nodes";
17463
+ EDGE_COLLECTION = "tina4_edges";
17464
+ RESERVED = /* @__PURE__ */ new Set(["_id", "_key", "_rev", "_from", "_to", "_labels", "_type"]);
17465
+ ArangoGraphAdapter = class {
17466
+ url;
17467
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17468
+ db;
17469
+ ensured = false;
17470
+ lastError = null;
17471
+ constructor(graphUrl, credentials = {}) {
17472
+ this.url = graphUrl;
17473
+ const scheme = graphUrl.useTls ? "https" : "http";
17474
+ const user = graphUrl.username || credentials.username || "root";
17475
+ const pwd = graphUrl.password || credentials.password || "";
17476
+ const database = graphUrl.graph || "_system";
17477
+ const timeout = resolveGraphConnectTimeout();
17478
+ const config = {
17479
+ url: `${scheme}://${graphUrl.host}:${graphUrl.port}`,
17480
+ databaseName: database,
17481
+ auth: { username: user, password: pwd }
17482
+ };
17483
+ if (timeout !== null) config.timeout = Math.max(1, Math.ceil(timeout * 1e3));
17484
+ this.db = new ArangoDatabase(config);
17485
+ }
17486
+ connectOrError(exc) {
17487
+ const text = errorMessage3(exc).toLowerCase();
17488
+ if (text.includes("timed out") || text.includes("timeout") || text.includes("connection") || text.includes("econnrefused") || text.includes("etimedout") || text.includes("max retries")) {
17489
+ return new GraphConnectTimeout(
17490
+ `Graph connect to ${this.url.host}:${this.url.port} timed out (${GRAPH_CONNECT_TIMEOUT_VARIABLE}). Raise ${GRAPH_CONNECT_TIMEOUT_VARIABLE} if the server is simply slow, or set it to 0 to wait indefinitely.`,
17491
+ exc
17492
+ );
17493
+ }
17494
+ return new GraphError(errorMessage3(exc), exc);
17495
+ }
17496
+ async ensureCollections() {
17497
+ if (this.ensured) return;
17498
+ try {
17499
+ const nodes = this.db.collection(VERTEX_COLLECTION);
17500
+ if (!await nodes.exists()) await this.db.createCollection(VERTEX_COLLECTION);
17501
+ const edges = this.db.collection(EDGE_COLLECTION);
17502
+ if (!await edges.exists()) await this.db.createEdgeCollection(EDGE_COLLECTION);
17503
+ this.ensured = true;
17504
+ } catch (exc) {
17505
+ this.lastError = errorMessage3(exc);
17506
+ throw this.connectOrError(exc);
17507
+ }
17508
+ }
17509
+ async aql(query, bind2 = null) {
17510
+ await this.ensureCollections();
17511
+ try {
17512
+ const cursor = await this.db.query({ query, bindVars: bind2 ?? {} });
17513
+ return await cursor.all();
17514
+ } catch (exc) {
17515
+ this.lastError = errorMessage3(exc);
17516
+ throw this.connectOrError(exc);
17517
+ }
17518
+ }
17519
+ async query(text, params = null) {
17520
+ const rows = await this.aql(text, params);
17521
+ const first = rows[0];
17522
+ const columns = rows.length && first && typeof first === "object" ? Object.keys(first) : [];
17523
+ return new GraphResult(rows, columns);
17524
+ }
17525
+ async execute(text, params = null) {
17526
+ return this.query(text, params);
17527
+ }
17528
+ // -- portable node/edge/traverse core (AQL) ----------------------------
17529
+ nodeFromDoc(doc) {
17530
+ if (doc === null || doc === void 0) return null;
17531
+ return new GraphNode(
17532
+ String(doc._id),
17533
+ doc._labels ?? [],
17534
+ cleanProps(doc)
17535
+ );
17536
+ }
17537
+ async addNode(label, properties = null) {
17538
+ const doc = { ...properties ?? {}, _labels: [label] };
17539
+ const rows = await this.aql(`INSERT @doc INTO ${VERTEX_COLLECTION} RETURN NEW`, { doc });
17540
+ return rows.length ? this.nodeFromDoc(rows[0]) : null;
17541
+ }
17542
+ async addEdge(fromId, toId, type2, properties = null) {
17543
+ const doc = { ...properties ?? {}, _from: fromId, _to: toId, _type: type2 };
17544
+ const rows = await this.aql(`INSERT @doc INTO ${EDGE_COLLECTION} RETURN NEW`, { doc });
17545
+ if (rows.length === 0) return null;
17546
+ const row = rows[0];
17547
+ return new GraphEdge(
17548
+ String(row._id),
17549
+ String(row._type),
17550
+ String(row._from),
17551
+ String(row._to),
17552
+ cleanProps(row)
17553
+ );
17554
+ }
17555
+ async getNode(nodeId) {
17556
+ const rows = await this.aql("RETURN DOCUMENT(@id)", { id: nodeId });
17557
+ return rows.length && rows[0] ? this.nodeFromDoc(rows[0]) : null;
17558
+ }
17559
+ async updateNode(nodeId, properties) {
17560
+ const rows = await this.aql(
17561
+ `UPDATE PARSE_IDENTIFIER(@id).key WITH @props IN ${VERTEX_COLLECTION} RETURN NEW`,
17562
+ { id: nodeId, props: properties ?? {} }
17563
+ );
17564
+ return rows.length ? this.nodeFromDoc(rows[0]) : null;
17565
+ }
17566
+ async deleteNode(nodeId) {
17567
+ await this.aql(
17568
+ `FOR e IN ${EDGE_COLLECTION} FILTER e._from == @id OR e._to == @id REMOVE e IN ${EDGE_COLLECTION}`,
17569
+ { id: nodeId }
17570
+ );
17571
+ await this.aql(
17572
+ `REMOVE PARSE_IDENTIFIER(@id).key IN ${VERTEX_COLLECTION}`,
17573
+ { id: nodeId }
17574
+ );
17575
+ return true;
17576
+ }
17577
+ async neighbors(nodeId, options = {}) {
17578
+ const direction = options.direction ?? "both";
17579
+ const limit = options.limit ?? 100;
17580
+ const arangoDir = { out: "OUTBOUND", in: "INBOUND", both: "ANY" }[direction];
17581
+ const typeFilter = options.edgeType ? "FILTER e._type == @etype " : "";
17582
+ const bind2 = { start: nodeId, limit: Math.trunc(limit) };
17583
+ if (options.edgeType) bind2.etype = options.edgeType;
17584
+ const rows = await this.aql(
17585
+ `FOR v, e IN 1..1 ${arangoDir} @start ${EDGE_COLLECTION} ${typeFilter}LIMIT @limit RETURN DISTINCT v`,
17586
+ bind2
17587
+ );
17588
+ return rows.map((doc) => this.nodeFromDoc(doc)).filter((node) => node !== null);
17589
+ }
17590
+ async traverse(startId, options = {}) {
17591
+ const depth = options.depth ?? 1;
17592
+ const direction = options.direction ?? "both";
17593
+ const limit = options.limit ?? 1e3;
17594
+ const arangoDir = { out: "OUTBOUND", in: "INBOUND", both: "ANY" }[direction];
17595
+ const typeFilter = options.edgeType ? "FILTER e._type == @etype " : "";
17596
+ const bind2 = { start: startId, limit: Math.trunc(limit) };
17597
+ if (options.edgeType) bind2.etype = options.edgeType;
17598
+ const rows = await this.aql(
17599
+ `FOR v, e IN 1..${Math.trunc(depth)} ${arangoDir} @start ${EDGE_COLLECTION} ${typeFilter}LIMIT @limit RETURN DISTINCT v`,
17600
+ bind2
17601
+ );
17602
+ return rows.map((doc) => this.nodeFromDoc(doc)).filter((node) => node !== null);
17603
+ }
17604
+ async close() {
17605
+ if (typeof this.db.close === "function") this.db.close();
17606
+ }
17607
+ getError() {
17608
+ return this.lastError;
17609
+ }
17610
+ };
17611
+ }
17612
+ });
17613
+
17614
+ // ../orm/src/graph/graphDatabase.ts
17615
+ var ENGINE_ADAPTERS, GraphDatabase;
17616
+ var init_graphDatabase = __esm({
17617
+ "../orm/src/graph/graphDatabase.ts"() {
17618
+ "use strict";
17619
+ init_graphUrl();
17620
+ init_errors();
17621
+ ENGINE_ADAPTERS = {
17622
+ ultipa: {
17623
+ load: () => init_ultipa().then(() => ultipa_exports),
17624
+ className: "UltipaGraphAdapter",
17625
+ package: "tina4-ultipa",
17626
+ installCommand: "npm install tina4-ultipa"
17627
+ },
17628
+ bolt: {
17629
+ // Neo4j AND Memgraph — both speak Bolt/Cypher over the neo4j-driver package.
17630
+ load: () => init_bolt().then(() => bolt_exports),
17631
+ className: "BoltGraphAdapter",
17632
+ package: "neo4j-driver",
17633
+ installCommand: "npm install neo4j-driver"
17634
+ },
17635
+ arango: {
17636
+ load: () => init_arango().then(() => arango_exports),
17637
+ className: "ArangoGraphAdapter",
17638
+ package: "arangojs",
17639
+ installCommand: "npm install arangojs"
17640
+ }
17641
+ };
17642
+ GraphDatabase = class _GraphDatabase {
17643
+ /**
17644
+ * Parse the URL, pick the engine adapter, connect lazily.
17645
+ *
17646
+ * The engine driver is imported only here (first use of that engine); if it is
17647
+ * absent the error names the package and the install command. Async because the
17648
+ * driver import is dynamic — the connect itself still happens lazily on first
17649
+ * operation (mirroring the relational adapters).
17650
+ */
17651
+ static async create(url, credentials = {}) {
17652
+ const graphUrl = new GraphUrl(url);
17653
+ const registration = ENGINE_ADAPTERS[graphUrl.engine];
17654
+ if (registration === void 0) {
17655
+ throw new GraphError(
17656
+ `No graph adapter for engine '${graphUrl.engine}' yet (scheme '${graphUrl.scheme}'). Available: ${Object.keys(ENGINE_ADAPTERS).sort().join(", ")}.`
17657
+ );
17658
+ }
17659
+ let module;
17660
+ try {
17661
+ module = await registration.load();
17662
+ } catch (cause) {
17663
+ throw new GraphError(
17664
+ `The graph driver for '${graphUrl.engine}' is not installed (${registration.package}). Install it with:
17665
+ ${registration.installCommand}`,
17666
+ cause
17667
+ );
17668
+ }
17669
+ const AdapterClass = module[registration.className];
17670
+ if (AdapterClass === void 0) {
17671
+ throw new GraphError(
17672
+ `The graph adapter '${registration.className}' is missing from its module for engine '${graphUrl.engine}'.`
17673
+ );
17674
+ }
17675
+ return new AdapterClass(graphUrl, credentials);
17676
+ }
17677
+ /** Build from TINA4_GRAPH_URL (+ TINA4_GRAPH_USERNAME/_PASSWORD). */
17678
+ static async fromEnv(envKey = "TINA4_GRAPH_URL") {
17679
+ const url = (process.env[envKey] ?? "").trim();
17680
+ if (url === "") return null;
17681
+ return _GraphDatabase.create(url, {
17682
+ username: process.env.TINA4_GRAPH_USERNAME,
17683
+ password: process.env.TINA4_GRAPH_PASSWORD
17684
+ });
17685
+ }
17686
+ };
17687
+ }
17688
+ });
17689
+
16921
17690
  // ../orm/src/realtime/models/workspace.ts
16922
17691
  var Workspace;
16923
17692
  var init_workspace = __esm({
@@ -17476,6 +18245,7 @@ __export(src_exports, {
17476
18245
  CachedDatabaseAdapter: () => CachedDatabaseAdapter,
17477
18246
  Cursor: () => Cursor,
17478
18247
  DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS: () => DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS,
18248
+ DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS: () => DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS,
17479
18249
  DEFAULT_SRID: () => DEFAULT_SRID,
17480
18250
  Database: () => Database,
17481
18251
  DatabaseResult: () => DatabaseResult,
@@ -17483,6 +18253,14 @@ __export(src_exports, {
17483
18253
  DocStoreDriverMissing: () => DocStoreDriverMissing,
17484
18254
  FakeData: () => FakeData2,
17485
18255
  FirebirdAdapter: () => FirebirdAdapter,
18256
+ GRAPH_CONNECT_TIMEOUT_VARIABLE: () => GRAPH_CONNECT_TIMEOUT_VARIABLE,
18257
+ GraphConnectTimeout: () => GraphConnectTimeout,
18258
+ GraphDatabase: () => GraphDatabase,
18259
+ GraphEdge: () => GraphEdge,
18260
+ GraphError: () => GraphError,
18261
+ GraphNode: () => GraphNode,
18262
+ GraphResult: () => GraphResult,
18263
+ GraphUrl: () => GraphUrl,
17486
18264
  InvalidId: () => InvalidId,
17487
18265
  LocalStorage: () => LocalStorage,
17488
18266
  Migration: () => Migration,
@@ -17564,6 +18342,7 @@ __export(src_exports, {
17564
18342
  resetRequestCaches: () => resetRequestCaches,
17565
18343
  resolveDbPool: () => resolveDbPool,
17566
18344
  resolveFirebirdCharset: () => resolveFirebirdCharset,
18345
+ resolveGraphConnectTimeout: () => resolveGraphConnectTimeout,
17567
18346
  rollback: () => rollback,
17568
18347
  seedModels: () => seedModels,
17569
18348
  seedOrm: () => seedOrm,
@@ -17613,6 +18392,11 @@ var init_src = __esm({
17613
18392
  init_firebird();
17614
18393
  init_mongodb();
17615
18394
  init_odbc();
18395
+ init_graphDatabase();
18396
+ init_graphUrl();
18397
+ init_shapes();
18398
+ init_errors();
18399
+ init_connectTimeout2();
17616
18400
  init_realtime2();
17617
18401
  }
17618
18402
  });
@@ -21308,7 +22092,7 @@ var init_mongoHandler = __esm({
21308
22092
  // ../core/src/sessionHandlers/sqlClient.ts
21309
22093
  import { createRequire as createRequire7 } from "node:module";
21310
22094
  function driverPath(engine) {
21311
- const packageName = DRIVER_PACKAGE[engine];
22095
+ const packageName = DRIVER_PACKAGE4[engine];
21312
22096
  try {
21313
22097
  return requireFromHere.resolve(packageName);
21314
22098
  } catch {
@@ -21332,14 +22116,14 @@ function sqlCommandSync(target, sql, params = [], label = "Database session") {
21332
22116
  return [];
21333
22117
  }
21334
22118
  }
21335
- var SQL_SESSION_ENGINES, CONNECT_TIMEOUT_MS2, DRIVER_PACKAGE, requireFromHere, SQL_WORKER;
22119
+ var SQL_SESSION_ENGINES, CONNECT_TIMEOUT_MS2, DRIVER_PACKAGE4, requireFromHere, SQL_WORKER;
21336
22120
  var init_sqlClient = __esm({
21337
22121
  "../core/src/sessionHandlers/sqlClient.ts"() {
21338
22122
  "use strict";
21339
22123
  init_syncBridge();
21340
22124
  SQL_SESSION_ENGINES = ["sqlite", "postgres", "mysql", "mssql", "firebird"];
21341
22125
  CONNECT_TIMEOUT_MS2 = 3e3;
21342
- DRIVER_PACKAGE = {
22126
+ DRIVER_PACKAGE4 = {
21343
22127
  postgres: "pg",
21344
22128
  mysql: "mysql2",
21345
22129
  mssql: "tedious",
@@ -32070,47 +32854,120 @@ function renderToolbarHtml(ctx) {
32070
32854
  const method = escapeHtml(ctx.method);
32071
32855
  const path8 = escapeHtml(ctx.path);
32072
32856
  const matchedPattern = escapeHtml(ctx.matchedPattern);
32073
- return `<div id="tina4-dev-toolbar" style="position:fixed;bottom:0;left:0;right:0;background:#333;color:#fff;font-family:monospace;font-size:12px;padding:6px 16px;z-index:99999;display:flex;align-items:center;gap:16px;">
32074
- <span id="tina4-ver-btn" style="color:#2e7d32;font-weight:bold;cursor:pointer;text-decoration:underline dotted;" onclick="tina4VersionModal()" title="Click to check for updates">Tina4 v${ctx.version}</span>
32075
- <div id="tina4-ver-modal" style="display:none;position:fixed;bottom:3rem;left:1rem;background:#1e1e2e;border:1px solid #2e7d32;border-radius:8px;padding:16px 20px;z-index:100000;min-width:320px;box-shadow:0 8px 32px rgba(0,0,0,0.5);font-family:monospace;font-size:13px;color:#cdd6f4;">
32076
- <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
32077
- <strong style="color:#89b4fa;">Version Info</strong>
32078
- <span onclick="document.getElementById('tina4-ver-modal').style.display='none'" style="cursor:pointer;color:#888;">&times;</span>
32857
+ const reload = ctx.reload === false ? "0" : "1";
32858
+ return `<link rel="stylesheet" href="/__dev/toolbar.css">
32859
+ <div id="tina4-dev-toolbar" data-reload="${reload}">
32860
+ <span id="tina4-ver-btn" title="Click to check for updates">Tina4 v${ctx.version}</span>
32861
+ <div id="tina4-ver-modal">
32862
+ <div class="t4-modal-head">
32863
+ <strong class="t4-modal-title">Version Info</strong>
32864
+ <span id="tina4-ver-close" class="t4-x">&times;</span>
32079
32865
  </div>
32080
- <div id="tina4-ver-body" style="line-height:1.8;">
32081
- <div>Current: <strong style="color:#a6e3a1;">v${ctx.version}</strong></div>
32082
- <div id="tina4-ver-latest" style="color:#888;">Checking for updates...</div>
32866
+ <div id="tina4-ver-body">
32867
+ <div>Current: <strong class="t4-ok">v${ctx.version}</strong></div>
32868
+ <div id="tina4-ver-latest" class="t4-dim">Checking for updates...</div>
32083
32869
  </div>
32084
32870
  </div>
32085
- <span style="color:#4caf50;">${method}</span>
32871
+ <span class="t4-green">${method}</span>
32086
32872
  <span>${path8}</span>
32087
- <span style="color:#666;">&rarr; ${matchedPattern}</span>
32088
- <span style="color:#ffeb3b;">req:${ctx.requestId}</span>
32089
- <span style="color:#90caf9;">${ctx.routeCount} routes</span>
32090
- <span style="color:#888;">Node.js ${nodeVersion}</span>
32091
- <a href="#" onclick="window.__tina4ToggleOverlay(event)" style="color:#ef9a9a;margin-left:auto;text-decoration:none;cursor:pointer;">Dashboard &#8599;</a>
32092
- <span onclick="this.parentElement.style.display='none'" style="cursor:pointer;color:#888;margin-left:8px;">&#10005;</span>
32873
+ <span class="t4-arrow">&rarr; ${matchedPattern}</span>
32874
+ <span class="t4-yellow">req:${ctx.requestId}</span>
32875
+ <span class="t4-blue">${ctx.routeCount} routes</span>
32876
+ <span class="t4-dim">Node.js ${nodeVersion}</span>
32877
+ <a href="#" id="tina4-dash-link" class="t4-dash">Dashboard &#8599;</a>
32878
+ <span id="tina4-bar-close" class="t4-x t4-bar-close">&#10005;</span>
32093
32879
  </div>
32094
- <script>
32095
- // Overlay open/toggle helper + auto-restore. Persist the dev-admin
32096
- // iframe's open/closed state across parent reloads so saving a file
32097
- // doesn't lose the user's dev-admin context. Cross-framework parity
32098
- // with PHP / Python / Ruby \u2014 same localStorage key.
32099
- (function(){
32880
+ <script src="/__dev/toolbar.js"></script>`;
32881
+ }
32882
+ function toolbarCss() {
32883
+ return `#tina4-dev-toolbar{position:fixed;bottom:0;left:0;right:0;background:#333;color:#fff;font-family:monospace;font-size:12px;padding:6px 16px;z-index:99999;display:flex;align-items:center;gap:16px}
32884
+ #tina4-dev-toolbar a{text-decoration:none}
32885
+ #tina4-ver-btn{color:#2e7d32;font-weight:bold;cursor:pointer;text-decoration:underline dotted}
32886
+ #tina4-ver-modal{display:none;position:fixed;bottom:3rem;left:1rem;background:#1e1e2e;border:1px solid #2e7d32;border-radius:8px;padding:16px 20px;z-index:100000;min-width:320px;box-shadow:0 8px 32px rgba(0,0,0,.5);font-family:monospace;font-size:13px;color:#cdd6f4}
32887
+ .t4-modal-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
32888
+ .t4-modal-title{color:#89b4fa}
32889
+ #tina4-ver-body{line-height:1.8}
32890
+ .t4-x{cursor:pointer;color:#888}
32891
+ .t4-bar-close{margin-left:8px}
32892
+ .t4-green{color:#4caf50}
32893
+ .t4-dim{color:#888}
32894
+ .t4-arrow{color:#666}
32895
+ .t4-yellow{color:#ffeb3b}
32896
+ .t4-blue{color:#90caf9}
32897
+ .t4-ok{color:#a6e3a1}
32898
+ .t4-warn{color:#f9e2af}
32899
+ .t4-err{color:#f38ba8}
32900
+ .t4-purple{color:#cba6f7}
32901
+ .t4-link{color:#89b4fa}
32902
+ .t4-code{background:#313244;padding:2px 6px;border-radius:3px}
32903
+ .t4-note{margin-top:6px}
32904
+ .t4-dash{color:#ef9a9a;margin-left:auto;cursor:pointer}
32905
+ #tina4-dev-panel{position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all .2s}
32906
+ #tina4-dev-panel iframe{width:100%;height:100%;border:1px solid #2e7d32;border-radius:.5rem;box-shadow:0 8px 32px rgba(0,0,0,.5);background:#0f172a}`;
32907
+ }
32908
+ function toolbarJs() {
32909
+ return `(function () {
32910
+ var bar = document.getElementById('tina4-dev-toolbar');
32911
+ if (!bar) { return; }
32912
+
32913
+ var modal = document.getElementById('tina4-ver-modal');
32914
+ function upToDate(el, latest) {
32915
+ el.className = 't4-ok';
32916
+ el.innerHTML = 'Latest: <strong class="t4-ok">v' + latest + '</strong> &mdash; You are up to date!';
32917
+ }
32918
+ function checkVersion() {
32919
+ if (modal.style.display === 'block') { modal.style.display = 'none'; return; }
32920
+ modal.style.display = 'block';
32921
+ var el = document.getElementById('tina4-ver-latest');
32922
+ el.className = 't4-dim';
32923
+ el.textContent = 'Checking for updates...';
32924
+ fetch('/__dev/api/version-check').then(function (r) { return r.json(); }).then(function (d) {
32925
+ var latest = d.latest, current = d.current;
32926
+ if (latest === current) { upToDate(el, latest); return; }
32927
+ var cP = current.split('.').map(Number), lP = latest.split('.').map(Number);
32928
+ var isNewer = false, i, c, l;
32929
+ for (i = 0; i < Math.max(cP.length, lP.length); i++) { c = cP[i] || 0; l = lP[i] || 0; if (l > c) { isNewer = true; break; } if (l < c) { break; } }
32930
+ var isAhead = false;
32931
+ if (!isNewer) { for (i = 0; i < Math.max(cP.length, lP.length); i++) { var c2 = cP[i] || 0, l2 = lP[i] || 0; if (c2 > l2) { isAhead = true; break; } if (c2 < l2) { break; } } }
32932
+ if (isNewer) {
32933
+ var breaking = (lP[0] !== cP[0] || lP[1] !== cP[1]);
32934
+ el.className = '';
32935
+ el.innerHTML = 'Latest: <strong class="t4-warn">v' + latest + '</strong>';
32936
+ if (breaking) {
32937
+ el.innerHTML += '<div class="t4-err t4-note">&#9888; Major/minor version change &mdash; check the <a href="https://github.com/tina4stack/tina4-nodejs/releases" target="_blank" class="t4-link">changelog</a> for breaking changes before upgrading.</div>';
32938
+ } else {
32939
+ el.innerHTML += '<div class="t4-warn t4-note">Patch update available. Run: <code class="t4-code">npm install tina4-nodejs@latest</code></div>';
32940
+ }
32941
+ } else if (isAhead) {
32942
+ el.className = 't4-purple';
32943
+ el.innerHTML = 'You are running <strong class="t4-purple">v' + current + '</strong> (ahead of npm <strong>v' + latest + '</strong> &mdash; not yet published).';
32944
+ } else {
32945
+ upToDate(el, latest);
32946
+ }
32947
+ }).catch(function () {
32948
+ el.className = 't4-err';
32949
+ el.textContent = 'Could not check for updates (offline?)';
32950
+ });
32951
+ }
32952
+ var verBtn = document.getElementById('tina4-ver-btn');
32953
+ if (verBtn) { verBtn.addEventListener('click', checkVersion); }
32954
+ var verClose = document.getElementById('tina4-ver-close');
32955
+ if (verClose) { verClose.addEventListener('click', function () { modal.style.display = 'none'; }); }
32956
+ var barClose = document.getElementById('tina4-bar-close');
32957
+ if (barClose) { barClose.addEventListener('click', function () { bar.style.display = 'none'; }); }
32958
+
32100
32959
  var STATE_KEY = 'tina4_dev_overlay_open';
32101
32960
  function buildOverlay() {
32102
32961
  var c = document.createElement('div');
32103
32962
  c.id = 'tina4-dev-panel';
32104
- c.style.cssText = 'position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all 0.2s';
32105
32963
  var f = document.createElement('iframe');
32106
32964
  f.src = '/__dev';
32107
- f.style.cssText = 'width:100%;height:100%;border:1px solid #2e7d32;border-radius:0.5rem;box-shadow:0 8px 32px rgba(0,0,0,0.5);background:#0f172a';
32108
32965
  c.appendChild(f);
32109
32966
  document.body.appendChild(c);
32110
32967
  return c;
32111
32968
  }
32112
- window.__tina4ToggleOverlay = function(e) {
32113
- if (e) e.preventDefault();
32969
+ function toggleOverlay(e) {
32970
+ if (e) { e.preventDefault(); }
32114
32971
  var p = document.getElementById('tina4-dev-panel');
32115
32972
  if (p) {
32116
32973
  var hide = p.style.display !== 'none';
@@ -32120,137 +32977,61 @@ function renderToolbarHtml(ctx) {
32120
32977
  }
32121
32978
  buildOverlay();
32122
32979
  try { localStorage.setItem(STATE_KEY, '1'); } catch (_) {}
32123
- };
32124
- function restoreIfOpen() {
32125
- try {
32126
- if (location.pathname.indexOf('/__dev') === 0) return;
32127
- if (localStorage.getItem(STATE_KEY) === '1' && !document.getElementById('tina4-dev-panel')) {
32128
- buildOverlay();
32129
- }
32130
- } catch (_) {}
32131
32980
  }
32132
- if (document.readyState === 'loading') {
32133
- document.addEventListener('DOMContentLoaded', restoreIfOpen);
32134
- } else {
32135
- restoreIfOpen();
32136
- }
32137
- })();
32138
- </script>
32139
- <script>
32140
- function tina4VersionModal(){
32141
- var m=document.getElementById('tina4-ver-modal');
32142
- if(m.style.display==='block'){m.style.display='none';return;}
32143
- m.style.display='block';
32144
- var el=document.getElementById('tina4-ver-latest');
32145
- el.innerHTML='Checking for updates...';
32146
- el.style.color='#888';
32147
- fetch('/__dev/api/version-check')
32148
- .then(function(r){return r.json()})
32149
- .then(function(d){
32150
- var latest=d.latest;
32151
- var current=d.current;
32152
- if(latest===current){
32153
- el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> &mdash; You are up to date!';
32154
- el.style.color='#a6e3a1';
32155
- }else{
32156
- var cParts=current.split('.').map(Number);
32157
- var lParts=latest.split('.').map(Number);
32158
- var isNewer=false;
32159
- for(var i=0;i<Math.max(cParts.length,lParts.length);i++){
32160
- var c=cParts[i]||0,l=lParts[i]||0;
32161
- if(l>c){isNewer=true;break;}
32162
- if(l<c)break;
32163
- }
32164
- var isAhead=false;
32165
- if(!isNewer){for(var i=0;i<Math.max(cParts.length,lParts.length);i++){var c2=cParts[i]||0,l2=lParts[i]||0;if(c2>l2){isAhead=true;break;}if(c2<l2)break;}}
32166
- if(isNewer){
32167
- var breaking=(lParts[0]!==cParts[0]||lParts[1]!==cParts[1]);
32168
- el.innerHTML='Latest: <strong style="color:#f9e2af;">v'+latest+'</strong>';
32169
- if(breaking){
32170
- el.innerHTML+='<div style="color:#f38ba8;margin-top:6px;">&#9888; Major/minor version change &mdash; check the <a href="https://github.com/tina4stack/tina4-nodejs/releases" target="_blank" style="color:#89b4fa;">changelog</a> for breaking changes before upgrading.</div>';
32171
- }else{
32172
- el.innerHTML+='<div style="color:#f9e2af;margin-top:6px;">Patch update available. Run: <code style="background:#313244;padding:2px 6px;border-radius:3px;">npm install tina4-nodejs@latest</code></div>';
32173
- }
32174
- }else if(isAhead){
32175
- el.innerHTML='You are running <strong style="color:#cba6f7;">v'+current+'</strong> (ahead of npm <strong>v'+latest+'</strong> &mdash; not yet published).';
32176
- el.style.color='#cba6f7';
32177
- }else{
32178
- el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> &mdash; You are up to date!';
32179
- el.style.color='#a6e3a1';
32180
- }
32181
- }
32182
- })
32183
- .catch(function(){
32184
- el.innerHTML='Could not check for updates (offline?)';
32185
- el.style.color='#f38ba8';
32186
- });
32187
- }
32188
- </script>
32189
- <script>
32190
- (function(){
32191
- // WebSocket-primary dev reloader. The running server re-imports changed
32192
- // src/ routes in-process and pushes a {type,file,mtime} message over
32193
- // /__dev_reload \u2014 no respawn, instant refresh. The mtime poll below is a
32194
- // FALLBACK only, started when the socket is down and stopped on connect.
32195
- var _t4_css_exts=['.css','.scss'],_t4_debounce=null;
32196
- var _t4_interval=3000;
32197
- var _t4_ws=null,_t4_poll_timer=null,_t4_mtime=null;
32198
- function _t4_apply(d){
32199
- d=d||{};
32200
- var f=d.file||'',t=d.type||'';
32201
- var isCss=t==='css'||_t4_css_exts.some(function(e){return f.endsWith(e)});
32202
- if(isCss){
32203
- var links=document.querySelectorAll('link[rel="stylesheet"]');
32204
- links.forEach(function(l){
32205
- var href=l.getAttribute('href');
32206
- if(href){l.setAttribute('href',href.split('?')[0]+'?_t4='+(d.mtime||Date.now()))}
32981
+ var dash = document.getElementById('tina4-dash-link');
32982
+ if (dash) { dash.addEventListener('click', toggleOverlay); }
32983
+ try {
32984
+ if (location.pathname.indexOf('/__dev') !== 0
32985
+ && localStorage.getItem(STATE_KEY) === '1'
32986
+ && !document.getElementById('tina4-dev-panel')) {
32987
+ buildOverlay();
32988
+ }
32989
+ } catch (_) {}
32990
+
32991
+ if (bar.getAttribute('data-reload') !== '1') { return; }
32992
+ var cssExts = ['.css', '.scss'], debounce = null, interval = 3000;
32993
+ var ws = null, pollTimer = null, mtime = null;
32994
+ function apply(d) {
32995
+ d = d || {};
32996
+ var f = d.file || '', t = d.type || '';
32997
+ var isCss = t === 'css' || cssExts.some(function (e) { return f.endsWith(e); });
32998
+ if (isCss) {
32999
+ document.querySelectorAll('link[rel="stylesheet"]').forEach(function (l) {
33000
+ var href = l.getAttribute('href');
33001
+ if (href) { l.setAttribute('href', href.split('?')[0] + '?_t4=' + (d.mtime || Date.now())); }
32207
33002
  });
32208
- }else{
33003
+ } else {
32209
33004
  location.reload();
32210
33005
  }
32211
33006
  }
32212
- function _t4_poll(){
32213
- fetch('/__dev/api/mtime').then(function(r){return r.json()}).then(function(d){
32214
- // Sentinel: first poll only records the baseline. Use !== (not >) so
32215
- // the first change after load is not swallowed and a counter reset on
32216
- // server restart still triggers a reload.
32217
- if(_t4_mtime===null){_t4_mtime=d.mtime;return;}
32218
- if(d.mtime!==_t4_mtime){
32219
- _t4_mtime=d.mtime;
32220
- if(_t4_debounce)clearTimeout(_t4_debounce);
32221
- _t4_debounce=setTimeout(function(){_t4_apply(d);},500);
32222
- }
32223
- }).catch(function(){});
32224
- }
32225
- function _t4_startPoll(){
32226
- if(_t4_poll_timer)return;
32227
- _t4_mtime=null;
32228
- _t4_poll_timer=setInterval(_t4_poll,_t4_interval);
32229
- }
32230
- function _t4_stopPoll(){
32231
- if(_t4_poll_timer){clearInterval(_t4_poll_timer);_t4_poll_timer=null;}
32232
- }
32233
- function _t4_connect(){
32234
- var url=(location.protocol==='https:'?'wss':'ws')+'://'+location.host+'/__dev_reload';
32235
- try{_t4_ws=new WebSocket(url);}catch(_){_t4_startPoll();return;}
32236
- _t4_ws.addEventListener('open',function(){_t4_stopPoll();});
32237
- _t4_ws.addEventListener('message',function(ev){
32238
- var d=null;
32239
- try{d=typeof ev.data==='string'?JSON.parse(ev.data):null;}catch(_){}
32240
- if(!d)return;
32241
- if(d.type==='reload'||d.type==='change'||d.type==='css'){
32242
- if(_t4_debounce)clearTimeout(_t4_debounce);
32243
- _t4_debounce=setTimeout(function(){_t4_apply(d);},150);
33007
+ function poll() {
33008
+ fetch('/__dev/api/mtime').then(function (r) { return r.json(); }).then(function (d) {
33009
+ if (mtime === null) { mtime = d.mtime; return; }
33010
+ if (d.mtime !== mtime) { mtime = d.mtime; if (debounce) { clearTimeout(debounce); } debounce = setTimeout(function () { apply(d); }, 500); }
33011
+ }).catch(function () {});
33012
+ }
33013
+ function startPoll() { if (pollTimer) { return; } mtime = null; pollTimer = setInterval(poll, interval); }
33014
+ function stopPoll() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }
33015
+ function connect() {
33016
+ var url = (location.protocol === 'https:' ? 'wss' : 'ws') + '://' + location.host + '/__dev_reload';
33017
+ try { ws = new WebSocket(url); } catch (_) { startPoll(); return; }
33018
+ ws.addEventListener('open', function () { stopPoll(); });
33019
+ ws.addEventListener('message', function (ev) {
33020
+ var d = null;
33021
+ try { d = typeof ev.data === 'string' ? JSON.parse(ev.data) : null; } catch (_) {}
33022
+ if (!d) { return; }
33023
+ if (d.type === 'reload' || d.type === 'change' || d.type === 'css') {
33024
+ if (debounce) { clearTimeout(debounce); }
33025
+ debounce = setTimeout(function () { apply(d); }, 150);
32244
33026
  }
32245
33027
  });
32246
- _t4_ws.addEventListener('close',function(){_t4_ws=null;_t4_startPoll();setTimeout(_t4_connect,2000);});
32247
- _t4_ws.addEventListener('error',function(){try{_t4_ws&&_t4_ws.close();}catch(_){}});
33028
+ ws.addEventListener('close', function () { ws = null; startPoll(); setTimeout(connect, 2000); });
33029
+ ws.addEventListener('error', function () { try { ws && ws.close(); } catch (_) {} });
32248
33030
  }
32249
- _t4_connect();
32250
- })();
32251
- </script>`;
33031
+ connect();
33032
+ })();`;
32252
33033
  }
32253
- var cpuCount, DEV_SAFE_METHODS, DEV_MCP_PREFIXES, DEV_SECRET_BASENAMES, DEV_SECRET_SUFFIXES, MessageLog, RequestInspector, ErrorTracker, DevMailboxStore, DevQueue, WsTracker, DevAdmin, handleDashboard, _reloadMtime, _reloadFile, handleMtime, handleReload, handleMessages, handleMessagesClear, handleRequests, handleRequestsClear, handleSystem, handleMessagesSearch, handleQueue, handleQueueTopics, handleQueueDeadLetters, handleQueueRetry, handleQueuePurge, handleQueueReplay, handleMailbox, handleMailboxRead, handleMailboxSeed, handleMailboxClear, handleTable, handleTables, handleSeed, handleQuery, handleBroken, handleBrokenResolve, handleBrokenClear, handleWebsockets, handleWebsocketsDisconnect, handleTool, handleChat, DEFAULT_MCP_URL, handleGroundingStatus, handleGroundingToken, handleMigrate, handleSeedRun, handleTest, handleThreads, handleThreadsSub, handleConnections, handleConnectionsTest, handleConnectionsSave, __devAdminFilename, __devAdminDirname, handleGalleryList, handleVersionCheck, handleThoughts, handleSuperviseStub, handleExecute, DEV_FILES_IGNORED, handleFiles, DEV_ADMIN_LANG_MAP, handleFileRead, handleFileSave, handleFileRaw, handleFileRename, handleFileDelete, handleDepsSearch, handleDepsInstall, handleGitStatus, handleMcpTools, handleMcpCall, handleMcpStreamable, handleMcpDelete, handleMcpGet405, handleMcpLegacyMessage, handleMcpSse, handleScaffoldList, handleScaffoldRun, handlePlanCurrent, handlePlanList, handlePlanCreate, handlePlanSwitch, handlePlanCompleteStep, handlePlanAddStep, handlePlanNote, handlePlanArchive, handlePlanRead, handlePlanFlesh, handleIndexRebuild, handleIndexSearch, handleIndexFile, handleIndexOverview, handleDocsSearch, handleDocsClass, handleDocsMethod, handleDocsIndex, handleDocsWellKnown, handleDevAdminJs;
33034
+ var cpuCount, DEV_SAFE_METHODS, DEV_MCP_PREFIXES, DEV_SECRET_BASENAMES, DEV_SECRET_SUFFIXES, MessageLog, RequestInspector, ErrorTracker, DevMailboxStore, DevQueue, WsTracker, DevAdmin, handleDashboard, _reloadMtime, _reloadFile, handleMtime, handleReload, handleMessages, handleMessagesClear, handleRequests, handleRequestsClear, handleSystem, handleMessagesSearch, handleQueue, handleQueueTopics, handleQueueDeadLetters, handleQueueRetry, handleQueuePurge, handleQueueReplay, handleMailbox, handleMailboxRead, handleMailboxSeed, handleMailboxClear, handleTable, handleTables, handleSeed, handleQuery, handleBroken, handleBrokenResolve, handleBrokenClear, handleWebsockets, handleWebsocketsDisconnect, handleTool, handleChat, DEFAULT_MCP_URL, handleGroundingStatus, handleGroundingToken, handleMigrate, handleSeedRun, handleTest, handleThreads, handleThreadsSub, handleConnections, handleConnectionsTest, handleConnectionsSave, __devAdminFilename, __devAdminDirname, handleGalleryList, handleVersionCheck, handleThoughts, handleSuperviseStub, handleExecute, DEV_FILES_IGNORED, handleFiles, DEV_ADMIN_LANG_MAP, handleFileRead, handleFileSave, handleFileRaw, handleFileRename, handleFileDelete, handleDepsSearch, handleDepsInstall, handleGitStatus, handleMcpTools, handleMcpCall, handleMcpStreamable, handleMcpDelete, handleMcpGet405, handleMcpLegacyMessage, handleMcpSse, handleScaffoldList, handleScaffoldRun, handlePlanCurrent, handlePlanList, handlePlanCreate, handlePlanSwitch, handlePlanCompleteStep, handlePlanAddStep, handlePlanNote, handlePlanArchive, handlePlanRead, handlePlanFlesh, handleIndexRebuild, handleIndexSearch, handleIndexFile, handleIndexOverview, handleDocsSearch, handleDocsClass, handleDocsMethod, handleDocsIndex, handleDocsWellKnown, handleDevAdminJs, handleToolbarCss, handleToolbarJs;
32254
33035
  var init_devAdmin = __esm({
32255
33036
  "../core/src/devAdmin.ts"() {
32256
33037
  "use strict";
@@ -32716,7 +33497,12 @@ var init_devAdmin = __esm({
32716
33497
  { method: "GET", pattern: "/__dev/api/docs/index", handler: handleDocsIndex },
32717
33498
  { method: "GET", pattern: "/__dev/api/docs/.well-known.json", handler: handleDocsWellKnown },
32718
33499
  // JS asset
32719
- { method: "GET", pattern: "/__dev/js/tina4-dev-admin.min.js", handler: handleDevAdminJs }
33500
+ { method: "GET", pattern: "/__dev/js/tina4-dev-admin.min.js", handler: handleDevAdminJs },
33501
+ // Dev-toolbar assets — served as external CSS/JS so the injected toolbar is
33502
+ // CSP-clean (no inline style=, onclick=, or <script>) under a strict
33503
+ // `default-src 'self'`. Parity with PHP's /__dev/toolbar.css + toolbar.js.
33504
+ { method: "GET", pattern: "/__dev/toolbar.css", handler: handleToolbarCss },
33505
+ { method: "GET", pattern: "/__dev/toolbar.js", handler: handleToolbarJs }
32720
33506
  ];
32721
33507
  for (const route of routes) {
32722
33508
  router.addRoute({
@@ -34152,6 +34938,14 @@ var init_devAdmin = __esm({
34152
34938
  res.raw.writeHead(404, { "Content-Type": "text/plain" });
34153
34939
  res.raw.end("tina4-dev-admin.min.js not found");
34154
34940
  };
34941
+ handleToolbarCss = (_req, res) => {
34942
+ res.raw.writeHead(200, { "Content-Type": "text/css; charset=utf-8", "Cache-Control": "no-cache" });
34943
+ res.raw.end(toolbarCss());
34944
+ };
34945
+ handleToolbarJs = (_req, res) => {
34946
+ res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
34947
+ res.raw.end(toolbarJs());
34948
+ };
34155
34949
  }
34156
34950
  });
34157
34951
 
@@ -36013,7 +36807,11 @@ function injectIntoHtml(ctx, devToolbar, html) {
36013
36807
  path: ctx.pathname,
36014
36808
  matchedPattern: ctx.matchedPattern.value || ctx.pathname,
36015
36809
  requestId: ctx.requestId,
36016
- routeCount: ctx.router.getRoutes().length
36810
+ routeCount: ctx.router.getRoutes().length,
36811
+ // Suppress the live reloader on the AI/stable port (data-reload="0"); the
36812
+ // toolbar JS early-returns when data-reload !== "1". Mirrors PHP's
36813
+ // suppressReload flag.
36814
+ reload: !ctx.isAiPortRequest
36017
36815
  };
36018
36816
  return injectFeedbackWidget(ctx.req, injectDevToolbar(html, toolbarCtx));
36019
36817
  }