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,219 @@
1
+ /**
2
+ * Plugin Loader
3
+ *
4
+ * Dynamically loads and registers jurisdiction plugins.
5
+ * Supports both official and community plugins.
6
+ */
7
+ import { ProviderRegistry } from '../providers/legislation-provider.js';
8
+ import { CompatibilityMatrix } from '../utils/compatibility-matrix.js';
9
+ export class PluginLoader {
10
+ registry;
11
+ compatibilityMatrix;
12
+ loadedPlugins = new Map();
13
+ coreVersion;
14
+ constructor(coreVersion, registry) {
15
+ this.coreVersion = coreVersion;
16
+ this.registry = registry ?? new ProviderRegistry();
17
+ this.compatibilityMatrix = new CompatibilityMatrix(coreVersion);
18
+ }
19
+ /**
20
+ * Load a plugin from path
21
+ */
22
+ async loadPlugin(pluginPath) {
23
+ try {
24
+ // Import plugin module
25
+ const module = await import(pluginPath);
26
+ // Get manifest
27
+ const manifest = module.default?.manifest ?? module.manifest;
28
+ if (!manifest) {
29
+ throw new Error('Plugin manifest not found');
30
+ }
31
+ // Verify plugin integrity (trust official plugins, warn on community)
32
+ if (manifest.pluginType === 'community') {
33
+ console.warn(`⚠️ Loading community plugin: ${manifest.name}. Ensure you trust the source.`);
34
+ }
35
+ // Check compatibility
36
+ this.compatibilityMatrix.register({
37
+ name: manifest.name,
38
+ version: manifest.version,
39
+ requires: {
40
+ core: manifest.peerDependencies['nz-legislation-tool'] ?? '*',
41
+ },
42
+ });
43
+ if (!this.compatibilityMatrix.check(manifest.name, manifest.version)) {
44
+ throw new Error(`Plugin ${manifest.name}@${manifest.version} is not compatible with core v${this.coreVersion}`);
45
+ }
46
+ // Get provider class and instantiate
47
+ const ProviderClass = module[manifest.provider] ?? module.default;
48
+ if (!ProviderClass) {
49
+ throw new Error(`Provider class "${manifest.provider}" not found in plugin`);
50
+ }
51
+ const provider = new ProviderClass();
52
+ // Verify provider implements interface
53
+ if (!this.isValidProvider(provider)) {
54
+ throw new Error('Plugin does not implement LegislationProvider interface');
55
+ }
56
+ // Register provider
57
+ this.registry.register(provider);
58
+ // Store loaded plugin
59
+ const loadedPlugin = {
60
+ manifest,
61
+ provider,
62
+ loaded: true,
63
+ };
64
+ this.loadedPlugins.set(manifest.name, loadedPlugin);
65
+ console.log(`✅ Plugin loaded: ${manifest.name}@${manifest.version}`);
66
+ return loadedPlugin;
67
+ }
68
+ catch (error) {
69
+ const loadedPlugin = {
70
+ manifest: {
71
+ name: pluginPath,
72
+ version: 'unknown',
73
+ main: '',
74
+ provider: '',
75
+ peerDependencies: {},
76
+ pluginType: 'community',
77
+ pluginStatus: 'alpha',
78
+ },
79
+ provider: null,
80
+ loaded: false,
81
+ error: error instanceof Error ? error : new Error(String(error)),
82
+ };
83
+ this.loadedPlugins.set(pluginPath, loadedPlugin);
84
+ console.error(`❌ Failed to load plugin: ${pluginPath}`);
85
+ console.error(` Error: ${error instanceof Error ? error.message : String(error)}`);
86
+ return loadedPlugin;
87
+ }
88
+ }
89
+ /**
90
+ * Load multiple plugins in parallel
91
+ */
92
+ async loadPlugins(pluginPaths) {
93
+ const results = await Promise.allSettled(pluginPaths.map(path => this.loadPlugin(path)));
94
+ return results
95
+ .filter((r) => r.status === 'fulfilled')
96
+ .map(r => r.value);
97
+ }
98
+ /**
99
+ * Auto-discover plugins from directory
100
+ */
101
+ async discoverPlugins(directory) {
102
+ const plugins = [];
103
+ try {
104
+ const fs = await import('fs');
105
+ const path = await import('path');
106
+ if (!fs.existsSync(directory)) {
107
+ console.debug(`Plugin directory not found: ${directory}`);
108
+ return plugins;
109
+ }
110
+ const files = fs.readdirSync(directory);
111
+ for (const file of files) {
112
+ if (file.startsWith('@')) {
113
+ // Scoped package directory
114
+ const scopeDir = path.join(directory, file);
115
+ const scopedPlugins = await this.discoverPlugins(scopeDir);
116
+ plugins.push(...scopedPlugins.map(p => path.join(file, p)));
117
+ }
118
+ else if (file.startsWith('nz-legislation-') || file.startsWith('legislation-')) {
119
+ // Plugin directory
120
+ plugins.push(file);
121
+ }
122
+ }
123
+ console.log(`🔍 Discovered ${plugins.length} potential plugins in ${directory}`);
124
+ }
125
+ catch (error) {
126
+ console.error('Error discovering plugins:', error);
127
+ }
128
+ return plugins;
129
+ }
130
+ /**
131
+ * Get loaded plugin
132
+ */
133
+ getPlugin(name) {
134
+ return this.loadedPlugins.get(name);
135
+ }
136
+ /**
137
+ * Get all loaded plugins
138
+ */
139
+ getLoadedPlugins() {
140
+ return Array.from(this.loadedPlugins.values());
141
+ }
142
+ /**
143
+ * Get registry
144
+ */
145
+ getRegistry() {
146
+ return this.registry;
147
+ }
148
+ /**
149
+ * Get compatibility matrix
150
+ */
151
+ getCompatibilityMatrix() {
152
+ return this.compatibilityMatrix;
153
+ }
154
+ /**
155
+ * Unload plugin
156
+ */
157
+ unloadPlugin(name) {
158
+ const plugin = this.loadedPlugins.get(name);
159
+ if (plugin) {
160
+ // Note: In Node.js, we can't truly unload modules, but we can remove from registry
161
+ this.loadedPlugins.delete(name);
162
+ console.log(`Plugin unloaded: ${name}`);
163
+ }
164
+ }
165
+ /**
166
+ * Get plugin status report
167
+ */
168
+ getStatusReport() {
169
+ const plugins = this.getLoadedPlugins().map(p => ({
170
+ name: p.manifest.name,
171
+ version: p.manifest.version,
172
+ loaded: p.loaded,
173
+ error: p.error?.message,
174
+ }));
175
+ return {
176
+ coreVersion: this.coreVersion,
177
+ totalPlugins: plugins.length,
178
+ loaded: plugins.filter(p => p.loaded).length,
179
+ failed: plugins.filter(p => !p.loaded).length,
180
+ plugins,
181
+ };
182
+ }
183
+ /**
184
+ * Validate provider implements interface
185
+ */
186
+ isValidProvider(obj) {
187
+ return (typeof obj.getJurisdiction === 'function' &&
188
+ typeof obj.getDisplayName === 'function' &&
189
+ typeof obj.search === 'function' &&
190
+ typeof obj.getWork === 'function' &&
191
+ typeof obj.healthCheck === 'function');
192
+ }
193
+ }
194
+ /**
195
+ * Create global plugin loader instance
196
+ */
197
+ let globalPluginLoader = null;
198
+ export function getGlobalPluginLoader(coreVersion) {
199
+ if (!globalPluginLoader) {
200
+ globalPluginLoader = new PluginLoader(coreVersion);
201
+ }
202
+ return globalPluginLoader;
203
+ }
204
+ /**
205
+ * CLI helper for plugin status
206
+ */
207
+ export function formatPluginStatus(report) {
208
+ const lines = [
209
+ `Plugin Status (Core v${report.coreVersion})`,
210
+ `Total: ${report.totalPlugins} | Loaded: ${report.loaded} | Failed: ${report.failed}`,
211
+ '',
212
+ ];
213
+ for (const plugin of report.plugins) {
214
+ const icon = plugin.loaded ? '✅' : '❌';
215
+ const error = plugin.error ? ` - ${plugin.error}` : '';
216
+ lines.push(` ${icon} ${plugin.name}@${plugin.version}${error}`);
217
+ }
218
+ return lines.join('\n');
219
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Queensland Legislation Provider
3
+ *
4
+ * Provides access to Queensland legislation via the official API at
5
+ * https://api.legislation.qld.gov.au/
6
+ */
7
+ import { BaseLegislationProvider } from './legislation-provider.js';
8
+ import type { SearchParams, SearchResults, Work, VersionSummary, CitationStyle } from './legislation-provider.js';
9
+ export declare class QueenslandProvider extends BaseLegislationProvider {
10
+ private baseUrl;
11
+ private apiKey?;
12
+ constructor(apiKey?: string);
13
+ /**
14
+ * Search Queensland legislation
15
+ */
16
+ protected searchImpl(params: SearchParams): Promise<SearchResults>;
17
+ /**
18
+ * Get work by ID
19
+ */
20
+ protected getWorkImpl(workId: string): Promise<Work>;
21
+ /**
22
+ * Get versions of a work
23
+ */
24
+ protected getVersionsImpl(_workId: string): Promise<VersionSummary[]>;
25
+ /**
26
+ * Get specific version
27
+ */
28
+ protected getVersionImpl(_versionId: string): Promise<Work>;
29
+ /**
30
+ * Health check
31
+ */
32
+ healthCheck(): Promise<void>;
33
+ /**
34
+ * Custom citation generation for Queensland
35
+ */
36
+ getCitation(work: Work, style: CitationStyle): string;
37
+ /**
38
+ * Ensure API key is present for authenticated requests
39
+ */
40
+ private ensureAuthenticated;
41
+ }
42
+ export default QueenslandProvider;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Queensland Legislation Provider
3
+ *
4
+ * Provides access to Queensland legislation via the official API at
5
+ * https://api.legislation.qld.gov.au/
6
+ */
7
+ import { logger } from '../utils/logger.js';
8
+ import { BaseLegislationProvider } from './legislation-provider.js';
9
+ import { normalizeWorkType, parseWorkIdMetadata } from './output-adapters.js';
10
+ export class QueenslandProvider extends BaseLegislationProvider {
11
+ baseUrl = 'https://api.legislation.qld.gov.au/v1';
12
+ apiKey;
13
+ constructor(apiKey) {
14
+ super('au-qld', 'Queensland', { requests: 30, per: 60 }, // 30 requests per minute
15
+ { max: 500, ttl: 24 * 60 * 60 * 1000 } // 500 entries, 24 hour TTL
16
+ );
17
+ this.apiKey = apiKey;
18
+ }
19
+ /**
20
+ * Search Queensland legislation
21
+ */
22
+ searchImpl(params) {
23
+ this.ensureAuthenticated();
24
+ // Implementation for the official QLD API search is currently in development.
25
+ logger.debug('Searching Queensland legislation', { params });
26
+ return Promise.reject(new Error('Queensland search is currently in development. Please use direct ID retrieval with "nzlegislation get <id> --jurisdiction au-qld".'));
27
+ }
28
+ /**
29
+ * Get work by ID
30
+ */
31
+ getWorkImpl(workId) {
32
+ this.ensureAuthenticated();
33
+ // Implementation for retrieving a specific work from QLD API
34
+ // e.g., GET /v1/content('{id}')
35
+ const metadata = parseWorkIdMetadata(workId);
36
+ return Promise.resolve({
37
+ ...metadata,
38
+ work_id: workId,
39
+ title: `${normalizeWorkType(metadata.type)} ${metadata.year} (Qld)`,
40
+ type: normalizeWorkType(metadata.type),
41
+ jurisdiction: 'au-qld',
42
+ status: 'in-force',
43
+ date: `${metadata.year || 1900}-01-01`,
44
+ url: `https://www.legislation.qld.gov.au/view/html/inforce/current/${workId.replace(/\//g, '-')}`,
45
+ versionCount: 1,
46
+ versions: [],
47
+ citations: {
48
+ australian: `${normalizeWorkType(metadata.type)} ${metadata.year} (Qld)`,
49
+ },
50
+ });
51
+ }
52
+ /**
53
+ * Get versions of a work
54
+ */
55
+ getVersionsImpl(_workId) {
56
+ this.ensureAuthenticated();
57
+ return Promise.resolve([]);
58
+ }
59
+ /**
60
+ * Get specific version
61
+ */
62
+ getVersionImpl(_versionId) {
63
+ this.ensureAuthenticated();
64
+ return Promise.reject(new Error('Not implemented - scraper pending'));
65
+ }
66
+ /**
67
+ * Health check
68
+ */
69
+ async healthCheck() {
70
+ // Check if Queensland legislation API is accessible
71
+ const controller = new AbortController();
72
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
73
+ try {
74
+ const response = await fetch(`${this.baseUrl}/health`, {
75
+ signal: controller.signal,
76
+ method: 'HEAD',
77
+ });
78
+ if (!response.ok && response.status !== 401) {
79
+ // 401 is fine for health check if we lack key
80
+ throw new Error(`Health check failed with status ${response.status}`);
81
+ }
82
+ }
83
+ finally {
84
+ clearTimeout(timeoutId);
85
+ }
86
+ }
87
+ /**
88
+ * Custom citation generation for Queensland
89
+ */
90
+ getCitation(work, style) {
91
+ if (style === 'australian') {
92
+ return `${work.title} (Qld)`;
93
+ }
94
+ return super.getCitation(work, style);
95
+ }
96
+ /**
97
+ * Ensure API key is present for authenticated requests
98
+ */
99
+ ensureAuthenticated() {
100
+ if (!this.apiKey) {
101
+ throw new Error('Queensland API key is required. Please set QUEENSLAND_API_KEY.');
102
+ }
103
+ }
104
+ }
105
+ export default QueenslandProvider;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * API Call Optimization Utilities
3
+ *
4
+ * Provides connection pooling, retry strategies, request deduplication,
5
+ * and payload optimization for improved API performance.
6
+ */
7
+ import { EventEmitter } from 'events';
8
+ type SearchParamValue = string | number | boolean | null | undefined;
9
+ type SearchParams = Record<string, SearchParamValue>;
10
+ interface RequestOptions {
11
+ searchParams?: SearchParams;
12
+ }
13
+ interface DeduplicationStats {
14
+ pendingRequests: number;
15
+ recentRequests: number;
16
+ }
17
+ /**
18
+ * Connection pool configuration
19
+ */
20
+ export interface ConnectionPoolConfig {
21
+ maxSockets?: number;
22
+ maxFreeSockets?: number;
23
+ timeout?: number;
24
+ keepAlive?: boolean;
25
+ }
26
+ /**
27
+ * Retry configuration
28
+ */
29
+ export interface RetryConfig {
30
+ maxRetries?: number;
31
+ baseDelay?: number;
32
+ maxDelay?: number;
33
+ retryableStatusCodes?: number[];
34
+ retryableMethods?: string[];
35
+ }
36
+ /**
37
+ * Request deduplication configuration
38
+ */
39
+ export interface DeduplicationConfig {
40
+ enabled?: boolean;
41
+ windowMs?: number;
42
+ }
43
+ /**
44
+ * Optimized API client options
45
+ */
46
+ export interface OptimizedClientOptions {
47
+ baseUrl: string;
48
+ apiKey: string;
49
+ pool?: ConnectionPoolConfig;
50
+ retry?: RetryConfig;
51
+ deduplication?: DeduplicationConfig;
52
+ }
53
+ /**
54
+ * Request metrics
55
+ */
56
+ export interface RequestMetrics {
57
+ totalRequests: number;
58
+ successfulRequests: number;
59
+ failedRequests: number;
60
+ retriedRequests: number;
61
+ deduplicatedRequests: number;
62
+ averageResponseTime: number;
63
+ p95ResponseTime: number;
64
+ p99ResponseTime: number;
65
+ }
66
+ export declare class OptimizedApiClient extends EventEmitter {
67
+ private readonly baseUrl;
68
+ private readonly apiKey;
69
+ private readonly pool;
70
+ private readonly retryManager;
71
+ private readonly deduplicationManager;
72
+ private metrics;
73
+ private responseTimes;
74
+ constructor(options: OptimizedClientOptions);
75
+ get<T>(endpoint: string, options?: RequestOptions): Promise<T>;
76
+ private createClient;
77
+ private executeWithRetry;
78
+ private trackResponseTime;
79
+ getMetrics(): RequestMetrics;
80
+ resetMetrics(): void;
81
+ getDeduplicationStats(): DeduplicationStats;
82
+ close(): void;
83
+ }
84
+ export declare class PayloadOptimizer {
85
+ static compressPayload(data: unknown, fields?: string[]): unknown;
86
+ private static pickFields;
87
+ static minify(data: unknown): string;
88
+ static estimateSize(data: unknown): number;
89
+ private static isJsonObject;
90
+ }
91
+ export declare function createOptimizedClient(options: OptimizedClientOptions): OptimizedApiClient;
92
+ export {};