{"id":22324641,"url":"https://github.com/entity-access/entity-access","last_synced_at":"2025-07-29T16:32:36.504Z","repository":{"id":176935737,"uuid":"659700702","full_name":"Entity-Access/entity-access","owner":"Entity-Access","description":"Inspired from Entity Framework Core, Entity Access is ORM for JavaScript runtime such as Node, YantraJS.","archived":false,"fork":false,"pushed_at":"2024-12-03T08:06:07.000Z","size":1343,"stargazers_count":7,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2024-12-03T08:55:22.012Z","etag":null,"topics":["arrow-functions","javascript","lambda-expressions","nodejs","orm","orm-framework","postgresql","sql","sqlserver","typescript","unit-of-work"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Entity-Access.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-28T11:27:21.000Z","updated_at":"2024-12-03T08:06:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"8c25b85c-bc7c-4ef1-aa5e-ae9f81d14940","html_url":"https://github.com/Entity-Access/entity-access","commit_stats":{"total_commits":722,"total_committers":2,"mean_commits":361.0,"dds":0.0166204986149584,"last_synced_commit":"bf5fae0edceecd028c2799c5f22909372faebb50"},"previous_names":["web-atoms/entity-access"],"tags_count":374,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Entity-Access%2Fentity-access","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Entity-Access%2Fentity-access/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Entity-Access%2Fentity-access/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Entity-Access%2Fentity-access/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Entity-Access","download_url":"https://codeload.github.com/Entity-Access/entity-access/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228030086,"owners_count":17858432,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arrow-functions","javascript","lambda-expressions","nodejs","orm","orm-framework","postgresql","sql","sqlserver","typescript","unit-of-work"],"created_at":"2024-12-04T02:08:07.293Z","updated_at":"2025-07-29T16:32:36.491Z","avatar_url":"https://github.com/Entity-Access.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Action Status](https://github.com/Entity-Access/entity-access/workflows/Build/badge.svg)](https://github.com/Entity-Access/entity-access/actions) [![npm version](https://badge.fury.io/js/%40entity-access%2Fentity-access.svg)](https://badge.fury.io/js/%40entity-access%2Fentity-access) \r\n# Entity Access\r\n\r\nInspired from Entity Framework Core, Entity Access is ORM for JavaScript runtime such as Node, YantraJS.\r\n\r\n\r\n# Project Status\r\n1. Released - Postgres Driver\r\n2. Released - Sql Server Driver\r\n3. Released - Include Feature\r\n4. Planned - MySql Driver\r\n5. Planned - Oracle Driver (Need help, we do not have Oracle Expertise)\r\n\r\n## Features\r\n1. Arrow function based query features with automatic joins.\r\n2. Unit of Work and Repository Pattern\r\n3. Automatic Migrations for missing schema - this is done for fast development and deployment.\r\n4. Sql functions such as LIKE, You can add your own custom functions easily.\r\n5. Postgres Driver\r\n6. Sql Server Driver\r\n7. Automatic parameterization to safeguard sql injection attacks.\r\n8. Context Filters - This is a new concept where you can setup filters that will be used against saving/retrieving data.\r\n9. Sum and Count query methods.\r\n10. Composite Primary Key Support.\r\n\r\n## Upcoming Features\r\n1. Projection - Split query mode only, single level only.\r\n2. Update column before save\r\n3. GroupBy\r\n4. Custom Migration Steps\r\n5. MySql support\r\n\r\n### Unit of Work\r\n\r\n```typescript\r\nconst db = new ShoppingContext();\r\ndb.orders.add({\r\n    orderDate: new Date(),\r\n    userID,\r\n    orderItems: [\r\n        db.orderItems.add({\r\n            productID,\r\n            amount\r\n        })\r\n    ]\r\n});\r\n\r\n// save all in single transaction\r\nawait db.saveChanges();\r\n\r\nconst existingOrderItem1: OrderItem;\r\nconst existingOrderItem2: OrderItem;\r\n\r\nexistingOrderItem2.status = \"cancelled\";\r\nexistingOrderItem1.status = \"cancelled\";\r\n// executes update statements in single transaction\r\nawait db.saveChanges();\r\n\r\n\r\ndb.orderItems.delete(existingOrderItem1);\r\ndb.orderItems.delete(existingOrderItem2);\r\n// executes delete statements in single transaction\r\nawait db.saveChanges();\r\n\r\n```\r\n\r\n### Arrow function based query features\r\n\r\nArrow function based query provides many benefits over tagged template literals or other fluent methods to build queries.\r\n1. Arrow functions are easy to visualize.\r\n2. You will get intellisense help to complete the query.\r\n3. You will get errors if the wrong data types are compared or used in computations.\r\n4. Change of property name will automatically refactor as typescript will keep references of where the property is used.\r\n\r\nSimple Query\r\n```typescript\r\nconst db = new ShoppingContext();\r\n\r\n// find customer from orderID\r\nconst q = db.customers\r\n    // set parameters\r\n    .where({ orderID },\r\n        // access parameters\r\n        (p) =\u003e\r\n            // following expression\r\n            // be converted to SQL\r\n            (customer) =\u003e customer.orders.some(\r\n                // joins/exists will be set\r\n                // based on relations declared\r\n                (order) =\u003e order.orderID === p.orderID );\r\nconst customer = await q.first();\r\n```\r\nAbove expression will result in following filter expression\r\n```sql\r\n    SELECT c.firstName, c.lastName, c.customerID\r\n    FROM customers as c\r\n    EXISTS (\r\n        SELECT 1\r\n        FROM Orders as o1\r\n        WHERE c.customerID = o1.orderID\r\n            AND o1.orderID = $1\r\n    )\r\n    LIMIT 1;\r\n```\r\n\r\nQuery with Like operator\r\n```typescript\r\n/// Find all orders for specified customer\r\n/// Sql functions\r\nconst userName = `akash%`;\r\nconst q = db.orders.where({ userName },\r\n    (params) =\u003e\r\n        (order) =\u003e\r\n            Sql.text.like(\r\n                order.customer.userName,\r\n                params.userName\r\n            )\r\n);\r\n\r\n// note that the join will be performed automatically\r\n```\r\n\r\nFollowing query will be generated for the query.\r\n```sql\r\n    SELECT o.orderID, o.orderDate, o.customerID, ...\r\n    FROM orders as o\r\n        INNER JOIN customers c\r\n            ON c.customerID = o.customerID\r\n    WHERE\r\n        c.userName like $1\r\n```\r\n\r\n### Typed Configurations\r\n```typescript\r\nclass ShoppingContext {\r\n    products = this.model.register(Product);\r\n    orders = this.model.register(Order);\r\n    orderItems = this.model.register(OrderItem);\r\n    users = this.model.register(User);\r\n}\r\n\r\n@Table(\"Users\")\r\n@Index({\r\n    name: \"IX_Unique_Usernames\",\r\n    unique: true,\r\n    columns: [{ name: (x) =\u003e x.lowerCaseUserName, descending: false }]\r\n})\r\nclass User {\r\n    @Column({ key: true, generated: \"identity\"})\r\n    userID: number;\r\n\r\n    @Column({ length: 200 })\r\n    userName: string;\r\n\r\n    /**\r\n     * Create computed column\r\n    */\r\n    @Column({ length: 200, computed: (x) =\u003e Sql.text.lower(x.userName) })\r\n    readonly lowerCaseUserName: string;\r\n}\r\n\r\n@Table(\"Products\")\r\n@CheckConstraint({\r\n    name: \"CX_Products_PositivePrice\",\r\n    filter: (x) =\u003e x.price \u003e 0\r\n})\r\nclass Product {\r\n\r\n    @Column({ key: true, generated: \"identity\"})\r\n    productID: number;\r\n\r\n    // Create a column with default expression\r\n    // the expression will be converted to equivalent SQL\r\n    // for the target provider `NOW()` for postgresql and\r\n    // `GETUTCDATE()` for sql server.\r\n    @Column({ default: () =\u003e Sql.date.now()})\r\n    dateUpdated: DateTime;\r\n\r\n    // create a column with empty string as default\r\n    @Column({ default: () =\u003e \"\"})\r\n    productCode: string;\r\n\r\n    // You can specifiy computed expression\r\n    // that will be converted to equivalent SQL\r\n    // for target provider.\r\n    @Column({\r\n        /* Certain providers might need length such as postgresql*/\r\n        length: 200,\r\n        computed: (p) =\u003e Sql.text.concatImmutable(Sql.cast.asText(p.productID), p.productCode)\r\n    })\r\n    readonly slug: string;\r\n\r\n    @Column({ dataType: \"Decimal\" })\r\n    price: number;\r\n\r\n    orderItems: OrderItem[];\r\n}\r\n\r\n@Table(\"OrderItems\")\r\nclass OrderItem {\r\n\r\n    @Column({ key: true, generated: \"identity\"})\r\n    orderItemID: number;\r\n\r\n    @Column({})\r\n    /**\r\n     * Following configuration declares Foreign Key Relation.\r\n     * That will give compilation error if configured incorrectly.\r\n     * \r\n     * RelateTo is for one to many mapping. Making column nullable will\r\n     * inverse relation optional.\r\n    */\r\n    @RelateTo(Product, {\r\n        property: (orderItem) =\u003e orderItem.product,\r\n        inverseProperty: (product) =\u003e product.orderItems,\r\n        foreignKeyContraint: {\r\n                name: \"FK_OrderItems_ProductID\",\r\n                onDelete: \"restrict\"\r\n        }\r\n    })\r\n    productID: number;\r\n\r\n    @Column({})\r\n    @RelateTo(Order, {\r\n        property: (orderItem) =\u003e orderItem.order,\r\n        inverseProperty: (order) =\u003e order.orderItems,\r\n        foreignKeyContraint: {\r\n                name: \"FK_OrderItems_OrderID\",\r\n                onDelete: \"delete\"\r\n        }\r\n    })\r\n    orderID: number;\r\n\r\n    product: Product;\r\n\r\n    order: Order;\r\n\r\n}\r\n\r\n// You can use `RelateToOne` for one to one mapping.\r\n\r\n// To prevent circular dependency issues, you can also use different\r\n// arguments as shown below...\r\n\r\n    @RelateTo({\r\n        type: () =\u003e Product\r\n        property: (orderItem) =\u003e orderItem.product,\r\n        inverseProperty: (product) =\u003e product.orderItems\r\n    })\r\n    productID: number;\r\n```\r\n\r\n## Query Examples\r\n\r\n### Compare operators\r\n\r\nOnly handful of operators are supported as of now.\r\n1. Equality Both `==`, `===`, will result in simple `=` operator in SQL. There is no type check performed and no conversion is performed to speed up\r\nexecution. However, typescript will give you warning and compilation for mismatch of operands and you can convert them as needed. But for conversion\r\nuse only `Sql.*` functions.\r\n2. Above also applies for operators `!=` and `!==`, they will result in `\u003c\u003e` in SQL.\r\n3. `+`, `-`, `*`, `/` operators will be sent to SQL as it is.\r\n4. For precedence, we recommend using brackets in the arrow functions as there might be differences in underlying database engine's preferences and you may not get correct results.\r\n5. Template Literals, will be sent to SQL as concat, however, please use\r\nconversion of non string to string type if underlying provider does not support direct conversion.\r\n6. Conversion methods, `Sql.cast.as*` methods will provide conversion from any type to desired type. `Sql.cast.asText` will convert to number to text.\r\n\r\n#### Equality\r\nBoth strict and non strict equality will result in\r\nsimple equality comparison in SQL. Database provider\r\nmay or may not convert them correctly, so we recommend\r\nusing helper functions to convert before comparison.\r\n```typescript\r\n    const q = db.customers\r\n        .where({ orderID },\r\n            (p) =\u003e\r\n                (x) =\u003e x.orders.some(\r\n                    (order) =\u003e order.orderID === p.orderID )\r\n        )\r\n\r\n```\r\n\u003e SQL Server does not recognize boolean field as a true/false, so to make your query compatible, you must use `(x) =\u003e x.isActive === true` to make it work correctly in sql server.\r\n\r\n#### IN\r\n\r\nTo use `IN` operator, you can simply use javascript's `in` keyword.\r\n```typescript\r\nlet all = await db.products.where(void 0, (p) =\u003e (x) =\u003e\r\n        x.productType in [\"mobile\", \"laptop\"]\r\n    ).toArray();\r\n```\r\n\r\nAbove query will result in following expression.\r\n\r\n```sql\r\nSELECT ... FROM products as p1 WHERE p1.productType in ('mobile', 'laptop')\r\n```\r\n\r\nHowever, you can also send `in` parameters as parameters as shown below.\r\n\r\n```typescript\r\nconst productTypes = [\"mobile\", \"laptop\"];\r\nall = await db.products.where({ productTypes }, (p) =\u003e (x) =\u003e\r\n        x.productType in p.productTypes\r\n    ).toArray();\r\n```\r\n\r\nAbove query will result in following expression.\r\n\r\n```sql\r\nSELECT ... FROM products as p1 WHERE p1.productType in ($1, $2)\r\n```\r\n\r\n\r\n#### Like\r\n\r\nTo use `LIKE` operator, `Sql.text.like` method must be used\r\nas it is. Query compiler will only match everything starting\r\nwith `Sql.` and it will inject available operator conversion.\r\n\r\nYou don't have to worry about sql injection as each parameter\r\npassed will be sent as a sql parameter and not as a literal.\r\n\r\n```typescript\r\n    const prefix = `${name}%`;\r\n    db.customers.where({ prefix },\r\n        (p) =\u003e\r\n            (customer) =\u003e Sql.text.like(customer.firstName, p.prefix)\r\n                || Sql.text.like(customer.lastName p.prefix)\r\n    )\r\n```\r\n\r\n#### Sql Text Functions\r\nFor other sql text functions you can use `Sql.text.startsWith`, `Sql.text.endsWith`, `Sql.text.left`... etc as shown below.\r\n```typescript\r\n    db.customers.where({ prefix },\r\n        (p) =\u003e\r\n            (customer) =\u003e Sql.text.startsWith(customer.firstName, p.prefix)\r\n                || Sql.text.startsWith(customer.lastName p.prefix)\r\n    )\r\n```\r\n\r\n#### Sql date functions\r\nJust as text functions you can also use date functions as shown below.\r\n```typescript\r\n    const year = (new Date()).getFullYear();\r\n    // get count of all orders of this year...\r\n    db.orders.where({ year },\r\n        (p) =\u003e\r\n            (order) =\u003e Sql.date.yearOf(order.orderDate) === p.year\r\n    )\r\n\r\n    // above example is only for illustrations only, it will not use index.\r\n    // for index usage, please consider window function shown below.\r\n    const start:Date = /* start date */;\r\n    const end:Date = /* start date */;\r\n    // get count of all orders of this year...\r\n    db.orders.where({ start, end },\r\n        (p) =\u003e\r\n            (order) =\u003e p.start \u003c= order.orderDate \u0026\u0026 order.orderDate \u003e= p.end\r\n    )\r\n\r\n```\r\n\r\n### OrderBy\r\n```typescript\r\n    q.orderBy({}, (p) =\u003e (x) =\u003e x.orderDate)\r\n    .thenBy({}, (p) =\u003e (x) =\u003e x.customer.firstName)\r\n\r\n    // custom...\r\n    q.orderBy({}, (p) =\u003e (x) =\u003e x.orderDate)\r\n    .thenBy({}, (p) =\u003e (x) =\u003e Sql.text.collate(x.customer.firstName, \"case_insensitive\"))\r\n\r\n```\r\n\r\n### Limit/Offset\r\n```typescript\r\n    q = q.orderByDescending({}, (p) =\u003e (x) =\u003e x.orderDate)\r\n    .thenBy({}, (p) =\u003e (x) =\u003e x.customer.firstName)\r\n    .limit(50)\r\n    .offset(50);\r\n```\r\n\r\n### Enumerate\r\n```typescript\r\n    for await(const product of q.enumerate()) {\r\n        //\r\n    }\r\n```\r\n\r\n### First / First or Fail\r\n```typescript\r\n    // it will return first product or null\r\n    const firstProduct = await q.first();\r\n\r\n    // it will throw and exception if product was not\r\n    // found\r\n    const firstProduct = await q.firstOrFail();\r\n```\r\n\r\n### Count\r\n```typescript\r\n    const total = await q.count();\r\n```\r\n\r\n## Bulk Updates\r\n### Update\r\nFollowing query will mark all users as active if they\r\nlogged in within 30 days.\r\n```typescript\r\n    const past30 = DateTime.now.addDays(-30);\r\n    db.users.asQuery()\r\n        .update({ past30 }, (p) =\u003e (x) =\u003e ({\r\n            active: Sql.cast.asBoolean(\r\n                x.lastLogin \u003e p.past30\r\n            )\r\n        }))\r\n```\r\n### Delete\r\nFollowing query will delete all users who did not login\r\nwithin one year.\r\n```typescript\r\n    const past365 = DateTime.now.addYears(-1);\r\n    db.users.asQuery()\r\n        .delete({ past365 }, (p) =\u003e\r\n            (x) =\u003e x.lastLogin \u003c p.past365)\r\n```\r\n### Insert\r\nFollowing query will insert all old messages to\r\narchivedMessages table and delete from messages\r\nin a single transaction.\r\n\r\nEverything happens on database server, no entity\r\nis loadded in the memory.\r\n\r\n```typescript\r\n    const past365 = DateTime.now.addYears(-1);\r\n\r\n    using tx = await db.connection.createTransaction();\r\n\r\n    const oldMessagesQuery = db.messages\r\n        .where({ past365 }, (p) =\u003e\r\n            (x) =\u003e x.dateCreated \u003c p.past365 );\r\n\r\n    oldMessagesQuery.insertInto(db.archivedMessages);\r\n\r\n    oldMessagesQuery.delete(void 0, (p) =\u003e (x) =\u003e x.messageID !== null);\r\n\r\n    await tx.commit();\r\n\r\n```\r\n\r\n## Direct Statements\r\nEntity Context provides direct statements to save/retrieve entities without loading them into change context.\r\n\r\n### Select\r\n```typescript\r\n   const msg = await context.messages.statements.select({\r\n       messageID\r\n   });\r\n```\r\n\r\n### Insert\r\n```typescript\r\n    const msg = await context.messages.statements.insert({\r\n       from,\r\n       to,\r\n       subject,\r\n       body\r\n    });\r\n```\r\n\r\n### Update\r\n```typescript\r\n   await context.messages.statements\r\n        .update(\r\n            /** Changes */\r\n            { archived: true },\r\n            /** key */\r\n            { messageID }\r\n        )\r\n```\r\n\r\n### Upsert\r\n```typescript\r\n   await context.messages.statements\r\n        .upsert(\r\n            /** Changes */\r\n            { read: 1 },\r\n            /** Apply update for an existing entity */\r\n            (existing) =\u003e ({\r\n                ... existing,\r\n                read: existing.read + 1\r\n            })\r\n            /** key */\r\n            { messageID }\r\n        )\r\n```\r\n\r\n## Provide Custom Sql Methods...\r\nWe have provided most used methods, however, to add inbuilt methods, we request you to submit feature request or pull request.\r\n\r\nLet's say you have custom function defined in your database and you want to invoke them.\r\n\r\nWe will extend ISql interface.\r\n\r\n```typescript\r\nimport Sql from \"@entity-access/entity-access/dist/sql/Sql.js\";\r\nimport ISql from \"@entity-access/entity-access/dist/sql/ISql.js\";\r\nimport { prepareAny } from \"@entity-access/entity-access/dist/query/ast/IStringTransformer.js\";\r\n\r\ndeclare module \"@entity-access/entity-access/dist/sql/ISql.js\" {\r\n    interface ISql {\r\n        myFunctions: {\r\n            calculateAmount(total: number, units: number, taxId: string): Date;\r\n        }\r\n    }\r\n}\r\n\r\nSql.myFunctions = {\r\n    calculateAmount(total: number, units: number, taxId: string): number {\r\n        // in reality this function will return number,\r\n        // but expression to sql compiler expects an array of\r\n        // strings and functions. Function represents parameters\r\n        // being sent to SQL. Parameters cannot be accessed here.\r\n        // So a placeholder function to parameter will be sent to\r\n        // this method and it should be passed as it is in array\r\n        // as shown below.\r\n\r\n        // note how comma is inserted as separate string literal.\r\n        return [\"mySchema.calculateAmount(\", total, \",\" , units , \",\" , taxId, \")\"] as any;\r\n\r\n        // DO NOT EVER USE THE FOLLOWING\r\n        return `mySchema.calculateAmount(${total}, ${units},${taxId})`;\r\n\r\n        // INSTEAD you can use prepareAny function \r\n        // In case if you need to use something else, you can return an array and send\r\n        // parameters as it is.\r\n\r\n        // Also you will not be able to convert the inputs to string because\r\n        // each input will only return the function declaration instead of the value as a text.\r\n        return prepareAny `mySchema.calculateAmount(${total}, ${units},${taxId})`;\r\n    }\r\n};\r\n\r\n// now you can use this as shown below...\r\n\r\ncontext.orders.all()\r\n    .where({amount}, (p) =\u003e (x) =\u003e\r\n        Sql.mySchema.calculateAmount(x.total, x.units, x.taxId) \u003c p.amount );\r\n\r\n```\r\n\r\n## Context Filters and Events\r\n\r\nLet's assume that you wan to setup filters in such a way that customer can only \r\naccess his own orders.\r\n\r\nIn order to setup context filters and events, we need to use inbuilt dependency injection, to provide, access to current user and events.\r\n\r\n```typescript\r\nexport class ProductEvents extends EntityEvents\u003cProduct\u003e {\r\n\r\n    @Inject\r\n    user: User;\r\n\r\n    @Inject\r\n    notificationService: NotificationService;\r\n\r\n    filter(query: IEntityQuery\u003cProduct\u003e) {\r\n        const { userID } = this.user ?? {};\r\n        if (userID) {\r\n\r\n            // user can only see products that\r\n            // user has purchased or products are\r\n            // active.\r\n\r\n            return query.where({ userID }, (p) =\u003e\r\n                p.isActive\r\n                || x.orderItems.some((oi) =\u003e\r\n                    oi.order.customerID === p.userID\r\n                )\r\n            );\r\n        }\r\n\r\n        // anonymous users can see only active products        \r\n        return query.where({ userID }, (p) =\u003e p.isActive === true);\r\n    }\r\n\r\n    /*\r\n    When you are using eager loading, you can avoid adding\r\n    extra filters for each relation if the parent is already\r\n    filtered. For example, if you are trying to list products\r\n    inside orders, since order is already filtered, you can \r\n    return query as it is.\r\n    */\r\n    includeFilter(query: IEntityQuery\u003cProduct\u003e, type, member) {\r\n        if(type === OrderItem) {\r\n            return query;\r\n        }\r\n        // for every other include\r\n        // use normal filter.\r\n        return this.filter(query);\r\n    }\r\n\r\n    /*\r\n    this will be called just before\r\n    save changes, before the actual editing occurs,\r\n    we will automatically determine if the product\r\n    can be edited or not by the current use.\r\n    */\r\n\r\n    /*\r\n    This will also work correctly when there are multiple\r\n    entities in the single transaction.\r\n    */\r\n    \r\n    modifyFilter(query: IEntityQuery\u003cProduct\u003e) {\r\n        const { userID } = this.user ?? {};\r\n        if (userID) {\r\n\r\n            // user can only see products that\r\n            // user has purchased or products are\r\n            // active.\r\n\r\n            return query.where({ userID }, (p) =\u003e\r\n                p.isActive === true\r\n                || x.orderItems.some((oi) =\u003e\r\n                    oi.order.customerID === p.userID\r\n                )\r\n            );\r\n        }\r\n        throw new EntityAccessError(`Cannot edit the product`);\r\n    }\r\n\r\n    // after above filter has passed the entity\r\n    // following methods will be raised for every entity\r\n    beforeInsert(entity: Product, entry: ChangeEntry\u003cProduct\u003e) {\r\n\r\n    }\r\n\r\n    // each of these methods, beforeInsert, afteInsert, beforeUpdate\r\n    // afterUpdate, beforeDelete and afterDelete are asynchronous and\r\n    // you can await on async methods.\r\n    async afterInsert(entity: Product, entry: ChangeEntry\u003cProduct\u003e) {\r\n        await this.notificationService.notify(entity);\r\n    }\r\n\r\n}\r\n\r\n\r\n// register all events in context events..\r\nexport class AppContextEvents extends ContextEvents{\r\n\r\n    constructor() {\r\n        this.register(Product, ProductEvents);\r\n    }\r\n}\r\nconst allEvents = new AppContextEvents();\r\n\r\n// create context with context events.\r\n\r\nconst db = new ShoppingContext(allEvents);\r\n\r\n\r\n// this will return the query with filter\r\nconst products = db.filteredQuery\u003cProduct\u003e(Product);\r\n```\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fentity-access%2Fentity-access","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fentity-access%2Fentity-access","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fentity-access%2Fentity-access/lists"}