tina4-nodejs 3.13.133 → 3.13.134

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 (50) hide show
  1. package/CLAUDE.md +3 -3
  2. package/README.md +2 -2
  3. package/package.json +1 -1
  4. package/packages/cli/dist/bin.js +3181 -3051
  5. package/packages/cli/src/commands/generate.ts +33 -22
  6. package/packages/cli/src/commands/lint.ts +77 -111
  7. package/packages/core/dist/index.js +3090 -2952
  8. package/packages/core/src/.tina4-metrics.json +15004 -0
  9. package/packages/core/src/aiClient.ts +199 -161
  10. package/packages/core/src/dispatchPipeline.ts +65 -67
  11. package/packages/core/src/docs.ts +52 -544
  12. package/packages/core/src/docsParser.ts +270 -0
  13. package/packages/core/src/docsScanner.ts +121 -0
  14. package/packages/core/src/docsSignatures.ts +165 -0
  15. package/packages/core/src/index.ts +2 -0
  16. package/packages/core/src/logger.ts +68 -82
  17. package/packages/core/src/mcp.ts +32 -60
  18. package/packages/core/src/messenger.ts +136 -157
  19. package/packages/core/src/middleware.ts +56 -60
  20. package/packages/core/src/plan.ts +78 -70
  21. package/packages/core/src/projectIndex.ts +15 -288
  22. package/packages/core/src/projectIndexExtractors.ts +126 -0
  23. package/packages/core/src/projectIndexStorage.ts +122 -0
  24. package/packages/core/src/push.ts +281 -0
  25. package/packages/core/src/server.ts +182 -183
  26. package/packages/frond/dist/index.js +607 -770
  27. package/packages/frond/src/engine.ts +670 -818
  28. package/packages/orm/dist/index.js +3100 -2965
  29. package/packages/orm/src/adapters/mongodb.ts +99 -144
  30. package/packages/orm/src/baseModel.ts +429 -515
  31. package/packages/orm/src/fakeData.ts +73 -61
  32. package/packages/orm/src/migration.ts +96 -126
  33. package/packages/orm/src/seeder.ts +6 -238
  34. package/packages/orm/src/seederTable.ts +101 -0
  35. package/packages/orm/src/seederTypes.ts +14 -0
  36. package/packages/orm/src/validation.ts +97 -80
  37. package/types/core/src/aiClient.d.ts +5 -0
  38. package/types/core/src/docsParser.d.ts +28 -0
  39. package/types/core/src/docsScanner.d.ts +1 -0
  40. package/types/core/src/docsSignatures.d.ts +11 -0
  41. package/types/core/src/index.d.ts +2 -0
  42. package/types/core/src/messenger.d.ts +8 -0
  43. package/types/core/src/projectIndexExtractors.d.ts +3 -0
  44. package/types/core/src/projectIndexStorage.d.ts +13 -0
  45. package/types/core/src/push.d.ts +45 -0
  46. package/types/frond/src/engine.d.ts +25 -0
  47. package/types/orm/src/fakeData.d.ts +3 -0
  48. package/types/orm/src/seeder.d.ts +3 -89
  49. package/types/orm/src/seederTable.d.ts +9 -0
  50. package/types/orm/src/seederTypes.d.ts +16 -0
@@ -183,158 +183,113 @@ function requireWriteFilter(filter: Record<string, unknown> | undefined, operati
183
183
  }
184
184
  }
185
185
 
