jpzip 0.1.0 → 0.1.2

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