jpzip 0.1.1 → 0.1.3

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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +201 -43
  3. data/jpzip.gemspec +2 -2
  4. data/lib/jpzip/version.rb +1 -1
  5. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 722afa4ce028d27afd5a01811cbe334eb4eaf5304636ab20ed9838536f96a020
4
- data.tar.gz: 7286d380fbcc2a862f94a19223461add4a3621d2e1c5e49bb12209b00c0734de
3
+ metadata.gz: fb66516d09276bf4a29b63cd4cc935cec2edf686925e0ac50288e100ea852615
4
+ data.tar.gz: f292c503fdb7a6776b650bfe376b21ff2de4b8b8dc375622d6747f496b21999c
5
5
  SHA512:
6
- metadata.gz: 457012c2c4117543699b0317fcfa9137d857a4e6270cff6d1d9982fc60f75f5564288b45b125bf315e675d1327ae679711f2179f849ebfcb59ef1ed76b65e0a6
7
- data.tar.gz: 154937818e358ff358dc1a628563128a57e452417dcfa0f2149fb2f6acb4572864775e6eb8d92fe3bbf55ca0e41a91cebc49c84cd0ef8ca667267b90498f2e82
6
+ metadata.gz: d1271a9a6582f47eedc72a9aa97e45ec57883971f3a5eccfa1b6298a6cd4a5306dd66bded08a19686ca90aa723a0f4509b7d5cf8ae6f4af927b96734e46ef02e
7
+ data.tar.gz: f1096c69a095d9c2ab9b67eb5d66c6b78b08a40a8568aa3ff9925337b0e3296ea3f27d9e022f5dd669f168b78b0154b3047428b7f8c3f9f041f6b0516fbb6d4d
data/README.md CHANGED
@@ -1,90 +1,248 @@
1
- # jpzip — Ruby SDK
1
+ # jpzip
2
2
 
