@nspot/geo-engine 0.1.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.
package/dist/paths.js ADDED
@@ -0,0 +1,7 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ const here = path.dirname(fileURLToPath(import.meta.url));
4
+ /** Package root: works both from src/ (tsx) and dist/ (compiled). */
5
+ export const PACKAGE_ROOT = path.resolve(here, "..");
6
+ /** The migration files shipped with this package (a copy of packages/db/sql). */
7
+ export const SQL_DIR = path.join(PACKAGE_ROOT, "sql");
@@ -0,0 +1,43 @@
1
+ import type { Queryable } from "./migrate.js";
2
+ import type { RegionSummary } from "./types.js";
3
+ export interface RegionFilter {
4
+ /** Region slugs or numeric ids; at least one. */
5
+ regions: string[];
6
+ /** any (default): in at least one; all: in every listed region. */
7
+ match?: "any" | "all";
8
+ exclude?: string[];
9
+ }
10
+ export interface FragmentOptions {
11
+ /**
12
+ * The host's id column as it appears in the statement, e.g. "h.id".
13
+ *
14
+ * **Trust model**: this string is interpolated into the fragment's SQL verbatim — it is
15
+ * raw SQL authored by you, not a value. Never build it from request input (a query
16
+ * parameter, a request body, a URL segment); use a literal or a value chosen from a
17
+ * fixed list in your own code. The same holds for `idType`, which is additionally
18
+ * checked against a plain-identifier pattern.
19
+ */
20
+ column: string;
21
+ /** SQL type of that column; defaults to text. */
22
+ idType?: string;
23
+ /** Number of placeholders already used in the statement; the fragment's start at offset + 1. */
24
+ offset?: number;
25
+ }
26
+ export interface SqlFragment {
27
+ text: string;
28
+ values: unknown[];
29
+ }
30
+ /** `<column> IN (SELECT … geo.ids_in(...))` for rows inside the given regions. */
31
+ export declare function inRegions(source: string, filter: RegionFilter, opts: FragmentOptions): SqlFragment;
32
+ /**
33
+ * `<column> NOT IN (...)`: rows inside none of the regions, including rows in no region at all.
34
+ *
35
+ * SQL `NOT IN` is null-propagating: if `opts.column` can be NULL (a LEFT JOINed id, for
36
+ * example) the predicate is NULL rather than true for that row, so the row does not come
37
+ * back. Add an explicit `OR <column> IS NULL` (in parentheses with the fragment) if you want those rows.
38
+ */
39
+ export declare function notInRegions(source: string, regions: string[], opts: FragmentOptions): SqlFragment;
40
+ /** External ids (as text) of the source's rows matching the filter. */
41
+ export declare function ids(db: Queryable, source: string, filter: RegionFilter): Promise<string[]>;
42
+ export declare function regionsOf(db: Queryable, source: string, externalId: string | number): Promise<RegionSummary[]>;
43
+ export declare function regionsAt(db: Queryable, lng: number, lat: number): Promise<RegionSummary[]>;
package/dist/query.js ADDED
@@ -0,0 +1,52 @@
1
+ import { GeoEngineError, run } from "./errors.js";
2
+ const IDENT = /^[A-Za-z_][A-Za-z0-9_$]*(\[\])?$/;
3
+ function idType(opts) {
4
+ const t = opts.idType ?? "text";
5
+ if (!IDENT.test(t))
6
+ throw new GeoEngineError("invalid_input", `idType must be a plain SQL type name, got "${t}"`);
7
+ return t;
8
+ }
9
+ /** `geo.ids_in` rejects an empty region list; fail before spending a round trip. */
10
+ function requireRegions(regions) {
11
+ if (!regions || regions.length === 0) {
12
+ throw new GeoEngineError("invalid_input", "regions must name at least one region");
13
+ }
14
+ }
15
+ /** `<column> IN (SELECT … geo.ids_in(...))` for rows inside the given regions. */
16
+ export function inRegions(source, filter, opts) {
17
+ requireRegions(filter.regions);
18
+ const o = opts.offset ?? 0;
19
+ return {
20
+ text: `${opts.column} IN (SELECT t.id::${idType(opts)} FROM geo.ids_in($${o + 1}, $${o + 2}::text[], $${o + 3}, $${o + 4}::text[]) AS t(id))`,
21
+ values: [source, filter.regions, filter.match ?? "any", filter.exclude ?? null],
22
+ };
23
+ }
24
+ /**
25
+ * `<column> NOT IN (...)`: rows inside none of the regions, including rows in no region at all.
26
+ *
27
+ * SQL `NOT IN` is null-propagating: if `opts.column` can be NULL (a LEFT JOINed id, for
28
+ * example) the predicate is NULL rather than true for that row, so the row does not come
29
+ * back. Add an explicit `OR <column> IS NULL` (in parentheses with the fragment) if you want those rows.
30
+ */
31
+ export function notInRegions(source, regions, opts) {
32
+ const o = opts.offset ?? 0;
33
+ return {
34
+ text: `${opts.column} NOT IN (SELECT t.id::${idType(opts)} FROM geo.ids_in($${o + 1}, $${o + 2}::text[]) AS t(id))`,
35
+ values: [source, regions],
36
+ };
37
+ }
38
+ /** External ids (as text) of the source's rows matching the filter. */
39
+ export async function ids(db, source, filter) {
40
+ requireRegions(filter.regions);
41
+ const rows = await run(db, "SELECT t.id FROM geo.ids_in($1, $2::text[], $3, $4::text[]) AS t(id)", [
42
+ source, filter.regions, filter.match ?? "any", filter.exclude ?? null,
43
+ ]);
44
+ return rows.map((r) => r.id);
45
+ }
46
+ export async function regionsOf(db, source, externalId) {
47
+ const rows = await run(db, "SELECT geo.regions_of($1, $2::text) AS r", [source, String(externalId)]);
48
+ return rows[0].r;
49
+ }
50
+ export async function regionsAt(db, lng, lat) {
51
+ return run(db, "SELECT id, slug, name, type, level, parent_id, code FROM geo.regions_at_point($1, $2)", [lng, lat]);
52
+ }
@@ -0,0 +1,30 @@
1
+ import type { Queryable } from "./migrate.js";
2
+ export interface Source {
3
+ id: number;
4
+ name: string;
5
+ table_schema: string;
6
+ table_name: string;
7
+ id_column: string;
8
+ id_type: string;
9
+ geom_expr: string;
10
+ registered_at: string;
11
+ }
12
+ export interface RegisterSourceOptions {
13
+ /** Name used in queries, e.g. "hotels". */
14
+ name: string;
15
+ /** "hotels" or "public.hotels". */
16
+ table: string;
17
+ idColumn: string;
18
+ /** A geometry column name, or the lng / lat column names to build a point from. */
19
+ geometry: string | {
20
+ lng: string;
21
+ lat: string;
22
+ };
23
+ }
24
+ /** SQL expression over a row alias `r` that yields the row's geometry in SRID 4326. */
25
+ export declare function geometryExpression(geometry: RegisterSourceOptions["geometry"]): string;
26
+ export declare function registerSource(db: Queryable, opts: RegisterSourceOptions): Promise<Source>;
27
+ export declare function unregisterSource(db: Queryable, name: string): Promise<void>;
28
+ /** Recompute memberships for one source, or for every source when no name is given. */
29
+ export declare function rebuildMemberships(db: Queryable, name?: string): Promise<number>;
30
+ export declare function listSources(db: Queryable): Promise<Source[]>;
@@ -0,0 +1,32 @@
1
+ import { run } from "./errors.js";
2
+ const quoteIdent = (s) => `"${s.replace(/"/g, '""')}"`;
3
+ /** SQL expression over a row alias `r` that yields the row's geometry in SRID 4326. */
4
+ export function geometryExpression(geometry) {
5
+ if (typeof geometry === "string")
6
+ return quoteIdent(geometry);
7
+ return `ST_SetSRID(ST_MakePoint(${quoteIdent(geometry.lng)}, ${quoteIdent(geometry.lat)}), 4326)`;
8
+ }
9
+ export async function registerSource(db, opts) {
10
+ const [schema, table] = opts.table.includes(".") ? opts.table.split(".", 2) : ["public", opts.table];
11
+ const rows = await run(db, "SELECT * FROM geo.register_source($1, $2, $3, $4, $5)", [
12
+ opts.name,
13
+ schema,
14
+ table,
15
+ opts.idColumn,
16
+ geometryExpression(opts.geometry),
17
+ ]);
18
+ return rows[0];
19
+ }
20
+ export async function unregisterSource(db, name) {
21
+ await run(db, "SELECT geo.unregister_source($1)", [name]);
22
+ }
23
+ /** Recompute memberships for one source, or for every source when no name is given. */
24
+ export async function rebuildMemberships(db, name) {
25
+ const rows = name
26
+ ? await run(db, "SELECT geo.rebuild_memberships($1)::text AS n", [name])
27
+ : await run(db, "SELECT geo.rebuild_all_memberships()::text AS n");
28
+ return Number(rows[0].n);
29
+ }
30
+ export async function listSources(db) {
31
+ return run(db, "SELECT * FROM geo.sources ORDER BY id");
32
+ }
@@ -0,0 +1,323 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * admin – official administrative units (județ, department, state…)
4
+ * cultural – ethnographic / historical areas (Mărginimea Sibiului, Țara Oltului)
5
+ * natural – landscape units (Delta Dunării, Carpații Meridionali)
6
+ * custom – anything you define for your own product (a marketing zone, a tour area)
7
+ */
8
+ export declare const REGION_TYPES: readonly ["admin", "cultural", "natural", "custom"];
9
+ export type RegionType = (typeof REGION_TYPES)[number];
10
+ export declare const regionTypeSchema: z.ZodEnum<["admin", "cultural", "natural", "custom"]>;
11
+ export type Position = [number, number] | [number, number, number];
12
+ export type PointGeometry = {
13
+ type: "Point";
14
+ coordinates: Position;
15
+ };
16
+ export type PolygonGeometry = {
17
+ type: "Polygon";
18
+ coordinates: Position[][];
19
+ };
20
+ export type MultiPolygonGeometry = {
21
+ type: "MultiPolygon";
22
+ coordinates: Position[][][];
23
+ };
24
+ export type AreaGeometry = PolygonGeometry | MultiPolygonGeometry;
25
+ export type Feature<G = AreaGeometry, P = Record<string, unknown>> = {
26
+ type: "Feature";
27
+ id?: string | number;
28
+ geometry: G;
29
+ properties: P;
30
+ };
31
+ export type FeatureCollection<G = AreaGeometry, P = Record<string, unknown>> = {
32
+ type: "FeatureCollection";
33
+ features: Feature<G, P>[];
34
+ };
35
+ export declare const geoJsonAnySchema: z.ZodObject<{
36
+ type: z.ZodString;
37
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
38
+ type: z.ZodString;
39
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
40
+ type: z.ZodString;
41
+ }, z.ZodTypeAny, "passthrough">>;
42
+ export interface RegionSummary {
43
+ id: number;
44
+ slug: string;
45
+ name: string;
46
+ type: RegionType;
47
+ level: number | null;
48
+ parent_id: number | null;
49
+ code: string | null;
50
+ }
51
+ export interface Region extends RegionSummary {
52
+ country_code: string;
53
+ tags: string[];
54
+ properties: Record<string, unknown>;
55
+ pack_id?: number | null;
56
+ location_count?: number;
57
+ geometry?: AreaGeometry;
58
+ created_at?: string;
59
+ updated_at?: string;
60
+ }
61
+ export declare const countryCodeSchema: z.ZodEffects<z.ZodString, string, string>;
62
+ export declare const packSlugSchema: z.ZodString;
63
+ export declare const packVersionSchema: z.ZodString;
64
+ export declare const packSchema: z.ZodObject<{
65
+ pack: z.ZodObject<{
66
+ slug: z.ZodString;
67
+ version: z.ZodString;
68
+ schema_version: z.ZodNumber;
69
+ published_at: z.ZodOptional<z.ZodString>;
70
+ }, "strip", z.ZodTypeAny, {
71
+ slug: string;
72
+ version: string;
73
+ schema_version: number;
74
+ published_at?: string | undefined;
75
+ }, {
76
+ slug: string;
77
+ version: string;
78
+ schema_version: number;
79
+ published_at?: string | undefined;
80
+ }>;
81
+ countries: z.ZodDefault<z.ZodArray<z.ZodObject<{
82
+ code: z.ZodEffects<z.ZodString, string, string>;
83
+ name: z.ZodString;
84
+ geometry: z.ZodOptional<z.ZodNullable<z.ZodObject<{
85
+ type: z.ZodString;
86
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
87
+ type: z.ZodString;
88
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
89
+ type: z.ZodString;
90
+ }, z.ZodTypeAny, "passthrough">>>>;
91
+ }, "strip", z.ZodTypeAny, {
92
+ code: string;
93
+ name: string;
94
+ geometry?: z.objectOutputType<{
95
+ type: z.ZodString;
96
+ }, z.ZodTypeAny, "passthrough"> | null | undefined;
97
+ }, {
98
+ code: string;
99
+ name: string;
100
+ geometry?: z.objectInputType<{
101
+ type: z.ZodString;
102
+ }, z.ZodTypeAny, "passthrough"> | null | undefined;
103
+ }>, "many">>;
104
+ regions: z.ZodObject<{
105
+ type: z.ZodLiteral<"FeatureCollection">;
106
+ features: z.ZodArray<z.ZodObject<{
107
+ type: z.ZodLiteral<"Feature">;
108
+ geometry: z.ZodObject<{
109
+ type: z.ZodString;
110
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
111
+ type: z.ZodString;
112
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
113
+ type: z.ZodString;
114
+ }, z.ZodTypeAny, "passthrough">>;
115
+ properties: z.ZodObject<{
116
+ country: z.ZodEffects<z.ZodString, string, string>;
117
+ slug: z.ZodString;
118
+ name: z.ZodString;
119
+ type: z.ZodEnum<["admin", "cultural", "natural", "custom"]>;
120
+ level: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
121
+ parent: z.ZodOptional<z.ZodNullable<z.ZodString>>;
122
+ parent_country: z.ZodOptional<z.ZodNullable<z.ZodEffects<z.ZodString, string, string>>>;
123
+ code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
124
+ tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
125
+ properties: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
126
+ }, "strip", z.ZodTypeAny, {
127
+ type: "admin" | "cultural" | "natural" | "custom";
128
+ slug: string;
129
+ name: string;
130
+ properties: Record<string, unknown>;
131
+ country: string;
132
+ tags: string[];
133
+ code?: string | null | undefined;
134
+ level?: number | null | undefined;
135
+ parent?: string | null | undefined;
136
+ parent_country?: string | null | undefined;
137
+ }, {
138
+ type: "admin" | "cultural" | "natural" | "custom";
139
+ slug: string;
140
+ name: string;
141
+ country: string;
142
+ code?: string | null | undefined;
143
+ properties?: Record<string, unknown> | undefined;
144
+ level?: number | null | undefined;
145
+ parent?: string | null | undefined;
146
+ parent_country?: string | null | undefined;
147
+ tags?: string[] | undefined;
148
+ }>;
149
+ }, "strip", z.ZodTypeAny, {
150
+ type: "Feature";
151
+ geometry: {
152
+ type: string;
153
+ } & {
154
+ [k: string]: unknown;
155
+ };
156
+ properties: {
157
+ type: "admin" | "cultural" | "natural" | "custom";
158
+ slug: string;
159
+ name: string;
160
+ properties: Record<string, unknown>;
161
+ country: string;
162
+ tags: string[];
163
+ code?: string | null | undefined;
164
+ level?: number | null | undefined;
165
+ parent?: string | null | undefined;
166
+ parent_country?: string | null | undefined;
167
+ };
168
+ }, {
169
+ type: "Feature";
170
+ geometry: {
171
+ type: string;
172
+ } & {
173
+ [k: string]: unknown;
174
+ };
175
+ properties: {
176
+ type: "admin" | "cultural" | "natural" | "custom";
177
+ slug: string;
178
+ name: string;
179
+ country: string;
180
+ code?: string | null | undefined;
181
+ properties?: Record<string, unknown> | undefined;
182
+ level?: number | null | undefined;
183
+ parent?: string | null | undefined;
184
+ parent_country?: string | null | undefined;
185
+ tags?: string[] | undefined;
186
+ };
187
+ }>, "many">;
188
+ }, "strip", z.ZodTypeAny, {
189
+ type: "FeatureCollection";
190
+ features: {
191
+ type: "Feature";
192
+ geometry: {
193
+ type: string;
194
+ } & {
195
+ [k: string]: unknown;
196
+ };
197
+ properties: {
198
+ type: "admin" | "cultural" | "natural" | "custom";
199
+ slug: string;
200
+ name: string;
201
+ properties: Record<string, unknown>;
202
+ country: string;
203
+ tags: string[];
204
+ code?: string | null | undefined;
205
+ level?: number | null | undefined;
206
+ parent?: string | null | undefined;
207
+ parent_country?: string | null | undefined;
208
+ };
209
+ }[];
210
+ }, {
211
+ type: "FeatureCollection";
212
+ features: {
213
+ type: "Feature";
214
+ geometry: {
215
+ type: string;
216
+ } & {
217
+ [k: string]: unknown;
218
+ };
219
+ properties: {
220
+ type: "admin" | "cultural" | "natural" | "custom";
221
+ slug: string;
222
+ name: string;
223
+ country: string;
224
+ code?: string | null | undefined;
225
+ properties?: Record<string, unknown> | undefined;
226
+ level?: number | null | undefined;
227
+ parent?: string | null | undefined;
228
+ parent_country?: string | null | undefined;
229
+ tags?: string[] | undefined;
230
+ };
231
+ }[];
232
+ }>;
233
+ }, "strip", z.ZodTypeAny, {
234
+ pack: {
235
+ slug: string;
236
+ version: string;
237
+ schema_version: number;
238
+ published_at?: string | undefined;
239
+ };
240
+ countries: {
241
+ code: string;
242
+ name: string;
243
+ geometry?: z.objectOutputType<{
244
+ type: z.ZodString;
245
+ }, z.ZodTypeAny, "passthrough"> | null | undefined;
246
+ }[];
247
+ regions: {
248
+ type: "FeatureCollection";
249
+ features: {
250
+ type: "Feature";
251
+ geometry: {
252
+ type: string;
253
+ } & {
254
+ [k: string]: unknown;
255
+ };
256
+ properties: {
257
+ type: "admin" | "cultural" | "natural" | "custom";
258
+ slug: string;
259
+ name: string;
260
+ properties: Record<string, unknown>;
261
+ country: string;
262
+ tags: string[];
263
+ code?: string | null | undefined;
264
+ level?: number | null | undefined;
265
+ parent?: string | null | undefined;
266
+ parent_country?: string | null | undefined;
267
+ };
268
+ }[];
269
+ };
270
+ }, {
271
+ pack: {
272
+ slug: string;
273
+ version: string;
274
+ schema_version: number;
275
+ published_at?: string | undefined;
276
+ };
277
+ regions: {
278
+ type: "FeatureCollection";
279
+ features: {
280
+ type: "Feature";
281
+ geometry: {
282
+ type: string;
283
+ } & {
284
+ [k: string]: unknown;
285
+ };
286
+ properties: {
287
+ type: "admin" | "cultural" | "natural" | "custom";
288
+ slug: string;
289
+ name: string;
290
+ country: string;
291
+ code?: string | null | undefined;
292
+ properties?: Record<string, unknown> | undefined;
293
+ level?: number | null | undefined;
294
+ parent?: string | null | undefined;
295
+ parent_country?: string | null | undefined;
296
+ tags?: string[] | undefined;
297
+ };
298
+ }[];
299
+ };
300
+ countries?: {
301
+ code: string;
302
+ name: string;
303
+ geometry?: z.objectInputType<{
304
+ type: z.ZodString;
305
+ }, z.ZodTypeAny, "passthrough"> | null | undefined;
306
+ }[] | undefined;
307
+ }>;
308
+ export type Pack = z.infer<typeof packSchema>;
309
+ export interface PackInfo {
310
+ id: number;
311
+ slug: string;
312
+ version: string;
313
+ schema_version: number;
314
+ installed_at: string;
315
+ region_count: number;
316
+ }
317
+ export interface InstallPackResult {
318
+ pack: string;
319
+ version: string;
320
+ countries: number;
321
+ regions: number;
322
+ memberships: number;
323
+ }
package/dist/types.js ADDED
@@ -0,0 +1,58 @@
1
+ import { z } from "zod";
2
+ // ---------------------------------------------------------------------------
3
+ // Region taxonomy
4
+ // ---------------------------------------------------------------------------
5
+ /**
6
+ * admin – official administrative units (județ, department, state…)
7
+ * cultural – ethnographic / historical areas (Mărginimea Sibiului, Țara Oltului)
8
+ * natural – landscape units (Delta Dunării, Carpații Meridionali)
9
+ * custom – anything you define for your own product (a marketing zone, a tour area)
10
+ */
11
+ export const REGION_TYPES = ["admin", "cultural", "natural", "custom"];
12
+ export const regionTypeSchema = z.enum(REGION_TYPES);
13
+ export const geoJsonAnySchema = z
14
+ .object({ type: z.string() })
15
+ .passthrough();
16
+ export const countryCodeSchema = z
17
+ .string()
18
+ .length(2)
19
+ .transform((s) => s.toUpperCase());
20
+ // ---------------------------------------------------------------------------
21
+ // Region packs — one JSON document moving regions between databases
22
+ // ---------------------------------------------------------------------------
23
+ export const packSlugSchema = z.string().min(1).max(100).regex(/^[a-z0-9][a-z0-9-]*$/, "lowercase letters, digits and dashes");
24
+ export const packVersionSchema = z
25
+ .string()
26
+ .min(1)
27
+ .max(50)
28
+ .regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "letters, digits, dots, dashes and underscores");
29
+ export const packSchema = z.object({
30
+ pack: z.object({
31
+ slug: packSlugSchema,
32
+ version: z.string().min(1).max(50),
33
+ schema_version: z.number().int().positive(),
34
+ published_at: z.string().optional(),
35
+ }),
36
+ countries: z
37
+ .array(z.object({ code: countryCodeSchema, name: z.string().min(1), geometry: geoJsonAnySchema.nullable().optional() }))
38
+ .default([]),
39
+ regions: z.object({
40
+ type: z.literal("FeatureCollection"),
41
+ features: z.array(z.object({
42
+ type: z.literal("Feature"),
43
+ geometry: geoJsonAnySchema,
44
+ properties: z.object({
45
+ country: countryCodeSchema,
46
+ slug: z.string().min(1).max(200),
47
+ name: z.string().min(1).max(200),
48
+ type: regionTypeSchema,
49
+ level: z.number().int().nullable().optional(),
50
+ parent: z.string().nullable().optional(),
51
+ parent_country: countryCodeSchema.nullable().optional(),
52
+ code: z.string().nullable().optional(),
53
+ tags: z.array(z.string()).default([]),
54
+ properties: z.record(z.unknown()).default({}),
55
+ }),
56
+ })),
57
+ }),
58
+ });
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@nspot/geo-engine",
3
+ "version": "0.1.0",
4
+ "description": "Region membership for your own PostGIS tables: install the geo schema, register tables, install region packs, query by region.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
12
+ "./types": { "types": "./dist/types.d.ts", "import": "./dist/types.js" }
13
+ },
14
+ "bin": { "geo-engine": "./dist/cli.js" },
15
+ "files": ["dist", "sql", "README.md", "LICENSE"],
16
+ "engines": { "node": ">=20" },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/NSpot-Games/geo-engine.git",
20
+ "directory": "packages/sdk-node"
21
+ },
22
+ "homepage": "https://github.com/NSpot-Games/geo-engine/tree/main/packages/sdk-node#readme",
23
+ "bugs": "https://github.com/NSpot-Games/geo-engine/issues",
24
+ "publishConfig": { "access": "public" },
25
+ "scripts": {
26
+ "sync-sql": "node scripts/sync-sql.mjs",
27
+ "prepack": "node scripts/sync-sql.mjs && tsc -p tsconfig.json",
28
+ "build": "node scripts/sync-sql.mjs && tsc -p tsconfig.json",
29
+ "typecheck": "tsc -p tsconfig.json --noEmit",
30
+ "clean": "rm -rf dist",
31
+ "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
32
+ "test": "vitest run"
33
+ },
34
+ "dependencies": {
35
+ "@types/pg": "^8.11.10",
36
+ "pg": "^8.13.1",
37
+ "zod": "^3.23.8"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.10.2",
41
+ "tsx": "^4.19.2",
42
+ "typescript": "^5.7.2",
43
+ "vitest": "^4.1.11"
44
+ }
45
+ }