georeference 0.0.0-stage → 1.0.0

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 (104) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/CONTRIBUTING.fr.md +44 -0
  3. package/CONTRIBUTING.md +44 -0
  4. package/LICENSE +21 -0
  5. package/README.fr.md +84 -0
  6. package/README.md +83 -2
  7. package/SECURITY.fr.md +9 -0
  8. package/SECURITY.md +9 -0
  9. package/dist/adapters/common.d.ts +5 -0
  10. package/dist/adapters/common.js +37 -0
  11. package/dist/adapters/common.js.map +1 -0
  12. package/dist/adapters/csv.d.ts +2 -0
  13. package/dist/adapters/csv.js +32 -0
  14. package/dist/adapters/csv.js.map +1 -0
  15. package/dist/adapters/geojson.d.ts +2 -0
  16. package/dist/adapters/geojson.js +36 -0
  17. package/dist/adapters/geojson.js.map +1 -0
  18. package/dist/adapters/index.d.ts +3 -0
  19. package/dist/adapters/index.js +10 -0
  20. package/dist/adapters/index.js.map +1 -0
  21. package/dist/adapters/json.d.ts +3 -0
  22. package/dist/adapters/json.js +48 -0
  23. package/dist/adapters/json.js.map +1 -0
  24. package/dist/adapters/postgres.d.ts +3 -0
  25. package/dist/adapters/postgres.js +59 -0
  26. package/dist/adapters/postgres.js.map +1 -0
  27. package/dist/adapters/sqlite.d.ts +2 -0
  28. package/dist/adapters/sqlite.js +49 -0
  29. package/dist/adapters/sqlite.js.map +1 -0
  30. package/dist/cli.d.ts +2 -0
  31. package/dist/cli.js +219 -0
  32. package/dist/cli.js.map +1 -0
  33. package/dist/client.d.ts +9 -0
  34. package/dist/client.js +24 -0
  35. package/dist/client.js.map +1 -0
  36. package/dist/export.d.ts +6 -0
  37. package/dist/export.js +127 -0
  38. package/dist/export.js.map +1 -0
  39. package/dist/files.d.ts +6 -0
  40. package/dist/files.js +28 -0
  41. package/dist/files.js.map +1 -0
  42. package/dist/generate.d.ts +10 -0
  43. package/dist/generate.js +102 -0
  44. package/dist/generate.js.map +1 -0
  45. package/dist/graph.d.ts +3 -0
  46. package/dist/graph.js +31 -0
  47. package/dist/graph.js.map +1 -0
  48. package/dist/index.d.ts +7 -0
  49. package/dist/index.js +6 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/package.d.ts +6 -0
  52. package/dist/package.js +133 -0
  53. package/dist/package.js.map +1 -0
  54. package/dist/seeding/index.d.ts +6 -0
  55. package/dist/seeding/index.js +80 -0
  56. package/dist/seeding/index.js.map +1 -0
  57. package/dist/seeding/ledger.d.ts +12 -0
  58. package/dist/seeding/ledger.js +54 -0
  59. package/dist/seeding/ledger.js.map +1 -0
  60. package/dist/seeding/mapping.d.ts +31 -0
  61. package/dist/seeding/mapping.js +54 -0
  62. package/dist/seeding/mapping.js.map +1 -0
  63. package/dist/seeding/preflight.d.ts +10 -0
  64. package/dist/seeding/preflight.js +149 -0
  65. package/dist/seeding/preflight.js.map +1 -0
  66. package/dist/seeding/retained.d.ts +5 -0
  67. package/dist/seeding/retained.js +32 -0
  68. package/dist/seeding/retained.js.map +1 -0
  69. package/dist/seeding/types.d.ts +41 -0
  70. package/dist/seeding/types.js +2 -0
  71. package/dist/seeding/types.js.map +1 -0
  72. package/dist/seeding/write.d.ts +4 -0
  73. package/dist/seeding/write.js +31 -0
  74. package/dist/seeding/write.js.map +1 -0
  75. package/dist/snapshot.d.ts +14 -0
  76. package/dist/snapshot.js +154 -0
  77. package/dist/snapshot.js.map +1 -0
  78. package/dist/sources/geonames.d.ts +14 -0
  79. package/dist/sources/geonames.js +118 -0
  80. package/dist/sources/geonames.js.map +1 -0
  81. package/dist/types.d.ts +113 -0
  82. package/dist/types.js +2 -0
  83. package/dist/types.js.map +1 -0
  84. package/dist/validation.d.ts +4 -0
  85. package/dist/validation.js +91 -0
  86. package/dist/validation.js.map +1 -0
  87. package/docs/architecture.fr.md +105 -0
  88. package/docs/architecture.md +105 -0
  89. package/docs/data-sources.fr.md +39 -0
  90. package/docs/data-sources.md +39 -0
  91. package/docs/format-decisions.fr.md +54 -0
  92. package/docs/format-decisions.md +54 -0
  93. package/docs/formats.fr.md +34 -0
  94. package/docs/formats.md +34 -0
  95. package/docs/generation.fr.md +29 -0
  96. package/docs/generation.md +29 -0
  97. package/docs/getting-started.fr.md +79 -0
  98. package/docs/getting-started.md +79 -0
  99. package/docs/seeding.fr.md +71 -0
  100. package/docs/seeding.md +71 -0
  101. package/examples/README.md +7 -0
  102. package/examples/metadata.json +13 -0
  103. package/examples/sample.ndjson +4 -0
  104. package/package.json +64 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport { createReadStream, createWriteStream } from 'node:fs';\nimport { access, mkdir, mkdtemp, rename, rm, writeFile, unlink } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { pipeline } from 'node:stream/promises';\nimport { createGzip } from 'node:zlib';\nimport yazl from 'yazl';\nimport { canonicalJson } from './validation.ts';\nimport { fileDigest, packageFile } from './files.ts';\nimport type { ExportAdapter, ExportManifest, ExportRequest } from './types.ts';\nimport type { Snapshot } from './snapshot.ts';\n\nconst extensions=new Map<string,ExportAdapter>();\nconst builtins=['json','ndjson','csv','sql-postgres','sqlite','geojson'];\nasync function writeManifest(directory:string,manifest:ExportManifest):Promise<void> {\n const text=JSON.stringify(manifest,null,2)+'\\n';\n if(Buffer.byteLength(text)>1024*1024) throw new Error('Manifest exceeds 1 MiB limit; split the export scope');\n await writeFile(join(directory,'manifest.json'),text);\n}\nexport function registerExportAdapter(adapter:ExportAdapter):void {\n if (!adapter.format || !adapter.version || builtins.includes(adapter.format) || extensions.has(adapter.format)) throw new Error('Adapter format must be unique and versioned');\n extensions.set(adapter.format,adapter);\n}\nfunction normalize(request:ExportRequest):ExportRequest {\n const allowed=new Set(['format','layout','compression','countries','subdivisions','entityTypes','includeAncestors','coordinates','boundaries','schemaMode','sqlMode','spreadsheetSafe']);\n for (const key of Object.keys(request)) if (!allowed.has(key)) throw new Error(`Unknown export option: ${key}`);\n if (!['none','gzip','zip'].includes(request.compression??'none')) throw new Error('Unsupported compression');\n for(const [key,allowedValues] of Object.entries({layout:['flat','hierarchical'],schemaMode:['schema-and-data','data-only'],sqlMode:['upsert','insert-only']})) {\n const value=(request as unknown as Record<string,unknown>)[key];\n if(value!==undefined&&!allowedValues.includes(value as string)) throw new Error(`Invalid ${key}`);\n }\n for (const key of ['includeAncestors','coordinates','boundaries','spreadsheetSafe'] as const) if (request[key]!==undefined && typeof request[key]!=='boolean') throw new Error(`Invalid boolean option: ${key}`);\n const result:ExportRequest={...request,layout:request.layout??'flat',compression:request.compression??'none',includeAncestors:request.includeAncestors??true,coordinates:request.coordinates??false,boundaries:request.boundaries??false};\n for (const key of ['countries','subdivisions','entityTypes'] as const) {\n if (request[key]!==undefined) {\n if (!Array.isArray(request[key]) || request[key]!.some(value=>typeof value!=='string'||!value)) throw new Error(`Invalid ${key}`);\n (result as unknown as Record<string,unknown>)[key]=[...new Set(request[key])].sort();\n }\n }\n return result;\n}\nexport async function exportSnapshot(snapshot:Snapshot,directory:string,request:ExportRequest,options:{signal?:AbortSignal}={}):Promise<ExportManifest> {\n options.signal?.throwIfAborted();\n const normalized=normalize(request);\n const adapters=await import('./adapters/index.ts');\n const adapter=extensions.get(normalized.format)??adapters.getBuiltinAdapters().find(item=>item.format===normalized.format);\n if (!adapter) throw new Error(`Unsupported export format: ${normalized.format}`);\n adapter.validate(normalized);\n await mkdir(dirname(directory),{recursive:true});\n try { await access(directory); throw new Error('Export destination already exists'); } catch(error) { if ((error as NodeJS.ErrnoException).code!=='ENOENT') throw error; }\n const temporary=await mkdtemp(join(dirname(directory),'.'+basename(directory)+'-'));\n let disposePlan:(()=>void)|undefined;\n try {\n const plan=snapshot.select(normalized);disposePlan=plan.dispose;\n const hash=createHash('sha256').update(canonicalJson({metadata:plan.metadata,request:normalized}));\n const counts={country:0,subdivision:0,locality:0}; let missingCoordinates=0,missingBoundaries=0;\n for (const entity of (plan.identityEntities?.()??plan.entities())) {\n options.signal?.throwIfAborted(); hash.update('\\n'+canonicalJson(entity));counts[entity.entityType]++;\n if (normalized.coordinates && !entity.coordinates) missingCoordinates++;\n if (normalized.boundaries && !entity.boundary) missingBoundaries++;\n }\n let paths=await adapter.write({plan,directory:temporary,...(options.signal?{signal:options.signal}:{})});\n if (!paths.length || new Set(paths).size!==paths.length || paths.includes('manifest.json')) throw new Error('Adapter returned invalid file list');\n for (const path of paths) await packageFile(temporary,path);\n if (normalized.compression==='gzip') {\n const compressed:string[]=[];\n for (const path of paths) {\n await pipeline(createReadStream(join(temporary,path)),createGzip(),createWriteStream(join(temporary,path+'.gz'),{flags:'wx'}),{signal:options.signal});\n await unlink(join(temporary,path));compressed.push(path+'.gz');\n }\n paths=compressed;\n }\n const manifest:ExportManifest={contractVersion:'1.0',generatorVersion:'1.0.0',adapterVersion:adapter.version,datasetVersion:plan.metadata.datasetVersion,\n contentId:hash.digest('hex'),request:normalized,sources:plan.metadata.sources,count:plan.count,counts,contextIds:plan.contextIds,externalReferences:plan.externalReferences,\n geometry:{coordinates:{requested:normalized.coordinates!,missing:missingCoordinates},boundaries:{requested:normalized.boundaries!,missing:missingBoundaries},crs:'OGC:CRS84'},\n scope:{kind:'full',complete:plan.count===snapshot.count&&normalized.includeAncestors!==false},files:[]};\n for (const path of paths.sort()) manifest.files.push({path,...await fileDigest(join(temporary,path))});\n await writeManifest(temporary,manifest);\n if (normalized.compression==='zip') {\n const zip=new yazl.ZipFile();\n for (const path of [...paths,'manifest.json']) zip.addFile(join(temporary,path),path,{mtime:new Date('1980-01-01T00:00:00Z')});\n const finish=pipeline(zip.outputStream,createWriteStream(join(temporary,'package.zip'),{flags:'wx'}),{signal:options.signal});\n zip.end();await finish;\n for (const path of paths) await unlink(join(temporary,path));\n manifest.files=[{path:'package.zip',...await fileDigest(join(temporary,'package.zip'))}];\n await writeManifest(temporary,manifest);\n }\n options.signal?.throwIfAborted();\n await rename(temporary,directory);\n return manifest;\n } catch(error) {\n await rm(temporary,{recursive:true,force:true}); throw error;\n } finally {disposePlan?.();}\n}\n"]}
