@profullstack/nichedb 0.31.0 → 0.32.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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +66 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profullstack/nichedb",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "description": "CLI and MCP bridge for NicheDB: browse collections, manage sources and feeds, search items, from any deployment",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -15,7 +15,7 @@ import { homedir, tmpdir } from 'node:os';
15
15
  import { join } from 'node:path';
16
16
  import { createInterface } from 'node:readline';
17
17
 
18
- export const VERSION = '0.31.0';
18
+ export const VERSION = '0.32.0';
19
19
  const GEO_FLAGS = ['lat', 'long', 'radius', 'unit', 'bbox', 'sort', 'offset'];
20
20
  const DEFAULT_API = process.env.NICHEDB_API ?? 'https://nichedb.dev';
21
21
  const CONFIG_DIR = join(process.env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'nichedb');
@@ -182,6 +182,13 @@ export const COMMANDS = [
182
182
  summary: 'Print the RSS URL (or the feed itself with --fetch).',
183
183
  options: ['--fetch'],
184
184
  },
185
+ {
186
+ name: 'population',
187
+ usage: 'population [<country> [<state> [<city>]]] | zip <zip> | find <name>',
188
+ summary:
189
+ 'Population from the world to the ZIP code: an area, its measures and its largest children.',
190
+ options: ['--level country|state|city|zip', '--limit', '--offset', '--json'],
191
+ },
185
192
  {
186
193
  name: 'profiles',
187
194
  usage: 'profiles [<query>] [--since <iso>] [--mine]',
@@ -740,6 +747,64 @@ export async function run(
740
747
  out(await res.text());
741
748
  return 0;
742
749
  }
750
+ case 'population': {
751
+ const qs = new URLSearchParams();
752
+ if (flags.level) qs.set('level', flags.level);
753
+ if (flags.limit) qs.set('limit', flags.limit);
754
+ if (flags.offset) qs.set('offset', flags.offset);
755
+ const people = (n) =>
756
+ n === null || n === undefined ? '—' : Math.round(n).toLocaleString('en-US');
757
+ const [sub, ...args] = rest;
758
+
759
+ if (sub === 'find') {
760
+ const name = args.join(' ');
761
+ if (!name) throw new Error('population find <name>');
762
+ const { areas } = await client.get(
763
+ `/api/v1/population/search?q=${encodeURIComponent(name)}`,
764
+ );
765
+ if (json) {
766
+ out(JSON.stringify(areas, null, 2));
767
+ return 0;
768
+ }
769
+ for (const a of areas)
770
+ out(`${pad(a.key, 34)} ${pad(a.level, 8)} ${pad(people(a.population), 14)} ${a.title}`);
771
+ if (areas.length === 0) out('(no place by that name)');
772
+ return 0;
773
+ }
774
+
775
+ let path = '/api/v1/population';
776
+ if (sub === 'zip') {
777
+ if (!args[0]) throw new Error('population zip <zip>');
778
+ path = `/api/v1/population/zip/${encodeURIComponent(String(args[0]).trim())}`;
779
+ } else if (sub) {
780
+ path = `/api/v1/population/${encodeURIComponent(rest.join('-').toLowerCase())}`;
781
+ }
782
+ const body = await client.get(`${path}${qs.size ? `?${qs}` : ''}`);
783
+ if (json) {
784
+ out(JSON.stringify(body, null, 2));
785
+ return 0;
786
+ }
787
+ if (body.area) {
788
+ const a = body.area;
789
+ out(`${a.title} (${a.key}, ${a.level})`);
790
+ out(
791
+ ` ${pad('population', 24)} ${people(a.population)}${a.year ? ` (${a.survey ?? a.year})` : ''}`,
792
+ );
793
+ for (const [k, v] of Object.entries(a.measures ?? {})) {
794
+ if (typeof v === 'number') out(` ${pad(k, 24)} ${v.toLocaleString('en-US')}`);
795
+ }
796
+ out(` ${a.page}`);
797
+ }
798
+ const kids = body.children?.areas ?? body.countries ?? [];
799
+ const total = body.children?.total ?? body.total ?? kids.length;
800
+ if (kids.length) {
801
+ out(
802
+ `\n${body.children?.level ?? 'country'}: ${kids.length} of ${Number(total).toLocaleString('en-US')}`,
803
+ );
804
+ for (const k of kids) out(` ${pad(k.key, 34)} ${pad(people(k.population), 14)} ${k.name}`);
805
+ }
806
+ return 0;
807
+ }
743
808
  case 'profiles': {
744
809
  const term = rest.join(' ');
745
810
  const qs = new URLSearchParams();