dirigent-examples 0.15.0__py3-none-any.whl
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.
- dirigent_examples/__init__.py +22 -0
- dirigent_examples/py.typed +0 -0
- dirigent_examples/shelves/README.md +299 -0
- dirigent_examples/shelves/composition/README.md +18 -0
- dirigent_examples/shelves/composition/chained-instances.yaml +97 -0
- dirigent_examples/shelves/composition/composition-child.yaml +64 -0
- dirigent_examples/shelves/composition/composition-parent.yaml +89 -0
- dirigent_examples/shelves/connections.yaml +52 -0
- dirigent_examples/shelves/demo/README.md +19 -0
- dirigent_examples/shelves/demo/markdown-showcase.yaml +117 -0
- dirigent_examples/shelves/demo/params-showcase.yaml +92 -0
- dirigent_examples/shelves/demo/requires.yaml +65 -0
- dirigent_examples/shelves/demo/weekly-import-malawi.yaml +54 -0
- dirigent_examples/shelves/demo/weekly-import-nepal.yaml +75 -0
- dirigent_examples/shelves/docker/README.md +29 -0
- dirigent_examples/shelves/docker/docker-build-push.yaml +110 -0
- dirigent_examples/shelves/docker/docker-build-run.yaml +106 -0
- dirigent_examples/shelves/docker/docker-compose-database.yaml +124 -0
- dirigent_examples/shelves/docker/docker-compose-failing-up.yaml +83 -0
- dirigent_examples/shelves/docker/docker-compose-file.yaml +117 -0
- dirigent_examples/shelves/docker/docker-compose-profiles-env.yaml +133 -0
- dirigent_examples/shelves/docker/docker-compose-stack.yaml +70 -0
- dirigent_examples/shelves/docker/docker-hello.yaml +53 -0
- dirigent_examples/shelves/docker/docker-remote-daemon.yaml +92 -0
- dirigent_examples/shelves/docker/docker-run-failing-teardown.yaml +94 -0
- dirigent_examples/shelves/docker/docker-ticker.yaml +49 -0
- dirigent_examples/shelves/execute/README.md +16 -0
- dirigent_examples/shelves/execute/long-log.yaml +89 -0
- dirigent_examples/shelves/failure/README.md +20 -0
- dirigent_examples/shelves/failure/error-handler.yaml +89 -0
- dirigent_examples/shelves/failure/optional-step.yaml +82 -0
- dirigent_examples/shelves/failure/retries.yaml +75 -0
- dirigent_examples/shelves/failure/retry-budget.yaml +82 -0
- dirigent_examples/shelves/failure/step-timeout.yaml +96 -0
- dirigent_examples/shelves/git/README.md +32 -0
- dirigent_examples/shelves/git/git-checkout-build.yaml +125 -0
- dirigent_examples/shelves/git/git-checkout-compose.yaml +138 -0
- dirigent_examples/shelves/git/git-checkout-public.yaml +84 -0
- dirigent_examples/shelves/graph/README.md +22 -0
- dirigent_examples/shelves/graph/deep-chain.yaml +119 -0
- dirigent_examples/shelves/graph/fan-in.yaml +80 -0
- dirigent_examples/shelves/graph/fan-out.yaml +66 -0
- dirigent_examples/shelves/graph/linear.yaml +66 -0
- dirigent_examples/shelves/graph/parallel-branches.yaml +57 -0
- dirigent_examples/shelves/graph/parallel-sleep.yaml +55 -0
- dirigent_examples/shelves/graph/skip-diamond.yaml +105 -0
- dirigent_examples/shelves/graph/wide-fan.yaml +147 -0
- dirigent_examples/shelves/hello-world.yaml +30 -0
- dirigent_examples/shelves/open-data/README.md +67 -0
- dirigent_examples/shelves/open-data/feeds-composition.yaml +120 -0
- dirigent_examples/shelves/open-data/gdacs-disaster-updates.yaml +230 -0
- dirigent_examples/shelves/open-data/github-releases-relay.yaml +225 -0
- dirigent_examples/shelves/open-data/hdx-dataset-watch.yaml +211 -0
- dirigent_examples/shelves/open-data/kobo-submissions-to-csv.yaml +149 -0
- dirigent_examples/shelves/open-data/nominatim-geocode-facilities.yaml +169 -0
- dirigent_examples/shelves/open-data/odk-central-submissions.yaml +146 -0
- dirigent_examples/shelves/open-data/open-meteo-weekly-report.yaml +142 -0
- dirigent_examples/shelves/open-data/overpass-health-facilities.yaml +154 -0
- dirigent_examples/shelves/open-data/usgs-earthquakes-alert.yaml +208 -0
- dirigent_examples/shelves/open-data/who-gho-indicators-to-parquet.yaml +163 -0
- dirigent_examples/shelves/open-data/wikidata-country-reference.yaml +146 -0
- dirigent_examples/shelves/open-data/world-bank-population-trend.yaml +166 -0
- dirigent_examples/shelves/patterns/README.md +144 -0
- dirigent_examples/shelves/patterns/concurrency-queue.yaml +89 -0
- dirigent_examples/shelves/patterns/concurrency-replace.yaml +92 -0
- dirigent_examples/shelves/patterns/concurrency-skip.yaml +97 -0
- dirigent_examples/shelves/patterns/connections-referenced-vs-carried.yaml +140 -0
- dirigent_examples/shelves/patterns/deadline-on-a-sensor.yaml +106 -0
- dirigent_examples/shelves/patterns/fan-out-continue.yaml +88 -0
- dirigent_examples/shelves/patterns/fan-out-fail-fast.yaml +80 -0
- dirigent_examples/shelves/patterns/fan-out-from-params.yaml +84 -0
- dirigent_examples/shelves/patterns/fan-out-item-wise.yaml +111 -0
- dirigent_examples/shelves/patterns/fan-out-literal-list.yaml +81 -0
- dirigent_examples/shelves/patterns/fan-out-nested-objects.yaml +107 -0
- dirigent_examples/shelves/patterns/fan-out-then-join.yaml +86 -0
- dirigent_examples/shelves/patterns/log-levels.yaml +119 -0
- dirigent_examples/shelves/patterns/outputs-inline-vs-storage.yaml +140 -0
- dirigent_examples/shelves/patterns/params-every-type.yaml +259 -0
- dirigent_examples/shelves/patterns/params-validation-refuses.yaml +131 -0
- dirigent_examples/shelves/patterns/pipeline-run-child.yaml +96 -0
- dirigent_examples/shelves/patterns/pipeline-run-fire-and-forget.yaml +99 -0
- dirigent_examples/shelves/patterns/pipeline-run-strict.yaml +103 -0
- dirigent_examples/shelves/patterns/pipeline-run-wait.yaml +107 -0
- dirigent_examples/shelves/patterns/pipeline-run-with-params.yaml +124 -0
- dirigent_examples/shelves/patterns/poll-cadence.yaml +102 -0
- dirigent_examples/shelves/patterns/priority-layered.yaml +120 -0
- dirigent_examples/shelves/patterns/references-cheat-sheet.yaml +186 -0
- dirigent_examples/shelves/patterns/retry-budget-exhausted.yaml +92 -0
- dirigent_examples/shelves/patterns/retry-exponential-backoff.yaml +88 -0
- dirigent_examples/shelves/patterns/retry-only-transient.yaml +108 -0
- dirigent_examples/shelves/patterns/retry-with-jitter.yaml +102 -0
- dirigent_examples/shelves/patterns/rule-all-done.yaml +83 -0
- dirigent_examples/shelves/patterns/rule-all-success.yaml +79 -0
- dirigent_examples/shelves/patterns/rule-always.yaml +92 -0
- dirigent_examples/shelves/patterns/rule-one-failed.yaml +86 -0
- dirigent_examples/shelves/patterns/schedule-at-once.yaml +110 -0
- dirigent_examples/shelves/patterns/schedule-cron-timezone.yaml +121 -0
- dirigent_examples/shelves/patterns/schedule-interval.yaml +109 -0
- dirigent_examples/shelves/patterns/schedule-window-half-open.yaml +105 -0
- dirigent_examples/shelves/patterns/sensor-http-ready.yaml +121 -0
- dirigent_examples/shelves/patterns/sensor-storage-exists.yaml +134 -0
- dirigent_examples/shelves/patterns/step-names-and-keys.yaml +99 -0
- dirigent_examples/shelves/patterns/timeout-fails-the-step.yaml +94 -0
- dirigent_examples/shelves/patterns/timeout-skips-the-step.yaml +102 -0
- dirigent_examples/shelves/patterns/webhook-mapping-nested-payload.yaml +125 -0
- dirigent_examples/shelves/patterns/webhook-signed.yaml +144 -0
- dirigent_examples/shelves/preview/s3-parquet-to-ingestion.yaml +92 -0
- dirigent_examples/shelves/python/README.md +31 -0
- dirigent_examples/shelves/python/apply_and_run.py +52 -0
- dirigent_examples/shelves/python/ci_gate.py +76 -0
- dirigent_examples/shelves/python/connections.py +61 -0
- dirigent_examples/shelves/python/error_handling.py +84 -0
- dirigent_examples/shelves/python/follow_logs.py +39 -0
- dirigent_examples/shelves/python/list_and_filter.py +52 -0
- dirigent_examples/shelves/queues/README.md +59 -0
- dirigent_examples/shelves/queues/kafka-consume-then-transform.yaml +105 -0
- dirigent_examples/shelves/queues/kafka-produce-then-consume.yaml +124 -0
- dirigent_examples/shelves/queues/rabbitmq-consume-ack-on-success.yaml +102 -0
- dirigent_examples/shelves/queues/report-to-kafka.yaml +105 -0
- dirigent_examples/shelves/queues/report-to-rabbitmq.yaml +105 -0
- dirigent_examples/shelves/recipes/README.md +130 -0
- dirigent_examples/shelves/recipes/csv-header-rules.yaml +147 -0
- dirigent_examples/shelves/recipes/csv-to-ndjson.yaml +107 -0
- dirigent_examples/shelves/recipes/etl-csv-clean-validate-parquet.yaml +207 -0
- dirigent_examples/shelves/recipes/filter-by-predicate.yaml +116 -0
- dirigent_examples/shelves/recipes/filter-then-map-then-reduce.yaml +109 -0
- dirigent_examples/shelves/recipes/http-fetch-validate-post.yaml +142 -0
- dirigent_examples/shelves/recipes/http-follow-redirects.yaml +100 -0
- dirigent_examples/shelves/recipes/http-get-with-query.yaml +96 -0
- dirigent_examples/shelves/recipes/http-headers-and-auth-connection.yaml +110 -0
- dirigent_examples/shelves/recipes/http-post-file-from-storage.yaml +117 -0
- dirigent_examples/shelves/recipes/http-post-json-echo.yaml +105 -0
- dirigent_examples/shelves/recipes/http-post-report.yaml +183 -0
- dirigent_examples/shelves/recipes/http-save-body-to-storage.yaml +106 -0
- dirigent_examples/shelves/recipes/http-success-status-list.yaml +80 -0
- dirigent_examples/shelves/recipes/http-timeout-override.yaml +104 -0
- dirigent_examples/shelves/recipes/jq-dedupe-by-key.yaml +78 -0
- dirigent_examples/shelves/recipes/jq-defaults-and-nulls.yaml +91 -0
- dirigent_examples/shelves/recipes/jq-group-by-and-sum.yaml +74 -0
- dirigent_examples/shelves/recipes/jq-join-two-lists.yaml +77 -0
- dirigent_examples/shelves/recipes/jq-long-to-wide.yaml +76 -0
- dirigent_examples/shelves/recipes/jq-nested-to-flat.yaml +89 -0
- dirigent_examples/shelves/recipes/jq-pivot-wide-to-long.yaml +65 -0
- dirigent_examples/shelves/recipes/jq-running-totals.yaml +82 -0
- dirigent_examples/shelves/recipes/jq-string-cleaning.yaml +88 -0
- dirigent_examples/shelves/recipes/jq-top-n.yaml +85 -0
- dirigent_examples/shelves/recipes/jq-validate-in-jq-vs-schema.yaml +124 -0
- dirigent_examples/shelves/recipes/jq-window-dates.yaml +82 -0
- dirigent_examples/shelves/recipes/json-to-csv-flattening.yaml +141 -0
- dirigent_examples/shelves/recipes/large-output-to-storage.yaml +134 -0
- dirigent_examples/shelves/recipes/map-enrich-with-lookup.yaml +96 -0
- dirigent_examples/shelves/recipes/ndjson-to-parquet.yaml +130 -0
- dirigent_examples/shelves/recipes/pagination-by-fan-out.yaml +115 -0
- dirigent_examples/shelves/recipes/parquet-round-trip-types.yaml +163 -0
- dirigent_examples/shelves/recipes/reconcile-two-sources.yaml +159 -0
- dirigent_examples/shelves/recipes/report-built-in.yaml +72 -0
- dirigent_examples/shelves/recipes/report-daily-digest.yaml +186 -0
- dirigent_examples/shelves/recipes/report-to-file.yaml +131 -0
- dirigent_examples/shelves/recipes/report-to-webhook.yaml +136 -0
- dirigent_examples/shelves/recipes/schema-carried.yaml +112 -0
- dirigent_examples/shelves/recipes/schema-formats.yaml +107 -0
- dirigent_examples/shelves/recipes/schema-referenced.yaml +86 -0
- dirigent_examples/shelves/recipes/schema-refuses-then-rule.yaml +127 -0
- dirigent_examples/shelves/recipes/storage-copy-dated-archive.yaml +114 -0
- dirigent_examples/shelves/recipes/storage-exists-gate.yaml +127 -0
- dirigent_examples/shelves/recipes/storage-manifest-of-a-fan-out.yaml +104 -0
- dirigent_examples/shelves/recipes/storage-write-then-read.yaml +119 -0
- dirigent_examples/shelves/recipes/webhook-post-hmac.yaml +132 -0
- dirigent_examples/shelves/recipes/webhook-post-summary.yaml +142 -0
- dirigent_examples/shelves/s3/README.md +34 -0
- dirigent_examples/shelves/s3/report-to-s3.yaml +93 -0
- dirigent_examples/shelves/s3/s3-copy-and-verify.yaml +105 -0
- dirigent_examples/shelves/s3/s3-csv-report.yaml +87 -0
- dirigent_examples/shelves/s3/s3-parquet-report.yaml +77 -0
- dirigent_examples/shelves/s3/s3-round-trip.yaml +125 -0
- dirigent_examples/shelves/schemas/README.md +36 -0
- dirigent_examples/shelves/schemas/echo-reading.json +18 -0
- dirigent_examples/shelves/schemas/ou-record.json +13 -0
- dirigent_examples/shelves/schemas/station-reading.json +13 -0
- dirigent_examples/shelves/sensors/README.md +16 -0
- dirigent_examples/shelves/sensors/sensor-gate.yaml +65 -0
- dirigent_examples/shelves/sensors/time-window.yaml +61 -0
- dirigent_examples/shelves/sql/README.md +52 -0
- dirigent_examples/shelves/sql/duckdb-parquet-to-report.yaml +146 -0
- dirigent_examples/shelves/sql/sql-postgres-readonly.yaml +111 -0
- dirigent_examples/shelves/sql/sql-query-to-storage.yaml +85 -0
- dirigent_examples/shelves/sql/sql-sqlite-roundtrip.yaml +114 -0
- dirigent_examples/shelves/sql/warehouse.sql +42 -0
- dirigent_examples/shelves/transform/README.md +36 -0
- dirigent_examples/shelves/transform/csv-report.yaml +55 -0
- dirigent_examples/shelves/transform/jq-filter-and-map.yaml +70 -0
- dirigent_examples/shelves/transform/jq-group-and-aggregate.yaml +70 -0
- dirigent_examples/shelves/transform/jq-join-two-sources.yaml +98 -0
- dirigent_examples/shelves/transform/jq-reshape.yaml +91 -0
- dirigent_examples/shelves/transform/jq-stream-through-storage.yaml +112 -0
- dirigent_examples/shelves/transform/ndjson-round-trip.yaml +56 -0
- dirigent_examples/shelves/transform/parquet-round-trip.yaml +68 -0
- dirigent_examples/shelves/transform/std-convert-fan-out.yaml +142 -0
- dirigent_examples/shelves/transform/xml-feed-to-ndjson.yaml +116 -0
- dirigent_examples/shelves/transform/yaml-config-to-json.yaml +104 -0
- dirigent_examples/shelves/triggers/README.md +45 -0
- dirigent_examples/shelves/triggers/at-one-time.yaml +78 -0
- dirigent_examples/shelves/triggers/cron-nightly.yaml +79 -0
- dirigent_examples/shelves/triggers/cron-windowed.yaml +86 -0
- dirigent_examples/shelves/triggers/document-nightly.yaml +80 -0
- dirigent_examples/shelves/triggers/interval-rolling.yaml +88 -0
- dirigent_examples/shelves/triggers/managed-and-manual.yaml +109 -0
- dirigent_examples/shelves/triggers/webhook-trigger.yaml +75 -0
- dirigent_examples/shelves/validate/README.md +31 -0
- dirigent_examples/shelves/validate/expects-a-shape.yaml +56 -0
- dirigent_examples/shelves/validate/the-shape-is-wrong.yaml +46 -0
- dirigent_examples-0.15.0.dist-info/METADATA +21 -0
- dirigent_examples-0.15.0.dist-info/RECORD +216 -0
- dirigent_examples-0.15.0.dist-info/WHEEL +4 -0
- dirigent_examples-0.15.0.dist-info/entry_points.txt +3 -0
- dirigent_examples-0.15.0.dist-info/licenses/LICENSE +18 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# storage.exists: waiting for an object, with a glob, a size floor, and a drop that arrives.
|
|
2
|
+
#
|
|
3
|
+
# deadline-on-a-sensor.yaml is this sensor missing its deadline. This one is the sensor doing
|
|
4
|
+
# its job: something writes the object while the run is going, the sensor sees it, and the
|
|
5
|
+
# pipeline carries on. Everything below is offline apart from one echo call, and the drop is
|
|
6
|
+
# written by the run itself so there is nothing to arrange.
|
|
7
|
+
#
|
|
8
|
+
# THE TWO CONFIG FIELDS, and both earn their place:
|
|
9
|
+
#
|
|
10
|
+
# uri may contain a GLOB in its final segments. Waiting for
|
|
11
|
+
# drops/2026-01-01/*.json is how a pipeline waits for "whatever the producer
|
|
12
|
+
# called it today" instead of demanding a name nobody agreed to. The sensor's
|
|
13
|
+
# output says which object actually matched, which is why every step downstream
|
|
14
|
+
# reads ${steps.<sensor>.output.uri} rather than rebuilding the pattern.
|
|
15
|
+
# min_size ignores an object until it is at least this large. A producer uploading a large
|
|
16
|
+
# file makes the key visible before the bytes are all there, so without a floor
|
|
17
|
+
# the sensor fires on a half-written object and the load reads a truncated file.
|
|
18
|
+
# This is the single most common way a drop-watching pipeline goes wrong.
|
|
19
|
+
#
|
|
20
|
+
# The output carries uri, size and modified_at -- what landed, how big it is, and when it was
|
|
21
|
+
# last written -- which is everything a downstream step needs to decide whether to trust it.
|
|
22
|
+
#
|
|
23
|
+
# Hop by hop:
|
|
24
|
+
#
|
|
25
|
+
# producer fetches the payload the drop is made of. It stands in for the upstream system.
|
|
26
|
+
# drop writes that payload to a URI under this run's scratch, which is the moment the
|
|
27
|
+
# sensor is waiting for: storage.write is the only way a value leaves a run.
|
|
28
|
+
# dropped the sensor. It matches on a glob, insists on a minimum size, and fires as soon
|
|
29
|
+
# as the object is there.
|
|
30
|
+
# archive copies what landed to its resting place, reading the sensor's answer rather than
|
|
31
|
+
# the pattern.
|
|
32
|
+
# receipt reports what was seen: the object, its size, and when it was written.
|
|
33
|
+
#
|
|
34
|
+
# WHERE A URI MAY POINT. ${run.scratch} is this run's own directory under the instance's
|
|
35
|
+
# artifact root, and file:// URIs are refused outside that root -- a stored pipeline cannot be
|
|
36
|
+
# turned into an arbitrary-file read. A real drop is an s3:// URI, or a path under that root;
|
|
37
|
+
# never an absolute path typed into a document.
|
|
38
|
+
#
|
|
39
|
+
# EXPECT THIS RUN TO SUCCEED in about two seconds, with the sensor firing on its first poke.
|
|
40
|
+
#
|
|
41
|
+
# To change it: raise -p min_size and the object is never big enough, so the sensor misses its
|
|
42
|
+
# deadline and the run fails -- which is the same shape deadline-on-a-sensor.yaml shows
|
|
43
|
+
# deliberately.
|
|
44
|
+
#
|
|
45
|
+
# dg run --local examples/patterns/sensor-storage-exists.yaml
|
|
46
|
+
# dg run --local examples/patterns/sensor-storage-exists.yaml -p min_size=10mb # fails at the deadline
|
|
47
|
+
|
|
48
|
+
format: dirigent/v1
|
|
49
|
+
kind: pipeline
|
|
50
|
+
code: sensor-storage-exists
|
|
51
|
+
name: Waiting for a drop
|
|
52
|
+
description: |
|
|
53
|
+
`storage.exists` waits for an object at a URI, which may end in a **glob**, and ignores it
|
|
54
|
+
until it is at least `min_size` -- the guard against opening a file a producer is still
|
|
55
|
+
uploading.
|
|
56
|
+
|
|
57
|
+
Its output says which object matched, how big it is and when it was written, so downstream
|
|
58
|
+
reads the answer rather than rebuilding the pattern.
|
|
59
|
+
|
|
60
|
+
tags: [patterns, http, sensor, storage, transform]
|
|
61
|
+
|
|
62
|
+
requires:
|
|
63
|
+
blocks:
|
|
64
|
+
- http.request
|
|
65
|
+
- storage.write
|
|
66
|
+
- storage.exists
|
|
67
|
+
- storage.copy
|
|
68
|
+
- transform.jq
|
|
69
|
+
|
|
70
|
+
params:
|
|
71
|
+
type: object
|
|
72
|
+
properties:
|
|
73
|
+
day:
|
|
74
|
+
type: string
|
|
75
|
+
format: date
|
|
76
|
+
description: The day the drop belongs to; it is a directory in the URI.
|
|
77
|
+
default: "2026-01-01"
|
|
78
|
+
min_size:
|
|
79
|
+
type: string
|
|
80
|
+
description: The floor the object has to clear. A humane size, so 1kb and 10mb both read as sizes.
|
|
81
|
+
default: 100b
|
|
82
|
+
|
|
83
|
+
steps:
|
|
84
|
+
producer:
|
|
85
|
+
block: http.request
|
|
86
|
+
# Stands in for the upstream system: it only fetches, and what it fetches is a value in
|
|
87
|
+
# its output like any other.
|
|
88
|
+
config:
|
|
89
|
+
url: https://postman-echo.com/get
|
|
90
|
+
query:
|
|
91
|
+
day: "${params.day}"
|
|
92
|
+
|
|
93
|
+
drop:
|
|
94
|
+
block: storage.write
|
|
95
|
+
depends_on: [producer]
|
|
96
|
+
# The write is what the sensor below is waiting for. The producer names the file, which
|
|
97
|
+
# is why the sensor matches a pattern rather than this exact target.
|
|
98
|
+
config:
|
|
99
|
+
target: "${run.scratch}/drops/${params.day}/export-0001.json"
|
|
100
|
+
value: "${steps.producer.output.body}"
|
|
101
|
+
|
|
102
|
+
dropped:
|
|
103
|
+
block: storage.exists
|
|
104
|
+
depends_on: [drop]
|
|
105
|
+
poll: 1s
|
|
106
|
+
deadline: 15s
|
|
107
|
+
config:
|
|
108
|
+
# A glob in the final segment: the producer names the file, and the pipeline does not
|
|
109
|
+
# have to know what it will be called.
|
|
110
|
+
uri: "${run.scratch}/drops/${params.day}/*.json"
|
|
111
|
+
# A configured size is humane and carries no _bytes suffix: the type says the unit.
|
|
112
|
+
min_size: "${params.min_size}"
|
|
113
|
+
|
|
114
|
+
archive:
|
|
115
|
+
block: storage.copy
|
|
116
|
+
depends_on: [dropped]
|
|
117
|
+
config:
|
|
118
|
+
# The sensor's answer, not the pattern. A glob may have matched anything; only the
|
|
119
|
+
# sensor knows what it actually matched.
|
|
120
|
+
source: "${steps.dropped.output.uri}"
|
|
121
|
+
target: "${run.scratch}/archive/${params.day}.json"
|
|
122
|
+
|
|
123
|
+
receipt:
|
|
124
|
+
block: transform.jq
|
|
125
|
+
depends_on: [archive]
|
|
126
|
+
config:
|
|
127
|
+
input:
|
|
128
|
+
landed: "${steps.dropped.output.uri}"
|
|
129
|
+
size: "${steps.dropped.output.size}"
|
|
130
|
+
modified_at: "${steps.dropped.output.modified_at}"
|
|
131
|
+
archived: "${steps.archive.output.target}"
|
|
132
|
+
copied_bytes: "${steps.archive.output.bytes_copied}"
|
|
133
|
+
program: |
|
|
134
|
+
{landed, size, modified_at, archived, copied_bytes}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# The map key is the identity; name is a title nothing may ever reference.
|
|
2
|
+
#
|
|
3
|
+
# Every addressable thing in dirigent carries the same quartet, and each field has exactly one
|
|
4
|
+
# job:
|
|
5
|
+
#
|
|
6
|
+
# id the uuid a machine holds. Never written in a document.
|
|
7
|
+
# code the addressable key: constrained, unique, and what appears in a URL, in a
|
|
8
|
+
# document, and in every reference. A step's map key IS its code.
|
|
9
|
+
# name an optional human title. Free-form, with no identity semantics at all --
|
|
10
|
+
# nothing may ever be referenced by it, and two things may share one.
|
|
11
|
+
# description long-form and markdown-capable; the body a screen renders. A STEP carries no
|
|
12
|
+
# description -- only name and its key -- because a step's explanation is the
|
|
13
|
+
# comment above it, which travels in the document a person reads.
|
|
14
|
+
#
|
|
15
|
+
# So a step is referenced by its map key and by nothing else: ${steps.load_batch.output.status}
|
|
16
|
+
# names load_batch, and renaming the STEP NAME below to anything at all leaves every reference
|
|
17
|
+
# in this document working. Renaming the KEY breaks them, and breaks them at apply time, which
|
|
18
|
+
# is the trade -- an identifier that is checkable is an identifier that cannot quietly drift.
|
|
19
|
+
#
|
|
20
|
+
# Two steps in this file deliberately share a name. That is legal, because a name identifies
|
|
21
|
+
# nothing; the keys tell them apart. A screen shows the name where there is one and the key
|
|
22
|
+
# otherwise, with the key always on the page in mono and never drawn twice.
|
|
23
|
+
#
|
|
24
|
+
# The same quartet, in the same shape, everywhere: the pipeline's code is its filename here
|
|
25
|
+
# (the corpus test insists on it), a schedule's code addresses it, a webhook's code addresses
|
|
26
|
+
# it, and a connection's code is what a document names instead of carrying a credential.
|
|
27
|
+
#
|
|
28
|
+
# Hop by hop:
|
|
29
|
+
#
|
|
30
|
+
# load_batch a key in snake_case, and a name that reads like a sentence.
|
|
31
|
+
# verify_rows depends on the KEY above, not on its name, and reads its output the same way.
|
|
32
|
+
# publish carries the same name as verify_rows, which nothing anywhere minds.
|
|
33
|
+
#
|
|
34
|
+
# EXPECT THIS RUN TO SUCCEED in about a second, with no network and no allowlist.
|
|
35
|
+
#
|
|
36
|
+
# To change it: edit any name below and re-run -- everything still works. Edit a key and the
|
|
37
|
+
# document stops validating, with the dangling reference named.
|
|
38
|
+
#
|
|
39
|
+
# dg run --local examples/patterns/step-names-and-keys.yaml
|
|
40
|
+
|
|
41
|
+
format: dirigent/v1
|
|
42
|
+
kind: pipeline
|
|
43
|
+
code: step-names-and-keys
|
|
44
|
+
name: Keys address, names describe
|
|
45
|
+
description: |
|
|
46
|
+
A step's map **key** is its code: the only thing anything references, and what
|
|
47
|
+
`${steps.<key>.output...}` names.
|
|
48
|
+
|
|
49
|
+
`name` is a free-form title with no identity semantics -- two steps may share one, and
|
|
50
|
+
renaming it breaks nothing.
|
|
51
|
+
|
|
52
|
+
tags: [patterns, transform, conventions, references]
|
|
53
|
+
|
|
54
|
+
requires:
|
|
55
|
+
blocks:
|
|
56
|
+
- transform.jq
|
|
57
|
+
|
|
58
|
+
params:
|
|
59
|
+
type: object
|
|
60
|
+
properties:
|
|
61
|
+
batch:
|
|
62
|
+
type: string
|
|
63
|
+
description: Which batch this run covers.
|
|
64
|
+
default: 2026-01-01-cases
|
|
65
|
+
|
|
66
|
+
steps:
|
|
67
|
+
load_batch:
|
|
68
|
+
# A title, for a person reading a run. Change it freely: nothing in this document, in the
|
|
69
|
+
# API, or in a URL refers to it.
|
|
70
|
+
name: Load the nightly batch
|
|
71
|
+
block: transform.jq
|
|
72
|
+
config:
|
|
73
|
+
input:
|
|
74
|
+
batch: "${params.batch}"
|
|
75
|
+
program: |
|
|
76
|
+
{batch, rows: 4120}
|
|
77
|
+
|
|
78
|
+
verify_rows:
|
|
79
|
+
name: Check the batch
|
|
80
|
+
block: transform.jq
|
|
81
|
+
# The key, never the name. A dependency on "Load the nightly batch" is not expressible,
|
|
82
|
+
# which is the point.
|
|
83
|
+
depends_on: [load_batch]
|
|
84
|
+
config:
|
|
85
|
+
# Same rule in a reference: steps.<key>.output.<field>.
|
|
86
|
+
input: "${steps.load_batch.output.value}"
|
|
87
|
+
program: |
|
|
88
|
+
{batch, rows, verified: (.rows > 0)}
|
|
89
|
+
|
|
90
|
+
publish:
|
|
91
|
+
# Deliberately the same name as the step above. Two rows may share a name because a name
|
|
92
|
+
# identifies nothing; publish and verify_rows are still two different steps.
|
|
93
|
+
name: Check the batch
|
|
94
|
+
block: transform.jq
|
|
95
|
+
depends_on: [verify_rows]
|
|
96
|
+
config:
|
|
97
|
+
input: "${steps.verify_rows.output.value}"
|
|
98
|
+
program: |
|
|
99
|
+
{published: .batch, rows}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# timeout: how long ONE block call may take, and what an expired one is classified as.
|
|
2
|
+
#
|
|
3
|
+
# Three different clocks can stop a step, and they answer three different questions:
|
|
4
|
+
#
|
|
5
|
+
# timeout (a step field) how long one block call may take. This file.
|
|
6
|
+
# deadline (a step field) how long a step may keep WAITING on a sensor or a
|
|
7
|
+
# remote job. deadline-on-a-sensor.yaml.
|
|
8
|
+
# timeout (block config) the block's own limit on its own work, such as the
|
|
9
|
+
# HTTP client's socket timeout.
|
|
10
|
+
#
|
|
11
|
+
# The two on slow_read below are set deliberately far apart: the endpoint is asked to take ten
|
|
12
|
+
# seconds, the HTTP client would allow thirty, and the engine allows two. So the engine is
|
|
13
|
+
# plainly the clock that fires, and the attempt says so.
|
|
14
|
+
#
|
|
15
|
+
# An expired timeout is a TRANSIENT failure, which matters more than it looks: a retry budget
|
|
16
|
+
# would be spent on it, three attempts against a call that is always too slow costing three
|
|
17
|
+
# full timeouts of waiting. There is no retry policy here for exactly that reason -- a
|
|
18
|
+
# deadline that cannot be met is not a flake. And note what on_timeout does NOT do: it governs
|
|
19
|
+
# an expired deadline, never an expired timeout, so there is no spelling of this step that
|
|
20
|
+
# skips instead of failing. timeout-skips-the-step.yaml is where that distinction lives.
|
|
21
|
+
#
|
|
22
|
+
# Hop by hop:
|
|
23
|
+
#
|
|
24
|
+
# start a local value, so the run has a step that plainly succeeded to compare against.
|
|
25
|
+
# slow_read /delay/10 against a two-second budget. It loses, in about two seconds.
|
|
26
|
+
# parse the default all_success edge, so it is skipped.
|
|
27
|
+
#
|
|
28
|
+
# EXPECT THIS RUN TO FAIL, in about two seconds, with slow_read failed and parse skipped.
|
|
29
|
+
# It fails ON TIME, not on work: the endpoint was healthy and would have answered.
|
|
30
|
+
#
|
|
31
|
+
# To change it: -p seconds=1 finishes inside the budget and the run goes green. Widen the
|
|
32
|
+
# step's timeout past ten and the same happens. Narrow the block's own timeout below two and
|
|
33
|
+
# the HTTP client gives up first -- same red step, different reason, and the attempt's message
|
|
34
|
+
# says which.
|
|
35
|
+
#
|
|
36
|
+
# dg run --local examples/patterns/timeout-fails-the-step.yaml # fails in ~2s
|
|
37
|
+
# dg run --local examples/patterns/timeout-fails-the-step.yaml -p seconds=1 # succeeds
|
|
38
|
+
|
|
39
|
+
format: dirigent/v1
|
|
40
|
+
kind: pipeline
|
|
41
|
+
code: timeout-fails-the-step
|
|
42
|
+
name: A step that runs out of time
|
|
43
|
+
description: |
|
|
44
|
+
`timeout` bounds one block call. An expired one is a **transient** failure, so a retry
|
|
45
|
+
budget would be spent on it -- which is why this step has none.
|
|
46
|
+
|
|
47
|
+
As written a ten-second endpoint is given two seconds, the step fails in about two, and
|
|
48
|
+
the run reports `failed`.
|
|
49
|
+
|
|
50
|
+
tags: [patterns, http, transform, failure, timeout]
|
|
51
|
+
|
|
52
|
+
requires:
|
|
53
|
+
blocks:
|
|
54
|
+
- http.request
|
|
55
|
+
- value.const
|
|
56
|
+
|
|
57
|
+
params:
|
|
58
|
+
type: object
|
|
59
|
+
properties:
|
|
60
|
+
seconds:
|
|
61
|
+
type: integer
|
|
62
|
+
description: How long the endpoint is asked to take. Anything above the two-second budget loses.
|
|
63
|
+
default: 10
|
|
64
|
+
minimum: 0
|
|
65
|
+
maximum: 30
|
|
66
|
+
|
|
67
|
+
steps:
|
|
68
|
+
start:
|
|
69
|
+
block: value.const
|
|
70
|
+
config:
|
|
71
|
+
value:
|
|
72
|
+
note: everything above the failing step is ordinary
|
|
73
|
+
|
|
74
|
+
slow_read:
|
|
75
|
+
block: http.request
|
|
76
|
+
depends_on: [start]
|
|
77
|
+
# The budget the engine holds the call to. It is the shortest of the three clocks in this
|
|
78
|
+
# file, which is what makes it the one that fires.
|
|
79
|
+
timeout: 2s
|
|
80
|
+
config:
|
|
81
|
+
url: "https://postman-echo.com/delay/${params.seconds}"
|
|
82
|
+
method: GET
|
|
83
|
+
# The block's own clock, left generous so it is plainly not the one that stops this.
|
|
84
|
+
timeout: 30s
|
|
85
|
+
|
|
86
|
+
parse:
|
|
87
|
+
block: transform.jq
|
|
88
|
+
depends_on: [slow_read]
|
|
89
|
+
# Skipped. An abandoned attempt is a failure like any other as far as the edge is
|
|
90
|
+
# concerned; there is no separate "timed out" outcome for a rule to branch on.
|
|
91
|
+
config:
|
|
92
|
+
input: "${steps.slow_read.output.body}"
|
|
93
|
+
program: |
|
|
94
|
+
{delayed: .}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# on_timeout: skip -- the wait that ran out and did not make the run red.
|
|
2
|
+
#
|
|
3
|
+
# WHICH CLOCK on_timeout GOVERNS IS THE WHOLE POINT OF THIS FILE. It governs an expired
|
|
4
|
+
# DEADLINE, which is how long a step may keep waiting. It has nothing to say about an expired
|
|
5
|
+
# `timeout`, which bounds one block call and always fails the step as a transient failure --
|
|
6
|
+
# timeout-fails-the-step.yaml is that one, and there is no spelling of it that skips.
|
|
7
|
+
#
|
|
8
|
+
# So this is the pair to read together. The same waiting step, one word apart:
|
|
9
|
+
#
|
|
10
|
+
# on_timeout: fail (the default) the deadline passing is a broken pipeline.
|
|
11
|
+
# deadline-on-a-sensor.yaml.
|
|
12
|
+
# on_timeout: skip the deadline passing is a quiet day. This file.
|
|
13
|
+
#
|
|
14
|
+
# skip is the right answer whenever "it never showed up" is an ordinary outcome: no export
|
|
15
|
+
# was published today, no drop landed, the upstream had nothing to say. Failing there trains
|
|
16
|
+
# people to ignore red runs, which is worse than the day with no data.
|
|
17
|
+
#
|
|
18
|
+
# Hop by hop:
|
|
19
|
+
#
|
|
20
|
+
# upstream_ready http.ready is a sensor: each poke is one cheap GET, "not yet" is the
|
|
21
|
+
# expected answer, and the run parks between pokes rather than holding a
|
|
22
|
+
# worker. It pokes /status/503, which never answers 2xx, so it never becomes
|
|
23
|
+
# ready and the six-second deadline expires.
|
|
24
|
+
# ingest the default all_success edge, and a skipped prerequisite does not satisfy
|
|
25
|
+
# it, so the skip carries down the branch.
|
|
26
|
+
# close_out all_done, so the run still reports what it did with the day.
|
|
27
|
+
#
|
|
28
|
+
# EXPECT THIS RUN TO SUCCEED, in about six seconds, with upstream_ready and ingest both
|
|
29
|
+
# skipped and close_out green. A run that did nothing is not a run that went wrong.
|
|
30
|
+
#
|
|
31
|
+
# To change it: -p status=200 makes the endpoint ready on the first poke and the whole branch
|
|
32
|
+
# runs. Drop the on_timeout line and the identical run fails instead.
|
|
33
|
+
#
|
|
34
|
+
# dg run --local examples/patterns/timeout-skips-the-step.yaml # succeeds, having skipped
|
|
35
|
+
# dg run --local examples/patterns/timeout-skips-the-step.yaml -p status=200 # succeeds, having worked
|
|
36
|
+
|
|
37
|
+
format: dirigent/v1
|
|
38
|
+
kind: pipeline
|
|
39
|
+
code: timeout-skips-the-step
|
|
40
|
+
name: A wait that skips instead of failing
|
|
41
|
+
description: |
|
|
42
|
+
`on_timeout: skip` turns an expired **deadline** into a skipped step rather than a failed
|
|
43
|
+
one, and the skip carries down the branch through the default `all_success` edges.
|
|
44
|
+
|
|
45
|
+
It governs the deadline only: an expired `timeout` always fails. As written the sensor
|
|
46
|
+
never sees a ready endpoint, everything below it skips, and the run reports `succeeded`.
|
|
47
|
+
|
|
48
|
+
tags: [patterns, http, sensor, timeout]
|
|
49
|
+
|
|
50
|
+
requires:
|
|
51
|
+
blocks:
|
|
52
|
+
- http.ready
|
|
53
|
+
- http.request
|
|
54
|
+
- value.const
|
|
55
|
+
|
|
56
|
+
params:
|
|
57
|
+
type: object
|
|
58
|
+
properties:
|
|
59
|
+
status:
|
|
60
|
+
type: integer
|
|
61
|
+
description: What the readiness endpoint answers; 503 is never ready, 200 is ready at once.
|
|
62
|
+
default: 503
|
|
63
|
+
enum: [200, 503]
|
|
64
|
+
|
|
65
|
+
steps:
|
|
66
|
+
upstream_ready:
|
|
67
|
+
block: http.ready
|
|
68
|
+
# Two seconds between pokes, so three of them fit inside the deadline and the parking
|
|
69
|
+
# between them is visible in the log. A real readiness check on an hourly export uses
|
|
70
|
+
# minutes here, because each poke costs a scheduled row and nothing else.
|
|
71
|
+
poll: 2s
|
|
72
|
+
# Short so the example finishes while you watch it. This is the clock on_timeout is about.
|
|
73
|
+
deadline: 6s
|
|
74
|
+
# The one line this file is about.
|
|
75
|
+
on_timeout: skip
|
|
76
|
+
config:
|
|
77
|
+
# A sensor has no method: it looks, and looking is a GET. Readiness means a 2xx unless
|
|
78
|
+
# expect_status says otherwise, so a 503 is "not yet" rather than an error -- a sensor
|
|
79
|
+
# observing an unready service has observed correctly.
|
|
80
|
+
url: "https://postman-echo.com/status/${params.status}"
|
|
81
|
+
|
|
82
|
+
ingest:
|
|
83
|
+
block: http.request
|
|
84
|
+
depends_on: [upstream_ready]
|
|
85
|
+
# No rule written, so all_success, which a skipped prerequisite does not satisfy. That
|
|
86
|
+
# propagation is what makes on_timeout: skip a branch-level decision rather than a
|
|
87
|
+
# step-level one.
|
|
88
|
+
config:
|
|
89
|
+
url: https://postman-echo.com/post
|
|
90
|
+
method: POST
|
|
91
|
+
body:
|
|
92
|
+
ingested_after_status: "${steps.upstream_ready.output.status}"
|
|
93
|
+
|
|
94
|
+
close_out:
|
|
95
|
+
block: value.const
|
|
96
|
+
depends_on: [ingest]
|
|
97
|
+
# all_done, so the run has an outcome either way: a skipped day is a fact worth recording,
|
|
98
|
+
# not an absence.
|
|
99
|
+
rule: all_done
|
|
100
|
+
config:
|
|
101
|
+
value:
|
|
102
|
+
note: nothing to ingest is a normal day
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# An inbound webhook, and the strict mapping that is the whole of its interface.
|
|
2
|
+
#
|
|
3
|
+
# A webhook declares one thing: which JSONPath in the delivered payload becomes which
|
|
4
|
+
# parameter. That mapping is the entire attack surface, and it is strict in both directions:
|
|
5
|
+
#
|
|
6
|
+
# * Anything the payload carries that is not named here is IGNORED. A caller who adds a
|
|
7
|
+
# field, or fifty, changes nothing about the run.
|
|
8
|
+
# * A parameter the mapping does not name cannot be reached at all, whatever is POSTed. So
|
|
9
|
+
# environment below is unreachable from outside and keeps its default -- which is how a
|
|
10
|
+
# webhook is exposed without letting the internet choose which environment to write to.
|
|
11
|
+
# * What is mapped is validated against params like any other run's parameters. A payload
|
|
12
|
+
# with a batch_size of 10000000 is refused by the schema, not by a step.
|
|
13
|
+
#
|
|
14
|
+
# The paths are JSONPath into the delivered body, so a nested payload needs no unwrapping step:
|
|
15
|
+
# $.published.date reaches two levels down, and $.source.system.name three. Reshaping the
|
|
16
|
+
# payload inside the pipeline would be doing at run time what the mapping already does at
|
|
17
|
+
# intake, one layer later and with worse errors.
|
|
18
|
+
#
|
|
19
|
+
# WHAT STAYS ON THE INSTANCE. The token is minted server-side and shown exactly once. It is
|
|
20
|
+
# not in this file, it will not be in an export, and there is nowhere in dirigent/v1 to write
|
|
21
|
+
# one. The same is true of whether the endpoint is enabled and of the delivery history: those
|
|
22
|
+
# are facts about one instance, so an apply never touches them.
|
|
23
|
+
#
|
|
24
|
+
# Hop by hop:
|
|
25
|
+
#
|
|
26
|
+
# ingest shapes the mapped parameters into the record a loader would be handed.
|
|
27
|
+
# receipt reads it back, standing in for the work.
|
|
28
|
+
#
|
|
29
|
+
# EXPECT THIS RUN TO SUCCEED in about a second, on the pipeline's defaults -- the webhook
|
|
30
|
+
# accepts deliveries only on an instance the document has been applied to.
|
|
31
|
+
#
|
|
32
|
+
# dg run --local examples/patterns/webhook-mapping-nested-payload.yaml
|
|
33
|
+
# dg apply examples/patterns/webhook-mapping-nested-payload.yaml
|
|
34
|
+
# dg webhook list webhook-mapping-nested-payload
|
|
35
|
+
# dg webhook rotate-token webhook-mapping-nested-payload upstream-publish # shows a token once
|
|
36
|
+
# curl -X POST "$DG_URL/hooks/$TOKEN" -H 'content-type: application/json' -d '{
|
|
37
|
+
# "published": {"date": "2026-01-01", "dataset": {"code": "cases"}},
|
|
38
|
+
# "source": {"system": {"name": "warehouse"}},
|
|
39
|
+
# "counts": {"rows": 4120},
|
|
40
|
+
# "anything_else": "ignored"
|
|
41
|
+
# }'
|
|
42
|
+
# dg webhook deliveries webhook-mapping-nested-payload upstream-publish
|
|
43
|
+
|
|
44
|
+
format: dirigent/v1
|
|
45
|
+
kind: pipeline
|
|
46
|
+
code: webhook-mapping-nested-payload
|
|
47
|
+
name: A webhook mapped from a nested payload
|
|
48
|
+
description: |
|
|
49
|
+
`params_from_payload` maps JSONPaths in the delivered body onto parameters, and that
|
|
50
|
+
mapping is the whole interface: unmapped fields are ignored, unmapped parameters are
|
|
51
|
+
unreachable, and what is mapped is validated like any other run's parameters.
|
|
52
|
+
|
|
53
|
+
The token, the enabled flag and the delivery history stay on the instance; none of them
|
|
54
|
+
belongs in git.
|
|
55
|
+
|
|
56
|
+
tags: [patterns, transform, webhook]
|
|
57
|
+
|
|
58
|
+
requires:
|
|
59
|
+
blocks:
|
|
60
|
+
- transform.jq
|
|
61
|
+
|
|
62
|
+
params:
|
|
63
|
+
type: object
|
|
64
|
+
properties:
|
|
65
|
+
day:
|
|
66
|
+
type: string
|
|
67
|
+
format: date
|
|
68
|
+
description: The day the upstream published; mapped from the payload.
|
|
69
|
+
default: "2026-01-01"
|
|
70
|
+
dataset:
|
|
71
|
+
type: string
|
|
72
|
+
description: Which dataset was published; mapped from two levels down.
|
|
73
|
+
default: cases
|
|
74
|
+
enum: [cases, climate, population]
|
|
75
|
+
source:
|
|
76
|
+
type: string
|
|
77
|
+
description: Which upstream system published it; mapped from three levels down.
|
|
78
|
+
default: warehouse
|
|
79
|
+
minLength: 1
|
|
80
|
+
maxLength: 64
|
|
81
|
+
rows:
|
|
82
|
+
type: integer
|
|
83
|
+
description: How many rows the upstream says it published; bounded, so a delivery cannot claim anything.
|
|
84
|
+
default: 0
|
|
85
|
+
minimum: 0
|
|
86
|
+
maximum: 10000000
|
|
87
|
+
environment:
|
|
88
|
+
type: string
|
|
89
|
+
description: Deliberately absent from the mapping, so no delivery can choose it.
|
|
90
|
+
default: staging
|
|
91
|
+
enum: [staging, production]
|
|
92
|
+
|
|
93
|
+
steps:
|
|
94
|
+
ingest:
|
|
95
|
+
block: transform.jq
|
|
96
|
+
config:
|
|
97
|
+
input:
|
|
98
|
+
day: "${params.day}"
|
|
99
|
+
dataset: "${params.dataset}"
|
|
100
|
+
source: "${params.source}"
|
|
101
|
+
rows: "${params.rows}"
|
|
102
|
+
environment: "${params.environment}"
|
|
103
|
+
program: |
|
|
104
|
+
{day, dataset, source, rows, environment}
|
|
105
|
+
|
|
106
|
+
receipt:
|
|
107
|
+
block: transform.jq
|
|
108
|
+
depends_on: [ingest]
|
|
109
|
+
config:
|
|
110
|
+
input: "${steps.ingest.output.value}"
|
|
111
|
+
program: |
|
|
112
|
+
{ingested: "\(.dataset) for \(.day) from \(.source)", rows, environment}
|
|
113
|
+
|
|
114
|
+
triggers:
|
|
115
|
+
webhooks:
|
|
116
|
+
- code: upstream-publish
|
|
117
|
+
name: Upstream publish notification
|
|
118
|
+
description: Fired by the warehouse when a dataset finishes publishing.
|
|
119
|
+
params_from_payload:
|
|
120
|
+
# One line per parameter, each a JSONPath into the delivered body. Depth costs
|
|
121
|
+
# nothing; a field the mapping does not name is not reachable at any depth.
|
|
122
|
+
day: "$.published.date"
|
|
123
|
+
dataset: "$.published.dataset.code"
|
|
124
|
+
source: "$.source.system.name"
|
|
125
|
+
rows: "$.counts.rows"
|