3
- > 日本の郵便番号を CDN 配信の JSON データから引く Ruby SDK。
3
+ [![Gem Version](https://img.shields.io/gem/v/jpzip.svg)](https://rubygems.org/gems/jpzip)
4
+ [![Gem Downloads](https://img.shields.io/gem/dt/jpzip.svg)](https://rubygems.org/gems/jpzip)
5
+ [![Docs](https://img.shields.io/badge/docs-jpzip.nadai.dev-0066cc.svg)](https://jpzip.nadai.dev)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+ [![Publish](https://github.com/jpzip/ruby/actions/workflows/publish.yml/badge.svg)](https://github.com/jpzip/ruby/actions/workflows/publish.yml)
4
8
 
5
- - 配信ドメイン: `https://jpzip.nadai.dev`
6
- - プロトコル仕様: [`jpzip/spec`](https://github.com/jpzip/spec)
7
- - データ ETL: [`jpzip/data`](https://github.com/jpzip/data)
9
+ > Ruby SDK for **jpzip** — a free, unlimited Japanese postal code (郵便番号) API.
10
+ > 日本の全郵便番号 120,677 件を CDN 配信 JSON から引く Ruby gem。
11
+
12
+ **English** | [日本語](./README.ja.md)
13
+
14
+ `jpzip` looks up Japanese postal codes (郵便番号) from `jpzip.nadai.dev`,
15
+ a CDN-hosted dataset built from Japan Post's `KEN_ALL.csv` and `KEN_ALL_ROME.csv`
16
+ normalized to JSON. No registration, no rate limits, no API key.
17
+
18
+ - 🇯🇵 **Complete dataset** — 120,677 entries with kanji, kana, romaji, and government codes (JIS X 0401 / 総務省地方公共団体コード)
19
+ - ⚡️ **Fast** — L1 LRU + optional L2 persistent cache; `preload` to serve lookups without per-request network round-trips
20
+ - 🛡️ **Resilient** — 3-attempt retry with exponential backoff on 5xx / network failures
21
+ - 🪶 **Zero runtime deps** — `net/http` + `json` + `monitor`, all stdlib
22
+ - 🆓 **Free forever** — backed by Cloudflare Pages' free tier (no billing axis exists)
23
+ - 🔌 **Drop-in** — same API surface across [every jpzip SDK](#other-languages)
24
+
25
+ ## Requirements
26
+
27
+ Ruby 3.2+ (uses `Data.define`)
28
+
29
+ ## Install
8
30
 
9
31
  ```ruby
10
32
  # Gemfile
11
33
  gem "jpzip"
12
34
  ```
13
35
 
14
- または直接:
36
+ Or directly:
15
37
 
16
38
  ```sh
17
39
  gem install jpzip
18
40
  ```
19
41
 
20
- Ruby 3.2 以上が必要(`Data.define` を使用)。
21
-
22
- ## 使い方
23
-
24
- ### モジュール関数 API
42
+ ## Quick Start
25
43
 
26
44
  ```ruby
27
45
  require "jpzip"
28
46
 
29
47
  entry = Jpzip.lookup("2310017")
30
- # entry == nil なら見つからなかった
31
- # entry.prefecture #=> "神奈川県"
32
- # entry.city #=> "横浜市中区"
33
- # entry.towns.first.town #=> "本町"
48
+ # entry is nil when not found
49
+ puts "#{entry.prefecture} #{entry.city} #{entry.towns.first.town}"
50
+ # => 神奈川県 横浜市中区 港町
51
+ ```
52
+
53
+ Romaji and government codes are included on the same entry:
54
+
55
+ ```ruby
56
+ puts "#{entry.prefecture_roma} #{entry.city_roma} #{entry.towns.first.roma}"
57
+ # => Kanagawa Ken Yokohama Shi Naka Ku Minatocho
58
+
59
+ puts "#{entry.prefecture_code} #{entry.city_code}"
60
+ # => 14 14104
61
+ ```
62
+
63
+ Entries are immutable `Data.define` values:
34
64
 
35
- dict = Jpzip.lookup_group("23") # 2 桁は 10 並列 fetch
36
- all = Jpzip.lookup_all
37
- meta = Jpzip.meta
65
+ ```ruby
66
+ entry.prefecture = "x" # raises NoMethodError
67
+ ```
68
+
69
+ ## Use Cases
70
+
71
+ ### Zipcode lookup HTTP endpoint (Rails)
38
72
 
39
- Jpzip.valid_zipcode?("2310017") #=> true
40
- Jpzip.valid_zipcode?("231-0017") #=> false
73
+ ```ruby
74
+ # config/routes.rb
75
+ get "/api/zipcode/:code", to: "zipcodes#show"
76
+
77
+ # app/controllers/zipcodes_controller.rb
78
+ class ZipcodesController < ApplicationController
79
+ def show
80
+ entry = Jpzip.lookup(params[:code])
81
+ return head :not_found if entry.nil?
82
+
83
+ render json: {
84
+ prefecture: entry.prefecture,
85
+ city: entry.city,
86
+ town: entry.towns.first&.town,
87
+ codes: { prefecture: entry.prefecture_code, city: entry.city_code }
88
+ }
89
+ end
90
+ end
41
91
  ```
42
92
 
43
- ### クライアント API (L2 キャッシュ・複数インスタンス用)
93
+ ### Zipcode lookup HTTP endpoint (Sinatra)
94
+
95
+ ```ruby
96
+ require "sinatra"
97
+ require "jpzip"
98
+ require "json"
99
+
100
+ get "/api/zipcode/:code" do
101
+ entry = Jpzip.lookup(params[:code])
102
+ halt 404 if entry.nil?
103
+
104
+ content_type :json
105
+ {
106
+ prefecture: entry.prefecture,
107
+ city: entry.city,
108
+ town: entry.towns.first&.town
109
+ }.to_json
110
+ end
111
+ ```
112
+
113
+ ### Batch validation
114
+
115
+ ```ruby
116
+ all = Jpzip.lookup_all # entire dataset in memory (~37 MiB JSON)
117
+ csv_zipcodes.each do |zip|
118
+ warn "invalid zipcode: #{zip}" unless all.key?(zip)
119
+ end
120
+ ```
121
+
122
+ ### Serve lookups from cache (BYO L2 backend)
123
+
124
+ The dataset is partitioned into 948 three-digit prefix buckets. The default
125
+ L1 (100 entries) keeps the hottest buckets; to cache the whole dataset, pair
126
+ `preload("all")` with an L2 cache or raise `memory_cache_size` above 948.
44
127
 
45
128
  ```ruby
46
129
  client = Jpzip::Client.new(
47
- base_url: "https://jpzip.nadai.dev",
48
- memory_cache_size: 200,
49
- cache: my_cache, # Jpzip::Cache サブクラス
50
- on_spec_mismatch: ->(expected, got) {
51
- warn "jpzip spec mismatch: expected=#{expected} got=#{got}"
52
- }
130
+ memory_cache_size: 1024,
131
+ cache: my_file_cache # any Jpzip::Cache subclass
53
132
  )
54
-
55
133
  client.preload("all")
134
+ # Subsequent lookups are served from L1/L2 without hitting the network.
56
135
  entry = client.lookup("2310017")
57
136
  ```
58
137
 
59
- ## Cache インターフェース
138
+ ## API Reference
139
+
140
+ ### Module functions (share a process-wide default Client)
141
+
142
+ | Function | Description |
143
+ |---|---|
144
+ | `Jpzip.lookup(zipcode)` | Look up a single 7-digit zipcode. Returns `nil` if not found or malformed (no network call for malformed input). |
145
+ | `Jpzip.lookup_group(prefix)` | Look up by 1-, 2-, or 3-digit prefix. 1-digit fetches `/g/{d}.json`; 3-digit fetches `/p/{ddd}.json`; 2-digit fans out into 10 parallel 3-digit fetches and merges. |
146
+ | `Jpzip.lookup_all` | Fetch entire dataset (120k entries, ~37 MiB) in parallel across `/g/0..9.json`. |
147
+ | `Jpzip.meta` | Dataset version, generated-at, per-prefecture counts, spec version. Result is cached until the default client is reset. |
148
+ | `Jpzip.preload(scope)` | Warm L1 (and L2 when configured) for `"all"` or a specific prefix. |
149
+ | `Jpzip.valid_zipcode?(str)` | Pure syntax check (`\A\d{7}\z`) — no network. |
150
+ | `Jpzip.configure(**opts)` | Replace the singleton with a configured `Client` (e.g. to share an L2 cache through the module helpers). |
151
+ | `Jpzip.reset_default_client!` | Drop the singleton (mainly for tests). |
152
+
153
+ ### `Jpzip::Client` (advanced)
154
+
155
+ `Client.new` returns a configurable instance; required for L2 caching, custom HTTP behavior, alternate base URL, or multiple isolated caches. Instances are thread-safe.
156
+
157
+ ```ruby
158
+ client = Jpzip::Client.new(
159
+ base_url: "https://jpzip.nadai.dev",
160
+ memory_cache_size: 200, # L1 capacity in prefix buckets, default 100
161
+ cache: my_cache, # optional L2 (Jpzip::Cache subclass)
162
+ on_spec_mismatch: ->(expected, got) {
163
+ warn "jpzip spec mismatch: SDK=#{expected} server=#{got}"
164
+ }
165
+ )
166
+ ```
167
+
168
+ `Client` exposes `#lookup` / `#lookup_group` / `#lookup_all` / `#meta` / `#preload` plus:
169
+
170
+ | Method | Description |
171
+ |---|---|
172
+ | `client.refresh` | Wipe L1 (and L2 when configured) and forget cached meta. |
173
+
174
+ When `meta` observes that `/meta.json`'s `version` has changed since the last successful fetch, L1 and L2 are cleared automatically — call `meta` periodically to pick up dataset rollovers.
175
+
176
+ ### Errors
177
+
178
+ - `Jpzip::InvalidPrefixError < ArgumentError` — raised by `lookup_group` / `preload` when the prefix is not 1-3 digits.
179
+ - `Jpzip::Http::HttpError < StandardError` — raised on 4xx (other than 404, which yields `nil`) or after exhausted retries on 5xx.
180
+ - Transient network failures and 5xx responses are retried up to 3 attempts (initial + 2 retries) with exponential backoff sleeps of 400ms and 800ms.
181
+
182
+ ### `Jpzip::Cache` interface
183
+
184
+ Bring your own L2 backend (file, Redis, Memcached, etc.) by subclassing `Jpzip::Cache`:
60
185
 
61
186
  ```ruby
62
187
  class MyFileCache < Jpzip::Cache
63
- def get(key); ...; end # => String or nil
64
- def set(key, value); ...; end
65
- def delete(key); ...; end
66
- def clear; ...; end
188
+ def get(key) # => String (raw JSON bytes) or nil
189
+ # ...
190
+ end
191
+
192
+ def set(key, value) # value is a String of bytes
193
+ # ...
194
+ end
195
+
196
+ def delete(key)
197
+ # ...
198
+ end
199
+
200
+ def clear
201
+ # ...
202
+ end
67
203
  end
68
204
  ```
69
205
 
70
- ファイル / Redis / Memcached 等の任意の実装を渡せる。L2 は明示的に有効化した場合のみ使われ、デフォルトは L1 (メモリ LRU) のみ。
206
+ Keys are the full prefix-bucket URLs (e.g. `https://jpzip.nadai.dev/p/231.json`); values are raw JSON bytes.
207
+
208
+ ### Data types
209
+
210
+ `Jpzip::ZipcodeEntry` and `Jpzip::Town` are immutable `Data.define` classes with `from_hash` / `to_h` helpers. Fields: `prefecture`, `prefecture_kana`, `prefecture_roma`, `prefecture_code`, `city`, `city_kana`, `city_roma`, `city_code`, `towns` (Array of `Town`). `Town` has `town`, `kana`, `roma`, `note`.
211
+
212
+ ## Why jpzip?
213
+
214
+ | | **jpzip** | [jpostcode][jpostcode] | [ken_all][kenall] | [zipcloud API][zipcloud] |
215
+ |---|---|---|---|---|
216
+ | Romaji (`Yokohama Shi`) | ✅ | ❌ | ❌ | ❌ |
217
+ | Government codes (JIS / 総務省) | ✅ | ⚠️ Prefecture only | ⚠️ JIS only | ❌ |
218
+ | No manual CSV / submodule sync | ✅ | ❌ Git submodule | ❌ Rake task | ✅ |
219
+ | Monthly updates | ✅ Auto | ⚠️ Manual submodule | ❌ Manual | ✅ |
220
+ | Offline after preload | ✅ | ✅ Local data | ✅ Local DB | ❌ |
221
+ | Rate-limit-free | ✅ | ✅ | ✅ | ⚠️ Discouraged |
222
+ | L1 + pluggable L2 cache | ✅ | ❌ | ❌ | ❌ |
223
+ | Zero runtime dependencies | ✅ | ⚠️ jpostcode-data submodule | ❌ Rails / activerecord-import / rubyzip / curses | n/a |
71
224
 
72
- ## 入力検証
225
+ [jpostcode]: https://github.com/kufu/jpostcode-rb
226
+ [kenall]: https://github.com/ozin/ken_all
227
+ [zipcloud]: http://zipcloud.ibsnet.co.jp/doc/api
73
228
 
74
- `Jpzip.lookup` `\A\d{7}\z` にマッチしない入力に対して fetch せず `nil` を返す。
229
+ ## Other Languages
75
230
 
76
- ## バージョン整合性
231
+ Same API surface across all SDKs:
77
232
 
78
- `Jpzip.meta` 取得時、`spec_version` SDK 対応バージョンと異なる場合 `on_spec_mismatch` コールバックが 1 度だけ呼ばれる。データバージョンが変わったら L1/L2 を自動 invalidate する。
233
+ [Go](https://github.com/jpzip/go) · [TypeScript](https://github.com/jpzip/js) · [Python](https://github.com/jpzip/python) · [Rust](https://github.com/jpzip/rust) · [PHP](https://github.com/jpzip/php) · [Swift](https://github.com/jpzip/swift) · [Dart](https://github.com/jpzip/dart)
79
234
 
80
- ## 並列性とスレッドセーフ
235
+ ## Resources
81
236
 
82
- `Jpzip::Client` はスレッドセーフ。複数スレッドから同一インスタンスを共有しても安全。`lookup_group("23")` `lookup_all` は内部で `Thread` を使って並列 fetch する。
237
+ - **Website** https://jpzip.nadai.dev
238
+ - **Protocol spec** — [jpzip/spec](https://github.com/jpzip/spec)
239
+ - **Data ETL** — [jpzip/data](https://github.com/jpzip/data)
240
+ - **MCP server** — [jpzip/mcp](https://github.com/jpzip/mcp) — use jpzip from Claude / ChatGPT / Cursor
83
241
 
84
- ## 依存
242
+ ## Keywords
85
243
 
86
- なし。Ruby 標準ライブラリ (`net/http`, `json`, `monitor`) のみ使用。
244
+ japanese postal code, japan zipcode, 郵便番号, KEN_ALL, KEN_ALL_ROME, address validation, japan address api, postal code lookup ruby, ruby japanese address gem, JIS X 0401, 総務省地方公共団体コード, rails postal code
87
245
 
88
- ## ライセンス
246
+ ## License
89
247
 
90
248
  [MIT](./LICENSE)
data/jpzip.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = "Ruby SDK for the jpzip Japanese postal-code dataset"
12
12
  spec.description = "jpzip は日本の郵便番号を CDN 配信の JSON データから引く Ruby SDK。" \
13
13
  "L1 LRU メモリキャッシュを内蔵し、任意の L2 永続キャッシュを差し込める。"
14
- spec.homepage = "https://github.com/jpzip/ruby"
14
+ spec.homepage = "https://jpzip.nadai.dev"
15
15
  spec.license = "MIT"
16
16
  spec.required_ruby_version = ">= 3.2"
17
17
 
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  "homepage_uri" => spec.homepage,
20
20
  "source_code_uri" => "https://github.com/jpzip/ruby",
21
21
  "bug_tracker_uri" => "https://github.com/jpzip/ruby/issues",
22
- "documentation_uri" => "https://github.com/jpzip/ruby",
22
+ "documentation_uri" => "https://jpzip.nadai.dev",
23
23
  "rubygems_mfa_required" => "true"
24
24
  }
25
25
 
data/lib/jpzip/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Jpzip
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
 
6
6
  # SpecVersion is the jpzip protocol version this SDK targets.
7
7
  SPEC_VERSION = "1.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jpzip
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - nadai
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-16 00:00:00.000000000 Z
11
+ date: 2026-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -69,14 +69,14 @@ files:
69
69
  - lib/jpzip/http.rb
70
70
  - lib/jpzip/types.rb
71
71
  - lib/jpzip/version.rb
72
- homepage: https://github.com/jpzip/ruby
72
+ homepage: https://jpzip.nadai.dev
73
73
  licenses:
74
74
  - MIT
75
75
  metadata:
76
- homepage_uri: https://github.com/jpzip/ruby
76
+ homepage_uri: https://jpzip.nadai.dev
77
77
  source_code_uri: https://github.com/jpzip/ruby
78
78
  bug_tracker_uri: https://github.com/jpzip/ruby/issues
79
- documentation_uri: https://github.com/jpzip/ruby
79
+ documentation_uri: https://jpzip.nadai.dev
80
80
  rubygems_mfa_required: 'true'
81
81
  post_install_message:
82
82
  rdoc_options: []