@lark-sh/client 0.1.15 → 0.1.17

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.
@@ -1,67 +1,3 @@
1
- // src/protocol/constants.ts
2
- var OnDisconnectAction = {
3
- SET: "s",
4
- UPDATE: "u",
5
- DELETE: "d",
6
- CANCEL: "c"
7
- };
8
- var ErrorCode = {
9
- PERMISSION_DENIED: "permission_denied",
10
- INVALID_DATA: "invalid_data",
11
- NOT_FOUND: "not_found",
12
- INVALID_PATH: "invalid_path",
13
- INVALID_OPERATION: "invalid_operation",
14
- INTERNAL_ERROR: "internal_error",
15
- CONDITION_FAILED: "condition_failed",
16
- INVALID_QUERY: "invalid_query",
17
- AUTH_REQUIRED: "auth_required"
18
- };
19
- var DEFAULT_COORDINATOR_URL = "https://db.lark.sh";
20
-
21
- // src/connection/Coordinator.ts
22
- var Coordinator = class {
23
- constructor(baseUrl = DEFAULT_COORDINATOR_URL) {
24
- this.baseUrl = baseUrl.replace(/\/$/, "");
25
- }
26
- /**
27
- * Request connection details from the coordinator.
28
- *
29
- * @param database - Database ID in format "project/database"
30
- * @param options - Either a user token or anonymous flag
31
- * @returns Connection details including host, port, and connection token
32
- */
33
- async connect(database, options = {}) {
34
- const body = { database };
35
- if (options.token) {
36
- body.token = options.token;
37
- } else if (options.anonymous) {
38
- body.anonymous = true;
39
- } else {
40
- body.anonymous = true;
41
- }
42
- const response = await fetch(`${this.baseUrl}/connect`, {
43
- method: "POST",
44
- headers: {
45
- "Content-Type": "application/json"
46
- },
47
- body: JSON.stringify(body)
48
- });
49
- if (!response.ok) {
50
- let errorMessage = `Coordinator request failed: ${response.status}`;
51
- try {
52
- const errorBody = await response.json();
53
- if (errorBody.message) {
54
- errorMessage = errorBody.message;
55
- }
56
- } catch {
57
- }
58
- throw new Error(errorMessage);
59
- }
60
- const data = await response.json();
61
- return data;
62
- }
63
- };
64
-
65
1
  // src/connection/WebSocketTransport.ts
66
2
  import WebSocketNode from "ws";
67
3
  var WebSocketImpl = typeof WebSocket !== "undefined" ? WebSocket : WebSocketNode;
