diff --git a/package-lock.json b/package-lock.json index 458040df6..03de6ae66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@mapbox/geojson-normalize": "^0.0.1", "@mapbox/point-geometry": "^0.1.0", "fast-deep-equal": "^3.1.3", - "hat": "0.0.3" + "nanoid": "^5.0.9" }, "devDependencies": { "@mapbox/cloudfriend": "^8.1.0", @@ -735,6 +735,24 @@ "node": ">=6" } }, + "node_modules/@mapbox/mapbox-gl-geocoder/node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/@mapbox/mapbox-gl-supported": { "version": "3.0.0", "dev": true, @@ -5366,10 +5384,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hat": { - "version": "0.0.3", - "license": "MIT/X11" - }, "node_modules/hosted-git-info": { "version": "2.8.9", "dev": true, @@ -6342,20 +6356,20 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.7", - "dev": true, + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz", + "integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { - "nanoid": "bin/nanoid.cjs" + "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^18 || >=20" } }, "node_modules/natural-compare": { @@ -6872,6 +6886,24 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/potpack": { "version": "2.0.0", "dev": true, diff --git a/package.json b/package.json index 8201833d9..3ef3fc3a1 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@mapbox/geojson-normalize": "^0.0.1", "@mapbox/point-geometry": "^0.1.0", "fast-deep-equal": "^3.1.3", - "hat": "0.0.3" + "nanoid": "^5.0.9" }, "files": [ "src", diff --git a/src/api.js b/src/api.js index 0a99dc406..16d3d95b2 100644 --- a/src/api.js +++ b/src/api.js @@ -1,6 +1,6 @@ import isEqual from 'fast-deep-equal'; import normalize from '@mapbox/geojson-normalize'; -import hat from 'hat'; +import {generateID} from './lib/id.js'; import featuresAt from './lib/features_at.js'; import stringSetsAreEqual from './lib/string_sets_are_equal.js'; import * as Constants from './constants.js'; @@ -76,7 +76,7 @@ export default function(ctx, api) { const featureCollection = JSON.parse(JSON.stringify(normalize(geojson))); const ids = featureCollection.features.map((feature) => { - feature.id = feature.id || hat(); + feature.id = feature.id || generateID(); if (feature.geometry === null) { throw new Error('Invalid geometry: null'); diff --git a/src/feature_types/feature.js b/src/feature_types/feature.js index 0d5c6b21e..ee2b92eab 100644 --- a/src/feature_types/feature.js +++ b/src/feature_types/feature.js @@ -1,11 +1,11 @@ -import hat from 'hat'; +import {generateID} from '../lib/id.js'; import * as Constants from '../constants.js'; const Feature = function(ctx, geojson) { this.ctx = ctx; this.properties = geojson.properties || {}; this.coordinates = geojson.geometry.coordinates; - this.id = geojson.id || hat(); + this.id = geojson.id || generateID(); this.type = geojson.geometry.type; }; diff --git a/src/feature_types/multi_feature.js b/src/feature_types/multi_feature.js index 78f194075..752eca006 100644 --- a/src/feature_types/multi_feature.js +++ b/src/feature_types/multi_feature.js @@ -1,4 +1,4 @@ -import hat from 'hat'; +import {generateID} from '../lib/id.js'; import Feature from './feature.js'; import * as Constants from '../constants.js'; @@ -33,7 +33,7 @@ MultiFeature.prototype = Object.create(Feature.prototype); MultiFeature.prototype._coordinatesToFeatures = function(coordinates) { const Model = this.model.bind(this); return coordinates.map(coords => new Model(this.ctx, { - id: hat(), + id: generateID(), type: Constants.geojsonTypes.FEATURE, properties: {}, geometry: { diff --git a/src/lib/id.js b/src/lib/id.js new file mode 100644 index 000000000..eaf080a99 --- /dev/null +++ b/src/lib/id.js @@ -0,0 +1,7 @@ +import {customAlphabet} from 'nanoid/non-secure'; + +const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 32); + +export function generateID() { + return nanoid(); +} diff --git a/test/api.test.js b/test/api.test.js index 34388a656..c0b345d50 100644 --- a/test/api.test.js +++ b/test/api.test.js @@ -33,6 +33,15 @@ afterEach(() => { deleteSpy = null; }); +test('Draw.add', async (t) => { + await t.test('should generate unique ID', () => { + const [id] = Draw.add(getGeoJSON('point')); + + assert.equal(typeof id, 'string', 'valid string id returned on add'); + assert.equal(id.length, 32, 'valid string id length'); + }); +}); + test('Draw.getFeatureIdsAt', async () => { const feature = getGeoJSON('point'); const [id] = Draw.add(feature); diff --git a/test/utils/create_feature.js b/test/utils/create_feature.js index 317eabbc9..6c5f5e5ee 100644 --- a/test/utils/create_feature.js +++ b/test/utils/create_feature.js @@ -1,11 +1,20 @@ -import hat from 'hat'; +import {generateID} from '../../src/lib/id.js'; import getGeoJSON from './get_geojson.js'; -const hatRack = hat.rack(); +const usedIds = new Set(); + +export function generateUniqueID() { + let id = generateID(); + while (usedIds.has(id)) { + id = generateID(); + } + usedIds.add(id); + return id; +} export default function createFeature(featureType) { const feature = Object.assign({ - id: hatRack(), + id: generateUniqueID(), properties: {} }, getGeoJSON(featureType)); feature.toGeoJSON = () => feature;