From 8851323012d54902fb899ba5ab21e8ad7726279c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 22 Jan 2026 13:36:08 +0700 Subject: [PATCH] Derive TS type from JSON Schema So we don't define (almost) the same schema twice --- package.json | 1 + pnpm-lock.yaml | 23 ++++++++++++++++ src/places.ts | 73 ++++++++++++++++++++++---------------------------- 3 files changed, 56 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index bacd5df..551eb58 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "author": "Râu Cao ", "license": "MIT", "devDependencies": { + "json-schema-to-ts": "^3.1.1", "remotestoragejs": "^2.0.0-beta.8", "rimraf": "^6.1.2", "typescript": "^5.9.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94f9552..9266067 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,9 @@ importers: specifier: ^3.0.2 version: 3.0.2 devDependencies: + json-schema-to-ts: + specifier: ^3.1.1 + version: 3.1.1 remotestoragejs: specifier: ^2.0.0-beta.8 version: 2.0.0-beta.8 @@ -27,6 +30,10 @@ importers: packages: + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -54,6 +61,10 @@ packages: resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} engines: {node: 20 || >=22} + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + latlon-geohash@2.0.0: resolution: {integrity: sha512-OKBswTwrvTdtenV+9C9euBmvgGuqyjJNAzpQCarRz1m8/pYD2nz9fKkXmLs2S3jeXaLi3Ry76twQplKKUlgS/g==} @@ -84,6 +95,9 @@ packages: engines: {node: 20 || >=22} hasBin: true + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + tv4@1.3.0: resolution: {integrity: sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==} engines: {node: '>= 0.8.0'} @@ -109,6 +123,8 @@ packages: snapshots: + '@babel/runtime@7.28.6': {} + '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': @@ -132,6 +148,11 @@ snapshots: minipass: 7.1.2 path-scurry: 2.0.1 + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.28.6 + ts-algebra: 2.0.0 + latlon-geohash@2.0.0: {} lru-cache@11.2.4: {} @@ -165,6 +186,8 @@ snapshots: glob: 13.0.0 package-json-from-dist: 1.0.1 + ts-algebra@2.0.0: {} + tv4@1.3.0: {} typescript@5.9.3: {} diff --git a/src/places.ts b/src/places.ts index 27ef74a..4408614 100644 --- a/src/places.ts +++ b/src/places.ts @@ -1,53 +1,43 @@ import BaseClient from 'remotestoragejs/release/types/baseclient'; import Geohash from 'latlon-geohash'; import { ulid } from 'ulid'; +import { FromSchema } from 'json-schema-to-ts'; -interface Place { - id: string; - title: string; - lat: number; - lon: number; - geohash: string; - zoom?: number; - url?: string; - osmId?: string; - osmType?: string; - osmTags?: Record; - description?: string; - tags?: string[]; - createdAt: string; - updatedAt?: string; - [key: string]: any; -} +const placeSchema = { + type: 'object', + properties: { + id: { type: 'string' }, + title: { type: 'string' }, + lat: { type: 'number' }, + lon: { type: 'number' }, + geohash: { type: 'string' }, + zoom: { type: 'number' }, + url: { type: 'string' }, + osmId: { type: 'string' }, + osmType: { type: 'string' }, + osmTags: { + type: 'object', + additionalProperties: { type: 'string' }, + }, + description: { type: 'string' }, + tags: { + type: 'array', + items: { type: 'string' }, + default: [], + }, + createdAt: { type: 'string', format: 'date-time' }, + updatedAt: { type: 'string', format: 'date-time' }, + }, + required: ['id', 'title', 'lat', 'lon', 'geohash', 'createdAt'], +} as const; + +type Place = FromSchema & { [key: string]: any }; const Places = function ( privateClient: BaseClient /*, publicClient: BaseClient */ ) { // Define Schema - privateClient.declareType('place', { - type: 'object', - properties: { - id: { type: 'string', required: true }, - title: { type: 'string', required: true }, - lat: { type: 'number', required: true }, - lon: { type: 'number', required: true }, - geohash: { type: 'string', required: true }, - zoom: { type: 'number' }, - url: { type: 'string' }, - osmId: { type: 'string' }, - osmType: { type: 'string' }, - osmTags: { type: 'object' }, - description: { type: 'string' }, - tags: { - type: 'array', - items: { type: 'string' }, - default: [], - }, - createdAt: { type: 'string', format: 'date-time', required: true }, - updatedAt: { type: 'string', format: 'date-time' }, - }, - required: ['id', 'title', 'lat', 'lon', 'geohash', 'createdAt'], - }); + privateClient.declareType('place', placeSchema as any); // Helper to normalize place object function preparePlace(data: Partial): Place { @@ -70,6 +60,7 @@ const Places = function ( lon, geohash, title, + tags: data.tags || [], createdAt: data.createdAt || now, updatedAt: data.id ? now : undefined, };