@fadhilp/stateql 0.5.3 → 0.6.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.
@@ -1,3 +1,4 @@
1
+ import { parseJson } from "./util.js";
1
2
  export function sessionData(session) {
2
3
  return {
3
4
  session_id: session.id,
@@ -27,8 +28,19 @@ export function operationData(operation) {
27
28
  state_version_before: operation.state_version_before,
28
29
  state_version_after: operation.state_version_after,
29
30
  ...(operation.replay_of ? { replay_of: operation.replay_of } : {}),
31
+ ...(operation.outcome_json === null
32
+ ? {}
33
+ : {
34
+ outcome: parseJson(operation.outcome_json, `operation "${operation.id}" outcome`, isMongoWriteOutcome),
35
+ }),
30
36
  };
31
37
  }
38
+ function isMongoWriteOutcome(value) {
39
+ return Boolean(value) &&
40
+ typeof value === "object" &&
41
+ !Array.isArray(value) &&
42
+ typeof value.acknowledged === "boolean";
43
+ }
32
44
  export function transactionData(transaction, statements) {
33
45
  return {
34
46
  transaction_id: transaction.id,
package/dist/src/sql.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { AST } from "node-sql-parser";
2
- import type { Driver } from "./types.js";
2
+ import type { Driver, SqlDriver } from "./types.js";
3
3
  export type StatementType = "select" | "insert" | "replace" | "update" | "delete" | "create" | "alter" | "drop" | "truncate";
4
4
  export interface SqlAnalysis {
5
5
  ast: AST;
@@ -10,4 +10,6 @@ export interface SqlAnalysis {
10
10
  destructive: boolean;
11
11
  ordered: boolean;
12
12
  }
13
+ export declare function analyzeSql(sql: string, driver: SqlDriver): SqlAnalysis;
14
+ /** @internal Compatibility for existing connection records during Mongo rollout. */
13
15
  export declare function analyzeSql(sql: string, driver: Driver): SqlAnalysis;
package/dist/src/sql.js CHANGED
@@ -14,6 +14,9 @@ const SUPPORTED_STATEMENTS = new Set([
14
14
  "truncate",
15
15
  ]);
16
16
  export function analyzeSql(sql, driver) {
17
+ if (driver === "mongodb") {
18
+ throw new StateQLError("INVALID_SQL", "SQL is not supported for MongoDB connections.");
19
+ }
17
20
  const trimmed = sql.trim();
18
21
  if (!trimmed)
19
22
  throw new StateQLError("INVALID_SQL", "SQL is empty.");
@@ -23,7 +26,10 @@ export function analyzeSql(sql, driver) {
23
26
  : driver === "mysql"
24
27
  ? "MySQL"
25
28
  : "Sqlite";
26
- const parsed = parser.astify(trimmed, { database });
29
+ const parserSql = driver === "postgres"
30
+ ? postgresParserSql(trimmed)
31
+ : trimmed;
32
+ const parsed = parser.astify(parserSql, { database });
27
33
  if (Array.isArray(parsed)) {
28
34
  if (parsed.length !== 1) {
29
35
  throw new StateQLError("INVALID_SQL", "Exactly one SQL statement is required.");
@@ -40,11 +46,15 @@ export function analyzeSql(sql, driver) {
40
46
  if (statementType === "select" && selectContainsWrite(ast)) {
41
47
  throw new StateQLError("INVALID_SQL", "Read statements cannot contain writes or SELECT INTO.");
42
48
  }
43
- const normalized = parser
44
- .sqlify(ast, { database })
45
- .replace(/;\s*$/, "")
46
- .replace(/\s+/g, " ")
47
- .trim();
49
+ const normalized = parserSql === trimmed
50
+ ? parser
51
+ .sqlify(ast, { database })
52
+ .replace(/;\s*$/, "")
53
+ .replace(/\s+/g, " ")
54
+ .trim()
55
+ // Keep the exact ordering modifiers in cache and idempotency fingerprints.
56
+ // The parser copy is analysis-only; adapters execute the original SQL.
57
+ : trimmed.replace(/;\s*$/, "");
48
58
  const details = ast;
49
59
  const read = statementType === "select";
50
60
  const mutation = statementType === "update" ||
@@ -74,6 +84,151 @@ export function analyzeSql(sql, driver) {
74
84
  throw new StateQLError("INVALID_SQL", message);
75
85
  }
76
86
  }
87
+ function postgresParserSql(sql) {
88
+ const output = sql.split("");
89
+ const orderDepths = new Set();
90
+ let previousWord;
91
+ let changed = false;
92
+ let depth = 0;
93
+ let index = 0;
94
+ while (index < sql.length) {
95
+ if (sql.startsWith("--", index)) {
96
+ index = lineCommentEnd(sql, index + 2);
97
+ continue;
98
+ }
99
+ if (sql.startsWith("/*", index)) {
100
+ index = blockCommentEnd(sql, index + 2);
101
+ continue;
102
+ }
103
+ const character = sql[index];
104
+ if (character === "'" || character === '"') {
105
+ previousWord = undefined;
106
+ index = quotedEnd(sql, index + 1, character);
107
+ continue;
108
+ }
109
+ if (character === "$") {
110
+ const delimiter = sql.slice(index).match(/^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/)?.[0];
111
+ if (delimiter) {
112
+ previousWord = undefined;
113
+ const end = sql.indexOf(delimiter, index + delimiter.length);
114
+ index = end < 0 ? sql.length : end + delimiter.length;
115
+ continue;
116
+ }
117
+ }
118
+ if (character === "(") {
119
+ previousWord = undefined;
120
+ depth += 1;
121
+ index += 1;
122
+ continue;
123
+ }
124
+ if (character === ")") {
125
+ previousWord = undefined;
126
+ orderDepths.delete(depth);
127
+ depth = Math.max(0, depth - 1);
128
+ index += 1;
129
+ continue;
130
+ }
131
+ if (!identifierStart(character)) {
132
+ if (!/\s/u.test(character))
133
+ previousWord = undefined;
134
+ if (character === ";")
135
+ orderDepths.clear();
136
+ index += 1;
137
+ continue;
138
+ }
139
+ let end = index + 1;
140
+ while (identifierPart(sql[end]))
141
+ end += 1;
142
+ const word = sql.slice(index, end).toUpperCase();
143
+ if (word === "NULLS" && orderDepths.has(depth)) {
144
+ const ordering = triviaEnd(sql, end);
145
+ const modifier = sql.slice(ordering, ordering + 5).toUpperCase();
146
+ const length = modifier === "FIRST" ? 5 : modifier.startsWith("LAST") ? 4 : 0;
147
+ if (length && !identifierPart(sql[ordering + length])) {
148
+ for (let cursor = index; cursor < end; cursor += 1)
149
+ output[cursor] = " ";
150
+ for (let cursor = ordering; cursor < ordering + length; cursor += 1)
151
+ output[cursor] = " ";
152
+ changed = true;
153
+ previousWord = undefined;
154
+ index = ordering + length;
155
+ continue;
156
+ }
157
+ }
158
+ if (word === "BY" && previousWord === "ORDER")
159
+ orderDepths.add(depth);
160
+ if (["LIMIT", "OFFSET", "FETCH", "FOR", "UNION", "INTERSECT", "EXCEPT"].includes(word)) {
161
+ orderDepths.delete(depth);
162
+ }
163
+ previousWord = word === "ORDER" ? word : undefined;
164
+ index = end;
165
+ }
166
+ return changed ? output.join("") : sql;
167
+ }
168
+ function triviaEnd(sql, start) {
169
+ let index = start;
170
+ while (index < sql.length) {
171
+ if (/\s/u.test(sql[index])) {
172
+ index += 1;
173
+ }
174
+ else if (sql.startsWith("--", index)) {
175
+ index = lineCommentEnd(sql, index + 2);
176
+ }
177
+ else if (sql.startsWith("/*", index)) {
178
+ index = blockCommentEnd(sql, index + 2);
179
+ }
180
+ else {
181
+ break;
182
+ }
183
+ }
184
+ return index;
185
+ }
186
+ function lineCommentEnd(sql, start) {
187
+ const end = sql.indexOf("\n", start);
188
+ return end < 0 ? sql.length : end + 1;
189
+ }
190
+ function blockCommentEnd(sql, start) {
191
+ let depth = 1;
192
+ let index = start;
193
+ while (index < sql.length && depth > 0) {
194
+ if (sql.startsWith("/*", index)) {
195
+ depth += 1;
196
+ index += 2;
197
+ }
198
+ else if (sql.startsWith("*/", index)) {
199
+ depth -= 1;
200
+ index += 2;
201
+ }
202
+ else {
203
+ index += 1;
204
+ }
205
+ }
206
+ return index;
207
+ }
208
+ function quotedEnd(sql, start, quote) {
209
+ let index = start;
210
+ while (index < sql.length) {
211
+ if (sql[index] === "\\" && quote === "'") {
212
+ index += 2;
213
+ }
214
+ else if (sql[index] !== quote) {
215
+ index += 1;
216
+ }
217
+ else if (sql[index + 1] === quote) {
218
+ index += 2;
219
+ }
220
+ else {
221
+ return index + 1;
222
+ }
223
+ }
224
+ return index;
225
+ }
226
+ function identifierStart(value) {
227
+ return value !== undefined && /[A-Za-z_\u0080-\uFFFF]/u.test(value);
228
+ }
229
+ function identifierPart(value) {
230
+ return value !== undefined && /[A-Za-z0-9_$\u0080-\uFFFF]/u.test(value);
231
+ }
77
232
  function selectContainsWrite(ast) {
78
233
  const details = ast;
79
234
  const into = details.into;
@@ -1,4 +1,4 @@
1
- import type { ActorLinkData, ActorResolutionData, ActorsData, ActorUnlinkData, AliasData, ApplyData, BatchCommand, BatchOptions, CapabilitiesData, CloseSessionData, ColumnsData, CommitTransactionData, ConnectOptions, ConnectionData, CountData, DisconnectData, DoctorData, ExecData, ExecOptions, ExecutionOptions, ExportData, FilterOptions, HistoryData, OperationData, PlanData, PlanOptions, ProfileData, ProfilesData, ProfileOptions, PurgeData, QueryOptions, RemovedProfileData, Response, ResultData, RollbackTransactionData, RowsData, RowsOptions, StateQLActorOptions, StateQLOptions, StateQLSnapshot, StatusData, SessionData, SessionsData, SessionSummaryData, TransactionData } from "./types.js";
1
+ import type { ActorLinkData, ActorResolutionData, ActorsData, ActorUnlinkData, AliasData, ApplyData, BatchCommand, BatchOptions, CapabilitiesData, CloseSessionData, ColumnsData, CommitTransactionData, ConnectOptions, ConnectionData, CountData, DisconnectData, DoctorData, ExecData, ExecOptions, ExecutionOptions, ExportData, FilterOptions, HistoryData, OperationData, PlanData, PlanOptions, ProfileData, ProfilesData, ProfileOptions, MongoExecOptions, MongoPlanOptions, MongoQueryOptions, MongoReadCommand, MongoWriteCommand, PurgeData, QueryOptions, RemovedProfileData, Response, ResultData, RollbackTransactionData, RowsData, RowsOptions, StateQLActorOptions, StateQLOptions, StateQLSnapshot, StatusData, SessionData, SessionsData, SessionSummaryData, TransactionData } from "./types.js";
2
2
  export declare class StateQL {
3
3
  static forActor(options: StateQLActorOptions): StateQL;
4
4
  private readonly store;
@@ -38,6 +38,7 @@ export declare class StateQL {
38
38
  sessionSummary(): Promise<Response<SessionSummaryData>>;
39
39
  closeSession(): Promise<Response<CloseSessionData>>;
40
40
  query(sql: string, options?: QueryOptions): Promise<Response<ResultData>>;
41
+ mongoQuery(command: MongoReadCommand, options?: MongoQueryOptions): Promise<Response<ResultData>>;
41
42
  show(idOrAlias: string): Promise<Response<ResultData>>;
42
43
  filter(idOrAlias: string, predicate: string, options?: FilterOptions): Promise<Response<ResultData>>;
43
44
  rows(idOrAlias: string, options?: RowsOptions): Promise<Response<RowsData>>;
@@ -46,6 +47,7 @@ export declare class StateQL {
46
47
  setAlias(name: string, id: string): Promise<Response<AliasData>>;
47
48
  exportResult(idOrAlias: string, output: string, format?: "json" | "jsonl" | "csv"): Promise<Response<ExportData>>;
48
49
  exec(sql: string, options?: ExecOptions): Promise<Response<ExecData>>;
50
+ mongoExec(command: MongoWriteCommand, options?: MongoExecOptions): Promise<Response<ExecData>>;
49
51
  receipt(id: string): Promise<Response<OperationData>>;
50
52
  beginTransaction(isolation?: string): Promise<Response<TransactionData>>;
51
53
  transactionStatus(id?: string): Promise<Response<TransactionData>>;
@@ -53,6 +55,7 @@ export declare class StateQL {
53
55
  rollbackTransaction(id?: string): Promise<Response<RollbackTransactionData>>;
54
56
  inspect(kind: string, table?: string, options?: ExecutionOptions): Promise<Response<unknown>>;
55
57
  plan(sql: string, options?: PlanOptions): Promise<Response<PlanData>>;
58
+ mongoPlan(command: MongoWriteCommand, options?: MongoPlanOptions): Promise<Response<PlanData>>;
56
59
  apply(planId: string, options?: ExecutionOptions): Promise<Response<ApplyData>>;
57
60
  history(limit?: number): Promise<Response<HistoryData>>;
58
61
  doctor(): Promise<Response<DoctorData>>;
@@ -61,11 +64,14 @@ export declare class StateQL {
61
64
  executeCommand(command: BatchCommand): Promise<Response<unknown>>;
62
65
  batch(commands: Iterable<BatchCommand> | AsyncIterable<BatchCommand>, options?: BatchOptions): AsyncGenerator<Response<unknown>>;
63
66
  private performExec;
67
+ private performMongoExec;
64
68
  private batchFailure;
65
69
  private withResult;
66
70
  private rejectDuringStagedTransaction;
67
71
  private requireResult;
68
72
  private requireConnection;
73
+ private requireMongoConnection;
74
+ private rejectMongoSql;
69
75
  private requireActiveTransaction;
70
76
  private requireSelectedSession;
71
77
  private validateActorId;
@@ -73,6 +79,7 @@ export declare class StateQL {
73
79
  private resolveConnectionSource;
74
80
  private resolveCredential;
75
81
  private openAdapter;
82
+ private openMongoAdapter;
76
83
  private executionContext;
77
84
  private resultData;
78
85
  private cacheValid;