activerecord-refined 0.5.1 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98f198f59d3bb4b6f4c51a16940d4e7ee44b890aa7e033a327a650b73cb3dfbd
4
- data.tar.gz: 5fb7cdd8964b82d6bb14ffdfecf1400ea875f1c5bc12d7fa4645c431caf47d19
3
+ metadata.gz: 7d179cda5b9633e349569aa64a129909938a377302ec7091d2dc342f1e809dc9
4
+ data.tar.gz: 94cd9271298ee1b67e53ebe53c1f9a7b3ab5548e796f18d3bd2ec3e8ec24f320
5
5
  SHA512:
6
- metadata.gz: 5ea7a29d21b31e3bf5f262a54eacb8e4e27590d9031f560d8de635da958cf91e6c5ef0872e77caf7b4e32907d2985f13fcd2ff8492ffbeda0edff1d8b7f658f4
7
- data.tar.gz: 4949bb4ce9b0e4cd0f964e7f2759460a72707734a56d746cbf1221f80f9f7a87161c4a494c0226b880d7ad905e6c83933b29254c5ce3e59c41de415339557f56
6
+ metadata.gz: d4a032b8a00105c9f1d655e1cd745e2b2c66e4c10a1f0ca737451bd1f4dabf519227cf4c86f79d17f47340f7f26071f386fd5e88bf7488bb4de31513380178c5
7
+ data.tar.gz: 42448bbad15330e96a8cb56760a463b17880256360d8f8f05684942ae758db148c24a3fb32c81c35bda840281265a21a120481aa9c5eaf3369d7b082ffc10e48
@@ -33,13 +33,75 @@ jobs:
33
33
  with:
34
34
  node-version: '24'
35
35
 
36
+ # What the binary is made of: the gems with C extensions, the scripts
37
+ # that drive the build -- RUBY_REV and the toolchain versions are pinned
38
+ # in them -- the staged extension, and the host Ruby. Not the gem's own
39
+ # lib and not the page, neither of which the binary contains; those are
40
+ # files the page fetches at startup.
41
+ #
42
+ # Naming the build after this rather than after the binary is what makes
43
+ # the URL stable. The build is not reproducible to the byte -- the same
44
+ # tree twice over came out 1 KB apart, mtimes riding along in the packed
45
+ # filesystem -- so a hash of the output would change on every deploy and
46
+ # give every visitor 12 MB to fetch again for nothing.
47
+ - name: Identify the build
48
+ id: build
49
+ run: |
50
+ hash="${{ hashFiles('sandbox/.ruby-version', 'sandbox/Gemfile.lock', 'sandbox/bin/build-wasm', 'sandbox/bin/rbwasm-build.rb', 'sandbox/bin/install-wasi-sdk.rb', 'sandbox/ext/**') }}"
51
+
52
+ # hashFiles gives back an empty string when it matches nothing, and
53
+ # an empty hash would quietly name every build alike -- and hand out
54
+ # whichever binary happened to be uploaded first, for good.
55
+ if [ -z "$hash" ]; then
56
+ echo "::error::hashFiles matched none of the build inputs" >&2
57
+ exit 1
58
+ fi
59
+
60
+ echo "hash=$hash" >> "$GITHUB_OUTPUT"
61
+ echo "key=ruby-${hash:0:16}.wasm" >> "$GITHUB_OUTPUT"
62
+ echo "this build is ruby-${hash:0:16}.wasm"
63
+
64
+ # The binary for these inputs may already be in the bucket from an
65
+ # earlier deploy, in which case there is nothing to build: 12 MB comes
66
+ # down in a few seconds where a build takes two minutes warm and sixteen
67
+ # cold. The examples are still checked against it below, which is what
68
+ # the deploy is really for.
69
+ #
70
+ # Anything at all going wrong here -- no credentials, as on a fork, a
71
+ # network failure, a half-written file -- falls through to building.
72
+ - name: Fetch this build from R2 if it is there
73
+ id: prebuilt
74
+ if: vars.R2_PUBLIC_URL != ''
75
+ env:
76
+ AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
77
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
78
+ AWS_DEFAULT_REGION: auto
79
+ AWS_REQUEST_CHECKSUM_CALCULATION: when_required
80
+ AWS_RESPONSE_CHECKSUM_VALIDATION: when_required
81
+ ENDPOINT: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
82
+ BUCKET: ${{ vars.R2_BUCKET }}
83
+ KEY: ${{ steps.build.outputs.key }}
84
+ run: |
85
+ if aws s3api get-object \
86
+ --endpoint-url "$ENDPOINT" \
87
+ --bucket "$BUCKET" \
88
+ --key "$KEY" \
89
+ /tmp/ruby.wasm.gz \
90
+ --no-cli-pager > /dev/null 2>&1 &&
91
+ gunzip -c /tmp/ruby.wasm.gz > ruby.wasm
92
+ then
93
+ echo "found=true" >> "$GITHUB_OUTPUT"
94
+ echo "took $KEY from the bucket; not building"
95
+ ls -l ruby.wasm
96
+ else
97
+ rm -f ruby.wasm
98
+ echo "$KEY is not in the bucket; building it"
99
+ fi
100
+
36
101
  # The Ruby checkout and the build tree have to be cached together. make
