tina4-nodejs 3.13.140 → 3.13.142

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 (38) hide show
  1. package/CLAUDE.md +2 -2
  2. package/package.json +1 -1
  3. package/packages/cli/dist/bin.js +438 -453
  4. package/packages/cli/src/bin.ts +4 -14
  5. package/packages/cli/src/commands/build.ts +2 -18
  6. package/packages/cli/src/commands/generate.ts +2 -22
  7. package/packages/cli/src/commands/lint.ts +3 -13
  8. package/packages/cli/src/commands/migrate.ts +2 -6
  9. package/packages/cli/src/commands/migrateRollback.ts +2 -6
  10. package/packages/cli/src/commands/migrateStatus.ts +2 -6
  11. package/packages/cli/src/commands/queue.ts +2 -30
  12. package/packages/cli/src/util.ts +90 -0
  13. package/packages/core/dist/index.js +215 -184
  14. package/packages/core/gallery/queue/src/lib/queueDb.ts +11 -0
  15. package/packages/core/gallery/queue/src/routes/api/gallery/queue/consume/post.ts +2 -5
  16. package/packages/core/gallery/queue/src/routes/api/gallery/queue/fail/post.ts +2 -5
  17. package/packages/core/src/devAdmin.ts +3 -12
  18. package/packages/core/src/dispatchPipeline.ts +2 -18
  19. package/packages/core/src/etag.ts +24 -0
  20. package/packages/core/src/feedback.ts +2 -11
  21. package/packages/core/src/session.ts +22 -66
  22. package/packages/core/src/sessionHandlers/respSessionHandler.ts +94 -0
  23. package/packages/core/src/sessionHandlers/valkeyHandler.ts +22 -73
  24. package/packages/core/src/static.ts +1 -13
  25. package/packages/core/src/upstreamResponse.ts +31 -0
  26. package/packages/core/src/websocket.ts +48 -7
  27. package/packages/orm/dist/index.js +215 -184
  28. package/packages/orm/src/graph/adapters/bolt.ts +8 -29
  29. package/packages/orm/src/graph/adapters/rowMapping.ts +57 -0
  30. package/packages/orm/src/graph/adapters/ultipa.ts +8 -30
  31. package/types/cli/src/util.d.ts +28 -0
  32. package/types/core/src/etag.d.ts +9 -0
  33. package/types/core/src/session.d.ts +2 -19
  34. package/types/core/src/sessionHandlers/respSessionHandler.d.ts +52 -0
  35. package/types/core/src/sessionHandlers/valkeyHandler.d.ts +2 -26
  36. package/types/core/src/upstreamResponse.d.ts +10 -0
  37. package/types/core/src/websocket.d.ts +21 -0
  38. package/types/orm/src/graph/adapters/rowMapping.d.ts +26 -0
@@ -26,6 +26,12 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/.
26
26
  * them as strings, so a Cypher `WHERE id(n) = $id` is fed `Number(id)` back.
27
27
  */
28
28
  import { GraphNode, GraphEdge, GraphResult } from "../shapes.js";
29
+ import {
30
+ errorMessage,
31
+ nodeFromRow as toGraphNode,
32
+ edgeFromRow as toGraphEdge,
33
+ type DriverRow,
34
+ } from "./rowMapping.js";
29
35
  import { GraphError, GraphConnectTimeout } from "../errors.js";
