uql-orm 0.5.2 → 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.
Files changed (38) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +91 -53
  3. package/dist/browser/uql-browser.min.js.map +1 -1
  4. package/dist/dialect/abstractSqlDialect.d.ts +13 -8
  5. package/dist/dialect/abstractSqlDialect.d.ts.map +1 -1
  6. package/dist/dialect/abstractSqlDialect.js +40 -21
  7. package/dist/dialect/abstractSqlDialect.js.map +1 -1
  8. package/dist/dialect/index.d.ts +1 -0
  9. package/dist/dialect/index.d.ts.map +1 -1
  10. package/dist/dialect/index.js +1 -0
  11. package/dist/dialect/index.js.map +1 -1
  12. package/dist/dialect/jsonArrayElemMatchUtils.d.ts +10 -0
  13. package/dist/dialect/jsonArrayElemMatchUtils.d.ts.map +1 -0
  14. package/dist/dialect/jsonArrayElemMatchUtils.js +24 -0
  15. package/dist/dialect/jsonArrayElemMatchUtils.js.map +1 -0
  16. package/dist/dialect/mysqlLikeSqlDialect.d.ts +26 -0
  17. package/dist/dialect/mysqlLikeSqlDialect.d.ts.map +1 -0
  18. package/dist/dialect/mysqlLikeSqlDialect.js +94 -0
  19. package/dist/dialect/mysqlLikeSqlDialect.js.map +1 -0
  20. package/dist/maria/mariaDialect.d.ts +3 -4
  21. package/dist/maria/mariaDialect.d.ts.map +1 -1
  22. package/dist/maria/mariaDialect.js +11 -13
  23. package/dist/maria/mariaDialect.js.map +1 -1
  24. package/dist/mysql/mysqlDialect.d.ts +3 -16
  25. package/dist/mysql/mysqlDialect.d.ts.map +1 -1
  26. package/dist/mysql/mysqlDialect.js +2 -93
  27. package/dist/mysql/mysqlDialect.js.map +1 -1
  28. package/dist/postgres/postgresDialect.d.ts +2 -2
  29. package/dist/postgres/postgresDialect.d.ts.map +1 -1
  30. package/dist/postgres/postgresDialect.js +10 -1
  31. package/dist/postgres/postgresDialect.js.map +1 -1
  32. package/dist/sqlite/sqliteDialect.d.ts +3 -13
  33. package/dist/sqlite/sqliteDialect.d.ts.map +1 -1
  34. package/dist/sqlite/sqliteDialect.js +25 -20
  35. package/dist/sqlite/sqliteDialect.js.map +1 -1
  36. package/dist/type/entity.d.ts +18 -8
  37. package/dist/type/entity.d.ts.map +1 -1
  38. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. Please add
4
4
 
5
5
  date format is [yyyy-mm-dd]
6
6
 
7
+ ## [0.6.0] - 2026-03-18
8
+ ### Features
9
+ - **JSON update operators expanded**: Added `$push` for atomic JSON array append in update payloads across SQL dialects.
10
+ - **Unified JSON update API type**: Introduced `JsonUpdateOp` with type-safe `$merge`, `$unset`, and `$push` support for `Json<T>` fields.
11
+
12
+ ### Bug Fixes
13
+ - **MariaDB JSON dot-notation correctness**: Fixed JSON path extraction for MariaDB by using `JSON_VALUE(...)` instead of MySQL-style `->` / `->>` operators.
14
+ - **PostgreSQL operator chaining semantics**: Fixed `$merge + $push` evaluation when targeting the same key so `$push` reads from the current intermediate expression (not stale column state).
15
+
16
+ ### Testing
17
+ - Added multi-dialect regression tests for JSON update chaining (`$merge`, `$push`, `$unset`), including same-key `$merge + $push` behavior.
18
+ - Added MariaDB-specific regression tests for dot-notation filtering/sorting SQL generation (`JSON_VALUE` paths).
19
+
20
+ ## [0.5.2] - 2026-03-17
21
+ ### Testing
22
+ - **Suite reliability**: Ensured the full test suite runs without runtime errors across all dialects with coverage thresholds still met (>97% statements, >90% branches).
23
+
7
24
  ## [0.5.1] - 2026-03-15