186
- /** Parse a SQL string into a MongoOperation. Returns null if parsing is not supported. */
187
- function parseSql(sql: string, params: unknown[] = []): MongoOperation | null {
188
- const s = sql.trim();
189
-
190
- // ---- SELECT ----
191
- const selectMatch = s.match(
192
- /^SELECT\s+(.*?)\s+FROM\s+["']?(\w+)["']?(?:\s+WHERE\s+(.*?))?(?:\s+ORDER\s+BY\s+(.*?))?(?:\s+LIMIT\s+(\d+))?(?:\s+OFFSET\s+(\d+))?$/is,
193
- );
194
- if (selectMatch) {
195
- const [, cols, collection, whereClause, orderBy, limitStr, skipStr] = selectMatch;
196
-
197
- // Projection
198
- let projection: Record<string, unknown> | undefined;
199
- if (cols && cols.trim() !== "*") {
200
- projection = {};
201
- for (const col of cols.split(",")) {
202
- const name = col.trim().replace(/^["']|["']$/g, "");
203
- if (name && name !== "*") projection[name] = 1;
204
- }
186
+ function parseSelect(sql: string, params: unknown[]): MongoOperation | null {
187
+ const match = sql.match(/^SELECT\s+(.*?)\s+FROM\s+["']?(\w+)["']?(?:\s+WHERE\s+(.*?))?(?:\s+ORDER\s+BY\s+(.*?))?(?:\s+LIMIT\s+(\d+))?(?:\s+OFFSET\s+(\d+))?$/is);
188
+ if (!match) return null;
189
+ const [, cols, collection, whereClause, orderBy, limitStr, skipStr] = match;
190
+ const projection: Record<string, unknown> = {};
191
+ if (cols && cols.trim() !== "*") {
192
+ for (const col of cols.split(",")) {
193
+ const name = col.trim().replace(/^["']|["']$/g, "");
194
+ if (name && name !== "*") projection[name] = 1;
205
195
  }
206
-
207
- // Filter
208
- let filter: Record<string, unknown> = {};
209
- if (whereClause) {
210
- const parsed = parseWhereClause(whereClause.trim(), params);
211
- filter = parsed.filter;
196
+ }
197
+ const filter = whereClause ? parseWhereClause(whereClause.trim(), params).filter : {};
198
+ const sort: Record<string, 1 | -1> = {};
199
+ if (orderBy) {
200
+ for (const part of orderBy.split(",")) {
201
+ const trimmed = part.trim();
202
+ const desc = /DESC$/i.test(trimmed);
203
+ const col = trimmed.replace(/\s+(ASC|DESC)$/i, "").replace(/^["']|["']$/g, "").trim();
204
+ sort[col] = desc ? -1 : 1;
212
205
  }
206
+ }
207
+ return {
208
+ type: "find",
209
+ collection,
210
+ filter,
211
+ projection: Object.keys(projection).length > 0 ? projection : undefined,
212
+ limit: limitStr ? parseInt(limitStr, 10) : undefined,
213
+ skip: skipStr ? parseInt(skipStr, 10) : undefined,
214
+ sort: orderBy ? sort : undefined,
215
+ };
216
+ }
213
217
 
214
- // Sort
215
- let sort: Record<string, 1 | -1> | undefined;
216
- if (orderBy) {
217
- sort = {};
218
- for (const part of orderBy.split(",")) {
219
- const t = part.trim();
220
- const desc = /DESC$/i.test(t);
221
- const col = t.replace(/\s+(ASC|DESC)$/i, "").replace(/^["']|["']$/g, "").trim();
222
- sort[col] = desc ? -1 : 1;
223
- }
224
- }
218
+ function parseInsert(sql: string, params: unknown[]): MongoOperation | null {
219
+ const match = sql.match(/^INSERT\s+INTO\s+["']?(\w+)["']?\s*\(([^)]+)\)\s*VALUES\s*\(([^)]+)\)$/is);
220
+ if (!match) return null;
221
+ const [, collection, colsStr, valsStr] = match;
222
+ const cols = colsStr.split(",").map((column) => column.trim().replace(/^["']|["']$/g, ""));
223
+ const values = valsStr.split(",").map((value) => value.trim());
224
+ const document: Record<string, unknown> = {};
225
+ let paramIndex = 0;
226
+ for (let index = 0; index < cols.length; index++) {
227
+ const value = values[index];
228
+ if (value === "?") document[cols[index]] = params[paramIndex++];
229
+ else if (/^'.*'$/.test(value)) document[cols[index]] = value.slice(1, -1);
230
+ else if (/^-?\d+(\.\d+)?$/.test(value)) document[cols[index]] = Number(value);
231
+ else document[cols[index]] = value;
232
+ }
233
+ return { type: "insertOne", collection, document };
234
+ }
225
235
 
226
- return {
227
- type: "find",
228
- collection,
229
- filter,
230
- projection: projection && Object.keys(projection).length > 0 ? projection : undefined,
231
- limit: limitStr ? parseInt(limitStr, 10) : undefined,
232
- skip: skipStr ? parseInt(skipStr, 10) : undefined,
233
- sort,
234
- };
236
+ function parseSetClause(setClause: string, params: unknown[]): { document: Record<string, unknown>; consumed: number } {
237
+ const document: Record<string, unknown> = {};
238
+ let consumed = 0;
239
+ for (const part of setClause.split(",")) {
240
+ const trimmed = part.trim();
241
+ const parameter = trimmed.match(/^["']?(\w+)["']?\s*=\s*\?$/);
242
+ if (parameter) {
243
+ document[parameter[1]] = params[consumed++];
244
+ continue;
245
+ }
246
+ const literal = trimmed.match(/^["']?(\w+)["']?\s*=\s*(?:'([^']*)'|(-?\d+(?:\.\d+)?))$/);
247
+ if (literal) document[literal[1]] = literal[2] !== undefined ? literal[2] : Number(literal[3]);
235
248
  }
249
+ return { document, consumed };
250
+ }
236
251
 
237
- // ---- INSERT ----
238
- const insertMatch = s.match(
239
- /^INSERT\s+INTO\s+["']?(\w+)["']?\s*\(([^)]+)\)\s*VALUES\s*\(([^)]+)\)$/is,
240
- );
241
- if (insertMatch) {
242
- const [, collection, colsStr, valsStr] = insertMatch;
243
- const cols = colsStr.split(",").map((c) => c.trim().replace(/^["']|["']$/g, ""));
244
- const valPlaceholders = valsStr.split(",").map((v) => v.trim());
245
- let paramIndex = 0;
246
- const doc: Record<string, unknown> = {};
247
- for (let i = 0; i < cols.length; i++) {
248
- if (valPlaceholders[i] === "?") {
249
- doc[cols[i]] = params[paramIndex++];
250
- } else if (/^'.*'$/.test(valPlaceholders[i])) {
251
- doc[cols[i]] = valPlaceholders[i].slice(1, -1);
252
- } else if (/^-?\d+(\.\d+)?$/.test(valPlaceholders[i])) {
253
- doc[cols[i]] = Number(valPlaceholders[i]);
254
- } else {
255
- doc[cols[i]] = valPlaceholders[i];
256
- }
257
- }
258
- return { type: "insertOne", collection, document: doc };
259
- }
260
-
261
- // ---- UPDATE ----
262
- // WHERE is OPTIONAL in the grammar so a filterless UPDATE ("UPDATE t SET x=1")
263
- // is RECOGNISED as an updateMany with an empty filter and reaches the
264
- // fail-closed guard -- rather than failing the regex, returning null, and
265
- // being silently acknowledged as a no-op (a silent wrong result).
266
- const updateMatch = s.match(
267
- /^UPDATE\s+["']?(\w+)["']?\s+SET\s+(.*?)(?:\s+WHERE\s+(.+))?$/is,
268
- );
269
- if (updateMatch) {
270
- const [, collection, setClause, whereClause] = updateMatch;
271
-
272
- // Parse SET clause
273
- const setDoc: Record<string, unknown> = {};
274
- let setParamIndex = 0;
275
- for (const setPart of setClause.split(",")) {
276
- const m = setPart.trim().match(/^["']?(\w+)["']?\s*=\s*\?$/);
277
- if (m) {
278
- setDoc[m[1]] = params[setParamIndex++];
279
- } else {
280
- const mLit = setPart.trim().match(/^["']?(\w+)["']?\s*=\s*(?:'([^']*)'|(-?\d+(?:\.\d+)?))$/);
281
- if (mLit) {
282
- setDoc[mLit[1]] = mLit[2] !== undefined ? mLit[2] : Number(mLit[3]);
283
- }
284
- }
285
- }
252
+ function parseUpdate(sql: string, params: unknown[]): MongoOperation | null {
253
+ const match = sql.match(/^UPDATE\s+["']?(\w+)["']?\s+SET\s+(.*?)(?:\s+WHERE\s+(.+))?$/is);
254
+ if (!match) return null;
255
+ const [, collection, setClause, whereClause] = match;
256
+ const set = parseSetClause(setClause, params);
257
+ const matchAll = isMatchAllWhere(whereClause?.trim());
258
+ const filter = whereClause ? parseWhereClause(whereClause.trim(), params, set.consumed).filter : {};
259
+ return { type: "updateMany", collection, filter, update: { $set: set.document }, matchAll };
260
+ }
286
261
 
287
- // Parse WHERE clause (params start after SET params). No WHERE -> empty
288
- // filter, which the write guard refuses; an explicit 1=1 tautology -> an
289
- // empty filter that IS an intentional match-all (matchAll bypasses the guard).
290
- const matchAll = isMatchAllWhere(whereClause?.trim());
291
- const filter = whereClause
292
- ? parseWhereClause(whereClause.trim(), params, setParamIndex).filter
293
- : {};
294
-
295
- return { type: "updateMany", collection, filter, update: { $set: setDoc }, matchAll };
296
- }
297
-
298
- // ---- DELETE ----
299
- const deleteMatch = s.match(
300
- /^DELETE\s+FROM\s+["']?(\w+)["']?(?:\s+WHERE\s+(.+))?$/is,
301
- );
302
- if (deleteMatch) {
303
- const [, collection, whereClause] = deleteMatch;
304
- // An explicit 1=1 tautology (truncate()'s WHERE) is an intentional match-all;
305
- // a blank/absent WHERE is the mass-delete footgun the executor guard refuses.
306
- const matchAll = isMatchAllWhere(whereClause?.trim());
307
- const filter = whereClause
308
- ? parseWhereClause(whereClause.trim(), params).filter
309
- : {};
310
- return { type: "deleteMany", collection, filter, matchAll };
311
- }
312
-
313
- // ---- CREATE TABLE (treated as createCollection) ----
314
- const createTableMatch = s.match(/^CREATE\s+(?:TABLE|COLLECTION)\s+(?:IF\s+NOT\s+EXISTS\s+)?["']?(\w+)["']?/i);
315
- if (createTableMatch) {
316
- // We handle this in createTable(); signal it as a "raw" skip
317
- return { type: "raw", collection: createTableMatch[1] };
318
- }
319
-
320
- // ---- SELECT COUNT(*) ----
321
- const countMatch = s.match(/^SELECT\s+COUNT\(\*\)\s+AS\s+(\w+)\s+FROM\s+["']?(\w+)["']?(?:\s+WHERE\s+(.+))?$/is);
322
- if (countMatch) {
323
- const [, alias, collection, whereClause] = countMatch;
324
- const filter = whereClause
325
- ? parseWhereClause(whereClause.trim(), params).filter
326
- : {};
327
- return {
328
- type: "aggregate",
329
- collection,
330
- pipeline: [
331
- { $match: filter },
332
- { $count: alias },
333
- ],
334
- };
335
- }
262
+ function parseDelete(sql: string, params: unknown[]): MongoOperation | null {
263
+ const match = sql.match(/^DELETE\s+FROM\s+["']?(\w+)["']?(?:\s+WHERE\s+(.+))?$/is);
264
+ if (!match) return null;
265
+ const [, collection, whereClause] = match;
266
+ const matchAll = isMatchAllWhere(whereClause?.trim());
267
+ const filter = whereClause ? parseWhereClause(whereClause.trim(), params).filter : {};
268
+ return { type: "deleteMany", collection, filter, matchAll };
269
+ }
270
+
271
+ function parseCreate(sql: string): MongoOperation | null {
272
+ const match = sql.match(/^CREATE\s+(?:TABLE|COLLECTION)\s+(?:IF\s+NOT\s+EXISTS\s+)?["']?(\w+)["']?/i);
273
+ return match ? { type: "raw", collection: match[1] } : null;
274
+ }
336
275
 
337
- return null;
276
+ function parseCount(sql: string, params: unknown[]): MongoOperation | null {
277
+ const match = sql.match(/^SELECT\s+COUNT\(\*\)\s+AS\s+(\w+)\s+FROM\s+["']?(\w+)["']?(?:\s+WHERE\s+(.+))?$/is);
278
+ if (!match) return null;
279
+ const [, alias, collection, whereClause] = match;
280
+ const filter = whereClause ? parseWhereClause(whereClause.trim(), params).filter : {};
281
+ return { type: "aggregate", collection, pipeline: [{ $match: filter }, { $count: alias }] };
282
+ }
283
+
284
+ /** Parse a SQL string into a MongoOperation. Returns null if parsing is not supported. */
285
+ function parseSql(sql: string, params: unknown[] = []): MongoOperation | null {
286
+ const statement = sql.trim();
287
+ return parseSelect(statement, params)
288
+ ?? parseInsert(statement, params)
289
+ ?? parseUpdate(statement, params)
290
+ ?? parseDelete(statement, params)
291
+ ?? parseCreate(statement)
292
+ ?? parseCount(statement, params);
338
293
  }
339
294
 
340
295
  export class MongodbAdapter implements DatabaseAdapter {