diff --git a/README.md b/README.md new file mode 100644 index 0000000..890e011 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# @remotestorage/module-places + +This module allows you to manage saved places (Points of Interest) using the [remoteStorage](https://remotestorage.io/) protocol. + +## Installation + +```bash +pnpm add @remotestorage/module-places +``` + +## Usage + +```javascript +import RemoteStorage from 'remotestoragejs'; +import PlacesModule from '@remotestorage/module-places'; + +const remoteStorage = new RemoteStorage({ + modules: [PlacesModule], +}); + +// Access the module +const places = remoteStorage.places; + +// Store a place +await places.store({ + title: 'My Favorite Coffee Shop', + lat: 52.520008, + lon: 13.404954, +}); + +// List all places +const allPlaces = await places.getPlaces(); +console.log(allPlaces); +``` + +## API Reference + +### Interfaces + +- [PlacesClient](docs/interfaces/PlacesClient.md) + +### Type Aliases + +- [Place](docs/type-aliases/Place.md) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..abc164d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,17 @@ +**@remotestorage/module-places** + +*** + +# @remotestorage/module-places + +## Interfaces + +- [PlacesClient](interfaces/PlacesClient.md) + +## Type Aliases + +- [Place](type-aliases/Place.md) + +## Variables + +- [default](variables/default.md) diff --git a/docs/interfaces/PlacesClient.md b/docs/interfaces/PlacesClient.md new file mode 100644 index 0000000..1f642e8 --- /dev/null +++ b/docs/interfaces/PlacesClient.md @@ -0,0 +1,135 @@ +[**@remotestorage/module-places**](../README.md) + +*** + +[@remotestorage/module-places](../README.md) / PlacesClient + +# Interface: PlacesClient + +## Methods + +### get() + +> **get**(`id`, `geohash`): `Promise`\<`unknown`\> + +Get a single place. +Requires geohash to locate the folder. + +#### Parameters + +##### id + +`string` + +The ID of the place to retrieve. + +##### geohash + +`string` + +The geohash of the place. + +#### Returns + +`Promise`\<`unknown`\> + +The place object. + +*** + +### getPlaces() + +> **getPlaces**(`prefixes?`): `Promise`\<[`Place`](../type-aliases/Place.md)[]\> + +Get places from specific prefixes. + +#### Parameters + +##### prefixes? + +`string`[] + +Optional array of 4-character geohash prefixes to load (e.g. ['w1q7', 'w1q8']). + If not provided, it will attempt to scan ALL prefixes (recursive). + +#### Returns + +`Promise`\<[`Place`](../type-aliases/Place.md)[]\> + +An array of places. + +*** + +### listByPrefix() + +> **listByPrefix**(`prefix`): `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. + +#### Parameters + +##### prefix + +`string` + +The geohash prefix to filter by. + +#### Returns + +`Promise`\<`unknown`\> + +A map of objects found at the prefix. + +*** + +### remove() + +> **remove**(`id`, `geohash`): `Promise`\<`unknown`\> + +Remove a place. +Requires geohash to locate the folder. + +#### Parameters + +##### id + +`string` + +The ID of the place to remove. + +##### geohash + +`string` + +The geohash of the place. + +#### Returns + +`Promise`\<`unknown`\> + +*** + +### store() + +> **store**(`placeData`): `Promise`\<[`Place`](../type-aliases/Place.md)\> + +Store a place. +Generates ID and Geohash if missing. +Path structure: // + +#### Parameters + +##### placeData + +`Partial`\<[`Place`](../type-aliases/Place.md)\> + +The data of the place to store. + +#### Returns + +`Promise`\<[`Place`](../type-aliases/Place.md)\> + +The stored place object. diff --git a/docs/type-aliases/Place.md b/docs/type-aliases/Place.md new file mode 100644 index 0000000..f188590 --- /dev/null +++ b/docs/type-aliases/Place.md @@ -0,0 +1,27 @@ +[**@remotestorage/module-places**](../README.md) + +*** + +[@remotestorage/module-places](../README.md) / Place + +# Type Alias: Place + +> **Place** = `FromSchema`\<*typeof* `placeSchema`\> & `object` + +Represents a Place object. + +Core properties enforced by schema: +- `id`: Unique identifier (ULID) +- `title`: Name of the place +- `lat`: Latitude +- `lon`: Longitude +- `geohash`: Geohash for indexing +- `createdAt`: ISO date string + +Optional properties: +- `description`: Text description +- `zoom`: Map zoom level +- `url`: Related URL +- `osmId`, `osmType`, `osmTags`: OpenStreetMap data +- `tags`: Array of string tags +- `updatedAt`: ISO date string diff --git a/docs/variables/default.md b/docs/variables/default.md new file mode 100644 index 0000000..7e294c8 --- /dev/null +++ b/docs/variables/default.md @@ -0,0 +1,33 @@ +[**@remotestorage/module-places**](../README.md) + +*** + +[@remotestorage/module-places](../README.md) / default + +# Variable: default + +> **default**: `object` + +## Type Declaration + +### builder() + +> **builder**: (`privateClient`) => `object` = `Places` + +#### Parameters + +##### privateClient + +`BaseClient` + +#### Returns + +`object` + +##### exports + +> **exports**: [`PlacesClient`](../interfaces/PlacesClient.md) + +### name + +> **name**: `string` = `'places'` diff --git a/package.json b/package.json index fb79aa7..5a0d4bc 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,9 @@ ], "scripts": { "build": "rimraf dist && tsc", + "doc": "typedoc", "test": "echo \"Error: no test specified\" && exit 1", - "version": "pnpm run build && git add dist" + "version": "pnpm run build && pnpm run doc && git add dist docs README.md" }, "author": "Râu Cao ", "license": "MIT", @@ -19,6 +20,8 @@ "json-schema-to-ts": "^3.1.1", "remotestoragejs": "^2.0.0-beta.8", "rimraf": "^6.1.2", + "typedoc": "^0.28.16", + "typedoc-plugin-markdown": "^4.9.0", "typescript": "^5.9.3" }, "dependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9266067..1b79738 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,12 @@ importers: rimraf: specifier: ^6.1.2 version: 6.1.2 + typedoc: + specifier: ^0.28.16 + version: 0.28.16(typescript@5.9.3) + typedoc-plugin-markdown: + specifier: ^4.9.0 + version: 4.9.0(typedoc@0.28.16(typescript@5.9.3)) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -34,6 +40,9 @@ packages: resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} + '@gerrit0/mini-shiki@3.21.0': + resolution: {integrity: sha512-9PrsT5DjZA+w3lur/aOIx3FlDeHdyCEFlv9U+fmsVyjPZh61G5SYURQ/1ebe2U63KbDmI2V8IhIUegWb8hjOyg==} + '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -42,12 +51,46 @@ packages: resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} engines: {node: 20 || >=22} + '@shikijs/engine-oniguruma@3.21.0': + resolution: {integrity: sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==} + + '@shikijs/langs@3.21.0': + resolution: {integrity: sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==} + + '@shikijs/themes@3.21.0': + resolution: {integrity: sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==} + + '@shikijs/types@3.21.0': + resolution: {integrity: sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/node@20.14.0': resolution: {integrity: sha512-5cHBxFGJx6L4s56Bubp4fglrEpmyJypsqI6RgzMfBHWUJQGWAAi8cWcgetEbZXHYXo9C2Fa4EEds/uSyS4cxmA==} '@types/tv4@1.2.33': resolution: {integrity: sha512-7phCVTXC6Bj50IV1iKOwqGkR4JONJyMbRZnKTSuujv1S/tO9rG5OdCt7BMSjytO+zJmYdn1/I4fd3SH0gtO99g==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + esm@3.2.25: resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} engines: {node: '>=6'} @@ -68,14 +111,31 @@ packages: latlon-geohash@2.0.0: resolution: {integrity: sha512-OKBswTwrvTdtenV+9C9euBmvgGuqyjJNAzpQCarRz1m8/pYD2nz9fKkXmLs2S3jeXaLi3Ry76twQplKKUlgS/g==} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + lru-cache@11.2.4: resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} + lunr@2.3.9: + resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + minimatch@10.1.1: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} @@ -87,6 +147,10 @@ packages: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + remotestoragejs@2.0.0-beta.8: resolution: {integrity: sha512-rtyHTG2VbtiKTRmbwjponRf5VTPJMcHv/ijNid1zX48C0Z0F8ZCBBfkKD2QCxTQyQvCupkWNy3wuIu4HE+AEng==} @@ -102,11 +166,27 @@ packages: resolution: {integrity: sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==} engines: {node: '>= 0.8.0'} + typedoc-plugin-markdown@4.9.0: + resolution: {integrity: sha512-9Uu4WR9L7ZBgAl60N/h+jqmPxxvnC9nQAlnnO/OujtG2ubjnKTVUFY1XDhcMY+pCqlX3N2HsQM2QTYZIU9tJuw==} + engines: {node: '>= 18'} + peerDependencies: + typedoc: 0.28.x + + typedoc@0.28.16: + resolution: {integrity: sha512-x4xW77QC3i5DUFMBp0qjukOTnr/sSg+oEs86nB3LjDslvAmwe/PUGDWbe3GrIqt59oTqoXK5GRK9tAa0sYMiog==} + engines: {node: '>= 18', pnpm: '>= 10'} + hasBin: true + peerDependencies: + typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + ulid@3.0.2: resolution: {integrity: sha512-yu26mwteFYzBAot7KVMqFGCVpsF6g8wXfJzQUHvu1no3+rRRSFcSV2nKeYvNPLD2J4b08jYBDhHUjeH0ygIl9w==} hasBin: true @@ -121,22 +201,71 @@ packages: resolution: {integrity: sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==} engines: {node: '>= 6'} + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + engines: {node: '>= 14.6'} + hasBin: true + snapshots: '@babel/runtime@7.28.6': {} + '@gerrit0/mini-shiki@3.21.0': + dependencies: + '@shikijs/engine-oniguruma': 3.21.0 + '@shikijs/langs': 3.21.0 + '@shikijs/themes': 3.21.0 + '@shikijs/types': 3.21.0 + '@shikijs/vscode-textmate': 10.0.2 + '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': dependencies: '@isaacs/balanced-match': 4.0.1 + '@shikijs/engine-oniguruma@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + + '@shikijs/themes@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + + '@shikijs/types@3.21.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/node@20.14.0': dependencies: undici-types: 5.26.5 '@types/tv4@1.2.33': {} + '@types/unist@3.0.3': {} + + argparse@2.0.1: {} + + balanced-match@1.0.2: {} + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + entities@4.5.0: {} + esm@3.2.25: {} fsevents@2.3.3: @@ -155,12 +284,33 @@ snapshots: latlon-geohash@2.0.0: {} + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + lru-cache@11.2.4: {} + lunr@2.3.9: {} + + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + + mdurl@2.0.0: {} + minimatch@10.1.1: dependencies: '@isaacs/brace-expansion': 5.0.0 + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + minipass@7.1.2: {} package-json-from-dist@1.0.1: {} @@ -170,6 +320,8 @@ snapshots: lru-cache: 11.2.4 minipass: 7.1.2 + punycode.js@2.3.1: {} + remotestoragejs@2.0.0-beta.8: dependencies: '@types/node': 20.14.0 @@ -190,8 +342,23 @@ snapshots: tv4@1.3.0: {} + typedoc-plugin-markdown@4.9.0(typedoc@0.28.16(typescript@5.9.3)): + dependencies: + typedoc: 0.28.16(typescript@5.9.3) + + typedoc@0.28.16(typescript@5.9.3): + dependencies: + '@gerrit0/mini-shiki': 3.21.0 + lunr: 2.3.9 + markdown-it: 14.1.0 + minimatch: 9.0.5 + typescript: 5.9.3 + yaml: 2.8.2 + typescript@5.9.3: {} + uc.micro@2.1.0: {} + ulid@3.0.2: {} undici-types@5.26.5: {} @@ -199,3 +366,5 @@ snapshots: webfinger.js@2.8.2: {} xhr2@0.2.1: {} + + yaml@2.8.2: {} diff --git a/src/places.ts b/src/places.ts index 4408614..d9f516f 100644 --- a/src/places.ts +++ b/src/places.ts @@ -31,11 +31,81 @@ const placeSchema = { required: ['id', 'title', 'lat', 'lon', 'geohash', 'createdAt'], } as const; -type Place = FromSchema & { [key: string]: any }; +/** + * Represents a Place object. + * + * Core properties enforced by schema: + * - `id`: Unique identifier (ULID) + * - `title`: Name of the place + * - `lat`: Latitude + * - `lon`: Longitude + * - `geohash`: Geohash for indexing + * - `createdAt`: ISO date string + * + * Optional properties: + * - `description`: Text description + * - `zoom`: Map zoom level + * - `url`: Related URL + * - `osmId`, `osmType`, `osmTags`: OpenStreetMap data + * - `tags`: Array of string tags + * - `updatedAt`: ISO date string + */ +export type Place = FromSchema & { [key: string]: any }; + +export interface PlacesClient { + /** + * Store a place. + * Generates ID and Geohash if missing. + * Path structure: // + * + * @param placeData - The data of the place to store. + * @returns The stored place object. + */ + store(placeData: Partial): Promise; + + /** + * Remove a place. + * Requires geohash to locate the folder. + * + * @param id - The ID of the place to remove. + * @param geohash - The geohash of the place. + */ + remove(id: string, geohash: string): Promise; + + /** + * Get a single place. + * Requires geohash to locate the folder. + * + * @param id - The ID of the place to retrieve. + * @param geohash - The geohash of the place. + * @returns The place object. + */ + get(id: string, geohash: string): Promise; + + /** + * 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. + * + * @param prefix - The geohash prefix to filter by. + * @returns A map of objects found at the prefix. + */ + listByPrefix(prefix: string): Promise; + + /** + * 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). + * @returns An array of places. + */ + getPlaces(prefixes?: string[]): Promise; +} const Places = function ( privateClient: BaseClient /*, publicClient: BaseClient */ -) { +): { exports: PlacesClient } { // Define Schema privateClient.declareType('place', placeSchema as any); diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..debce81 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["./src/places.ts"], + "out": "docs", + "plugin": ["typedoc-plugin-markdown"], + "disableSources": true, + "readme": "none", + "name": "@remotestorage/module-places" +}