@mepuka/skygent 0.2.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/README.md +59 -0
- package/index.ts +146 -0
- package/package.json +56 -0
- package/src/cli/app.ts +75 -0
- package/src/cli/config-command.ts +140 -0
- package/src/cli/config.ts +91 -0
- package/src/cli/derive.ts +205 -0
- package/src/cli/doc/annotation.ts +36 -0
- package/src/cli/doc/filter.ts +69 -0
- package/src/cli/doc/index.ts +9 -0
- package/src/cli/doc/post.ts +155 -0
- package/src/cli/doc/primitives.ts +25 -0
- package/src/cli/doc/render.ts +18 -0
- package/src/cli/doc/table.ts +114 -0
- package/src/cli/doc/thread.ts +46 -0
- package/src/cli/doc/tree.ts +126 -0
- package/src/cli/errors.ts +59 -0
- package/src/cli/exit-codes.ts +52 -0
- package/src/cli/feed.ts +177 -0
- package/src/cli/filter-dsl.ts +1411 -0
- package/src/cli/filter-errors.ts +208 -0
- package/src/cli/filter-help.ts +70 -0
- package/src/cli/filter-input.ts +54 -0
- package/src/cli/filter.ts +435 -0
- package/src/cli/graph.ts +472 -0
- package/src/cli/help.ts +14 -0
- package/src/cli/interval.ts +35 -0
- package/src/cli/jetstream.ts +173 -0
- package/src/cli/layers.ts +180 -0
- package/src/cli/logging.ts +136 -0
- package/src/cli/output-format.ts +26 -0
- package/src/cli/output.ts +82 -0
- package/src/cli/parse.ts +80 -0
- package/src/cli/post.ts +193 -0
- package/src/cli/preferences.ts +11 -0
- package/src/cli/query-fields.ts +247 -0
- package/src/cli/query.ts +415 -0
- package/src/cli/range.ts +44 -0
- package/src/cli/search.ts +465 -0
- package/src/cli/shared-options.ts +169 -0
- package/src/cli/shared.ts +20 -0
- package/src/cli/store-errors.ts +80 -0
- package/src/cli/store-tree.ts +392 -0
- package/src/cli/store.ts +395 -0
- package/src/cli/sync-factory.ts +107 -0
- package/src/cli/sync.ts +366 -0
- package/src/cli/view-thread.ts +196 -0
- package/src/cli/view.ts +47 -0
- package/src/cli/watch.ts +344 -0
- package/src/db/migrations/store-catalog/001_init.ts +14 -0
- package/src/db/migrations/store-index/001_init.ts +34 -0
- package/src/db/migrations/store-index/002_event_log.ts +24 -0
- package/src/db/migrations/store-index/003_fts_and_derived.ts +52 -0
- package/src/db/migrations/store-index/004_query_indexes.ts +9 -0
- package/src/db/migrations/store-index/005_post_lang.ts +15 -0
- package/src/db/migrations/store-index/006_has_embed.ts +10 -0
- package/src/db/migrations/store-index/007_event_seq_and_checkpoints.ts +68 -0
- package/src/domain/bsky.ts +467 -0
- package/src/domain/config.ts +11 -0
- package/src/domain/credentials.ts +6 -0
- package/src/domain/defaults.ts +8 -0
- package/src/domain/derivation.ts +55 -0
- package/src/domain/errors.ts +71 -0
- package/src/domain/events.ts +55 -0
- package/src/domain/extract.ts +64 -0
- package/src/domain/filter-describe.ts +551 -0
- package/src/domain/filter-explain.ts +9 -0
- package/src/domain/filter.ts +797 -0
- package/src/domain/format.ts +91 -0
- package/src/domain/index.ts +13 -0
- package/src/domain/indexes.ts +17 -0
- package/src/domain/policies.ts +16 -0
- package/src/domain/post.ts +88 -0
- package/src/domain/primitives.ts +50 -0
- package/src/domain/raw.ts +140 -0
- package/src/domain/store.ts +103 -0
- package/src/domain/sync.ts +211 -0
- package/src/domain/text-width.ts +56 -0
- package/src/services/app-config.ts +278 -0
- package/src/services/bsky-client.ts +2113 -0
- package/src/services/credential-store.ts +408 -0
- package/src/services/derivation-engine.ts +502 -0
- package/src/services/derivation-settings.ts +61 -0
- package/src/services/derivation-validator.ts +68 -0
- package/src/services/filter-compiler.ts +269 -0
- package/src/services/filter-library.ts +371 -0
- package/src/services/filter-runtime.ts +821 -0
- package/src/services/filter-settings.ts +30 -0
- package/src/services/identity-resolver.ts +563 -0
- package/src/services/jetstream-sync.ts +636 -0
- package/src/services/lineage-store.ts +89 -0
- package/src/services/link-validator.ts +244 -0
- package/src/services/output-manager.ts +274 -0
- package/src/services/post-parser.ts +62 -0
- package/src/services/profile-resolver.ts +223 -0
- package/src/services/resource-monitor.ts +106 -0
- package/src/services/shared.ts +69 -0
- package/src/services/store-cleaner.ts +43 -0
- package/src/services/store-commit.ts +168 -0
- package/src/services/store-db.ts +248 -0
- package/src/services/store-event-log.ts +285 -0
- package/src/services/store-index-sql.ts +289 -0
- package/src/services/store-index.ts +1152 -0
- package/src/services/store-keys.ts +4 -0
- package/src/services/store-manager.ts +358 -0
- package/src/services/store-stats.ts +522 -0
- package/src/services/store-writer.ts +200 -0
- package/src/services/sync-checkpoint-store.ts +169 -0
- package/src/services/sync-engine.ts +547 -0
- package/src/services/sync-reporter.ts +16 -0
- package/src/services/sync-settings.ts +72 -0
- package/src/services/trending-topics.ts +226 -0
- package/src/services/view-checkpoint-store.ts +238 -0
- package/src/typeclass/chunk.ts +84 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { ParseResult } from "effect";
|
|
2
|
+
import { safeParseJson, issueDetails } from "./shared.js";
|
|
3
|
+
import { formatAgentError, type AgentErrorPayload } from "./errors.js";
|
|
4
|
+
|
|
5
|
+
const validFilterTags = [
|
|
6
|
+
"All",
|
|
7
|
+
"None",
|
|
8
|
+
"And",
|
|
9
|
+
"Or",
|
|
10
|
+
"Not",
|
|
11
|
+
"Author",
|
|
12
|
+
"Hashtag",
|
|
13
|
+
"AuthorIn",
|
|
14
|
+
"HashtagIn",
|
|
15
|
+
"Contains",
|
|
16
|
+
"IsReply",
|
|
17
|
+
"IsQuote",
|
|
18
|
+
"IsRepost",
|
|
19
|
+
"IsOriginal",
|
|
20
|
+
"Engagement",
|
|
21
|
+
"HasImages",
|
|
22
|
+
"HasVideo",
|
|
23
|
+
"HasLinks",
|
|
24
|
+
"HasMedia",
|
|
25
|
+
"HasEmbed",
|
|
26
|
+
"Language",
|
|
27
|
+
"Regex",
|
|
28
|
+
"DateRange",
|
|
29
|
+
"HasValidLinks",
|
|
30
|
+
"Trending"
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const filterDocs = "docs/filters/README.md";
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
const getTag = (raw: string): string | undefined => {
|
|
37
|
+
const parsed = safeParseJson(raw);
|
|
38
|
+
if (!parsed || typeof parsed !== "object") {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
if (!("_tag" in parsed)) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const tag = (parsed as { readonly _tag?: unknown })._tag;
|
|
45
|
+
return typeof tag === "string" ? tag : undefined;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const hasPath = (issue: { readonly path: ReadonlyArray<unknown> }, key: string) =>
|
|
49
|
+
issue.path.length === 1 && issue.path[0] === key;
|
|
50
|
+
|
|
51
|
+
const validationError = (
|
|
52
|
+
payload: Omit<AgentErrorPayload, "error">
|
|
53
|
+
) => formatAgentError({ error: "FilterValidationError", ...payload });
|
|
54
|
+
|
|
55
|
+
const jsonParseError = (
|
|
56
|
+
payload: Omit<AgentErrorPayload, "error">
|
|
57
|
+
) => formatAgentError({ error: "FilterJsonParseError", ...payload });
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
export const formatFilterParseError = (error: ParseResult.ParseError, raw: string): string => {
|
|
61
|
+
const issues = ParseResult.ArrayFormatter.formatErrorSync(error);
|
|
62
|
+
if (issues.length === 0) {
|
|
63
|
+
return validationError({
|
|
64
|
+
message: "Filter expression failed validation.",
|
|
65
|
+
details: [ParseResult.TreeFormatter.formatErrorSync(error)]
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const tag = getTag(raw);
|
|
70
|
+
const received = safeParseJson(raw);
|
|
71
|
+
const receivedValue = received === undefined ? raw : received;
|
|
72
|
+
|
|
73
|
+
const jsonParseIssue = issues.find(
|
|
74
|
+
(issue) =>
|
|
75
|
+
issue._tag === "Transformation" &&
|
|
76
|
+
typeof issue.message === "string" &&
|
|
77
|
+
issue.message.startsWith("JSON Parse error")
|
|
78
|
+
);
|
|
79
|
+
if (jsonParseIssue) {
|
|
80
|
+
return jsonParseError({
|
|
81
|
+
message: "Invalid JSON in --filter-json.",
|
|
82
|
+
received: raw,
|
|
83
|
+
details: [
|
|
84
|
+
jsonParseIssue.message,
|
|
85
|
+
"Tip: wrap JSON in single quotes to avoid shell escaping issues."
|
|
86
|
+
]
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const tagMissing = issues.some((issue) => issue._tag === "Missing" && hasPath(issue, "_tag"));
|
|
91
|
+
if (tagMissing) {
|
|
92
|
+
return validationError({
|
|
93
|
+
message: "Filter expression requires a _tag field.",
|
|
94
|
+
received: receivedValue,
|
|
95
|
+
expected: { _tag: "Hashtag", tag: "#ai" },
|
|
96
|
+
fix:
|
|
97
|
+
"Add a valid _tag such as Hashtag, Author, Regex, or DateRange.",
|
|
98
|
+
details: [`See ${filterDocs} for filter examples.`],
|
|
99
|
+
validTags: validFilterTags
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const tagInvalid = issues.some((issue) => issue._tag === "Type" && hasPath(issue, "_tag"));
|
|
104
|
+
if (tagInvalid) {
|
|
105
|
+
return validationError({
|
|
106
|
+
message: `Invalid filter type${tag ? ` "${tag}"` : ""}.`,
|
|
107
|
+
received: receivedValue,
|
|
108
|
+
expected: { _tag: "Hashtag", tag: "#ai" },
|
|
109
|
+
fix: "Use a valid filter _tag.",
|
|
110
|
+
details: [`See ${filterDocs} for the full list of filters.`],
|
|
111
|
+
validTags: validFilterTags
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (tag === "Regex" && issues.some((issue) => hasPath(issue, "patterns"))) {
|
|
116
|
+
const hasPatternField =
|
|
117
|
+
received && typeof received === "object" && "pattern" in received;
|
|
118
|
+
return validationError({
|
|
119
|
+
message: "Regex filter requires a patterns field (array of strings).",
|
|
120
|
+
received: receivedValue,
|
|
121
|
+
expected: { _tag: "Regex", patterns: ["[Tt]rump"], flags: "i" },
|
|
122
|
+
fix: hasPatternField
|
|
123
|
+
? "Change 'pattern' to 'patterns' and wrap the value in an array."
|
|
124
|
+
: "Add a patterns array with at least one regex pattern."
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (tag === "AuthorIn" && issues.some((issue) => hasPath(issue, "handles"))) {
|
|
128
|
+
return validationError({
|
|
129
|
+
message: "AuthorIn filter requires a handles array.",
|
|
130
|
+
received: receivedValue,
|
|
131
|
+
expected: { _tag: "AuthorIn", handles: ["alice.bsky.social", "bob.bsky.social"] },
|
|
132
|
+
fix: "Provide at least one handle in handles."
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (tag === "HashtagIn" && issues.some((issue) => hasPath(issue, "tags"))) {
|
|
136
|
+
return validationError({
|
|
137
|
+
message: "HashtagIn filter requires a tags array.",
|
|
138
|
+
received: receivedValue,
|
|
139
|
+
expected: { _tag: "HashtagIn", tags: ["#tech", "#coding"] },
|
|
140
|
+
fix: "Provide at least one hashtag in tags."
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (tag === "Contains" && issues.some((issue) => hasPath(issue, "text"))) {
|
|
144
|
+
return validationError({
|
|
145
|
+
message: "Contains filter requires a text string.",
|
|
146
|
+
received: receivedValue,
|
|
147
|
+
expected: { _tag: "Contains", text: "typescript", caseSensitive: false },
|
|
148
|
+
fix: "Provide a non-empty text value to search for."
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (tag === "Hashtag" && issues.some((issue) => hasPath(issue, "tag"))) {
|
|
152
|
+
return validationError({
|
|
153
|
+
message: "Hashtag filter requires a tag field.",
|
|
154
|
+
received: receivedValue,
|
|
155
|
+
expected: { _tag: "Hashtag", tag: "#ai" },
|
|
156
|
+
fix: "Add tag with a leading #, e.g. #ai."
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (tag === "Author" && issues.some((issue) => hasPath(issue, "handle"))) {
|
|
160
|
+
return validationError({
|
|
161
|
+
message: "Author filter requires a handle field.",
|
|
162
|
+
received: receivedValue,
|
|
163
|
+
expected: { _tag: "Author", handle: "user.bsky.social" },
|
|
164
|
+
fix: "Add handle with the full Bluesky handle."
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (tag === "DateRange" && issues.some((issue) => hasPath(issue, "start") || hasPath(issue, "end"))) {
|
|
168
|
+
return validationError({
|
|
169
|
+
message: "DateRange filter requires start and end ISO timestamps with timezone.",
|
|
170
|
+
received: receivedValue,
|
|
171
|
+
expected: {
|
|
172
|
+
_tag: "DateRange",
|
|
173
|
+
start: "2026-01-01T00:00:00Z",
|
|
174
|
+
end: "2026-01-31T23:59:59Z"
|
|
175
|
+
},
|
|
176
|
+
fix: "Provide ISO timestamps for start and end with timezone (e.g. Z).",
|
|
177
|
+
details: [`See ${filterDocs} for range examples.`]
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (tag === "Language" && issues.some((issue) => hasPath(issue, "langs"))) {
|
|
181
|
+
return validationError({
|
|
182
|
+
message: "Language filter requires a langs array.",
|
|
183
|
+
received: receivedValue,
|
|
184
|
+
expected: { _tag: "Language", langs: ["en", "es"] },
|
|
185
|
+
fix: "Provide one or more language codes in langs."
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (
|
|
189
|
+
(tag === "HasValidLinks" || tag === "Trending") &&
|
|
190
|
+
issues.some((issue) => hasPath(issue, "onError"))
|
|
191
|
+
) {
|
|
192
|
+
return validationError({
|
|
193
|
+
message: "Effectful filters require an onError policy.",
|
|
194
|
+
received: receivedValue,
|
|
195
|
+
expected: {
|
|
196
|
+
_tag: tag,
|
|
197
|
+
onError: { _tag: "Retry", maxRetries: 3, baseDelay: "1 second" }
|
|
198
|
+
},
|
|
199
|
+
fix: "Add an onError policy (Include, Exclude, or Retry with maxRetries/baseDelay)."
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return validationError({
|
|
204
|
+
message: "Filter expression failed validation.",
|
|
205
|
+
received: receivedValue,
|
|
206
|
+
details: issueDetails(issues)
|
|
207
|
+
});
|
|
208
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const filterJsonExamples = [
|
|
2
|
+
"Examples:",
|
|
3
|
+
" All posts: '{\"_tag\":\"All\"}'",
|
|
4
|
+
" By author: '{\"_tag\":\"Author\",\"handle\":\"user.bsky.social\"}'",
|
|
5
|
+
" By hashtag: '{\"_tag\":\"Hashtag\",\"tag\":\"#ai\"}'",
|
|
6
|
+
" Authors list: '{\"_tag\":\"AuthorIn\",\"handles\":[\"alice.bsky.social\",\"bob.bsky.social\"]}'",
|
|
7
|
+
" Tags list: '{\"_tag\":\"HashtagIn\",\"tags\":[\"#tech\",\"#coding\"]}'",
|
|
8
|
+
" Contains text: '{\"_tag\":\"Contains\",\"text\":\"typescript\",\"caseSensitive\":false}'",
|
|
9
|
+
" Is reply: '{\"_tag\":\"IsReply\"}'",
|
|
10
|
+
" Engagement: '{\"_tag\":\"Engagement\",\"minLikes\":100}'",
|
|
11
|
+
" Has media: '{\"_tag\":\"HasMedia\"}'",
|
|
12
|
+
" Has embed: '{\"_tag\":\"HasEmbed\"}'",
|
|
13
|
+
" Language: '{\"_tag\":\"Language\",\"langs\":[\"en\",\"es\"]}'",
|
|
14
|
+
" By regex: '{\"_tag\":\"Regex\",\"patterns\":[\"pattern\"],\"flags\":\"i\"}'",
|
|
15
|
+
" Trending tag: '{\"_tag\":\"Trending\",\"tag\":\"#ai\",\"onError\":{\"_tag\":\"Include\"}}'",
|
|
16
|
+
" Valid links: '{\"_tag\":\"HasValidLinks\",\"onError\":{\"_tag\":\"Exclude\"}}'",
|
|
17
|
+
" Combined (AND): '{\"_tag\":\"And\",\"left\":{...},\"right\":{...}}'",
|
|
18
|
+
" Combined (OR): '{\"_tag\":\"Or\",\"left\":{...},\"right\":{...}}'",
|
|
19
|
+
" Inverted (NOT): '{\"_tag\":\"Not\",\"expr\":{...}}'"
|
|
20
|
+
].join("\n");
|
|
21
|
+
|
|
22
|
+
export const filterJsonDescription = (extra?: string) =>
|
|
23
|
+
[
|
|
24
|
+
"Filter expression as JSON string.",
|
|
25
|
+
"Sync/query filters run at ingestion or query time; store config filters are materialized views.",
|
|
26
|
+
...(extra ? [extra] : []),
|
|
27
|
+
"",
|
|
28
|
+
filterJsonExamples
|
|
29
|
+
].join("\n");
|
|
30
|
+
|
|
31
|
+
const filterDslExamples = [
|
|
32
|
+
"Examples:",
|
|
33
|
+
" hashtag:#ai AND author:user.bsky.social",
|
|
34
|
+
" authorin:alice.bsky.social,bob.bsky.social",
|
|
35
|
+
" hashtagin:#tech,#coding",
|
|
36
|
+
" contains:\"typescript\",caseSensitive=false",
|
|
37
|
+
" NOT hashtag:#spam",
|
|
38
|
+
" regex:/pattern/i",
|
|
39
|
+
" is:reply",
|
|
40
|
+
" engagement:minLikes=100,minReplies=5",
|
|
41
|
+
" hasmedia",
|
|
42
|
+
" hasembed",
|
|
43
|
+
" language:en,es",
|
|
44
|
+
" @tech AND author:user.bsky.social",
|
|
45
|
+
" date:2024-01-01T00:00:00Z..2024-01-31T00:00:00Z",
|
|
46
|
+
" links:onError=exclude",
|
|
47
|
+
" trending:#ai,onError=include",
|
|
48
|
+
" (hashtag:#ai OR hashtag:#ml) AND author:user.bsky.social",
|
|
49
|
+
"",
|
|
50
|
+
"Aliases:",
|
|
51
|
+
" from:alice.bsky.social -> author:alice.bsky.social",
|
|
52
|
+
" tag:#ai -> hashtag:#ai",
|
|
53
|
+
" text:\"hello\" -> contains:\"hello\"",
|
|
54
|
+
" lang:en -> language:en",
|
|
55
|
+
" authors:alice,bob -> authorin:alice,bob",
|
|
56
|
+
" tags:#ai,#ml -> hashtagin:#ai,#ml",
|
|
57
|
+
" is:reply|quote|repost|original",
|
|
58
|
+
" has:images|video|links|media|embed"
|
|
59
|
+
].join("\n");
|
|
60
|
+
|
|
61
|
+
export const filterDslDescription = () =>
|
|
62
|
+
[
|
|
63
|
+
"Filter expression using the DSL.",
|
|
64
|
+
"Sync/query filters run at ingestion or query time; store config filters are materialized views.",
|
|
65
|
+
"Options are comma-separated (no spaces); quote values with spaces.",
|
|
66
|
+
"Lists use commas (e.g. authorin:alice,bob). Named filters use @name.",
|
|
67
|
+
"Defaults: onError defaults to include for trending and exclude for links.",
|
|
68
|
+
"",
|
|
69
|
+
filterDslExamples
|
|
70
|
+
].join("\n");
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
2
|
+
import { FilterExprSchema, all } from "../domain/filter.js";
|
|
3
|
+
import type { FilterExpr } from "../domain/filter.js";
|
|
4
|
+
import type { FilterLibrary } from "../services/filter-library.js";
|
|
5
|
+
import { CliInputError, CliJsonError } from "./errors.js";
|
|
6
|
+
import { decodeJson } from "./parse.js";
|
|
7
|
+
import { formatFilterParseError } from "./filter-errors.js";
|
|
8
|
+
import { parseFilterDsl } from "./filter-dsl.js";
|
|
9
|
+
|
|
10
|
+
const conflictError = (filter: boolean, filterJson: boolean) =>
|
|
11
|
+
CliInputError.make({
|
|
12
|
+
message: "Use only one of --filter or --filter-json.",
|
|
13
|
+
cause: { filter, filterJson }
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const parseFilterExpr = (
|
|
17
|
+
filter: Option.Option<string>,
|
|
18
|
+
filterJson: Option.Option<string>
|
|
19
|
+
): Effect.Effect<FilterExpr, CliInputError | CliJsonError, FilterLibrary> =>
|
|
20
|
+
Option.match(filter, {
|
|
21
|
+
onNone: () =>
|
|
22
|
+
Option.match(filterJson, {
|
|
23
|
+
onNone: () => Effect.succeed(all()),
|
|
24
|
+
onSome: (raw) =>
|
|
25
|
+
decodeJson(FilterExprSchema, raw, {
|
|
26
|
+
formatter: formatFilterParseError
|
|
27
|
+
})
|
|
28
|
+
}),
|
|
29
|
+
onSome: (raw) =>
|
|
30
|
+
Option.match(filterJson, {
|
|
31
|
+
onNone: () => parseFilterDsl(raw),
|
|
32
|
+
onSome: () => Effect.fail(conflictError(true, true))
|
|
33
|
+
})
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const parseOptionalFilterExpr = (
|
|
37
|
+
filter: Option.Option<string>,
|
|
38
|
+
filterJson: Option.Option<string>
|
|
39
|
+
): Effect.Effect<Option.Option<FilterExpr>, CliInputError | CliJsonError, FilterLibrary> =>
|
|
40
|
+
Option.match(filter, {
|
|
41
|
+
onNone: () =>
|
|
42
|
+
Option.match(filterJson, {
|
|
43
|
+
onNone: () => Effect.succeed(Option.none()),
|
|
44
|
+
onSome: (raw) =>
|
|
45
|
+
decodeJson(FilterExprSchema, raw, {
|
|
46
|
+
formatter: formatFilterParseError
|
|
47
|
+
}).pipe(Effect.map(Option.some))
|
|
48
|
+
}),
|
|
49
|
+
onSome: (raw) =>
|
|
50
|
+
Option.match(filterJson, {
|
|
51
|
+
onNone: () => parseFilterDsl(raw).pipe(Effect.map(Option.some)),
|
|
52
|
+
onSome: () => Effect.fail(conflictError(true, true))
|
|
53
|
+
})
|
|
54
|
+
});
|