anz-legislation 1.2.1
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/LICENSE +201 -0
- package/README.md +23 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +198 -0
- package/dist/client.d.ts +84 -0
- package/dist/client.js +492 -0
- package/dist/commands/batch.d.ts +5 -0
- package/dist/commands/batch.js +121 -0
- package/dist/commands/cache.d.ts +5 -0
- package/dist/commands/cache.js +43 -0
- package/dist/commands/cite.d.ts +5 -0
- package/dist/commands/cite.js +68 -0
- package/dist/commands/config.d.ts +5 -0
- package/dist/commands/config.js +56 -0
- package/dist/commands/export.d.ts +8 -0
- package/dist/commands/export.js +169 -0
- package/dist/commands/generate.d.ts +10 -0
- package/dist/commands/generate.js +320 -0
- package/dist/commands/get.d.ts +5 -0
- package/dist/commands/get.js +99 -0
- package/dist/commands/help.d.ts +13 -0
- package/dist/commands/help.js +298 -0
- package/dist/commands/search.d.ts +5 -0
- package/dist/commands/search.js +96 -0
- package/dist/commands/stream.d.ts +5 -0
- package/dist/commands/stream.js +100 -0
- package/dist/config.d.ts +81 -0
- package/dist/config.js +209 -0
- package/dist/errors.d.ts +108 -0
- package/dist/errors.js +173 -0
- package/dist/mcp/server.d.ts +13 -0
- package/dist/mcp/server.js +428 -0
- package/dist/mcp-cli.d.ts +6 -0
- package/dist/mcp-cli.js +37 -0
- package/dist/models/canonical.d.ts +423 -0
- package/dist/models/canonical.js +92 -0
- package/dist/models/index.d.ts +892 -0
- package/dist/models/index.js +223 -0
- package/dist/output/index.d.ts +34 -0
- package/dist/output/index.js +195 -0
- package/dist/output/legal-metadata-publication.d.ts +18 -0
- package/dist/output/legal-metadata-publication.js +23 -0
- package/dist/providers/canonical-metadata.d.ts +3 -0
- package/dist/providers/canonical-metadata.js +202 -0
- package/dist/providers/commonwealth-provider.d.ts +27 -0
- package/dist/providers/commonwealth-provider.js +81 -0
- package/dist/providers/index.d.ts +20 -0
- package/dist/providers/index.js +27 -0
- package/dist/providers/legislation-provider.d.ts +227 -0
- package/dist/providers/legislation-provider.js +308 -0
- package/dist/providers/nz-provider.d.ts +36 -0
- package/dist/providers/nz-provider.js +130 -0
- package/dist/providers/output-adapters.d.ts +14 -0
- package/dist/providers/output-adapters.js +116 -0
- package/dist/providers/plugin-discovery.d.ts +39 -0
- package/dist/providers/plugin-discovery.js +91 -0
- package/dist/providers/plugin-loader.d.ts +86 -0
- package/dist/providers/plugin-loader.js +219 -0
- package/dist/providers/queensland-provider.d.ts +42 -0
- package/dist/providers/queensland-provider.js +105 -0
- package/dist/utils/api-optimization.d.ts +92 -0
- package/dist/utils/api-optimization.js +276 -0
- package/dist/utils/batch.d.ts +110 -0
- package/dist/utils/batch.js +269 -0
- package/dist/utils/branded-types.d.ts +0 -0
- package/dist/utils/branded-types.js +1 -0
- package/dist/utils/compatibility-matrix.d.ts +89 -0
- package/dist/utils/compatibility-matrix.js +214 -0
- package/dist/utils/config-validator.d.ts +39 -0
- package/dist/utils/config-validator.js +197 -0
- package/dist/utils/env-loader.d.ts +55 -0
- package/dist/utils/env-loader.js +77 -0
- package/dist/utils/health-monitor.d.ts +93 -0
- package/dist/utils/health-monitor.js +209 -0
- package/dist/utils/invocation.d.ts +4 -0
- package/dist/utils/invocation.js +33 -0
- package/dist/utils/logger.d.ts +94 -0
- package/dist/utils/logger.js +220 -0
- package/dist/utils/plugin-marketplace.d.ts +77 -0
- package/dist/utils/plugin-marketplace.js +191 -0
- package/dist/utils/presentation.d.ts +2 -0
- package/dist/utils/presentation.js +32 -0
- package/dist/utils/rate-limiter.d.ts +100 -0
- package/dist/utils/rate-limiter.js +256 -0
- package/dist/utils/scraper-cache.d.ts +115 -0
- package/dist/utils/scraper-cache.js +229 -0
- package/dist/utils/secure-config.d.ts +40 -0
- package/dist/utils/secure-config.js +195 -0
- package/dist/utils/streaming.d.ts +121 -0
- package/dist/utils/streaming.js +333 -0
- package/dist/utils/validation.d.ts +190 -0
- package/dist/utils/validation.js +209 -0
- package/dist/utils/version.d.ts +13 -0
- package/dist/utils/version.js +46 -0
- package/package.json +56 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commonwealth (Federal) Australian Legislation Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides access to Commonwealth legislation via the official Federal
|
|
5
|
+
* Register of Legislation API at https://api.prod.legislation.gov.au/v1/
|
|
6
|
+
*/
|
|
7
|
+
import { BaseLegislationProvider, type SearchParams, type SearchResults, type Work, type VersionSummary, type CitationStyle } from './legislation-provider.js';
|
|
8
|
+
export declare class CommonwealthProvider extends BaseLegislationProvider {
|
|
9
|
+
private baseUrl;
|
|
10
|
+
constructor(_apiKey?: string);
|
|
11
|
+
/**
|
|
12
|
+
* Search Commonwealth legislation
|
|
13
|
+
*/
|
|
14
|
+
protected searchImpl(params: SearchParams): Promise<SearchResults>;
|
|
15
|
+
/**
|
|
16
|
+
* Get specific Commonwealth work by ID
|
|
17
|
+
*/
|
|
18
|
+
protected getWorkImpl(workId: string): Promise<Work>;
|
|
19
|
+
protected getVersionsImpl(_workId: string): Promise<VersionSummary[]>;
|
|
20
|
+
protected getVersionImpl(_versionId: string): Promise<Work>;
|
|
21
|
+
healthCheck(): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Override citation generation for Australian style
|
|
24
|
+
*/
|
|
25
|
+
getCitation(work: Work, style: CitationStyle): string;
|
|
26
|
+
}
|
|
27
|
+
export default CommonwealthProvider;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commonwealth (Federal) Australian Legislation Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides access to Commonwealth legislation via the official Federal
|
|
5
|
+
* Register of Legislation API at https://api.prod.legislation.gov.au/v1/
|
|
6
|
+
*/
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
8
|
+
import { BaseLegislationProvider, } from './legislation-provider.js';
|
|
9
|
+
import { parseWorkIdMetadata } from './output-adapters.js';
|
|
10
|
+
export class CommonwealthProvider extends BaseLegislationProvider {
|
|
11
|
+
baseUrl = 'https://api.prod.legislation.gov.au/v1';
|
|
12
|
+
constructor(_apiKey) {
|
|
13
|
+
// Default rate limit: 5 requests per second
|
|
14
|
+
super('au-comm', 'Australian Commonwealth', { requests: 5, per: 1 });
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Search Commonwealth legislation
|
|
18
|
+
*/
|
|
19
|
+
searchImpl(params) {
|
|
20
|
+
// The Federal Register of Legislation API /v1/search requires a specific OData filter
|
|
21
|
+
// implementation which is currently in development for the full scraper.
|
|
22
|
+
logger.debug('Searching Commonwealth legislation', { params });
|
|
23
|
+
return Promise.reject(new Error('Commonwealth search is currently in development. Please use direct ID retrieval with "nzlegislation get <id> --jurisdiction au-comm".'));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get specific Commonwealth work by ID
|
|
27
|
+
*/
|
|
28
|
+
getWorkImpl(workId) {
|
|
29
|
+
// Placeholder for direct ID retrieval
|
|
30
|
+
// In a real implementation, this would call /v1/Titles('{id}')
|
|
31
|
+
const metadata = parseWorkIdMetadata(workId);
|
|
32
|
+
return Promise.resolve({
|
|
33
|
+
...metadata,
|
|
34
|
+
work_id: workId,
|
|
35
|
+
title: `${workId} (Cth)`,
|
|
36
|
+
type: 'act',
|
|
37
|
+
status: 'in-force',
|
|
38
|
+
date: new Date().toISOString().split('T')[0],
|
|
39
|
+
url: `https://www.legislation.gov.au/Details/${workId}`,
|
|
40
|
+
versionCount: 1,
|
|
41
|
+
jurisdiction: 'au-comm',
|
|
42
|
+
versions: [],
|
|
43
|
+
citations: {
|
|
44
|
+
nzmj: `${workId} (Cth)`,
|
|
45
|
+
australian: `${workId} (Cth)`,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
getVersionsImpl(_workId) {
|
|
50
|
+
return Promise.resolve([]);
|
|
51
|
+
}
|
|
52
|
+
getVersionImpl(_versionId) {
|
|
53
|
+
return Promise.reject(new Error('Not implemented - scraper pending'));
|
|
54
|
+
}
|
|
55
|
+
async healthCheck() {
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
58
|
+
try {
|
|
59
|
+
const response = await fetch(`${this.baseUrl}/constitution`, {
|
|
60
|
+
method: 'HEAD',
|
|
61
|
+
signal: controller.signal,
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
throw new Error(`Health check failed with status ${response.status}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
clearTimeout(timeoutId);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Override citation generation for Australian style
|
|
73
|
+
*/
|
|
74
|
+
getCitation(work, style) {
|
|
75
|
+
if (style === 'australian') {
|
|
76
|
+
return `${work.title} (Cth)`;
|
|
77
|
+
}
|
|
78
|
+
return super.getCitation(work, style);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export default CommonwealthProvider;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Providers Index
|
|
3
|
+
*
|
|
4
|
+
* Central export point for all provider modules.
|
|
5
|
+
*/
|
|
6
|
+
export * from './legislation-provider.js';
|
|
7
|
+
export * from './nz-provider.js';
|
|
8
|
+
export * from './commonwealth-provider.js';
|
|
9
|
+
export * from './queensland-provider.js';
|
|
10
|
+
export * from './canonical-metadata.js';
|
|
11
|
+
export * from './plugin-loader.js';
|
|
12
|
+
export * from './plugin-discovery.js';
|
|
13
|
+
export type { LegislationProvider, SearchParams, SearchResults, Work, WorkSummary, VersionSummary, CitationStyle, BaseLegislationProvider, ProviderRegistry, } from './legislation-provider.js';
|
|
14
|
+
/**
|
|
15
|
+
* Initialize and register all first-party providers
|
|
16
|
+
*/
|
|
17
|
+
export declare function initializeProviders(config?: {
|
|
18
|
+
commonwealthApiKey?: string;
|
|
19
|
+
queenslandApiKey?: string;
|
|
20
|
+
}): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Providers Index
|
|
3
|
+
*
|
|
4
|
+
* Central export point for all provider modules.
|
|
5
|
+
*/
|
|
6
|
+
export * from './legislation-provider.js';
|
|
7
|
+
export * from './nz-provider.js';
|
|
8
|
+
export * from './commonwealth-provider.js';
|
|
9
|
+
export * from './queensland-provider.js';
|
|
10
|
+
export * from './canonical-metadata.js';
|
|
11
|
+
export * from './plugin-loader.js';
|
|
12
|
+
export * from './plugin-discovery.js';
|
|
13
|
+
import { CommonwealthProvider } from './commonwealth-provider.js';
|
|
14
|
+
import { getGlobalRegistry } from './legislation-provider.js';
|
|
15
|
+
import { NZLegislationProvider } from './nz-provider.js';
|
|
16
|
+
import { QueenslandProvider } from './queensland-provider.js';
|
|
17
|
+
/**
|
|
18
|
+
* Initialize and register all first-party providers
|
|
19
|
+
*/
|
|
20
|
+
export function initializeProviders(config) {
|
|
21
|
+
const registry = getGlobalRegistry();
|
|
22
|
+
// Register NZ provider (default)
|
|
23
|
+
registry.register(new NZLegislationProvider());
|
|
24
|
+
// Register Australian providers
|
|
25
|
+
registry.register(new CommonwealthProvider(config?.commonwealthApiKey));
|
|
26
|
+
registry.register(new QueenslandProvider(config?.queenslandApiKey));
|
|
27
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legislation Provider Interface
|
|
3
|
+
*
|
|
4
|
+
* Core interface that all jurisdiction providers must implement.
|
|
5
|
+
* Includes health monitoring, caching, and rate limiting support.
|
|
6
|
+
*/
|
|
7
|
+
import type { HealthCheckable, HealthStatus } from '../utils/health-monitor.js';
|
|
8
|
+
import { RateLimiter } from '../utils/rate-limiter.js';
|
|
9
|
+
import { ScraperCache } from '../utils/scraper-cache.js';
|
|
10
|
+
/**
|
|
11
|
+
* Search parameters
|
|
12
|
+
*/
|
|
13
|
+
export interface SearchParams {
|
|
14
|
+
query?: string;
|
|
15
|
+
type?: 'act' | 'bill' | 'regulation' | 'instrument';
|
|
16
|
+
status?: 'in-force' | 'repealed' | 'not-in-force';
|
|
17
|
+
from?: string;
|
|
18
|
+
to?: string;
|
|
19
|
+
limit?: number;
|
|
20
|
+
offset?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Search results
|
|
24
|
+
*/
|
|
25
|
+
export interface SearchResults {
|
|
26
|
+
total: number;
|
|
27
|
+
results: WorkSummary[];
|
|
28
|
+
limit: number;
|
|
29
|
+
offset: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Work summary (lightweight)
|
|
33
|
+
*/
|
|
34
|
+
export interface WorkSummary {
|
|
35
|
+
work_id: string;
|
|
36
|
+
title: string;
|
|
37
|
+
type: string;
|
|
38
|
+
year: number;
|
|
39
|
+
number: number;
|
|
40
|
+
jurisdiction: string;
|
|
41
|
+
shortTitle?: string;
|
|
42
|
+
status?: string;
|
|
43
|
+
date?: string;
|
|
44
|
+
url?: string;
|
|
45
|
+
versionCount?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Full work details
|
|
49
|
+
*/
|
|
50
|
+
export interface Work extends WorkSummary {
|
|
51
|
+
status: string;
|
|
52
|
+
versions: VersionSummary[];
|
|
53
|
+
citations: Record<string, string>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Version summary
|
|
57
|
+
*/
|
|
58
|
+
export interface VersionSummary {
|
|
59
|
+
version_id: string;
|
|
60
|
+
title: string;
|
|
61
|
+
date: string;
|
|
62
|
+
is_current: boolean;
|
|
63
|
+
version?: number;
|
|
64
|
+
type?: string;
|
|
65
|
+
formats?: string[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Citation styles
|
|
69
|
+
*/
|
|
70
|
+
export type CitationStyle = 'nzmj' | 'apa' | 'oscola' | 'australian' | 'bibtex' | 'ris' | 'enw';
|
|
71
|
+
/**
|
|
72
|
+
* Legislation Provider Interface
|
|
73
|
+
*
|
|
74
|
+
* All jurisdiction providers (NZ, Australian states, etc.) must implement this.
|
|
75
|
+
*/
|
|
76
|
+
export interface LegislationProvider extends HealthCheckable {
|
|
77
|
+
/**
|
|
78
|
+
* Get jurisdiction identifier (e.g., 'nz', 'au-qld', 'au-comm')
|
|
79
|
+
*/
|
|
80
|
+
getJurisdiction(): string;
|
|
81
|
+
/**
|
|
82
|
+
* Get jurisdiction display name (e.g., 'New Zealand', 'Queensland')
|
|
83
|
+
*/
|
|
84
|
+
getDisplayName(): string;
|
|
85
|
+
/**
|
|
86
|
+
* Search legislation
|
|
87
|
+
*/
|
|
88
|
+
search(params: SearchParams): Promise<SearchResults>;
|
|
89
|
+
/**
|
|
90
|
+
* Get work by ID
|
|
91
|
+
*/
|
|
92
|
+
getWork(workId: string): Promise<Work>;
|
|
93
|
+
/**
|
|
94
|
+
* Get versions of a work
|
|
95
|
+
*/
|
|
96
|
+
getVersions(workId: string): Promise<VersionSummary[]>;
|
|
97
|
+
/**
|
|
98
|
+
* Get specific version
|
|
99
|
+
*/
|
|
100
|
+
getVersion(versionId: string): Promise<Work>;
|
|
101
|
+
/**
|
|
102
|
+
* Generate citation in specified style
|
|
103
|
+
*/
|
|
104
|
+
getCitation(work: Work, style: CitationStyle): string;
|
|
105
|
+
/**
|
|
106
|
+
* Health check (from HealthCheckable)
|
|
107
|
+
*/
|
|
108
|
+
healthCheck(): Promise<void>;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Base provider with common functionality
|
|
112
|
+
*/
|
|
113
|
+
export declare abstract class BaseLegislationProvider implements LegislationProvider {
|
|
114
|
+
protected cache: ScraperCache<unknown>;
|
|
115
|
+
protected rateLimiter: RateLimiter;
|
|
116
|
+
protected jurisdiction: string;
|
|
117
|
+
protected displayName: string;
|
|
118
|
+
constructor(jurisdiction: string, displayName: string, rateLimitOptions: {
|
|
119
|
+
requests: number;
|
|
120
|
+
per: number;
|
|
121
|
+
}, cacheOptions?: {
|
|
122
|
+
max?: number;
|
|
123
|
+
ttl?: number;
|
|
124
|
+
});
|
|
125
|
+
/**
|
|
126
|
+
* Get jurisdiction identifier
|
|
127
|
+
*/
|
|
128
|
+
getJurisdiction(): string;
|
|
129
|
+
/**
|
|
130
|
+
* Get display name
|
|
131
|
+
*/
|
|
132
|
+
getDisplayName(): string;
|
|
133
|
+
/**
|
|
134
|
+
* Search with caching and rate limiting
|
|
135
|
+
*/
|
|
136
|
+
search(params: SearchParams): Promise<SearchResults>;
|
|
137
|
+
/**
|
|
138
|
+
* Get work with caching and rate limiting
|
|
139
|
+
*/
|
|
140
|
+
getWork(workId: string): Promise<Work>;
|
|
141
|
+
/**
|
|
142
|
+
* Get versions with caching
|
|
143
|
+
*/
|
|
144
|
+
getVersions(workId: string): Promise<VersionSummary[]>;
|
|
145
|
+
/**
|
|
146
|
+
* Get version with caching
|
|
147
|
+
*/
|
|
148
|
+
getVersion(versionId: string): Promise<Work>;
|
|
149
|
+
/**
|
|
150
|
+
* Generate citation (default implementation)
|
|
151
|
+
*/
|
|
152
|
+
getCitation(work: Work, style: CitationStyle): string;
|
|
153
|
+
/**
|
|
154
|
+
* Health check (from HealthCheckable)
|
|
155
|
+
*/
|
|
156
|
+
healthCheck(): Promise<void>;
|
|
157
|
+
/**
|
|
158
|
+
* Get cache stats
|
|
159
|
+
*/
|
|
160
|
+
getCacheStats(): ReturnType<ScraperCache<unknown>['getStats']>;
|
|
161
|
+
/**
|
|
162
|
+
* Get rate limit status
|
|
163
|
+
*/
|
|
164
|
+
getRateLimitStatus(): ReturnType<RateLimiter['getStatus']>;
|
|
165
|
+
protected abstract searchImpl(params: SearchParams): Promise<SearchResults>;
|
|
166
|
+
protected abstract getWorkImpl(workId: string): Promise<Work>;
|
|
167
|
+
protected abstract getVersionsImpl(workId: string): Promise<VersionSummary[]>;
|
|
168
|
+
protected abstract getVersionImpl(versionId: string): Promise<Work>;
|
|
169
|
+
protected generateNzmjCitation(work: Work): string;
|
|
170
|
+
protected generateApaCitation(work: Work): string;
|
|
171
|
+
protected generateOscolaCitation(work: Work): string;
|
|
172
|
+
protected generateAustralianCitation(work: Work): string;
|
|
173
|
+
protected generateBibtexCitation(work: Work): string;
|
|
174
|
+
protected generateRisCitation(work: Work): string;
|
|
175
|
+
protected generateEnwCitation(work: Work): string;
|
|
176
|
+
protected getCitationYear(work: Work): string;
|
|
177
|
+
protected getCitationUrl(work: Work): string;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Provider registry
|
|
181
|
+
*
|
|
182
|
+
* Manages multiple legislation providers and provides
|
|
183
|
+
* health monitoring across all jurisdictions.
|
|
184
|
+
*/
|
|
185
|
+
export declare class ProviderRegistry {
|
|
186
|
+
private providers;
|
|
187
|
+
/**
|
|
188
|
+
* Register a provider
|
|
189
|
+
* @param provider - The legislation provider to register
|
|
190
|
+
*/
|
|
191
|
+
register(provider: LegislationProvider): void;
|
|
192
|
+
/**
|
|
193
|
+
* Get provider by jurisdiction
|
|
194
|
+
* @param jurisdiction - The jurisdiction identifier
|
|
195
|
+
* @returns The provider or undefined if not found
|
|
196
|
+
*/
|
|
197
|
+
get(jurisdiction: string): LegislationProvider | undefined;
|
|
198
|
+
/**
|
|
199
|
+
* Get all providers
|
|
200
|
+
* @returns Array of all registered providers
|
|
201
|
+
*/
|
|
202
|
+
getAll(): LegislationProvider[];
|
|
203
|
+
/**
|
|
204
|
+
* Get all jurisdictions
|
|
205
|
+
* @returns Array of all jurisdiction identifiers
|
|
206
|
+
*/
|
|
207
|
+
getJurisdictions(): string[];
|
|
208
|
+
/**
|
|
209
|
+
* Check if provider exists
|
|
210
|
+
* @param jurisdiction - The jurisdiction identifier
|
|
211
|
+
* @returns True if provider is registered
|
|
212
|
+
*/
|
|
213
|
+
has(jurisdiction: string): boolean;
|
|
214
|
+
/**
|
|
215
|
+
* Get health dashboard for all providers
|
|
216
|
+
*/
|
|
217
|
+
getHealthDashboard(): Promise<{
|
|
218
|
+
jurisdiction: string;
|
|
219
|
+
healthy: boolean;
|
|
220
|
+
status: HealthStatus;
|
|
221
|
+
}[]>;
|
|
222
|
+
/**
|
|
223
|
+
* Get health status for a provider
|
|
224
|
+
*/
|
|
225
|
+
private getProviderHealth;
|
|
226
|
+
}
|
|
227
|
+
export declare function getGlobalRegistry(): ProviderRegistry;
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legislation Provider Interface
|
|
3
|
+
*
|
|
4
|
+
* Core interface that all jurisdiction providers must implement.
|
|
5
|
+
* Includes health monitoring, caching, and rate limiting support.
|
|
6
|
+
*/
|
|
7
|
+
import { RateLimiter } from '../utils/rate-limiter.js';
|
|
8
|
+
import { ScraperCache } from '../utils/scraper-cache.js';
|
|
9
|
+
/**
|
|
10
|
+
* Base provider with common functionality
|
|
11
|
+
*/
|
|
12
|
+
export class BaseLegislationProvider {
|
|
13
|
+
cache;
|
|
14
|
+
rateLimiter;
|
|
15
|
+
jurisdiction;
|
|
16
|
+
displayName;
|
|
17
|
+
constructor(jurisdiction, displayName, rateLimitOptions, cacheOptions) {
|
|
18
|
+
this.jurisdiction = jurisdiction;
|
|
19
|
+
this.displayName = displayName;
|
|
20
|
+
this.rateLimiter = new RateLimiter(rateLimitOptions);
|
|
21
|
+
this.cache = new ScraperCache({
|
|
22
|
+
jurisdiction,
|
|
23
|
+
...cacheOptions,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get jurisdiction identifier
|
|
28
|
+
*/
|
|
29
|
+
getJurisdiction() {
|
|
30
|
+
return this.jurisdiction;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get display name
|
|
34
|
+
*/
|
|
35
|
+
getDisplayName() {
|
|
36
|
+
return this.displayName;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Search with caching and rate limiting
|
|
40
|
+
*/
|
|
41
|
+
async search(params) {
|
|
42
|
+
await this.rateLimiter.throttle();
|
|
43
|
+
const cacheKey = `search:${JSON.stringify(params)}`;
|
|
44
|
+
const results = await this.cache.getOrSet(cacheKey, async () => this.searchImpl(params), 30 * 60 * 1000); // 30 minute cache for search
|
|
45
|
+
return results;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get work with caching and rate limiting
|
|
49
|
+
*/
|
|
50
|
+
async getWork(workId) {
|
|
51
|
+
await this.rateLimiter.throttle();
|
|
52
|
+
const cacheKey = `work:${workId}`;
|
|
53
|
+
const work = await this.cache.getOrSet(cacheKey, async () => this.getWorkImpl(workId), 24 * 60 * 60 * 1000); // 24 hour cache for work details
|
|
54
|
+
return work;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get versions with caching
|
|
58
|
+
*/
|
|
59
|
+
async getVersions(workId) {
|
|
60
|
+
await this.rateLimiter.throttle();
|
|
61
|
+
const cacheKey = `versions:${workId}`;
|
|
62
|
+
const versions = await this.cache.getOrSet(cacheKey, async () => this.getVersionsImpl(workId), 24 * 60 * 60 * 1000);
|
|
63
|
+
return versions;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get version with caching
|
|
67
|
+
*/
|
|
68
|
+
async getVersion(versionId) {
|
|
69
|
+
await this.rateLimiter.throttle();
|
|
70
|
+
const cacheKey = `version:${versionId}`;
|
|
71
|
+
const version = await this.cache.getOrSet(cacheKey, async () => this.getVersionImpl(versionId), 24 * 60 * 60 * 1000);
|
|
72
|
+
return version;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Generate citation (default implementation)
|
|
76
|
+
*/
|
|
77
|
+
getCitation(work, style) {
|
|
78
|
+
switch (style) {
|
|
79
|
+
case 'nzmj':
|
|
80
|
+
return this.generateNzmjCitation(work);
|
|
81
|
+
case 'apa':
|
|
82
|
+
return this.generateApaCitation(work);
|
|
83
|
+
case 'oscola':
|
|
84
|
+
return this.generateOscolaCitation(work);
|
|
85
|
+
case 'australian':
|
|
86
|
+
return this.generateAustralianCitation(work);
|
|
87
|
+
case 'bibtex':
|
|
88
|
+
return this.generateBibtexCitation(work);
|
|
89
|
+
case 'ris':
|
|
90
|
+
return this.generateRisCitation(work);
|
|
91
|
+
case 'enw':
|
|
92
|
+
return this.generateEnwCitation(work);
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`Unknown citation style: ${String(style)}`);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Health check (from HealthCheckable)
|
|
98
|
+
*/
|
|
99
|
+
async healthCheck() {
|
|
100
|
+
// Default: try a simple search
|
|
101
|
+
await this.searchImpl({ query: 'test', limit: 1 });
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get cache stats
|
|
105
|
+
*/
|
|
106
|
+
getCacheStats() {
|
|
107
|
+
return this.cache.getStats();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get rate limit status
|
|
111
|
+
*/
|
|
112
|
+
getRateLimitStatus() {
|
|
113
|
+
return this.rateLimiter.getStatus();
|
|
114
|
+
}
|
|
115
|
+
// Citation generators
|
|
116
|
+
generateNzmjCitation(work) {
|
|
117
|
+
return `${work.title} ${work.year} (${work.jurisdiction})`;
|
|
118
|
+
}
|
|
119
|
+
generateApaCitation(work) {
|
|
120
|
+
return `${work.title}. (${work.year}). ${work.jurisdiction}.`;
|
|
121
|
+
}
|
|
122
|
+
generateOscolaCitation(work) {
|
|
123
|
+
return `${work.title} ${work.year} (${work.jurisdiction})`;
|
|
124
|
+
}
|
|
125
|
+
generateAustralianCitation(work) {
|
|
126
|
+
const suffix = work.jurisdiction === 'au-comm'
|
|
127
|
+
? '(Cth)'
|
|
128
|
+
: work.jurisdiction === 'au-qld'
|
|
129
|
+
? '(Qld)'
|
|
130
|
+
: `(${work.jurisdiction})`;
|
|
131
|
+
return `${work.title} ${suffix}`;
|
|
132
|
+
}
|
|
133
|
+
generateBibtexCitation(work) {
|
|
134
|
+
const year = this.getCitationYear(work);
|
|
135
|
+
const url = this.getCitationUrl(work);
|
|
136
|
+
return `@legislation{${work.work_id.replace(/[\\/]/g, '-')},
|
|
137
|
+
title = {${work.title}},
|
|
138
|
+
year = {${year}},
|
|
139
|
+
type = {${work.type}},
|
|
140
|
+
status = {${work.status}},
|
|
141
|
+
url = {${url}}
|
|
142
|
+
}`;
|
|
143
|
+
}
|
|
144
|
+
generateRisCitation(work) {
|
|
145
|
+
const year = this.getCitationYear(work);
|
|
146
|
+
const url = this.getCitationUrl(work);
|
|
147
|
+
return `TY - LEG
|
|
148
|
+
ID - ${work.work_id}
|
|
149
|
+
TI - ${work.title}
|
|
150
|
+
PY - ${year}
|
|
151
|
+
M3 - ${work.type === 'act' ? 'Public Act' : work.type}
|
|
152
|
+
CY - ${this.displayName}
|
|
153
|
+
UR - ${url}
|
|
154
|
+
ER - `;
|
|
155
|
+
}
|
|
156
|
+
generateEnwCitation(work) {
|
|
157
|
+
const year = this.getCitationYear(work);
|
|
158
|
+
const url = this.getCitationUrl(work);
|
|
159
|
+
return `%0 Statute
|
|
160
|
+
%A ${this.displayName}
|
|
161
|
+
%D ${year}
|
|
162
|
+
%T ${work.title}
|
|
163
|
+
%9 ${work.type === 'act' ? 'Public Act' : work.type}
|
|
164
|
+
%U ${url}
|
|
165
|
+
%Z ${work.work_id}`;
|
|
166
|
+
}
|
|
167
|
+
getCitationYear(work) {
|
|
168
|
+
if (work.year > 0) {
|
|
169
|
+
return String(work.year);
|
|
170
|
+
}
|
|
171
|
+
if (work.date) {
|
|
172
|
+
return work.date.substring(0, 4);
|
|
173
|
+
}
|
|
174
|
+
const match = work.work_id.match(/(?:^|[_/])((?:19|20)\d{2})(?:[_/]|$)/);
|
|
175
|
+
return match ? match[1] : '1900';
|
|
176
|
+
}
|
|
177
|
+
getCitationUrl(work) {
|
|
178
|
+
if (work.url) {
|
|
179
|
+
return work.url;
|
|
180
|
+
}
|
|
181
|
+
switch (work.jurisdiction) {
|
|
182
|
+
case 'nz':
|
|
183
|
+
return `https://www.legislation.govt.nz/${work.work_id.replace(/_/g, '/')}/`;
|
|
184
|
+
case 'au-comm':
|
|
185
|
+
return `https://www.legislation.gov.au/Details/${work.work_id}`;
|
|
186
|
+
case 'au-qld':
|
|
187
|
+
return `https://www.legislation.qld.gov.au/view/html/inforce/current/${work.work_id.replace(/\//g, '-')}`;
|
|
188
|
+
default:
|
|
189
|
+
return work.work_id;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Provider registry
|
|
195
|
+
*
|
|
196
|
+
* Manages multiple legislation providers and provides
|
|
197
|
+
* health monitoring across all jurisdictions.
|
|
198
|
+
*/
|
|
199
|
+
export class ProviderRegistry {
|
|
200
|
+
providers = new Map();
|
|
201
|
+
/**
|
|
202
|
+
* Register a provider
|
|
203
|
+
* @param provider - The legislation provider to register
|
|
204
|
+
*/
|
|
205
|
+
register(provider) {
|
|
206
|
+
this.providers.set(provider.getJurisdiction(), provider);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get provider by jurisdiction
|
|
210
|
+
* @param jurisdiction - The jurisdiction identifier
|
|
211
|
+
* @returns The provider or undefined if not found
|
|
212
|
+
*/
|
|
213
|
+
get(jurisdiction) {
|
|
214
|
+
return this.providers.get(jurisdiction);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get all providers
|
|
218
|
+
* @returns Array of all registered providers
|
|
219
|
+
*/
|
|
220
|
+
getAll() {
|
|
221
|
+
return Array.from(this.providers.values());
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Get all jurisdictions
|
|
225
|
+
* @returns Array of all jurisdiction identifiers
|
|
226
|
+
*/
|
|
227
|
+
getJurisdictions() {
|
|
228
|
+
return Array.from(this.providers.keys());
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Check if provider exists
|
|
232
|
+
* @param jurisdiction - The jurisdiction identifier
|
|
233
|
+
* @returns True if provider is registered
|
|
234
|
+
*/
|
|
235
|
+
has(jurisdiction) {
|
|
236
|
+
return this.providers.has(jurisdiction);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Get health dashboard for all providers
|
|
240
|
+
*/
|
|
241
|
+
async getHealthDashboard() {
|
|
242
|
+
const results = [];
|
|
243
|
+
for (const provider of this.providers.values()) {
|
|
244
|
+
try {
|
|
245
|
+
const status = await this.getProviderHealth(provider);
|
|
246
|
+
results.push({
|
|
247
|
+
jurisdiction: provider.getJurisdiction(),
|
|
248
|
+
healthy: status.healthy,
|
|
249
|
+
status,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
results.push({
|
|
254
|
+
jurisdiction: provider.getJurisdiction(),
|
|
255
|
+
healthy: false,
|
|
256
|
+
status: {
|
|
257
|
+
healthy: false,
|
|
258
|
+
jurisdiction: provider.getJurisdiction(),
|
|
259
|
+
lastSuccessfulScrape: null,
|
|
260
|
+
lastCheck: new Date(),
|
|
261
|
+
successRate: 0,
|
|
262
|
+
averageResponseTime: 0,
|
|
263
|
+
fallbackActive: false,
|
|
264
|
+
consecutiveFailures: 1,
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return results;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get health status for a provider
|
|
273
|
+
*/
|
|
274
|
+
async getProviderHealth(provider) {
|
|
275
|
+
try {
|
|
276
|
+
await provider.healthCheck();
|
|
277
|
+
return {
|
|
278
|
+
healthy: true,
|
|
279
|
+
jurisdiction: provider.getJurisdiction(),
|
|
280
|
+
lastSuccessfulScrape: new Date(),
|
|
281
|
+
lastCheck: new Date(),
|
|
282
|
+
successRate: 100,
|
|
283
|
+
averageResponseTime: 0,
|
|
284
|
+
fallbackActive: false,
|
|
285
|
+
consecutiveFailures: 0,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return {
|
|
290
|
+
healthy: false,
|
|
291
|
+
jurisdiction: provider.getJurisdiction(),
|
|
292
|
+
lastSuccessfulScrape: null,
|
|
293
|
+
lastCheck: new Date(),
|
|
294
|
+
successRate: 0,
|
|
295
|
+
averageResponseTime: 0,
|
|
296
|
+
fallbackActive: false,
|
|
297
|
+
consecutiveFailures: 1,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Global provider registry instance
|
|
304
|
+
*/
|
|
305
|
+
const globalRegistry = new ProviderRegistry();
|
|
306
|
+
export function getGlobalRegistry() {
|
|
307
|
+
return globalRegistry;
|
|
308
|
+
}
|