masquerade-orm 0.1.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 (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/bin/universalTsInit.js +59 -0
  4. package/docs/deletion.md +186 -0
  5. package/docs/find.md +265 -0
  6. package/docs/getting-started-javascript.md +112 -0
  7. package/docs/getting-started-typescript.md +159 -0
  8. package/docs/in-depth-class-definitions.md +193 -0
  9. package/docs/jsdoc-ux-tips.md +17 -0
  10. package/docs/managing-the-database.md +37 -0
  11. package/docs/saving-to-database.md +37 -0
  12. package/index.d.ts +7 -0
  13. package/index.js +11 -0
  14. package/jsconfig.json +29 -0
  15. package/package.json +40 -0
  16. package/src/ORM/DbManager.js +76 -0
  17. package/src/ORM/ORM.js +181 -0
  18. package/src/ORM/bootOrm.js +929 -0
  19. package/src/changeLogger/changeLogger.js +55 -0
  20. package/src/changeLogger/save.js +237 -0
  21. package/src/changeLogger/sqlClients/postgres.js +97 -0
  22. package/src/changeLogger/sqlClients/sqlite.js +120 -0
  23. package/src/entity/delete/delete.js +20 -0
  24. package/src/entity/delete/getDependents.js +46 -0
  25. package/src/entity/entity.d.ts +46 -0
  26. package/src/entity/entity.js +228 -0
  27. package/src/entity/find/find.js +157 -0
  28. package/src/entity/find/joins.js +52 -0
  29. package/src/entity/find/queryBuilder.js +95 -0
  30. package/src/entity/find/relations.js +23 -0
  31. package/src/entity/find/scopeProxies.js +60 -0
  32. package/src/entity/find/sqlClients/postgresFuncs.js +171 -0
  33. package/src/entity/find/sqlClients/sqliteFuncs.js +211 -0
  34. package/src/entity/find/where/relationalWhere.js +142 -0
  35. package/src/entity/find/where/where.js +244 -0
  36. package/src/entity/find/where/whereArgsFunctions.js +49 -0
  37. package/src/misc/classes.js +63 -0
  38. package/src/misc/constants.js +42 -0
  39. package/src/misc/miscFunctions.js +167 -0
  40. package/src/misc/ormStore.js +18 -0
  41. package/src/misc/types.d.ts +560 -0
  42. package/src/proxies/instanceProxy.js +544 -0
  43. package/src/proxies/nonRelationalArrayProxy.js +76 -0
  44. package/src/proxies/objectProxy.js +50 -0
  45. package/src/proxies/relationalArrayProxy.js +130 -0
  46. package/src/webpack/masquerade-loader.js +14 -0
  47. package/src/webpack/plugin.js +43 -0
  48. package/src/webpack/store.js +2 -0
  49. package/src/webpack/webpack.config.js +41 -0
  50. package/testing/dev-doc.md +7 -0
  51. package/testing/generationFuncs.js +21 -0
  52. package/testing/miscFunctions.js +97 -0
  53. package/testing/postgres.test.js +254 -0
  54. package/testing/sqlite.test.js +257 -0
  55. package/testing/testing-classes.js +102 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Masquerade-Orm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ <div align="center">
2
+ <a href="#">
3
+ <img
4
+ src="https://github.com/user-attachments/assets/3bf1ab31-f9c6-4362-b17d-1dfe7c414f17"
5
+ alt="Masquerade ORM Logo"
6
+ style="max-width: 50%; height: auto;"
7
+ />
8
+ </a>
9
+ <br><br>
10
+ <a href="">
11
+ <br><br>
12
+ <img src="https://img.shields.io/badge/License-MIT-teal.svg" alt="MIT License"/>
13
+ </a>
14
+ <br><br>
15
+ </div>
16
+
17
+ **MasqueradeORM** is a lightweight ORM for Node.js that works seamlessly with both TypeScript and JavaScript.
18
+ Its goal is to hide SQL complexity while letting you work naturally in JS/TS syntax.
19
+ Instead of forcing you into ORM-specific models, metadata systems, or decorators, MasqueradeORM lets you use **your own classes** directly, exactly as you normally would.
20
+
21
+ MasqueradeORM improves readability, maintainability, and workflow simplicity through a unified coding approach and extremely minimal setup.
22
+ No ORM offers a simpler start.
23
+ There’s no need to manage heavy configuration layers, maintain secondary schema systems, or even plan your database structure separately.
24
+ Your schema and tables are generated automatically from a single source of truth: **Your class definitions.**
25
+
26
+ MasqueradeORM currently supports the following SQL clients:
27
+ - **SQLite**
28
+ - **Postgresql**
29
+
30
+ # Features
31
+ - **Effortless setup** - no ORM-specific structures; just use your classes.
32
+ - **Zero schema planning** - tables and schema are generated automatically.
33
+ - **Powerful IntelliSense** - confidently build complex queries with real-time IDE feedback when something’s wrong.
34
+ - **Minimal memory usage** - one class instance per database row, minimizing memory usage and avoiding duplicates through smart state management.
35
+ - **Optimized querying** - fewer queries through intelligent transaction grouping without sacrificing data integrity.
36
+ - **Relational WHERE clauses** - easily write conditions that compare two columns within the same table or columns across different tables.
37
+ - **Write complex WHERE conditions using a template-literal helper** - enabling expressive comparisons like >=, LIKE, object-property access, and even array element matching, all without cluttering your query code.
38
+ - **SQL injection protection** - all queries are parameterized.
39
+ - **Lightweight** - minimal dependencies.
40
+ - **Strong typing even in JavaScript** - powered by JSDoc, no compile step required.
41
+ - **Reduced data transfer size** - improves performance in client-server setups (not applicable for embedded databases like SQLite).
42
+ - **Abstract and non-abstract inheritance** - enables the use of abstract classes, even in JavaScript.
43
+ - **Combines the convenience of embedded SQLite with the strict typing of RDBMS**
44
+ - **Eager and lazy relations**
45
+ - **Unidirectional, bidirectional, and self-referenced relations**
46
+
47
+
48
+ # Example Code Implementation
49
+
50
+ ### Creating an ORM-Compatible Class
51
+ ```ts
52
+ import { Entity } from 'masquerade'
53
+
54
+ type UserSettings = {
55
+ theme: 'light' | 'dark' | 'system'
56
+ twoStepVerification: boolean
57
+ locale: 'en' | 'es' | 'fr' | 'de'
58
+ }
59
+
60
+ export class User extends Entity {
61
+ username: string
62
+ email: string
63
+ password: string
64
+ createdAt: Date = new Date()
65
+ friendList: User[] = []
66
+ settings: UserSettings & object = {
67
+ locale: "en",
68
+ theme: "system",
69
+ twoStepVerification: false
70
+ }
71
+
72
+ constructor(username: string, email: string, password: string) {
73
+ super()
74
+ this.username = username
75
+ this.email = email
76
+ this.password = password
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### Basic Find Example
82
+ ```ts
83
+ // finds any User instance with email === lookupEmail
84
+ async function findUserByEmail(lookupEmail: string): Promise<User | undefined> {
85
+ const resultArray = await User.find({
86
+ where: { email: lookupEmail }
87
+ })
88
+ // the static 'find' method above is inherited from 'Entity'
89
+
90
+ return resultArray[0]
91
+ }
92
+ ```
93
+
94
+ ### Saving Instances
95
+ ```ts
96
+ // Creating a new table row in the User table
97
+ const newUser = new User('JohnDoe57', 'johnDoe@yahoo.com', 'passwordHash')
98
+ // newUser will be saved to the database automatically, no explicit save call is required.
99
+
100
+ // Finding a user by email
101
+ const user = await findUserByEmail('johnDoe@yahoo.com') // user's friendList is a promise
102
+ console.log(user.username === 'JohnDoe57') // true
103
+ ```
104
+
105
+ ### Mutating Data
106
+ All mutations are persisted implicitly and automatically, meaning that simply changing a value is enough for it to be reflected in the database.
107
+
108
+ **Mutating non-Relational Properties**
109
+ ```ts
110
+ user.settings.theme = 'dark'
111
+ ```
112
+
113
+ **Mutating Relational Properties**
114
+ ```ts
115
+ // lazy-load friendList
116
+ await user.friendList
117
+ // add a new relation
118
+ user.friendList.push(new User('JaneDoe33', 'janeDoe@yahoo.com', 'passwordHash2'))
119
+ // remove a relation
120
+ user.friendList.pop()
121
+ ```
122
+
123
+
124
+ # Further Reading
125
+
126
+ - **[Getting Started - Javascript](https://github.com/MasqueradeORM/MasqueradeORM/blob/master/docs/getting-started-javascript.md#class-definitions)**
127
+ - **[Getting Started - Typescript](https://github.com/MasqueradeORM/MasqueradeORM/blob/master/docs/getting-started-typescript.md#class-definitions)**
128
+ - **[Find Method](https://github.com/MasqueradeORM/MasqueradeORM/blob/master/docs/find.md#find)**
129
+ - **[Saving to Database](https://github.com/MasqueradeORM/MasqueradeORM/blob/master/docs/saving-to-database.md#saving-to-the-database)**
130
+ - **[Deleting Instances from the Database](https://github.com/MasqueradeORM/MasqueradeORM/blob/master/docs/deletion.md)**
131
+ - **[Managing Database Tables](https://github.com/MasqueradeORM/MasqueradeORM/blob/master/docs/managing-the-database.md)**
132
+ - **[Defining Classes: In-Depth](https://github.com/MasqueradeORM/MasqueradeORM/blob/master/docs/in-depth-class-definitions.md)** **(important read)**
133
+
134
+
135
+
136
+ <br>
137
+ <div align="center">
138
+ <strong>
139
+ © 2026
140
+ <a href="https://github.com/MasqueradeORM">MasqueradeORM </a>
141
+ -
142
+ Released under the MIT License
143
+ </strong>
144
+ </div>
145
+
146
+
147
+
148
+
149
+
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+
3
+ import ts from "typescript"
4
+ import fs from "fs"
5
+ import path from "path"
6
+ import { createSourceFile, SyntaxKind, ScriptKind, ScriptTarget } from "typescript"
7
+ import { nodeArr2ClassDict } from "../src/ORM/bootOrm.js"
8
+
9
+ const configPath = ts.findConfigFile(
10
+ "./",
11
+ ts.sys.fileExists,
12
+ "tsconfig.json"
13
+ )
14
+
15
+ if (!configPath) {
16
+ throw new Error("tsconfig.json not found")
17
+ }
18
+
19
+ const configFile = ts.readConfigFile(
20
+ configPath,
21
+ ts.sys.readFile
22
+ )
23
+
24
+ const parsed = ts.parseJsonConfigFileContent(
25
+ configFile.config,
26
+ ts.sys,
27
+ "./"
28
+ )
29
+
30
+ const program = ts.createProgram({
31
+ rootNames: parsed.fileNames,
32
+ options: parsed.options
33
+ })
34
+
35
+ const classNodesArr = []
36
+
37
+ for (const sourceFile of program.getSourceFiles()) {
38
+ if (sourceFile.isDeclarationFile) continue
39
+ const fileText = sourceFile.getFullText()
40
+ const fileNodesArr = createSourceFile('', fileText, ScriptTarget.Latest, true, ScriptKind.TSX).statements
41
+
42
+ for (const node of fileNodesArr) {
43
+ //@ts-ignore
44
+ if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && node.heritageClauses)
45
+ classNodesArr.push(node)
46
+ }
47
+ }
48
+
49
+ const classDict = nodeArr2ClassDict(classNodesArr)
50
+ const filePath = path.join(
51
+ process.cwd(),
52
+ "ormTypeScriptSetup.js"
53
+ )
54
+ const content = `export function UniversalTsSetup() {globalThis.masqueradeClassDict_ = ${JSON.stringify(classDict)};\n}`
55
+ fs.writeFileSync(filePath, content, "utf8")
56
+
57
+
58
+
59
+
@@ -0,0 +1,186 @@
1
+ # Deleting Instances from the Database
2
+
3
+ ### Soft Deletion
4
+ The ORM does not support soft deletion by default. To implement soft deletion, just create an abstract class like so:
5
+
6
+ **TypeScript**
7
+ ```ts
8
+ export abstract class SoftDel extends Entity {
9
+ isDeleted: boolean = false
10
+
11
+ constructor() {
12
+ super()
13
+ }
14
+ }
15
+
16
+ export abstract class SoftDelUuid extends Entity {
17
+ // switch 'UUID' to 'INT' or 'BIGINT' as needed.
18
+ static ormClassSettings_ = { idType: 'UUID' }
19
+ isDeleted: boolean = false
20
+
21
+ constructor() {
22
+ super()
23
+ }
24
+ }
25
+ ```
26
+
27
+ **JavaScript**
28
+ ```js
29
+ export class SoftDel extends Entity {
30
+ /**@type {boolean}*/ isDeleted = false
31
+
32
+ /** @abstract */
33
+ constructor() {
34
+ super()
35
+ }
36
+ }
37
+
38
+ export class SoftDelUuid extends Entity {
39
+ // switch 'UUID' to 'INT' or 'BIGINT' as needed.
40
+ static ormClassSettings_ = { idType: 'UUID' }
41
+ /**@type {boolean}*/ isDeleted = false
42
+
43
+ /** @abstract */
44
+ constructor() {
45
+ super()
46
+ }
47
+ }
48
+ ```
49
+
50
+ Every class that extends these `SoftDel` classes will have a boolean property `isDeleted` that is `false` by default, allowing instances to be soft-deleted by setting the `isDeleted` value to `true`.
51
+
52
+ ```ts
53
+ export class SoftDeletableUser extends SoftDelUuid {
54
+ username: string
55
+ email: string
56
+ password: string
57
+
58
+ constructor (username, email, password) {
59
+ this.username = username
60
+ this.email = email
61
+ this.password = password
62
+ }
63
+ }
64
+
65
+ const softDelUser = new SoftDeletableUser('JohnDoe', 'JohnDoe@gmail.com', 'hashedPassword')
66
+ // softDelUser has an 'isDeleted' property that can be toggled to true for soft deletion
67
+ // and has an id type of UUID.
68
+ ```
69
+
70
+ **Note:** Unlike `Entity`, the `SoftDel` classes need to be passed into the `ORM.boot` method that is being called to init the ORM.
71
+
72
+ ### Hard Deletion
73
+
74
+ ```ts
75
+ import { sql } from "masquerade"
76
+ const twoYearsAgo = new Date().setFullYear(new Date().getFullYear() - 2)
77
+
78
+ // finds all instances that haven't been mutated in over two years
79
+ const instances4Deletion = await ExampleClass.find({where: {updatedAt: sql`< ${twoYearsAgo}`}})
80
+
81
+ // delete all instances
82
+ instances4Deletion.forEach(instance => instance.delete())
83
+ ```
84
+
85
+ The `delete` method hard-deletes the instance from the database; the instance cannot be recovered afterwards.
86
+
87
+ <div align="center">
88
+ <strong style="font-size: 1.2em;">
89
+ ** This method will throw an error in case a pre-deletion step is required. **
90
+ </strong>
91
+ </div>
92
+
93
+
94
+ ## Pre-Deletion Step
95
+
96
+ ### When is a pre-deletion step required?
97
+ A pre-deletion step is required when the class instance has **dependents**.
98
+
99
+
100
+ ### What is a dependent?
101
+ A dependent is a class that has a required 1-to-1 relationship with another class, meaning the relation cannot be undefined.
102
+
103
+ ```ts
104
+ // dependent 1-to-1 relation, 'sender' cannot be undefined
105
+ sender: User
106
+ ```
107
+
108
+ For example, if a `Message` has a `sender` property of type `User`, then `Message` is dependent on `User`. As a result, a `User` instance cannot be safely deleted until all dependent `Message` instances are resolved of said dependency.
109
+
110
+ ```ts
111
+ sender?: User // optional relation
112
+ sender: User | undefined // optional relation
113
+ ```
114
+
115
+ If the property was optional or of type `User | undefined`, the `Message` class would **NOT** be dependent on `User`.
116
+ In such a case, when a `User` instance is deleted, any of its messages will have an `undefined` value in the `sender` property.
117
+
118
+
119
+ ### How to find an instance's dependents?
120
+ Assuming `User` has dependents:
121
+
122
+ ```ts
123
+ // finds the user with a username of 'JohnDoe57'
124
+ const user = (await User.find({ where: {username: 'JohnDoe57'} }))[0]
125
+ const dependentsDict = await user.getDependents()
126
+ ```
127
+
128
+ ### What is the structure of dependentsDict?
129
+
130
+ ```ts
131
+ type DependentsDict<T extends Entity> = {
132
+ [dependentClassName: string]: [
133
+ dependentInstances: T[],
134
+ dependentProps: string[]
135
+ ]
136
+ }
137
+ ```
138
+
139
+ Assuming the classes `Payment` and `Message` are dependents of the `User` class, with `Payment` having a dependent property of `sentBy` and `receivedBy`, and `Message` having a dependent property of `sender`.
140
+
141
+ ```ts
142
+ // structure of dependentsDict
143
+ dependentsDict = {
144
+ 'Payment': [
145
+ // all Payment instances that depend on the deleted User instance
146
+ [paymentInstance1, paymentInstance2, ...],
147
+ // all properties on Payment that depend on the deleted User instance
148
+ ['sentBy', 'receivedBy']
149
+ ],
150
+ 'Message': [
151
+ // all Message instances that depend on the deleted User instance
152
+ [messageInstance1, messageInstance2, ...],
153
+ // all properties on Message that depend on the deleted User instance
154
+ ['sender']
155
+ ]
156
+ }
157
+ ```
158
+
159
+ ### Resolving dependencies
160
+ To resolve the dependencies, access the data as such:
161
+ ```ts
162
+ const user = (await User.find({ where: { username: 'JohnDoe57' } }))[0]
163
+ const dependentsDict = await user.getDependents()
164
+ const deletedUserId = user.id
165
+
166
+ for (const dependentClassName of Object.keys(dependentsDict)) {
167
+ const [dependentInstances, dependentProps] = dependentsDict[dependentClassName]
168
+
169
+ for (const instance of dependentInstances) {
170
+ dependentProps.forEach(prop => {
171
+ if (instance[prop].id !== deletedUserId) continue
172
+ // decoupling logic here...
173
+ })
174
+ }
175
+ }
176
+ ```
177
+
178
+ <br>
179
+ <div align="center">
180
+ <strong>
181
+ © 2026
182
+ <a href="https://github.com/MasqueradeORM">MasqueradeORM </a>
183
+ -
184
+ Released under the MIT License
185
+ </strong>
186
+ </div>
package/docs/find.md ADDED
@@ -0,0 +1,265 @@
1
+ # Find
2
+
3
+ ```js
4
+ await ExampleClass.find(findObj)
5
+ ```
6
+
7
+ The `find` method is the most complex part of the ORM. **Fortunately, it is fully covered by IntelliSense, and you are strongly encouraged to rely on it.**
8
+
9
+ It accepts a single argument, findObj, which contains three optional fields.
10
+ Because all fields are optional, findObj itself may be an empty object (although this is rarely useful, as it would return all instances of ExampleClass).
11
+
12
+ The three optional fields are:
13
+
14
+ - relations
15
+ - where
16
+ - relationalWhere
17
+
18
+
19
+
20
+ ## The `relations` Field:
21
+
22
+ The `relations` field determines which relations are eagerly loaded from the database.
23
+
24
+ A crucial detail to understand is that relations are never filtered.
25
+ They are either loaded or not. The ORM never displays partial relational data.
26
+
27
+ ```js
28
+ // assume that relationalProp is a property of type SomeClass or SomeClass[]
29
+ await ExampleClass.find(
30
+ {
31
+ relations: {relationalProp: true},
32
+ where: {
33
+ relationalProp: {
34
+ id: 57
35
+ }
36
+ }
37
+ }
38
+ )
39
+ ```
40
+
41
+ The example above translates to:
42
+ **“Fetch all instances of ExampleClass whose relationalProp contains a SomeClass instance with id = 57.”**
43
+
44
+ In other words, the condition matches when either of the following is true:
45
+
46
+ ```js
47
+ // 1-to-1 relationship case
48
+ exampleClassInstance.relationalProp === someClassId57
49
+ ```
50
+ or
51
+ ```js
52
+ // 1-to-many relationship case
53
+ exampleClassInstance.relationalProp.includes(someClassId57)
54
+ ```
55
+
56
+ ### Lazy Loading
57
+
58
+ ```js
59
+ // Assume the 'Chat' class has relational properties 'users' and 'messages'.
60
+ // Initially, we load only the 'messages' relation for a specific chat.
61
+
62
+ const resultArray = await Chat.find({
63
+ relations: { messages: true }, // eager load the 'messages' relation
64
+ where: { id: 123 } // fetch the chat with ID 123
65
+ })
66
+
67
+ const someChat = resultArray[0]
68
+
69
+ // At this point, 'someChat.users' is not loaded.
70
+ // To load the 'users' relation, we need to await it.
71
+ await someChat.users
72
+ ```
73
+
74
+
75
+ ## The `where` Field:
76
+
77
+ The `where` field is for filtering the root instances, in the following case, Chat instances.
78
+ ```js
79
+ await Chat.find({
80
+ where: {
81
+ messages: {
82
+ sender: {
83
+ id: 12
84
+ }
85
+ }
86
+ }
87
+ })
88
+ ```
89
+ Translation: **“Find all chats that contain a message from a user with the id 12, without loading messages.“**
90
+
91
+ - **Note:** The scope of the `where` condtions is agnostic to the scope of the `relations` (eager-loading).
92
+ It is completely safe to filter based on specific relations without having said relations passed into the `relations` field.
93
+
94
+ ### Introduction to the `sql`, `OR` and `AND` functions
95
+
96
+ ```js
97
+ import { sql, AND, OR } from "masquerade"
98
+
99
+ await Angel.find({
100
+ where: {
101
+ // name is either "Micheal" OR "Gabriel"
102
+ name: OR('Micheal', 'Gabriel'),
103
+
104
+ // demonsSentToAbyss is greater than 12,000 AND less than 57,000
105
+ demonsSentToAbyss: AND(sql`> 12000`, sql`< 57000`)
106
+ }
107
+ })
108
+ ```
109
+
110
+ ### Using the `sql` function with explicit column identifiers
111
+ In the previous example, the `sql` function implicitly inserted a column identifier (`#`) on the left side of the SQL statement.
112
+ ```js
113
+ // these two statements are equivalent
114
+ sql`> 12000`
115
+ sql`# > 12000`
116
+ ```
117
+
118
+ In next example, `#` identifiers must be written explicitly because the SQL string uses `AND` conditional operators directly, rather than using the `AND()` helper function.
119
+
120
+ ```js
121
+ import { sql } from "masquerade"
122
+
123
+ const twoYearsAgo = new Date().setFullYear(new Date().getFullYear() - 2)
124
+ const oneYearAgo = new Date().setFullYear(new Date().getFullYear() - 1)
125
+
126
+ await User.find({
127
+ where: {
128
+ // donations between 1,200 and 5,700 cents (exclusive)
129
+ donations: sql`1200 < # AND # < 5700`,
130
+
131
+ // account's age is between one and two years old.
132
+ createdAt: sql`${twoYearsAgo} <= # AND # <= ${oneYearAgo}`
133
+ }
134
+ })
135
+ // The ANDs are written directly inside the 'sql' string instead of
136
+ // having to rely on helper functions to achieve the same result.
137
+ // The 'sql' function gives you the ability to write powerful
138
+ // 'where' conditions in an easy-to-read and easy-to-write manner.
139
+ ```
140
+
141
+ ### Using the `sql` function to create a `LIKE` `WHERE` condition
142
+ ```js
143
+ import { sql } from "masquerade"
144
+
145
+ await User.find({
146
+ where: {
147
+ // registered using a Gmail email
148
+ email: sql`LIKE '%@gmail.com%'`
149
+ }
150
+ })
151
+ ```
152
+
153
+ ### Using the `sql` function to create a `WHERE` condition for matching JSON values
154
+
155
+ ```ts
156
+ import { Entity } from "masquerade"
157
+
158
+ type OrderOverview = {
159
+ status: "pending" | "completed" | "cancelled"
160
+ total: number
161
+ currency: string
162
+ }
163
+
164
+ class Order extends Entity {
165
+ // other properties...
166
+ metadata: UserMetadata
167
+ // other properties + constructor...
168
+ }
169
+
170
+ const completedOrders = await Order.find({
171
+ where:
172
+ { overview: sql`json_extract(#, '$.status') = 'completed'` }
173
+ })
174
+ ```
175
+
176
+ - **Note:** for SQL-client specific guide for writing `WHERE` conditions involving JSON and array data, go to the bottom of this page or click **[here](https://github.com/MasqueradeORM/MasqueradeORM/blob/master/docs/find.md#array-and-json-where-conditions-guide)**.
177
+
178
+
179
+ ## The `relationalWhere` Field:
180
+
181
+ ```js
182
+ import { sql } from "masquerade"
183
+
184
+ // Finds users that have at least one chat that contains at least one message whose sender's username is 'Glory2Christ'.
185
+ await User.find({
186
+ relationalWhere: (user) => sql`${user.chats.messages.sender.username} = 'Glory2Christ'`
187
+ })
188
+ ```
189
+
190
+ ```js
191
+ import { sql } from "masquerade"
192
+
193
+ // Identical to the previous example, but here the relational where is called from a different scope.
194
+ // note: the field has an underscore, to prevent any (rather impossible) name collisions.
195
+
196
+ await User.find({
197
+ where: {
198
+ chats: {
199
+ relationalWhere_: (chat) => sql`${chat.messages.sender.username} = 'Glory2Christ'`,
200
+ // can be combined with regular 'where' conditions - below is valid code
201
+ // chatName: 'The History of Orthodoxy'
202
+ }
203
+ }
204
+ })
205
+ ```
206
+
207
+ ### Array and JSON `WHERE` Conditions Guide
208
+
209
+ The model we will use for the examples:
210
+
211
+ ```ts
212
+ import { Entity } from "masquerade"
213
+
214
+ type UserMetadata = {
215
+ roles: string[] // e.g., ["admin", "moderator"]
216
+ lastLogin?: string // optional, ISO date string
217
+ preferences?: {
218
+ theme?: "light" | "dark"
219
+ notifications?: boolean
220
+ }
221
+ }
222
+
223
+ class User extends Entity {
224
+ // other properties...
225
+ metadata: UserMetadata
226
+ sessions: string[]
227
+ // other properties + constructor...
228
+ }
229
+ ```
230
+
231
+ Assuming we are writing the condition for the property `metadata` or `sessions` like so:
232
+ ```ts
233
+ import { sql } from "masquerade"
234
+ // 'metadata' find
235
+ const users = await User.find({where: {metadata: sql`_OPERATION_STRING_`}})
236
+
237
+ // 'sessions' find
238
+ const users2 = await User.find({where: {sessions: sql`_OPERATION_STRING_`}})
239
+
240
+ // **if not specified, the default is the 'metadata' find
241
+
242
+ // replace _OPERATION_STRING_ with the appropriate
243
+ // operation string from the table below
244
+ ```
245
+
246
+ <strong>**Operation String Table**
247
+
248
+ | Operation | SQLite | PostgreSQL |
249
+ |-------------|---------------|------------|
250
+ Array length <br>(example uses len = 2) | **'metadata' find** <br> `json_array_length(json_extract(#, '$.roles')) > 2` <br> **'sessions' find** <br> `json_array_length(json_extract(#)) > 2` | **'metadata' find** <br> `jsonb_array_length(#->'roles') > 2` <br> **'sessions' find** <br> `jsonb_array_length(#) > 2`|
251
+ | Access index `i` of array | **'metadata' find** <br>`json_extract(#, '$.roles[i]') = 'admin'`<br> **'sessions' find** <br>`json_extract(#, '$[i]') = 'SOME_SESSION_ID'` | **'metadata' find** <br>`#->'roles'->>i = 'admin''`<br> **'sessions' find** <br>`#->>i = 'admin'` |
252
+ | Check if array contains a value | `json_extract(#, '$.roles') LIKE '%"admin"%'` | `#->'roles' @> '["admin"]'::jsonb` |
253
+ Check nested field | `json_extract(#, '$.preferences.theme') = 'dark'` | `#->'preferences'->>'theme' = 'dark'` |
254
+ </strong>
255
+
256
+
257
+ <br>
258
+ <div align="center">
259
+ <strong>
260
+ © 2026
261
+ <a href="https://github.com/MasqueradeORM">MasqueradeORM </a>
262
+ -
263
+ Released under the MIT License
264
+ </strong>
265
+ </div>