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.
Files changed (95) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +23 -0
  3. package/dist/cli.d.ts +6 -0
  4. package/dist/cli.js +198 -0
  5. package/dist/client.d.ts +84 -0
  6. package/dist/client.js +492 -0
  7. package/dist/commands/batch.d.ts +5 -0
  8. package/dist/commands/batch.js +121 -0
  9. package/dist/commands/cache.d.ts +5 -0
  10. package/dist/commands/cache.js +43 -0
  11. package/dist/commands/cite.d.ts +5 -0
  12. package/dist/commands/cite.js +68 -0
  13. package/dist/commands/config.d.ts +5 -0
  14. package/dist/commands/config.js +56 -0
  15. package/dist/commands/export.d.ts +8 -0
  16. package/dist/commands/export.js +169 -0
  17. package/dist/commands/generate.d.ts +10 -0
  18. package/dist/commands/generate.js +320 -0
  19. package/dist/commands/get.d.ts +5 -0
  20. package/dist/commands/get.js +99 -0
  21. package/dist/commands/help.d.ts +13 -0
  22. package/dist/commands/help.js +298 -0
  23. package/dist/commands/search.d.ts +5 -0
  24. package/dist/commands/search.js +96 -0
  25. package/dist/commands/stream.d.ts +5 -0
  26. package/dist/commands/stream.js +100 -0
  27. package/dist/config.d.ts +81 -0
  28. package/dist/config.js +209 -0
  29. package/dist/errors.d.ts +108 -0
  30. package/dist/errors.js +173 -0
  31. package/dist/mcp/server.d.ts +13 -0
  32. package/dist/mcp/server.js +428 -0
  33. package/dist/mcp-cli.d.ts +6 -0
  34. package/dist/mcp-cli.js +37 -0
  35. package/dist/models/canonical.d.ts +423 -0
  36. package/dist/models/canonical.js +92 -0
  37. package/dist/models/index.d.ts +892 -0
  38. package/dist/models/index.js +223 -0
  39. package/dist/output/index.d.ts +34 -0
  40. package/dist/output/index.js +195 -0
  41. package/dist/output/legal-metadata-publication.d.ts +18 -0
  42. package/dist/output/legal-metadata-publication.js +23 -0
  43. package/dist/providers/canonical-metadata.d.ts +3 -0
  44. package/dist/providers/canonical-metadata.js +202 -0
  45. package/dist/providers/commonwealth-provider.d.ts +27 -0
  46. package/dist/providers/commonwealth-provider.js +81 -0
  47. package/dist/providers/index.d.ts +20 -0
  48. package/dist/providers/index.js +27 -0
  49. package/dist/providers/legislation-provider.d.ts +227 -0
  50. package/dist/providers/legislation-provider.js +308 -0
  51. package/dist/providers/nz-provider.d.ts +36 -0
  52. package/dist/providers/nz-provider.js +130 -0
  53. package/dist/providers/output-adapters.d.ts +14 -0
  54. package/dist/providers/output-adapters.js +116 -0
  55. package/dist/providers/plugin-discovery.d.ts +39 -0
  56. package/dist/providers/plugin-discovery.js +91 -0
  57. package/dist/providers/plugin-loader.d.ts +86 -0
  58. package/dist/providers/plugin-loader.js +219 -0
  59. package/dist/providers/queensland-provider.d.ts +42 -0
  60. package/dist/providers/queensland-provider.js +105 -0
  61. package/dist/utils/api-optimization.d.ts +92 -0
  62. package/dist/utils/api-optimization.js +276 -0
  63. package/dist/utils/batch.d.ts +110 -0
  64. package/dist/utils/batch.js +269 -0
  65. package/dist/utils/branded-types.d.ts +0 -0
  66. package/dist/utils/branded-types.js +1 -0
  67. package/dist/utils/compatibility-matrix.d.ts +89 -0
  68. package/dist/utils/compatibility-matrix.js +214 -0
  69. package/dist/utils/config-validator.d.ts +39 -0
  70. package/dist/utils/config-validator.js +197 -0
  71. package/dist/utils/env-loader.d.ts +55 -0
  72. package/dist/utils/env-loader.js +77 -0
  73. package/dist/utils/health-monitor.d.ts +93 -0
  74. package/dist/utils/health-monitor.js +209 -0
  75. package/dist/utils/invocation.d.ts +4 -0
  76. package/dist/utils/invocation.js +33 -0
  77. package/dist/utils/logger.d.ts +94 -0
  78. package/dist/utils/logger.js +220 -0
  79. package/dist/utils/plugin-marketplace.d.ts +77 -0
  80. package/dist/utils/plugin-marketplace.js +191 -0
  81. package/dist/utils/presentation.d.ts +2 -0
  82. package/dist/utils/presentation.js +32 -0
  83. package/dist/utils/rate-limiter.d.ts +100 -0
  84. package/dist/utils/rate-limiter.js +256 -0
  85. package/dist/utils/scraper-cache.d.ts +115 -0
  86. package/dist/utils/scraper-cache.js +229 -0
  87. package/dist/utils/secure-config.d.ts +40 -0
  88. package/dist/utils/secure-config.js +195 -0
  89. package/dist/utils/streaming.d.ts +121 -0
  90. package/dist/utils/streaming.js +333 -0
  91. package/dist/utils/validation.d.ts +190 -0
  92. package/dist/utils/validation.js +209 -0
  93. package/dist/utils/version.d.ts +13 -0
  94. package/dist/utils/version.js +46 -0
  95. 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
+ }