30
36
  import {
31
37
  resolveGraphConnectTimeout,
@@ -52,20 +58,6 @@ const driverModule: any = await import(DRIVER_PACKAGE);
52
58
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
59
  const neo4j: any = driverModule.default ?? driverModule;
54
60
 
55
- interface DriverRow {
56
- id?: unknown;
57
- labels?: unknown;
58
- props?: unknown;
59
- type?: unknown;
60
- f?: unknown;
61
- t?: unknown;
62
- [key: string]: unknown;
63
- }
64
-
65
- function errorMessage(exc: unknown): string {
66
- if (exc instanceof Error) return exc.message;
67
- return String(exc);
68
- }
69
61
 
70
62
  /**
71
63
  * A neutral GraphNode id back to the engine's integer form. Non-numeric ids
@@ -151,12 +143,7 @@ export class BoltGraphAdapter implements GraphAdapter {
151
143
 
152
144
  // -- portable node/edge/traverse core (Cypher) -------------------------
153
145
  private nodeFromRow(row: DriverRow | undefined | null): GraphNode | null {
154
- if (row === null || row === undefined) return null;
155
- return new GraphNode(
156
- String(row.id),
157
- (row.labels as string[]) ?? [],
158
- (row.props as Record<string, unknown>) ?? {},
159
- );
146
+ return toGraphNode(row);
160
147
  }
161
148
 
162
149
  async addNode(
@@ -185,15 +172,7 @@ export class BoltGraphAdapter implements GraphAdapter {
185
172
  to_id: boltId(toId),
186
173
  props: properties ?? {},
187
174
  });
188
- if (rows.length === 0) return null;
189
- const row = rows[0];
190
- return new GraphEdge(
191
- String(row.id),
192
- String(row.type),
193
- String(row.f),
194
- String(row.t),
195
- (row.props as Record<string, unknown>) ?? {},
196
- );
175
+ return toGraphEdge(rows[0]);
197
176
  }
198
177
 
199
178
  async getNode(nodeId: string): Promise<GraphNode | null> {
@@ -0,0 +1,57 @@
1
+ /*
2
+ Copyright (c) 2026 Code Infinity
3
+ SPDX-License-Identifier: MPL-2.0
4
+ This Source Code Form is subject to the terms of the Mozilla Public
5
+ License, v. 2.0. If a copy of the MPL was not distributed with this
6
+ file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
+ */
8
+
9
+ /**
10
+ * Shared driver-row mapping for the Cypher/GQL graph adapters (Bolt, Ultipa).
11
+ *
12
+ * Both drivers return a flat record with the same reserved column names —
13
+ * `id`, `labels`, `props`, `type`, `f`, `t` — because both adapters run the
14
+ * same `RETURN ... AS id, ... AS props` projection. Turning such a row into an
15
+ * engine-neutral GraphNode or GraphEdge is therefore identical work, so it
16
+ * lives here once rather than in each adapter.
17
+ */
18
+ import { GraphNode, GraphEdge } from "../shapes.js";
19
+
20
+ /** A flat driver record from a Cypher/GQL `RETURN ... AS <col>` projection. */
21
+ export interface DriverRow {
22
+ id?: unknown;
23
+ labels?: unknown;
24
+ props?: unknown;
25
+ type?: unknown;
26
+ f?: unknown;
27
+ t?: unknown;
28
+ [key: string]: unknown;
29
+ }
30
+
31
+ /** Message of an error value, whether or not it is a real Error. */
32
+ export function errorMessage(exc: unknown): string {
33
+ if (exc instanceof Error) return exc.message;
34
+ return String(exc);
35
+ }
36
+
37
+ /** Build an engine-neutral GraphNode from a driver row, or null when there is none. */
38
+ export function nodeFromRow(row: DriverRow | undefined | null): GraphNode | null {
39
+ if (row === null || row === undefined) return null;
40
+ return new GraphNode(
41
+ String(row.id),
42
+ (row.labels as string[]) ?? [],
43
+ (row.props as Record<string, unknown>) ?? {},
44
+ );
45
+ }
46
+
47
+ /** Build an engine-neutral GraphEdge from a driver row, or null when there is none. */
48
+ export function edgeFromRow(row: DriverRow | undefined | null): GraphEdge | null {
49
+ if (row === null || row === undefined) return null;
50
+ return new GraphEdge(
51
+ String(row.id),
52
+ String(row.type),
53
+ String(row.f),
54
+ String(row.t),
55
+ (row.props as Record<string, unknown>) ?? {},
56
+ );
57
+ }
@@ -23,6 +23,12 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/.
23
23
  * query() is rejected by Ultipa.
24
24
  */
25
25
  import { GraphNode, GraphEdge, GraphResult } from "../shapes.js";
26
+ import {
27
+ errorMessage,
28
+ nodeFromRow as toGraphNode,
29
+ edgeFromRow as toGraphEdge,
30
+ type DriverRow,
31
+ } from "./rowMapping.js";
26
32
  import { GraphError, GraphConnectTimeout } from "../errors.js";
27
33
  import {
28
34
  resolveGraphConnectTimeout,
@@ -56,16 +62,6 @@ const UltipaConnectError = driver.UltipaConnectError;
56
62
  */
57
63
  const UNBOUNDED_CONNECT_SECONDS = 315_360_000; // ~10 years
58
64
 
59
- interface DriverRow {
60
- id?: unknown;
61
- labels?: unknown;
62
- props?: unknown;
63
- type?: unknown;
64
- f?: unknown;
65
- t?: unknown;
66
- [key: string]: unknown;
67
- }
68
-
69
65
  /**
70
66
  * Build a GQL property map `{k1: $p_k1, ...}` plus the param dict for it.
71
67
  *
@@ -84,11 +80,6 @@ function propClause(
84
80
  return { clause: `{${pairs}}`, params };
85
81
  }
86
82
 
87
- function errorMessage(exc: unknown): string {
88
- if (exc instanceof Error) return exc.message;
89
- return String(exc);
90
- }
91
-
92
83
  export class UltipaGraphAdapter implements GraphAdapter {
93
84
  private readonly url: GraphUrl;
94
85
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -154,12 +145,7 @@ export class UltipaGraphAdapter implements GraphAdapter {
154
145
 
155
146
  // -- portable node/edge/traverse core (GQL) ----------------------------
156
147
  private nodeFromRow(row: DriverRow | undefined | null): GraphNode | null {
157
- if (row === null || row === undefined) return null;
158
- return new GraphNode(
159
- String(row.id),
160
- (row.labels as string[]) ?? [],
161
- (row.props as Record<string, unknown>) ?? {},
162
- );
148
+ return toGraphNode(row);
163
149
  }
164
150
 
165
151
  async addNode(
@@ -190,15 +176,7 @@ export class UltipaGraphAdapter implements GraphAdapter {
190
176
  + `INSERT (a)-[e:\`${type}\` ${clause}]->(b) `
191
177
  + `RETURN id(e) AS id, type(e) AS type, id(a) AS f, id(b) AS t, properties(e) AS props`;
192
178
  const rows = (await this.run(gql, params, false)).dicts() as DriverRow[];
193
- if (rows.length === 0) return null;
194
- const row = rows[0];
195
- return new GraphEdge(
196
- String(row.id),
197
- String(row.type),
198
- String(row.f),
199
- String(row.t),
200
- (row.props as Record<string, unknown>) ?? {},
201
- );
179
+ return toGraphEdge(rows[0]);
202
180
  }
203
181
 
204
182
  async getNode(nodeId: string): Promise<GraphNode | null> {
@@ -0,0 +1,28 @@
1
+ /** Flags and positional arguments split out of a command's argv slice. */
2
+ export interface ParsedFlags {
3
+ flags: Record<string, string | boolean>;
4
+ positional: string[];
5
+ }
6
+ /**
7
+ * Parse `--key value` and bare `--flag` arguments into flags plus positionals.
8
+ *
9
+ * A name listed in `booleanFlags` never consumes the next token; every other
10
+ * `--key` takes the following token as its value unless that token is itself a
11
+ * `--flag` (or the argv ends), in which case the key is a bare boolean.
12
+ */
13
+ export declare function parseFlags(args: string[], booleanFlags?: ReadonlySet<string>): ParsedFlags;
14
+ /**
15
+ * Absolute path of the first of `names` found on PATH, or null.
16
+ *
17
+ * Scans PATH directories directly (no `which` shell-out) so it behaves the same
18
+ * on every platform and honours a deliberately emptied PATH. Callers pass the
19
+ * platform-specific candidate names (e.g. `["npm.cmd", "npm.exe", "npm"]`).
20
+ */
21
+ export declare function findOnPath(names: string[]): string | null;
22
+ /**
23
+ * Await the ORM's async `initDatabase()` and exit(1) with a clear message on
24
+ * failure. The await matters: initDatabase() calls setAdapter() inside the
25
+ * promise, so without it the next getAdapter() throws "No database adapter
26
+ * configured." Shared by the migrate, rollback, and status commands.
27
+ */
28
+ export declare function initDatabaseOrExit(initDatabase: () => Promise<unknown>): Promise<void>;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Does an `If-None-Match` header match the response ETag?
3
+ *
4
+ * The header may be `*`, a single tag, or a comma-separated list; the `W/`
5
+ * weak prefix is stripped on both sides before comparing (RFC 7232 §2.3.2
6
+ * weak comparison). Shared by the static file server and the compression
7
+ * interceptor so both answer 304 the same way.
8
+ */
9
+ export declare function etagMatches(ifNoneMatch: string, etag: string): boolean;
@@ -1,3 +1,4 @@
1
+ import { RespSessionHandler } from "./sessionHandlers/respSessionHandler.js";
1
2
  export interface SessionConfig {
2
3
  /** Session backend type: "file", "redis", "valkey", "mongo", "memcached", "database" (or "db") */
3
4
  backend?: string;
@@ -95,26 +96,8 @@ export declare class FileSessionHandler implements SessionHandler {
95
96
  *
96
97
  * Or pass via SessionConfig.
97
98
  */
98
- export declare class RedisSessionHandler implements SessionHandler {
99
- private host;
100
- private port;
101
- private password;
102
- private prefix;
103
- private db;
99
+ export declare class RedisSessionHandler extends RespSessionHandler {
104
100
  constructor(config?: SessionConfig);
105
- /**
106
- * Execute a Redis command synchronously against the live server.
107
- *
108
- * Delegates to the shared {@link respCommandSync} transport: a genuine key miss
109
- * yields `""`, and a transport/connection FAILURE (server unreachable, rejected
110
- * AUTH, timeout) THROWS so the Session boundary can distinguish "not found"
111
- * (silent) from "backend failed" (log-loud + degrade). Backend-failure parity.
112
- */
113
- private execSync;
114
- private key;
115
- read(sessionId: string): SessionData | null;
116
- write(sessionId: string, data: SessionData, ttl?: number): void;
117
- destroy(sessionId: string): void;
118
101
  }
119
102
  export declare class Session {
120
103
  private handler;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Tina4 RESP Session Handler base — the shared store logic behind Redis and
3
+ * Valkey, zero dependencies.
4
+ *
5
+ * Redis and Valkey speak the same RESP protocol, so the read/write/destroy
6
+ * transport is byte-for-byte identical between them — only the configuration
7
+ * variable names and the log label differ. That difference lives entirely in
8
+ * each subclass constructor; everything the session store actually DOES lives
9
+ * here, once. A subclass resolves its own host/port/password/prefix/db from
10
+ * config or environment, then hands a {@link RespConnection} plus a label to
11
+ * `super()`.
12
+ */
13
+ import type { SessionHandler } from "../session.js";
14
+ interface SessionData {
15
+ _created: number;
16
+ _accessed: number;
17
+ [key: string]: unknown;
18
+ }
19
+ /** Resolved connection details a RESP session handler needs. */
20
+ export interface RespConnection {
21
+ host: string;
22
+ port: number;
23
+ password: string;
24
+ prefix: string;
25
+ db: number;
26
+ }
27
+ /**
28
+ * RESP (Redis/Valkey) session handler using raw TCP — no external client.
29
+ *
30
+ * Stores session data as JSON strings with a server-side TTL for automatic
31
+ * expiry. A genuine key miss yields `null`; a transport/connection FAILURE
32
+ * (server unreachable, rejected AUTH, timeout) THROWS so the Session boundary
33
+ * can tell "not found" (silent) from "backend failed" (log-loud + degrade).
34
+ */
35
+ export declare abstract class RespSessionHandler implements SessionHandler {
36
+ private readonly conn;
37
+ private readonly label;
38
+ protected constructor(conn: RespConnection, label: string);
39
+ /**
40
+ * Execute a RESP command synchronously against the live server.
41
+ *
42
+ * Delegates to the shared {@link respCommandSync} transport: a genuine key
43
+ * miss yields `""`, and a transport/connection FAILURE THROWS so the caller
44
+ * can distinguish "not found" from "backend failed". Backend-failure parity.
45
+ */
46
+ private execSync;
47
+ private key;
48
+ read(sessionId: string): SessionData | null;
49
+ write(sessionId: string, data: SessionData, ttl?: number): void;
50
+ destroy(sessionId: string): void;
51
+ }
52
+ export {};
@@ -11,12 +11,7 @@
11
11
  * TINA4_SESSION_VALKEY_PREFIX (default: "tina4:session:")
12
12
  * TINA4_SESSION_VALKEY_DB (default: 0)
13
13
  */
14
- import type { SessionHandler } from "../session.js";
15
- interface SessionData {
16
- _created: number;
17
- _accessed: number;
18
- [key: string]: unknown;
19
- }
14
+ import { RespSessionHandler } from "./respSessionHandler.js";
20
15
  export interface ValkeySessionConfig {
21
16
  host?: string;
22
17
  port?: number;
@@ -41,25 +36,6 @@ export interface ValkeySessionConfig {
41
36
  * Valkey uses the same RESP protocol as Redis, so this handler is functionally
42
37
  * identical to RedisSessionHandler but with VALKEY config variable names.
43
38
  */
44
- export declare class ValkeySessionHandler implements SessionHandler {
45
- private host;
46
- private port;
47
- private password;
48
- private prefix;
49
- private db;
39
+ export declare class ValkeySessionHandler extends RespSessionHandler {
50
40
  constructor(config?: ValkeySessionConfig);
51
- /**
52
- * Execute a RESP command synchronously against the live Valkey server.
53
- *
54
- * Delegates to the shared {@link respCommandSync} transport: a genuine key miss
55
- * yields `""`, and a transport/connection FAILURE (server unreachable, rejected
56
- * AUTH, timeout) THROWS so the Session boundary can distinguish "not found"
57
- * (silent) from "backend failed" (log-loud + degrade). Backend-failure parity.
58
- */
59
- private execSync;
60
- private key;
61
- read(sessionId: string): SessionData | null;
62
- write(sessionId: string, data: SessionData, ttl: number): void;
63
- destroy(sessionId: string): void;
64
41
  }
65
- export {};
@@ -0,0 +1,10 @@
1
+ import type { Tina4Response } from "./types.js";
2
+ /**
3
+ * Relay a drained upstream (proxied) response to the client.
4
+ *
5
+ * The body is read to a string once, then returned as JSON when it parses and
6
+ * as raw text with the upstream Content-Type otherwise, keeping the upstream
7
+ * status either way. Shared by the dev-admin supervisor proxy and the feedback
8
+ * agent proxy so both pass a backend response through identically.
9
+ */
10
+ export declare function relayUpstreamResponse(res: Tina4Response, upstream: Response): Promise<void>;
@@ -131,6 +131,25 @@ export declare function parseFrame(data: Buffer): {
131
131
  payload: Buffer;
132
132
  bytesConsumed: number;
133
133
  } | null;
134
+ /**
135
+ * Reassembles fragmented messages (RFC 6455 section 5.4). A browser sends a large message - a pasted image in a chat,
136
+ * base64 inside JSON - as a TEXT or BINARY frame with FIN clear, then CONTINUATION frames; control frames may arrive
137
+ * between the pieces. Returns a complete message when one is done, a control frame as it comes, and null while a
138
+ * message is still arriving. A CONTINUATION with no message started is dropped. Mirrors the Python master
139
+ * (tina4_python.websocket _handle_frame). One per connection.
140
+ */
141
+ export declare class MessageAssembler {
142
+ private parts;
143
+ private opcode;
144
+ push(frame: {
145
+ fin: boolean;
146
+ opcode: number;
147
+ payload: Buffer;
148
+ }): {
149
+ opcode: number;
150
+ payload: Buffer;
151
+ } | null;
152
+ }
134
153
  export declare class WebSocketServer {
135
154
  private port;
136
155
  private server;
@@ -271,6 +290,8 @@ export declare class WebSocketServer {
271
290
  */
272
291
  private startIdleReaper;
273
292
  private handleUpgrade;
293
+ private readonly assemblers;
294
+ private assemblerFor;
274
295
  private processBuffer;
275
296
  private removeClientFromAllRooms;
276
297
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared driver-row mapping for the Cypher/GQL graph adapters (Bolt, Ultipa).
3
+ *
4
+ * Both drivers return a flat record with the same reserved column names —
5
+ * `id`, `labels`, `props`, `type`, `f`, `t` — because both adapters run the
6
+ * same `RETURN ... AS id, ... AS props` projection. Turning such a row into an
7
+ * engine-neutral GraphNode or GraphEdge is therefore identical work, so it
8
+ * lives here once rather than in each adapter.
9
+ */
10
+ import { GraphNode, GraphEdge } from "../shapes.js";
11
+ /** A flat driver record from a Cypher/GQL `RETURN ... AS <col>` projection. */
12
+ export interface DriverRow {
13
+ id?: unknown;
14
+ labels?: unknown;
15
+ props?: unknown;
16
+ type?: unknown;
17
+ f?: unknown;
18
+ t?: unknown;
19
+ [key: string]: unknown;
20
+ }
21
+ /** Message of an error value, whether or not it is a real Error. */
22
+ export declare function errorMessage(exc: unknown): string;
23
+ /** Build an engine-neutral GraphNode from a driver row, or null when there is none. */
24
+ export declare function nodeFromRow(row: DriverRow | undefined | null): GraphNode | null;
25
+ /** Build an engine-neutral GraphEdge from a driver row, or null when there is none. */
26
+ export declare function edgeFromRow(row: DriverRow | undefined | null): GraphEdge | null;