@abcubed3/postcode-wasm 0.0.0-stage → 0.1.7
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.
- package/README.md +171 -2
- package/index.d.ts +245 -0
- package/index.js +81 -0
- package/package.json +32 -4
- package/postcode.wasm +0 -0
- package/wasm_exec.js +575 -0
package/README.md
CHANGED
|
@@ -1,3 +1,172 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Postcode Nigeria WebAssembly (WASM)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Zero-dependency, offline-first Nigerian Postcode engine compiled to WebAssembly. Enables AI agents, edge workers, mobile apps, and web browsers to perform high-speed validation, diagnostics, formatting, administrative segment parsing/assembly, geocoding, and agent tool execution without network overhead.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Blazing Fast**: Microsecond execution on edge runtimes (Cloudflare Workers, Fastly, Vercel Edge, AWS Lambda@Edge).
|
|
8
|
+
- **Full Parity with Go Native**: Exposes all 19 Go library and CLI capabilities directly to Node.js, Bun, Deno, and web browsers.
|
|
9
|
+
- **Zero Network Overhead (Offline Core)**: Validation, batch validation, format repair tips, parsing, formatting, segment assembly/disassembly, geocoding, and synthetic address benchmarking run 100% offline in WASM memory.
|
|
10
|
+
- **NIPOST Gateway Integration (Online)**: Level 1–3 lookups, address autocompletion, radius-based nearby search, and reverse geocoding via standard async Promises.
|
|
11
|
+
- **Native AI Agent Protocols**: Pre-formatted tool definitions ready for OpenAI (`function`), Anthropic (`input_schema`), and Google Gemini (`functionDeclarations`), with a unified `executeTool` dispatcher and telemetry guardrails.
|
|
12
|
+
|
|
13
|
+
## Building the WASM Binary
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
GOOS=js GOARCH=wasm go build -ldflags="-s -w" -o wasm/postcode.wasm ./wasm
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start (Node.js / Edge)
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
const { initPostcode } = require('@abcubed3/postcode-wasm');
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
const postcode = await initPostcode();
|
|
26
|
+
|
|
27
|
+
// 1. Single & Batch Validation
|
|
28
|
+
const valid = postcode.validate('EK 01 A03 FK 01');
|
|
29
|
+
console.log(valid.valid, valid.state_name); // true, 'Ekiti'
|
|
30
|
+
|
|
31
|
+
const batch = postcode.validateBatch(['EK 01 A03 FK 01', 'LA 11 W06 TC 10', 'INVALID']);
|
|
32
|
+
console.log(batch.map(r => r.valid)); // [true, true, false]
|
|
33
|
+
|
|
34
|
+
// 2. Formatting & Styles (canonical, spaced, compact, slug)
|
|
35
|
+
console.log(postcode.format('EK 01 A03 FK 01', 'compact')); // 'EK01A03FK01'
|
|
36
|
+
console.log(postcode.format('EK01A03FK01', 'canonical')); // 'EK-01-A03-FK-01'
|
|
37
|
+
console.log(postcode.format('EK 01 A03 FK 01', 'slug')); // 'ek-01-a03-fk-01'
|
|
38
|
+
|
|
39
|
+
// 3. Administrative Disassembly & Assembly
|
|
40
|
+
const segs = postcode.disassemble('EK-01-A03-FK-01');
|
|
41
|
+
// { state: 'EK', lga: '01', district: 'A03', area: 'FK', unit: '01' }
|
|
42
|
+
|
|
43
|
+
const assembled = postcode.assemble({
|
|
44
|
+
state: 'LA', lga: '11', district: 'W06', area: 'TC', unit: '10'
|
|
45
|
+
});
|
|
46
|
+
console.log(assembled.postcode, assembled.display); // 'LA-11-W06-TC-10', 'LA 11 W06 TC 10'
|
|
47
|
+
|
|
48
|
+
// 4. Intelligent Diagnostic Engine
|
|
49
|
+
const diag = postcode.diagnose('10001');
|
|
50
|
+
console.log(diag.actionable_tip);
|
|
51
|
+
// "Fix Length: Exactly 11 alphanumeric characters (e.g. 'EK 01 A03 FK 01'); Fix State: consider LA (Lagos)..."
|
|
52
|
+
|
|
53
|
+
// 5. Offline Geolocation & Universal Maps
|
|
54
|
+
const loc = postcode.resolveLocation('EK 01 A03 FK 01');
|
|
55
|
+
console.log(loc.latitude, loc.longitude);
|
|
56
|
+
console.log(loc.google_maps_directions_url); // Navigation URL
|
|
57
|
+
console.log(loc.apple_maps_url); // Apple Maps URL
|
|
58
|
+
|
|
59
|
+
// 6. AI Agent Protocol (OpenAI, Anthropic, Gemini)
|
|
60
|
+
const openAITools = postcode.getAgentTools('openai');
|
|
61
|
+
const toolResult = await postcode.executeTool('validate_postcode', { code: 'EK 01 A03 FK 01' });
|
|
62
|
+
console.log('Agent tool execution:', toolResult.valid, toolResult.state_name);
|
|
63
|
+
|
|
64
|
+
// 7. Synthetic Address Generation for LLM Benchmarking
|
|
65
|
+
const dataset = postcode.generateSyntheticAddresses({ count: 5, noiseRate: 0.2 });
|
|
66
|
+
console.log(dataset[0].raw_text, dataset[0].expected_postcode);
|
|
67
|
+
|
|
68
|
+
// 8. Live Gateway Operations (L1-L3 Lookup, Autocomplete, Nearby, Reverse Geocoding)
|
|
69
|
+
postcode.setAPIKey('nipost_live_your_api_key_here');
|
|
70
|
+
|
|
71
|
+
// Query live NIPOST Gateway for commercial building data
|
|
72
|
+
const details = await postcode.lookup('EK 01 A03 FK 01', 3);
|
|
73
|
+
console.log(details.building_use_status);
|
|
74
|
+
|
|
75
|
+
// Autocomplete search
|
|
76
|
+
const suggestions = await postcode.autocomplete('Adetokunbo');
|
|
77
|
+
console.log(suggestions.results);
|
|
78
|
+
|
|
79
|
+
// Search nearby units
|
|
80
|
+
const nearby = await postcode.nearby({ latitude: 6.6018, longitude: 3.3515, radiusKm: 2 });
|
|
81
|
+
console.log(nearby.results);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## API Reference
|
|
88
|
+
|
|
89
|
+
### Core Offline Engine (Sync)
|
|
90
|
+
| Method | Description |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `validate(code: string): ValidationResult` | Instant 11-char grammar & state validation. |
|
|
93
|
+
| `validateBatch(codes: string[]): ValidationResult[]` | High-throughput batch validation in WebAssembly memory. |
|
|
94
|
+
| `diagnose(code: string): DiagnosticReport` | Granular per-segment error detection with fuzzy repair tips. |
|
|
95
|
+
| `parse(code: string): ParsedPostcode` | Extracts full parsed model with capital, zone, and components. |
|
|
96
|
+
| `format(code: string, style?: string): string` | Normalizes to `canonical`, `compact`, `spaced`, `hyphenated`, or `slug`. |
|
|
97
|
+
| `assemble(segments: Segments): AssembledPostcode` | Assembles 5 administrative segments into canonical, display, and compact codes. |
|
|
98
|
+
| `disassemble(code: string): Segments` | Decomposes code into state, LGA, district, area, and building unit. |
|
|
99
|
+
| `resolveLocation(code: string): LocationResult` | Resolves offline centroid coordinates, Google Maps, Apple Maps, and OSM links. |
|
|
100
|
+
| `listStates(): Record<string, StateRecord>` | Returns static dictionary of all 36 Nigerian States + FCT. |
|
|
101
|
+
|
|
102
|
+
### AI Agent Protocol & Benchmarking
|
|
103
|
+
| Method | Description |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `getAgentTools(format?: 'openai' \| 'anthropic' \| 'gemini'): any[]` | Exports JSON schemas for agent function calling. |
|
|
106
|
+
| `executeTool(name: string, args: any): Promise<any>` | Directly invokes agent tool handlers with structured responses. |
|
|
107
|
+
| `getAgentMetrics(): AgentGuardMetrics` | Telemetry for commercial vs. offline calls and downgrade rates. |
|
|
108
|
+
| `generateSyntheticAddresses(options?: GeneratorOptions): SyntheticAddress[]` | Generates realistic dirty/clean addresses with ground truth for LLM evaluation. |
|
|
109
|
+
|
|
110
|
+
### Live Gateway Operations (Async)
|
|
111
|
+
| Method | Description |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `lookup(code: string, level?: number): Promise<LookupResponse>` | Queries NIPOST Gateway for Level 1, 2, or 3 commercial metadata. |
|
|
114
|
+
| `autocomplete(query: string): Promise<AutocompleteResponse>` | Real-time address and street suggestions. |
|
|
115
|
+
| `nearby(params: NearbyParams): Promise<NearbyResponse>` | Radius search for postcode units surrounding a coordinate. |
|
|
116
|
+
| `reverseGeocode(params: ReverseParams): Promise<ReverseResponse>` | Reverse-resolves latitude and longitude into nearest valid postcode. |
|
|
117
|
+
|
|
118
|
+
## Configuring the NIPOST API Key
|
|
119
|
+
|
|
120
|
+
### 1. Offline vs. Online Boundary
|
|
121
|
+
- **Offline Methods (No API Key Required)**: `validate()`, `validateBatch()`, `diagnose()`, `parse()`, `format()`, `assemble()`, `disassemble()`, `resolveLocation()`, `listStates()`, `getAgentTools()`, and `generateSyntheticAddresses()` run 100% offline in WebAssembly memory with zero network latency.
|
|
122
|
+
- **Online Methods (API Key Configurable)**: `lookup()`, `autocomplete()`, `nearby()`, and `reverseGeocode()` connect to the live NIPOST Gateway (for commercial Level 2/3 street names, building use status, and GIS point geometry).
|
|
123
|
+
|
|
124
|
+
### 2. Ways to Set or Update the API Key
|
|
125
|
+
|
|
126
|
+
#### Option A: Dynamically at Runtime
|
|
127
|
+
```javascript
|
|
128
|
+
postcode.setAPIKey('nipost_live_your_new_key');
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### Option B: Full Configuration Object
|
|
132
|
+
```javascript
|
|
133
|
+
postcode.configure({
|
|
134
|
+
apiKey: 'nipost_live_your_key',
|
|
135
|
+
baseURL: 'https://api.postcode.gov.ng' // optional custom gateway
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### Option C: At Engine Initialization
|
|
140
|
+
```javascript
|
|
141
|
+
const postcode = await initPostcode(undefined, {
|
|
142
|
+
apiKey: 'nipost_live_your_key'
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
#### Option D: Automatic Environment Variable (Node.js)
|
|
147
|
+
In Node.js, `initPostcode()` automatically detects `NIPOST_API_KEY` or `POSTCODE_API_KEY` from `process.env`.
|
|
148
|
+
```bash
|
|
149
|
+
export NIPOST_API_KEY=nipost_live_...
|
|
150
|
+
node app.js
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Browser Usage
|
|
154
|
+
|
|
155
|
+
```html
|
|
156
|
+
<script src="wasm_exec.js"></script>
|
|
157
|
+
<script>
|
|
158
|
+
async function loadWasm() {
|
|
159
|
+
const go = new Go();
|
|
160
|
+
const result = await WebAssembly.instantiateStreaming(fetch("postcode.wasm"), go.importObject);
|
|
161
|
+
go.run(result.instance);
|
|
162
|
+
|
|
163
|
+
// window.Postcode is now globally available
|
|
164
|
+
const res = window.Postcode.validate("EK 01 A03 FK 01");
|
|
165
|
+
console.log(res);
|
|
166
|
+
|
|
167
|
+
const loc = window.Postcode.resolveLocation("EK 01 A03 FK 01");
|
|
168
|
+
console.log("Directions:", loc.google_maps_directions_url);
|
|
169
|
+
}
|
|
170
|
+
loadWasm();
|
|
171
|
+
</script>
|
|
172
|
+
```
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript definitions for Postcode Nigeria WebAssembly module.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ValidationResult {
|
|
6
|
+
valid: boolean;
|
|
7
|
+
postcode?: string;
|
|
8
|
+
compact?: string;
|
|
9
|
+
state_code?: string;
|
|
10
|
+
state_name?: string;
|
|
11
|
+
lga_code?: string;
|
|
12
|
+
district?: string;
|
|
13
|
+
area?: string;
|
|
14
|
+
unit?: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
actionable_tip?: string;
|
|
17
|
+
clean_length?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface Segments {
|
|
21
|
+
valid?: boolean;
|
|
22
|
+
error?: string;
|
|
23
|
+
state: string;
|
|
24
|
+
lga: string;
|
|
25
|
+
district: string;
|
|
26
|
+
area: string;
|
|
27
|
+
unit: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AssembledPostcode {
|
|
31
|
+
valid: boolean;
|
|
32
|
+
error?: string;
|
|
33
|
+
postcode?: string;
|
|
34
|
+
display?: string;
|
|
35
|
+
compact?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ParsedPostcode {
|
|
39
|
+
valid: boolean;
|
|
40
|
+
input?: string;
|
|
41
|
+
postcode?: string;
|
|
42
|
+
formatted?: string;
|
|
43
|
+
compact?: string;
|
|
44
|
+
spaced?: string;
|
|
45
|
+
state_code?: string;
|
|
46
|
+
state_name?: string;
|
|
47
|
+
lga_code?: string;
|
|
48
|
+
lga_name?: string;
|
|
49
|
+
district?: string;
|
|
50
|
+
area?: string;
|
|
51
|
+
unit?: string;
|
|
52
|
+
zone?: string;
|
|
53
|
+
state_capital?: string;
|
|
54
|
+
error?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface SegmentDiagnosis {
|
|
58
|
+
segment: 'Length' | 'State' | 'LGA' | 'District' | 'Area' | 'BuildingUnit';
|
|
59
|
+
input: string;
|
|
60
|
+
expected: string;
|
|
61
|
+
message: string;
|
|
62
|
+
suggestions?: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface DiagnosticReport {
|
|
66
|
+
input: string;
|
|
67
|
+
valid: boolean;
|
|
68
|
+
normalized?: string;
|
|
69
|
+
clean_length: number;
|
|
70
|
+
format_score: number;
|
|
71
|
+
diagnoses?: SegmentDiagnosis[];
|
|
72
|
+
actionable_tip?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface LocationResult {
|
|
76
|
+
postcode: string;
|
|
77
|
+
compact: string;
|
|
78
|
+
latitude: number;
|
|
79
|
+
longitude: number;
|
|
80
|
+
address?: string;
|
|
81
|
+
state?: string;
|
|
82
|
+
state_code: string;
|
|
83
|
+
state_name: string;
|
|
84
|
+
lga?: string;
|
|
85
|
+
lga_code?: string;
|
|
86
|
+
lga_name?: string;
|
|
87
|
+
zone?: string;
|
|
88
|
+
precision: 'building' | 'area' | 'district' | 'lga' | 'state' | number;
|
|
89
|
+
google_maps_url: string;
|
|
90
|
+
google_maps_directions_url?: string;
|
|
91
|
+
apple_maps_url: string;
|
|
92
|
+
osm_url: string;
|
|
93
|
+
search_query?: string;
|
|
94
|
+
error?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface AdministrativeAddress {
|
|
98
|
+
state?: string;
|
|
99
|
+
state_name?: string;
|
|
100
|
+
lga?: string;
|
|
101
|
+
lga_name?: string;
|
|
102
|
+
district?: string;
|
|
103
|
+
district_name?: string;
|
|
104
|
+
area?: string;
|
|
105
|
+
area_name?: string;
|
|
106
|
+
unit?: string;
|
|
107
|
+
zone?: string;
|
|
108
|
+
locality_name?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface RecentHouseAddress {
|
|
112
|
+
address?: string;
|
|
113
|
+
recent?: string;
|
|
114
|
+
house_number?: string;
|
|
115
|
+
street_name?: string;
|
|
116
|
+
locality_name?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface PointGeometry {
|
|
120
|
+
type?: string;
|
|
121
|
+
coordinates?: [number, number];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface LookupResponse {
|
|
125
|
+
postcode: string;
|
|
126
|
+
valid: boolean;
|
|
127
|
+
administrative_address?: AdministrativeAddress;
|
|
128
|
+
recent_house_address?: RecentHouseAddress;
|
|
129
|
+
building_use_status?: string;
|
|
130
|
+
other_building_info?: Record<string, any>;
|
|
131
|
+
point_geometry?: PointGeometry;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface AutocompleteSuggestion {
|
|
135
|
+
postcode: string;
|
|
136
|
+
description: string;
|
|
137
|
+
score?: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface AutocompleteResponse {
|
|
141
|
+
query: string;
|
|
142
|
+
suggestions: AutocompleteSuggestion[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface NearbyUnit {
|
|
146
|
+
postcode: string;
|
|
147
|
+
display: string;
|
|
148
|
+
distance_m: number;
|
|
149
|
+
confidence: string;
|
|
150
|
+
state_name?: string;
|
|
151
|
+
lga_name?: string;
|
|
152
|
+
address?: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface NearbyResponse {
|
|
156
|
+
results: NearbyUnit[];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface ReverseResponse {
|
|
160
|
+
latitude: number;
|
|
161
|
+
longitude: number;
|
|
162
|
+
postcode: string;
|
|
163
|
+
distance_m?: number;
|
|
164
|
+
location?: LocationResult;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface AgentGuardMetrics {
|
|
168
|
+
total_calls: number;
|
|
169
|
+
commercial_calls: number;
|
|
170
|
+
downgraded_calls: number;
|
|
171
|
+
offline_fallbacks: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface SyntheticAddress {
|
|
175
|
+
raw_text: string;
|
|
176
|
+
expected_state: string;
|
|
177
|
+
expected_state_code: string;
|
|
178
|
+
expected_lga?: string;
|
|
179
|
+
expected_postcode?: string;
|
|
180
|
+
expected_lat?: number;
|
|
181
|
+
expected_lng?: number;
|
|
182
|
+
noise_type?: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface GeneratorOptions {
|
|
186
|
+
count?: number;
|
|
187
|
+
noiseRate?: number;
|
|
188
|
+
seed?: number;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface PostcodeEngineOptions {
|
|
192
|
+
apiKey?: string;
|
|
193
|
+
baseURL?: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface StateRecord {
|
|
197
|
+
code: string;
|
|
198
|
+
name: string;
|
|
199
|
+
capital: string;
|
|
200
|
+
latitude: number;
|
|
201
|
+
longitude: number;
|
|
202
|
+
zone: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface PostcodeEngine {
|
|
206
|
+
// --- 1. Core Offline (Sync) ---
|
|
207
|
+
validate(code: string): ValidationResult;
|
|
208
|
+
validateBatch(codes: string[]): ValidationResult[];
|
|
209
|
+
diagnose(code: string): DiagnosticReport;
|
|
210
|
+
parse(code: string): ParsedPostcode;
|
|
211
|
+
format(code: string, style?: 'canonical' | 'compact' | 'spaced' | 'hyphenated' | 'slug'): string;
|
|
212
|
+
assemble(segments: { state: string; lga: string; district: string; area: string; unit: string }): AssembledPostcode;
|
|
213
|
+
disassemble(code: string): Segments;
|
|
214
|
+
resolveLocation(code: string): LocationResult;
|
|
215
|
+
listStates(): Record<string, StateRecord>;
|
|
216
|
+
|
|
217
|
+
// --- 2. Configuration & State (Sync) ---
|
|
218
|
+
setAPIKey(key: string): boolean;
|
|
219
|
+
getAPIKey(): string;
|
|
220
|
+
configure(options: PostcodeEngineOptions): boolean;
|
|
221
|
+
|
|
222
|
+
// --- 3. Live Gateway Operations (Async) ---
|
|
223
|
+
lookup(code: string, level?: number): Promise<LookupResponse>;
|
|
224
|
+
autocomplete(query: string): Promise<AutocompleteResponse>;
|
|
225
|
+
nearby(params: { latitude: number; longitude: number; radiusKm?: number; radius_m?: number; limit?: number }): Promise<NearbyResponse>;
|
|
226
|
+
reverseGeocode(params: { latitude: number; longitude: number; maxDistanceKm?: number; max_distance_m?: number }): Promise<ReverseResponse>;
|
|
227
|
+
|
|
228
|
+
// --- 4. AI Agent Tooling & Guardrails ---
|
|
229
|
+
getAgentTools(format?: 'openai' | 'anthropic' | 'gemini'): any[];
|
|
230
|
+
executeTool(name: string, args?: Record<string, any> | string): Promise<any>;
|
|
231
|
+
getAgentMetrics(): AgentGuardMetrics;
|
|
232
|
+
|
|
233
|
+
// --- 5. Synthetic Evaluation Benchmark ---
|
|
234
|
+
generateSyntheticAddresses(options?: GeneratorOptions): SyntheticAddress[];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Initializes and instantiates the Postcode Nigeria WebAssembly module.
|
|
239
|
+
* @param wasmSourceOrOptions Optional custom path, buffer, fetch Response, or options object.
|
|
240
|
+
* @param options Optional configuration options when wasmSource is specified.
|
|
241
|
+
*/
|
|
242
|
+
export function initPostcode(
|
|
243
|
+
wasmSourceOrOptions?: string | Buffer | ArrayBuffer | Response | PostcodeEngineOptions,
|
|
244
|
+
options?: PostcodeEngineOptions
|
|
245
|
+
): Promise<PostcodeEngine>;
|
package/index.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postcode Nigeria WebAssembly Loader
|
|
3
|
+
* Supports Node.js, Deno, Bun, and browser environments.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = typeof window === 'undefined' ? require('fs') : null;
|
|
7
|
+
const path = typeof window === 'undefined' ? require('path') : null;
|
|
8
|
+
|
|
9
|
+
// Require wasm_exec.js if running in Node.js
|
|
10
|
+
if (typeof window === 'undefined' && typeof globalThis.Go === 'undefined') {
|
|
11
|
+
require('./wasm_exec.js');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initializes the Postcode WebAssembly module.
|
|
16
|
+
* @param {string|Buffer|ArrayBuffer|Response|{ apiKey?: string, baseURL?: string }} [wasmSource] Optional path, buffer, fetch response, or options.
|
|
17
|
+
* @param {{ apiKey?: string, baseURL?: string }} [options] Optional configuration options.
|
|
18
|
+
* @returns {Promise<typeof globalThis.Postcode>} The initialized Postcode module.
|
|
19
|
+
*/
|
|
20
|
+
async function initPostcode(wasmSource, options = {}) {
|
|
21
|
+
if (wasmSource && typeof wasmSource === 'object' && !(wasmSource instanceof ArrayBuffer) && !(typeof Buffer !== 'undefined' && Buffer.isBuffer(wasmSource)) && !(typeof Response !== 'undefined' && wasmSource instanceof Response)) {
|
|
22
|
+
options = wasmSource;
|
|
23
|
+
wasmSource = undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (globalThis.Postcode) {
|
|
27
|
+
if (options.apiKey) globalThis.Postcode.setAPIKey(options.apiKey);
|
|
28
|
+
if (options.baseURL) globalThis.Postcode.configure({ baseURL: options.baseURL });
|
|
29
|
+
return globalThis.Postcode;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const go = new globalThis.Go();
|
|
33
|
+
let wasmBytes;
|
|
34
|
+
|
|
35
|
+
if (!wasmSource) {
|
|
36
|
+
if (typeof window === 'undefined') {
|
|
37
|
+
const defaultPath = path.resolve(__dirname, 'postcode.wasm');
|
|
38
|
+
if (fs.existsSync(defaultPath)) {
|
|
39
|
+
wasmBytes = fs.readFileSync(defaultPath);
|
|
40
|
+
} else {
|
|
41
|
+
throw new Error(`postcode.wasm not found at ${defaultPath}. Build it first with: GOOS=js GOARCH=wasm go build -o wasm/postcode.wasm ./wasm`);
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
wasmSource = 'postcode.wasm';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let instance;
|
|
49
|
+
if (wasmBytes) {
|
|
50
|
+
const result = await WebAssembly.instantiate(wasmBytes, go.importObject);
|
|
51
|
+
instance = result.instance;
|
|
52
|
+
} else if (typeof wasmSource === 'string' && typeof fetch !== 'undefined') {
|
|
53
|
+
const response = await fetch(wasmSource);
|
|
54
|
+
const result = await WebAssembly.instantiateStreaming(response, go.importObject);
|
|
55
|
+
instance = result.instance;
|
|
56
|
+
} else if (wasmSource instanceof ArrayBuffer || (typeof Buffer !== 'undefined' && Buffer.isBuffer(wasmSource))) {
|
|
57
|
+
const result = await WebAssembly.instantiate(wasmSource, go.importObject);
|
|
58
|
+
instance = result.instance;
|
|
59
|
+
} else {
|
|
60
|
+
throw new Error('Unsupported wasmSource format');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Run the Go runtime in background (it will register globalThis.Postcode)
|
|
64
|
+
go.run(instance);
|
|
65
|
+
|
|
66
|
+
// Configure API key if provided via options or environment variable
|
|
67
|
+
const envKey = typeof process !== 'undefined' && process.env ? (process.env.NIPOST_API_KEY || process.env.POSTCODE_API_KEY) : undefined;
|
|
68
|
+
const apiKey = options.apiKey || envKey;
|
|
69
|
+
if (apiKey) {
|
|
70
|
+
globalThis.Postcode.setAPIKey(apiKey);
|
|
71
|
+
}
|
|
72
|
+
if (options.baseURL) {
|
|
73
|
+
globalThis.Postcode.configure({ baseURL: options.baseURL });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return globalThis.Postcode;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
initPostcode,
|
|
81
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abcubed3/postcode-wasm",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "Zero-dependency Nigerian Postcode validation, format diagnostics, and geocoding engine compiled to WebAssembly from Go",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.js",
|
|
9
|
+
"index.d.ts",
|
|
10
|
+
"wasm_exec.js",
|
|
11
|
+
"postcode.wasm",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"nigeria",
|
|
16
|
+
"postcode",
|
|
17
|
+
"nipost",
|
|
18
|
+
"geocoding",
|
|
19
|
+
"wasm",
|
|
20
|
+
"webassembly",
|
|
21
|
+
"address",
|
|
22
|
+
"validation"
|
|
23
|
+
],
|
|
24
|
+
"author": "abcubed3",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/abcubed3/postcode.git",
|
|
29
|
+
"directory": "wasm"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/postcode.wasm
ADDED
|
Binary file
|
package/wasm_exec.js
ADDED
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
// Copyright 2018 The Go Authors. All rights reserved.
|
|
2
|
+
// Use of this source code is governed by a BSD-style
|
|
3
|
+
// license that can be found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
(() => {
|
|
8
|
+
const enosys = () => {
|
|
9
|
+
const err = new Error("not implemented");
|
|
10
|
+
err.code = "ENOSYS";
|
|
11
|
+
return err;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
if (!globalThis.fs) {
|
|
15
|
+
let outputBuf = "";
|
|
16
|
+
globalThis.fs = {
|
|
17
|
+
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused
|
|
18
|
+
writeSync(fd, buf) {
|
|
19
|
+
outputBuf += decoder.decode(buf);
|
|
20
|
+
const nl = outputBuf.lastIndexOf("\n");
|
|
21
|
+
if (nl != -1) {
|
|
22
|
+
console.log(outputBuf.substring(0, nl));
|
|
23
|
+
outputBuf = outputBuf.substring(nl + 1);
|
|
24
|
+
}
|
|
25
|
+
return buf.length;
|
|
26
|
+
},
|
|
27
|
+
write(fd, buf, offset, length, position, callback) {
|
|
28
|
+
if (offset !== 0 || length !== buf.length || position !== null) {
|
|
29
|
+
callback(enosys());
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const n = this.writeSync(fd, buf);
|
|
33
|
+
callback(null, n);
|
|
34
|
+
},
|
|
35
|
+
chmod(path, mode, callback) { callback(enosys()); },
|
|
36
|
+
chown(path, uid, gid, callback) { callback(enosys()); },
|
|
37
|
+
close(fd, callback) { callback(enosys()); },
|
|
38
|
+
fchmod(fd, mode, callback) { callback(enosys()); },
|
|
39
|
+
fchown(fd, uid, gid, callback) { callback(enosys()); },
|
|
40
|
+
fstat(fd, callback) { callback(enosys()); },
|
|
41
|
+
fsync(fd, callback) { callback(null); },
|
|
42
|
+
ftruncate(fd, length, callback) { callback(enosys()); },
|
|
43
|
+
lchown(path, uid, gid, callback) { callback(enosys()); },
|
|
44
|
+
link(path, link, callback) { callback(enosys()); },
|
|
45
|
+
lstat(path, callback) { callback(enosys()); },
|
|
46
|
+
mkdir(path, perm, callback) { callback(enosys()); },
|
|
47
|
+
open(path, flags, mode, callback) { callback(enosys()); },
|
|
48
|
+
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
|
|
49
|
+
readdir(path, callback) { callback(enosys()); },
|
|
50
|
+
readlink(path, callback) { callback(enosys()); },
|
|
51
|
+
rename(from, to, callback) { callback(enosys()); },
|
|
52
|
+
rmdir(path, callback) { callback(enosys()); },
|
|
53
|
+
stat(path, callback) { callback(enosys()); },
|
|
54
|
+
symlink(path, link, callback) { callback(enosys()); },
|
|
55
|
+
truncate(path, length, callback) { callback(enosys()); },
|
|
56
|
+
unlink(path, callback) { callback(enosys()); },
|
|
57
|
+
utimes(path, atime, mtime, callback) { callback(enosys()); },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!globalThis.process) {
|
|
62
|
+
globalThis.process = {
|
|
63
|
+
getuid() { return -1; },
|
|
64
|
+
getgid() { return -1; },
|
|
65
|
+
geteuid() { return -1; },
|
|
66
|
+
getegid() { return -1; },
|
|
67
|
+
getgroups() { throw enosys(); },
|
|
68
|
+
pid: -1,
|
|
69
|
+
ppid: -1,
|
|
70
|
+
umask() { throw enosys(); },
|
|
71
|
+
cwd() { throw enosys(); },
|
|
72
|
+
chdir() { throw enosys(); },
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!globalThis.path) {
|
|
77
|
+
globalThis.path = {
|
|
78
|
+
resolve(...pathSegments) {
|
|
79
|
+
return pathSegments.join("/");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!globalThis.crypto) {
|
|
85
|
+
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!globalThis.performance) {
|
|
89
|
+
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!globalThis.TextEncoder) {
|
|
93
|
+
throw new Error("globalThis.TextEncoder is not available, polyfill required");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!globalThis.TextDecoder) {
|
|
97
|
+
throw new Error("globalThis.TextDecoder is not available, polyfill required");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const encoder = new TextEncoder("utf-8");
|
|
101
|
+
const decoder = new TextDecoder("utf-8");
|
|
102
|
+
|
|
103
|
+
globalThis.Go = class {
|
|
104
|
+
constructor() {
|
|
105
|
+
this.argv = ["js"];
|
|
106
|
+
this.env = {};
|
|
107
|
+
this.exit = (code) => {
|
|
108
|
+
if (code !== 0) {
|
|
109
|
+
console.warn("exit code:", code);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
this._exitPromise = new Promise((resolve) => {
|
|
113
|
+
this._resolveExitPromise = resolve;
|
|
114
|
+
});
|
|
115
|
+
this._pendingEvent = null;
|
|
116
|
+
this._scheduledTimeouts = new Map();
|
|
117
|
+
this._nextCallbackTimeoutID = 1;
|
|
118
|
+
|
|
119
|
+
const setInt64 = (addr, v) => {
|
|
120
|
+
this.mem.setUint32(addr + 0, v, true);
|
|
121
|
+
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const setInt32 = (addr, v) => {
|
|
125
|
+
this.mem.setUint32(addr + 0, v, true);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const getInt64 = (addr) => {
|
|
129
|
+
const low = this.mem.getUint32(addr + 0, true);
|
|
130
|
+
const high = this.mem.getInt32(addr + 4, true);
|
|
131
|
+
return low + high * 4294967296;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const loadValue = (addr) => {
|
|
135
|
+
const f = this.mem.getFloat64(addr, true);
|
|
136
|
+
if (f === 0) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
if (!isNaN(f)) {
|
|
140
|
+
return f;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const id = this.mem.getUint32(addr, true);
|
|
144
|
+
return this._values[id];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const storeValue = (addr, v) => {
|
|
148
|
+
const nanHead = 0x7FF80000;
|
|
149
|
+
|
|
150
|
+
if (typeof v === "number" && v !== 0) {
|
|
151
|
+
if (isNaN(v)) {
|
|
152
|
+
this.mem.setUint32(addr + 4, nanHead, true);
|
|
153
|
+
this.mem.setUint32(addr, 0, true);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.mem.setFloat64(addr, v, true);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (v === undefined) {
|
|
161
|
+
this.mem.setFloat64(addr, 0, true);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let id = this._ids.get(v);
|
|
166
|
+
if (id === undefined) {
|
|
167
|
+
id = this._idPool.pop();
|
|
168
|
+
if (id === undefined) {
|
|
169
|
+
id = this._values.length;
|
|
170
|
+
}
|
|
171
|
+
this._values[id] = v;
|
|
172
|
+
this._goRefCounts[id] = 0;
|
|
173
|
+
this._ids.set(v, id);
|
|
174
|
+
}
|
|
175
|
+
this._goRefCounts[id]++;
|
|
176
|
+
let typeFlag = 0;
|
|
177
|
+
switch (typeof v) {
|
|
178
|
+
case "object":
|
|
179
|
+
if (v !== null) {
|
|
180
|
+
typeFlag = 1;
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
case "string":
|
|
184
|
+
typeFlag = 2;
|
|
185
|
+
break;
|
|
186
|
+
case "symbol":
|
|
187
|
+
typeFlag = 3;
|
|
188
|
+
break;
|
|
189
|
+
case "function":
|
|
190
|
+
typeFlag = 4;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
|
|
194
|
+
this.mem.setUint32(addr, id, true);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const loadSlice = (addr) => {
|
|
198
|
+
const array = getInt64(addr + 0);
|
|
199
|
+
const len = getInt64(addr + 8);
|
|
200
|
+
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const loadSliceOfValues = (addr) => {
|
|
204
|
+
const array = getInt64(addr + 0);
|
|
205
|
+
const len = getInt64(addr + 8);
|
|
206
|
+
const a = new Array(len);
|
|
207
|
+
for (let i = 0; i < len; i++) {
|
|
208
|
+
a[i] = loadValue(array + i * 8);
|
|
209
|
+
}
|
|
210
|
+
return a;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const loadString = (addr) => {
|
|
214
|
+
const saddr = getInt64(addr + 0);
|
|
215
|
+
const len = getInt64(addr + 8);
|
|
216
|
+
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const testCallExport = (a, b) => {
|
|
220
|
+
this._inst.exports.testExport0();
|
|
221
|
+
return this._inst.exports.testExport(a, b);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const timeOrigin = Date.now() - performance.now();
|
|
225
|
+
this.importObject = {
|
|
226
|
+
_gotest: {
|
|
227
|
+
add: (a, b) => a + b,
|
|
228
|
+
callExport: testCallExport,
|
|
229
|
+
},
|
|
230
|
+
gojs: {
|
|
231
|
+
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
|
|
232
|
+
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
|
|
233
|
+
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
|
|
234
|
+
// This changes the SP, thus we have to update the SP used by the imported function.
|
|
235
|
+
|
|
236
|
+
// func wasmExit(code int32)
|
|
237
|
+
"runtime.wasmExit": (sp) => {
|
|
238
|
+
sp >>>= 0;
|
|
239
|
+
const code = this.mem.getInt32(sp + 8, true);
|
|
240
|
+
this.exited = true;
|
|
241
|
+
delete this._inst;
|
|
242
|
+
delete this._values;
|
|
243
|
+
delete this._goRefCounts;
|
|
244
|
+
delete this._ids;
|
|
245
|
+
delete this._idPool;
|
|
246
|
+
this.exit(code);
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
|
250
|
+
"runtime.wasmWrite": (sp) => {
|
|
251
|
+
sp >>>= 0;
|
|
252
|
+
const fd = getInt64(sp + 8);
|
|
253
|
+
const p = getInt64(sp + 16);
|
|
254
|
+
const n = this.mem.getInt32(sp + 24, true);
|
|
255
|
+
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
// func resetMemoryDataView()
|
|
259
|
+
"runtime.resetMemoryDataView": (sp) => {
|
|
260
|
+
sp >>>= 0;
|
|
261
|
+
this.mem = new DataView(this._inst.exports.mem.buffer);
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
// func nanotime1() int64
|
|
265
|
+
"runtime.nanotime1": (sp) => {
|
|
266
|
+
sp >>>= 0;
|
|
267
|
+
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
// func walltime() (sec int64, nsec int32)
|
|
271
|
+
"runtime.walltime": (sp) => {
|
|
272
|
+
sp >>>= 0;
|
|
273
|
+
const msec = (new Date).getTime();
|
|
274
|
+
setInt64(sp + 8, msec / 1000);
|
|
275
|
+
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
// func scheduleTimeoutEvent(delay int64) int32
|
|
279
|
+
"runtime.scheduleTimeoutEvent": (sp) => {
|
|
280
|
+
sp >>>= 0;
|
|
281
|
+
const id = this._nextCallbackTimeoutID;
|
|
282
|
+
this._nextCallbackTimeoutID++;
|
|
283
|
+
this._scheduledTimeouts.set(id, setTimeout(
|
|
284
|
+
() => {
|
|
285
|
+
this._resume();
|
|
286
|
+
while (this._scheduledTimeouts.has(id)) {
|
|
287
|
+
// for some reason Go failed to register the timeout event, log and try again
|
|
288
|
+
// (temporary workaround for https://github.com/golang/go/issues/28975)
|
|
289
|
+
console.warn("scheduleTimeoutEvent: missed timeout event");
|
|
290
|
+
this._resume();
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
getInt64(sp + 8),
|
|
294
|
+
));
|
|
295
|
+
this.mem.setInt32(sp + 16, id, true);
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
// func clearTimeoutEvent(id int32)
|
|
299
|
+
"runtime.clearTimeoutEvent": (sp) => {
|
|
300
|
+
sp >>>= 0;
|
|
301
|
+
const id = this.mem.getInt32(sp + 8, true);
|
|
302
|
+
clearTimeout(this._scheduledTimeouts.get(id));
|
|
303
|
+
this._scheduledTimeouts.delete(id);
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
// func getRandomData(r []byte)
|
|
307
|
+
"runtime.getRandomData": (sp) => {
|
|
308
|
+
sp >>>= 0;
|
|
309
|
+
crypto.getRandomValues(loadSlice(sp + 8));
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
// func finalizeRef(v ref)
|
|
313
|
+
"syscall/js.finalizeRef": (sp) => {
|
|
314
|
+
sp >>>= 0;
|
|
315
|
+
const id = this.mem.getUint32(sp + 8, true);
|
|
316
|
+
this._goRefCounts[id]--;
|
|
317
|
+
if (this._goRefCounts[id] === 0) {
|
|
318
|
+
const v = this._values[id];
|
|
319
|
+
this._values[id] = null;
|
|
320
|
+
this._ids.delete(v);
|
|
321
|
+
this._idPool.push(id);
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
// func stringVal(value string) ref
|
|
326
|
+
"syscall/js.stringVal": (sp) => {
|
|
327
|
+
sp >>>= 0;
|
|
328
|
+
storeValue(sp + 24, loadString(sp + 8));
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
// func valueGet(v ref, p string) ref
|
|
332
|
+
"syscall/js.valueGet": (sp) => {
|
|
333
|
+
sp >>>= 0;
|
|
334
|
+
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
|
|
335
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
336
|
+
storeValue(sp + 32, result);
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
// func valueSet(v ref, p string, x ref)
|
|
340
|
+
"syscall/js.valueSet": (sp) => {
|
|
341
|
+
sp >>>= 0;
|
|
342
|
+
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
// func valueDelete(v ref, p string)
|
|
346
|
+
"syscall/js.valueDelete": (sp) => {
|
|
347
|
+
sp >>>= 0;
|
|
348
|
+
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
// func valueIndex(v ref, i int) ref
|
|
352
|
+
"syscall/js.valueIndex": (sp) => {
|
|
353
|
+
sp >>>= 0;
|
|
354
|
+
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
// valueSetIndex(v ref, i int, x ref)
|
|
358
|
+
"syscall/js.valueSetIndex": (sp) => {
|
|
359
|
+
sp >>>= 0;
|
|
360
|
+
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
|
364
|
+
"syscall/js.valueCall": (sp) => {
|
|
365
|
+
sp >>>= 0;
|
|
366
|
+
try {
|
|
367
|
+
const v = loadValue(sp + 8);
|
|
368
|
+
const m = Reflect.get(v, loadString(sp + 16));
|
|
369
|
+
const args = loadSliceOfValues(sp + 32);
|
|
370
|
+
const result = Reflect.apply(m, v, args);
|
|
371
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
372
|
+
storeValue(sp + 56, result);
|
|
373
|
+
this.mem.setUint8(sp + 64, 1);
|
|
374
|
+
} catch (err) {
|
|
375
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
376
|
+
storeValue(sp + 56, err);
|
|
377
|
+
this.mem.setUint8(sp + 64, 0);
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
// func valueInvoke(v ref, args []ref) (ref, bool)
|
|
382
|
+
"syscall/js.valueInvoke": (sp) => {
|
|
383
|
+
sp >>>= 0;
|
|
384
|
+
try {
|
|
385
|
+
const v = loadValue(sp + 8);
|
|
386
|
+
const args = loadSliceOfValues(sp + 16);
|
|
387
|
+
const result = Reflect.apply(v, undefined, args);
|
|
388
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
389
|
+
storeValue(sp + 40, result);
|
|
390
|
+
this.mem.setUint8(sp + 48, 1);
|
|
391
|
+
} catch (err) {
|
|
392
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
393
|
+
storeValue(sp + 40, err);
|
|
394
|
+
this.mem.setUint8(sp + 48, 0);
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
// func valueNew(v ref, args []ref) (ref, bool)
|
|
399
|
+
"syscall/js.valueNew": (sp) => {
|
|
400
|
+
sp >>>= 0;
|
|
401
|
+
try {
|
|
402
|
+
const v = loadValue(sp + 8);
|
|
403
|
+
const args = loadSliceOfValues(sp + 16);
|
|
404
|
+
const result = Reflect.construct(v, args);
|
|
405
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
406
|
+
storeValue(sp + 40, result);
|
|
407
|
+
this.mem.setUint8(sp + 48, 1);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
410
|
+
storeValue(sp + 40, err);
|
|
411
|
+
this.mem.setUint8(sp + 48, 0);
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
// func valueLength(v ref) int
|
|
416
|
+
"syscall/js.valueLength": (sp) => {
|
|
417
|
+
sp >>>= 0;
|
|
418
|
+
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
// valuePrepareString(v ref) (ref, int)
|
|
422
|
+
"syscall/js.valuePrepareString": (sp) => {
|
|
423
|
+
sp >>>= 0;
|
|
424
|
+
const str = encoder.encode(String(loadValue(sp + 8)));
|
|
425
|
+
storeValue(sp + 16, str);
|
|
426
|
+
setInt64(sp + 24, str.length);
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
// valueLoadString(v ref, b []byte)
|
|
430
|
+
"syscall/js.valueLoadString": (sp) => {
|
|
431
|
+
sp >>>= 0;
|
|
432
|
+
const str = loadValue(sp + 8);
|
|
433
|
+
loadSlice(sp + 16).set(str);
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
// func valueInstanceOf(v ref, t ref) bool
|
|
437
|
+
"syscall/js.valueInstanceOf": (sp) => {
|
|
438
|
+
sp >>>= 0;
|
|
439
|
+
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
|
443
|
+
"syscall/js.copyBytesToGo": (sp) => {
|
|
444
|
+
sp >>>= 0;
|
|
445
|
+
const dst = loadSlice(sp + 8);
|
|
446
|
+
const src = loadValue(sp + 32);
|
|
447
|
+
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
|
448
|
+
this.mem.setUint8(sp + 48, 0);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const toCopy = src.subarray(0, dst.length);
|
|
452
|
+
dst.set(toCopy);
|
|
453
|
+
setInt64(sp + 40, toCopy.length);
|
|
454
|
+
this.mem.setUint8(sp + 48, 1);
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
// func copyBytesToJS(dst ref, src []byte) (int, bool)
|
|
458
|
+
"syscall/js.copyBytesToJS": (sp) => {
|
|
459
|
+
sp >>>= 0;
|
|
460
|
+
const dst = loadValue(sp + 8);
|
|
461
|
+
const src = loadSlice(sp + 16);
|
|
462
|
+
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
|
463
|
+
this.mem.setUint8(sp + 48, 0);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const toCopy = src.subarray(0, dst.length);
|
|
467
|
+
dst.set(toCopy);
|
|
468
|
+
setInt64(sp + 40, toCopy.length);
|
|
469
|
+
this.mem.setUint8(sp + 48, 1);
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
"debug": (value) => {
|
|
473
|
+
console.log(value);
|
|
474
|
+
},
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async run(instance) {
|
|
480
|
+
if (!(instance instanceof WebAssembly.Instance)) {
|
|
481
|
+
throw new Error("Go.run: WebAssembly.Instance expected");
|
|
482
|
+
}
|
|
483
|
+
this._inst = instance;
|
|
484
|
+
this.mem = new DataView(this._inst.exports.mem.buffer);
|
|
485
|
+
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
|
486
|
+
NaN,
|
|
487
|
+
0,
|
|
488
|
+
null,
|
|
489
|
+
true,
|
|
490
|
+
false,
|
|
491
|
+
globalThis,
|
|
492
|
+
this,
|
|
493
|
+
];
|
|
494
|
+
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
|
|
495
|
+
this._ids = new Map([ // mapping from JS values to reference ids
|
|
496
|
+
[0, 1],
|
|
497
|
+
[null, 2],
|
|
498
|
+
[true, 3],
|
|
499
|
+
[false, 4],
|
|
500
|
+
[globalThis, 5],
|
|
501
|
+
[this, 6],
|
|
502
|
+
]);
|
|
503
|
+
this._idPool = []; // unused ids that have been garbage collected
|
|
504
|
+
this.exited = false; // whether the Go program has exited
|
|
505
|
+
|
|
506
|
+
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
|
507
|
+
let offset = 4096;
|
|
508
|
+
|
|
509
|
+
const strPtr = (str) => {
|
|
510
|
+
const ptr = offset;
|
|
511
|
+
const bytes = encoder.encode(str + "\0");
|
|
512
|
+
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
|
|
513
|
+
offset += bytes.length;
|
|
514
|
+
if (offset % 8 !== 0) {
|
|
515
|
+
offset += 8 - (offset % 8);
|
|
516
|
+
}
|
|
517
|
+
return ptr;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const argc = this.argv.length;
|
|
521
|
+
|
|
522
|
+
const argvPtrs = [];
|
|
523
|
+
this.argv.forEach((arg) => {
|
|
524
|
+
argvPtrs.push(strPtr(arg));
|
|
525
|
+
});
|
|
526
|
+
argvPtrs.push(0);
|
|
527
|
+
|
|
528
|
+
const keys = Object.keys(this.env).sort();
|
|
529
|
+
keys.forEach((key) => {
|
|
530
|
+
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
|
|
531
|
+
});
|
|
532
|
+
argvPtrs.push(0);
|
|
533
|
+
|
|
534
|
+
const argv = offset;
|
|
535
|
+
argvPtrs.forEach((ptr) => {
|
|
536
|
+
this.mem.setUint32(offset, ptr, true);
|
|
537
|
+
this.mem.setUint32(offset + 4, 0, true);
|
|
538
|
+
offset += 8;
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// The linker guarantees global data starts from at least wasmMinDataAddr.
|
|
542
|
+
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
|
|
543
|
+
const wasmMinDataAddr = 4096 + 8192;
|
|
544
|
+
if (offset >= wasmMinDataAddr) {
|
|
545
|
+
throw new Error("total length of command line and environment variables exceeds limit");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
this._inst.exports.run(argc, argv);
|
|
549
|
+
if (this.exited) {
|
|
550
|
+
this._resolveExitPromise();
|
|
551
|
+
}
|
|
552
|
+
await this._exitPromise;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
_resume() {
|
|
556
|
+
if (this.exited) {
|
|
557
|
+
throw new Error("Go program has already exited");
|
|
558
|
+
}
|
|
559
|
+
this._inst.exports.resume();
|
|
560
|
+
if (this.exited) {
|
|
561
|
+
this._resolveExitPromise();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
_makeFuncWrapper(id) {
|
|
566
|
+
const go = this;
|
|
567
|
+
return function () {
|
|
568
|
+
const event = { id: id, this: this, args: arguments };
|
|
569
|
+
go._pendingEvent = event;
|
|
570
|
+
go._resume();
|
|
571
|
+
return event.result;
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
})();
|