@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 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.
@@ -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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"}
@@ -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"}
@@ -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
+ }