@@ -0,0 +1,6 @@
1
+ export declare function safeRelativePath(path: string): void;
2
+ export declare function packageFile(directory: string, path: string): Promise<string>;
3
+ export declare function fileDigest(path: string): Promise<{
4
+ bytes: number;
5
+ sha256: string;
6
+ }>;
package/dist/files.js ADDED
@@ -0,0 +1,28 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { createReadStream } from 'node:fs';
3
+ import { lstat, realpath } from 'node:fs/promises';
4
+ import { isAbsolute, resolve, sep } from 'node:path';
5
+ export function safeRelativePath(path) {
6
+ if (typeof path !== 'string' || !path || isAbsolute(path) || path.includes('\\') || path.split('/').some(part => part === '..' || part === '.' || !part) || path.includes(':') || path.includes('\0'))
7
+ throw new Error('Unsafe package file path');
8
+ }
9
+ export async function packageFile(directory, path) {
10
+ safeRelativePath(path);
11
+ const base = await realpath(directory);
12
+ const file = resolve(base, path);
13
+ const stat = await lstat(file);
14
+ const actual = await realpath(file);
15
+ if (!stat.isFile() || stat.isSymbolicLink() || !actual.startsWith(base + sep))
16
+ throw new Error('Unsafe package file path or symlink');
17
+ return file;
18
+ }
19
+ export async function fileDigest(path) {
20
+ const hash = createHash('sha256');
21
+ let bytes = 0;
22
+ for await (const chunk of createReadStream(path)) {
23
+ bytes += chunk.length;
24
+ hash.update(chunk);
25
+ }
26
+ return { bytes, sha256: hash.digest('hex') };
27
+ }
28
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAErD,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA,EAAE,CAAA,IAAI,KAAG,IAAI,IAAE,IAAI,KAAG,GAAG,IAAE,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;AAC3O,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,SAAiB,EAAE,IAAY;IAC/D,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,GAAC,MAAM,QAAQ,CAAC,SAAS,CAAC,CAAC;IAAC,MAAM,IAAI,GAAC,OAAO,CAAC,IAAI,EAAC,IAAI,CAAC,CAAC;IACpE,MAAM,IAAI,GAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IAAC,MAAM,MAAM,GAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IAChE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,GAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACpI,OAAO,IAAI,CAAC;AACd,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,MAAM,IAAI,GAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAAC,IAAI,KAAK,GAAC,CAAC,CAAC;IAC7C,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAAC,KAAK,IAAE,KAAK,CAAC,MAAM,CAAC;QAAA,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAAC,CAAC;IAC7F,OAAO,EAAC,KAAK,EAAC,MAAM,EAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,CAAC;AAC3C,CAAC","sourcesContent":["import { createHash } from 'node:crypto';\nimport { createReadStream } from 'node:fs';\nimport { lstat, realpath } from 'node:fs/promises';\nimport { isAbsolute, resolve, sep } from 'node:path';\n\nexport function safeRelativePath(path: string): void {\n if (typeof path !== 'string' || !path || isAbsolute(path) || path.includes('\\\\') || path.split('/').some(part=>part==='..'||part==='.'||!part) || path.includes(':') || path.includes('\\0')) throw new Error('Unsafe package file path');\n}\nexport async function packageFile(directory: string, path: string): Promise<string> {\n safeRelativePath(path);\n const base=await realpath(directory); const file=resolve(base,path);\n const stat=await lstat(file); const actual=await realpath(file);\n if (!stat.isFile() || stat.isSymbolicLink() || !actual.startsWith(base+sep)) throw new Error('Unsafe package file path or symlink');\n return file;\n}\nexport async function fileDigest(path: string): Promise<{bytes:number;sha256:string}> {\n const hash=createHash('sha256'); let bytes=0;\n for await (const chunk of createReadStream(path)) { bytes+=chunk.length;hash.update(chunk); }\n return {bytes,sha256:hash.digest('hex')};\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { ExportManifest, ExportRequest } from './types.ts';
2
+ export interface GenerateOptions {
3
+ output: string;
4
+ request: ExportRequest;
5
+ snapshot?: string;
6
+ sourceFile?: string;
7
+ signal?: AbortSignal;
8
+ }
9
+ /** Owns only temporary source/snapshot files and the new output directory. */
10
+ export declare function generateFiles(options: GenerateOptions, download?: typeof fetch): Promise<ExportManifest>;
@@ -0,0 +1,102 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { lstat, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { Snapshot, createSnapshot } from "./snapshot.js";
6
+ import { exportSnapshot } from "./export.js";
7
+ import { importGeoNames } from "./sources/geonames.js";
8
+ import { getBuiltinAdapters } from "./adapters/index.js";
9
+ const COUNTRY_SOURCE = 'https://download.geonames.org/export/dump/countryInfo.txt';
10
+ const MAX_SOURCE_BYTES = 2 * 1024 * 1024;
11
+ async function downloadCountries(download, signal) {
12
+ let response;
13
+ try {
14
+ response = await download(COUNTRY_SOURCE, { signal });
15
+ }
16
+ catch (error) {
17
+ if (signal.aborted)
18
+ throw signal.reason;
19
+ throw new Error('Unable to download GeoNames; check your connection or use --source-file countryInfo.txt', { cause: error });
20
+ }
21
+ if (!response.ok) {
22
+ await response.body?.cancel();
23
+ throw new Error(`GeoNames download failed (HTTP ${response.status}); retry or use --source-file countryInfo.txt`);
24
+ }
25
+ if (!response.body)
26
+ throw new Error('GeoNames returned an empty response');
27
+ const reader = response.body.getReader();
28
+ const chunks = [];
29
+ let size = 0;
30
+ try {
31
+ while (true) {
32
+ signal.throwIfAborted();
33
+ const chunk = await reader.read();
34
+ if (chunk.done)
35
+ break;
36
+ size += chunk.value.byteLength;
37
+ if (size > MAX_SOURCE_BYTES)
38
+ throw new Error('Country source exceeds 2 MiB limit');
39
+ chunks.push(chunk.value);
40
+ }
41
+ return Buffer.concat(chunks);
42
+ }
43
+ finally {
44
+ await reader.cancel();
45
+ reader.releaseLock();
46
+ }
47
+ }
48
+ /** Owns only temporary source/snapshot files and the new output directory. */
49
+ export async function generateFiles(options, download = fetch) {
50
+ options.signal?.throwIfAborted();
51
+ if (options.snapshot && options.sourceFile)
52
+ throw new Error('--snapshot and --source-file cannot be combined');
53
+ const adapter = getBuiltinAdapters().find(item => item.format === options.request.format);
54
+ if (!adapter)
55
+ throw new Error(`Unsupported export format: ${options.request.format}`);
56
+ adapter.validate(options.request);
57
+ try {
58
+ await lstat(options.output);
59
+ throw new Error('Export destination already exists');
60
+ }
61
+ catch (error) {
62
+ if (error.code !== 'ENOENT')
63
+ throw error;
64
+ }
65
+ const exportOptions = options.signal ? { signal: options.signal } : {};
66
+ if (options.snapshot) {
67
+ const snapshot = Snapshot.open(options.snapshot);
68
+ try {
69
+ return await exportSnapshot(snapshot, options.output, options.request, exportOptions);
70
+ }
71
+ finally {
72
+ snapshot.close();
73
+ }
74
+ }
75
+ const temporary = await mkdtemp(join(tmpdir(), 'georeference-generate-'));
76
+ try {
77
+ if (options.sourceFile && (await stat(options.sourceFile)).size > MAX_SOURCE_BYTES)
78
+ throw new Error('Country source exceeds 2 MiB limit');
79
+ const signal = options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(30_000)]) : AbortSignal.timeout(30_000);
80
+ const bytes = options.sourceFile ? await readFile(options.sourceFile) : await downloadCountries(download, signal);
81
+ options.signal?.throwIfAborted();
82
+ if (bytes.length > MAX_SOURCE_BYTES)
83
+ throw new Error('Country source exceeds 2 MiB limit');
84
+ const source = join(temporary, 'countryInfo.txt');
85
+ await writeFile(source, bytes);
86
+ const revision = createHash('sha256').update(bytes).digest('hex');
87
+ const imported = await importGeoNames({ countryInfo: source }, { datasetVersion: 'geonames-countryInfo-sha256:' + revision });
88
+ const path = join(temporary, 'snapshot.sqlite');
89
+ await createSnapshot(path, imported.metadata, imported.entities);
90
+ const snapshot = Snapshot.open(path);
91
+ try {
92
+ return await exportSnapshot(snapshot, options.output, options.request, exportOptions);
93
+ }
94
+ finally {
95
+ snapshot.close();
96
+ }
97
+ }
98
+ finally {
99
+ await rm(temporary, { recursive: true, force: true });
100
+ }
101
+ }
102
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExportManifest, ExportRequest } from './types.ts';\nimport { createHash } from 'node:crypto';\nimport { lstat,mkdtemp,readFile,rm,stat,writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { Snapshot,createSnapshot } from './snapshot.ts';\nimport { exportSnapshot } from './export.ts';\nimport { importGeoNames } from './sources/geonames.ts';\nimport { getBuiltinAdapters } from './adapters/index.ts';\nexport interface GenerateOptions { output:string; request:ExportRequest; snapshot?:string; sourceFile?:string; signal?:AbortSignal }\nconst COUNTRY_SOURCE='https://download.geonames.org/export/dump/countryInfo.txt';\nconst MAX_SOURCE_BYTES=2*1024*1024;\nasync function downloadCountries(download:typeof fetch,signal:AbortSignal):Promise<Buffer> {\n let response:Response;\n try {response=await download(COUNTRY_SOURCE,{signal});}\n catch(error) {\n if(signal.aborted) throw signal.reason;\n throw new Error('Unable to download GeoNames; check your connection or use --source-file countryInfo.txt',{cause:error});\n }\n if(!response.ok) {await response.body?.cancel();throw new Error(`GeoNames download failed (HTTP ${response.status}); retry or use --source-file countryInfo.txt`);}\n if(!response.body) throw new Error('GeoNames returned an empty response');\n const reader=response.body.getReader();const chunks:Uint8Array[]=[];let size=0;\n try {\n while(true) {\n signal.throwIfAborted();const chunk=await reader.read();if(chunk.done)break;\n size+=chunk.value.byteLength;\n if(size>MAX_SOURCE_BYTES) throw new Error('Country source exceeds 2 MiB limit');\n chunks.push(chunk.value);\n }\n return Buffer.concat(chunks);\n } finally {await reader.cancel();reader.releaseLock();}\n}\n/** Owns only temporary source/snapshot files and the new output directory. */\nexport async function generateFiles(options:GenerateOptions,download:typeof fetch=fetch):Promise<ExportManifest> {\n options.signal?.throwIfAborted();\n if(options.snapshot&&options.sourceFile) throw new Error('--snapshot and --source-file cannot be combined');\n const adapter=getBuiltinAdapters().find(item=>item.format===options.request.format);\n if(!adapter) throw new Error(`Unsupported export format: ${options.request.format}`);\n adapter.validate(options.request);\n try {await lstat(options.output);throw new Error('Export destination already exists');}\n catch(error) {if((error as NodeJS.ErrnoException).code!=='ENOENT') throw error;}\n const exportOptions=options.signal?{signal:options.signal}:{};\n if(options.snapshot) {\n const snapshot=Snapshot.open(options.snapshot);\n try {return await exportSnapshot(snapshot,options.output,options.request,exportOptions);}\n finally {snapshot.close();}\n }\n const temporary=await mkdtemp(join(tmpdir(),'georeference-generate-'));\n try {\n if(options.sourceFile&&(await stat(options.sourceFile)).size>MAX_SOURCE_BYTES) throw new Error('Country source exceeds 2 MiB limit');\n const signal=options.signal?AbortSignal.any([options.signal,AbortSignal.timeout(30_000)]):AbortSignal.timeout(30_000);\n const bytes=options.sourceFile?await readFile(options.sourceFile):await downloadCountries(download,signal);\n options.signal?.throwIfAborted();\n if(bytes.length>MAX_SOURCE_BYTES) throw new Error('Country source exceeds 2 MiB limit');\n const source=join(temporary,'countryInfo.txt');await writeFile(source,bytes);\n const revision=createHash('sha256').update(bytes).digest('hex');\n const imported=await importGeoNames({countryInfo:source},{datasetVersion:'geonames-countryInfo-sha256:'+revision});\n const path=join(temporary,'snapshot.sqlite');await createSnapshot(path,imported.metadata,imported.entities);\n const snapshot=Snapshot.open(path);\n try {return await exportSnapshot(snapshot,options.output,options.request,exportOptions);}\n finally {snapshot.close();}\n } finally {await rm(temporary,{recursive:true,force:true});}\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { Entity } from './types.ts';
2
+ /** Validate internal relationships; declared external references are resolved by the target importer. */
3
+ export declare function validateGraph(entities: Entity[]): void;
package/dist/graph.js ADDED
@@ -0,0 +1,31 @@
1
+ /** Validate internal relationships; declared external references are resolved by the target importer. */
2
+ export function validateGraph(entities) {
3
+ const byId = new Map(entities.map(entity => [entity.id, entity]));
4
+ const children = new Map();
5
+ const indegree = new Map();
6
+ for (const entity of entities) {
7
+ const country = byId.get(entity.countryId), parent = entity.parentId ? byId.get(entity.parentId) : undefined;
8
+ if (country && country.entityType !== 'country')
9
+ throw new Error('countryId must reference a country');
10
+ if (parent && (parent.countryId !== entity.countryId || parent.entityType === 'locality'))
11
+ throw new Error('Invalid country or parent relationship');
12
+ indegree.set(entity.id, parent ? 1 : 0);
13
+ if (parent) {
14
+ const list = children.get(parent.id) ?? [];
15
+ list.push(entity.id);
16
+ children.set(parent.id, list);
17
+ }
18
+ }
19
+ const queue = entities.filter(entity => indegree.get(entity.id) === 0).map(entity => entity.id);
20
+ let visited = 0;
21
+ for (let index = 0; index < queue.length; index++) {
22
+ visited++;
23
+ for (const child of children.get(queue[index]) ?? []) {
24
+ indegree.set(child, 0);
25
+ queue.push(child);
26
+ }
27
+ }
28
+ if (visited !== entities.length)
29
+ throw new Error('Cycle in entity parent graph');
30
+ }
31
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"AAEA,yGAAyG;AACzG,MAAM,UAAU,aAAa,CAAC,QAAiB;IAC7C,MAAM,IAAI,GAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAA,EAAE,CAAA,CAAC,MAAM,CAAC,EAAE,EAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAC,IAAI,GAAG,EAAmB,CAAC;IAAA,MAAM,QAAQ,GAAC,IAAI,GAAG,EAAiB,CAAC;IAClF,KAAI,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,EAAC,MAAM,GAAC,MAAM,CAAC,QAAQ,CAAA,CAAC,CAAA,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA,CAAC,CAAA,SAAS,CAAC;QACpG,IAAG,OAAO,IAAE,OAAO,CAAC,UAAU,KAAG,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QAClG,IAAG,MAAM,IAAE,CAAC,MAAM,CAAC,SAAS,KAAG,MAAM,CAAC,SAAS,IAAE,MAAM,CAAC,UAAU,KAAG,UAAU,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5I,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAC,MAAM,CAAA,CAAC,CAAA,CAAC,CAAA,CAAC,CAAA,CAAC,CAAC,CAAC;QACnC,IAAG,MAAM,EAAE,CAAC;YAAA,MAAM,IAAI,GAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAE,EAAE,CAAC;YAAA,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAAA,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAC,IAAI,CAAC,CAAC;QAAA,CAAC;IACxG,CAAC;IACD,MAAM,KAAK,GAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAA,EAAE,CAAA,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,KAAG,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAA,EAAE,CAAA,MAAM,CAAC,EAAE,CAAC,CAAC;IAAA,IAAI,OAAO,GAAC,CAAC,CAAC;IACtG,KAAI,IAAI,KAAK,GAAC,CAAC,EAAC,KAAK,GAAC,KAAK,CAAC,MAAM,EAAC,KAAK,EAAE,EAAE,CAAC;QAC3C,OAAO,EAAE,CAAC;QACV,KAAI,MAAM,KAAK,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAE,CAAC,IAAE,EAAE,EAAE,CAAC;YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAC,CAAC,CAAC,CAAC;YAAA,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAAA,CAAC;IAChG,CAAC;IACD,IAAG,OAAO,KAAG,QAAQ,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;AAChF,CAAC","sourcesContent":["import type { Entity } from './types.ts';\n\n/** Validate internal relationships; declared external references are resolved by the target importer. */\nexport function validateGraph(entities:Entity[]):void {\n const byId=new Map(entities.map(entity=>[entity.id,entity]));\n const children=new Map<string,string[]>();const indegree=new Map<string,number>();\n for(const entity of entities) {\n const country=byId.get(entity.countryId),parent=entity.parentId?byId.get(entity.parentId):undefined;\n if(country&&country.entityType!=='country') throw new Error('countryId must reference a country');\n if(parent&&(parent.countryId!==entity.countryId||parent.entityType==='locality')) throw new Error('Invalid country or parent relationship');\n indegree.set(entity.id,parent?1:0);\n if(parent) {const list=children.get(parent.id)??[];list.push(entity.id);children.set(parent.id,list);}\n }\n const queue=entities.filter(entity=>indegree.get(entity.id)===0).map(entity=>entity.id);let visited=0;\n for(let index=0;index<queue.length;index++) {\n visited++;\n for(const child of children.get(queue[index]!)??[]) {indegree.set(child,0);queue.push(child);}\n }\n if(visited!==entities.length) throw new Error('Cycle in entity parent graph');\n}\n"]}
@@ -0,0 +1,7 @@
1
+ export type * from './types.ts';
2
+ export { Snapshot, createSnapshot } from './snapshot.ts';
3
+ export { createClient } from './client.ts';
4
+ export { exportSnapshot, registerExportAdapter } from './export.ts';
5
+ export { verifyPackage, readPackage } from './package.ts';
6
+ export { importGeoNames } from './sources/geonames.ts';
7
+ export type { GeoNamesPaths } from './sources/geonames.ts';
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { Snapshot, createSnapshot } from "./snapshot.js";
2
+ export { createClient } from "./client.js";
3
+ export { exportSnapshot, registerExportAdapter } from "./export.js";
4
+ export { verifyPackage, readPackage } from "./package.js";
5
+ export { importGeoNames } from "./sources/geonames.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC","sourcesContent":["export type * from './types.ts';\nexport { Snapshot, createSnapshot } from './snapshot.ts';\nexport { createClient } from './client.ts';\nexport { exportSnapshot, registerExportAdapter } from './export.ts';\nexport { verifyPackage, readPackage } from './package.ts';\nexport { importGeoNames } from './sources/geonames.ts';\nexport type { GeoNamesPaths } from './sources/geonames.ts';\n"]}
@@ -0,0 +1,6 @@
1
+ import type { ExportManifest, Entity } from './types.ts';
2
+ export declare function verifyPackage(directory: string): Promise<ExportManifest>;
3
+ export declare function readPackage(directory: string): Promise<{
4
+ manifest: ExportManifest;
5
+ entities: Entity[];
6
+ }>;
@@ -0,0 +1,133 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import { readFile, stat } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { createGunzip } from 'node:zlib';
5
+ import { createHash } from 'node:crypto';
6
+ import { parse } from 'csv-parse/sync';
7
+ import { fileDigest, packageFile } from "./files.js";
8
+ import { canonicalJson, validateEntity, validateMetadata } from "./validation.js";
9
+ import { validateGraph } from "./graph.js";
10
+ export async function verifyPackage(directory) {
11
+ const manifestPath = await packageFile(directory, 'manifest.json');
12
+ if ((await stat(manifestPath)).size > 1024 * 1024)
13
+ throw new Error('Manifest exceeds 1 MiB limit');
14
+ const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
15
+ validateMetadata({ contractVersion: manifest.contractVersion, datasetVersion: manifest.datasetVersion, sources: manifest.sources });
16
+ if (!manifest.request || !manifest.counts || !Number.isSafeInteger(manifest.count) || manifest.count < 0 || !/^[a-f0-9]{64}$/.test(manifest.contentId) || !Array.isArray(manifest.files) || !manifest.files.length || manifest.files.length > 10000 || !Array.isArray(manifest.externalReferences) || !Array.isArray(manifest.contextIds))
17
+ throw new Error('Invalid package manifest');
18
+ const seen = new Set();
19
+ for (const item of manifest.files) {
20
+ if (!item || seen.has(item.path) || item.path === 'manifest.json' || !/^[a-f0-9]{64}$/.test(item.sha256) || !Number.isSafeInteger(item.bytes) || item.bytes < 0)
21
+ throw new Error('Invalid manifest file entry');
22
+ seen.add(item.path);
23
+ const digest = await fileDigest(await packageFile(directory, item.path));
24
+ if (digest.bytes !== item.bytes || digest.sha256 !== item.sha256)
25
+ throw new Error(`Package integrity/checksum mismatch: ${item.path}`);
26
+ }
27
+ return manifest;
28
+ }
29
+ const MAX_IMPORT_BYTES = 128 * 1024 * 1024;
30
+ async function boundedText(path) {
31
+ const input = createReadStream(path);
32
+ const stream = path.endsWith('.gz') ? input.pipe(createGunzip()) : input;
33
+ const chunks = [];
34
+ let size = 0;
35
+ try {
36
+ for await (const chunk of stream) {
37
+ size += chunk.length;
38
+ if (size > MAX_IMPORT_BYTES)
39
+ throw new Error('Import exceeds 128 MiB uncompressed limit; split the package');
40
+ chunks.push(Buffer.from(chunk));
41
+ }
42
+ return Buffer.concat(chunks).toString('utf8');
43
+ }
44
+ finally {
45
+ input.destroy();
46
+ stream.destroy();
47
+ }
48
+ }
49
+ function flatten(nodes, entities) {
50
+ for (const node of nodes) {
51
+ if (!node || typeof node !== 'object' || Array.isArray(node))
52
+ throw new Error('Invalid hierarchical entity');
53
+ const { children, ...entity } = node;
54
+ entities.push(entity);
55
+ if (children !== undefined) {
56
+ if (!Array.isArray(children))
57
+ throw new Error('Invalid children');
58
+ flatten(children, entities);
59
+ }
60
+ }
61
+ }
62
+ export async function readPackage(directory) {
63
+ const manifest = await verifyPackage(directory);
64
+ if (manifest.request.spreadsheetSafe)
65
+ throw new Error('Spreadsheet consultation profiles cannot be imported');
66
+ if (manifest.files.some(file => file.path.endsWith('.zip')))
67
+ throw new Error('Extract package.zip into a new directory before importing');
68
+ const format = manifest.request.format;
69
+ if (!['json', 'ndjson', 'csv'].includes(format))
70
+ throw new Error('Seeding requires a JSON, NDJSON or normalized CSV package');
71
+ if (manifest.files.length !== 1)
72
+ throw new Error('V1 import expects one data file per package');
73
+ const text = await boundedText(await packageFile(directory, manifest.files[0].path));
74
+ let raw;
75
+ if (format === 'json') {
76
+ const document = JSON.parse(text);
77
+ if (!Array.isArray(document.entities))
78
+ throw new Error('Invalid JSON entities array');
79
+ raw = [];
80
+ flatten(document.entities, raw);
81
+ }
82
+ else if (format === 'ndjson')
83
+ raw = text.split(/\r?\n/).filter(line => line.length > 0).map(line => JSON.parse(line));
84
+ else {
85
+ const rows = parse(text, { columns: true, skip_empty_lines: true, max_record_size: 8 * 1024 * 1024, bom: true });
86
+ const jsonFields = new Set(['parentId', 'codes', 'names', 'level', 'coordinates', 'boundary']);
87
+ raw = rows.map(row => {
88
+ const result = {};
89
+ for (const [key, value] of Object.entries(row)) {
90
+ if (jsonFields.has(key)) {
91
+ if (value !== '')
92
+ result[key] = JSON.parse(value);
93
+ }
94
+ else
95
+ result[key] = value;
96
+ }
97
+ return result;
98
+ });
99
+ }
100
+ if (raw.length !== manifest.count || raw.length > 500000)
101
+ throw new Error('Package entity count mismatch or import limit exceeded');
102
+ const entities = [];
103
+ const seen = new Set();
104
+ const counts = { country: 0, subdivision: 0, locality: 0 };
105
+ const required = new Set();
106
+ for (const row of raw) {
107
+ validateEntity(row);
108
+ if (seen.has(row.id))
109
+ throw new Error(`Duplicate entity ID: ${row.id}`);
110
+ if ((row.coordinates !== undefined && !manifest.request.coordinates) || (row.boundary !== undefined && !manifest.request.boundaries))
111
+ throw new Error('Unexpected field outside manifest projection');
112
+ seen.add(row.id);
113
+ entities.push(row);
114
+ counts[row.entityType]++;
115
+ if (row.parentId)
116
+ required.add(row.parentId);
117
+ required.add(row.countryId);
118
+ }
119
+ for (const key of ['country', 'subdivision', 'locality'])
120
+ if (counts[key] !== manifest.counts[key])
121
+ throw new Error('Manifest counts do not match data');
122
+ const external = [...required].filter(id => !seen.has(id)).sort();
123
+ if (JSON.stringify(external) !== JSON.stringify([...manifest.externalReferences].sort()))
124
+ throw new Error('Unresolved references differ from manifest');
125
+ validateGraph(entities);
126
+ const identity = createHash('sha256').update(canonicalJson({ metadata: { contractVersion: manifest.contractVersion, datasetVersion: manifest.datasetVersion, sources: manifest.sources }, request: manifest.request }));
127
+ for (const entity of [...entities].sort((a, b) => Buffer.compare(Buffer.from(a.id), Buffer.from(b.id))))
128
+ identity.update('\n' + canonicalJson(entity));
129
+ if (identity.digest('hex') !== manifest.contentId)
130
+ throw new Error('Package contentId identity does not match metadata and data');
131
+ return { manifest, entities };
132
+ }
133
+ //# sourceMappingURL=package.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package.js","sourceRoot":"","sources":["../src/package.ts"],"names":[],"mappings":"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","sourcesContent":["import { createReadStream } from 'node:fs';\nimport { readFile, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { createGunzip } from 'node:zlib';\nimport { createHash } from 'node:crypto';\nimport { parse } from 'csv-parse/sync';\nimport { fileDigest, packageFile } from './files.ts';\nimport { canonicalJson, validateEntity, validateMetadata } from './validation.ts';\nimport { validateGraph } from './graph.ts';\nimport type { ExportManifest, Entity } from './types.ts';\n\nexport async function verifyPackage(directory:string):Promise<ExportManifest> {\n const manifestPath=await packageFile(directory,'manifest.json');\n if ((await stat(manifestPath)).size>1024*1024) throw new Error('Manifest exceeds 1 MiB limit');\n const manifest=JSON.parse(await readFile(manifestPath,'utf8')) as ExportManifest;\n validateMetadata({contractVersion:manifest.contractVersion,datasetVersion:manifest.datasetVersion,sources:manifest.sources});\n if (!manifest.request || !manifest.counts || !Number.isSafeInteger(manifest.count) || manifest.count<0 || !/^[a-f0-9]{64}$/.test(manifest.contentId) || !Array.isArray(manifest.files) || !manifest.files.length || manifest.files.length>10000 || !Array.isArray(manifest.externalReferences) || !Array.isArray(manifest.contextIds)) throw new Error('Invalid package manifest');\n const seen=new Set<string>();\n for (const item of manifest.files) {\n if (!item || seen.has(item.path) || item.path==='manifest.json' || !/^[a-f0-9]{64}$/.test(item.sha256) || !Number.isSafeInteger(item.bytes) || item.bytes<0) throw new Error('Invalid manifest file entry');\n seen.add(item.path);\n const digest=await fileDigest(await packageFile(directory,item.path));\n if (digest.bytes!==item.bytes||digest.sha256!==item.sha256) throw new Error(`Package integrity/checksum mismatch: ${item.path}`);\n }\n return manifest;\n}\nconst MAX_IMPORT_BYTES=128*1024*1024;\nasync function boundedText(path:string):Promise<string> {\n const input=createReadStream(path);const stream=path.endsWith('.gz')?input.pipe(createGunzip()):input;\n const chunks:Buffer[]=[];let size=0;\n try {\n for await (const chunk of stream) {\n size+=chunk.length;\n if (size>MAX_IMPORT_BYTES) throw new Error('Import exceeds 128 MiB uncompressed limit; split the package');\n chunks.push(Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString('utf8');\n } finally { input.destroy();stream.destroy(); }\n}\nfunction flatten(nodes:unknown[],entities:unknown[]):void {\n for (const node of nodes) {\n if (!node || typeof node!=='object' || Array.isArray(node)) throw new Error('Invalid hierarchical entity');\n const {children,...entity}=node as Record<string,unknown>;\n entities.push(entity);\n if (children!==undefined) { if(!Array.isArray(children)) throw new Error('Invalid children');flatten(children,entities); }\n }\n}\nexport async function readPackage(directory:string):Promise<{manifest:ExportManifest;entities:Entity[]}> {\n const manifest=await verifyPackage(directory);\n if (manifest.request.spreadsheetSafe) throw new Error('Spreadsheet consultation profiles cannot be imported');\n if (manifest.files.some(file=>file.path.endsWith('.zip'))) throw new Error('Extract package.zip into a new directory before importing');\n const format=manifest.request.format;\n if (!['json','ndjson','csv'].includes(format)) throw new Error('Seeding requires a JSON, NDJSON or normalized CSV package');\n if (manifest.files.length!==1) throw new Error('V1 import expects one data file per package');\n const text=await boundedText(await packageFile(directory,manifest.files[0]!.path));\n let raw:unknown[];\n if (format==='json') {\n const document=JSON.parse(text) as {entities?:unknown[]};\n if (!Array.isArray(document.entities)) throw new Error('Invalid JSON entities array');\n raw=[];flatten(document.entities,raw);\n } else if(format==='ndjson') raw=text.split(/\\r?\\n/).filter(line=>line.length>0).map(line=>JSON.parse(line));\n else {\n const rows=parse(text,{columns:true,skip_empty_lines:true,max_record_size:8*1024*1024,bom:true}) as Record<string,string>[];\n const jsonFields=new Set(['parentId','codes','names','level','coordinates','boundary']);\n raw=rows.map(row=>{\n const result:Record<string,unknown>={};\n for(const [key,value] of Object.entries(row)) {\n if (jsonFields.has(key)) { if(value!=='') result[key]=JSON.parse(value); }\n else result[key]=value;\n }\n return result;\n });\n }\n if(raw.length!==manifest.count || raw.length>500000) throw new Error('Package entity count mismatch or import limit exceeded');\n const entities:Entity[]=[];const seen=new Set<string>();const counts={country:0,subdivision:0,locality:0};const required=new Set<string>();\n for(const row of raw) {\n validateEntity(row);\n if(seen.has(row.id)) throw new Error(`Duplicate entity ID: ${row.id}`);\n if((row.coordinates!==undefined&&!manifest.request.coordinates)||(row.boundary!==undefined&&!manifest.request.boundaries)) throw new Error('Unexpected field outside manifest projection');\n seen.add(row.id);entities.push(row);counts[row.entityType]++;\n if(row.parentId) required.add(row.parentId);required.add(row.countryId);\n }\n for(const key of ['country','subdivision','locality'] as const) if(counts[key]!==manifest.counts[key]) throw new Error('Manifest counts do not match data');\n const external=[...required].filter(id=>!seen.has(id)).sort();\n if(JSON.stringify(external)!==JSON.stringify([...manifest.externalReferences].sort())) throw new Error('Unresolved references differ from manifest');\n validateGraph(entities);\n const identity=createHash('sha256').update(canonicalJson({metadata:{contractVersion:manifest.contractVersion,datasetVersion:manifest.datasetVersion,sources:manifest.sources},request:manifest.request}));\n for(const entity of [...entities].sort((a,b)=>Buffer.compare(Buffer.from(a.id),Buffer.from(b.id)))) identity.update('\\n'+canonicalJson(entity));\n if(identity.digest('hex')!==manifest.contentId) throw new Error('Package contentId identity does not match metadata and data');\n return {manifest,entities};\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { QueryClient, SeedInput, SeedOptions, SeedReport } from './types.ts';
2
+ export type { QueryClient, SeedInput, SeedMapping, SeedOptions, SeedReport } from './types.ts';
3
+ /** Verify a package before importing. The client must be one dedicated PostgreSQL session. */
4
+ export declare function seedPostgres(client: QueryClient, packageDirectory: string, options?: SeedOptions): Promise<SeedReport>;
5
+ /** Low-level entry point for already verified canonical packages. No deletion mode is supported. */
6
+ export declare function seedEntities(client: QueryClient, input: SeedInput, options?: SeedOptions): Promise<SeedReport>;
@@ -0,0 +1,80 @@
1
+ import { hash, resolveMapping } from "./mapping.js";
2
+ import { existingRows, localIds, summarize, targetExists, validateInput, validateReferences } from "./preflight.js";
3
+ import { assertCanStart, beginImport, checkpoint, revisionFor, setup } from "./ledger.js";
4
+ import { writeEntity } from "./write.js";
5
+ import { validateRetainedChildren } from "./retained.js";
6
+ /** Verify a package before importing. The client must be one dedicated PostgreSQL session. */
7
+ export async function seedPostgres(client, packageDirectory, options = {}) {
8
+ const { readPackage } = await import("../package.js");
9
+ return seedEntities(client, await readPackage(packageDirectory), options);
10
+ }
11
+ /** Low-level entry point for already verified canonical packages. No deletion mode is supported. */
12
+ export async function seedEntities(client, input, options = {}) {
13
+ options.signal?.throwIfAborted();
14
+ const entities = validateInput(input, options);
15
+ const mapping = resolveMapping(options.mapping);
16
+ const dryRun = options.dryRun ?? true;
17
+ let locked = false;
18
+ try {
19
+ if (!dryRun) {
20
+ const acquired = (await client.query('SELECT pg_try_advisory_lock(hashtext($1)) AS acquired', [`georeference:${mapping.targetKey}`])).rows[0]?.acquired;
21
+ if (acquired !== true)
22
+ throw new Error('Another import holds the target lock; use a dedicated client connection');
23
+ locked = true;
24
+ }
25
+ const exists = await targetExists(client, mapping);
26
+ if (!exists && (!options.createSchema || mapping.localIdColumn))
27
+ throw new Error('Target table is missing; use createSchema for standard tables or create a compatible mapped table first');
28
+ const existing = await existingRows(client, mapping, entities, input, exists);
29
+ validateReferences(entities, existing, options);
30
+ await validateRetainedChildren(client, mapping, entities, existing, options);
31
+ const batchSize = options.batchSize ?? 500;
32
+ const importKey = hash({ contentId: input.manifest.contentId, mappingId: mapping.mappingId, batchSize, mode: options.mode ?? 'upsert' });
33
+ await assertCanStart(client, mapping, importKey);
34
+ const revision = await revisionFor(client, mapping, input, options);
35
+ const report = summarize(input, entities, existing, mapping, { ...options, revision });
36
+ if (dryRun)
37
+ return report;
38
+ await setup(client, mapping, exists);
39
+ const state = await beginImport(client, mapping, input, importKey, revision);
40
+ if (state.offset > entities.length || state.offset % batchSize !== 0 && state.offset !== entities.length)
41
+ throw new Error('Invalid import checkpoint');
42
+ report.resumedBatches = Math.ceil(state.offset / batchSize);
43
+ const ids = localIds(existing);
44
+ if (entities.length === 0) {
45
+ await client.query('BEGIN');
46
+ try {
47
+ await checkpoint(client, mapping, state, 0, true);
48
+ await client.query('COMMIT');
49
+ }
50
+ catch (error) {
51
+ await client.query('ROLLBACK');
52
+ throw error;
53
+ }
54
+ }
55
+ for (let offset = state.offset; offset < entities.length; offset += batchSize) {
56
+ options.signal?.throwIfAborted();
57
+ const end = Math.min(offset + batchSize, entities.length);
58
+ await client.query('BEGIN');
59
+ try {
60
+ for (const entity of entities.slice(offset, end)) {
61
+ options.signal?.throwIfAborted();
62
+ await writeEntity(client, mapping, entity, input, options, existing, ids);
63
+ }
64
+ await checkpoint(client, mapping, state, end, end === entities.length);
65
+ await client.query('COMMIT');
66
+ report.completedBatches++;
67
+ }
68
+ catch (error) {
69
+ await client.query('ROLLBACK');
70
+ throw error;
71
+ }
72
+ }
73
+ return report;
74
+ }
75
+ finally {
76
+ if (locked)
77
+ await client.query('SELECT pg_advisory_unlock(hashtext($1))', [`georeference:${mapping.targetKey}`]);
78
+ }
79
+ }
80
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/seeding/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { QueryClient, SeedInput, SeedOptions, SeedReport } from './types.ts';\nimport { hash, resolveMapping } from './mapping.ts';\nimport { existingRows, localIds, summarize, targetExists, validateInput, validateReferences } from './preflight.ts';\nimport { assertCanStart, beginImport, checkpoint, revisionFor, setup } from './ledger.ts';\nimport { writeEntity } from './write.ts';\nimport { validateRetainedChildren } from './retained.ts';\n\nexport type { QueryClient, SeedInput, SeedMapping, SeedOptions, SeedReport } from './types.ts';\n\n/** Verify a package before importing. The client must be one dedicated PostgreSQL session. */\nexport async function seedPostgres(client: QueryClient, packageDirectory: string, options: SeedOptions = {}): Promise<SeedReport> {\n const { readPackage } = await import('../package.ts');\n return seedEntities(client, await readPackage(packageDirectory), options);\n}\n\n/** Low-level entry point for already verified canonical packages. No deletion mode is supported. */\nexport async function seedEntities(client: QueryClient, input: SeedInput, options: SeedOptions = {}): Promise<SeedReport> {\n options.signal?.throwIfAborted();\n const entities = validateInput(input, options);\n const mapping = resolveMapping(options.mapping);\n const dryRun = options.dryRun ?? true;\n let locked = false;\n try {\n if (!dryRun) {\n const acquired = (await client.query('SELECT pg_try_advisory_lock(hashtext($1)) AS acquired', [`georeference:${mapping.targetKey}`])).rows[0]?.acquired;\n if (acquired !== true) throw new Error('Another import holds the target lock; use a dedicated client connection');\n locked = true;\n }\n const exists = await targetExists(client, mapping);\n if (!exists && (!options.createSchema || mapping.localIdColumn)) throw new Error('Target table is missing; use createSchema for standard tables or create a compatible mapped table first');\n const existing = await existingRows(client, mapping, entities, input, exists);\n validateReferences(entities, existing, options);\n await validateRetainedChildren(client, mapping, entities, existing, options);\n const batchSize = options.batchSize ?? 500;\n const importKey = hash({ contentId: input.manifest.contentId, mappingId: mapping.mappingId, batchSize, mode: options.mode ?? 'upsert' });\n await assertCanStart(client, mapping, importKey);\n const revision = await revisionFor(client, mapping, input, options);\n const report = summarize(input, entities, existing, mapping, { ...options, revision });\n if (dryRun) return report;\n await setup(client, mapping, exists);\n const state = await beginImport(client, mapping, input, importKey, revision);\n if (state.offset > entities.length || state.offset % batchSize !== 0 && state.offset !== entities.length) throw new Error('Invalid import checkpoint');\n report.resumedBatches = Math.ceil(state.offset / batchSize);\n const ids = localIds(existing);\n if (entities.length === 0) {\n await client.query('BEGIN');\n try { await checkpoint(client, mapping, state, 0, true); await client.query('COMMIT'); }\n catch (error) { await client.query('ROLLBACK'); throw error; }\n }\n for (let offset = state.offset; offset < entities.length; offset += batchSize) {\n options.signal?.throwIfAborted();\n const end = Math.min(offset + batchSize, entities.length);\n await client.query('BEGIN');\n try {\n for (const entity of entities.slice(offset, end)) {\n options.signal?.throwIfAborted();\n await writeEntity(client, mapping, entity, input, options, existing, ids);\n }\n await checkpoint(client, mapping, state, end, end === entities.length);\n await client.query('COMMIT');\n report.completedBatches++;\n } catch (error) { await client.query('ROLLBACK'); throw error; }\n }\n return report;\n } finally {\n if (locked) await client.query('SELECT pg_advisory_unlock(hashtext($1))', [`georeference:${mapping.targetKey}`]);\n }\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import type { QueryClient, SeedInput, SeedOptions } from './types.ts';
2
+ import type { ResolvedMapping } from './mapping.ts';
3
+ export interface ImportState {
4
+ importKey: string;
5
+ revision: number;
6
+ offset: number;
7
+ }
8
+ export declare function assertCanStart(client: QueryClient, mapping: ResolvedMapping, importKey: string): Promise<void>;
9
+ export declare function revisionFor(client: QueryClient, mapping: ResolvedMapping, input: SeedInput, options: SeedOptions): Promise<number>;
10
+ export declare function setup(client: QueryClient, mapping: ResolvedMapping, targetExists: boolean): Promise<void>;
11
+ export declare function beginImport(client: QueryClient, mapping: ResolvedMapping, input: SeedInput, importKey: string, revision: number): Promise<ImportState>;
12
+ export declare function checkpoint(client: QueryClient, mapping: ResolvedMapping, state: ImportState, offset: number, complete: boolean): Promise<void>;