@lark-sh/client 0.1.15 → 0.1.16

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.
@@ -72,18 +72,10 @@ var Coordinator = class {
72
72
  * Request connection details from the coordinator.
73
73
  *
74
74
  * @param database - Database ID in format "project/database"
75
- * @param options - Either a user token or anonymous flag
76
- * @returns Connection details including host, port, and connection token
75
+ * @returns Connection details including WebSocket URL and UDP host/port
77
76
  */
78
- async connect(database, options = {}) {
77
+ async connect(database) {
79
78
  const body = { database };
80
- if (options.token) {
81
- body.token = options.token;
82
- } else if (options.anonymous) {
83
- body.anonymous = true;
84
- } else {
85
- body.anonymous = true;
86
- }
87
79
  const response = await fetch(`${this.baseUrl}/connect`, {
88
80
  method: "POST",
89
81
  headers: {
@@ -2919,6 +2911,125 @@ function generatePushId() {
2919
2911
  return id;
2920
2912
  }
2921
2913
 
2914
+ // src/utils/validation.ts
2915
+ var MAX_KEY_BYTES = 768;
2916
+ var INVALID_KEY_CHARS = /[.#$\[\]\/]/;
2917
+ var CONTROL_CHARS = /[\x00-\x1f\x7f]/;
2918
+ function hasControlChars(str) {
2919
+ return CONTROL_CHARS.test(str);
2920
+ }
2921
+ function getByteLength(str) {
2922
+ if (typeof TextEncoder !== "undefined") {
2923
+ return new TextEncoder().encode(str).length;
2924
+ }
2925
+ return Buffer.byteLength(str, "utf8");
2926
+ }
2927
+ function validateKey(key, context) {
2928
+ const prefix = context ? `${context}: ` : "";
2929
+ if (typeof key !== "string") {
2930
+ throw new LarkError(
2931
+ ErrorCode.INVALID_DATA,
2932
+ `${prefix}key must be a string`
2933
+ );
2934
+ }
2935
+ if (key.length === 0) {
2936
+ throw new LarkError(
2937
+ ErrorCode.INVALID_DATA,
2938
+ `${prefix}key cannot be empty`
2939
+ );
2940
+ }
2941
+ if (INVALID_KEY_CHARS.test(key)) {
2942
+ throw new LarkError(
2943
+ ErrorCode.INVALID_DATA,
2944
+ `${prefix}key "${key}" contains invalid characters (. $ # [ ] / are not allowed)`
2945
+ );
2946
+ }
2947
+ if (hasControlChars(key)) {
2948
+ throw new LarkError(
2949
+ ErrorCode.INVALID_DATA,
2950
+ `${prefix}key "${key}" contains control characters (ASCII 0-31, 127 are not allowed)`
2951
+ );
2952
+ }
2953
+ const byteLength = getByteLength(key);
2954
+ if (byteLength > MAX_KEY_BYTES) {
2955
+ throw new LarkError(
2956
+ ErrorCode.INVALID_DATA,
2957
+ `${prefix}key "${key.substring(0, 50)}..." exceeds maximum size of ${MAX_KEY_BYTES} bytes (got ${byteLength})`
2958
+ );
2959
+ }
2960
+ }
2961
+ function validatePath(path, context) {
2962
+ if (typeof path !== "string") {
2963
+ throw new LarkError(
2964
+ ErrorCode.INVALID_PATH,
2965
+ `${context ? `${context}: ` : ""}path must be a string`
2966
+ );
2967
+ }
2968
+ if (path === "" || path === "/") {
2969
+ return;
2970
+ }
2971
+ const segments = path.split("/").filter((s) => s.length > 0);
2972
+ for (const segment of segments) {
2973
+ if (segment === ".priority" || segment === ".value" || segment === ".sv" || segment === ".info") {
2974
+ continue;
2975
+ }
2976
+ validateKey(segment, context);
2977
+ }
2978
+ }
2979
+ function validateValue(value, context, path = "") {
2980
+ const prefix = context ? `${context}: ` : "";
2981
+ const location = path ? ` at "${path}"` : "";
2982
+ if (value === null || value === void 0) {
2983
+ return;
2984
+ }
2985
+ if (typeof value === "string") {
2986
+ if (hasControlChars(value)) {
2987
+ throw new LarkError(
2988
+ ErrorCode.INVALID_DATA,
2989
+ `${prefix}string value${location} contains control characters (ASCII 0-31, 127 are not allowed)`
2990
+ );
2991
+ }
2992
+ return;
2993
+ }
2994
+ if (typeof value === "number" || typeof value === "boolean") {
2995
+ return;
2996
+ }
2997
+ if (Array.isArray(value)) {
2998
+ for (let i = 0; i < value.length; i++) {
2999
+ validateValue(value[i], context, path ? `${path}/${i}` : String(i));
3000
+ }
3001
+ return;
3002
+ }
3003
+ if (typeof value === "object") {
3004
+ const obj = value;
3005
+ const keys = Object.keys(obj);
3006
+ if (".value" in obj) {
3007
+ const otherKeys = keys.filter((k) => k !== ".value" && k !== ".priority");
3008
+ if (otherKeys.length > 0) {
3009
+ const loc = path || "/";
3010
+ throw new LarkError(
3011
+ ErrorCode.INVALID_DATA,
3012
+ `${prefix}data at ${loc} contains ".value" alongside other children (${otherKeys.join(", ")}). ".value" can only be used with ".priority" for primitives with priority.`
3013
+ );
3014
+ }
3015
+ }
3016
+ for (const [key, childValue] of Object.entries(obj)) {
3017
+ if (key !== ".priority" && key !== ".value" && key !== ".sv") {
3018
+ validateKey(key, context);
3019
+ }
3020
+ validateValue(childValue, context, path ? `${path}/${key}` : key);
3021
+ }
3022
+ return;
3023
+ }
3024
+ throw new LarkError(
3025
+ ErrorCode.INVALID_DATA,
3026
+ `${prefix}invalid value type${location}: ${typeof value}`
3027
+ );
3028
+ }
3029
+ function validateWriteData(value, context) {
3030
+ validateValue(value, context);
3031
+ }
3032
+
2922
3033
  // src/DatabaseReference.ts
2923
3034
  function isInfoPath(path) {
2924
3035
  return path === "/.info" || path.startsWith("/.info/");
@@ -3090,6 +3201,7 @@ var DatabaseReference = class _DatabaseReference {
3090
3201
  * Get a reference to a child path.
3091
3202
  */
3092
3203
  child(path) {
3204
+ validatePath(path, "child");
3093
3205
  const childPath = joinPath(this._path, path);
3094
3206
  return new _DatabaseReference(this._db, childPath);
3095
3207
  }
@@ -3106,6 +3218,7 @@ var DatabaseReference = class _DatabaseReference {
3106
3218
  */
3107
3219
  async set(value) {
3108
3220
  validateNotInfoPath(this._path, "set");
3221
+ validateWriteData(value, "set");
3109
3222
  if (this._db.isVolatilePath(this._path)) {
3110
3223
  this._db._sendVolatileSet(this._path, value);
3111
3224
  return;
@@ -3135,6 +3248,16 @@ var DatabaseReference = class _DatabaseReference {
3135
3248
  */
3136
3249
  async update(values) {
3137
3250
  validateNotInfoPath(this._path, "update");
3251
+ for (const [key, value] of Object.entries(values)) {
3252
+ if (key.includes("/")) {
3253
+ validatePath(key, "update");
3254
+ } else {
3255
+ validateKey(key, "update");
3256
+ }
3257
+ if (value !== null) {
3258
+ validateWriteData(value, "update");
3259
+ }
3260
+ }
3138
3261
  const hasPathKeys = Object.keys(values).some((key) => key.startsWith("/"));
3139
3262
  if (hasPathKeys) {
3140
3263
  const ops = [];
@@ -3193,6 +3316,10 @@ var DatabaseReference = class _DatabaseReference {
3193
3316
  */
3194
3317
  async setWithPriority(value, priority) {
3195
3318
  validateNotInfoPath(this._path, "setWithPriority");
3319
+ validateWriteData(value, "setWithPriority");
3320
+ if (typeof priority === "string") {
3321
+ validateWriteData(priority, "setWithPriority (priority)");
3322
+ }
3196
3323
  if (value === null || value === void 0) {
3197
3324
  await this._db._sendSet(this._path, value);
3198
3325
  return;
@@ -3734,17 +3861,11 @@ var DatabaseReference = class _DatabaseReference {
3734
3861
  }
3735
3862
  }
3736
3863
  /**
3737
- * Validate that a key is a valid Firebase key format.
3738
- * Invalid characters: . $ # [ ] /
3739
- * Also cannot start or end with .
3864
+ * Validate that a key is a valid key format.
3865
+ * Delegates to the shared validation utility.
3740
3866
  */
3741
3867
  _validateKeyFormat(methodName, key) {
3742
- if (/[.#$\[\]\/]/.test(key)) {
3743
- throw new LarkError(
3744
- ErrorCode.INVALID_QUERY,
3745
- `Query.${methodName}: invalid key "${key}" - keys cannot contain . # $ [ ] /`
3746
- );
3747
- }
3868
+ validateKey(key, `Query.${methodName}`);
3748
3869
  }
3749
3870
  // ============================================
3750
3871
  // Internal Helpers
@@ -4112,28 +4233,6 @@ function isVolatilePath(path, patterns) {
4112
4233
  }
4113
4234
 
4114
4235
  // src/LarkDatabase.ts
4115
- function validateWriteData(data, path = "") {
4116
- if (!data || typeof data !== "object" || Array.isArray(data)) {
4117
- return;
4118
- }
4119
- const obj = data;
4120
- const keys = Object.keys(obj);
4121
- if (".value" in obj) {
4122
- const otherKeys = keys.filter((k) => k !== ".value" && k !== ".priority");
4123
- if (otherKeys.length > 0) {
4124
- const location = path || "/";
4125
- throw new LarkError(
4126
- "invalid_data",
4127
- `Data at ${location} contains ".value" alongside other children (${otherKeys.join(", ")}). ".value" can only be used with ".priority" for primitives with priority.`
4128
- );
4129
- }
4130
- }
4131
- for (const key of keys) {
4132
- if (key !== ".priority" && key !== ".value") {
4133
- validateWriteData(obj[key], path ? `${path}/${key}` : `/${key}`);
4134
- }
4135
- }
4136
- }
4137
4236
  var RECONNECT_BASE_DELAY_MS = 1e3;
4138
4237
  var RECONNECT_MAX_DELAY_MS = 3e4;
4139
4238
  var RECONNECT_JITTER_FACTOR = 0.5;
@@ -4356,10 +4455,7 @@ var LarkDatabase = class {
4356
4455
  try {
4357
4456
  const coordinatorUrl = this._coordinatorUrl;
4358
4457
  const coordinator = new Coordinator(coordinatorUrl);
4359
- const connectResponse = await coordinator.connect(databaseId, {
4360
- token: options.token,
4361
- anonymous: options.anonymous
4362
- });
4458
+ const connectResponse = await coordinator.connect(databaseId);
4363
4459
  const wsUrl = connectResponse.ws_url;
4364
4460
  const transportResult = await createTransport(
4365
4461
  wsUrl,
@@ -4756,14 +4852,20 @@ var LarkDatabase = class {
4756
4852
  */
4757
4853
  async convertToTxOp(op) {
4758
4854
  const path = normalizePath(op.path) || "/";
4855
+ validatePath(op.path, "transaction");
4759
4856
  switch (op.op) {
4760
4857
  case "set":
4858
+ validateWriteData(op.value, "transaction");
4761
4859
  return { o: "s", p: path, v: op.value };
4762
4860
  case "update":
4861
+ validateWriteData(op.value, "transaction");
4763
4862
  return { o: "u", p: path, v: op.value };
4764
4863
  case "delete":
4765
4864
  return { o: "d", p: path };
4766
4865
  case "condition":
4866
+ if (op.value !== void 0 && op.value !== null) {
4867
+ validateWriteData(op.value, "transaction condition");
4868
+ }
4767
4869
  if (isPrimitive(op.value)) {
4768
4870
  return { o: "c", p: path, v: op.value };
4769
4871
  } else {
@@ -4781,10 +4883,12 @@ var LarkDatabase = class {
4781
4883
  convertObjectToTxOps(obj) {
4782
4884
  const ops = [];
4783
4885
  for (const [path, value] of Object.entries(obj)) {
4886
+ validatePath(path, "transaction");
4784
4887
  const normalizedPath = normalizePath(path) || "/";
4785
4888
  if (value === null) {
4786
4889
  ops.push({ o: "d", p: normalizedPath });
4787
4890
  } else {
4891
+ validateWriteData(value, "transaction");
4788
4892
  ops.push({ o: "s", p: normalizedPath, v: value });
4789
4893
  }
4790
4894
  }