Hello world
This commit is contained in:
55
dist/places.d.ts
vendored
Normal file
55
dist/places.d.ts
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import BaseClient from 'remotestoragejs/release/types/baseclient';
|
||||
interface Place {
|
||||
id: string;
|
||||
title: string;
|
||||
lat: number;
|
||||
lon: number;
|
||||
geohash: string;
|
||||
zoom?: number;
|
||||
url?: string;
|
||||
osmId?: string;
|
||||
osmType?: string;
|
||||
osmTags?: Record<string, string>;
|
||||
description?: string;
|
||||
tags?: string[];
|
||||
createdAt: string;
|
||||
updatedAt?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
declare const _default: {
|
||||
name: string;
|
||||
builder: (privateClient: BaseClient) => {
|
||||
exports: {
|
||||
/**
|
||||
* Store a place.
|
||||
* Generates ID and Geohash if missing.
|
||||
* Path structure: <geohash-prefix-2>/<geohash-prefix-2>/<id>
|
||||
*/
|
||||
store: (placeData: Partial<Place>) => Promise<Place>;
|
||||
/**
|
||||
* Remove a place.
|
||||
* Requires geohash to locate the folder.
|
||||
*/
|
||||
remove: (id: string, geohash: string) => Promise<import("remotestoragejs/release/types/interfaces/queued_request_response").QueuedRequestResponse>;
|
||||
/**
|
||||
* Get a single place.
|
||||
* Requires geohash to locate the folder.
|
||||
*/
|
||||
get: (id: string, geohash: string) => Promise<unknown>;
|
||||
/**
|
||||
* List places matching a geohash prefix.
|
||||
* Supports 2-char ("ab") or 4-char ("abcd") prefixes.
|
||||
* If 2-char, it returns the sub-folders (prefixes), not places.
|
||||
* If 4-char, it returns the places in that sector.
|
||||
*/
|
||||
listByPrefix: (prefix: string) => Promise<unknown>;
|
||||
/**
|
||||
* Get places from specific prefixes.
|
||||
* @param prefixes Optional array of 4-character geohash prefixes to load (e.g. ['w1q7', 'w1q8']).
|
||||
* If not provided, it will attempt to scan ALL prefixes (recursive).
|
||||
*/
|
||||
getPlaces: (prefixes?: string[]) => Promise<Place[]>;
|
||||
};
|
||||
};
|
||||
};
|
||||
export default _default;
|
||||
176
dist/places.js
vendored
Normal file
176
dist/places.js
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
import Geohash from 'latlon-geohash';
|
||||
import { ulid } from 'ulid';
|
||||
const Places = function (privateClient /*, 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'],
|
||||
});
|
||||
// Helper to normalize place object
|
||||
function preparePlace(data) {
|
||||
const now = new Date().toISOString();
|
||||
const id = data.id || ulid();
|
||||
// Ensure essential data is present before processing (runtime check for safety, though caller should validate)
|
||||
// We default to 0/0 if missing to satisfy TS, relying on schema validation later if really empty
|
||||
const lat = typeof data.lat === 'number' ? data.lat : 0;
|
||||
const lon = typeof data.lon === 'number' ? data.lon : 0;
|
||||
const title = data.title || 'Untitled Place';
|
||||
// Compute full precision geohash (default from library is usually 9-10 chars)
|
||||
const geohash = data.geohash || Geohash.encode(lat, lon, 10);
|
||||
const place = {
|
||||
...data,
|
||||
id,
|
||||
lat,
|
||||
lon,
|
||||
geohash,
|
||||
title,
|
||||
createdAt: data.createdAt || now,
|
||||
updatedAt: data.id ? now : undefined,
|
||||
};
|
||||
// Explicitly remove undefined fields (like URL) so storeObject validation doesn't fail on them
|
||||
// RemoteStorage schemas are strict about types; undefined is not a string.
|
||||
Object.keys(place).forEach((key) => {
|
||||
if (place[key] === undefined) {
|
||||
delete place[key];
|
||||
}
|
||||
});
|
||||
return place;
|
||||
}
|
||||
// Get path for a place based on its geohash (group by first 4 chars: 2+2)
|
||||
function getPath(geohash, id) {
|
||||
const p1 = geohash.substring(0, 2);
|
||||
const p2 = geohash.substring(2, 4);
|
||||
return `${p1}/${p2}/${id}`;
|
||||
}
|
||||
const places = {
|
||||
/**
|
||||
* Store a place.
|
||||
* Generates ID and Geohash if missing.
|
||||
* Path structure: <geohash-prefix-2>/<geohash-prefix-2>/<id>
|
||||
*/
|
||||
store: async function (placeData) {
|
||||
const place = preparePlace(placeData);
|
||||
const path = getPath(place.geohash, place.id);
|
||||
await privateClient.storeObject('place', path, place);
|
||||
return place;
|
||||
},
|
||||
/**
|
||||
* Remove a place.
|
||||
* Requires geohash to locate the folder.
|
||||
*/
|
||||
remove: async function (id, geohash) {
|
||||
if (!id || !geohash) {
|
||||
throw new Error('Both id and geohash are required to remove a place');
|
||||
}
|
||||
const path = getPath(geohash, id);
|
||||
return privateClient.remove(path);
|
||||
},
|
||||
/**
|
||||
* Get a single place.
|
||||
* Requires geohash to locate the folder.
|
||||
*/
|
||||
get: async function (id, geohash) {
|
||||
if (!id || !geohash) {
|
||||
throw new Error('Both id and geohash are required to get a place');
|
||||
}
|
||||
const path = getPath(geohash, id);
|
||||
return privateClient.getObject(path);
|
||||
},
|
||||
/**
|
||||
* List places matching a geohash prefix.
|
||||
* Supports 2-char ("ab") or 4-char ("abcd") prefixes.
|
||||
* If 2-char, it returns the sub-folders (prefixes), not places.
|
||||
* If 4-char, it returns the places in that sector.
|
||||
*/
|
||||
listByPrefix: async function (prefix) {
|
||||
let path = '';
|
||||
if (prefix.length >= 2) {
|
||||
path += prefix.substring(0, 2) + '/';
|
||||
}
|
||||
if (prefix.length >= 4) {
|
||||
path += prefix.substring(2, 4) + '/';
|
||||
}
|
||||
return privateClient.getAll(path);
|
||||
},
|
||||
/**
|
||||
* Get places from specific prefixes.
|
||||
* @param prefixes Optional array of 4-character geohash prefixes to load (e.g. ['w1q7', 'w1q8']).
|
||||
* If not provided, it will attempt to scan ALL prefixes (recursive).
|
||||
*/
|
||||
getPlaces: async function (prefixes) {
|
||||
const places = [];
|
||||
// Helper to process a specific 4-char path (e.g. "ab/cd/")
|
||||
const fetchPath = async (path) => {
|
||||
// Use getAll to load objects (places)
|
||||
// Set maxAge to false to skip cache check if configured
|
||||
const items = await privateClient.getAll(path, false);
|
||||
if (items) {
|
||||
Object.values(items).forEach((p) => {
|
||||
if (p && typeof p === 'object' && !p.toString().endsWith('/')) {
|
||||
places.push(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
if (prefixes && prefixes.length > 0) {
|
||||
// Load specific sectors
|
||||
const promises = prefixes.map(async (prefix) => {
|
||||
if (prefix.length < 4)
|
||||
return;
|
||||
const p1 = prefix.substring(0, 2);
|
||||
const p2 = prefix.substring(2, 4);
|
||||
await fetchPath(`${p1}/${p2}/`);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
}
|
||||
else {
|
||||
// Load EVERYTHING (Recursive scan)
|
||||
// Helper to extract keys from a listing result, handling both simple and detailed formats
|
||||
const getKeys = (listing) => {
|
||||
if (!listing || typeof listing !== 'object')
|
||||
return [];
|
||||
let target = listing;
|
||||
if (listing.items && typeof listing.items === 'object') {
|
||||
target = listing.items;
|
||||
}
|
||||
return Object.keys(target).filter((k) => k.endsWith('/') && !k.startsWith('@'));
|
||||
};
|
||||
// Level 1: xx/
|
||||
const level1 = await privateClient.getListing('', false);
|
||||
const l1Keys = getKeys(level1);
|
||||
await Promise.all(l1Keys.map(async (l1Key) => {
|
||||
// Level 2: xx/yy/
|
||||
const level2 = await privateClient.getListing(l1Key, false);
|
||||
const l2Keys = getKeys(level2);
|
||||
await Promise.all(l2Keys.map(async (l2Key) => {
|
||||
// Level 3: Items in xx/yy/
|
||||
await fetchPath(l1Key + l2Key);
|
||||
}));
|
||||
}));
|
||||
}
|
||||
return places;
|
||||
},
|
||||
};
|
||||
return { exports: places };
|
||||
};
|
||||
export default { name: 'places', builder: Places };
|
||||
Reference in New Issue
Block a user