@fireproof/core 0.3.22 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37227,7 +37227,10 @@ class TransactionBlockstore {
37227
37227
  inflightTransactions = new Set()
37228
37228
 
37229
37229
  constructor (name, encryptionKey) {
37230
- this.valet = new Valet(name, encryptionKey);
37230
+ if (name) {
37231
+ this.valet = new Valet(name, encryptionKey);
37232
+ }
37233
+ this.remoteBlockFunction = null;
37231
37234
  }
37232
37235
 
37233
37236
  /**
@@ -37260,6 +37263,7 @@ class TransactionBlockstore {
37260
37263
  async committedGet (key) {
37261
37264
  const old = this.committedBlocks.get(key);
37262
37265
  if (old) return old
37266
+ if (!this.valet) throw new Error('Missing block: ' + key)
37263
37267
  const got = await this.valet.getBlock(key);
37264
37268
  // console.log('committedGet: ' + key)
37265
37269
  this.committedBlocks.set(key, got);
@@ -37271,9 +37275,9 @@ class TransactionBlockstore {
37271
37275
  }
37272
37276
 
37273
37277
  async networkGet (key) {
37274
- if (this.valet.remoteBlockFunction) {
37278
+ if (this.remoteBlockFunction) {
37275
37279
  // todo why is this on valet?
37276
- const value = await husher(key, async () => await this.valet.remoteBlockFunction(key));
37280
+ const value = await husher(key, async () => await this.remoteBlockFunction(key));
37277
37281
  if (value) {
37278
37282
  // console.log('networkGot: ' + key, value.length)
37279
37283
  doTransaction('networkGot: ' + key, this, async innerBlockstore => {
@@ -37351,7 +37355,7 @@ class TransactionBlockstore {
37351
37355
  cids.add(stringCid);
37352
37356
  }
37353
37357
  }
37354
- if (cids.size > 0) {
37358
+ if (cids.size > 0 && this.valet) {
37355
37359
  // console.log(innerBlockstore.label, 'committing', cids.size, 'blocks')
37356
37360
  await this.valet.writeTransaction(innerBlockstore, cids);
37357
37361
  }
@@ -37442,6 +37446,10 @@ class InnerBlockstore {
37442
37446
 
37443
37447
  const blockOpts = { cache: nocache, chunker: bf(3), codec: codec$1, hasher: sha256$2, compare: simpleCompare };
37444
37448
 
37449
+ /**
37450
+ * @typedef {import('./blockstore.js').TransactionBlockstore} TransactionBlockstore
37451
+ */
37452
+
37445
37453
  const withLog = async (label, fn) => {
37446
37454
  const resp = await fn();
37447
37455
  // console.log('withLog', label, !!resp)
@@ -37662,7 +37670,7 @@ async function put (inBlocks, head, event, options) {
37662
37670
  /**
37663
37671
  * Determine the effective prolly root given the current merkle clock head.
37664
37672
  *
37665
- * @param {import('./blockstore.js').TransactionBlockstore} inBlocks Bucket block storage.
37673
+ * @param {TransactionBlockstore} inBlocks Bucket block storage.
37666
37674
  * @param {import('./clock').EventLink<import('./clock').EventData>[]} head Merkle clock head.
37667
37675
  */
37668
37676
  async function root (inBlocks, head) {
@@ -37671,8 +37679,8 @@ async function root (inBlocks, head) {
37671
37679
  }
37672
37680
  const { root: newProllyRootNode, blocks: newBlocks, cids } = await doProllyBulk(inBlocks, head);
37673
37681
  // todo maybe these should go to a temp blockstore?
37674
- await doTransaction('root', inBlocks, async (transactionBlockstore) => {
37675
- const { bigPut } = makeGetAndPutBlock(transactionBlockstore);
37682
+ await doTransaction('root', inBlocks, async (transactionBlocks) => {
37683
+ const { bigPut } = makeGetAndPutBlock(transactionBlocks);
37676
37684
  for (const nb of newBlocks) {
37677
37685
  bigPut(nb);
37678
37686
  }
@@ -37682,7 +37690,7 @@ async function root (inBlocks, head) {
37682
37690
 
37683
37691
  /**
37684
37692
  * Get the list of events not known by the `since` event
37685
- * @param {import('./blockstore.js').TransactionBlockstore} blocks Bucket block storage.
37693
+ * @param {TransactionBlockstore} blocks Bucket block storage.
37686
37694
  * @param {import('./clock').EventLink<import('./clock').EventData>[]} head Merkle clock head.
37687
37695
  * @param {import('./clock').EventLink<import('./clock').EventData>} since Event to compare against.
37688
37696
  * @returns {Promise<{clockCIDs: CIDCounter, result: import('./clock').EventData[]}>}
@@ -37699,7 +37707,7 @@ async function eventsSince (blocks, head, since) {
37699
37707
 
37700
37708
  /**
37701
37709
  *
37702
- * @param {import('./blockstore.js').TransactionBlockstore} blocks Bucket block storage.
37710
+ * @param {TransactionBlockstore} blocks Bucket block storage.
37703
37711
  * @param {import('./clock').EventLink<import('./clock').EventData>[]} head Merkle clock head.
37704
37712
  *
37705
37713
  * @returns {Promise<{cids: CIDCounter, clockCIDs: CIDCounter, result: import('./clock').EventData[]}>}
@@ -37720,7 +37728,7 @@ async function getAll (blocks, head) {
37720
37728
  }
37721
37729
 
37722
37730
  /**
37723
- * @param {import('./blockstore.js').TransactionBlockstore} blocks Bucket block storage.
37731
+ * @param {TransactionBlockstore} blocks Bucket block storage.
37724
37732
  * @param {import('./clock').EventLink<import('./clock').EventData>[]} head Merkle clock head.
37725
37733
  * @param {string} key The key of the value to retrieve.
37726
37734
  */
@@ -38002,6 +38010,22 @@ object.factory = function (codec) {
38002
38010
  exports.type = 'charwise';
38003
38011
  } (charwise));
38004
38012
 
38013
+ /* global localStorage */
38014
+ let storageSupported = false;
38015
+ try {
38016
+ storageSupported = window.localStorage && true;
38017
+ } catch (e) {}
38018
+ function localGet (key) {
38019
+ if (storageSupported) {
38020
+ return localStorage && localStorage.getItem(key)
38021
+ }
38022
+ }
38023
+ function localSet (key, value) {
38024
+ if (storageSupported) {
38025
+ return localStorage && localStorage.setItem(key, value)
38026
+ }
38027
+ }
38028
+
38005
38029
  // @ts-nocheck
38006
38030
 
38007
38031
  /**
@@ -38016,31 +38040,16 @@ object.factory = function (codec) {
38016
38040
  * @param {object} [authCtx] - Optional authorization context object to use for any authentication checks.
38017
38041
  *
38018
38042
  */
38019
- class Fireproof {
38043
+ class Database {
38020
38044
  listeners = new Set()
38021
38045
 
38022
- /**
38023
- * @function storage
38024
- * @memberof Fireproof
38025
- * Creates a new Fireproof instance with default storage settings
38026
- * Most apps should use this and not worry about the details.
38027
- * @static
38028
- * @returns {Fireproof} - a new Fireproof instance
38029
- */
38030
- static storage = (name = 'global') => {
38031
- const instanceKey = browserExports(32).toString('hex'); // pass null to disable encryption
38032
- // pick a random key from const validatedKeys
38033
- // const instanceKey = validatedKeys[Math.floor(Math.random() * validatedKeys.length)]
38034
- return new Fireproof(new TransactionBlockstore(name, instanceKey), [], { name })
38035
- }
38036
-
38037
- constructor (blocks, clock, config, authCtx = {}) {
38038
- this.name = config?.name || 'global';
38046
+ // todo refactor this for the next version
38047
+ constructor (blocks, clock, config = {}) {
38048
+ this.name = config.name;
38039
38049
  this.instanceId = `fp.${this.name}.${Math.random().toString(36).substring(2, 7)}`;
38040
38050
  this.blocks = blocks;
38041
38051
  this.clock = clock;
38042
38052
  this.config = config;
38043
- this.authCtx = authCtx;
38044
38053
  this.indexes = new Map();
38045
38054
  }
38046
38055
 
@@ -38055,7 +38064,7 @@ class Fireproof {
38055
38064
  return {
38056
38065
  clock: this.clockToJSON(),
38057
38066
  name: this.name,
38058
- key: this.blocks.valet.getKeyMaterial(),
38067
+ key: this.blocks.valet?.getKeyMaterial(),
38059
38068
  indexes: [...this.indexes.values()].map(index => index.toJSON())
38060
38069
  }
38061
38070
  }
@@ -38073,10 +38082,16 @@ class Fireproof {
38073
38082
  hydrate ({ clock, name, key }) {
38074
38083
  this.name = name;
38075
38084
  this.clock = clock;
38076
- this.blocks.valet.setKeyMaterial(key);
38085
+ this.blocks.valet?.setKeyMaterial(key);
38077
38086
  this.indexBlocks = null;
38078
38087
  }
38079
38088
 
38089
+ maybeSaveClock () {
38090
+ if (this.name && this.blocks.valet) {
38091
+ localSet('fp.' + this.name, JSON.stringify(this));
38092
+ }
38093
+ }
38094
+
38080
38095
  /**
38081
38096
  * Triggers a notification to all listeners
38082
38097
  * of the Fireproof instance so they can repaint UI, etc.
@@ -38187,14 +38202,19 @@ class Fireproof {
38187
38202
  doc._id = key;
38188
38203
  return doc
38189
38204
  }
38205
+ /**
38206
+ * @typedef {Object} Document
38207
+ * @property {string} _id - The ID of the document (required)
38208
+ * @property {string} [_proof] - The proof of the document (optional)
38209
+ * @property {string} [_clock] - The clock of the document (optional)
38210
+ * @property {any} [key: string] - Index signature notation to allow any other unknown fields
38211
+ * * @property {Object.<string, any>} [otherProperties] - Any other unknown properties (optional)
38212
+ */
38190
38213
 
38191
38214
  /**
38192
38215
  * Adds a new document to the database, or updates an existing document. Returns the ID of the document and the new clock head.
38193
38216
  *
38194
- * @param {Object} doc - the document to be added
38195
- * @param {string} doc._id - the document ID. If not provided, a random ID will be generated.
38196
- * @param {CID[]} doc._clock - the document ID. If not provided, a random ID will be generated.
38197
- * @param {Proof} doc._proof - CIDs referenced by the update
38217
+ * @param {Document} doc - the document to be added
38198
38218
  * @returns {Promise<{ id: string, clock: CID[] }>} - The result of adding the document to the database
38199
38219
  * @memberof Fireproof
38200
38220
  * @instance
@@ -38302,6 +38322,7 @@ class Fireproof {
38302
38322
 
38303
38323
  async notifyListeners (changes) {
38304
38324
  // await sleep(10)
38325
+ await this.maybeSaveClock();
38305
38326
  for (const listener of this.listeners) {
38306
38327
  await listener(changes);
38307
38328
  }
@@ -38314,8 +38335,7 @@ class Fireproof {
38314
38335
  }
38315
38336
 
38316
38337
  setRemoteBlockReader (remoteBlockReaderFn) {
38317
- // console.log('registering remote block reader')
38318
- this.blocks.valet.remoteBlockFunction = remoteBlockReaderFn;
38338
+ this.blocks.remoteBlockFunction = remoteBlockReaderFn;
38319
38339
  }
38320
38340
  }
38321
38341
 
@@ -38336,6 +38356,124 @@ function encodeEvent (event) {
38336
38356
  return { ...event, key: encodedKey }
38337
38357
  }
38338
38358
 
38359
+ /**
38360
+ * A Fireproof database Listener allows you to react to events in the database.
38361
+ *
38362
+ * @class Listener
38363
+ * @classdesc An listener attaches to a Fireproof database and runs a routing function on each change, sending the results to subscribers.
38364
+ *
38365
+ * @param {import('./database.js').Database} database - The Database database instance to index.
38366
+ * @param {Function} routingFn - The routing function to apply to each entry in the database.
38367
+ */
38368
+ // import { ChangeEvent } from './db-index'
38369
+
38370
+ class Listener {
38371
+ subcribers = new Map()
38372
+ doStopListening = null
38373
+
38374
+ /**
38375
+ * @param {import('./database.js').Database} database
38376
+ * @param {(_: any, emit: any) => void} routingFn
38377
+ */
38378
+ constructor (
38379
+ database,
38380
+ routingFn = function (/** @type {any} */ _, /** @type {(arg0: string) => void} */ emit) {
38381
+ emit('*');
38382
+ }
38383
+ ) {
38384
+ this.database = database;
38385
+ this.doStopListening = database.registerListener((/** @type {any} */ changes) => this.onChanges(changes));
38386
+ /**
38387
+ * The map function to apply to each entry in the database.
38388
+ * @type {Function}
38389
+ */
38390
+ this.routingFn = routingFn;
38391
+
38392
+ this.dbHead = null;
38393
+ }
38394
+
38395
+ /**
38396
+ * Subscribe to a topic emitted by the event function.
38397
+ * @param {string} topic - The topic to subscribe to.
38398
+ * @param {Function} subscriber - The function to call when the topic is emitted.
38399
+ * @returns {Function} A function to unsubscribe from the topic.
38400
+ * @memberof Listener
38401
+ * @instance
38402
+ * @param {any} since
38403
+ */
38404
+ on (topic, subscriber, since) {
38405
+ const listOfTopicSubscribers = getTopicList(this.subcribers, topic);
38406
+ listOfTopicSubscribers.push(subscriber);
38407
+ if (typeof since !== 'undefined') {
38408
+ this.database.changesSince(since).then(({ rows: changes }) => {
38409
+ const keys = topicsForChanges(changes, this.routingFn).get(topic);
38410
+ if (keys) keys.forEach((/** @type {any} */ key) => subscriber(key));
38411
+ });
38412
+ }
38413
+ return () => {
38414
+ const index = listOfTopicSubscribers.indexOf(subscriber);
38415
+ if (index > -1) listOfTopicSubscribers.splice(index, 1);
38416
+ }
38417
+ }
38418
+
38419
+ /**
38420
+ * @typedef {import('./db-index').ChangeEvent} ChangeEvent
38421
+ */
38422
+
38423
+ /**
38424
+ * @param {ChangeEvent[]} changes
38425
+ */
38426
+ onChanges (changes) {
38427
+ if (Array.isArray(changes)) {
38428
+ const seenTopics = topicsForChanges(changes, this.routingFn);
38429
+ for (const [topic, keys] of seenTopics) {
38430
+ const listOfTopicSubscribers = getTopicList(this.subcribers, topic);
38431
+ listOfTopicSubscribers.forEach((/** @type {(arg0: any) => any} */ subscriber) =>
38432
+ keys.forEach((/** @type {any} */ key) => subscriber(key))
38433
+ );
38434
+ }
38435
+ } else {
38436
+ // non-arrays go to all subscribers
38437
+ for (const [, listOfTopicSubscribers] of this.subcribers) {
38438
+ listOfTopicSubscribers.forEach((/** @type {(arg0: any) => any} */ subscriber) => subscriber(changes));
38439
+ }
38440
+ }
38441
+ }
38442
+ }
38443
+
38444
+ /**
38445
+ * @param {Map<any, any>} subscribersMap
38446
+ * @param {string} name
38447
+ */
38448
+ function getTopicList (subscribersMap, name) {
38449
+ let topicList = subscribersMap.get(name);
38450
+ if (!topicList) {
38451
+ topicList = [];
38452
+ subscribersMap.set(name, topicList);
38453
+ }
38454
+ return topicList
38455
+ }
38456
+
38457
+ /**
38458
+ * Transforms a set of changes to events using an emitter function.
38459
+ *
38460
+ * @param {ChangeEvent[]} changes
38461
+ * @param {Function} routingFn
38462
+ * @returns {Map<string,string[]>} The topics emmitted by the event function.
38463
+ * @private
38464
+ */
38465
+ const topicsForChanges = (changes, routingFn) => {
38466
+ const seenTopics = new Map();
38467
+ changes.forEach(({ key, value, del }) => {
38468
+ if (del || !value) value = { _deleted: true };
38469
+ routingFn({ _id: key, ...value }, (/** @type {any} */ t) => {
38470
+ const topicList = getTopicList(seenTopics, t);
38471
+ topicList.push(key);
38472
+ });
38473
+ });
38474
+ return seenTopics
38475
+ };
38476
+
38339
38477
  const compare$1 = (a, b) => {
38340
38478
  const [aKey, aRef] = a;
38341
38479
  const [bKey, bRef] = b;
@@ -38530,7 +38668,7 @@ const indexEntriesForChanges = (changes, mapFn) => {
38530
38668
  * @class DbIndex
38531
38669
  * @classdesc An DbIndex can be used to order and filter the documents in a Fireproof database.
38532
38670
  *
38533
- * @param {Fireproof} database - The Fireproof database instance to DbIndex.
38671
+ * @param {Database} database - The Fireproof database instance to DbIndex.
38534
38672
  * @param {Function} mapFn - The map function to apply to each entry in the database.
38535
38673
  *
38536
38674
  */
@@ -38538,7 +38676,7 @@ class DbIndex {
38538
38676
  constructor (database, mapFn, clock, opts = {}) {
38539
38677
  this.database = database;
38540
38678
  if (!database.indexBlocks) {
38541
- database.indexBlocks = new TransactionBlockstore(database.name + '.indexes', database.blocks.valet.getKeyMaterial());
38679
+ database.indexBlocks = new TransactionBlockstore(database?.name + '.indexes', database.blocks.valet?.getKeyMaterial());
38542
38680
  }
38543
38681
  /**
38544
38682
  * The map function to apply to each entry in the database.
@@ -38757,125 +38895,34 @@ async function doIndexQuery (blocks, indexByKey, query = {}) {
38757
38895
  }
38758
38896
  }
38759
38897
 
38760
- /**
38761
- * A Fireproof database Listener allows you to react to events in the database.
38762
- *
38763
- * @class Listener
38764
- * @classdesc An listener attaches to a Fireproof database and runs a routing function on each change, sending the results to subscribers.
38765
- *
38766
- * @param {import('./fireproof.js').Fireproof} database - The Fireproof database instance to index.
38767
- * @param {Function} routingFn - The routing function to apply to each entry in the database.
38768
- */
38769
- // import { ChangeEvent } from './db-index'
38770
-
38771
- class Listener {
38772
- subcribers = new Map()
38773
- doStopListening = null
38774
-
38775
- /**
38776
- * @param {import('./fireproof.js').Fireproof} database
38777
- * @param {(_: any, emit: any) => void} routingFn
38778
- */
38779
- constructor (database, routingFn) {
38780
- this.database = database;
38781
- this.doStopListening = database.registerListener((/** @type {any} */ changes) => this.onChanges(changes));
38782
- /**
38783
- * The map function to apply to each entry in the database.
38784
- * @type {Function}
38785
- */
38786
- this.routingFn =
38787
- routingFn ||
38788
- function (/** @type {any} */ _, /** @type {(arg0: string) => void} */ emit) {
38789
- emit('*');
38790
- };
38791
- this.dbHead = null;
38792
- }
38793
-
38794
- /**
38795
- * Subscribe to a topic emitted by the event function.
38796
- * @param {string} topic - The topic to subscribe to.
38797
- * @param {Function} subscriber - The function to call when the topic is emitted.
38798
- * @returns {Function} A function to unsubscribe from the topic.
38799
- * @memberof Listener
38800
- * @instance
38801
- * @param {any} since
38802
- */
38803
- on (topic, subscriber, since) {
38804
- const listOfTopicSubscribers = getTopicList(this.subcribers, topic);
38805
- listOfTopicSubscribers.push(subscriber);
38806
- if (typeof since !== 'undefined') {
38807
- this.database.changesSince(since).then(({ rows: changes }) => {
38808
- const keys = topicsForChanges(changes, this.routingFn).get(topic);
38809
- if (keys) keys.forEach((/** @type {any} */ key) => subscriber(key));
38810
- });
38811
- }
38812
- return () => {
38813
- const index = listOfTopicSubscribers.indexOf(subscriber);
38814
- if (index > -1) listOfTopicSubscribers.splice(index, 1);
38815
- }
38816
- }
38817
-
38818
- /**
38819
- * @typedef {import('./db-index').ChangeEvent} ChangeEvent
38820
- */
38898
+ const parseCID = cid => typeof cid === 'string' ? CID$1.parse(cid) : cid;
38821
38899
 
38900
+ class Fireproof {
38822
38901
  /**
38823
- * @param {ChangeEvent[]} changes
38902
+ * @function storage
38903
+ * @memberof Fireproof
38904
+ * Creates a new Fireproof instance with default storage settings
38905
+ * Most apps should use this and not worry about the details.
38906
+ * @static
38907
+ * @returns {Database} - a new Fireproof instance
38824
38908
  */
38825
- onChanges (changes) {
38826
- if (Array.isArray(changes)) {
38827
- const seenTopics = topicsForChanges(changes, this.routingFn);
38828
- for (const [topic, keys] of seenTopics) {
38829
- const listOfTopicSubscribers = getTopicList(this.subcribers, topic);
38830
- listOfTopicSubscribers.forEach((/** @type {(arg0: any) => any} */ subscriber) =>
38831
- keys.forEach((/** @type {any} */ key) => subscriber(key))
38832
- );
38909
+ static storage = (name = null, opts = {}) => {
38910
+ if (name) {
38911
+ opts.name = name;
38912
+ const existing = localGet('fp.' + name);
38913
+ if (existing) {
38914
+ const existingConfig = JSON.parse(existing);
38915
+ const fp = new Database(new TransactionBlockstore(name, existingConfig.key), [], opts);
38916
+ return this.fromJSON(existingConfig, fp)
38917
+ } else {
38918
+ const instanceKey = browserExports(32).toString('hex'); // pass null to disable encryption
38919
+ return new Database(new TransactionBlockstore(name, instanceKey), [], opts)
38833
38920
  }
38834
38921
  } else {
38835
- // non-arrays go to all subscribers
38836
- for (const [, listOfTopicSubscribers] of this.subcribers) {
38837
- listOfTopicSubscribers.forEach((/** @type {(arg0: any) => any} */ subscriber) => subscriber(changes));
38838
- }
38922
+ return new Database(new TransactionBlockstore(), [], opts)
38839
38923
  }
38840
38924
  }
38841
- }
38842
-
38843
- /**
38844
- * @param {Map<any, any>} subscribersMap
38845
- * @param {string} name
38846
- */
38847
- function getTopicList (subscribersMap, name) {
38848
- let topicList = subscribersMap.get(name);
38849
- if (!topicList) {
38850
- topicList = [];
38851
- subscribersMap.set(name, topicList);
38852
- }
38853
- return topicList
38854
- }
38855
-
38856
- /**
38857
- * Transforms a set of changes to events using an emitter function.
38858
- *
38859
- * @param {ChangeEvent[]} changes
38860
- * @param {Function} routingFn
38861
- * @returns {Map<string,string[]>} The topics emmitted by the event function.
38862
- * @private
38863
- */
38864
- const topicsForChanges = (changes, routingFn) => {
38865
- const seenTopics = new Map();
38866
- changes.forEach(({ key, value, del }) => {
38867
- if (del || !value) value = { _deleted: true };
38868
- routingFn({ _id: key, ...value }, (/** @type {any} */ t) => {
38869
- const topicList = getTopicList(seenTopics, t);
38870
- topicList.push(key);
38871
- });
38872
- });
38873
- return seenTopics
38874
- };
38875
-
38876
- const parseCID = cid => typeof cid === 'string' ? CID$1.parse(cid) : cid;
38877
38925
 
38878
- class Hydrator {
38879
38926
  static fromJSON (json, database) {
38880
38927
  database.hydrate({ clock: json.clock.map(c => parseCID(c)), name: json.name, key: json.key });
38881
38928
  if (json.indexes) {
@@ -38896,7 +38943,7 @@ class Hydrator {
38896
38943
 
38897
38944
  static snapshot (database, clock) {
38898
38945
  const definition = database.toJSON();
38899
- const withBlocks = new Fireproof(database.blocks);
38946
+ const withBlocks = new Database(database.blocks);
38900
38947
  if (clock) {
38901
38948
  definition.clock = clock.map(c => parseCID(c));
38902
38949
  definition.indexes.forEach(index => {
@@ -38925,7 +38972,6 @@ class Hydrator {
38925
38972
  }
38926
38973
 
38927
38974
  exports.Fireproof = Fireproof;
38928
- exports.Hydrator = Hydrator;
38929
38975
  exports.Index = DbIndex;
38930
38976
  exports.Listener = Listener;
38931
- //# sourceMappingURL=index.js.map
38977
+ //# sourceMappingURL=fireproof.js.map