@sproutboat/runtime 0.34.4 → 0.34.5

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @sproutboat/runtime
2
2
 
3
+ ## 0.34.5
4
+
5
+ ### Patch Changes
6
+
7
+ - e62b600: The client address is folded to plain IPv4 once per distinct address instead of on every request: a request read `x-sb-remote-addr` and, for an IPv4-mapped IPv6 peer, lowercased, split and hex-parsed it each time. The last address and its result are kept; a different address takes the same path as before.
8
+ - 0b12b94: A strict D1 query hands its rows to JavaScript with fewer copies: the text of every cell goes into one arena instead of one `malloc` per cell, and a cell that is plain ASCII becomes its JS string in one copy (it used to go through a UTF-16 buffer, an 8-bit buffer and a third copy, three `malloc`s each). Non-ASCII text still takes the old path, so the strings are unchanged.
9
+
3
10
  ## 0.34.4
4
11
 
5
12
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutboat/runtime",
3
- "version": "0.34.4",
3
+ "version": "0.34.5",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -7959,8 +7959,19 @@ function __sbParseHex(s) {
7959
7959
  // uWS hands back IPv4-mapped IPv6 for IPv4 clients on a dual-stack socket
7960
7960
  // (`::ffff:1.2.3.4`, or fully expanded `0:0:0:0:0:ffff:0102:0304`). Fold those
7961
7961
  // to the dotted IPv4 so CIDR matching and `clientIp` see a plain address.
7962
+ // The last address folded and its result: a client keeps the same address from one request to the
7963
+ // next, and folding an IPv6 text costs a lowercase, a split and eight hex parses.
7964
+ let __sbIpRaw = "";
7965
+ let __sbIpFolded = "";
7962
7966
  function __sbNormalizeIp(ip) {
7963
7967
  const s = String(ip || "");
7968
+ if (s === __sbIpRaw) return __sbIpFolded;
7969
+ const folded = __sbFoldIp(s);
7970
+ __sbIpRaw = s;
7971
+ __sbIpFolded = folded;
7972
+ return folded;
7973
+ }
7974
+ function __sbFoldIp(s) {
7964
7975
  if (s.indexOf(":") === -1) return s;
7965
7976
  const low = s.toLowerCase();
7966
7977
  const dotted = low.indexOf("::ffff:");
@@ -2162,13 +2162,33 @@ static char* sb_sql_error(sqlite3* db, const char* message, int code, int skip)
2162
2162
  // A strict D1 query can hand its rows back as cells instead of JSON text: the caller turns
2163
2163
  // them into JS arrays, with no JSON written here and none parsed there. t is 0 NULL,
2164
2164
  // 1 number, 2 TEXT (UTF-8 in p), 3 BLOB (bytes in p).
2165
- typedef struct { unsigned char t; double d; char* p; int n; } sb_cell;
2166
- typedef struct { sb_cell* c; size_t n; size_t cap; int ncol; int used; } sb_cells;
2165
+ // The text and blob bytes of every cell share one arena (a cell holds an offset into it), and a
2166
+ // TEXT cell says whether it is all ASCII: that one becomes a JS string with a single copy.
2167
+ typedef struct { unsigned char t; unsigned char ascii; double d; size_t off; int n; } sb_cell;
2168
+ typedef struct { sb_cell* c; size_t n; size_t cap; int ncol; int used; char* a; size_t alen; size_t acap; } sb_cells;
2167
2169
  static void sb_cells_free(sb_cells* cs) {
2168
2170
  if (!cs) return;
2169
- for (size_t i = 0; i < cs->n; i++) free(cs->c[i].p);
2170
2171
  free(cs->c);
2171
- cs->c = 0; cs->n = cs->cap = 0;
2172
+ free(cs->a);
2173
+ cs->c = 0; cs->a = 0; cs->n = cs->cap = cs->alen = cs->acap = 0;
2174
+ }
2175
+ // Copies n bytes into the arena and returns their offset, or (size_t)-1 when out of memory.
2176
+ static size_t sb_cells_bytes(sb_cells* cs, const unsigned char* src, size_t n, unsigned char* ascii) {
2177
+ if (cs->alen + n + 1 > cs->acap) {
2178
+ size_t cap = cs->acap ? cs->acap * 2 : 1024;
2179
+ while (cap < cs->alen + n + 1) cap *= 2;
2180
+ char* grown = (char*)realloc(cs->a, cap);
2181
+ if (!grown) return (size_t)-1;
2182
+ cs->a = grown; cs->acap = cap;
2183
+ }
2184
+ const size_t off = cs->alen;
2185
+ unsigned char high = 0;
2186
+ for (size_t i = 0; i < n; i++) high |= src[i];
2187
+ if (n) memcpy(cs->a + off, src, n);
2188
+ cs->a[off + n] = 0;
2189
+ cs->alen = off + n + 1;
2190
+ *ascii = high < 0x80;
2191
+ return off;
2172
2192
  }
2173
2193
  static sb_cell* sb_cells_add(sb_cells* cs) {
2174
2194
  if (cs->n == cs->cap) {
@@ -2178,7 +2198,7 @@ static sb_cell* sb_cells_add(sb_cells* cs) {
2178
2198
  cs->c = grown; cs->cap = cap;
2179
2199
  }
2180
2200
  sb_cell* cell = &cs->c[cs->n++];
2181
- cell->t = 0; cell->d = 0; cell->p = 0; cell->n = 0;
2201
+ cell->t = 0; cell->ascii = 0; cell->d = 0; cell->off = 0; cell->n = 0;
2182
2202
  return cell;
2183
2203
  }
2184
2204
  static char* sb_sql_run_x(const char* path, const char* sql, const char* params, sb_cells* cells) {
@@ -2226,10 +2246,8 @@ static char* sb_sql_run_x(const char* path, const char* sql, const char* params,
2226
2246
  int n = sqlite3_column_bytes(st, i);
2227
2247
  cell->t = t == 4 ? 3 : 2;
2228
2248
  cell->n = txt ? n : 0;
2229
- cell->p = (char*)malloc((size_t)cell->n + 1);
2230
- if (!cell->p) { cells->used = -1; break; }
2231
- if (cell->n) memcpy(cell->p, txt, (size_t)cell->n);
2232
- cell->p[cell->n] = 0;
2249
+ cell->off = sb_cells_bytes(cells, txt, (size_t)cell->n, &cell->ascii);
2250
+ if (cell->off == (size_t)-1) { cells->used = -1; break; }
2233
2251
  }
2234
2252
  if (cells->used < 0) break;
2235
2253
  continue;
@@ -4261,12 +4279,14 @@ function __sbSqlRowsRaw(path, sql, paramsJson) {
4261
4279
  const sb_cell* __cell = &__cells.c[__r * __ncol + __c];
4262
4280
  jsval __v = porf_box(0.0, 7);
4263
4281
  if (__cell->t == 1) __v = porf_box_num(__cell->d);
4264
- else if (__cell->t == 2) {
4282
+ else if (__cell->t == 2 && __cell->ascii) {
4283
+ __v = porf_box((f64)porf_native_fetch_alloc_bytestring(__cells.a + __cell->off, (size_t)__cell->n), 195);
4284
+ } else if (__cell->t == 2) {
4265
4285
  const size_t __n = (size_t)__cell->n;
4266
4286
  uint16_t* __u = (uint16_t*)malloc(__n * 2 + 2);
4267
4287
  if (__u) {
4268
4288
  int __wide = 0;
4269
- const size_t __k = sb_text_units((const unsigned char*)__cell->p, __n, __u, &__wide);
4289
+ const size_t __k = sb_text_units((const unsigned char*)(__cells.a + __cell->off), __n, __u, &__wide);
4270
4290
  if (__wide) {
4271
4291
  const u32 __str = porf_native_fetch_alloc((u32)(4 + __k * 2), 67);
4272
4292
  *(u32*)(MEM + __str) = (u32)__k;
@@ -4284,7 +4304,7 @@ function __sbSqlRowsRaw(path, sql, paramsJson) {
4284
4304
  }
4285
4305
  } else if (__cell->t == 3) {
4286
4306
  const u32 __bytes = porf_arr_new((i32)__cell->n, (i32)__cell->n);
4287
- for (int __i = 0; __i < __cell->n; __i++) porf_arr_set(__bytes, (u32)__i, porf_box_num((f64)(unsigned char)__cell->p[__i]));
4307
+ for (int __i = 0; __i < __cell->n; __i++) porf_arr_set(__bytes, (u32)__i, porf_box_num((f64)(unsigned char)__cells.a[__cell->off + __i]));
4288
4308
  __v = porf_box((f64)__bytes, 72);
4289
4309
  }
4290
4310
  porf_arr_set(__row, (u32)__c, __v);
package/test/native-d1.ts CHANGED
@@ -67,12 +67,12 @@ int main(void) {
67
67
  else if (x->t == 1) printf("%.17g", x->d);
68
68
  else if (x->t == 2) {
69
69
  uint16_t* u = (uint16_t*)malloc((size_t)x->n * 2 + 2); int wide = 0;
70
- size_t k = sb_text_units((const unsigned char*)x->p, (size_t)x->n, u, &wide);
70
+ size_t k = sb_text_units((const unsigned char*)(cs.a + x->off), (size_t)x->n, u, &wide);
71
71
  printf("\\"");
72
72
  for (size_t i = 0; i < k; i++) printf("\\\\u%04x", u[i]);
73
73
  printf("\\"");
74
74
  free(u);
75
- } else { printf("{\\"$blob\\":\\""); for (int i = 0; i < x->n; i++) printf("%02x", (unsigned char)x->p[i]); printf("\\"}"); }
75
+ } else { printf("{\\"$blob\\":\\""); for (int i = 0; i < x->n; i++) printf("%02x", (unsigned char)cs.a[x->off + i]); printf("\\"}"); }
76
76
  }
77
77
  printf("]");
78
78
  }