37
102
  # decides by mtime, so restoring objects next to a freshly cloned Ruby
38
- # would rebuild everything.
39
- #
40
- # The build directory is keyed on the gems that have C extensions, so
41
- # Gemfile.lock covers it; RUBY_REV is pinned in bin/build-wasm, and the
42
- # staged extension comes from ext/ and the sqlite3 gem.
103
+ # would rebuild everything. Keyed on the same inputs as the binary, so
104
+ # the two cannot drift apart.
43
105
  #
44
106
  # restore-keys is what keeps this worth caching -- a key change would
45
107
  # otherwise mean a cold 16-minute build rather than two minutes -- but it
@@ -48,16 +110,18 @@ jobs:
48
110
  # deleting the cache does. bin/build-wasm rejects a ruby.wasm built from
49
111
  # such a tree, so it fails the run rather than shipping.
50
112
  - name: Restore the WASM build tree
113
+ if: steps.prebuilt.outputs.found != 'true'
51
114
  uses: actions/cache@v6
52
115
  with:
53
116
  path: sandbox/build
54
- key: wasm-${{ runner.os }}-${{ hashFiles('sandbox/Gemfile.lock', 'sandbox/bin/build-wasm', 'sandbox/ext/**') }}
117
+ key: wasm-${{ runner.os }}-${{ steps.build.outputs.hash }}
55
118
  restore-keys: wasm-${{ runner.os }}-
56
119
 
57
120
  - run: bundle install --jobs 4
58
121
  - run: npm ci
59
122
 
60
123
  - name: Build ruby.wasm
124
+ if: steps.prebuilt.outputs.found != 'true'
61
125
  run: ./bin/build-wasm
62
126
 
63
127
  - name: Assemble the Ruby files served to the page
@@ -69,12 +133,13 @@ jobs:
69
133
  - name: Check the examples
70
134
  run: node --stack-size=4000 check-examples.mjs
71
135
 
72
- # ruby.wasm is 40 MB, which a Cloudflare Pages asset may not be and which
73
- # is nearly all of the bandwidth this page uses, so it is served from R2
74
- # -- egress there is free -- and only the page itself from Pages.
136
+ # ruby.wasm is 40 MB and nearly all of the bandwidth this page uses, so
137
+ # it is served from R2 -- egress there is free -- and only the page
138
+ # itself from Pages.
75
139
  #
76
- # The key carries a hash of the binary, so a rebuild lands on a new URL
77
- # and nothing has to be purged. R2 serves what it is given and does not
140
+ # Only when it was built here: found in the bucket, it is already up.
141
+ # The object is therefore written once and never rewritten, which is what
142
+ # the immutable caching claims. R2 serves what it is given and does not
78
143
  # compress, hence gzip with the encoding recorded on the object: 40 MB
79
144
  # becomes 12 MB on the wire.
80
145
  #
@@ -88,36 +153,108 @@ jobs:
88
153
  # account, which is not what a public repository's CI should hold.
89
154
  - name: Upload ruby.wasm to R2