@@ -1428,7 +1364,6 @@ var View = class {
1428
1364
  /**
1429
1365
  * Check if this View loads all data (no limits, no range filters).
1430
1366
  * A View that loads all data can serve as a "complete" cache for child paths.
1431
- * This matches Firebase's loadsAllData() semantics.
1432
1367
  */
1433
1368
  loadsAllData() {
1434
1369
  if (!this.queryParams) return true;
@@ -1844,8 +1779,8 @@ var SubscriptionManager = class {
1844
1779
  /**
1845
1780
  * Detect and fire child_moved events for children that changed position OR sort value.
1846
1781
  *
1847
- * Firebase fires child_moved for ANY priority/sort value change, regardless of whether
1848
- * the position actually changes. This is Case 2003 behavior.
1782
+ * child_moved fires for ANY priority/sort value change, regardless of whether
1783
+ * the position actually changes.
1849
1784
  *
1850
1785
  * IMPORTANT: child_moved should only fire for children whose VALUE or PRIORITY changed.
1851
1786
  * Children that are merely "displaced" by another child moving should NOT fire child_moved.
@@ -2048,9 +1983,6 @@ var SubscriptionManager = class {
2048
1983
  * A complete View has no limits and no range filters, so it contains all data
2049
1984
  * for its subtree. Child subscriptions can use the ancestor's data instead
2050
1985
  * of creating their own server subscription.
2051
- *
2052
- * This matches Firebase's behavior where child listeners don't need their own
2053
- * server subscription if an ancestor has an unlimited listener.
2054
1986
  */
2055
1987
  hasAncestorCompleteView(path) {
2056
1988
  const normalized = normalizePath(path);
@@ -2176,8 +2108,8 @@ var SubscriptionManager = class {
2176
2108
  /**
2177
2109
  * Recursively sort object keys for consistent comparison.
2178
2110
  * This ensures {a:1, b:2} and {b:2, a:1} compare as equal.
2179
- * Uses simple alphabetical sorting (not Firebase key sorting) since we're
2180
- * just checking data equality, not display order.
2111
+ * Uses simple alphabetical sorting since we're just checking data equality,
2112
+ * not display order.
2181
2113
  */
2182
2114
  sortKeysForComparison(value) {
2183
2115
  if (value === null || typeof value !== "object") {
@@ -2771,6 +2703,26 @@ var SubscriptionManager = class {
2771
2703
  }
2772
2704
  };
2773
2705
 
2706
+ // src/protocol/constants.ts
2707
+ var OnDisconnectAction = {
2708
+ SET: "s",
2709
+ UPDATE: "u",
2710
+ DELETE: "d",
2711
+ CANCEL: "c"
2712
+ };
2713
+ var ErrorCode = {
2714
+ PERMISSION_DENIED: "permission_denied",
2715
+ INVALID_DATA: "invalid_data",
2716
+ NOT_FOUND: "not_found",
2717
+ INVALID_PATH: "invalid_path",
2718
+ INVALID_OPERATION: "invalid_operation",
2719
+ INTERNAL_ERROR: "internal_error",
2720
+ CONDITION_FAILED: "condition_failed",
2721
+ INVALID_QUERY: "invalid_query",
2722
+ AUTH_REQUIRED: "auth_required"
2723
+ };
2724
+ var DEFAULT_LARK_DOMAIN = "larkdb.net";
2725
+
2774
2726
  // src/OnDisconnect.ts
2775
2727
  var OnDisconnect = class {
2776
2728
  constructor(db, path) {
@@ -2874,6 +2826,125 @@ function generatePushId() {
2874
2826
  return id;
2875
2827
  }
2876
2828
 
2829
+ // src/utils/validation.ts
2830
+ var MAX_KEY_BYTES = 768;
2831
+ var INVALID_KEY_CHARS = /[.#$\[\]\/]/;
2832
+ var CONTROL_CHARS = /[\x00-\x1f\x7f]/;
2833
+ function hasControlChars(str) {
2834
+ return CONTROL_CHARS.test(str);
2835
+ }
2836
+ function getByteLength(str) {
2837
+ if (typeof TextEncoder !== "undefined") {
2838
+ return new TextEncoder().encode(str).length;
2839
+ }
2840
+ return Buffer.byteLength(str, "utf8");
2841
+ }
2842
+ function validateKey(key, context) {
2843
+ const prefix = context ? `${context}: ` : "";
2844
+ if (typeof key !== "string") {
2845
+ throw new LarkError(
2846
+ ErrorCode.INVALID_DATA,
2847
+ `${prefix}key must be a string`
2848
+ );
2849
+ }
2850
+ if (key.length === 0) {
2851
+ throw new LarkError(
2852
+ ErrorCode.INVALID_DATA,
2853
+ `${prefix}key cannot be empty`
2854
+ );
2855
+ }
2856
+ if (INVALID_KEY_CHARS.test(key)) {
2857
+ throw new LarkError(
2858
+ ErrorCode.INVALID_DATA,
2859
+ `${prefix}key "${key}" contains invalid characters (. $ # [ ] / are not allowed)`
2860
+ );
2861
+ }
2862
+ if (hasControlChars(key)) {
2863
+ throw new LarkError(
2864
+ ErrorCode.INVALID_DATA,
2865
+ `${prefix}key "${key}" contains control characters (ASCII 0-31, 127 are not allowed)`
2866
+ );
2867
+ }
2868
+ const byteLength = getByteLength(key);
2869
+ if (byteLength > MAX_KEY_BYTES) {
2870
+ throw new LarkError(
2871
+ ErrorCode.INVALID_DATA,
2872
+ `${prefix}key "${key.substring(0, 50)}..." exceeds maximum size of ${MAX_KEY_BYTES} bytes (got ${byteLength})`
2873
+ );
2874
+ }
2875
+ }
2876
+ function validatePath(path, context) {
2877
+ if (typeof path !== "string") {
2878
+ throw new LarkError(
2879
+ ErrorCode.INVALID_PATH,
2880
+ `${context ? `${context}: ` : ""}path must be a string`
2881
+ );
2882
+ }
2883
+ if (path === "" || path === "/") {
2884
+ return;
2885
+ }
2886
+ const segments = path.split("/").filter((s) => s.length > 0);
2887
+ for (const segment of segments) {
2888
+ if (segment === ".priority" || segment === ".value" || segment === ".sv" || segment === ".info") {
2889
+ continue;
2890
+ }
2891
+ validateKey(segment, context);
2892
+ }
2893
+ }
2894
+ function validateValue(value, context, path = "") {
2895
+ const prefix = context ? `${context}: ` : "";
2896
+ const location = path ? ` at "${path}"` : "";
2897
+ if (value === null || value === void 0) {
2898
+ return;
2899
+ }
2900
+ if (typeof value === "string") {
2901
+ if (hasControlChars(value)) {
2902
+ throw new LarkError(
2903
+ ErrorCode.INVALID_DATA,
2904
+ `${prefix}string value${location} contains control characters (ASCII 0-31, 127 are not allowed)`
2905
+ );
2906
+ }
2907
+ return;
2908
+ }
2909
+ if (typeof value === "number" || typeof value === "boolean") {
2910
+ return;
2911
+ }
2912
+ if (Array.isArray(value)) {
2913
+ for (let i = 0; i < value.length; i++) {
2914
+ validateValue(value[i], context, path ? `${path}/${i}` : String(i));
2915
+ }
2916
+ return;
2917
+ }
2918
+ if (typeof value === "object") {
2919
+ const obj = value;
2920
+ const keys = Object.keys(obj);
2921
+ if (".value" in obj) {
2922
+ const otherKeys = keys.filter((k) => k !== ".value" && k !== ".priority");
2923
+ if (otherKeys.length > 0) {
2924
+ const loc = path || "/";
2925
+ throw new LarkError(
2926
+ ErrorCode.INVALID_DATA,
2927
+ `${prefix}data at ${loc} contains ".value" alongside other children (${otherKeys.join(", ")}). ".value" can only be used with ".priority" for primitives with priority.`
2928
+ );
2929
+ }
2930
+ }
2931
+ for (const [key, childValue] of Object.entries(obj)) {
2932
+ if (key !== ".priority" && key !== ".value" && key !== ".sv") {
2933
+ validateKey(key, context);
2934
+ }
2935
+ validateValue(childValue, context, path ? `${path}/${key}` : key);
2936
+ }
2937
+ return;
2938
+ }
2939
+ throw new LarkError(
2940
+ ErrorCode.INVALID_DATA,
2941
+ `${prefix}invalid value type${location}: ${typeof value}`
2942
+ );
2943
+ }
2944
+ function validateWriteData(value, context) {
2945
+ validateValue(value, context);
2946
+ }
2947
+
2877
2948
  // src/DatabaseReference.ts
2878
2949
  function isInfoPath(path) {
2879
2950
  return path === "/.info" || path.startsWith("/.info/");
@@ -2949,7 +3020,6 @@ var DatabaseReference = class _DatabaseReference {
2949
3020
  * For queries (created via orderBy*, limitTo*, startAt, etc.), this returns
2950
3021
  * a reference to the same path without query constraints.
2951
3022
  * For non-query references, this returns the reference itself.
2952
- * This matches Firebase's Query.ref behavior.
2953
3023
  */
2954
3024
  get ref() {
2955
3025
  if (Object.keys(this._query).length === 0) {
@@ -2974,7 +3044,7 @@ var DatabaseReference = class _DatabaseReference {
2974
3044
  * Returns "default" for non-query references (no constraints).
2975
3045
  * Returns a sorted JSON string of wire-format params for queries.
2976
3046
  *
2977
- * This matches Firebase's queryIdentifier format for wire compatibility.
3047
+ * Used for wire protocol and subscription deduplication.
2978
3048
  */
2979
3049
  get queryIdentifier() {
2980
3050
  const queryObj = {};
@@ -3033,7 +3103,7 @@ var DatabaseReference = class _DatabaseReference {
3033
3103
  }
3034
3104
  /**
3035
3105
  * Get the data at this location. Alias for once('value').
3036
- * This is Firebase's newer API for reading data.
3106
+ * Alternative to once('value') for reading data.
3037
3107
  */
3038
3108
  async get() {
3039
3109
  return this.once("value");
@@ -3045,6 +3115,7 @@ var DatabaseReference = class _DatabaseReference {
3045
3115
  * Get a reference to a child path.
3046
3116
  */
3047
3117
  child(path) {
3118
+ validatePath(path, "child");
3048
3119
  const childPath = joinPath(this._path, path);
3049
3120
  return new _DatabaseReference(this._db, childPath);
3050
3121
  }
@@ -3061,6 +3132,7 @@ var DatabaseReference = class _DatabaseReference {
3061
3132
  */
3062
3133
  async set(value) {
3063
3134
  validateNotInfoPath(this._path, "set");
3135
+ validateWriteData(value, "set");
3064
3136
  if (this._db.isVolatilePath(this._path)) {
3065
3137
  this._db._sendVolatileSet(this._path, value);
3066
3138
  return;
@@ -3070,7 +3142,7 @@ var DatabaseReference = class _DatabaseReference {
3070
3142
  /**
3071
3143
  * Update specific children at this location without overwriting other children.
3072
3144
  *
3073
- * Also supports Firebase-style multi-path updates when keys look like paths
3145
+ * Also supports multi-path updates when keys look like paths
3074
3146
  * (start with '/'). In this mode, each path is written atomically as a transaction.
3075
3147
  *
3076
3148
  * @example
@@ -3090,6 +3162,16 @@ var DatabaseReference = class _DatabaseReference {
3090
3162
  */
3091
3163
  async update(values) {
3092
3164
  validateNotInfoPath(this._path, "update");
3165
+ for (const [key, value] of Object.entries(values)) {
3166
+ if (key.includes("/")) {
3167
+ validatePath(key, "update");
3168
+ } else {
3169
+ validateKey(key, "update");
3170
+ }
3171
+ if (value !== null) {
3172
+ validateWriteData(value, "update");
3173
+ }
3174
+ }
3093
3175
  const hasPathKeys = Object.keys(values).some((key) => key.startsWith("/"));
3094
3176
  if (hasPathKeys) {
3095
3177
  const ops = [];
@@ -3141,13 +3223,17 @@ var DatabaseReference = class _DatabaseReference {
3141
3223
  * For objects: injects `.priority` into the value object.
3142
3224
  * For primitives: wraps as `{ '.value': primitive, '.priority': priority }`.
3143
3225
  *
3144
- * This follows Firebase's wire format for primitives with priority.
3226
+ * Uses { '.value': value, '.priority': priority } format for the wire protocol.
3145
3227
  *
3146
3228
  * @param value - The value to write
3147
3229
  * @param priority - The priority for ordering
3148
3230
  */
3149
3231
  async setWithPriority(value, priority) {
3150
3232
  validateNotInfoPath(this._path, "setWithPriority");
3233
+ validateWriteData(value, "setWithPriority");
3234
+ if (typeof priority === "string") {
3235
+ validateWriteData(priority, "setWithPriority (priority)");
3236
+ }
3151
3237
  if (value === null || value === void 0) {
3152
3238
  await this._db._sendSet(this._path, value);
3153
3239
  return;
@@ -3689,17 +3775,11 @@ var DatabaseReference = class _DatabaseReference {
3689
3775
  }
3690
3776
  }
3691
3777
  /**
3692
- * Validate that a key is a valid Firebase key format.
3693
- * Invalid characters: . $ # [ ] /
3694
- * Also cannot start or end with .
3778
+ * Validate that a key is a valid key format.
3779
+ * Delegates to the shared validation utility.
3695
3780
  */
3696
3781
  _validateKeyFormat(methodName, key) {
3697
- if (/[.#$\[\]\/]/.test(key)) {
3698
- throw new LarkError(
3699
- ErrorCode.INVALID_QUERY,
3700
- `Query.${methodName}: invalid key "${key}" - keys cannot contain . # $ [ ] /`
3701
- );
3702
- }
3782
+ validateKey(key, `Query.${methodName}`);
3703
3783
  }
3704
3784
  // ============================================
3705
3785
  // Internal Helpers
@@ -3903,9 +3983,9 @@ var DataSnapshot = class _DataSnapshot {
3903
3983
  * Get a child snapshot at the specified path.
3904
3984
  *
3905
3985
  * Special handling:
3906
- * - `.priority` returns the priority value (Firebase compatible)
3986
+ * - `.priority` returns the priority value
3907
3987
  * - For wrapped primitives, only `.priority` returns data; other paths return null
3908
- * - Non-existent paths return a snapshot with val() === null (Firebase compatible)
3988
+ * - Non-existent paths return a snapshot with val() === null
3909
3989
  */
3910
3990
  child(path) {
3911
3991
  const childPath = joinPath(this._path, path);
@@ -4009,7 +4089,6 @@ var DataSnapshot = class _DataSnapshot {
4009
4089
  }
4010
4090
  /**
4011
4091
  * Check if this snapshot was from a volatile (high-frequency) update.
4012
- * This is a Lark extension not present in Firebase.
4013
4092
  */
4014
4093
  isVolatile() {
4015
4094
  return this._volatile;
@@ -4018,7 +4097,6 @@ var DataSnapshot = class _DataSnapshot {
4018
4097
  * Get the server timestamp for this snapshot (milliseconds since Unix epoch).
4019
4098
  * Only present on volatile value events. Use deltas between timestamps for
4020
4099
  * interpolation rather than absolute times to avoid clock sync issues.
4021
- * This is a Lark extension not present in Firebase.
4022
4100
  */
4023
4101
  getServerTimestamp() {
4024
4102
  return this._serverTimestamp;
@@ -4067,28 +4145,6 @@ function isVolatilePath(path, patterns) {
4067
4145
  }
4068
4146
 
4069
4147
  // src/LarkDatabase.ts
4070
- function validateWriteData(data, path = "") {
4071
- if (!data || typeof data !== "object" || Array.isArray(data)) {
4072
- return;
4073
- }
4074
- const obj = data;
4075
- const keys = Object.keys(obj);
4076
- if (".value" in obj) {
4077
- const otherKeys = keys.filter((k) => k !== ".value" && k !== ".priority");
4078
- if (otherKeys.length > 0) {
4079
- const location = path || "/";
4080
- throw new LarkError(
4081
- "invalid_data",
4082
- `Data at ${location} contains ".value" alongside other children (${otherKeys.join(", ")}). ".value" can only be used with ".priority" for primitives with priority.`
4083
- );
4084
- }
4085
- }
4086
- for (const key of keys) {
4087
- if (key !== ".priority" && key !== ".value") {
4088
- validateWriteData(obj[key], path ? `${path}/${key}` : `/${key}`);
4089
- }
4090
- }
4091
- }
4092
4148
  var RECONNECT_BASE_DELAY_MS = 1e3;
4093
4149
  var RECONNECT_MAX_DELAY_MS = 3e4;
4094
4150
  var RECONNECT_JITTER_FACTOR = 0.5;
@@ -4163,7 +4219,7 @@ var LarkDatabase = class {
4163
4219
  this._state = "disconnected";
4164
4220
  this._auth = null;
4165
4221
  this._databaseId = null;
4166
- this._coordinatorUrl = null;
4222
+ this._domain = null;
4167
4223
  this._volatilePaths = [];
4168
4224
  this._transportType = null;
4169
4225
  // Auth state
@@ -4226,8 +4282,9 @@ var LarkDatabase = class {
4226
4282
  * @internal Get the base URL for reference toString().
4227
4283
  */
4228
4284
  _getBaseUrl() {
4229
- if (this._coordinatorUrl && this._databaseId) {
4230
- return `${this._coordinatorUrl}/${this._databaseId}`;
4285
+ if (this._domain && this._databaseId) {
4286
+ const projectId = this._databaseId.split("/")[0];
4287
+ return `https://${projectId}.${this._domain}`;
4231
4288
  }
4232
4289
  return "lark://";
4233
4290
  }
@@ -4279,7 +4336,7 @@ var LarkDatabase = class {
4279
4336
  * Connect to a database.
4280
4337
  *
4281
4338
  * @param databaseId - Database ID in format "project/database"
4282
- * @param options - Connection options (token, anonymous, coordinator URL)
4339
+ * @param options - Connection options (token, anonymous, domain)
4283
4340
  */
4284
4341
  async connect(databaseId, options = {}) {
4285
4342
  if (this._state !== "disconnected") {
@@ -4303,19 +4360,17 @@ var LarkDatabase = class {
4303
4360
  const previousState = this._state;
4304
4361
  this._state = isReconnect ? "reconnecting" : "connecting";
4305
4362
  this._databaseId = databaseId;
4306
- this._coordinatorUrl = options.coordinator || DEFAULT_COORDINATOR_URL;
4363
+ this._domain = options.domain || DEFAULT_LARK_DOMAIN;
4307
4364
  if (!isReconnect) {
4308
4365
  this._currentToken = options.token || "";
4309
4366
  this._isAnonymous = !options.token && options.anonymous !== false;
4310
4367
  }
4311
4368
  try {
4312
- const coordinatorUrl = this._coordinatorUrl;
4313
- const coordinator = new Coordinator(coordinatorUrl);
4314
- const connectResponse = await coordinator.connect(databaseId, {
4315
- token: options.token,
4316
- anonymous: options.anonymous
4317
- });
4318
- const wsUrl = connectResponse.ws_url;
4369
+ const projectId = databaseId.split("/")[0];
4370
+ if (!projectId) {
4371
+ throw new Error('Invalid database ID: must be in format "projectId/databaseName"');
4372
+ }
4373
+ const wsUrl = `wss://${projectId}.${this._domain}/ws`;
4319
4374
  const transportResult = await createTransport(
4320
4375
  wsUrl,
4321
4376
  {
@@ -4476,7 +4531,7 @@ var LarkDatabase = class {
4476
4531
  this._auth = null;
4477
4532
  this._databaseId = null;
4478
4533
  this._volatilePaths = [];
4479
- this._coordinatorUrl = null;
4534
+ this._domain = null;
4480
4535
  this._connectionId = null;
4481
4536
  this._connectOptions = null;
4482
4537
  this._transportType = null;
@@ -4673,7 +4728,7 @@ var LarkDatabase = class {
4673
4728
  *
4674
4729
  * Supports two syntaxes:
4675
4730
  *
4676
- * **Object syntax** (like Firebase multi-path update):
4731
+ * **Object syntax** (multi-path update):
4677
4732
  * ```javascript
4678
4733
  * await db.transaction({
4679
4734
  * '/users/alice/name': 'Alice',
@@ -4711,14 +4766,20 @@ var LarkDatabase = class {
4711
4766
  */
4712
4767
  async convertToTxOp(op) {
4713
4768
  const path = normalizePath(op.path) || "/";
4769
+ validatePath(op.path, "transaction");
4714
4770
  switch (op.op) {
4715
4771
  case "set":
4772
+ validateWriteData(op.value, "transaction");
4716
4773
  return { o: "s", p: path, v: op.value };
4717
4774
  case "update":
4775
+ validateWriteData(op.value, "transaction");
4718
4776
  return { o: "u", p: path, v: op.value };
4719
4777
  case "delete":
4720
4778
  return { o: "d", p: path };
4721
4779
  case "condition":
4780
+ if (op.value !== void 0 && op.value !== null) {
4781
+ validateWriteData(op.value, "transaction condition");
4782
+ }
4722
4783
  if (isPrimitive(op.value)) {
4723
4784
  return { o: "c", p: path, v: op.value };
4724
4785
  } else {
@@ -4736,10 +4797,12 @@ var LarkDatabase = class {
4736
4797
  convertObjectToTxOps(obj) {
4737
4798
  const ops = [];
4738
4799
  for (const [path, value] of Object.entries(obj)) {
4800
+ validatePath(path, "transaction");
4739
4801
  const normalizedPath = normalizePath(path) || "/";
4740
4802
  if (value === null) {
4741
4803
  ops.push({ o: "d", p: normalizedPath });
4742
4804
  } else {
4805
+ validateWriteData(value, "transaction");
4743
4806
  ops.push({ o: "s", p: normalizedPath, v: value });
4744
4807
  }
4745
4808
  }
@@ -5285,4 +5348,4 @@ export {
5285
5348
  ServerValue,
5286
5349
  LarkDatabase
5287
5350
  };
5288
- //# sourceMappingURL=chunk-EK7OYLDG.mjs.map
5351
+ //# sourceMappingURL=chunk-VLAAYUVX.mjs.map