@nyc-transit-kit/cli 0.1.4 → 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/package.json +6 -6
- package/src/commands/mta.ts +74 -592
- package/src/commands/nyc-dot.ts +53 -196
- package/src/commands/nyc-open-data.ts +51 -167
- package/src/commands/root.ts +2 -4
- package/src/commands/shared.ts +92 -132
- package/src/commands/socrata.ts +72 -41
- package/src/files.ts +23 -1
- package/src/main.ts +15 -26
- package/src/runtime.ts +28 -2
- package/src/commands/catalog.ts +0 -59
- package/src/commands/soda3-shared.ts +0 -103
- package/src/commands/types.ts +0 -33
- package/src/config.ts +0 -31
- package/src/output.ts +0 -39
package/src/commands/mta.ts
CHANGED
|
@@ -1,616 +1,98 @@
|
|
|
1
|
+
// biome-ignore-all format: first-wave CLI compression budget
|
|
1
2
|
import { type GtfsFeedKind, MtaGtfsRealtimeCaptureManifest } from "@nyc-transit-kit/contracts/mta"
|
|
2
|
-
import {
|
|
3
|
-
findMtaOpenDataDataset,
|
|
4
|
-
mtaOpenDataDatasets,
|
|
5
|
-
mtaOpenDataDomain
|
|
6
|
-
} from "@nyc-transit-kit/mta/datasets"
|
|
7
|
-
import {
|
|
8
|
-
findMtaGtfsRealtimeFeed,
|
|
9
|
-
findMtaGtfsStaticFeed,
|
|
10
|
-
type MtaGtfsRealtimeFeedDescriptor,
|
|
11
|
-
type MtaGtfsStaticFeedDescriptor,
|
|
12
|
-
mtaGtfsRealtimeFeeds,
|
|
13
|
-
mtaGtfsStaticFeeds
|
|
14
|
-
} from "@nyc-transit-kit/mta/feeds"
|
|
3
|
+
import { findMtaGtfsRealtimeFeed, findMtaGtfsStaticFeed, type MtaGtfsRealtimeFeedDescriptor, type MtaGtfsStaticFeedDescriptor, mtaGtfsRealtimeFeeds, mtaGtfsStaticFeeds } from "@nyc-transit-kit/mta/feeds"
|
|
15
4
|
import { captureGtfsRealtime, probeGtfsRealtime } from "@nyc-transit-kit/mta/gtfs-realtime"
|
|
16
5
|
import { fetchGtfsStaticResponse, probeGtfsStatic } from "@nyc-transit-kit/mta/gtfs-static"
|
|
17
|
-
import { queryMtaOpenData } from "@nyc-transit-kit/mta/open-data"
|
|
6
|
+
import { findMtaOpenDataDataset, mtaOpenDataDatasets, mtaOpenDataDomain, queryMtaOpenData } from "@nyc-transit-kit/mta/open-data"
|
|
18
7
|
import * as Effect from "effect/Effect"
|
|
19
8
|
import * as Option from "effect/Option"
|
|
20
9
|
import * as Command from "effect/unstable/cli/Command"
|
|
21
10
|
import * as Flag from "effect/unstable/cli/Flag"
|
|
22
11
|
import { atomicWriteGroup } from "../files"
|
|
23
|
-
import { failCommand, missingOption, runEffect,
|
|
24
|
-
import { fileWriteError, writeResponseToFile, writeSoda3QueryDryRun } from "./soda3-shared"
|
|
25
|
-
import { CliCommandContext, type CommandContext } from "./types"
|
|
26
|
-
|
|
27
|
-
export const mtaCommandPaths: ReadonlyArray<ReadonlyArray<string>> = [
|
|
28
|
-
["mta", "open-data", "dataset", "list"],
|
|
29
|
-
["mta", "open-data", "dataset", "info"],
|
|
30
|
-
["mta", "open-data", "dataset", "query"],
|
|
31
|
-
["mta", "gtfs-static", "list"],
|
|
32
|
-
["mta", "gtfs-static", "probe"],
|
|
33
|
-
["mta", "gtfs-static", "fetch"],
|
|
34
|
-
["mta", "gtfs-rt", "list"],
|
|
35
|
-
["mta", "gtfs-rt", "probe"],
|
|
36
|
-
["mta", "gtfs-rt", "decode"],
|
|
37
|
-
["mta", "gtfs-rt", "capture"]
|
|
38
|
-
]
|
|
39
|
-
|
|
40
|
-
type StaticFeedTarget =
|
|
41
|
-
| {
|
|
42
|
-
readonly _tag: "Resolved"
|
|
43
|
-
readonly url: string
|
|
44
|
-
readonly source: "descriptor" | "url"
|
|
45
|
-
readonly descriptor?: MtaGtfsStaticFeedDescriptor
|
|
46
|
-
}
|
|
47
|
-
| {
|
|
48
|
-
readonly _tag: "Missing"
|
|
49
|
-
}
|
|
50
|
-
| {
|
|
51
|
-
readonly _tag: "Unsupported"
|
|
52
|
-
readonly feed: string
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
type RealtimeFeedTarget =
|
|
56
|
-
| {
|
|
57
|
-
readonly _tag: "Resolved"
|
|
58
|
-
readonly feed: GtfsFeedKind
|
|
59
|
-
readonly url: string
|
|
60
|
-
readonly source: "descriptor" | "url"
|
|
61
|
-
readonly descriptor?: MtaGtfsRealtimeFeedDescriptor
|
|
62
|
-
}
|
|
63
|
-
| {
|
|
64
|
-
readonly _tag: "MissingUrl"
|
|
65
|
-
readonly feed: GtfsFeedKind
|
|
66
|
-
}
|
|
67
|
-
| {
|
|
68
|
-
readonly _tag: "Unsupported"
|
|
69
|
-
readonly feed: string
|
|
70
|
-
}
|
|
12
|
+
import { CliCommandContext, type CommandContext, dryRunFlag, failCommand, fileWriteError, makeDatasetCommands, missingOption, outputFlag, runEffect, writeResponseToFile, writeSuccess } from "./shared"
|
|
71
13
|
|
|
14
|
+
type StaticFeedTarget = { readonly _tag: "Resolved"; readonly url: string; readonly source: "descriptor" | "url"; readonly descriptor?: MtaGtfsStaticFeedDescriptor } | { readonly _tag: "Missing" } | { readonly _tag: "Unsupported"; readonly feed: string }
|
|
15
|
+
type RealtimeFeedTarget = { readonly _tag: "Resolved"; readonly feed: GtfsFeedKind; readonly url: string; readonly source: "descriptor" | "url"; readonly descriptor?: MtaGtfsRealtimeFeedDescriptor } | { readonly _tag: "MissingUrl"; readonly feed: GtfsFeedKind } | { readonly _tag: "Unsupported"; readonly feed: string }
|
|
72
16
|
const gtfsFeedKinds = new Set(["vehicle-positions", "trip-updates", "alerts"])
|
|
73
|
-
|
|
74
17
|
const optionalString = (value: Option.Option<string>) => Option.getOrUndefined(value)
|
|
75
|
-
|
|
76
18
|
const isGtfsFeedKind = (value: string): value is GtfsFeedKind => gtfsFeedKinds.has(value)
|
|
77
19
|
|
|
78
|
-
const resolveStaticFeedTarget = (
|
|
79
|
-
feed:
|
|
80
|
-
url:
|
|
81
|
-
):
|
|
82
|
-
|
|
83
|
-
const urlOverride = optionalString(url)
|
|
84
|
-
const descriptor = feedKey === undefined ? undefined : findMtaGtfsStaticFeed(feedKey)
|
|
85
|
-
|
|
86
|
-
if (urlOverride !== undefined) {
|
|
87
|
-
return {
|
|
88
|
-
_tag: "Resolved",
|
|
89
|
-
url: urlOverride,
|
|
90
|
-
source: "url",
|
|
91
|
-
...(descriptor === undefined ? {} : { descriptor })
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (feedKey === undefined) {
|
|
96
|
-
return { _tag: "Missing" }
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (descriptor === undefined) {
|
|
100
|
-
return {
|
|
101
|
-
_tag: "Unsupported",
|
|
102
|
-
feed: feedKey
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
_tag: "Resolved",
|
|
108
|
-
url: descriptor.url,
|
|
109
|
-
source: "descriptor",
|
|
110
|
-
descriptor
|
|
111
|
-
}
|
|
20
|
+
const resolveStaticFeedTarget = (feed: Option.Option<string>, url: Option.Option<string>): StaticFeedTarget => {
|
|
21
|
+
const feedKey = optionalString(feed), urlOverride = optionalString(url), descriptor = feedKey === undefined ? undefined : findMtaGtfsStaticFeed(feedKey)
|
|
22
|
+
if (urlOverride !== undefined) return { _tag: "Resolved", url: urlOverride, source: "url", ...(descriptor === undefined ? {} : { descriptor }) }
|
|
23
|
+
if (feedKey === undefined) return { _tag: "Missing" }
|
|
24
|
+
return descriptor === undefined ? { _tag: "Unsupported", feed: feedKey } : { _tag: "Resolved", url: descriptor.url, source: "descriptor", descriptor }
|
|
112
25
|
}
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const urlOverride = optionalString(url)
|
|
119
|
-
const descriptor = findMtaGtfsRealtimeFeed(feedKey)
|
|
120
|
-
|
|
121
|
-
if (descriptor !== undefined) {
|
|
122
|
-
return {
|
|
123
|
-
_tag: "Resolved",
|
|
124
|
-
feed: descriptor.feed,
|
|
125
|
-
url: urlOverride ?? descriptor.url,
|
|
126
|
-
source: urlOverride === undefined ? "descriptor" : "url",
|
|
127
|
-
descriptor
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (isGtfsFeedKind(feedKey)) {
|
|
132
|
-
if (urlOverride === undefined) {
|
|
133
|
-
return {
|
|
134
|
-
_tag: "MissingUrl",
|
|
135
|
-
feed: feedKey
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
_tag: "Resolved",
|
|
141
|
-
feed: feedKey,
|
|
142
|
-
url: urlOverride,
|
|
143
|
-
source: "url"
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
_tag: "Unsupported",
|
|
149
|
-
feed: feedKey
|
|
150
|
-
}
|
|
26
|
+
const resolveRealtimeFeedTarget = (feedKey: string, url: Option.Option<string>): RealtimeFeedTarget => {
|
|
27
|
+
const urlOverride = optionalString(url), descriptor = findMtaGtfsRealtimeFeed(feedKey)
|
|
28
|
+
if (descriptor !== undefined) return { _tag: "Resolved", feed: descriptor.feed, url: urlOverride ?? descriptor.url, source: urlOverride === undefined ? "descriptor" : "url", descriptor }
|
|
29
|
+
if (!isGtfsFeedKind(feedKey)) return { _tag: "Unsupported", feed: feedKey }
|
|
30
|
+
return urlOverride === undefined ? { _tag: "MissingUrl", feed: feedKey } : { _tag: "Resolved", feed: feedKey, url: urlOverride, source: "url" }
|
|
151
31
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
tokens:
|
|
156
|
-
|
|
157
|
-
) => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return missingOption(context, tokens, "feed")
|
|
161
|
-
case "Unsupported":
|
|
162
|
-
return failCommand(
|
|
163
|
-
context,
|
|
164
|
-
"mta",
|
|
165
|
-
"unsupported-feed",
|
|
166
|
-
`Unsupported MTA GTFS static feed: ${target.feed}`,
|
|
167
|
-
tokens
|
|
168
|
-
)
|
|
169
|
-
case "Resolved":
|
|
170
|
-
return Effect.void
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const failRealtimeTarget = (
|
|
175
|
-
context: CommandContext,
|
|
176
|
-
tokens: ReadonlyArray<string>,
|
|
177
|
-
target: RealtimeFeedTarget
|
|
178
|
-
) => {
|
|
179
|
-
switch (target._tag) {
|
|
180
|
-
case "MissingUrl":
|
|
181
|
-
return missingOption(context, tokens, "url")
|
|
182
|
-
case "Unsupported":
|
|
183
|
-
return failCommand(
|
|
184
|
-
context,
|
|
185
|
-
"mta",
|
|
186
|
-
"unsupported-feed",
|
|
187
|
-
`Unsupported MTA GTFS realtime feed: ${target.feed}`,
|
|
188
|
-
tokens
|
|
189
|
-
)
|
|
190
|
-
case "Resolved":
|
|
191
|
-
return Effect.void
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export const handleMtaOpenDataList = () =>
|
|
32
|
+
const failStaticTarget = (context: CommandContext, tokens: ReadonlyArray<string>, target: StaticFeedTarget) =>
|
|
33
|
+
target._tag === "Missing" ? missingOption(context, tokens, "feed") : target._tag === "Unsupported" ? failCommand(context, "mta", "unsupported-feed", `Unsupported MTA GTFS static feed: ${target.feed}`, tokens) : Effect.void
|
|
34
|
+
const failRealtimeTarget = (context: CommandContext, tokens: ReadonlyArray<string>, target: RealtimeFeedTarget) =>
|
|
35
|
+
target._tag === "MissingUrl" ? missingOption(context, tokens, "url") : target._tag === "Unsupported" ? failCommand(context, "mta", "unsupported-feed", `Unsupported MTA GTFS realtime feed: ${target.feed}`, tokens) : Effect.void
|
|
36
|
+
const feedTargetPayload = (target: { readonly url: string; readonly source: "descriptor" | "url"; readonly descriptor?: unknown }) => ({ url: target.url, source: target.source, descriptor: target.descriptor })
|
|
37
|
+
const feedListHandler = (feeds: unknown) => () => Effect.gen(function* () { const context = yield* CliCommandContext; return yield* writeSuccess(context, "mta", { feeds }) })
|
|
38
|
+
|
|
39
|
+
const handleMtaGtfsStaticProbe = (config: { readonly feed: Option.Option<string>; readonly url: Option.Option<string>; readonly dryRun: boolean }) =>
|
|
196
40
|
Effect.gen(function* () {
|
|
197
|
-
const context = yield* CliCommandContext
|
|
198
|
-
return yield*
|
|
199
|
-
|
|
200
|
-
})
|
|
41
|
+
const context = yield* CliCommandContext, tokens = ["mta", "gtfs-static", "probe"], target = resolveStaticFeedTarget(config.feed, config.url)
|
|
42
|
+
if (target._tag !== "Resolved") return yield* failStaticTarget(context, tokens, target)
|
|
43
|
+
return yield* config.dryRun ? writeSuccess(context, "mta", { dryRun: true, method: "HEAD", ...feedTargetPayload(target) }) : runEffect(context, "mta", tokens, probeGtfsStatic({ url: target.url }).pipe(Effect.provide(context.runtime.mtaLayer)))
|
|
201
44
|
})
|
|
202
|
-
|
|
203
|
-
export const handleMtaOpenDataInfo = (config: { readonly datasetId: string }) =>
|
|
204
|
-
Effect.gen(function* () {
|
|
205
|
-
const context = yield* CliCommandContext
|
|
206
|
-
const descriptor = findMtaOpenDataDataset(config.datasetId)
|
|
207
|
-
|
|
208
|
-
return yield* writeSuccess(context, "mta", {
|
|
209
|
-
known: descriptor !== undefined,
|
|
210
|
-
descriptor:
|
|
211
|
-
descriptor === undefined
|
|
212
|
-
? {
|
|
213
|
-
id: config.datasetId,
|
|
214
|
-
domain: mtaOpenDataDomain,
|
|
215
|
-
backing: "socrata"
|
|
216
|
-
}
|
|
217
|
-
: descriptor
|
|
218
|
-
})
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
export const handleMtaOpenDataQuery = (config: {
|
|
222
|
-
readonly datasetId: string
|
|
223
|
-
readonly select: string
|
|
224
|
-
readonly dryRun: boolean
|
|
225
|
-
}) =>
|
|
226
|
-
Effect.gen(function* () {
|
|
227
|
-
const context = yield* CliCommandContext
|
|
228
|
-
const tokens = ["mta", "open-data", "dataset", "query"]
|
|
229
|
-
const request = {
|
|
230
|
-
datasetId: config.datasetId,
|
|
231
|
-
query: soqlFromSelect(config.select)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (config.dryRun) {
|
|
235
|
-
return yield* writeSoda3QueryDryRun({
|
|
236
|
-
context,
|
|
237
|
-
apiFamily: "mta",
|
|
238
|
-
tokens,
|
|
239
|
-
domain: mtaOpenDataDomain,
|
|
240
|
-
...request,
|
|
241
|
-
extra: {
|
|
242
|
-
known: findMtaOpenDataDataset(config.datasetId) !== undefined
|
|
243
|
-
}
|
|
244
|
-
})
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return yield* runEffect(
|
|
248
|
-
context,
|
|
249
|
-
"mta",
|
|
250
|
-
tokens,
|
|
251
|
-
queryMtaOpenData(request).pipe(Effect.provide(context.runtime.soda3Layer))
|
|
252
|
-
)
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
export const handleMtaGtfsStaticList = () =>
|
|
45
|
+
const handleMtaGtfsStaticFetch = (config: { readonly feed: Option.Option<string>; readonly url: Option.Option<string>; readonly output: string; readonly dryRun: boolean }) =>
|
|
256
46
|
Effect.gen(function* () {
|
|
257
|
-
const context = yield* CliCommandContext
|
|
258
|
-
return yield*
|
|
259
|
-
|
|
260
|
-
})
|
|
47
|
+
const context = yield* CliCommandContext, tokens = ["mta", "gtfs-static", "fetch"], target = resolveStaticFeedTarget(config.feed, config.url)
|
|
48
|
+
if (target._tag !== "Resolved") return yield* failStaticTarget(context, tokens, target)
|
|
49
|
+
return yield* config.dryRun ? writeSuccess(context, "mta", { dryRun: true, method: "GET", output: config.output, ...feedTargetPayload(target) }) : runEffect(context, "mta", tokens, Effect.gen(function* () { const response = yield* fetchGtfsStaticResponse({ url: target.url }).pipe(Effect.provide(context.runtime.mtaLayer)); return yield* writeResponseToFile(config.output, response) }))
|
|
261
50
|
})
|
|
262
|
-
|
|
263
|
-
export const handleMtaGtfsStaticProbe = (config: {
|
|
264
|
-
readonly feed: Option.Option<string>
|
|
265
|
-
readonly url: Option.Option<string>
|
|
266
|
-
readonly dryRun: boolean
|
|
267
|
-
}) =>
|
|
51
|
+
const runMtaGtfsRealtimeRequest = (tokens: ReadonlyArray<string>, config: { readonly feed: string; readonly url: Option.Option<string>; readonly dryRun: boolean }) =>
|
|
268
52
|
Effect.gen(function* () {
|
|
269
|
-
const context = yield* CliCommandContext
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (target._tag !== "Resolved") {
|
|
274
|
-
return yield* failStaticTarget(context, tokens, target)
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (config.dryRun) {
|
|
278
|
-
return yield* writeSuccess(context, "mta", {
|
|
279
|
-
dryRun: true,
|
|
280
|
-
method: "HEAD",
|
|
281
|
-
url: target.url,
|
|
282
|
-
source: target.source,
|
|
283
|
-
descriptor: target.descriptor
|
|
284
|
-
})
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return yield* runEffect(
|
|
288
|
-
context,
|
|
289
|
-
"mta",
|
|
290
|
-
tokens,
|
|
291
|
-
probeGtfsStatic({ url: target.url }).pipe(Effect.provide(context.runtime.mtaLayer))
|
|
292
|
-
)
|
|
53
|
+
const context = yield* CliCommandContext, target = resolveRealtimeFeedTarget(config.feed, config.url)
|
|
54
|
+
if (target._tag !== "Resolved") return yield* failRealtimeTarget(context, tokens, target)
|
|
55
|
+
return yield* config.dryRun ? writeSuccess(context, "mta", { dryRun: true, method: "GET", feed: target.feed, ...feedTargetPayload(target) }) : runEffect(context, "mta", tokens, probeGtfsRealtime({ feed: target.feed, url: target.url }).pipe(Effect.provide(context.runtime.mtaRealtimeLayer)))
|
|
293
56
|
})
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
readonly url: Option.Option<string>
|
|
298
|
-
readonly output: string
|
|
299
|
-
readonly dryRun: boolean
|
|
300
|
-
}) =>
|
|
57
|
+
const handleMtaGtfsRealtimeProbe = (config: { readonly feed: string; readonly url: Option.Option<string>; readonly dryRun: boolean }) => runMtaGtfsRealtimeRequest(["mta", "gtfs-rt", "probe"], config)
|
|
58
|
+
const handleMtaGtfsRealtimeDecode = (config: { readonly feed: string; readonly url: Option.Option<string>; readonly dryRun: boolean }) => runMtaGtfsRealtimeRequest(["mta", "gtfs-rt", "decode"], config)
|
|
59
|
+
const handleMtaGtfsRealtimeCapture = (config: { readonly feed: string; readonly url: Option.Option<string>; readonly output: string; readonly manifestOutput: string; readonly dryRun: boolean }) =>
|
|
301
60
|
Effect.gen(function* () {
|
|
302
|
-
const context = yield* CliCommandContext
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
return yield* writeSuccess(context, "mta", {
|
|
312
|
-
dryRun: true,
|
|
313
|
-
method: "GET",
|
|
314
|
-
url: target.url,
|
|
315
|
-
output: config.output,
|
|
316
|
-
source: target.source,
|
|
317
|
-
descriptor: target.descriptor
|
|
318
|
-
})
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
return yield* runEffect(
|
|
322
|
-
context,
|
|
323
|
-
"mta",
|
|
324
|
-
tokens,
|
|
325
|
-
Effect.gen(function* () {
|
|
326
|
-
const response = yield* fetchGtfsStaticResponse({ url: target.url }).pipe(
|
|
327
|
-
Effect.provide(context.runtime.mtaLayer)
|
|
328
|
-
)
|
|
329
|
-
return yield* writeResponseToFile(config.output, response)
|
|
330
|
-
})
|
|
331
|
-
)
|
|
61
|
+
const context = yield* CliCommandContext, tokens = ["mta", "gtfs-rt", "capture"], target = resolveRealtimeFeedTarget(config.feed, config.url)
|
|
62
|
+
if (target._tag !== "Resolved") return yield* failRealtimeTarget(context, tokens, target)
|
|
63
|
+
if (config.dryRun) return yield* writeSuccess(context, "mta", { dryRun: true, method: "GET", feed: target.feed, output: config.output, manifestOutput: config.manifestOutput, ...feedTargetPayload(target) })
|
|
64
|
+
return yield* runEffect(context, "mta", tokens, Effect.gen(function* () {
|
|
65
|
+
const result = yield* captureGtfsRealtime({ feed: target.feed, url: target.url }).pipe(Effect.provide(context.runtime.mtaRealtimeLayer))
|
|
66
|
+
const manifest = MtaGtfsRealtimeCaptureManifest.make({ feed: result.feed, status: result.status, byteLength: result.byteLength, sha256: result.sha256, capturedAt: result.capturedAt, url: result.url })
|
|
67
|
+
yield* Effect.tryPromise({ try: () => atomicWriteGroup([{ path: config.output, body: result.bytes }, { path: config.manifestOutput, body: `${JSON.stringify(manifest, null, 2)}\n` }]), catch: fileWriteError })
|
|
68
|
+
return { output: config.output, manifestOutput: config.manifestOutput, manifest }
|
|
69
|
+
}))
|
|
332
70
|
})
|
|
333
71
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
method: "GET",
|
|
362
|
-
feed: target.feed,
|
|
363
|
-
url: target.url,
|
|
364
|
-
source: target.source,
|
|
365
|
-
descriptor: target.descriptor
|
|
366
|
-
})
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return yield* runEffect(
|
|
370
|
-
context,
|
|
371
|
-
"mta",
|
|
372
|
-
tokens,
|
|
373
|
-
probeGtfsRealtime({ feed: target.feed, url: target.url }).pipe(
|
|
374
|
-
Effect.provide(context.runtime.mtaRealtimeLayer)
|
|
375
|
-
)
|
|
376
|
-
)
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
export const handleMtaGtfsRealtimeProbe = (config: {
|
|
380
|
-
readonly feed: string
|
|
381
|
-
readonly url: Option.Option<string>
|
|
382
|
-
readonly dryRun: boolean
|
|
383
|
-
}) => runMtaGtfsRealtimeRequest(["mta", "gtfs-rt", "probe"], config)
|
|
384
|
-
|
|
385
|
-
export const handleMtaGtfsRealtimeDecode = (config: {
|
|
386
|
-
readonly feed: string
|
|
387
|
-
readonly url: Option.Option<string>
|
|
388
|
-
readonly dryRun: boolean
|
|
389
|
-
}) => runMtaGtfsRealtimeRequest(["mta", "gtfs-rt", "decode"], config)
|
|
390
|
-
|
|
391
|
-
export const handleMtaGtfsRealtimeCapture = (config: {
|
|
392
|
-
readonly feed: string
|
|
393
|
-
readonly url: Option.Option<string>
|
|
394
|
-
readonly output: string
|
|
395
|
-
readonly manifestOutput: string
|
|
396
|
-
readonly dryRun: boolean
|
|
397
|
-
}) =>
|
|
398
|
-
Effect.gen(function* () {
|
|
399
|
-
const context = yield* CliCommandContext
|
|
400
|
-
const tokens = ["mta", "gtfs-rt", "capture"]
|
|
401
|
-
const target = resolveRealtimeFeedTarget(config.feed, config.url)
|
|
402
|
-
|
|
403
|
-
if (target._tag !== "Resolved") {
|
|
404
|
-
return yield* failRealtimeTarget(context, tokens, target)
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
if (config.dryRun) {
|
|
408
|
-
return yield* writeSuccess(context, "mta", {
|
|
409
|
-
dryRun: true,
|
|
410
|
-
method: "GET",
|
|
411
|
-
feed: target.feed,
|
|
412
|
-
url: target.url,
|
|
413
|
-
output: config.output,
|
|
414
|
-
manifestOutput: config.manifestOutput,
|
|
415
|
-
source: target.source,
|
|
416
|
-
descriptor: target.descriptor
|
|
417
|
-
})
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return yield* runEffect(
|
|
421
|
-
context,
|
|
422
|
-
"mta",
|
|
423
|
-
tokens,
|
|
424
|
-
Effect.gen(function* () {
|
|
425
|
-
const result = yield* captureGtfsRealtime({
|
|
426
|
-
feed: target.feed,
|
|
427
|
-
url: target.url
|
|
428
|
-
}).pipe(Effect.provide(context.runtime.mtaRealtimeLayer))
|
|
429
|
-
const manifest = MtaGtfsRealtimeCaptureManifest.make({
|
|
430
|
-
feed: result.feed,
|
|
431
|
-
status: result.status,
|
|
432
|
-
byteLength: result.byteLength,
|
|
433
|
-
sha256: result.sha256,
|
|
434
|
-
capturedAt: result.capturedAt,
|
|
435
|
-
url: result.url
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
yield* Effect.tryPromise({
|
|
439
|
-
try: () =>
|
|
440
|
-
atomicWriteGroup([
|
|
441
|
-
{
|
|
442
|
-
path: config.output,
|
|
443
|
-
body: result.bytes
|
|
444
|
-
},
|
|
445
|
-
{
|
|
446
|
-
path: config.manifestOutput,
|
|
447
|
-
body: `${JSON.stringify(manifest, null, 2)}\n`
|
|
448
|
-
}
|
|
449
|
-
]),
|
|
450
|
-
catch: fileWriteError
|
|
451
|
-
})
|
|
452
|
-
|
|
453
|
-
return {
|
|
454
|
-
output: config.output,
|
|
455
|
-
manifestOutput: config.manifestOutput,
|
|
456
|
-
manifest
|
|
457
|
-
}
|
|
458
|
-
})
|
|
459
|
-
)
|
|
460
|
-
})
|
|
461
|
-
|
|
462
|
-
const feedFlagDescription =
|
|
463
|
-
"Known feed id/name. Use --url with vehicle-positions, trip-updates, or alerts for manual URLs."
|
|
464
|
-
|
|
465
|
-
const optionalFeedFlag = Flag.optional(
|
|
466
|
-
Flag.string("feed").pipe(Flag.withDescription("Known feed id/name."))
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
const optionalUrlFlag = Flag.optional(
|
|
470
|
-
Flag.string("url").pipe(Flag.withDescription("Manual provider URL override."))
|
|
471
|
-
)
|
|
472
|
-
|
|
473
|
-
const mtaOpenDataDatasetListCommand = Command.make("list", {}, handleMtaOpenDataList).pipe(
|
|
474
|
-
Command.withShortDescription("List curated MTA Open Data descriptors.")
|
|
475
|
-
)
|
|
476
|
-
|
|
477
|
-
const mtaOpenDataDatasetInfoCommand = Command.make(
|
|
478
|
-
"info",
|
|
479
|
-
{
|
|
480
|
-
datasetId: Flag.string("dataset").pipe(
|
|
481
|
-
Flag.withDescription("MTA Open Data Socrata dataset id.")
|
|
482
|
-
)
|
|
483
|
-
},
|
|
484
|
-
handleMtaOpenDataInfo
|
|
485
|
-
).pipe(Command.withShortDescription("Show MTA Open Data dataset metadata."))
|
|
486
|
-
|
|
487
|
-
const mtaOpenDataDatasetQueryCommand = Command.make(
|
|
488
|
-
"query",
|
|
489
|
-
{
|
|
490
|
-
datasetId: Flag.string("dataset").pipe(
|
|
491
|
-
Flag.withDescription("MTA Open Data Socrata dataset id.")
|
|
492
|
-
),
|
|
493
|
-
select: Flag.string("select").pipe(
|
|
494
|
-
Flag.withDescription("SoQL select expression or full SELECT query.")
|
|
495
|
-
),
|
|
496
|
-
dryRun: Flag.boolean("dry-run").pipe(
|
|
497
|
-
Flag.withDescription("Print the planned request without contacting the provider.")
|
|
498
|
-
)
|
|
499
|
-
},
|
|
500
|
-
handleMtaOpenDataQuery
|
|
501
|
-
).pipe(Command.withShortDescription("Run an MTA Open Data SODA3 query."))
|
|
502
|
-
|
|
503
|
-
const mtaOpenDataDatasetCommand = Command.make("dataset").pipe(
|
|
504
|
-
Command.withShortDescription("MTA Open Data dataset commands."),
|
|
505
|
-
Command.withSubcommands([
|
|
506
|
-
mtaOpenDataDatasetListCommand,
|
|
507
|
-
mtaOpenDataDatasetInfoCommand,
|
|
508
|
-
mtaOpenDataDatasetQueryCommand
|
|
509
|
-
])
|
|
510
|
-
)
|
|
511
|
-
|
|
512
|
-
const mtaOpenDataCommand = Command.make("open-data").pipe(
|
|
513
|
-
Command.withShortDescription("MTA Open Data commands."),
|
|
514
|
-
Command.withSubcommands([mtaOpenDataDatasetCommand])
|
|
515
|
-
)
|
|
516
|
-
|
|
517
|
-
const mtaGtfsStaticListCommand = Command.make("list", {}, handleMtaGtfsStaticList).pipe(
|
|
518
|
-
Command.withShortDescription("List known MTA GTFS static feed descriptors.")
|
|
519
|
-
)
|
|
520
|
-
|
|
521
|
-
const mtaGtfsStaticProbeCommand = Command.make(
|
|
522
|
-
"probe",
|
|
523
|
-
{
|
|
524
|
-
feed: optionalFeedFlag,
|
|
525
|
-
url: optionalUrlFlag,
|
|
526
|
-
dryRun: Flag.boolean("dry-run").pipe(
|
|
527
|
-
Flag.withDescription("Print the planned request without contacting the provider.")
|
|
528
|
-
)
|
|
529
|
-
},
|
|
530
|
-
handleMtaGtfsStaticProbe
|
|
531
|
-
).pipe(Command.withShortDescription("Probe an MTA GTFS static feed."))
|
|
532
|
-
|
|
533
|
-
const mtaGtfsStaticFetchCommand = Command.make(
|
|
534
|
-
"fetch",
|
|
535
|
-
{
|
|
536
|
-
feed: optionalFeedFlag,
|
|
537
|
-
url: optionalUrlFlag,
|
|
538
|
-
output: Flag.string("output").pipe(
|
|
539
|
-
Flag.withDescription("Destination file path for the downloaded response body.")
|
|
540
|
-
),
|
|
541
|
-
dryRun: Flag.boolean("dry-run").pipe(
|
|
542
|
-
Flag.withDescription("Print the planned request without contacting the provider.")
|
|
543
|
-
)
|
|
544
|
-
},
|
|
545
|
-
handleMtaGtfsStaticFetch
|
|
546
|
-
).pipe(Command.withShortDescription("Fetch an MTA GTFS static file."))
|
|
547
|
-
|
|
548
|
-
const mtaGtfsStaticCommand = Command.make("gtfs-static").pipe(
|
|
549
|
-
Command.withShortDescription("MTA GTFS static commands."),
|
|
550
|
-
Command.withSubcommands([
|
|
551
|
-
mtaGtfsStaticListCommand,
|
|
552
|
-
mtaGtfsStaticProbeCommand,
|
|
553
|
-
mtaGtfsStaticFetchCommand
|
|
554
|
-
])
|
|
555
|
-
)
|
|
556
|
-
|
|
557
|
-
const mtaGtfsRealtimeListCommand = Command.make("list", {}, handleMtaGtfsRealtimeList).pipe(
|
|
558
|
-
Command.withShortDescription("List known MTA GTFS realtime feed descriptors.")
|
|
559
|
-
)
|
|
560
|
-
|
|
561
|
-
const mtaGtfsRealtimeProbeCommand = Command.make(
|
|
562
|
-
"probe",
|
|
563
|
-
{
|
|
564
|
-
feed: Flag.string("feed").pipe(Flag.withDescription(feedFlagDescription)),
|
|
565
|
-
url: optionalUrlFlag,
|
|
566
|
-
dryRun: Flag.boolean("dry-run").pipe(
|
|
567
|
-
Flag.withDescription("Print the planned request without contacting the provider.")
|
|
568
|
-
)
|
|
569
|
-
},
|
|
570
|
-
handleMtaGtfsRealtimeProbe
|
|
571
|
-
).pipe(Command.withShortDescription("Probe an MTA GTFS realtime feed."))
|
|
572
|
-
|
|
573
|
-
const mtaGtfsRealtimeDecodeCommand = Command.make(
|
|
574
|
-
"decode",
|
|
575
|
-
{
|
|
576
|
-
feed: Flag.string("feed").pipe(Flag.withDescription(feedFlagDescription)),
|
|
577
|
-
url: optionalUrlFlag,
|
|
578
|
-
dryRun: Flag.boolean("dry-run").pipe(
|
|
579
|
-
Flag.withDescription("Print the planned request without contacting the provider.")
|
|
580
|
-
)
|
|
581
|
-
},
|
|
582
|
-
handleMtaGtfsRealtimeDecode
|
|
583
|
-
).pipe(Command.withShortDescription("Fetch and decode an MTA GTFS realtime feed."))
|
|
584
|
-
|
|
585
|
-
const mtaGtfsRealtimeCaptureCommand = Command.make(
|
|
586
|
-
"capture",
|
|
587
|
-
{
|
|
588
|
-
feed: Flag.string("feed").pipe(Flag.withDescription(feedFlagDescription)),
|
|
589
|
-
url: optionalUrlFlag,
|
|
590
|
-
output: Flag.string("output").pipe(
|
|
591
|
-
Flag.withDescription("Destination file path for the raw GTFS realtime protobuf bytes.")
|
|
592
|
-
),
|
|
593
|
-
manifestOutput: Flag.string("manifest-output").pipe(
|
|
594
|
-
Flag.withDescription("Destination file path for the capture manifest JSON.")
|
|
595
|
-
),
|
|
596
|
-
dryRun: Flag.boolean("dry-run").pipe(
|
|
597
|
-
Flag.withDescription("Print the planned request without contacting the provider.")
|
|
598
|
-
)
|
|
599
|
-
},
|
|
600
|
-
handleMtaGtfsRealtimeCapture
|
|
601
|
-
).pipe(Command.withShortDescription("Capture raw MTA GTFS realtime bytes and a manifest."))
|
|
602
|
-
|
|
603
|
-
const mtaGtfsRealtimeCommand = Command.make("gtfs-rt").pipe(
|
|
604
|
-
Command.withShortDescription("MTA GTFS realtime commands."),
|
|
605
|
-
Command.withSubcommands([
|
|
606
|
-
mtaGtfsRealtimeListCommand,
|
|
607
|
-
mtaGtfsRealtimeProbeCommand,
|
|
608
|
-
mtaGtfsRealtimeDecodeCommand,
|
|
609
|
-
mtaGtfsRealtimeCaptureCommand
|
|
610
|
-
])
|
|
611
|
-
)
|
|
612
|
-
|
|
613
|
-
export const mtaCommand = Command.make("mta").pipe(
|
|
614
|
-
Command.withShortDescription("MTA data commands."),
|
|
615
|
-
Command.withSubcommands([mtaOpenDataCommand, mtaGtfsStaticCommand, mtaGtfsRealtimeCommand])
|
|
616
|
-
)
|
|
72
|
+
const feedFlagDescription = "Known feed id/name. Use --url with vehicle-positions, trip-updates, or alerts for manual URLs."
|
|
73
|
+
const optionalFeedFlag = Flag.optional(Flag.string("feed").pipe(Flag.withDescription("Known feed id/name.")))
|
|
74
|
+
const optionalUrlFlag = Flag.optional(Flag.string("url").pipe(Flag.withDescription("Manual provider URL override.")))
|
|
75
|
+
const realtimeFeedFlag = Flag.string("feed").pipe(Flag.withDescription(feedFlagDescription))
|
|
76
|
+
const realtimeRequestCommand = (name: "probe" | "decode", description: string, handler: (config: { readonly feed: string; readonly url: Option.Option<string>; readonly dryRun: boolean }) => Effect.Effect<void, never, CliCommandContext>) =>
|
|
77
|
+
Command.make(name, { feed: realtimeFeedFlag, url: optionalUrlFlag, dryRun: dryRunFlag }, handler).pipe(Command.withShortDescription(description))
|
|
78
|
+
|
|
79
|
+
const mtaOpenDataDatasetCommands = makeDatasetCommands({
|
|
80
|
+
family: "mta", tokens: ["mta", "open-data", "dataset"], idConfigKey: "datasetId", idFlagName: "dataset", idFlagDescription: "MTA Open Data Socrata dataset id.", domain: mtaOpenDataDomain,
|
|
81
|
+
listShortDescription: "List curated MTA Open Data descriptors.", infoShortDescription: "Show MTA Open Data dataset metadata.", queryShortDescription: "Run an MTA Open Data SODA3 query.",
|
|
82
|
+
listPayload: () => ({ datasets: mtaOpenDataDatasets }),
|
|
83
|
+
infoPayload: (datasetId) => { const descriptor = findMtaOpenDataDataset(datasetId); return { payload: { known: descriptor !== undefined, descriptor: descriptor === undefined ? { id: datasetId, domain: mtaOpenDataDomain, backing: "socrata" } : descriptor } } },
|
|
84
|
+
resolveDataset: (datasetId) => ({ payload: { datasetId, extra: { known: findMtaOpenDataDataset(datasetId) !== undefined } } }),
|
|
85
|
+
query: ({ id, query }) => queryMtaOpenData({ datasetId: id, query })
|
|
86
|
+
})
|
|
87
|
+
const mtaOpenDataDatasetCommand = Command.make("dataset").pipe(Command.withShortDescription("MTA Open Data dataset commands."), Command.withSubcommands(mtaOpenDataDatasetCommands))
|
|
88
|
+
const mtaOpenDataCommand = Command.make("open-data").pipe(Command.withShortDescription("MTA Open Data commands."), Command.withSubcommands([mtaOpenDataDatasetCommand]))
|
|
89
|
+
const mtaGtfsStaticListCommand = Command.make("list", {}, feedListHandler(mtaGtfsStaticFeeds)).pipe(Command.withShortDescription("List known MTA GTFS static feed descriptors."))
|
|
90
|
+
const mtaGtfsStaticProbeCommand = Command.make("probe", { feed: optionalFeedFlag, url: optionalUrlFlag, dryRun: dryRunFlag }, handleMtaGtfsStaticProbe).pipe(Command.withShortDescription("Probe an MTA GTFS static feed."))
|
|
91
|
+
const mtaGtfsStaticFetchCommand = Command.make("fetch", { feed: optionalFeedFlag, url: optionalUrlFlag, output: outputFlag, dryRun: dryRunFlag }, handleMtaGtfsStaticFetch).pipe(Command.withShortDescription("Fetch an MTA GTFS static file."))
|
|
92
|
+
const mtaGtfsStaticCommand = Command.make("gtfs-static").pipe(Command.withShortDescription("MTA GTFS static commands."), Command.withSubcommands([mtaGtfsStaticListCommand, mtaGtfsStaticProbeCommand, mtaGtfsStaticFetchCommand]))
|
|
93
|
+
const mtaGtfsRealtimeListCommand = Command.make("list", {}, feedListHandler(mtaGtfsRealtimeFeeds)).pipe(Command.withShortDescription("List known MTA GTFS realtime feed descriptors."))
|
|
94
|
+
const mtaGtfsRealtimeProbeCommand = realtimeRequestCommand("probe", "Probe an MTA GTFS realtime feed.", handleMtaGtfsRealtimeProbe)
|
|
95
|
+
const mtaGtfsRealtimeDecodeCommand = realtimeRequestCommand("decode", "Fetch and decode an MTA GTFS realtime feed.", handleMtaGtfsRealtimeDecode)
|
|
96
|
+
const mtaGtfsRealtimeCaptureCommand = Command.make("capture", { feed: realtimeFeedFlag, url: optionalUrlFlag, output: Flag.string("output").pipe(Flag.withDescription("Destination file path for the raw GTFS realtime protobuf bytes.")), manifestOutput: Flag.string("manifest-output").pipe(Flag.withDescription("Destination file path for the capture manifest JSON.")), dryRun: dryRunFlag }, handleMtaGtfsRealtimeCapture).pipe(Command.withShortDescription("Capture raw MTA GTFS realtime bytes and a manifest."))
|
|
97
|
+
const mtaGtfsRealtimeCommand = Command.make("gtfs-rt").pipe(Command.withShortDescription("MTA GTFS realtime commands."), Command.withSubcommands([mtaGtfsRealtimeListCommand, mtaGtfsRealtimeProbeCommand, mtaGtfsRealtimeDecodeCommand, mtaGtfsRealtimeCaptureCommand]))
|
|
98
|
+
export const mtaCommand = Command.make("mta").pipe(Command.withShortDescription("MTA data commands."), Command.withSubcommands([mtaOpenDataCommand, mtaGtfsStaticCommand, mtaGtfsRealtimeCommand]))
|