90
155
  id: r2
91
- if: github.ref == 'refs/heads/master' && vars.R2_PUBLIC_URL != ''
156
+ if: >-
157
+ github.ref == 'refs/heads/master' && vars.R2_PUBLIC_URL != ''
158
+ && steps.prebuilt.outputs.found != 'true'
92
159
  env:
160
+ KEY: ${{ steps.build.outputs.key }}
93
161
  AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
94
162
  AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
95
163
  AWS_DEFAULT_REGION: auto
96
164
  # R2 sends no checksum headers back, which newer AWS CLIs insist on.
97
165
  AWS_REQUEST_CHECKSUM_CALCULATION: when_required
98
166
  AWS_RESPONSE_CHECKSUM_VALIDATION: when_required
167
+ ENDPOINT: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
168
+ BUCKET: ${{ vars.R2_BUCKET }}
169
+ # How many builds to keep, the newest included. A page loaded just
170
+ # before a deploy goes on finding its binary, and a bad build can be
171
+ # rolled back by hand; further back is 12 MB apiece for nothing.
172
+ KEEP: 3
99
173
  run: |
100
174
  gzip -9 -c ruby.wasm > /tmp/ruby.wasm.gz
101
175
 
102
- # One key, overwritten each time, since only the newest build is of
103
- # any use. That rules out caching it as immutable: the URL no longer
104
- # changes with the contents, so a long max-age would go on serving
105
- # the previous binary. Five minutes bounds how stale a visitor's
106
- # copy can be just after a deploy, and revalidation costs a 304
107
- # rather than 12 MB.
108
176
  aws s3api put-object \
109
- --endpoint-url "https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com" \
110
- --bucket "${{ vars.R2_BUCKET }}" \
111
- --key ruby-head.wasm \
177
+ --endpoint-url "$ENDPOINT" \
178
+ --bucket "$BUCKET" \
179
+ --key "$KEY" \
112
180
  --body /tmp/ruby.wasm.gz \
113
181
  --content-type application/wasm \
114
182
  --content-encoding gzip \
115
- --cache-control "public, max-age=300" \
183
+ --cache-control "public, max-age=31536000, immutable" \
116
184
  --no-cli-pager
117
185
 
118
- echo '{"rubyWasmUrl":"${{ vars.R2_PUBLIC_URL }}/ruby-head.wasm"}' > config.json
119
186
  echo "uploaded=true" >> "$GITHUB_OUTPUT"
120
187
 
