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
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog / Historique
2
+
3
+ ## 1.0.0
4
+
5
+ ### English
6
+
7
+ CLI-first generation: the default command generates country files directly in a target project, with automatic GeoNames download, offline source input, geographic filters and existing-destination protection. No project dependency installation is required; the snapshot SDK remains optional.
8
+
9
+ Initial V1: validated geographic snapshots; country/subdivision/type selection; JSON, NDJSON, CSV, PostgreSQL SQL, SQLite, and GeoJSON adapters; manifest verification and compression; PostgreSQL seeding; local read SDK; offline GeoNames ingestion; MIT software and bilingual public documentation.
10
+
11
+ This entry describes the V1 codebase. A package version in source is not evidence of an npm publication or hosted deployment. Geographic source coverage and licensing are separate from software versioning.
12
+
13
+ ### Français
14
+
15
+ Génération par commande : le parcours par défaut génère les fichiers de pays directement dans le projet cible, avec téléchargement GeoNames, entrée hors ligne, filtres géographiques et protection des destinations existantes. Aucune dépendance à installer dans le projet ; le SDK d'instantanés reste optionnel.
16
+
17
+ Première V1 : instantanés géographiques validés ; sélection par pays/subdivision/type ; adaptateurs JSON, NDJSON, CSV, SQL PostgreSQL, SQLite et GeoJSON ; manifestes vérifiables et compression ; amorçage PostgreSQL ; SDK de lecture local ; ingestion GeoNames hors ligne ; logiciel MIT et documentation publique bilingue.
18
+
19
+ Cette entrée décrit le code de la V1. Un numéro de version dans le dépôt ne prouve ni une publication npm ni un déploiement hébergé. Couverture et licences géographiques restent distinctes de la version logicielle.
@@ -0,0 +1,44 @@
1
+ # Contribuer à GeoReference
2
+
3
+ [English](CONTRIBUTING.md) · [Accueil](README.fr.md)
4
+
5
+ Les contributions doivent faciliter l'usage des données géographiques tout en préservant les identifiants, la provenance et les imports reproductibles. Pour un changement important, une nouvelle source ou un format, commencez par une issue afin de préciser le besoin et la cible de compatibilité.
6
+
7
+ ## Installation locale
8
+
9
+ Utilisez Node.js 24.18.x et npm :
10
+
11
+ ```sh
12
+ npm ci
13
+ npm run check
14
+ ```
15
+
16
+ Cette vérification lance la validation TypeScript, les tests de comportement et la compilation. Les tests emploient des données fictives et des répertoires temporaires. Un test d'intégration PostgreSQL utilise `DATABASE_URL` lorsqu'elle est définie ; un test sauté ne prouve pas le fonctionnement du transport PostgreSQL. Ne pointez jamais des fixtures de tests destructives vers une base de production.
17
+
18
+ ## Branches et pull requests
19
+
20
+ - Créez une branche de tâche ciblée depuis `dev`, par exemple `feature/country-aliases`, `fix/csv-empty-values` ou `docs/import-guide`.
21
+ - Les pull requests de travail ciblent `dev`. Les pull requests de livraison vont du `dev` de ce dépôt vers `main`.
22
+ - Faites des commits cohérents et vérifiés selon Conventional Commits, par exemple `fix(csv): preserve absent optional fields`. Réunissez le comportement et ses tests ; ne commitez pas une étape volontairement rouge.
23
+ - Expliquez le problème observable, le nouveau comportement, l'impact de compatibilité et les vérifications réellement exécutées. Liez l'issue correspondante si elle existe.
24
+ - Les vérifications requises doivent passer sur la tête exacte de la pull request. Ne forcez pas les pushes, ne supprimez pas les branches protégées et ne contournez pas les revues/vérifications.
25
+
26
+ La protection repose sur les réglages du dépôt ; un fichier de workflow seul n'est pas une protection. Sa disponibilité et ses paramètres effectifs doivent être vérifiés lors de la configuration.
27
+
28
+ ## Tests avant les changements de comportement
29
+
30
+ Écrivez un test ciblé depuis le comportement attendu ou le bug reproduit, exécutez-le et observez un échec significatif, puis implémentez et refactorisez avec les tests verts. Par exemple, un import doit démontrer qu'une seconde exécution ne modifie pas les lignes inchangées, plutôt que vérifier seulement l'appel d'un helper.
31
+
32
+ Les résultats attendus sont indépendants de l'implémentation. N'adaptez pas les attentes à un code erroné. Un échec d'import de module ne prouve que l'absence de structure ; les assertions doivent aussi distinguer un résultat concret incorrect. Documentation et fichiers statiques utilisent des vérifications directes plutôt que des tests artificiels.
33
+
34
+ Pour un adaptateur, couvrez IDs stables, noms non ASCII, distinction absence/null, options refusées, lisibilité de la sortie, annulation et pertes éventuelles. Les changements en base demandent une validation sémantique sur un moteur réel et un parcours d'intégration PostgreSQL. Toute dépendance d'exécution doit avoir une utilité concrète et une licence/maintenance vérifiées.
35
+
36
+ ## Documentation publique et exemples
37
+
38
+ Actualisez les guides anglais/français correspondants dans la même pull request. Code, champs d'API, options CLI et titres de commits restent anglais. Gardez les exemples exécutables et indiquez clairement les données fictives. N'annoncez pas un format ou une version de moteur non vérifié.
39
+
40
+ Séparez les licences du logiciel et des données. Enregistrez licences, attributions et révisions sources. Ne commitez ni identifiants de connexion, téléchargements de données, exports de production, bases locales, builds générés, ni données personnelles. Les plans, prompts, rapports et transmissions privés d'agents n'appartiennent pas à ce dépôt.
41
+
42
+ ## Avant de demander une revue
43
+
44
+ Examinez le diff indexé complet, lancez les vérifications adaptées et retirez les modifications sans lien. Signalez honnêtement les limites et vérifications sautées ou impossibles. Pour une vulnérabilité, suivez [SECURITY.fr.md](SECURITY.fr.md) plutôt que publier les détails d'exploitation dans une issue publique.
@@ -0,0 +1,44 @@
1
+ # Contributing to GeoReference
2
+
3
+ [Français](CONTRIBUTING.fr.md) · [Home](README.md)
4
+
5
+ Contributions should make geographic data easier to use without weakening identifiers, provenance, or repeatable imports. Start with an issue for substantial behavior changes, new sources, or new formats so the use case and compatibility target are clear.
6
+
7
+ ## Local setup
8
+
9
+ Use Node.js 24.18.x and npm:
10
+
11
+ ```sh
12
+ npm ci
13
+ npm run check
14
+ ```
15
+
16
+ The check runs TypeScript validation, behavior tests, and the build. Tests use synthetic data and temporary directories. A PostgreSQL integration test uses `DATABASE_URL` when provided; a skipped integration test is not evidence that PostgreSQL transport was verified. Never point destructive test fixtures at a production database.
17
+
18
+ ## Branches and pull requests
19
+
20
+ - Create a focused task branch from `dev`, for example `feature/country-aliases`, `fix/csv-empty-values`, or `docs/import-guide`.
21
+ - Open working pull requests against `dev`. Release pull requests go from this repository's `dev` to `main`.
22
+ - Keep commits coherent and verified, using Conventional Commits such as `fix(csv): preserve absent optional fields`. Include the behavior and its tests together; do not commit an intentionally failing intermediate state.
23
+ - Explain the observable problem, resulting behavior, compatibility impact, and checks actually run. Link the relevant issue when one exists.
24
+ - Required checks must pass on the actual pull-request head. Do not force push protected branches, delete them, or bypass required reviews/checks.
25
+
26
+ Branch protection is enforced by repository settings; a workflow file alone is not protection. Availability and effective settings must be verified when configuring the repository.
27
+
28
+ ## Tests before behavior changes
29
+
30
+ Write a focused test from the expected behavior or reproduced bug, run it and observe a meaningful failure, then implement and refactor with tests green. For example, an import change should demonstrate the second run leaves unchanged records untouched, not merely check that a helper was called.
31
+
32
+ Use independent expected values. Do not adjust expectations to fit a broken implementation. An import/module-missing failure only proves missing scaffolding; behavioral assertions must also distinguish a concrete wrong result. Documentation and static assets use direct checks rather than artificial tests.
33
+
34
+ For format adapters, cover stable IDs, non-ASCII names, absent versus null fields, unsupported options, output readability, cancellation, and any representation loss. Database changes need real engine semantics and a PostgreSQL integration path. Any new runtime dependency needs a concrete purpose and a license/maintenance check.
35
+
36
+ ## Public documentation and examples
37
+
38
+ Update matching English/French guides in the same pull request. Code, API fields, CLI options, and commit subjects remain English. Keep examples executable and mark fictional data clearly. Do not advertise a format or database version that has not been verified.
39
+
40
+ Keep software and data licensing separate. Record source licenses, attribution, and revisions. Do not commit credentials, downloaded datasets, production exports, local databases, generated builds, or personal data. Private agent plans, prompts, reports, and handoffs do not belong in this repository.
41
+
42
+ ## Before requesting review
43
+
44
+ Inspect the full staged diff, run the applicable checks, and remove unrelated changes. State limitations honestly, including checks that were skipped or could not run. For security-sensitive findings, follow [SECURITY.md](SECURITY.md) instead of posting exploit details in a public issue.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Osiris Balonga
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.fr.md ADDED
@@ -0,0 +1,84 @@
1
+ # GeoReference
2
+
3
+ [English](README.md) · [Prise en main](docs/getting-started.fr.md) · [Formats](docs/formats.fr.md) · [Contribuer](CONTRIBUTING.fr.md)
4
+
5
+ Des données géographiques versionnées, des exports portables et un amorçage de base reproductible.
6
+
7
+ GeoReference génère des fichiers de données géographiques directement dans votre projet. Exécutez la commande une fois, conservez les fichiers et utilisez-les sans dépendance GeoReference à l'exécution. Un SDK TypeScript optionnel permet aux applications de consulter des instantanés locaux.
8
+
9
+ ## Ce que propose la V1
10
+
11
+ - Génération des pays en une commande depuis GeoNames, avec entrée hors ligne et filtrage géographique.
12
+ - Instantanés SQLite validés et versionnés depuis du JSON/NDJSON canonique ou des fichiers texte GeoNames locaux.
13
+ - Sélection par pays, descendants d'une subdivision et type d'entité, avec les ancêtres nécessaires inclus par défaut.
14
+ - Inclusion indépendante des coordonnées ponctuelles et des frontières Polygon/MultiPolygon.
15
+ - Exports JSON (plats ou hiérarchiques), NDJSON, CSV, SQL PostgreSQL, SQLite et GeoJSON.
16
+ - Manifestes d'export avec provenance, options, nombres d'entités et sommes de contrôle SHA-256 ; compression gzip et ZIP.
17
+ - Amorçage PostgreSQL avec aperçu par défaut, application explicite, mise à jour des champs gérés, identifiants stables et reprise par lots.
18
+ - SDK local de lecture pour rechercher les pays/subdivisions et parcourir les localités.
19
+
20
+ La commande par défaut télécharge les informations de pays GeoNames ; le dépôt contient le logiciel et un petit exemple **fictif**. Les autres couvertures dépendent des sources fournies. Les données GeoNames sont sous CC BY 4.0 ; la licence MIT du logiciel ne modifie pas celle des jeux de données.
21
+
22
+ ## Essayer en local
23
+
24
+ Utilisez Node.js **24.18.x** et npm. Exécutez l'outil depuis votre projet :
25
+
26
+ ```sh
27
+ npx georeference
28
+ npx georeference countries --country FR,CG --format csv --output ./data/countries
29
+ ```
30
+
31
+ La première commande crée `data/georeference/entities.json` et `manifest.json`. Le JSON contient `{ metadata, entities }`. `--output` désigne un nouveau dossier, pas un nom de fichier. La commande préserve les destinations existantes et ne modifie ni le manifeste du projet, ni le fichier de verrouillage, ni les dépendances. `npx` télécharge/met en cache l'outil hors du projet ; votre application lit directement les fichiers générés. Voir [la génération en une commande](docs/getting-started.fr.md) pour l'usage hors ligne et les six formats.
32
+
33
+ Pour exécuter cette révision depuis les sources :
34
+
35
+ ```sh
36
+ git clone https://github.com/Osiris-Balonga/georeference.git
37
+ cd georeference
38
+ npm ci
39
+ npm run build
40
+ node dist/cli.js
41
+ ```
42
+
43
+ Le répertoire de destination d'un export doit être nouveau. Le [guide complet](docs/getting-started.fr.md) présente aussi l'ingestion GeoNames, l'amorçage PostgreSQL et le SDK.
44
+
45
+ ## SDK optionnel
46
+
47
+ Seules les applications qui souhaitent utiliser l'API de consultation des instantanés ont besoin de `npm install georeference`. La génération de fichiers JSON, CSV ou autres ne nécessite pas d'ajouter cette dépendance. Voir [l'exemple SDK](docs/getting-started.fr.md#sdk-optionnel).
48
+
49
+ ## Trois façons d'utiliser les données
50
+
51
+ | Besoin | Solution |
52
+ |---|---|
53
+ | Télécharger ou échanger un instantané | Exporter des fichiers accompagnés d'un manifeste |
54
+ | Initialiser/mettre à jour une base applicative | Utiliser le seeder PostgreSQL avec une politique de mise à jour examinée |
55
+ | Consulter le référentiel depuis une application | Utiliser le SDK de lecture sur un instantané local |
56
+
57
+ Un export SQL ne suffit pas à constituer un seeder universel. L'import doit aussi gérer les clés existantes, les champs gérés, les conflits, les révisions, les transactions et la reprise. Voir [l'import en base](docs/seeding.fr.md).
58
+
59
+ ## Documentation
60
+
61
+ | Français | English |
62
+ |---|---|
63
+ | [Génération en une commande](docs/generation.fr.md) | [One-shot generation](docs/generation.md) |
64
+ | [Prise en main](docs/getting-started.fr.md) | [Getting started](docs/getting-started.md) |
65
+ | [Formats pris en charge et limites](docs/formats.fr.md) | [Supported formats and limits](docs/formats.md) |
66
+ | [Amorçage PostgreSQL](docs/seeding.fr.md) | [PostgreSQL seeding](docs/seeding.md) |
67
+ | [Sources et licences des données](docs/data-sources.fr.md) | [Data sources and licenses](docs/data-sources.md) |
68
+ | [Architecture et adaptateurs](docs/architecture.fr.md) | [Architecture and adapters](docs/architecture.md) |
69
+ | [Contribuer](CONTRIBUTING.fr.md) | [Contributing](CONTRIBUTING.md) |
70
+
71
+ Les guides publics sont maintenus dans les deux langues. Les identifiants d'API, champs et commandes restent anglais.
72
+
73
+ ## Développement
74
+
75
+ ```sh
76
+ npm ci
77
+ npm run check
78
+ ```
79
+
80
+ Les changements de comportement commencent par les tests. Les branches de tâche ciblent `dev` ; les pull requests de livraison vont de `dev` vers `main`. Voir [CONTRIBUTING.fr.md](CONTRIBUTING.fr.md) pour le workflow et les vérifications PostgreSQL.
81
+
82
+ ## Licence
83
+
84
+ Logiciel et exemples fictifs : [MIT](LICENSE), © 2026 Osiris Balonga. Données GeoNames : [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), avec attribution GeoNames obligatoire. Toute autre source conserve ses conditions. Lire [les licences des données](docs/data-sources.fr.md) avant de redistribuer des exports.
package/README.md CHANGED
@@ -1,3 +1,84 @@
1
- # Temporary Holding Version
1
+ # GeoReference
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [Français](README.fr.md) · [Getting started](docs/getting-started.md) · [Formats](docs/formats.md) · [Contributing](CONTRIBUTING.md)
4
+
5
+ Versioned geographic datasets, portable exports, and repeatable database seeding.
6
+
7
+ GeoReference generates geographic data files directly in your project. Run the CLI once, keep the generated files, and use them without a GeoReference runtime dependency. An optional TypeScript SDK supports applications that want to query local snapshots.
8
+
9
+ ## What V1 provides
10
+
11
+ - One-command country generation from GeoNames, with offline input and geographic filtering.
12
+ - Validated, versioned SQLite snapshots from canonical JSON/NDJSON or local GeoNames text files.
13
+ - Selection by country, subdivision descendants, and entity type, with required ancestors included by default.
14
+ - Independent inclusion of point coordinates and polygon/multipolygon boundaries.
15
+ - JSON (flat or hierarchical), NDJSON, CSV, PostgreSQL SQL, SQLite, and GeoJSON exports.
16
+ - Export manifests with provenance, request options, entity counts, and SHA-256 file checksums; gzip and ZIP packaging.
17
+ - PostgreSQL seeding with a preview by default, explicit apply, managed-field updates, stable identifiers, and resumable batches.
18
+ - A local read SDK with country/subdivision lookups and locality iteration.
19
+
20
+ The default command downloads GeoNames country information; the repository itself ships software and a small **synthetic** example. Other geographic coverage depends on your input sources. GeoNames data is CC BY 4.0; the MIT software license does not change dataset licenses.
21
+
22
+ ## Try it locally
23
+
24
+ Use Node.js **24.18.x** and npm. Run the tool from your project:
25
+
26
+ ```sh
27
+ npx georeference
28
+ npx georeference countries --country FR,CG --format csv --output ./data/countries
29
+ ```
30
+
31
+ The first command creates `data/georeference/entities.json` and `manifest.json`. JSON contains `{ metadata, entities }`. `--output` is a new directory, not a filename. The CLI preserves existing destinations and does not edit your package manifest, lockfile, or dependencies. `npx` downloads/caches the tool outside the project; your application reads the generated files directly. See [one-shot generation](docs/getting-started.md) for offline use and all six formats.
32
+
33
+ To run this revision from source:
34
+
35
+ ```sh
36
+ git clone https://github.com/Osiris-Balonga/georeference.git
37
+ cd georeference
38
+ npm ci
39
+ npm run build
40
+ node dist/cli.js
41
+ ```
42
+
43
+ Export output directories must be new. Start with the [complete guide](docs/getting-started.md) for GeoNames ingestion, PostgreSQL seeding, and SDK usage.
44
+
45
+ ## Optional SDK
46
+
47
+ Only applications that want the snapshot query API need `npm install georeference`. Generating JSON, CSV, or other files does not require adding that dependency. See the [SDK example](docs/getting-started.md#optional-sdk).
48
+
49
+ ## Three ways to use the data
50
+
51
+ | Need | Use |
52
+ |---|---|
53
+ | Download or exchange a snapshot | Export files with a manifest |
54
+ | Initialize/update an application database | Use the PostgreSQL seeder with a reviewed update policy |
55
+ | Query reference data from application code | Use the read SDK over a local snapshot |
56
+
57
+ An SQL export is not a universal seeder. Seeding also deals with existing keys, field ownership, conflicts, revisions, transactions, and recovery. See [database import](docs/seeding.md).
58
+
59
+ ## Documentation
60
+
61
+ | English | Français |
62
+ |---|---|
63
+ | [One-shot generation](docs/generation.md) | [Génération en une commande](docs/generation.fr.md) |
64
+ | [Getting started](docs/getting-started.md) | [Prise en main](docs/getting-started.fr.md) |
65
+ | [Supported formats and limits](docs/formats.md) | [Formats pris en charge et limites](docs/formats.fr.md) |
66
+ | [PostgreSQL seeding](docs/seeding.md) | [Amorçage PostgreSQL](docs/seeding.fr.md) |
67
+ | [Data sources and licenses](docs/data-sources.md) | [Sources et licences des données](docs/data-sources.fr.md) |
68
+ | [Architecture and adapters](docs/architecture.md) | [Architecture et adaptateurs](docs/architecture.fr.md) |
69
+ | [Contributing](CONTRIBUTING.md) | [Contribuer](CONTRIBUTING.fr.md) |
70
+
71
+ Public guides are maintained in both languages. API identifiers, fields, and commands stay English.
72
+
73
+ ## Development
74
+
75
+ ```sh
76
+ npm ci
77
+ npm run check
78
+ ```
79
+
80
+ Behavior changes use tests first. Task branches target `dev`; release pull requests go from `dev` to `main`. See [CONTRIBUTING.md](CONTRIBUTING.md) for the workflow and PostgreSQL integration checks.
81
+
82
+ ## License
83
+
84
+ Software and synthetic examples: [MIT](LICENSE), © 2026 Osiris Balonga. GeoNames data: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), with required GeoNames attribution. Any other source keeps its own terms. Read [data licensing](docs/data-sources.md) before redistributing exports.
package/SECURITY.fr.md ADDED
@@ -0,0 +1,9 @@
1
+ # Signaler une vulnérabilité
2
+
3
+ [English](SECURITY.md)
4
+
5
+ Ne publiez pas d'identifiants de connexion, de jeux de données privés ni de procédure d'exploitation dans une issue publique. Si le signalement privé GitHub est activé pour ce dépôt, utilisez **Security → Report a vulnerability**. S'il ne l'est pas, ouvrez une issue demandant au mainteneur d'activer un canal privé, sans inclure les détails d'exploitation.
6
+
7
+ Dans le rapport privé, indiquez la révision concernée, une reproduction fictive minimale, le résultat attendu/observé et l'impact constaté. Aucun délai de réponse ni période de support n'est promis ici. Les bugs ordinaires sans impact sensible passent par les issues habituelles.
8
+
9
+ Traitez données téléchargées et paquets comme non fiables. Conservez manifestes et licences, utilisez des permissions locales adaptées et ne lancez jamais des tests d'import sur une base de production.
package/SECURITY.md ADDED
@@ -0,0 +1,9 @@
1
+ # Reporting security issues
2
+
3
+ [Français](SECURITY.fr.md)
4
+
5
+ Please do not publish credentials, private datasets, or exploit instructions in a public issue. If GitHub private vulnerability reporting is enabled for this repository, use the repository's **Security → Report a vulnerability** entry. If it is unavailable, open a public issue asking the maintainer to enable a private reporting channel, without including exploit details.
6
+
7
+ For a private report, include the affected revision, a minimal synthetic reproduction, expected/actual behavior, and the impact you observed. No response-time commitment or support window is promised here. General bugs without sensitive impact belong in the normal issue tracker.
8
+
9
+ Treat downloaded data and packages as untrusted. Preserve manifests and source licenses, use appropriate local file permissions, and never run import tests against production databases.
@@ -0,0 +1,5 @@
1
+ import type { AdapterContext, Entity, ExportRequest } from '../types.ts';
2
+ export declare function validateOptions(request: ExportRequest, hierarchical?: boolean): void;
3
+ export declare function project(entity: Entity, request: ExportRequest): Entity;
4
+ export declare function writeChunks(context: AdapterContext, filename: string, chunks: Iterable<string> | AsyncIterable<string>): Promise<void>;
5
+ export declare function jsonArray(values: Iterable<unknown>): Generator<string>;
@@ -0,0 +1,37 @@
1
+ import { createWriteStream } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { Readable } from 'node:stream';
4
+ import { pipeline } from 'node:stream/promises';
5
+ export function validateOptions(request, hierarchical = false) {
6
+ if (request.layout === 'hierarchical' && !hierarchical)
7
+ throw new Error(`${request.format} does not support hierarchical layout`);
8
+ if (request.format !== 'sql-postgres' && (request.schemaMode !== undefined || request.sqlMode !== undefined)) {
9
+ throw new Error('SQL options are only supported by sql-postgres');
10
+ }
11
+ if (request.format !== 'csv' && request.spreadsheetSafe !== undefined)
12
+ throw new Error('spreadsheetSafe is only supported by CSV');
13
+ }
14
+ export function project(entity, request) {
15
+ const result = { ...entity };
16
+ if (!request.coordinates)
17
+ delete result.coordinates;
18
+ if (!request.boundaries)
19
+ delete result.boundary;
20
+ return result;
21
+ }
22
+ export async function writeChunks(context, filename, chunks) {
23
+ context.signal?.throwIfAborted();
24
+ await pipeline(Readable.from(chunks), createWriteStream(join(context.directory, filename), { flags: 'wx' }), context.signal ? { signal: context.signal } : {});
25
+ }
26
+ export function* jsonArray(values) {
27
+ yield '[';
28
+ let first = true;
29
+ for (const value of values) {
30
+ if (!first)
31
+ yield ',';
32
+ yield JSON.stringify(value);
33
+ first = false;
34
+ }
35
+ yield ']';
36
+ }
37
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/adapters/common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGhD,MAAM,UAAU,eAAe,CAAC,OAAsB,EAAE,YAAY,GAAG,KAAK;IAC1E,IAAI,OAAO,CAAC,MAAM,KAAK,cAAc,IAAI,CAAC,YAAY;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,uCAAuC,CAAC,CAAC;IAClI,IAAI,OAAO,CAAC,MAAM,KAAK,cAAc,IAAI,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,EAAE,CAAC;QAC7G,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,eAAe,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;AACrI,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,MAAc,EAAE,OAAsB;IAC5D,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO,CAAC,WAAW;QAAE,OAAO,MAAM,CAAC,WAAW,CAAC;IACpD,IAAI,CAAC,OAAO,CAAC,UAAU;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC;IAChD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAuB,EAAE,QAAgB,EAAE,MAAgD;IAC3H,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;IACjC,MAAM,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACjK,CAAC;AAED,MAAM,SAAS,CAAC,CAAC,SAAS,CAAC,MAAyB;IAClD,MAAM,GAAG,CAAC;IACV,IAAI,KAAK,GAAG,IAAI,CAAC;IACjB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,KAAK;YAAE,MAAM,GAAG,CAAC;QACtB,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC5B,KAAK,GAAG,KAAK,CAAC;IAChB,CAAC;IACD,MAAM,GAAG,CAAC;AACZ,CAAC","sourcesContent":["import { createWriteStream } from 'node:fs';\nimport { join } from 'node:path';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport type { AdapterContext, Entity, ExportRequest } from '../types.ts';\n\nexport function validateOptions(request: ExportRequest, hierarchical = false): void {\n if (request.layout === 'hierarchical' && !hierarchical) throw new Error(`${request.format} does not support hierarchical layout`);\n if (request.format !== 'sql-postgres' && (request.schemaMode !== undefined || request.sqlMode !== undefined)) {\n throw new Error('SQL options are only supported by sql-postgres');\n }\n if (request.format !== 'csv' && request.spreadsheetSafe !== undefined) throw new Error('spreadsheetSafe is only supported by CSV');\n}\n\nexport function project(entity: Entity, request: ExportRequest): Entity {\n const result = { ...entity };\n if (!request.coordinates) delete result.coordinates;\n if (!request.boundaries) delete result.boundary;\n return result;\n}\n\nexport async function writeChunks(context: AdapterContext, filename: string, chunks: Iterable<string> | AsyncIterable<string>): Promise<void> {\n context.signal?.throwIfAborted();\n await pipeline(Readable.from(chunks), createWriteStream(join(context.directory, filename), { flags: 'wx' }), context.signal ? { signal: context.signal } : {});\n}\n\nexport function* jsonArray(values: Iterable<unknown>): Generator<string> {\n yield '[';\n let first = true;\n for (const value of values) {\n if (!first) yield ',';\n yield JSON.stringify(value);\n first = false;\n }\n yield ']';\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { ExportAdapter } from '../types.ts';
2
+ export declare const csvAdapter: ExportAdapter;
@@ -0,0 +1,32 @@
1
+ import { stringify } from 'csv-stringify/sync';
2
+ import { project, validateOptions, writeChunks } from "./common.js";
3
+ function spreadsheetCell(value, enabled) {
4
+ return enabled && /^[\s]*[=+\-@\t\r\n]/u.test(value) ? `'${value}` : value;
5
+ }
6
+ export const csvAdapter = {
7
+ format: 'csv', version: '1.0.0',
8
+ validate: validateOptions,
9
+ async write(context) {
10
+ const { request } = context.plan;
11
+ const columns = ['id', 'entityType', 'name', 'countryId', 'parentId', 'codes', 'names', 'level'];
12
+ if (request.coordinates)
13
+ columns.push('coordinates');
14
+ if (request.boundaries)
15
+ columns.push('boundary');
16
+ await writeChunks(context, 'entities.csv', (function* () {
17
+ yield stringify([columns], { record_delimiter: '\r\n' });
18
+ for (const original of context.plan.entities()) {
19
+ const entity = project(original, request);
20
+ const cells = {
21
+ id: spreadsheetCell(entity.id, request.spreadsheetSafe), entityType: entity.entityType,
22
+ name: spreadsheetCell(entity.name, request.spreadsheetSafe), countryId: spreadsheetCell(entity.countryId, request.spreadsheetSafe),
23
+ parentId: JSON.stringify(entity.parentId), codes: JSON.stringify(entity.codes) ?? '', names: JSON.stringify(entity.names) ?? '',
24
+ level: JSON.stringify(entity.level) ?? '', coordinates: JSON.stringify(entity.coordinates) ?? '', boundary: JSON.stringify(entity.boundary) ?? '',
25
+ };
26
+ yield stringify([columns.map(column => cells[column])], { record_delimiter: '\r\n' });
27
+ }
28
+ })());
29
+ return ['entities.csv'];
30
+ },
31
+ };
32
+ //# sourceMappingURL=csv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csv.js","sourceRoot":"","sources":["../../src/adapters/csv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEpE,SAAS,eAAe,CAAC,KAAa,EAAE,OAA4B;IAClE,OAAO,OAAO,IAAI,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AAC7E,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAkB;IACvC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO;IAC/B,QAAQ,EAAE,eAAe;IACzB,KAAK,CAAC,KAAK,CAAC,OAAO;QACjB,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;QACjC,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QACjG,IAAI,OAAO,CAAC,WAAW;YAAE,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrD,IAAI,OAAO,CAAC,UAAU;YAAE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACjD,MAAM,WAAW,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC,QAAQ,CAAC;YACnD,MAAM,SAAS,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAAC;YACzD,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;gBAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAC1C,MAAM,KAAK,GAA2B;oBACpC,EAAE,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,eAAe,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU;oBACtF,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,EAAE,SAAS,EAAE,eAAe,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,eAAe,CAAC;oBAClI,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE;oBAC/H,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;iBAClJ,CAAC;gBACF,MAAM,SAAS,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC,CAAC,EAAE,EAAE,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAAC;YACzF,CAAC;QACH,CAAC,CAAC,EAAE,CAAC,CAAC;QACN,OAAO,CAAC,cAAc,CAAC,CAAC;IAC1B,CAAC;CACF,CAAC","sourcesContent":["import { stringify } from 'csv-stringify/sync';\nimport type { ExportAdapter } from '../types.ts';\nimport { project, validateOptions, writeChunks } from './common.ts';\n\nfunction spreadsheetCell(value: string, enabled: boolean | undefined): string {\n return enabled && /^[\\s]*[=+\\-@\\t\\r\\n]/u.test(value) ? `'${value}` : value;\n}\n\nexport const csvAdapter: ExportAdapter = {\n format: 'csv', version: '1.0.0',\n validate: validateOptions,\n async write(context) {\n const { request } = context.plan;\n const columns = ['id', 'entityType', 'name', 'countryId', 'parentId', 'codes', 'names', 'level'];\n if (request.coordinates) columns.push('coordinates');\n if (request.boundaries) columns.push('boundary');\n await writeChunks(context, 'entities.csv', (function* () {\n yield stringify([columns], { record_delimiter: '\\r\\n' });\n for (const original of context.plan.entities()) {\n const entity = project(original, request);\n const cells: Record<string, string> = {\n id: spreadsheetCell(entity.id, request.spreadsheetSafe), entityType: entity.entityType,\n name: spreadsheetCell(entity.name, request.spreadsheetSafe), countryId: spreadsheetCell(entity.countryId, request.spreadsheetSafe),\n parentId: JSON.stringify(entity.parentId), codes: JSON.stringify(entity.codes) ?? '', names: JSON.stringify(entity.names) ?? '',\n level: JSON.stringify(entity.level) ?? '', coordinates: JSON.stringify(entity.coordinates) ?? '', boundary: JSON.stringify(entity.boundary) ?? '',\n };\n yield stringify([columns.map(column => cells[column]!)], { record_delimiter: '\\r\\n' });\n }\n })());\n return ['entities.csv'];\n },\n};\n"]}
@@ -0,0 +1,2 @@
1
+ import type { ExportAdapter } from '../types.ts';
2
+ export declare const geojsonAdapter: ExportAdapter;
@@ -0,0 +1,36 @@
1
+ import { jsonArray, project, validateOptions, writeChunks } from "./common.js";
2
+ function* collection(context, layer) {
3
+ yield '{"type":"FeatureCollection","features":';
4
+ yield* jsonArray((function* () {
5
+ for (const original of context.plan.entities()) {
6
+ const entity = project(original, context.plan.request);
7
+ const { coordinates, boundary, ...properties } = entity;
8
+ const geometry = layer === 'points'
9
+ ? coordinates ? { type: 'Point', coordinates: [coordinates.longitude, coordinates.latitude] } : null
10
+ : boundary ?? null;
11
+ yield { type: 'Feature', id: entity.id, properties, geometry };
12
+ }
13
+ })());
14
+ yield '}\n';
15
+ }
16
+ export const geojsonAdapter = {
17
+ format: 'geojson', version: '1.0.0',
18
+ validate(request) {
19
+ validateOptions(request);
20
+ if (!request.coordinates && !request.boundaries)
21
+ throw new Error('GeoJSON requires coordinates and/or boundaries');
22
+ },
23
+ async write(context) {
24
+ const files = [];
25
+ if (context.plan.request.coordinates) {
26
+ await writeChunks(context, 'points.geojson', collection(context, 'points'));
27
+ files.push('points.geojson');
28
+ }
29
+ if (context.plan.request.boundaries) {
30
+ await writeChunks(context, 'boundaries.geojson', collection(context, 'boundaries'));
31
+ files.push('boundaries.geojson');
32
+ }
33
+ return files;
34
+ },
35
+ };
36
+ //# sourceMappingURL=geojson.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geojson.js","sourceRoot":"","sources":["../../src/adapters/geojson.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/E,QAAQ,CAAC,CAAC,UAAU,CAAC,OAAuB,EAAE,KAA8B;IAC1E,MAAM,yCAAyC,CAAC;IAChD,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC;QACzB,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvD,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAE,GAAG,MAAM,CAAC;YACxD,MAAM,QAAQ,GAAG,KAAK,KAAK,QAAQ;gBACjC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;gBACpG,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC;YACrB,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;QACjE,CAAC;IACH,CAAC,CAAC,EAAE,CAAC,CAAC;IACN,MAAM,KAAK,CAAC;AACd,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAkB;IAC3C,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO;IACnC,QAAQ,CAAC,OAAO;QACd,eAAe,CAAC,OAAO,CAAC,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACrH,CAAC;IACD,KAAK,CAAC,KAAK,CAAC,OAAO;QACjB,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YACrC,MAAM,WAAW,CAAC,OAAO,EAAE,gBAAgB,EAAE,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC5E,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC/B,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;YACpC,MAAM,WAAW,CAAC,OAAO,EAAE,oBAAoB,EAAE,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;YACpF,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF,CAAC","sourcesContent":["import type { AdapterContext, ExportAdapter } from '../types.ts';\nimport { jsonArray, project, validateOptions, writeChunks } from './common.ts';\n\nfunction* collection(context: AdapterContext, layer: 'points' | 'boundaries'): Generator<string> {\n yield '{\"type\":\"FeatureCollection\",\"features\":';\n yield* jsonArray((function* () {\n for (const original of context.plan.entities()) {\n const entity = project(original, context.plan.request);\n const { coordinates, boundary, ...properties } = entity;\n const geometry = layer === 'points'\n ? coordinates ? { type: 'Point', coordinates: [coordinates.longitude, coordinates.latitude] } : null\n : boundary ?? null;\n yield { type: 'Feature', id: entity.id, properties, geometry };\n }\n })());\n yield '}\\n';\n}\n\nexport const geojsonAdapter: ExportAdapter = {\n format: 'geojson', version: '1.0.0',\n validate(request) {\n validateOptions(request);\n if (!request.coordinates && !request.boundaries) throw new Error('GeoJSON requires coordinates and/or boundaries');\n },\n async write(context) {\n const files: string[] = [];\n if (context.plan.request.coordinates) {\n await writeChunks(context, 'points.geojson', collection(context, 'points'));\n files.push('points.geojson');\n }\n if (context.plan.request.boundaries) {\n await writeChunks(context, 'boundaries.geojson', collection(context, 'boundaries'));\n files.push('boundaries.geojson');\n }\n return files;\n },\n};\n"]}
@@ -0,0 +1,3 @@
1
+ import type { ExportAdapter } from '../types.ts';
2
+ /** A static registry keeps format dependencies separate from selection and identity rules. */
3
+ export declare function getBuiltinAdapters(): ExportAdapter[];
@@ -0,0 +1,10 @@
1
+ import { csvAdapter } from "./csv.js";
2
+ import { geojsonAdapter } from "./geojson.js";
3
+ import { jsonAdapter, ndjsonAdapter } from "./json.js";
4
+ import { postgresAdapter } from "./postgres.js";
5
+ import { sqliteAdapter } from "./sqlite.js";
6
+ /** A static registry keeps format dependencies separate from selection and identity rules. */
7
+ export function getBuiltinAdapters() {
8
+ return [jsonAdapter, ndjsonAdapter, csvAdapter, postgresAdapter, sqliteAdapter, geojsonAdapter];
9
+ }
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,8FAA8F;AAC9F,MAAM,UAAU,kBAAkB;IAChC,OAAO,CAAC,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,CAAC,CAAC;AAClG,CAAC","sourcesContent":["import type { ExportAdapter } from '../types.ts';\nimport { csvAdapter } from './csv.ts';\nimport { geojsonAdapter } from './geojson.ts';\nimport { jsonAdapter, ndjsonAdapter } from './json.ts';\nimport { postgresAdapter } from './postgres.ts';\nimport { sqliteAdapter } from './sqlite.ts';\n\n/** A static registry keeps format dependencies separate from selection and identity rules. */\nexport function getBuiltinAdapters(): ExportAdapter[] {\n return [jsonAdapter, ndjsonAdapter, csvAdapter, postgresAdapter, sqliteAdapter, geojsonAdapter];\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { ExportAdapter } from '../types.ts';
2
+ export declare const jsonAdapter: ExportAdapter;
3
+ export declare const ndjsonAdapter: ExportAdapter;
@@ -0,0 +1,48 @@
1
+ import { jsonArray, project, validateOptions, writeChunks } from "./common.js";
2
+ function* flatDocument(context) {
3
+ yield `{"metadata":${JSON.stringify(context.plan.metadata)},"entities":`;
4
+ yield* jsonArray((function* () { for (const entity of context.plan.entities())
5
+ yield project(entity, context.plan.request); })());
6
+ yield '}\n';
7
+ }
8
+ function hierarchy(context) {
9
+ if (context.plan.count > 50_000)
10
+ throw new Error('Hierarchical JSON is limited to 50,000 entities; use flat JSON or partitions');
11
+ const nodes = new Map();
12
+ for (const entity of context.plan.entities())
13
+ nodes.set(entity.id, { ...project(entity, context.plan.request), children: [] });
14
+ const roots = [];
15
+ for (const node of nodes.values()) {
16
+ const parent = node.parentId === null ? undefined : nodes.get(node.parentId);
17
+ if (parent)
18
+ parent.children.push(node);
19
+ else
20
+ roots.push(node);
21
+ }
22
+ return roots;
23
+ }
24
+ export const jsonAdapter = {
25
+ format: 'json', version: '1.0.0',
26
+ validate(request) { validateOptions(request, true); },
27
+ async write(context) {
28
+ if (context.plan.request.layout === 'hierarchical') {
29
+ const entities = hierarchy(context);
30
+ await writeChunks(context, 'entities.json', [JSON.stringify({ metadata: context.plan.metadata, entities }) + '\n']);
31
+ }
32
+ else
33
+ await writeChunks(context, 'entities.json', flatDocument(context));
34
+ return ['entities.json'];
35
+ },
36
+ };
37
+ export const ndjsonAdapter = {
38
+ format: 'ndjson', version: '1.0.0',
39
+ validate: validateOptions,
40
+ async write(context) {
41
+ await writeChunks(context, 'entities.ndjson', (function* () {
42
+ for (const entity of context.plan.entities())
43
+ yield JSON.stringify(project(entity, context.plan.request)) + '\n';
44
+ })());
45
+ return ['entities.ndjson'];
46
+ },
47
+ };
48
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/adapters/json.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/E,QAAQ,CAAC,CAAC,YAAY,CAAC,OAAuB;IAC5C,MAAM,eAAe,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC;IACzE,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE;QAAE,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAClI,MAAM,KAAK,CAAC;AACd,CAAC;AAED,SAAS,SAAS,CAAC,OAAuB;IACxC,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAC;IACjI,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC5C,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE;QAAE,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/H,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7E,IAAI,MAAM;YAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;YAClC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAkB;IACxC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAChC,QAAQ,CAAC,OAAO,IAAI,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACrD,KAAK,CAAC,KAAK,CAAC,OAAO;QACjB,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;YACpC,MAAM,WAAW,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QACtH,CAAC;;YAAM,MAAM,WAAW,CAAC,OAAO,EAAE,eAAe,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;QAC1E,OAAO,CAAC,eAAe,CAAC,CAAC;IAC3B,CAAC;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAkB;IAC1C,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO;IAClC,QAAQ,EAAE,eAAe;IACzB,KAAK,CAAC,KAAK,CAAC,OAAO;QACjB,MAAM,WAAW,CAAC,OAAO,EAAE,iBAAiB,EAAE,CAAC,QAAQ,CAAC;YACtD,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE;gBAAE,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;QACnH,CAAC,CAAC,EAAE,CAAC,CAAC;QACN,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC7B,CAAC;CACF,CAAC","sourcesContent":["import type { AdapterContext, Entity, ExportAdapter } from '../types.ts';\nimport { jsonArray, project, validateOptions, writeChunks } from './common.ts';\n\ninterface TreeEntity extends Entity { children: TreeEntity[] }\n\nfunction* flatDocument(context: AdapterContext): Generator<string> {\n yield `{\"metadata\":${JSON.stringify(context.plan.metadata)},\"entities\":`;\n yield* jsonArray((function* () { for (const entity of context.plan.entities()) yield project(entity, context.plan.request); })());\n yield '}\\n';\n}\n\nfunction hierarchy(context: AdapterContext): TreeEntity[] {\n if (context.plan.count > 50_000) throw new Error('Hierarchical JSON is limited to 50,000 entities; use flat JSON or partitions');\n const nodes = new Map<string, TreeEntity>();\n for (const entity of context.plan.entities()) nodes.set(entity.id, { ...project(entity, context.plan.request), children: [] });\n const roots: TreeEntity[] = [];\n for (const node of nodes.values()) {\n const parent = node.parentId === null ? undefined : nodes.get(node.parentId);\n if (parent) parent.children.push(node);\n else roots.push(node);\n }\n return roots;\n}\n\nexport const jsonAdapter: ExportAdapter = {\n format: 'json', version: '1.0.0',\n validate(request) { validateOptions(request, true); },\n async write(context) {\n if (context.plan.request.layout === 'hierarchical') {\n const entities = hierarchy(context);\n await writeChunks(context, 'entities.json', [JSON.stringify({ metadata: context.plan.metadata, entities }) + '\\n']);\n } else await writeChunks(context, 'entities.json', flatDocument(context));\n return ['entities.json'];\n },\n};\n\nexport const ndjsonAdapter: ExportAdapter = {\n format: 'ndjson', version: '1.0.0',\n validate: validateOptions,\n async write(context) {\n await writeChunks(context, 'entities.ndjson', (function* () {\n for (const entity of context.plan.entities()) yield JSON.stringify(project(entity, context.plan.request)) + '\\n';\n })());\n return ['entities.ndjson'];\n },\n};\n"]}
@@ -0,0 +1,3 @@
1
+ import type { ExportAdapter } from '../types.ts';
2
+ export declare const POSTGRES_SCHEMA = "CREATE TABLE IF NOT EXISTS \"georeference_entities\" (\n \"id\" TEXT PRIMARY KEY,\n \"entity_type\" TEXT NOT NULL,\n \"name\" TEXT NOT NULL,\n \"country_id\" TEXT NOT NULL,\n \"parent_id\" TEXT REFERENCES \"georeference_entities\"(\"id\") DEFERRABLE INITIALLY DEFERRED,\n \"codes\" JSONB NOT NULL DEFAULT '{}',\n \"names\" JSONB NOT NULL DEFAULT '{}',\n \"level\" INTEGER,\n \"coordinates\" JSONB,\n \"boundary\" JSONB\n);\nCREATE INDEX IF NOT EXISTS \"georeference_entities_parent_idx\" ON \"georeference_entities\" (\"parent_id\");\n";
3
+ export declare const postgresAdapter: ExportAdapter;