8
25
  ### Chore
9
26
  - **Documentation**: Unified documentation strategy using NPM lifecycle scripts across subpackages.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![tests](https://github.com/rogerpadilla/uql/actions/workflows/tests.yml/badge.svg)](https://github.com/rogerpadilla/uql) [![Coverage Status](https://coveralls.io/repos/github/rogerpadilla/uql/badge.svg?branch=main)](https://coveralls.io/github/rogerpadilla/uql?branch=main) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/rogerpadilla/uql/blob/main/LICENSE) [![npm version](https://img.shields.io/npm/v/uql-orm.svg)](https://www.npmjs.com/package/uql-orm)
6
6
 
7
- **[UQL](https://uql-orm.dev)** is a clean, ultra-fast TypeScript ORM designed for developers who value portability and performance. It eliminates the friction between SQL and MongoDB, providing a unified, type-safe experience without proprietary DSLs or heavy codegen steps.
7
+ **[UQL](https://uql-orm.dev)** is a TypeScript ORM focused on portability, performance, and a consistent query model across SQL and MongoDB. In our open benchmark, SQL generation reaches [**3.9M+ ops/s**](https://github.com/rogerpadilla/ts-orm-benchmark). UQL is designed for teams that want type safety and dialect portability without introducing a proprietary query DSL.
8
8
 
9
9
  ```ts
10
10
  const results = await querier.findMany(User, {
@@ -15,28 +15,61 @@ const results = await querier.findMany(User, {
15
15
  });
16
16
  ```
17
17
 
18
- &nbsp;
18
+ ## Quick Start
19
+
20
+ ```sh
21
+ npm install uql-orm pg
22
+ ```
23
+
24
+ ```ts
25
+ import { PgQuerierPool } from 'uql-orm/postgres';
26
+
27
+ const pool = new PgQuerierPool({ host: 'localhost', database: 'app' });
28
+ const users = await pool.withQuerier((querier) => querier.findMany(User, { $limit: 10 }));
29
+ ```
30
+
31
+ For production setup and migrations, jump to:
32
+ - [Install](#1-install)
33
+ - [Define your Entities](#2-define-your-entities)
34
+ - [Migrations & Synchronization](#5-migrations--synchronization)
35
+
36
+ > **Note:** For explicit lifecycle control, use manual `getQuerier()` + `release()` (shown in [Core Query Pattern](#core-query-pattern)).
37
+
38
+ ## Guide Map
39
+
40
+ Core path:
41
+ - [1. Install](#1-install)
42
+ - [2. Define your Entities](#2-define-your-entities)
43
+ - [3. Set up a pool](#3-set-up-a-pool)
44
+ - [4. Manipulate the Data](#4-manipulate-the-data)
45
+ - [5. Migrations & Synchronization](#5-migrations--synchronization)
46
+
47
+ Advanced and operations:
48
+ - [Semantic Search](#modern-indexing-semantic-search)
49
+ - [JSON Operators & Relation Filtering](#json-operators--relation-filtering)
50
+ - [Thread-Safe Transactions](#thread-safe-transactions)
51
+ - [6. Logging & Monitoring](#6-logging--monitoring)
52
+ - [Deep Dive: Tests & Technical Resources](#deep-dive-tests--technical-resources)
19
53
 
20
54
  ## Features
21
55
 
22
56
  | Feature | Why it matters |
23
57
  | :--- | :--- |
24
58
  | **[Intelligent Querying](https://uql-orm.dev/querying/relations)** | Deep auto-completion for operators and relations at any depth—no more guessing property names. |
25
- | **Serializable JSON** | 100% valid JSON queries. Send your query logic over HTTP or WebSockets as easily as a string. |
26
- | **Unified Dialects** | Write once, run anywhere. Seamlessly switch between PostgreSQL, MySQL, SQLite, and MongoDB. |
59
+ | **Serializable JSON** | Query objects are valid JSON, which makes them straightforward to transport over HTTP/gRPC/WebSockets. |
60
+ | **Unified Dialects** | Write once, run anywhere. Seamlessly switch between PostgreSQL, MySQL, MariaDB, SQLite, and MongoDB. |
27
61
  | **[Naming Strategies](https://uql-orm.dev/naming-strategy)** | No more `camelCase` vs `snake_case` headaches. Map your code to your database automatically. |
28
- | **Smart SQL Engine** | Zero-allocation SQL generation. Built for high-throughput apps where every millisecond counts. |
62
+ | **Smart SQL Engine** | Zero-allocation SQL generation with top-ranked results in our [open benchmark](https://github.com/rogerpadilla/ts-orm-benchmark). |
29
63
  | **Thread-Safe by Design** | Protect your data integrity with centralized task queues and the `@Serialized()` decorator. |
30
64
  | **[Declarative Transactions](https://uql-orm.dev/querying/transactions)** | Clean `@Transactional()` decorators that work beautifully with modern DI frameworks like NestJS. |
31
65
  | **[Lifecycle Hooks](https://uql-orm.dev/entities/lifecycle-hooks)** | Automate validation, timestamps, and computed logic with intuitive class-based decorators. |
32
66
  | **[Aggregate Queries](https://uql-orm.dev/querying/aggregate)** | Real-time analytics with `GROUP BY`, `HAVING`, and native math operators across all dialects. |
33
67
  | **[Semantic Search](https://uql-orm.dev/querying/semantic-search)** | Native vector similarity search. Rank results by meaning using standard ORM operators. |
34
68
  | **[Cursor Streaming](https://uql-orm.dev/querying/streaming)** | Process millions of rows with a stable memory footprint using native driver-level cursors. |
35
- | **[Modern & Versatile](https://uql-orm.dev/entities/virtual-fields)** | Pure ESM, high-res timing, built-in soft-delete, and first-class JSONB/JSON support. |
69
+ | **[Modern & Versatile](https://uql-orm.dev/entities/virtual-fields)** | Pure ESM, high-res timing, built-in soft-delete, and first-class JSON/JSONB support. |
36
70
  | **[Database Migrations](https://www.uql-orm.dev/migrations)** | Entity-First synchronization. DDL is auto-generated by diffing your code against the live DB. |
37
71
  | **[Logging & Monitoring](https://www.uql-orm.dev/logging)** | High-visibility debugging with slow-query detection and high-contrast terminal output. |
38
-
39
- &nbsp;
72
+ | **[Fullstack Bridge](https://www.uql-orm.dev/comparison#network-boundaries--apis)** | Speak to your database from the browser securely. First-party `HttpQuerier` removes API boilerplate. |
40
73
 
41
74
  ## 1. Install
42
75
 
@@ -57,7 +90,6 @@ npm install uql-orm # or bun add / pnpm add
57
90
  | **SQLite** | `npm install better-sqlite3` |
58
91
  | **LibSQL** (incl. Turso) | `npm install @libsql/client` |
59
92
  | **MongoDB** | `npm install mongodb` |
60
- | **CockroachDB** | `npm install pg` |
61
93
  | **Cloudflare D1** | _Native (no driver needed)_ |
62
94
 
63
95
  ### TypeScript Configuration
@@ -73,7 +105,7 @@ Ensure your `tsconfig.json` is configured to support decorators and metadata:
73
105
  }
74
106
  ```
75
107
 
76
- &nbsp;**Note:** UQL is Modern Pure ESM — ensure your project's `module` supports ESM imports (e.g., `NodeNext`, `ESNext`, `Bundler`).
108
+ > **Note:** UQL is Modern Pure ESM — ensure your project's `module` supports ESM imports (e.g., `NodeNext`, `ESNext`, `Bundler`).
77
109
 
78
110
  ## 2. Define your Entities
79
111
 
@@ -129,9 +161,6 @@ price?: number;
129
161
  statusCode?: number;
130
162
  ```
131
163
 
132
-
133
- &nbsp;
134
-
135
164
  ```ts
136
165
  import { v7 as uuidv7 } from 'uuid';
137
166
  import { Entity, Id, Field, OneToOne, OneToMany, ManyToOne, ManyToMany, type Relation, type Json } from 'uql-orm';
@@ -224,9 +253,7 @@ export class PostTag {
224
253
  }
225
254
  ```
226
255
 
227
- > **Senior Insight**: Use the `Relation<T>` utility type for relationship properties. It prevents TypeScript circular dependency errors while maintaining full type-safety throughout your app.
228
-
229
- &nbsp;
256
+ > **Note:** Use the `Relation<T>` utility type for relationship properties. It prevents TypeScript circular dependency errors while maintaining full type-safety throughout your app.
230
257
 
231
258
  ## 3. Set up a pool
232
259
 
@@ -241,7 +268,7 @@ export const pool = new PgQuerierPool(
241
268
  { host: 'localhost', database: 'uql_app', max: 10 },
242
269
  {
243
270
  logger: ['error', 'warn', 'migration'],
244
- namingStrategy: new SnakeCaseNamingStrategy()
271
+ namingStrategy: new SnakeCaseNamingStrategy(),
245
272
  slowQuery: { threshold: 1000 },
246
273
  }
247
274
  );
@@ -253,27 +280,27 @@ export default {
253
280
  } satisfies Config;
254
281
  ```
255
282
 
256
- > **Senior Insight**: Don't overcomplicate your setup. Reusing the same connection pool for both your application and migrations reduces overhead and ensures consistent behavior (like naming strategies) across your entire stack.
257
-
258
- &nbsp;
259
-
260
- &nbsp;
283
+ > **Notes:**
284
+ > - Reuse one pool for both app queries and migrations to keep behavior (for example naming strategy) consistent.
285
+ > - If your architecture spans backend + browser, `HttpQuerier` reduces custom API mapping and keeps query semantics aligned.
261
286
 
262
287
  ## 4. Manipulate the Data
263
288
 
264
289
  UQL provides a straightforward API to interact with your data. **Always ensure queriers are released back to the pool.**
265
290
 
291
+ ### Core Query Pattern
292
+
266
293
  ```ts
267
294
  const querier = await pool.getQuerier();
268
295
  try {
269
296
  const results = await querier.findMany(User, {
270
297
  $select: {
271
298
  name: true,
272
- profile: { $select: { bio: true }, $required: true } // INNER JOIN
299
+ profile: { $select: { bio: true }, $required: true }, // INNER JOIN
273
300
  },
274
301
  $where: {
275
302
  status: 'active',
276
- name: { $istartsWith: 'a' }
303
+ name: { $istartsWith: 'a' },
277
304
  },
278
305
  $limit: 10,
279
306
  });
@@ -292,7 +319,19 @@ WHERE "User"."status" = 'active' AND "User"."name" ILIKE 'a%'
292
319
  LIMIT 10 OFFSET 0
293
320
  ```
294
321
 
295
- &nbsp;
322
+ ### Advanced Query Patterns
323
+
324
+ ### Modern Indexing: Semantic Search
325
+
326
+ AI-driven applications require ranking results by meaning. UQL treats vector similarity as a first-class citizen, allowing you to perform semantic search without raw SQL or proprietary extensions.
327
+
328
+ ```ts
329
+ const results = await querier.findMany(Item, {
330
+ $select: { id: true, title: true },
331
+ $sort: { $vector: { embedding: queryVector } },
332
+ $limit: 10,
333
+ });
334
+ ```
296
335
 
297
336
  ### Advanced: Virtual Fields & Raw SQL
298
337
 
@@ -314,11 +353,9 @@ export class Item {
314
353
  }
315
354
  ```
316
355
 
317
- &nbsp;
356
+ ### JSON Operators & Relation Filtering
318
357
 
319
- ### JSONB Operators & Relation Filtering
320
-
321
- Query nested JSON fields using **type-safe dot-notation** with full operator support. Wrap fields with `Json<T>` to get IDE autocompletion for valid paths. UQL generates the correct SQL for each dialect.
358
+ Query nested JSON fields using **type-safe dot-notation** with full operator support. Wrap fields with `Json<T>` to get IDE autocompletion for valid paths. UQL generates native SQL per dialect.
322
359
 
323
360
  ```ts
324
361
  // Filter by nested JSONB field paths
@@ -330,9 +367,22 @@ const items = await querier.findMany(Company, {
330
367
  });
331
368
  ```
332
369
 
333
- **PostgreSQL:** `WHERE ("settings"->>'isArchived') IS DISTINCT FROM $1 AND (("settings"->>'priority'))::numeric >= $2`
370
+ **PostgreSQL:** `WHERE ("settings"->>'isArchived') IS DISTINCT FROM $1 AND (("settings"->>'priority'))::numeric >= $2`
371
+ **MySQL:** `WHERE (\`settings\`->>'isArchived') <> ? AND CAST((\`settings\`->>'priority') AS DECIMAL) >= ?`
372
+ **MariaDB:** `WHERE JSON_VALUE(\`settings\`, '$.isArchived') <> ? AND CAST(JSON_VALUE(\`settings\`, '$.priority') AS DECIMAL) >= ?`
334
373
  **SQLite:** `WHERE json_extract("settings", '$.isArchived') IS NOT ? AND CAST(json_extract("settings", '$.priority') AS REAL) >= ?`
335
374
 
375
+ Atomic JSON updates support `$merge`, `$unset`, and `$push`:
376
+
377
+ ```ts
378
+ await querier.updateOneById(Company, id, {
379
+ settings: { $merge: { theme: 'dark' }, $push: { tags: 'orm' }, $unset: ['deprecated'] },
380
+ });
381
+ ```
382
+
383
+ > **Modern DB Baselines used by docs/examples:** PostgreSQL 16+, MySQL 8.4+ (LTS), MariaDB 12.2+, SQLite 3.45+.
384
+ > Full generated SQL examples: [JSON / JSONB docs](https://uql-orm.dev/querying/json).
385
+
336
386
  Filter parent entities by their **ManyToMany** or **OneToMany** relations using automatic EXISTS subqueries:
337
387
 
338
388
  ```ts
@@ -344,9 +394,7 @@ const posts = await querier.findMany(Post, {
344
394
 
345
395
  **PostgreSQL:** `WHERE EXISTS (SELECT 1 FROM "PostTag" WHERE "PostTag"."postId" = "Post"."id" AND "PostTag"."tagId" IN (SELECT "Tag"."id" FROM "Tag" WHERE "Tag"."name" = $1))`
346
396
 
347
- > **Senior Insight**: Wrap your JSON fields with `Json<T>` to get deep autocompletion for dot-notation paths. It turns a "guess and check" process into a type-safe workflow.
348
-
349
- &nbsp;
397
+ > **Note:** Wrap JSON fields with `Json<T>` to get autocompletion for valid dot-notation paths.
350
398
 
351
399
  ### Aggregate Queries
352
400
 
@@ -388,8 +436,6 @@ const names = await querier.findMany(User, {
388
436
 
389
437
  > **Learn more**: See the full [Aggregate Queries guide](https://uql-orm.dev/querying/aggregate) for `$having` operators, MongoDB pipeline details, and advanced patterns.
390
438
 
391
- &nbsp;
392
-
393
439
  ### Cursor-Based Streaming
394
440
 
395
441
  For large result sets, use `findManyStream()` to iterate row-by-row without loading everything into memory. Each driver uses its optimal native cursor API.
@@ -400,8 +446,6 @@ for await (const user of querier.findManyStream(User, { $where: { active: true }
400
446
  }
401
447
  ```
402
448
 
403
- &nbsp;
404
-
405
449
  ### Thread-Safe Transactions
406
450
 
407
451
  UQL is one of the few ORMs with a **centralized serialization engine**. Transactions are guaranteed to be race-condition free.
@@ -452,8 +496,6 @@ try {
452
496
  }
453
497
  ```
454
498
 
455
- &nbsp;
456
-
457
499
  ## 5. Migrations & Synchronization
458
500
 
459
501
  UQL takes an **Entity-First** approach. You modify your TypeScript classes, and UQL handles the heavy lifting—auto-generating migration files by diffing your code against the live database.
@@ -467,7 +509,7 @@ npx uql-migrate generate:entities add_user_nickname
467
509
  npx uql-migrate up
468
510
  ```
469
511
 
470
- > **Senior Insight**: Your entities are the single source of truth. This workflow eliminates the "drift" between what's in your code and what's in production.
512
+ > **Note:** Keep entities as the source of truth to minimize drift between code and database schema.
471
513
 
472
514
  ### 1. Unified Configuration
473
515
 
@@ -527,7 +569,7 @@ npx uql-migrate generate seed_default_roles
527
569
 
528
570
  ### 3. AutoSync (Development)
529
571
 
530
- Keep your schema in sync without manual migrations. It is **Safe by Default**: In safe mode (default), it strictly **adds** new tables and columns but **blocks** any destructive operations (column drops or type alterations) to prevent data loss. It provides **Transparent Feedback** by logging detailed warnings for any blocked changes, so you know exactly what remains to be migrated manually.
572
+ Keep your schema in sync without manual migrations. It is **safe by default**: in safe mode (default), it adds new tables/columns and blocks destructive changes (column drops or type alterations). Blocked actions are logged so you can migrate them manually.
531
573
 
532
574
  **New Capabilities (v3.8+):**
533
575
 
@@ -565,11 +607,13 @@ const migrator = new Migrator(pool, {
565
607
  await migrator.autoSync({ logging: true });
566
608
  ```
567
609
 
568
- &nbsp;
610
+ > **Note:** In development, `autoSync` accelerates iteration while still protecting data by blocking destructive schema changes.
611
+
612
+ ## Operations
569
613
 
570
- ## 6. Logging & Monitoring
614
+ ### 6. Logging & Monitoring
571
615
 
572
- UQL features a professional-grade, structured logging system designed for high visibility and sub-millisecond performance monitoring.
616
+ UQL includes a structured logging system for query visibility and performance monitoring.
573
617
 
574
618
  ### Log Levels
575
619
 
@@ -584,7 +628,7 @@ UQL features a professional-grade, structured logging system designed for high v
584
628
 
585
629
  ### Visual Feedback
586
630
 
587
- The `DefaultLogger` provides high-contrast, colored output that makes debugging feel like a premium experience:
631
+ The `DefaultLogger` provides high-contrast, colored output for quick debugging:
588
632
 
589
633
  ```text
590
634
  query: SELECT * FROM "user" WHERE "id" = $1 -- [123] [2ms]
@@ -592,9 +636,7 @@ slow query: UPDATE "post" SET "title" = $1 -- ["New Title"] [1250ms]
592
636
  error: Failed to connect to database: Connection timeout
593
637
  ```
594
638
 
595
- > **Senior Insight**: In production, keep your logs lean. By setting `logger: ['error', 'warn', 'slowQuery']`, UQL stays silent until a performance bottleneck actually occurs.
596
-
597
- &nbsp;
639
+ > **Note:** In production, keep logs lean with `logger: ['error', 'warn', 'slowQuery']`.
598
640
 
599
641
  Learn more about UQL at [uql-orm.dev](https://uql-orm.dev) for details on:
600
642
 
@@ -606,9 +648,7 @@ Learn more about UQL at [uql-orm.dev](https://uql-orm.dev) for details on:
606
648
  - [Soft Deletes &amp; Auditing](https://uql-orm.dev/entities/soft-delete)
607
649
  - [Database Migration &amp; Syncing](https://uql-orm.dev/migrations)
608
650
 
609
- &nbsp;
610
-
611
- ## 🛠 Deep Dive: Tests & Technical Resources
651
+ ### Deep Dive: Tests & Technical Resources
612
652
 
613
653
  For those who want to see the "engine under the hood," check out these resources in the source code:
614
654
 
@@ -619,8 +659,6 @@ For those who want to see the "engine under the hood," check out these resources
619
659
  - [PostgreSQL](https://github.com/rogerpadilla/uql/blob/main/packages/uql-orm/src/postgres/postgresDialect.spec.ts) \| [MySQL](https://github.com/rogerpadilla/uql/blob/main/packages/uql-orm/src/mysql/mysqlDialect.spec.ts) \| [SQLite](https://github.com/rogerpadilla/uql/blob/main/packages/uql-orm/src/sqlite/sqliteDialect.spec.ts) specs.
620
660
  - [Querier Integration Tests](https://github.com/rogerpadilla/uql/blob/main/packages/uql-orm/src/querier/abstractSqlQuerier-spec.ts): SQL generation & connection management tests.
621
661
 
622
- &nbsp;
623
-
624
662
  ## Built with ❤️ and supported by
625
663
 
626
664
  UQL is an open-source project proudly sponsored by **[Variability.ai](https://variability.ai)**.