Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
e32607b
Move from tinytest to mocha
harryadel Jul 11, 2025
2d09e8b
Re-add old insert_both.test.js
harryadel Jul 11, 2025
0fa6578
Get insert local collections tests to pass
harryadel Jul 13, 2025
ee08a51
Fix insert_both tests
harryadel Jul 13, 2025
aef0e4f
fix remove_both
harryadel Jul 13, 2025
947001a
Split find_findone_userid.test.js into two files
harryadel Jul 17, 2025
991a9fc
Remove packages directory from .gitignore
harryadel Jul 17, 2025
242823b
Track package code
harryadel Jul 17, 2025
ebed9df
track .meteor/packages
harryadel Jul 17, 2025
182e9be
Revert collecion-hooks changes
harryadel Jul 17, 2025
9f61cfb
Fix find and findOne tests
harryadel Jul 17, 2025
b6e1436
Fix insert_local.test.js
harryadel Jul 17, 2025
fa7f58d
Remove console.log from getUserId
harryadel Jul 17, 2025
83ed694
Add publish.test.js
harryadel Jul 17, 2025
9cbafa2
Fix CI file
harryadel Jul 17, 2025
8c2299f
Update linting scripts in package.json to target tests-app directory
harryadel Jul 17, 2025
1b034c7
Remove unnecessary user count assertion from find_users test
harryadel Jul 17, 2025
bfd607f
Update GitHub Actions workflow to use Meteor version 3.3 only
harryadel Jul 17, 2025
f27a3bb
Add package types entry and update version to 2.1.0-beta.1 in meteor-…
harryadel Jul 18, 2025
32279e3
Remove tinytest
harryadel Jul 18, 2025
4dde3b5
Remove dburles:mongo-collection-instances
harryadel Jul 18, 2025
b61aab0
Update version to 2.1.0-beta.2 and adjust supported Meteor versions i…
harryadel Jul 18, 2025
fcca86b
Revert .find hook changes
harryadel Jul 18, 2025
25177ff
Update version to 2.1.0-beta.3 in meteor-collection-hooks and adjust …
harryadel Jul 18, 2025
e48b062
Comment out find hooks tests in find_users.test.js and remove find co…
harryadel Jul 18, 2025
bb85f4d
Integrate lai:collection-extensions
harryadel Jul 18, 2025
82677cd
Refactor method references in collection hooks to use `originalMethod…
harryadel Jul 24, 2025
0f3d85d
Add a helper function to determine when to bypass hooks in collection…
harryadel Jul 24, 2025
ba49bc4
Refactor collection hooks setup by modularizing functionality into de…
harryadel Jul 24, 2025
0e105f2
Add createHookController function to modularize hook management, enab…
harryadel Jul 24, 2025
6745a1f
Refactor collection hooks to use constants for property names
harryadel Jul 24, 2025
c1614fa
Update History.md
harryadel Jul 24, 2025
21b7aed
Update README.md to reflect Meteor 3 compatibility, async hook suppor…
harryadel Jul 24, 2025
a441257
Enhance TypeScript definitions for collection hooks
harryadel Jul 24, 2025
62d3c10
Remove outdated note on find/findOne hooks support from History.md
harryadel Jul 24, 2025
8d1fad9
Update find_userid.test.js to use async methods for find hooks in Met…
harryadel Jul 24, 2025
1b03c75
Update linting scripts in package.json to target 'packages' directory…
harryadel Jul 24, 2025
1405e2c
Run tools:lintfix
harryadel Jul 24, 2025
500f1e3
Fix lint problems
harryadel Jul 24, 2025
7259441
Fix lint errors in find_users.test.js
harryadel Jul 25, 2025
35b3a65
Add /* eslint-disable no-unused-vars / to the top of publish.test.js
harryadel Jul 25, 2025
4c95daf
Publish a new beta
harryadel Jul 25, 2025
92149ba
Set a version for lai:collection-extensions
harryadel Jul 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 21 additions & 22 deletions .github/workflows/testsuite.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
# the test suite runs the tests (headless, server+client) for multiple Meteor releases
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Test suite
on:
push:
branches:
- master
pull_request:

on: [push, pull_request]

jobs:
test:
tests:
name: tests
runs-on: ubuntu-latest
strategy:
matrix:
meteorRelease:
- '--release 3.0.4'
- '--release 3.1'
meteor: [ '3.3' ]
# needs: [lintcode,lintstyle,lintdocs] # we could add prior jobs for linting, if desired
steps:
- name: Checkout code
- name: checkout
uses: actions/checkout@v4

- name: Install Node.js
uses: actions/setup-node@v4
- name: Setup meteor
uses: meteorengineer/setup-meteor@v2
with:
node-version: '20.x'
meteor-release: ${{ matrix.meteor }}

- name: Install Dependencies
run: |
curl https://install.meteor.com | /bin/sh
npm i -g @zodern/mtest
- name: Run Tests
run: |
mtest --package ./ --once ${{ matrix.meteorRelease }}
- name: cache dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: cd tests-app && meteor npm install && meteor npm run test
Comment on lines +10 to +32

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium test

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 4 months ago

To fix the issue, we will add a permissions block at the root of the workflow. This block will apply to all jobs in the workflow unless overridden by a job-specific permissions block. Since the workflow only needs to read repository contents (e.g., for checking out code), we will set contents: read as the minimal required permission.


Suggested changeset 1
.github/workflows/testsuite.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml
--- a/.github/workflows/testsuite.yml
+++ b/.github/workflows/testsuite.yml
@@ -4,2 +4,4 @@
 name: Test suite
+permissions:
+  contents: read
 
EOF
@@ -4,2 +4,4 @@
name: Test suite
permissions:
contents: read

Copilot is powered by AI and may make mistakes. Always verify output.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
.DS_Store
Thumbs.db
packages
*~
versions.json
.versions
Expand Down
24 changes: 18 additions & 6 deletions History.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
## v2.1.0-beta.3

* Replace tinytest with Mocha
* Code architecture improvements and refactoring for better maintainability
* Extracted magic strings to constants for improved code safety
* Improved options merging logic with clear precedence documentation
* Enhanced hook controller implementation using factory functions
* Maintained backward compatibility with Meteor 2.16+

## v2.0.0

* BREAKING: find hooks have been removed (due to Meteor 3 compatibility)
* Async hooks are now supported
* Meteor 3.0 is now the minimum required Meteor version
* BREAKING: find hooks behavior changed for Meteor 3 compatibility:
- `before.find` hooks can no longer be async (throws error if async function used)
- `find` hooks only trigger on cursor async methods (fetchAsync, countAsync, etc.)
- `findOne` hooks only trigger on `findOneAsync()`, not sync `findOne()`
- Sync collection methods (`find().fetch()`, `findOne()`) no longer trigger hooks
* Async hooks are now supported for insert/update/remove/upsert operations
* Added support for Meteor 2.16+ through 3.1+ (backward compatible, not "3.0 minimum")
* Enhanced async method handling with proper hook integration

## v1.4.0
* Test suite minimum Meteor version is 2.12 to support new counts and to be fully compatible with Meteor 3
Expand Down Expand Up @@ -172,6 +186,4 @@

## v0.6.6

* Add automated testing and additional tests for `userId` in publish functions. (#21)
* Add functions for direct operations on underlying collection, ignoring hooks. (#3)
* Update argument/input logic of hooks for better compatibility with other packages. (#24)
* Add automated testing and additional tests for `
143 changes: 123 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
![CodeQL Analysis](https://github.com/Meteor-Community-Packages/meteor-collection-hooks/workflows/CodeQL/badge.svg)


Extends Mongo.Collection with `before`/`after` hooks for `insert`, `update`, `remove`, `find`, and `findOne`.
Extends Mongo.Collection with `before`/`after` hooks for `insert`, `update`, `remove`, `upsert`, `find`, and `findOne`. **Meteor 2.16+ & 3.x compatible with async hook support.**

Works across client, server or a mix. Also works when a client initiates a collection method and the server runs the hook, all while respecting the collection validators (allow/deny).

Expand All @@ -19,6 +19,67 @@ Installation:
meteor add matb33:collection-hooks
```

## Meteor 3 Compatibility & Async Hooks

**Meteor Version Support:** 2.16+ through 3.1+

**Important Behavioral Changes in Meteor 3:**

### Async Hooks Support
As of v2.0.0, most hooks support async functions:

```javascript
// ✅ Async hooks work for these operations
collection.before.insert(async function(userId, doc) {
await validateDoc(doc);
});

collection.after.update(async function(userId, doc, fieldNames, modifier, options) {
await notifyExternalService(doc);
});
```

### Find Hooks Limitations
Due to Meteor 3's synchronous `find()` method, find hooks have specific limitations:

```javascript
// ✅ WORKS: Sync before.find hooks
collection.before.find(function(userId, selector, options) {
selector.deletedAt = { $exists: false }; // Modify selector
});

// ❌ THROWS ERROR: Async before.find hooks
collection.before.find(async function(userId, selector, options) {
// This will throw: "Cannot use async function as before.find hook"
});

// ✅ WORKS: after.find hooks (sync and async)
collection.after.find(async function(userId, selector, options, cursor) {
await logFindOperation(selector);
});
```

### Hook Trigger Conditions

**findOne Hooks:**
```javascript
await collection.findOneAsync({}) // ✅ Triggers hooks
collection.findOne({}) // ❌ No hooks triggered
```

**find Hooks:**
```javascript
// ✅ These trigger find hooks:
const cursor = collection.find({});
await cursor.fetchAsync(); // ✅ Hooks fire
await cursor.countAsync(); // ✅ Hooks fire
await cursor.forEachAsync(); // ✅ Hooks fire

// ❌ These DON'T trigger find hooks:
collection.find({}).fetch(); // ❌ No hooks
collection.find({}).count(); // ❌ No hooks
```

--------------------------------------------------------------------------------

### .before.insert(userId, doc)
Expand Down Expand Up @@ -185,64 +246,100 @@ test.after.remove(function (userId, doc) {

### .before.find(userId, selector, options)

Fired before a find query.
Fired before a find query. **Meteor 3 Limitation: Cannot be async.**

Allows you to adjust selector/options on-the-fly.

```javascript
test.before.find(function (userId, selector, options) {
// ...
// ✅ Sync operations only
selector.deletedAt = { $exists: false };
});

// ❌ This will throw an error:
test.before.find(async function (userId, selector, options) {
// Error: "Cannot use async function as before.find hook"
});
```

__Important:__
- The function used as `before.find` hook cannot be async
- This hook does not get called for `after.update` hooks (see https://github.com/Meteor-Community-Packages/meteor-collection-hooks/pull/297).
- The function used as `before.find` hook **cannot be async** (throws error)
- This hook does not get called for `after.update` hooks (see https://github.com/Meteor-Community-Packages/meteor-collection-hooks/pull/297)
- Only triggers when using cursor async methods (`fetchAsync()`, `countAsync()`, etc.)

--------------------------------------------------------------------------------

### .after.find(userId, selector, options, cursor)

Fired after a find query.
Fired after a find query when using cursor async methods.

Allows you to act on a given find query. The cursor resulting from
the query is provided as the last argument for convenience.
Allows you to act on a given find query. Both sync and async functions are supported.

```javascript
// ✅ Sync after.find
test.after.find(function (userId, selector, options, cursor) {
// ...
logOperation(selector);
});

// ✅ Async after.find
test.after.find(async function (userId, selector, options, cursor) {
await logToExternalService(selector);
});
```

**Triggers only on cursor async methods:**
```javascript
const cursor = collection.find({});
await cursor.fetchAsync(); // ✅ Triggers after.find
await cursor.countAsync(); // ✅ Triggers after.find
cursor.fetch(); // ❌ No hooks triggered
```

--------------------------------------------------------------------------------

### .before.findOne(userId, selector, options)

Fired before a findOne query.

Allows you to adjust selector/options on-the-fly.
Fired before a findOne query. **Supports async functions.**

```javascript
// ✅ Sync before.findOne
test.before.findOne(function (userId, selector, options) {
// ...
selector.status = 'active';
});

// ✅ Async before.findOne
test.before.findOne(async function (userId, selector, options) {
await enrichSelector(selector);
// Return false to abort the operation
return false;
});
```

**Only triggers on async methods:**
```javascript
await collection.findOneAsync({}) // ✅ Triggers hooks
collection.findOne({}) // ❌ No hooks triggered
```

--------------------------------------------------------------------------------

### .after.findOne(userId, selector, options, doc)

Fired after a findOne query.

Allows you to act on a given findOne query. The document resulting
from the query is provided as the last argument for convenience.
Fired after a findOne query. **Supports async functions.**

```javascript
test.after.findOne(function (userId, selector, options, doc) {
// ...
// ✅ Async after.findOne
test.after.findOne(async function (userId, selector, options, doc) {
await processDocument(doc);
});
```

**Only triggers on async methods:**
```javascript
await collection.findOneAsync({}) // ✅ Triggers hooks
collection.findOne({}) // ❌ No hooks triggered
```

--------------------------------------------------------------------------------

## Direct access (circumventing hooks)
Expand Down Expand Up @@ -321,7 +418,7 @@ still continue to run even if the first hook returns `false`.
- ~~If you wish to make `userId` available to a `find` query in a `publish`
function, try the technique detailed in this [comment](https://github.com/matb33/meteor-collection-hooks/issues/7#issuecomment-24021616)~~ `userId` is available to `find` and `findOne` queries that were invoked within a `publish` function.

- All hook callbacks have `this._super` available to them (the underlying
- All hook callbacks have `this.originalMethod` available to them (the underlying
method) as well as `this.context`, the equivalent of `this` to the underlying
method. Additionally, `this.args` contain the original arguments passed to the
method and can be modified by reference (for example, modifying a selector in a
Expand Down Expand Up @@ -353,6 +450,12 @@ server.*

- `find` hooks are also fired when fetching documents for `update`, `upsert` and `remove` hooks.

- **Meteor 3 Behavior:** Find hooks only trigger on async cursor methods (`fetchAsync()`, `countAsync()`, etc.). Sync methods (`fetch()`, `count()`) do not trigger hooks.

- **findOne Behavior:** findOne hooks only trigger on `findOneAsync()`. The sync `findOne()` method does not trigger hooks in Meteor 3.

- `before.find` hooks cannot be async and will throw an error if an async function is provided.

- If using the `direct` version to bypass a hook, any mongo operations done within nested
callbacks of the `direct` operation will also by default run as `direct`. You can use the following
line in a nested callback before the operation to unset the `direct` setting:
Expand Down
Loading