sequel-pgt_outbox 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.
data/ci/build_image.sh ADDED
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env bash
2
+
3
+ if readlink -f . >/dev/null 2>&1 # {{{ makes readlink work on mac
4
+ then
5
+ readlink=readlink
6
+ else
7
+ if greadlink -f . >/dev/null 2>&1
8
+ then
9
+ readlink=greadlink
10
+ else
11
+ printf "You must install greadlink to use this (brew install coreutils)\n" >&2
12
+ fi
13
+ fi # }}}
14
+
15
+ # Set here to the full path to this script
16
+ me=${BASH_SOURCE[0]}
17
+ [ -L "$me" ] && me=$($readlink -f "$me")
18
+ here=$(cd "$(dirname "$me")" && pwd)
19
+ just_me=$(basename "$me")
20
+
21
+ repo_top=$(git rev-parse --show-toplevel)
22
+ cd "$repo_top" || {
23
+ printf "Could not cd to %s\n" "$repo_top" >&2
24
+ exit 1
25
+ }
26
+
27
+ base_dir=$(basename "$(pwd)")
28
+ : "${UBUNTU_VERSION:=bookworm}"
29
+ : "${BUILD_CONTEXT:=$(pwd)}"
30
+ : "${IMAGE_NAME:=$base_dir}"
31
+ : "${LICENSE:=MIT}"
32
+ : "${REGISTRY:=ghcr.io}"
33
+ : "${RUBY_VERSION:=3.3.6}"
34
+ : "${REGISTRY_TOKEN:=$GITHUB_TOKEN}"
35
+
36
+ base_image_tag="$RUBY_VERSION-$UBUNTU_VERSION"
37
+ base_exists=$(skopeo list-tags docker://docker.io/ruby |jq -r "any(.Tags[] == \"$base_image_tag\"; .)")
38
+ if [ "$base_exists" = "false" ]
39
+ then
40
+ printf "Base image %s does not exist at docker.io/ruby, cannot build.\n" "$base_image_tag" >&2
41
+ exit 99
42
+ fi
43
+
44
+ usage() { # {{{
45
+ cat <<-EOT
46
+ Build an image, optionally pushing it to the registry
47
+
48
+ Usage: $0 <options> <image_tag>
49
+ Options:
50
+ -c CONTAINERFILE Path to the containerfile (default: ./oci/Containerfile)
51
+ -C CONTEXT Build context (default: $BUILD_CONTEXT)
52
+ -i NAME Name of the image (default: $IMAGE_NAME)
53
+ -l LICENSE License of the image (default: $LICENSE)
54
+ -r REGISTRY Registry to push the image to when -p is given (default: $REGISTRY)
55
+ -p Push the image to the registry
56
+ -h Show help / usage
57
+ EOT
58
+ } # }}}
59
+
60
+ die() { # {{{
61
+ local -i code
62
+ code=$1
63
+ shift
64
+ error "$*"
65
+ printf "\n" >&2
66
+ usage >&2
67
+ # shellcheck disable=SC2086
68
+ exit $code
69
+ } # }}}
70
+
71
+ ## Logging functions # {{{
72
+ log() { # {{{
73
+ printf "%s [%s] <%s> %s\n" "$(date '+%Y-%m-%d %H:%M:%S.%6N')" "$$" "${just_me:-$0}" "$*"
74
+ } # }}}
75
+
76
+ debug() { # {{{
77
+ [ $verbose -lt 2 ] && return 0
78
+ # shellcheck disable=SC2059
79
+ log_line=$(printf "$@")
80
+ log "[DEBUG] $log_line" >&2
81
+ } # }}}
82
+
83
+ warn() { # {{{
84
+ # shellcheck disable=SC2059
85
+ log_line=$(printf "$@")
86
+ log "[WARN] $log_line" >&2
87
+ } # }}}
88
+
89
+ error() { # {{{
90
+ # shellcheck disable=SC2059
91
+ log_line=$(printf "$@")
92
+ log "[ERROR] $log_line" >&2
93
+ } # }}}
94
+
95
+ info() { # {{{
96
+ [ $verbose -lt 1 ] && return 0
97
+ # shellcheck disable=SC2059
98
+ log_line=$(printf "$@")
99
+ log "[INFO] $log_line" >&2
100
+ } # }}}
101
+ # }}}
102
+
103
+ push=0
104
+ verbose=0
105
+ while getopts :hpvc:C:i:l:r: opt # {{{
106
+ do
107
+ case $opt in
108
+ c)
109
+ CONTAINERFILE=$OPTARG
110
+ ;;
111
+ C)
112
+ BUILD_CONTEXT=$OPTARG
113
+ ;;
114
+ i)
115
+ IMAGE_NAME=$OPTARG
116
+ ;;
117
+ l)
118
+ LICENSE=$OPTARG
119
+ ;;
120
+ r)
121
+ REGISTRY=$OPTARG
122
+ ;;
123
+ p)
124
+ push=1
125
+ ;;
126
+ v)
127
+ verbose=$((verbose + 1))
128
+ ;;
129
+ h)
130
+ usage
131
+ exit
132
+ ;;
133
+ :)
134
+ printf "Option %s requires an argument\n" "$OPTARG" >&2
135
+ usage >&2
136
+ exit 28
137
+ ;;
138
+ ?)
139
+ printf "Invalid option '%s'\n" "$OPTARG" >&2
140
+ usage >&2
141
+ exit 27
142
+ ;;
143
+ esac
144
+ done # }}}
145
+ shift $((OPTIND-1))
146
+
147
+ tag=$1
148
+ [ -z "$tag" ] && die 1 "Missing image tag"
149
+ shift
150
+
151
+ # Check for extra argument
152
+ if [ $# -gt 0 ]; then
153
+ # If we have the special argument '--' we shift it away, otherwise we die
154
+ [ "$1" != '--' ] && die 2 "Too many arguments"
155
+ # Once this is shifted away, the rest of the arguments are passed to the build command, below
156
+ shift
157
+ fi
158
+
159
+ if [ -z "$CONTAINERFILE" ]; then
160
+ printf "No containerfile specified, looking for default locations\n"
161
+ for containerfile in Containerfile Dockerfile
162
+ do
163
+ if [ -f ./oci/"$containerfile" ]; then
164
+ debug "Found ./oci/%s\n" "$containerfile"
165
+ containerfile=./oci/"$containerfile"
166
+ break
167
+ fi
168
+ if [ -f "$containerfile" ]; then
169
+ debug "Found %s\n" "$containerfile"
170
+ break
171
+ fi
172
+ done
173
+ else
174
+ [ -f "$CONTAINERFILE" ] || die 3 "Containerfile '$CONTAINERFILE' not found"
175
+ debug "Using containerfile %s\n" "$CONTAINERFILE"
176
+ containerfile=$CONTAINERFILE
177
+ fi
178
+
179
+ [ -f "$containerfile" ] || die 4 "No containerfile found"
180
+
181
+ [ -d "$BUILD_CONTEXT" ] || die 5 "Build context '$BUILD_CONTEXT' not found"
182
+
183
+ debug 'Building image from %s in in %s\n' "$containerfile" "$here"
184
+ # Build the image
185
+ if command -v podman 2>/dev/null
186
+ then
187
+ runtime=podman
188
+ elif command -v docker 2>/dev/null
189
+ then
190
+ runtime=docker
191
+ else
192
+ die 6 "No container runtime found"
193
+ fi
194
+
195
+ revision=$(git rev-parse HEAD)
196
+ shortref=$(git rev-parse --short "$revision")
197
+ repo_url=$(git remote get-url upstream)
198
+ if [ -z "$repo_url" ]
199
+ then
200
+ die 7 "No remote found"
201
+ fi
202
+ if [[ $repo_url == *github.com/* ]]
203
+ then
204
+ owner_and_repo=${repo_url#*github.com/}
205
+ else
206
+ owner_and_repo=${repo_url##*:}
207
+ fi
208
+ # Get rid of the trailing .git
209
+ service=$(basename "$owner_and_repo" .git)
210
+ owner=$(dirname "$owner_and_repo")
211
+
212
+ full_tag=$IMAGE_NAME:$tag-ruby$RUBY_VERSION-$UBUNTU_VERSION
213
+ # Pass any extra arguments to the build command ("$@" contains the rest of the arguments)
214
+ $runtime build --tag "$full_tag" "$@" \
215
+ --label org.opencontainers.image.created="$(date --utc --iso-8601=seconds)" \
216
+ --label org.opencontainers.image.description="Image for $service" \
217
+ --label org.opencontainers.image.licenses="$LICENSE" \
218
+ --label org.opencontainers.image.revision="$revision" \
219
+ --label org.opencontainers.image.url="$repo_url" \
220
+ --label org.opencontainers.image.title="$IMAGE_NAME" \
221
+ --label org.opencontainers.image.source="Generated by ruby-automation's build_image.sh ($USER@$HOSTNAME)" \
222
+ --label org.opencontainers.image.version="$full_tag" \
223
+ --label shortref="$shortref" \
224
+ --build-arg UBUNTU_VERSION="$UBUNTU_VERSION" \
225
+ --build-arg RUBY_VERSION="$RUBY_VERSION" \
226
+ -f "$containerfile" "$BUILD_CONTEXT" || die 8 "Failed to build image"
227
+
228
+ [ $push -eq 1 ] || exit 0
229
+ if ! $runtime login --get-login "$REGISTRY" >/dev/null 2>/dev/null
230
+ then
231
+ printf "Not logged in to '%s', trying to login\n" "$REGISTRY" >&2
232
+ [ -z "$REGISTRY_TOKEN" ] && die 9 "No REGISTRY_TOKEN (nor GITHUB_TOKEN) set, cannot login"
233
+ printf "%s" "$REGISTRY_TOKEN" | $runtime login -u "$REGISTRY_TOKEN" --password-stdin "$REGISTRY" || die 10 "Failed to login to $REGISTRY"
234
+ fi
235
+
236
+ # Split 1.2.3 into 1.2.3, 1.2, 1. We want to tag our image with all 3 of these
237
+ mapfile -t tags < <(echo "$tag" | awk -F'.' 'NF==3{print; print $1"."$2; print $1; next} NF==2{print; print $1; next} {print}')
238
+ for t in "${tags[@]}"
239
+ do
240
+ new_tag=$IMAGE_NAME:$t-ruby$RUBY_VERSION-$UBUNTU_VERSION
241
+ registry_image_name="$REGISTRY/$owner/$new_tag"
242
+ if [ "$runtime" = "podman" ]
243
+ then
244
+ if [ "$full_tag" != "$new_tag" ]
245
+ then
246
+ debug "Tagging %s as %s\n" "$full_tag" "$new_tag"
247
+ podman tag "$full_tag" "$new_tag" || die 11 "Failed to tag image $full_tag as $new_tag"
248
+ fi
249
+ podman push "$new_tag" "$registry_image_name" || die 12 "Failed to push image $new_tag to $registry_image_name"
250
+ else
251
+ debug "Tagging %s as %s\n" "$full_tag" "$registry_image_name"
252
+ docker tag "$full_tag" "$registry_image_name" || die 13 "Failed to tag image $full_tag as $registry_image_name"
253
+ docker push "$registry_image_name" || die 14 "Failed to push image $new_tag to $registry_image_name"
254
+ fi
255
+ done
256
+
257
+ # vim: set foldmethod=marker et ts=4 sts=4 sw=4 ft=bash :
data/ci/publish-gem.sh ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env bash
2
+
3
+ if readlink -f . >/dev/null 2>&1 # {{{ makes readlink work on mac
4
+ then
5
+ readlink=readlink
6
+ else
7
+ if greadlink -f . >/dev/null 2>&1
8
+ then
9
+ readlink=greadlink
10
+ else
11
+ printf "You must install greadlink to use this (brew install coreutils)\n" >&2
12
+ fi
13
+ fi # }}}
14
+
15
+ # Set here to the full path to this script
16
+ me=${BASH_SOURCE[0]}
17
+ [ -L "$me" ] && me=$($readlink -f "$me")
18
+ here=$(cd "$(dirname "$me")" && pwd)
19
+ just_me=$(basename "$me")
20
+
21
+ : "${GEM_NAME:=sequel-pgt_outbox}"
22
+ : "${GIT_ORG:=rubyists}"
23
+
24
+ GEM_HOST=$1
25
+ : "${GEM_HOST:=rubygems}"
26
+
27
+ case "$GEM_HOST" in
28
+ rubygems)
29
+ gem_key='rubygems'
30
+ gem_host='https://rubygems.org'
31
+ ;;
32
+ github)
33
+ gem_key='github'
34
+ gem_host="https://rubygems.pkg.github.com/$GIT_ORG"
35
+ # Replace the gem host in the gemspec, so it allows pushing to the GitHub package registry
36
+ sed --in-place=.bak -e "s|https://rubygems.org|https://rubygems.pkg.github.com/$GIT_ORG|" "$here/../$GEM_NAME".gemspec
37
+ # Restore the original gemspec after the script finishes
38
+ trap 'mv -v "$here/../$GEM_NAME".gemspec.bak "$here/../$GEM_NAME".gemspec' EXIT
39
+ ;;
40
+ *)
41
+ printf 'Unknown GEM_HOST: %s\n' "$GEM_HOST" >&2
42
+ exit 1
43
+ ;;
44
+ esac
45
+
46
+ # We only want this part running in CI, with no ~/.gem dir
47
+ # For local testing, you should have a ~/.gem/credentials file with
48
+ # the keys you need to push to rubygems or github
49
+ if [ ! -d ~/.gem ]
50
+ then
51
+ if [ -z "$GEM_TOKEN" ]
52
+ then
53
+ printf 'No GEM_TOKEN provided, cannot publish\n' >&2
54
+ exit 1
55
+ fi
56
+ mkdir -p ~/.gem
57
+ printf '%s\n:%s: %s\n' '---' "$gem_key" "$GEM_TOKEN" > ~/.gem/credentials
58
+ chmod 600 ~/.gem/credentials
59
+ fi
60
+
61
+ if [ -f "$here"/../.version.txt ]
62
+ then
63
+ version=$(<"$here"/../.version.txt)
64
+ else
65
+ version=$(git describe --tags --abbrev=0 | sed -e 's/^v//')
66
+ fi
67
+
68
+ gem="$(printf '%s-%s.gem' "$GEM_NAME" "$version")"
69
+ if [[ "${TRACE:-false}" == true || "${ACTIONS_STEP_DEBUG:-false}" == true ]]
70
+ then
71
+ printf "DEBUG: [%s] Building And Publishing %s to %s\n" "$just_me" "$gem" "$gem_host" >&2
72
+ fi
73
+
74
+ bundle exec gem build
75
+ bundle exec gem push -k "$gem_key" --host "$gem_host" "$gem"
76
+
77
+ # vim: set foldmethod=marker et ts=4 sts=4 sw=4 ft=bash :
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../pgt_outbox'
4
+
5
+ # The pgt_outbox extension adds support to the Database instance for
6
+ # implementing the transactional outbox pattern using triggers.
7
+ module Sequel
8
+ # Postgres namespace
9
+ module Postgres
10
+ # The PgtOutboxMethods module provides methods for creating outbox tables and triggers
11
+ module PgtOutboxMethods
12
+ class_eval(&Rubyists::PgtOutbox::DEFINITION)
13
+ end
14
+ end
15
+
16
+ Database.register_extension(:pgt_outbox, Postgres::PgtOutboxMethods)
17
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../pgt_outbox'
4
+
5
+ module Sequel
6
+ module Postgres
7
+ # Extends the Sequel::Database class with the PgtOutbox methods
8
+ module DatabaseMethods
9
+ class_eval(&Rubyists::PgtOutbox::DEFINITION)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../pgt_outbox'
4
+
5
+ module Rubyists
6
+ # Top-level module for the PgtOutbox gem
7
+ module PgtOutbox
8
+ # The Outbox Function
9
+ class Function
10
+ include PgtOutbox
11
+ DEFAULT_OPTS = { language: :plpgsql, returns: :trigger, replace: true }.freeze
12
+
13
+ attr_reader(*%i[outbox db opts])
14
+
15
+ def self.create!(outbox, opts: {})
16
+ new(outbox, opts: opts).create!
17
+ end
18
+
19
+ def initialize(outbox, opts: {})
20
+ @outbox = outbox
21
+ @db = outbox.db
22
+ @opts = opts
23
+ end
24
+
25
+ def name
26
+ @name = opts.fetch(:function_name, "pgt_outbox_#{mangled_table_name(db, outbox.name)}")
27
+ end
28
+
29
+ %i[data_after_column data_before_column event_prefix event_type_column quoted_name].each do |meth|
30
+ define_method(meth) { outbox.send(meth) }
31
+ end
32
+
33
+ def create!
34
+ db.create_function(name, function_sql, function_opts)
35
+ self
36
+ end
37
+
38
+ def function_sql
39
+ <<~SQL
40
+ BEGIN
41
+ #{depth_guard_clause}
42
+ IF (TG_OP = 'INSERT') THEN
43
+ INSERT INTO #{quoted_name} ("#{event_type_column}", "#{data_after_column}") VALUES
44
+ ('#{event_prefix}_created', to_jsonb(NEW));
45
+ RETURN NEW;
46
+ ELSIF (TG_OP = 'UPDATE') THEN
47
+ INSERT INTO #{quoted_name} ("#{event_type_column}", "#{data_before_column}", "#{data_after_column}") VALUES
48
+ ('#{event_prefix}_updated', to_jsonb(OLD), to_jsonb(NEW));
49
+ RETURN NEW;
50
+ ELSIF (TG_OP = 'DELETE') THEN
51
+ INSERT INTO #{quoted_name} ("#{event_type_column}", "#{data_before_column}") VALUES
52
+ ('#{event_prefix}_deleted', to_jsonb(OLD));
53
+ RETURN OLD;
54
+ END IF;
55
+ END;
56
+ SQL
57
+ end
58
+
59
+ private
60
+
61
+ def function_opts
62
+ @function_opts ||= DEFAULT_OPTS.merge(opts.fetch(:function_opts, {}))
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../pgt_outbox'
4
+ require_relative 'function'
5
+
6
+ module Rubyists
7
+ module PgtOutbox
8
+ # The Outbox Table
9
+ class Table # rubocop:disable Metrics/ClassLength
10
+ attr_reader(*%i[table opts db])
11
+
12
+ def self.create!(table, db, opts: {})
13
+ new(table, db, opts:).create!
14
+ end
15
+
16
+ def initialize(table, db, opts: {})
17
+ @table = table
18
+ @opts = opts
19
+ @db = db
20
+ end
21
+
22
+ def create!
23
+ db.run 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"' if uuid_primary_key?
24
+ create_table!
25
+ integer_columns!
26
+ completed_column!
27
+ timestamp_columns!
28
+ string_columns!
29
+ jsonb_columns!
30
+ indexes!
31
+ self
32
+ end
33
+
34
+ def name
35
+ @name ||= opts.fetch(:outbox_table, "#{table}_outbox")
36
+ end
37
+
38
+ def timestamp_type
39
+ opts.fetch(:timestamp_type, :timestamptz)
40
+ end
41
+
42
+ def quoted_name
43
+ @quoted_name ||= db.send(:quote_schema_table, name)
44
+ end
45
+
46
+ def event_prefix
47
+ @event_prefix ||= opts.fetch(:event_prefix, table)
48
+ end
49
+
50
+ def created_column
51
+ @created_column ||= opts.fetch(:created_column, :created)
52
+ end
53
+
54
+ def updated_column
55
+ @updated_column ||= opts.fetch(:updated_column, :updated)
56
+ end
57
+
58
+ def completed_column
59
+ @completed_column ||= opts.fetch(:completed_column, :completed)
60
+ end
61
+
62
+ def attempts_column
63
+ @attempts_column ||= opts.fetch(:attempts_column, :attempts)
64
+ end
65
+
66
+ def attempted_column
67
+ @attempted_column ||= opts.fetch(:attempted_column, :attempted)
68
+ end
69
+
70
+ def event_type_column
71
+ @event_type_column ||= opts.fetch(:event_type_column, :event_type)
72
+ end
73
+
74
+ def last_error_column
75
+ @last_error_column ||= opts.fetch(:last_error_column, :last_error)
76
+ end
77
+
78
+ def data_after_column
79
+ @data_after_column ||= opts.fetch(:data_after_column, :data_after)
80
+ end
81
+
82
+ def data_before_column
83
+ @data_before_column ||= opts.fetch(:data_before_column, :data_before)
84
+ end
85
+
86
+ def metadata_column
87
+ @metadata_column ||= opts.fetch(:metadata_column, :metadata)
88
+ end
89
+
90
+ def boolean_completed_column
91
+ @boolean_completed_column ||= opts.fetch(:boolean_completed_column, false)
92
+ end
93
+
94
+ def uuid_primary_key?
95
+ @uuid_primary_key ||= opts.fetch(:uuid_primary_key, false)
96
+ end
97
+
98
+ def uuid_function
99
+ @uuid_function ||= opts.fetch(:uuid_function, :uuid_generate_v4)
100
+ end
101
+
102
+ def function
103
+ @function ||= Function.create!(self, opts:)
104
+ end
105
+
106
+ private
107
+
108
+ def create_table!
109
+ uuid_primary_key = uuid_primary_key?
110
+ uuid_func = uuid_function
111
+ db.create_table(name) do
112
+ if uuid_primary_key
113
+ uuid :id, default: Sequel.function(uuid_func), primary_key: true
114
+ else
115
+ primary_key :id
116
+ end
117
+ end
118
+ end
119
+
120
+ def completed_column!
121
+ if boolean_completed_column
122
+ db.add_column name, completed_column, FalseClass, null: false, default: false
123
+ else
124
+ db.add_column name, completed_column, timestamp_type
125
+ end
126
+ self
127
+ end
128
+
129
+ def integer_columns!
130
+ db.add_column name, attempts_column, Integer, null: false, default: 0
131
+ self
132
+ end
133
+
134
+ def timestamp_columns!
135
+ db.add_column name, created_column, timestamp_type
136
+ db.add_column name, updated_column, timestamp_type
137
+ db.add_column name, attempted_column, timestamp_type
138
+ self
139
+ end
140
+
141
+ def string_columns!
142
+ db.add_column name, event_type_column, String, null: false
143
+ db.add_column name, last_error_column, String
144
+ self
145
+ end
146
+
147
+ def jsonb_columns!
148
+ db.add_column name, data_before_column, :jsonb
149
+ db.add_column name, data_after_column, :jsonb
150
+ db.add_column name, metadata_column, :jsonb
151
+ self
152
+ end
153
+
154
+ def indexes!
155
+ db.add_index name, Sequel.asc(created_column)
156
+ db.add_index name, Sequel.desc(attempted_column)
157
+ self
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../pgt_outbox'
4
+
5
+ module Rubyists
6
+ module PgtOutbox
7
+ # The Outbox Trigger
8
+ class Trigger
9
+ DEFAULT_OPTS = { after: true, each_row: true }.freeze
10
+
11
+ attr_reader(*%i[db table function events opts])
12
+
13
+ def self.create!(db, table, function, events: %i[insert update delete], opts: { when: nil })
14
+ new(db, table, function, events: events, opts: opts).create!
15
+ end
16
+
17
+ def initialize(db, table, function, events: %i[insert update delete], opts: { when: nil })
18
+ @db = db
19
+ @table = table
20
+ @function = function
21
+ @events = events
22
+ @opts = opts
23
+ end
24
+
25
+ def name
26
+ @name ||= opts.fetch(:trigger_name, function)
27
+ end
28
+
29
+ def create!
30
+ db.create_trigger(table, name, function, events:, each_row:, after:, when: where)
31
+ self
32
+ end
33
+
34
+ def after
35
+ trigger_opts.fetch(:after)
36
+ end
37
+
38
+ def where
39
+ opts.fetch(:when, nil)
40
+ end
41
+
42
+ def each_row
43
+ trigger_opts.fetch(:each_row)
44
+ end
45
+
46
+ def trigger_opts
47
+ @trigger_opts ||= DEFAULT_OPTS.merge(opts.fetch(:trigger_opts, {}))
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyists
4
+ module PgtOutbox
5
+ # x-release-please-start-version
6
+ VERSION = '0.2.0'
7
+ # x-release-please-end
8
+ end
9
+ end