@orgloop/transform-filter 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.
- package/LICENSE.md +21 -0
- package/README.md +140 -0
- package/dist/filter.d.ts +23 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.js +123 -0
- package/dist/filter.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/matcher.d.ts +31 -0
- package/dist/matcher.d.ts.map +1 -0
- package/dist/matcher.js +79 -0
- package/dist/matcher.js.map +1 -0
- package/package.json +28 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OrgLoop contributors
|
|
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,140 @@
|
|
|
1
|
+
# @orgloop/transform-filter
|
|
2
|
+
|
|
3
|
+
Filters events by field matching or jq expressions. Events that pass the filter continue through the pipeline; events that fail are dropped.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @orgloop/transform-filter
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
The filter supports two modes: **match/exclude** (built-in) and **jq** (subprocess).
|
|
14
|
+
|
|
15
|
+
### Mode 1: Match / Exclude
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
transforms:
|
|
19
|
+
- name: humans-only
|
|
20
|
+
type: package
|
|
21
|
+
package: "@orgloop/transform-filter"
|
|
22
|
+
config:
|
|
23
|
+
match: # all criteria must match to pass
|
|
24
|
+
type: "resource.changed"
|
|
25
|
+
"provenance.author_type": "team_member"
|
|
26
|
+
exclude: # any match here drops the event
|
|
27
|
+
"provenance.author":
|
|
28
|
+
- "dependabot[bot]"
|
|
29
|
+
- "renovate[bot]"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Mode 2: jq expression
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
transforms:
|
|
36
|
+
- name: high-priority
|
|
37
|
+
type: package
|
|
38
|
+
package: "@orgloop/transform-filter"
|
|
39
|
+
config:
|
|
40
|
+
jq: '.payload.priority == "high"' # truthy result = pass, falsy = drop
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Config options
|
|
44
|
+
|
|
45
|
+
| Field | Type | Description |
|
|
46
|
+
|-------|------|-------------|
|
|
47
|
+
| `match` | `object` | Dot-path field to value map. **All** criteria must match for the event to pass |
|
|
48
|
+
| `exclude` | `object` | Dot-path field to value map. **Any** match drops the event |
|
|
49
|
+
| `jq` | `string` | jq expression evaluated against the full event. Truthy = pass, falsy/error = drop |
|
|
50
|
+
|
|
51
|
+
If both `match` and `exclude` are set, `exclude` is checked first.
|
|
52
|
+
|
|
53
|
+
If `jq` is set, it takes precedence over `match`/`exclude`.
|
|
54
|
+
|
|
55
|
+
### Value matching
|
|
56
|
+
|
|
57
|
+
The `match` and `exclude` fields support several value types:
|
|
58
|
+
|
|
59
|
+
| Pattern type | Example | Behavior |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| String | `"team_member"` | Exact match |
|
|
62
|
+
| Number | `42` | Strict equality |
|
|
63
|
+
| Boolean | `true` | Strict equality |
|
|
64
|
+
| Array | `["bot", "system"]` | Matches if actual value equals any element |
|
|
65
|
+
| Regex string | `"/fix\\|bug/i"` | Regex test against stringified value |
|
|
66
|
+
| `null` | `null` | Matches `null` or `undefined` |
|
|
67
|
+
|
|
68
|
+
### Dot-path field access
|
|
69
|
+
|
|
70
|
+
Fields are accessed via dot-notation paths into the event object:
|
|
71
|
+
|
|
72
|
+
- `type` -- top-level event type
|
|
73
|
+
- `provenance.author` -- nested provenance field
|
|
74
|
+
- `payload.pr_number` -- payload field
|
|
75
|
+
|
|
76
|
+
## Examples
|
|
77
|
+
|
|
78
|
+
### Filter by event type and author
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
routes:
|
|
82
|
+
- name: human-pr-reviews
|
|
83
|
+
when:
|
|
84
|
+
source: github-eng
|
|
85
|
+
events:
|
|
86
|
+
- resource.changed
|
|
87
|
+
transforms:
|
|
88
|
+
- ref: humans-only
|
|
89
|
+
then:
|
|
90
|
+
actor: openclaw-agent
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### CWD-based routing with regex
|
|
94
|
+
|
|
95
|
+
Route Claude Code stop events to different agents based on the working directory:
|
|
96
|
+
|
|
97
|
+
```yaml
|
|
98
|
+
transforms:
|
|
99
|
+
- name: work-repos
|
|
100
|
+
type: package
|
|
101
|
+
package: "@orgloop/transform-filter"
|
|
102
|
+
config:
|
|
103
|
+
match:
|
|
104
|
+
"payload.cwd": "/\\/code\\/mono/" # regex: matches ~/code/mono*
|
|
105
|
+
- name: personal-repos
|
|
106
|
+
type: package
|
|
107
|
+
package: "@orgloop/transform-filter"
|
|
108
|
+
config:
|
|
109
|
+
match:
|
|
110
|
+
"payload.cwd": "/\\/personal\\//" # regex: matches ~/personal/*
|
|
111
|
+
|
|
112
|
+
routes:
|
|
113
|
+
- name: work-tasks
|
|
114
|
+
when:
|
|
115
|
+
source: claude-code
|
|
116
|
+
events: [actor.stopped]
|
|
117
|
+
transforms: [{ ref: work-repos }]
|
|
118
|
+
then:
|
|
119
|
+
actor: work-agent
|
|
120
|
+
- name: personal-tasks
|
|
121
|
+
when:
|
|
122
|
+
source: claude-code
|
|
123
|
+
events: [actor.stopped]
|
|
124
|
+
transforms: [{ ref: personal-repos }]
|
|
125
|
+
then:
|
|
126
|
+
actor: personal-agent
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Auth / prerequisites
|
|
130
|
+
|
|
131
|
+
- None for match/exclude mode.
|
|
132
|
+
- **jq mode** requires the `jq` binary to be installed and available on `PATH`. The subprocess runs with a 5-second timeout.
|
|
133
|
+
|
|
134
|
+
## Limitations / known issues
|
|
135
|
+
|
|
136
|
+
- **jq mode spawns a subprocess** per event. This is convenient but not suitable for high-throughput pipelines.
|
|
137
|
+
- **jq errors drop the event** -- If the jq expression fails to parse or returns an error exit code, the event is silently dropped (not passed through).
|
|
138
|
+
- **jq can modify events** -- If the jq expression returns a valid JSON object with an `id` field, that object replaces the original event. Otherwise, the original event passes through unchanged.
|
|
139
|
+
- **No OR logic for match** -- All `match` criteria use AND logic. For OR, use an array value within a single field or use jq mode.
|
|
140
|
+
- **Regex patterns** -- Must start and end with `/` (e.g., `"/pattern/flags"`). Invalid regex falls back to exact string comparison.
|
package/dist/filter.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter transform — the workhorse event filter for OrgLoop.
|
|
3
|
+
*
|
|
4
|
+
* Two modes:
|
|
5
|
+
* 1. Match/exclude mode: built-in dot-path field matching
|
|
6
|
+
* 2. jq mode: pipe event through jq subprocess
|
|
7
|
+
*
|
|
8
|
+
* Match modes:
|
|
9
|
+
* - match: AND — all criteria must match (keep if all match)
|
|
10
|
+
* - match_any: OR — any criterion can match (keep if any matches)
|
|
11
|
+
* - exclude: OR — any criterion drops the event
|
|
12
|
+
*/
|
|
13
|
+
import type { OrgLoopEvent, Transform, TransformContext } from '@orgloop/sdk';
|
|
14
|
+
export declare class FilterTransform implements Transform {
|
|
15
|
+
readonly id = "filter";
|
|
16
|
+
private config;
|
|
17
|
+
init(config: Record<string, unknown>): Promise<void>;
|
|
18
|
+
execute(event: OrgLoopEvent, _context: TransformContext): Promise<OrgLoopEvent | null>;
|
|
19
|
+
shutdown(): Promise<void>;
|
|
20
|
+
private executeMatchExclude;
|
|
21
|
+
private executeJq;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../src/filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AA2B9E,qBAAa,eAAgB,YAAW,SAAS;IAChD,QAAQ,CAAC,EAAE,YAAY;IACvB,OAAO,CAAC,MAAM,CAAoB;IAE5B,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAUpD,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAStF,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B,OAAO,CAAC,mBAAmB;IA2B3B,OAAO,CAAC,SAAS;CAmDjB"}
|
package/dist/filter.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter transform — the workhorse event filter for OrgLoop.
|
|
3
|
+
*
|
|
4
|
+
* Two modes:
|
|
5
|
+
* 1. Match/exclude mode: built-in dot-path field matching
|
|
6
|
+
* 2. jq mode: pipe event through jq subprocess
|
|
7
|
+
*
|
|
8
|
+
* Match modes:
|
|
9
|
+
* - match: AND — all criteria must match (keep if all match)
|
|
10
|
+
* - match_any: OR — any criterion can match (keep if any matches)
|
|
11
|
+
* - exclude: OR — any criterion drops the event
|
|
12
|
+
*/
|
|
13
|
+
import { spawn } from 'node:child_process';
|
|
14
|
+
import { matchesAll, matchesAny } from './matcher.js';
|
|
15
|
+
/**
|
|
16
|
+
* Expand comma-separated string values into arrays for matching.
|
|
17
|
+
* "alice,bob" → ["alice", "bob"]. Already-array values pass through.
|
|
18
|
+
* Only applies to criterion values, not field paths.
|
|
19
|
+
*/
|
|
20
|
+
function expandCsvValues(criteria) {
|
|
21
|
+
const result = {};
|
|
22
|
+
for (const [key, value] of Object.entries(criteria)) {
|
|
23
|
+
if (typeof value === 'string' && value.includes(',')) {
|
|
24
|
+
result[key] = value.split(',').map((s) => s.trim());
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
result[key] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
export class FilterTransform {
|
|
33
|
+
id = 'filter';
|
|
34
|
+
config = {};
|
|
35
|
+
async init(config) {
|
|
36
|
+
const raw = config;
|
|
37
|
+
this.config = {
|
|
38
|
+
match: raw.match ? expandCsvValues(raw.match) : undefined,
|
|
39
|
+
match_any: raw.match_any ? expandCsvValues(raw.match_any) : undefined,
|
|
40
|
+
exclude: raw.exclude ? expandCsvValues(raw.exclude) : undefined,
|
|
41
|
+
jq: raw.jq,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async execute(event, _context) {
|
|
45
|
+
// jq mode takes precedence if specified
|
|
46
|
+
if (this.config.jq) {
|
|
47
|
+
return this.executeJq(event);
|
|
48
|
+
}
|
|
49
|
+
return this.executeMatchExclude(event);
|
|
50
|
+
}
|
|
51
|
+
async shutdown() {
|
|
52
|
+
// No resources to clean up
|
|
53
|
+
}
|
|
54
|
+
executeMatchExclude(event) {
|
|
55
|
+
const eventObj = event;
|
|
56
|
+
// Check exclude first — if any exclude criterion matches, drop
|
|
57
|
+
if (this.config.exclude) {
|
|
58
|
+
if (matchesAny(eventObj, this.config.exclude)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Check match — all criteria must match (AND)
|
|
63
|
+
if (this.config.match) {
|
|
64
|
+
if (!matchesAll(eventObj, this.config.match)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Check match_any — at least one criterion must match (OR)
|
|
69
|
+
if (this.config.match_any) {
|
|
70
|
+
if (!matchesAny(eventObj, this.config.match_any)) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return event;
|
|
75
|
+
}
|
|
76
|
+
executeJq(event) {
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
try {
|
|
79
|
+
const input = JSON.stringify(event);
|
|
80
|
+
const proc = spawn('jq', ['-e', this.config.jq], {
|
|
81
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
82
|
+
timeout: 5000,
|
|
83
|
+
});
|
|
84
|
+
let stdout = '';
|
|
85
|
+
proc.stdout.on('data', (chunk) => {
|
|
86
|
+
stdout += chunk.toString();
|
|
87
|
+
});
|
|
88
|
+
proc.on('error', () => {
|
|
89
|
+
resolve(null);
|
|
90
|
+
});
|
|
91
|
+
proc.on('close', (code) => {
|
|
92
|
+
if (code !== 0) {
|
|
93
|
+
resolve(null);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const trimmed = stdout.trim();
|
|
97
|
+
if (!trimmed || trimmed === 'null' || trimmed === 'false') {
|
|
98
|
+
resolve(null);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// If jq returned a modified object, try to parse it
|
|
102
|
+
try {
|
|
103
|
+
const result = JSON.parse(trimmed);
|
|
104
|
+
if (typeof result === 'object' && result !== null && result.id) {
|
|
105
|
+
resolve(result);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Non-JSON truthy output means pass through
|
|
111
|
+
}
|
|
112
|
+
resolve(event);
|
|
113
|
+
});
|
|
114
|
+
proc.stdin.write(input);
|
|
115
|
+
proc.stdin.end();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
resolve(null);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.js","sourceRoot":"","sources":["../src/filter.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @orgloop/transform-filter — registration entry point.
|
|
3
|
+
*/
|
|
4
|
+
import type { TransformRegistration } from '@orgloop/sdk';
|
|
5
|
+
export declare function register(): TransformRegistration;
|
|
6
|
+
export { FilterTransform } from './filter.js';
|
|
7
|
+
export { getByPath, matchesAll, matchesAny, matchesValue } from './matcher.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAG1D,wBAAgB,QAAQ,IAAI,qBAAqB,CAuBhD;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @orgloop/transform-filter — registration entry point.
|
|
3
|
+
*/
|
|
4
|
+
import { FilterTransform } from './filter.js';
|
|
5
|
+
export function register() {
|
|
6
|
+
return {
|
|
7
|
+
id: 'filter',
|
|
8
|
+
transform: FilterTransform,
|
|
9
|
+
configSchema: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
match: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
description: 'Dot-path field → value patterns. All must match for event to pass.',
|
|
15
|
+
},
|
|
16
|
+
exclude: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
description: 'Dot-path field → value or array. Any match drops the event.',
|
|
19
|
+
},
|
|
20
|
+
jq: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'jq expression. Truthy result = pass, falsy/error = drop.',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
additionalProperties: false,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export { FilterTransform } from './filter.js';
|
|
30
|
+
export { getByPath, matchesAll, matchesAny, matchesValue } from './matcher.js';
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,UAAU,QAAQ;IACvB,OAAO;QACN,EAAE,EAAE,QAAQ;QACZ,SAAS,EAAE,eAAe;QAC1B,YAAY,EAAE;YACb,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACX,KAAK,EAAE;oBACN,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,oEAAoE;iBACjF;gBACD,OAAO,EAAE;oBACR,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,6DAA6D;iBAC1E;gBACD,EAAE,EAAE;oBACH,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,0DAA0D;iBACvE;aACD;YACD,oBAAoB,EAAE,KAAK;SAC3B;KACD,CAAC;AACH,CAAC;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dot-path field matching logic for the filter transform.
|
|
3
|
+
*
|
|
4
|
+
* Supports nested field access via dot notation (e.g., "provenance.author_type")
|
|
5
|
+
* and pattern matching against values.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Get a value from a nested object using a dot-separated path.
|
|
9
|
+
* Returns undefined if any segment is missing.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getByPath(obj: unknown, path: string): unknown;
|
|
12
|
+
/**
|
|
13
|
+
* Check if a value matches a pattern.
|
|
14
|
+
* - Strings: exact match
|
|
15
|
+
* - Numbers/booleans: strict equality
|
|
16
|
+
* - Arrays: value must match any element in the array
|
|
17
|
+
* - RegExp-like strings (/pattern/flags): regex test
|
|
18
|
+
* - null: matches null or undefined
|
|
19
|
+
*/
|
|
20
|
+
export declare function matchesValue(actual: unknown, pattern: unknown): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Check if an event matches all criteria in a match object.
|
|
23
|
+
* Every field in `criteria` must match the corresponding event field.
|
|
24
|
+
*/
|
|
25
|
+
export declare function matchesAll(event: Record<string, unknown>, criteria: Record<string, unknown>): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Check if an event matches any criterion in an exclude object.
|
|
28
|
+
* If any field matches, the event should be excluded.
|
|
29
|
+
*/
|
|
30
|
+
export declare function matchesAny(event: Record<string, unknown>, criteria: Record<string, unknown>): boolean;
|
|
31
|
+
//# sourceMappingURL=matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAU7D;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAwBvE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAQT;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAQT"}
|
package/dist/matcher.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dot-path field matching logic for the filter transform.
|
|
3
|
+
*
|
|
4
|
+
* Supports nested field access via dot notation (e.g., "provenance.author_type")
|
|
5
|
+
* and pattern matching against values.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Get a value from a nested object using a dot-separated path.
|
|
9
|
+
* Returns undefined if any segment is missing.
|
|
10
|
+
*/
|
|
11
|
+
export function getByPath(obj, path) {
|
|
12
|
+
const segments = path.split('.');
|
|
13
|
+
let current = obj;
|
|
14
|
+
for (const segment of segments) {
|
|
15
|
+
if (current === null || current === undefined || typeof current !== 'object') {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
current = current[segment];
|
|
19
|
+
}
|
|
20
|
+
return current;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check if a value matches a pattern.
|
|
24
|
+
* - Strings: exact match
|
|
25
|
+
* - Numbers/booleans: strict equality
|
|
26
|
+
* - Arrays: value must match any element in the array
|
|
27
|
+
* - RegExp-like strings (/pattern/flags): regex test
|
|
28
|
+
* - null: matches null or undefined
|
|
29
|
+
*/
|
|
30
|
+
export function matchesValue(actual, pattern) {
|
|
31
|
+
if (pattern === null || pattern === undefined) {
|
|
32
|
+
return actual === null || actual === undefined;
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(pattern)) {
|
|
35
|
+
return pattern.some((p) => matchesValue(actual, p));
|
|
36
|
+
}
|
|
37
|
+
if (typeof pattern === 'string' && pattern.startsWith('/')) {
|
|
38
|
+
const lastSlash = pattern.lastIndexOf('/');
|
|
39
|
+
if (lastSlash > 0) {
|
|
40
|
+
const regexBody = pattern.slice(1, lastSlash);
|
|
41
|
+
const flags = pattern.slice(lastSlash + 1);
|
|
42
|
+
try {
|
|
43
|
+
const regex = new RegExp(regexBody, flags);
|
|
44
|
+
return regex.test(String(actual));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Invalid regex, fall through to exact match
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return actual === pattern;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Check if an event matches all criteria in a match object.
|
|
55
|
+
* Every field in `criteria` must match the corresponding event field.
|
|
56
|
+
*/
|
|
57
|
+
export function matchesAll(event, criteria) {
|
|
58
|
+
for (const [path, pattern] of Object.entries(criteria)) {
|
|
59
|
+
const actual = getByPath(event, path);
|
|
60
|
+
if (!matchesValue(actual, pattern)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Check if an event matches any criterion in an exclude object.
|
|
68
|
+
* If any field matches, the event should be excluded.
|
|
69
|
+
*/
|
|
70
|
+
export function matchesAny(event, criteria) {
|
|
71
|
+
for (const [path, pattern] of Object.entries(criteria)) {
|
|
72
|
+
const actual = getByPath(event, path);
|
|
73
|
+
if (matchesValue(actual, pattern)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=matcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.js","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,GAAY,EAAE,IAAY;IACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,OAAO,GAAY,GAAG,CAAC;IAC3B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAChC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC9E,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,OAAO,GAAI,OAAmC,CAAC,OAAO,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,MAAe,EAAE,OAAgB;IAC7D,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC/C,OAAO,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,CAAC;IAChD,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5D,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YACnB,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;YAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;YAC3C,IAAI,CAAC;gBACJ,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;gBAC3C,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YACnC,CAAC;YAAC,MAAM,CAAC;gBACR,6CAA6C;YAC9C,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,MAAM,KAAK,OAAO,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACzB,KAA8B,EAC9B,QAAiC;IAEjC,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;YACpC,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACzB,KAA8B,EAC9B,QAAiC;IAEjC,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACtC,IAAI,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@orgloop/transform-filter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OrgLoop filter transform — jq-based and match/exclude filtering",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@orgloop/sdk": "0.1.0"
|
|
10
|
+
},
|
|
11
|
+
"orgloop": {
|
|
12
|
+
"type": "transform",
|
|
13
|
+
"id": "filter"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"clean": "rm -rf dist",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "vitest run"
|
|
27
|
+
}
|
|
28
|
+
}
|