@hasna/events 0.1.9 → 0.1.10
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/README.md +17 -2
- package/dist/cli/index.js +120 -18
- package/dist/commander.js +55 -12
- package/dist/filter-options.d.ts +4 -2
- package/dist/filter.js +32 -6
- package/dist/index.js +32 -6
- package/dist/types.d.ts +7 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -207,8 +207,11 @@ events webhooks remove ops
|
|
|
207
207
|
Field filters can match nested `data` or `metadata` values. Plain
|
|
208
208
|
`--data`/`--metadata` values are strings, which keeps ids and slugs such as
|
|
209
209
|
`001` intact. Use `--data-json` or `--metadata-json` for typed JSON predicates.
|
|
210
|
-
Dot paths access nested object keys; dots inside key names are not escaped yet
|
|
211
|
-
|
|
210
|
+
Dot paths access nested object keys; dots inside key names are not escaped yet.
|
|
211
|
+
When the actual event value is an array, string filters match any primitive
|
|
212
|
+
array member, which is useful for tag routing such as `data.tags=auto:route`.
|
|
213
|
+
Use `path!=value` or `path!=json` for negative predicates such as
|
|
214
|
+
`metadata-json 'automation.no_auto!=true'`. Wildcard behavior stays broad for
|
|
212
215
|
legacy source/type/subject filters. For field paths ending in `_path` or `.path`,
|
|
213
216
|
`*` matches one path segment and `**` matches recursively.
|
|
214
217
|
|
|
@@ -218,12 +221,24 @@ events webhooks add loops \
|
|
|
218
221
|
--transport command \
|
|
219
222
|
--source todos \
|
|
220
223
|
--type task.created \
|
|
224
|
+
--timeout-ms 15000 \
|
|
225
|
+
--retry-attempts 3 \
|
|
226
|
+
--retry-backoff-ms 500 \
|
|
221
227
|
--metadata 'project_path=/home/hasna/workspace/hasna/opensource/*' \
|
|
222
228
|
--metadata-json 'route_enabled=true' \
|
|
229
|
+
--metadata-json 'automation.no_auto!=true' \
|
|
230
|
+
--data 'tags=auto:route' \
|
|
223
231
|
--arg events \
|
|
224
232
|
--arg handle \
|
|
225
233
|
--arg todos-task
|
|
226
234
|
|
|
235
|
+
# Command args that begin with dashes can be passed either form:
|
|
236
|
+
events webhooks add events --id json-route --transport command --arg --json
|
|
237
|
+
events webhooks add events --id json-route --transport command --arg=--json
|
|
238
|
+
|
|
239
|
+
# For nested CLIs, put child positional args and flags after an explicit delimiter.
|
|
240
|
+
events webhooks add events --id nested-route --transport command -- handle todos-task --json
|
|
241
|
+
|
|
227
242
|
events webhooks match open-source-task-route \
|
|
228
243
|
--source todos \
|
|
229
244
|
--type task.created \
|
package/dist/cli/index.js
CHANGED
|
@@ -48,14 +48,40 @@ function matchRecord(input, matcher) {
|
|
|
48
48
|
return true;
|
|
49
49
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
50
50
|
const actual = getPathValue(input, path);
|
|
51
|
-
|
|
52
|
-
return matchString(actual === undefined ? undefined : String(actual), expected, {
|
|
53
|
-
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
return actual === expected;
|
|
51
|
+
return matchField(actual, expected, path);
|
|
57
52
|
});
|
|
58
53
|
}
|
|
54
|
+
function matchField(actual, expected, path) {
|
|
55
|
+
if (isNegativeMatcher(expected)) {
|
|
56
|
+
return !matchPositiveField(actual, expected.not, path);
|
|
57
|
+
}
|
|
58
|
+
return matchPositiveField(actual, expected, path);
|
|
59
|
+
}
|
|
60
|
+
function matchPositiveField(actual, expected, path) {
|
|
61
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
62
|
+
return stringCandidates(actual).some((candidate) => matchString(candidate, expected, {
|
|
63
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(actual)) {
|
|
67
|
+
return actual.some((item) => item === expected);
|
|
68
|
+
}
|
|
69
|
+
return actual === expected;
|
|
70
|
+
}
|
|
71
|
+
function stringCandidates(actual) {
|
|
72
|
+
if (actual === undefined)
|
|
73
|
+
return [];
|
|
74
|
+
if (Array.isArray(actual)) {
|
|
75
|
+
return actual.flatMap((item) => isPrimitiveFieldValue(item) ? [String(item)] : []);
|
|
76
|
+
}
|
|
77
|
+
return [String(actual)];
|
|
78
|
+
}
|
|
79
|
+
function isPrimitiveFieldValue(value) {
|
|
80
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
81
|
+
}
|
|
82
|
+
function isNegativeMatcher(value) {
|
|
83
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "not" in value);
|
|
84
|
+
}
|
|
59
85
|
function eventMatchesFilter(event, filter) {
|
|
60
86
|
return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
|
|
61
87
|
}
|
|
@@ -640,13 +666,12 @@ function parseFieldMatchers(values, label, typed = false) {
|
|
|
640
666
|
return;
|
|
641
667
|
const result = {};
|
|
642
668
|
for (const value of values) {
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
throw new Error(`Invalid ${label} filter, expected path=value: ${value}`);
|
|
646
|
-
const path = value.slice(0, separator);
|
|
669
|
+
const parsed = parseMatcherExpression(value, label);
|
|
670
|
+
const path = parsed.path;
|
|
647
671
|
if (path in result)
|
|
648
672
|
throw new Error(`Duplicate ${label} filter path: ${path}`);
|
|
649
|
-
|
|
673
|
+
const matcherValue = typed ? parseTypedMatcherValue(parsed.rawValue, label) : parsed.rawValue;
|
|
674
|
+
result[path] = parsed.negated ? { not: matcherValue } : matcherValue;
|
|
650
675
|
}
|
|
651
676
|
return result;
|
|
652
677
|
}
|
|
@@ -688,6 +713,24 @@ function parseTypedMatcherValue(value, label) {
|
|
|
688
713
|
}
|
|
689
714
|
throw new Error(`${label} filter JSON values must be string, string[], number, boolean, or null`);
|
|
690
715
|
}
|
|
716
|
+
function parseMatcherExpression(value, label) {
|
|
717
|
+
const negativeSeparator = value.indexOf("!=");
|
|
718
|
+
if (negativeSeparator > 0) {
|
|
719
|
+
return {
|
|
720
|
+
path: value.slice(0, negativeSeparator),
|
|
721
|
+
rawValue: value.slice(negativeSeparator + 2),
|
|
722
|
+
negated: true
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
const separator = value.indexOf("=");
|
|
726
|
+
if (separator <= 0)
|
|
727
|
+
throw new Error(`Invalid ${label} filter, expected path=value or path!=value: ${value}`);
|
|
728
|
+
return {
|
|
729
|
+
path: value.slice(0, separator),
|
|
730
|
+
rawValue: value.slice(separator + 1),
|
|
731
|
+
negated: false
|
|
732
|
+
};
|
|
733
|
+
}
|
|
691
734
|
|
|
692
735
|
// src/cli/index.ts
|
|
693
736
|
function version() {
|
|
@@ -704,12 +747,23 @@ function parseGlobalArgs(argv) {
|
|
|
704
747
|
let dir;
|
|
705
748
|
for (let index = 0;index < argv.length; index += 1) {
|
|
706
749
|
const arg = argv[index];
|
|
750
|
+
if (arg === "--") {
|
|
751
|
+
rest.push(...argv.slice(index + 1));
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
if (!arg.startsWith("-")) {
|
|
755
|
+
rest.push(...argv.slice(index));
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
707
758
|
if (arg === "--json" || arg === "-j") {
|
|
708
759
|
json = true;
|
|
760
|
+
} else if (arg.startsWith("--dir=")) {
|
|
761
|
+
dir = arg.slice("--dir=".length);
|
|
709
762
|
} else if (arg === "--dir") {
|
|
710
763
|
dir = argv[++index];
|
|
711
764
|
} else {
|
|
712
|
-
rest.push(
|
|
765
|
+
rest.push(...argv.slice(index));
|
|
766
|
+
break;
|
|
713
767
|
}
|
|
714
768
|
}
|
|
715
769
|
return { json, dir, rest };
|
|
@@ -827,10 +881,10 @@ Options:
|
|
|
827
881
|
--source <source> Event source filter
|
|
828
882
|
--subject <subject> Event subject filter
|
|
829
883
|
--severity <severity> Event severity filter
|
|
830
|
-
--data <path=value>
|
|
831
|
-
--metadata <path=value>
|
|
832
|
-
--data-json <path=json>
|
|
833
|
-
--metadata-json <path=json> Event metadata field filter with typed JSON value
|
|
884
|
+
--data <path=value|path!=value> Event data field filter, repeatable; strings, dot paths, array-member matching, * segment wildcard, ** recursive wildcard
|
|
885
|
+
--metadata <path=value|path!=value> Event metadata field filter, repeatable; strings, dot paths, array-member matching, * segment wildcard, ** recursive wildcard
|
|
886
|
+
--data-json <path=json|path!=json> Event data field filter with typed JSON value
|
|
887
|
+
--metadata-json <path=json|path!=json> Event metadata field filter with typed JSON value
|
|
834
888
|
--honor-filters On webhooks test, skip delivery when the sample event does not match filters
|
|
835
889
|
--transport <kind> webhook or command
|
|
836
890
|
--secret <secret> Webhook signing secret
|
|
@@ -838,6 +892,41 @@ Options:
|
|
|
838
892
|
--redact <path> Redaction path, repeatable
|
|
839
893
|
--no-deliver Available on events emit`);
|
|
840
894
|
}
|
|
895
|
+
function printWebhookAddHelp(options = {}) {
|
|
896
|
+
const name = commandName(options);
|
|
897
|
+
console.log(`${name} webhooks add
|
|
898
|
+
|
|
899
|
+
Usage:
|
|
900
|
+
${name} [--dir <path>] [--json] webhooks add <url|command> [options]
|
|
901
|
+
${name} [--dir <path>] [--json] webhooks add <command> --transport command [options] -- [command-args...]
|
|
902
|
+
|
|
903
|
+
Options:
|
|
904
|
+
--id <id> Channel id
|
|
905
|
+
--name <name> Display name
|
|
906
|
+
--transport <kind> webhook or command
|
|
907
|
+
--type <pattern> Event type filter, supports wildcards
|
|
908
|
+
--source <source> Event source filter
|
|
909
|
+
--subject <subject> Event subject filter
|
|
910
|
+
--severity <severity> Event severity filter
|
|
911
|
+
--data <path=value|path!=value> Event data field filter, repeatable
|
|
912
|
+
--metadata <path=value|path!=value> Event metadata field filter, repeatable
|
|
913
|
+
--data-json <path=json|path!=json> Event data field filter with typed JSON value
|
|
914
|
+
--metadata-json <path=json|path!=json> Event metadata field filter with typed JSON value
|
|
915
|
+
--secret <secret> Webhook signing secret
|
|
916
|
+
--header <name=value> Webhook header, repeatable
|
|
917
|
+
--arg <arg> Command argument, repeatable; values may begin with dashes
|
|
918
|
+
--timeout-ms <ms> Transport timeout in milliseconds
|
|
919
|
+
--retry-attempts <n> Maximum delivery attempts
|
|
920
|
+
--retry-backoff-ms <ms> Initial retry backoff in milliseconds
|
|
921
|
+
--redact <path> Redaction path, repeatable
|
|
922
|
+
--disabled Create channel disabled
|
|
923
|
+
|
|
924
|
+
Examples:
|
|
925
|
+
${name} webhooks add https://example.com/webhooks/hasna --id ops --retry-attempts 3 --retry-backoff-ms 500
|
|
926
|
+
${name} webhooks add bun --id command-hook --transport command --arg run --arg ./handler.ts --arg --json
|
|
927
|
+
${name} webhooks add bun --id command-hook --transport command --arg=--json
|
|
928
|
+
${name} webhooks add bun --id command-hook --transport command -- run ./handler.ts --json`);
|
|
929
|
+
}
|
|
841
930
|
function printEventsHelp(options = {}) {
|
|
842
931
|
const name = commandName(options);
|
|
843
932
|
console.log(`${name} events
|
|
@@ -884,6 +973,10 @@ async function runEventsCli(argv = process.argv.slice(2), options = {}) {
|
|
|
884
973
|
printWebhooksHelp(options);
|
|
885
974
|
return;
|
|
886
975
|
}
|
|
976
|
+
if (command === "add" && (tail[0] === "--help" || tail[0] === "-h")) {
|
|
977
|
+
printWebhookAddHelp(options);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
887
980
|
if (tail.includes("--help") || tail.includes("-h")) {
|
|
888
981
|
printWebhooksHelp(options);
|
|
889
982
|
return;
|
|
@@ -907,7 +1000,7 @@ async function runEventsCli(argv = process.argv.slice(2), options = {}) {
|
|
|
907
1000
|
}
|
|
908
1001
|
async function handleWebhooks(client, command, tail, parsed, options) {
|
|
909
1002
|
if (command === "add") {
|
|
910
|
-
const args =
|
|
1003
|
+
const { args, delimiterArgs } = splitDelimiter(tail);
|
|
911
1004
|
const transport = takeOption(args, "--transport") ?? "webhook";
|
|
912
1005
|
const id = takeOption(args, "--id") ?? crypto.randomUUID();
|
|
913
1006
|
const name = takeOption(args, "--name");
|
|
@@ -938,7 +1031,7 @@ async function handleWebhooks(client, command, tail, parsed, options) {
|
|
|
938
1031
|
if (transport === "webhook") {
|
|
939
1032
|
channel.webhook = { url: target, secret, headers: parseHeaders(headerValues), timeoutMs };
|
|
940
1033
|
} else if (transport === "command") {
|
|
941
|
-
channel.command = { command: target, args: [...args.slice(1), ...commandArgs], timeoutMs };
|
|
1034
|
+
channel.command = { command: target, args: [...args.slice(1), ...commandArgs, ...delimiterArgs], timeoutMs };
|
|
942
1035
|
} else {
|
|
943
1036
|
throw new Error(`Transport ${transport} is reserved for future use and cannot be added yet`);
|
|
944
1037
|
}
|
|
@@ -1011,6 +1104,15 @@ async function handleWebhooks(client, command, tail, parsed, options) {
|
|
|
1011
1104
|
}
|
|
1012
1105
|
throw new Error(`Unknown webhooks command: ${command ?? ""}`);
|
|
1013
1106
|
}
|
|
1107
|
+
function splitDelimiter(values) {
|
|
1108
|
+
const delimiterIndex = values.indexOf("--");
|
|
1109
|
+
if (delimiterIndex === -1)
|
|
1110
|
+
return { args: [...values], delimiterArgs: [] };
|
|
1111
|
+
return {
|
|
1112
|
+
args: values.slice(0, delimiterIndex),
|
|
1113
|
+
delimiterArgs: values.slice(delimiterIndex + 1)
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1014
1116
|
async function handleEvents(client, command, tail, parsed, options) {
|
|
1015
1117
|
if (command === "emit") {
|
|
1016
1118
|
const args = [...tail];
|
package/dist/commander.js
CHANGED
|
@@ -38,14 +38,40 @@ function matchRecord(input, matcher) {
|
|
|
38
38
|
return true;
|
|
39
39
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
40
40
|
const actual = getPathValue(input, path);
|
|
41
|
-
|
|
42
|
-
return matchString(actual === undefined ? undefined : String(actual), expected, {
|
|
43
|
-
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
return actual === expected;
|
|
41
|
+
return matchField(actual, expected, path);
|
|
47
42
|
});
|
|
48
43
|
}
|
|
44
|
+
function matchField(actual, expected, path) {
|
|
45
|
+
if (isNegativeMatcher(expected)) {
|
|
46
|
+
return !matchPositiveField(actual, expected.not, path);
|
|
47
|
+
}
|
|
48
|
+
return matchPositiveField(actual, expected, path);
|
|
49
|
+
}
|
|
50
|
+
function matchPositiveField(actual, expected, path) {
|
|
51
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
52
|
+
return stringCandidates(actual).some((candidate) => matchString(candidate, expected, {
|
|
53
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(actual)) {
|
|
57
|
+
return actual.some((item) => item === expected);
|
|
58
|
+
}
|
|
59
|
+
return actual === expected;
|
|
60
|
+
}
|
|
61
|
+
function stringCandidates(actual) {
|
|
62
|
+
if (actual === undefined)
|
|
63
|
+
return [];
|
|
64
|
+
if (Array.isArray(actual)) {
|
|
65
|
+
return actual.flatMap((item) => isPrimitiveFieldValue(item) ? [String(item)] : []);
|
|
66
|
+
}
|
|
67
|
+
return [String(actual)];
|
|
68
|
+
}
|
|
69
|
+
function isPrimitiveFieldValue(value) {
|
|
70
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
71
|
+
}
|
|
72
|
+
function isNegativeMatcher(value) {
|
|
73
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "not" in value);
|
|
74
|
+
}
|
|
49
75
|
function eventMatchesFilter(event, filter) {
|
|
50
76
|
return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
|
|
51
77
|
}
|
|
@@ -651,13 +677,12 @@ function parseFieldMatchers(values, label, typed = false) {
|
|
|
651
677
|
return;
|
|
652
678
|
const result = {};
|
|
653
679
|
for (const value of values) {
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
throw new Error(`Invalid ${label} filter, expected path=value: ${value}`);
|
|
657
|
-
const path = value.slice(0, separator);
|
|
680
|
+
const parsed = parseMatcherExpression(value, label);
|
|
681
|
+
const path = parsed.path;
|
|
658
682
|
if (path in result)
|
|
659
683
|
throw new Error(`Duplicate ${label} filter path: ${path}`);
|
|
660
|
-
|
|
684
|
+
const matcherValue = typed ? parseTypedMatcherValue(parsed.rawValue, label) : parsed.rawValue;
|
|
685
|
+
result[path] = parsed.negated ? { not: matcherValue } : matcherValue;
|
|
661
686
|
}
|
|
662
687
|
return result;
|
|
663
688
|
}
|
|
@@ -699,6 +724,24 @@ function parseTypedMatcherValue(value, label) {
|
|
|
699
724
|
}
|
|
700
725
|
throw new Error(`${label} filter JSON values must be string, string[], number, boolean, or null`);
|
|
701
726
|
}
|
|
727
|
+
function parseMatcherExpression(value, label) {
|
|
728
|
+
const negativeSeparator = value.indexOf("!=");
|
|
729
|
+
if (negativeSeparator > 0) {
|
|
730
|
+
return {
|
|
731
|
+
path: value.slice(0, negativeSeparator),
|
|
732
|
+
rawValue: value.slice(negativeSeparator + 2),
|
|
733
|
+
negated: true
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
const separator = value.indexOf("=");
|
|
737
|
+
if (separator <= 0)
|
|
738
|
+
throw new Error(`Invalid ${label} filter, expected path=value or path!=value: ${value}`);
|
|
739
|
+
return {
|
|
740
|
+
path: value.slice(0, separator),
|
|
741
|
+
rawValue: value.slice(separator + 1),
|
|
742
|
+
negated: false
|
|
743
|
+
};
|
|
744
|
+
}
|
|
702
745
|
|
|
703
746
|
// src/commander.ts
|
|
704
747
|
function parseJsonObject(value, fallback) {
|
|
@@ -741,7 +784,7 @@ function wantsJson(actionOptions, command) {
|
|
|
741
784
|
}
|
|
742
785
|
function registerWebhookCommands(program, options) {
|
|
743
786
|
const webhooks = program.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
|
|
744
|
-
webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--data <path=value...>", "Event data field filter; string values, dot paths, * segment wildcard, ** recursive wildcard", collectValues, []).option("--metadata <path=value...>", "Event metadata field filter; string values, dot paths, * segment wildcard, ** recursive wildcard", collectValues, []).option("--data-json <path=json...>", "Event data field filter with typed JSON value", collectValues, []).option("--metadata-json <path=json...>", "Event metadata field filter with typed JSON value", collectValues, []).option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions, command) => {
|
|
787
|
+
webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--data <path=value...>", "Event data field filter; string values, path!=value negatives, array-member matching, dot paths, * segment wildcard, ** recursive wildcard", collectValues, []).option("--metadata <path=value...>", "Event metadata field filter; string values, path!=value negatives, array-member matching, dot paths, * segment wildcard, ** recursive wildcard", collectValues, []).option("--data-json <path=json...>", "Event data field filter with typed JSON value; path!=json negatives supported", collectValues, []).option("--metadata-json <path=json...>", "Event metadata field filter with typed JSON value; path!=json negatives supported", collectValues, []).option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions, command) => {
|
|
745
788
|
const timestamp = new Date().toISOString();
|
|
746
789
|
const channel = {
|
|
747
790
|
id: actionOptions.id,
|
package/dist/filter-options.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { EventFilter,
|
|
2
|
-
type MatcherValue =
|
|
1
|
+
import type { EventFilter, FieldMatcherValue } from "./types.js";
|
|
2
|
+
type MatcherValue = FieldMatcherValue | {
|
|
3
|
+
not: FieldMatcherValue;
|
|
4
|
+
};
|
|
3
5
|
export interface FilterOptionInput {
|
|
4
6
|
source?: string;
|
|
5
7
|
type?: string;
|
package/dist/filter.js
CHANGED
|
@@ -38,14 +38,40 @@ function matchRecord(input, matcher) {
|
|
|
38
38
|
return true;
|
|
39
39
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
40
40
|
const actual = getPathValue(input, path);
|
|
41
|
-
|
|
42
|
-
return matchString(actual === undefined ? undefined : String(actual), expected, {
|
|
43
|
-
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
return actual === expected;
|
|
41
|
+
return matchField(actual, expected, path);
|
|
47
42
|
});
|
|
48
43
|
}
|
|
44
|
+
function matchField(actual, expected, path) {
|
|
45
|
+
if (isNegativeMatcher(expected)) {
|
|
46
|
+
return !matchPositiveField(actual, expected.not, path);
|
|
47
|
+
}
|
|
48
|
+
return matchPositiveField(actual, expected, path);
|
|
49
|
+
}
|
|
50
|
+
function matchPositiveField(actual, expected, path) {
|
|
51
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
52
|
+
return stringCandidates(actual).some((candidate) => matchString(candidate, expected, {
|
|
53
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(actual)) {
|
|
57
|
+
return actual.some((item) => item === expected);
|
|
58
|
+
}
|
|
59
|
+
return actual === expected;
|
|
60
|
+
}
|
|
61
|
+
function stringCandidates(actual) {
|
|
62
|
+
if (actual === undefined)
|
|
63
|
+
return [];
|
|
64
|
+
if (Array.isArray(actual)) {
|
|
65
|
+
return actual.flatMap((item) => isPrimitiveFieldValue(item) ? [String(item)] : []);
|
|
66
|
+
}
|
|
67
|
+
return [String(actual)];
|
|
68
|
+
}
|
|
69
|
+
function isPrimitiveFieldValue(value) {
|
|
70
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
71
|
+
}
|
|
72
|
+
function isNegativeMatcher(value) {
|
|
73
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "not" in value);
|
|
74
|
+
}
|
|
49
75
|
function eventMatchesFilter(event, filter) {
|
|
50
76
|
return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
|
|
51
77
|
}
|
package/dist/index.js
CHANGED
|
@@ -38,14 +38,40 @@ function matchRecord(input, matcher) {
|
|
|
38
38
|
return true;
|
|
39
39
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
40
40
|
const actual = getPathValue(input, path);
|
|
41
|
-
|
|
42
|
-
return matchString(actual === undefined ? undefined : String(actual), expected, {
|
|
43
|
-
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
return actual === expected;
|
|
41
|
+
return matchField(actual, expected, path);
|
|
47
42
|
});
|
|
48
43
|
}
|
|
44
|
+
function matchField(actual, expected, path) {
|
|
45
|
+
if (isNegativeMatcher(expected)) {
|
|
46
|
+
return !matchPositiveField(actual, expected.not, path);
|
|
47
|
+
}
|
|
48
|
+
return matchPositiveField(actual, expected, path);
|
|
49
|
+
}
|
|
50
|
+
function matchPositiveField(actual, expected, path) {
|
|
51
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
52
|
+
return stringCandidates(actual).some((candidate) => matchString(candidate, expected, {
|
|
53
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(actual)) {
|
|
57
|
+
return actual.some((item) => item === expected);
|
|
58
|
+
}
|
|
59
|
+
return actual === expected;
|
|
60
|
+
}
|
|
61
|
+
function stringCandidates(actual) {
|
|
62
|
+
if (actual === undefined)
|
|
63
|
+
return [];
|
|
64
|
+
if (Array.isArray(actual)) {
|
|
65
|
+
return actual.flatMap((item) => isPrimitiveFieldValue(item) ? [String(item)] : []);
|
|
66
|
+
}
|
|
67
|
+
return [String(actual)];
|
|
68
|
+
}
|
|
69
|
+
function isPrimitiveFieldValue(value) {
|
|
70
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
71
|
+
}
|
|
72
|
+
function isNegativeMatcher(value) {
|
|
73
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "not" in value);
|
|
74
|
+
}
|
|
49
75
|
function eventMatchesFilter(event, filter) {
|
|
50
76
|
return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
|
|
51
77
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -27,13 +27,18 @@ export interface EventInput<TData extends EventData = EventData> {
|
|
|
27
27
|
metadata?: Record<string, unknown>;
|
|
28
28
|
}
|
|
29
29
|
export type StringMatcher = string | string[];
|
|
30
|
+
export type FieldMatcherValue = StringMatcher | number | boolean | null;
|
|
31
|
+
export interface NegativeFieldMatcher {
|
|
32
|
+
not: FieldMatcherValue;
|
|
33
|
+
}
|
|
34
|
+
export type FieldMatcher = FieldMatcherValue | NegativeFieldMatcher;
|
|
30
35
|
export interface EventFilter {
|
|
31
36
|
source?: StringMatcher;
|
|
32
37
|
type?: StringMatcher;
|
|
33
38
|
subject?: StringMatcher;
|
|
34
39
|
severity?: StringMatcher;
|
|
35
|
-
data?: Record<string,
|
|
36
|
-
metadata?: Record<string,
|
|
40
|
+
data?: Record<string, FieldMatcher>;
|
|
41
|
+
metadata?: Record<string, FieldMatcher>;
|
|
37
42
|
}
|
|
38
43
|
export interface RetryPolicy {
|
|
39
44
|
maxAttempts?: number;
|