188
+ # Distinct keys have nothing to remove them, so the oldest go here.
189
+ # Ordered by age rather than by name, since a key says which inputs
190
+ # built it but not which came first; the timestamps are UTC ISO 8601,
191
+ # so sorting them as text sorts them by time. `head -n -KEEP` is
192
+ # every line but the last KEEP, and prints nothing until there are
193
+ # more.
194
+ #
195
+ # The listing is written out before it is sorted, so that what the if
196
+ # tests is the listing: a pipeline would report the sort, which
197
+ # succeeds on no input either way. A failure is said rather than
198
+ # raised, since the binary is already up and a stale object left
199
+ # behind is no reason to hold back the deploy that needs the new one.
200
+ if aws s3api list-objects-v2 \
201
+ --endpoint-url "$ENDPOINT" \
202
+ --bucket "$BUCKET" \
203
+ --prefix ruby- \
204
+ --query "Contents[].[LastModified,Key]" \
205
+ --output text --no-cli-pager > /tmp/builds
206
+ then
207
+ sort /tmp/builds | head -n "-$KEEP" | cut -f2 | while read -r stale; do
208
+ # The one just uploaded is never stale, whatever the listing says
209
+ # of its age: it is the binary the page about to be deployed asks
210
+ # for. Whatever else is under the prefix is not ours to delete.
211
+ case "$stale" in
212
+ "$KEY") echo "keeping $stale, which is this build"; continue ;;
213
+ ruby-*.wasm) ;;
214
+ *) echo "leaving $stale alone"; continue ;;
215
+ esac
216
+ echo "removing $stale"
217
+ aws s3api delete-object \
218
+ --endpoint-url "$ENDPOINT" \
219
+ --bucket "$BUCKET" \
220
+ --key "$stale" \
221
+ --no-cli-pager ||
222
+ echo "::warning::could not remove $stale"
223
+ done
224
+ else
225
+ echo "::warning::could not list $BUCKET; older builds were left in place"
226
+ fi
227
+
228
+ # The preload in the head is what the page fetches the binary by, and it
229
+ # ships pointing at the local build. Naming R2 there rather than in a
230
+ # file the page has to read first is worth a round trip on every visit:
231
+ # the browser can start on the 12 MB while it is still parsing the head,
232
+ # instead of after the scripts have loaded and asked where to look.
233
+ #
234
+ # Done whether the binary went up in this run or was already there. A
235
+ # checkout with no deployment behind it keeps the line as written, and
236
+ # the artifact carries the ruby.wasm beside it.
237
+ - name: Point the page at R2
238
+ id: r2url
239
+ if: steps.prebuilt.outputs.found == 'true' || steps.r2.outputs.uploaded == 'true'
240
+ env:
241
+ URL: ${{ vars.R2_PUBLIC_URL }}/${{ steps.build.outputs.key }}
242
+ run: |
243
+ before=$(grep -c 'id="ruby-wasm"' index.html)
244
+ if [ "$before" != 1 ]; then
245
+ echo "::error::expected one preload link in index.html, found $before" >&2
246
+ exit 1
247
+ fi
248
+
249
+ sed -i "s|\(<link id=\"ruby-wasm\"[^>]*href=\)\"[^\"]*\"|\1\"$URL\"|" index.html
250
+ grep '<link id="ruby-wasm"' index.html
251
+
252
+ if ! grep -q "href=\"$URL\"" index.html; then
253
+ echo "::error::the preload link was not rewritten" >&2
254
+ exit 1
255
+ fi
256
+ echo "served=true" >> "$GITHUB_OUTPUT"
257
+
121
258
  - uses: actions/upload-artifact@v7
122
259
  with:
123
260
  name: sandbox
@@ -126,8 +263,7 @@ jobs:
126
263
  sandbox/boot.rb
127
264
  sandbox/examples.js
128
265
  sandbox/manifest.json
129
- sandbox/config.json
130
- ${{ steps.r2.outputs.uploaded != 'true' && 'sandbox/ruby.wasm' || '' }}
266
+ ${{ steps.r2url.outputs.served != 'true' && 'sandbox/ruby.wasm' || '' }}
131
267
  sandbox/rb
132
268
  sandbox/assets
133
269
  if-no-files-found: ignore
@@ -48,8 +48,21 @@ jobs:
48
48
  bundler-cache: true
49
49
  - run: bundle exec rake test
50
50
 
51
+ # Both answer to the mysql2 adapter and they do not agree: MariaDB's json
52
+ # column is a checked longtext where MySQL's is a type of its own, and
53
+ # MariaDB casts to integer where MySQL knows only signed. Each has passed
54
+ # while the other failed, so both run.
51
55
  mysql:
56
+ name: mysql (${{ matrix.image }})
52
57
  runs-on: ubuntu-latest
58
+ strategy:
59
+ fail-fast: false
60
+ matrix:
61
+ include:
62
+ - image: mysql:8
63
+ health: mysqladmin ping -proot
64
+ - image: mariadb:11
65
+ health: healthcheck.sh --connect --innodb_initialized
53
66
  env:
54
67
  ADAPTER: mysql2
55
68
  # The suite creates its own database, which MYSQL_USER would not be
@@ -58,13 +71,13 @@ jobs:
58
71
  DB_PASSWORD: root
59
72
  services:
60
73
  mysql:
61
- image: mysql:8
74
+ image: ${{ matrix.image }}
62
75
  env:
63
76
  MYSQL_ROOT_PASSWORD: root
64
77
  ports:
65
78
  - 3306:3306
66
79
  options: >-
67
- --health-cmd "mysqladmin ping -proot"
80
+ --health-cmd "${{ matrix.health }}"
68
81
  --health-interval 10s
69
82
  --health-timeout 5s
70
83
  --health-retries 5