Derive TS type from JSON Schema

So we don't define (almost) the same schema twice
This commit is contained in:
2026-01-22 13:36:08 +07:00
parent 5f55fc1da1
commit 8851323012
3 changed files with 56 additions and 41 deletions

View File

@@ -15,6 +15,7 @@
"author": "Râu Cao <raucao@kosmos.org>", "author": "Râu Cao <raucao@kosmos.org>",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"json-schema-to-ts": "^3.1.1",
"remotestoragejs": "^2.0.0-beta.8", "remotestoragejs": "^2.0.0-beta.8",
"rimraf": "^6.1.2", "rimraf": "^6.1.2",
"typescript": "^5.9.3" "typescript": "^5.9.3"

23
pnpm-lock.yaml generated
View File

@@ -15,6 +15,9 @@ importers:
specifier: ^3.0.2 specifier: ^3.0.2
version: 3.0.2 version: 3.0.2
devDependencies: devDependencies:
json-schema-to-ts:
specifier: ^3.1.1
version: 3.1.1
remotestoragejs: remotestoragejs:
specifier: ^2.0.0-beta.8 specifier: ^2.0.0-beta.8
version: 2.0.0-beta.8 version: 2.0.0-beta.8
@@ -27,6 +30,10 @@ importers:
packages: packages:
'@babel/runtime@7.28.6':
resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==}
engines: {node: '>=6.9.0'}
'@isaacs/balanced-match@4.0.1': '@isaacs/balanced-match@4.0.1':
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
engines: {node: 20 || >=22} engines: {node: 20 || >=22}
@@ -54,6 +61,10 @@ packages:
resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==}
engines: {node: 20 || >=22} 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: latlon-geohash@2.0.0:
resolution: {integrity: sha512-OKBswTwrvTdtenV+9C9euBmvgGuqyjJNAzpQCarRz1m8/pYD2nz9fKkXmLs2S3jeXaLi3Ry76twQplKKUlgS/g==} resolution: {integrity: sha512-OKBswTwrvTdtenV+9C9euBmvgGuqyjJNAzpQCarRz1m8/pYD2nz9fKkXmLs2S3jeXaLi3Ry76twQplKKUlgS/g==}
@@ -84,6 +95,9 @@ packages:
engines: {node: 20 || >=22} engines: {node: 20 || >=22}
hasBin: true hasBin: true
ts-algebra@2.0.0:
resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==}
tv4@1.3.0: tv4@1.3.0:
resolution: {integrity: sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==} resolution: {integrity: sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -109,6 +123,8 @@ packages:
snapshots: snapshots:
'@babel/runtime@7.28.6': {}
'@isaacs/balanced-match@4.0.1': {} '@isaacs/balanced-match@4.0.1': {}
'@isaacs/brace-expansion@5.0.0': '@isaacs/brace-expansion@5.0.0':
@@ -132,6 +148,11 @@ snapshots:
minipass: 7.1.2 minipass: 7.1.2
path-scurry: 2.0.1 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: {} latlon-geohash@2.0.0: {}
lru-cache@11.2.4: {} lru-cache@11.2.4: {}
@@ -165,6 +186,8 @@ snapshots:
glob: 13.0.0 glob: 13.0.0
package-json-from-dist: 1.0.1 package-json-from-dist: 1.0.1
ts-algebra@2.0.0: {}
tv4@1.3.0: {} tv4@1.3.0: {}
typescript@5.9.3: {} typescript@5.9.3: {}

View File

@@ -1,53 +1,43 @@
import BaseClient from 'remotestoragejs/release/types/baseclient'; import BaseClient from 'remotestoragejs/release/types/baseclient';
import Geohash from 'latlon-geohash'; import Geohash from 'latlon-geohash';
import { ulid } from 'ulid'; import { ulid } from 'ulid';
import { FromSchema } from 'json-schema-to-ts';
interface Place { const placeSchema = {
id: string; type: 'object',
title: string; properties: {
lat: number; id: { type: 'string' },
lon: number; title: { type: 'string' },
geohash: string; lat: { type: 'number' },
zoom?: number; lon: { type: 'number' },
url?: string; geohash: { type: 'string' },
osmId?: string; zoom: { type: 'number' },
osmType?: string; url: { type: 'string' },
osmTags?: Record<string, string>; osmId: { type: 'string' },
description?: string; osmType: { type: 'string' },
tags?: string[]; osmTags: {
createdAt: string; type: 'object',
updatedAt?: string; additionalProperties: { type: 'string' },
[key: string]: any; },
} 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<typeof placeSchema> & { [key: string]: any };
const Places = function ( const Places = function (
privateClient: BaseClient /*, publicClient: BaseClient */ privateClient: BaseClient /*, publicClient: BaseClient */
) { ) {
// Define Schema // Define Schema
privateClient.declareType('place', { privateClient.declareType('place', placeSchema as any);
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'],
});
// Helper to normalize place object // Helper to normalize place object
function preparePlace(data: Partial<Place>): Place { function preparePlace(data: Partial<Place>): Place {
@@ -70,6 +60,7 @@ const Places = function (
lon, lon,
geohash, geohash,
title, title,
tags: data.tags || [],
createdAt: data.createdAt || now, createdAt: data.createdAt || now,
updatedAt: data.id ? now : undefined, updatedAt: data.id ? now : undefined,
}; };