@photostructure/sqlite 0.0.1 → 0.2.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +38 -2
  2. package/README.md +47 -483
  3. package/SECURITY.md +27 -83
  4. package/binding.gyp +69 -22
  5. package/dist/index.cjs +185 -18
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +552 -100
  8. package/dist/index.d.mts +552 -100
  9. package/dist/index.d.ts +552 -100
  10. package/dist/index.mjs +183 -18
  11. package/dist/index.mjs.map +1 -1
  12. package/package.json +51 -41
  13. package/prebuilds/darwin-arm64/@photostructure+sqlite.glibc.node +0 -0
  14. package/prebuilds/darwin-x64/@photostructure+sqlite.glibc.node +0 -0
  15. package/prebuilds/linux-arm64/@photostructure+sqlite.glibc.node +0 -0
  16. package/prebuilds/linux-arm64/@photostructure+sqlite.musl.node +0 -0
  17. package/prebuilds/linux-x64/@photostructure+sqlite.glibc.node +0 -0
  18. package/prebuilds/linux-x64/@photostructure+sqlite.musl.node +0 -0
  19. package/prebuilds/test_extension.so +0 -0
  20. package/prebuilds/win32-arm64/@photostructure+sqlite.glibc.node +0 -0
  21. package/prebuilds/win32-x64/@photostructure+sqlite.glibc.node +0 -0
  22. package/src/aggregate_function.cpp +503 -235
  23. package/src/aggregate_function.h +57 -42
  24. package/src/binding.cpp +117 -14
  25. package/src/dirname.ts +1 -1
  26. package/src/index.ts +122 -332
  27. package/src/lru-cache.ts +84 -0
  28. package/src/shims/env-inl.h +6 -15
  29. package/src/shims/node_errors.h +7 -1
  30. package/src/shims/sqlite_errors.h +168 -0
  31. package/src/shims/util.h +29 -4
  32. package/src/sql-tag-store.ts +140 -0
  33. package/src/sqlite_exception.h +49 -0
  34. package/src/sqlite_impl.cpp +736 -129
  35. package/src/sqlite_impl.h +84 -6
  36. package/src/{stack_path.ts → stack-path.ts} +7 -1
  37. package/src/types/aggregate-options.ts +22 -0
  38. package/src/types/changeset-apply-options.ts +18 -0
  39. package/src/types/database-sync-instance.ts +203 -0
  40. package/src/types/database-sync-options.ts +69 -0
  41. package/src/types/session-options.ts +10 -0
  42. package/src/types/sql-tag-store-instance.ts +51 -0
  43. package/src/types/sqlite-authorization-actions.ts +77 -0
  44. package/src/types/sqlite-authorization-results.ts +15 -0
  45. package/src/types/sqlite-changeset-conflict-types.ts +19 -0
  46. package/src/types/sqlite-changeset-resolution.ts +15 -0
  47. package/src/types/sqlite-open-flags.ts +50 -0
  48. package/src/types/statement-sync-instance.ts +73 -0
  49. package/src/types/user-functions-options.ts +14 -0
  50. package/src/upstream/node_sqlite.cc +960 -259
  51. package/src/upstream/node_sqlite.h +127 -2
  52. package/src/upstream/sqlite.js +1 -14
  53. package/src/upstream/sqlite3.c +4510 -1411
  54. package/src/upstream/sqlite3.h +390 -195
  55. package/src/upstream/sqlite3ext.h +7 -0
  56. package/src/user_function.cpp +88 -36
  57. package/src/user_function.h +2 -1
@@ -368,6 +368,10 @@ struct sqlite3_api_routines {
368
368
  int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
369
369
  /* Version 3.50.0 and later */
370
370
  int (*setlk_timeout)(sqlite3*,int,int);
371
+ /* Version 3.51.0 and later */
372
+ int (*set_errmsg)(sqlite3*,int,const char*);
373
+ int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int);
374
+
371
375
  };
372
376
 
373
377
  /*
@@ -703,6 +707,9 @@ typedef int (*sqlite3_loadext_entry)(
703
707
  #define sqlite3_set_clientdata sqlite3_api->set_clientdata
704
708
  /* Version 3.50.0 and later */
705
709
  #define sqlite3_setlk_timeout sqlite3_api->setlk_timeout
710
+ /* Version 3.51.0 and later */
711
+ #define sqlite3_set_errmsg sqlite3_api->set_errmsg
712
+ #define sqlite3_db_status64 sqlite3_api->db_status64
706
713
  #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
707
714
 
708
715
  #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
@@ -2,25 +2,49 @@
2
2
 
3
3
  #include <climits>
4
4
  #include <cmath>
5
+ #include <limits>
5
6
  #include <stdexcept>
6
7
 
7
8
  #include "sqlite_impl.h"
8
9
 
9
- namespace photostructure {
10
- namespace sqlite {
10
+ namespace photostructure::sqlite {
11
11
 
12
12
  UserDefinedFunction::UserDefinedFunction(Napi::Env env, Napi::Function fn,
13
13
  DatabaseSync *db, bool use_bigint_args)
14
14
  : env_(env), fn_(Napi::Reference<Napi::Function>::New(fn, 1)), db_(db),
15
- use_bigint_args_(use_bigint_args) {
16
- // No need for SuppressDestruct when using reference count
15
+ use_bigint_args_(use_bigint_args), async_context_(nullptr) {
16
+ // Create async context for callbacks
17
+ const napi_status status = napi_async_init(
18
+ env, nullptr, Napi::String::New(env, "SQLiteUserFunction"),
19
+ &async_context_);
20
+ if (status != napi_ok) {
21
+ Napi::Error::New(env, "Failed to create async context")
22
+ .ThrowAsJavaScriptException();
23
+ }
17
24
  }
18
25
 
19
- UserDefinedFunction::~UserDefinedFunction() {
20
- // Clean up the persistent function reference
21
- if (!fn_.IsEmpty()) {
22
- fn_.Reset();
26
+ UserDefinedFunction::~UserDefinedFunction() noexcept {
27
+ // Check if environment is still valid before N-API operations.
28
+ // During shutdown, env_ may be torn down and N-API operations would crash.
29
+ // Try to create a handle scope - if this fails, env is invalid.
30
+ napi_handle_scope scope;
31
+ napi_status status = napi_open_handle_scope(env_, &scope);
32
+
33
+ if (status == napi_ok) {
34
+ // Safe to do N-API operations
35
+ if (!fn_.IsEmpty()) {
36
+ fn_.Reset();
37
+ }
38
+
39
+ if (async_context_ != nullptr) {
40
+ napi_async_destroy(env_, async_context_);
41
+ async_context_ = nullptr;
42
+ }
43
+
44
+ napi_close_handle_scope(env_, scope);
23
45
  }
46
+ // If status != napi_ok, env is invalid - skip cleanup.
47
+ // References will be leaked, but that's better than crashing.
24
48
  }
25
49
 
26
50
  void UserDefinedFunction::xFunc(sqlite3_context *ctx, int argc,
@@ -35,6 +59,7 @@ void UserDefinedFunction::xFunc(sqlite3_context *ctx, int argc,
35
59
 
36
60
  try {
37
61
  Napi::HandleScope scope(self->env_);
62
+ Napi::CallbackScope callback_scope(self->env_, self->async_context_);
38
63
 
39
64
  // Check if function reference is still valid
40
65
  if (self->fn_.IsEmpty()) {
@@ -68,34 +93,33 @@ void UserDefinedFunction::xFunc(sqlite3_context *ctx, int argc,
68
93
  js_args.push_back(js_val);
69
94
  }
70
95
 
71
- // Call the JavaScript function
72
- Napi::Value result = fn.Call(js_args);
96
+ // Call the JavaScript function with safer exception handling
97
+ napi_value js_result;
98
+ napi_value js_func = fn;
99
+ napi_value this_arg = self->env_.Undefined();
73
100
 
74
- // Check if there's a pending exception after the call
75
- if (self->env_.IsExceptionPending()) {
76
- Napi::Error error = self->env_.GetAndClearPendingException();
77
- std::string error_msg = error.Message();
78
- try {
79
- sqlite3_result_error(ctx, error_msg.c_str(),
80
- SafeCastToInt(error_msg.length()));
81
- } catch (const std::overflow_error &) {
82
- sqlite3_result_error(ctx, "Error message too long", -1);
101
+ napi_status status =
102
+ napi_call_function(self->env_, this_arg, js_func, js_args.size(),
103
+ js_args.data(), &js_result);
104
+
105
+ if (status != napi_ok || self->env_.IsExceptionPending()) {
106
+ // Handle JavaScript exception by setting a generic SQLite error
107
+ if (self->env_.IsExceptionPending()) {
108
+ sqlite3_result_error(ctx, "JavaScript exception in user function", -1);
109
+ } else {
110
+ sqlite3_result_error(ctx, "Failed to call user function", -1);
83
111
  }
84
112
  return;
85
113
  }
86
114
 
115
+ Napi::Value result(self->env_, js_result);
116
+
87
117
  // Convert result back to SQLite
88
118
  self->JSValueToSqliteResult(ctx, result);
89
119
 
90
120
  } catch (const Napi::Error &e) {
91
- // Handle JavaScript exceptions
92
- std::string error_msg = e.Message();
93
- try {
94
- sqlite3_result_error(ctx, error_msg.c_str(),
95
- SafeCastToInt(error_msg.length()));
96
- } catch (const std::overflow_error &) {
97
- sqlite3_result_error(ctx, "Error message too long", -1);
98
- }
121
+ // Handle JavaScript errors by setting a generic SQLite error
122
+ sqlite3_result_error(ctx, "JavaScript exception in user function", -1);
99
123
  } catch (const std::exception &e) {
100
124
  sqlite3_result_error(ctx, e.what(), -1);
101
125
  } catch (...) {
@@ -138,15 +162,20 @@ Napi::Value UserDefinedFunction::SqliteValueToJS(sqlite3_value *value) {
138
162
  }
139
163
 
140
164
  case SQLITE_TEXT: {
141
- const unsigned char *text = sqlite3_value_text(value);
142
- return Napi::String::New(env_, reinterpret_cast<const char *>(text));
165
+ const char *text =
166
+ reinterpret_cast<const char *>(sqlite3_value_text(value));
167
+ return Napi::String::New(env_, text ? text : "");
143
168
  }
144
169
 
145
170
  case SQLITE_BLOB: {
146
- const void *blob_data = sqlite3_value_blob(value);
147
- int blob_size = sqlite3_value_bytes(value);
148
- return Napi::Buffer<uint8_t>::Copy(
149
- env_, static_cast<const uint8_t *>(blob_data), blob_size);
171
+ const void *blob = sqlite3_value_blob(value);
172
+ int bytes = sqlite3_value_bytes(value);
173
+ if (blob && bytes > 0) {
174
+ return Napi::Buffer<uint8_t>::Copy(
175
+ env_, static_cast<const uint8_t *>(blob), static_cast<size_t>(bytes));
176
+ } else {
177
+ return Napi::Buffer<uint8_t>::New(env_, 0);
178
+ }
150
179
  }
151
180
 
152
181
  case SQLITE_NULL:
@@ -186,7 +215,8 @@ void UserDefinedFunction::JSValueToSqliteResult(sqlite3_context *ctx,
186
215
  // Check if it's an integer value
187
216
  // Note: We cast INT64_MIN/MAX to double to avoid implicit conversion
188
217
  // warnings
189
- if (std::floor(num_val) == num_val &&
218
+ if (std::abs(num_val - std::floor(num_val)) <
219
+ std::numeric_limits<double>::epsilon() &&
190
220
  num_val >= static_cast<double>(INT64_MIN) &&
191
221
  num_val <= static_cast<double>(INT64_MAX)) {
192
222
  sqlite3_result_int64(ctx, static_cast<sqlite3_int64>(num_val));
@@ -201,7 +231,30 @@ void UserDefinedFunction::JSValueToSqliteResult(sqlite3_context *ctx,
201
231
  } catch (const std::overflow_error &) {
202
232
  sqlite3_result_error(ctx, "String value too long", -1);
203
233
  }
234
+ } else if (value.IsDataView()) {
235
+ // IMPORTANT: Check DataView BEFORE IsBuffer() because N-API's IsBuffer()
236
+ // returns true for ALL ArrayBufferViews (including DataView), but
237
+ // Buffer::As() doesn't work correctly for DataView (returns length=0).
238
+ // See: https://github.com/nodejs/node/pull/56227
239
+ Napi::DataView dataView = value.As<Napi::DataView>();
240
+ Napi::ArrayBuffer arrayBuffer = dataView.ArrayBuffer();
241
+ size_t byteOffset = dataView.ByteOffset();
242
+ size_t byteLength = dataView.ByteLength();
243
+
244
+ if (arrayBuffer.Data() != nullptr && byteLength > 0) {
245
+ const uint8_t *data =
246
+ static_cast<const uint8_t *>(arrayBuffer.Data()) + byteOffset;
247
+ try {
248
+ sqlite3_result_blob(ctx, data, SafeCastToInt(byteLength),
249
+ SQLITE_TRANSIENT);
250
+ } catch (const std::overflow_error &) {
251
+ sqlite3_result_error(ctx, "DataView too large", -1);
252
+ }
253
+ } else {
254
+ sqlite3_result_zeroblob(ctx, 0);
255
+ }
204
256
  } else if (value.IsBuffer()) {
257
+ // Handles both Node.js Buffer and TypedArrays (Uint8Array, etc.)
205
258
  Napi::Buffer<uint8_t> buffer = value.As<Napi::Buffer<uint8_t>>();
206
259
  try {
207
260
  sqlite3_result_blob(ctx, buffer.Data(), SafeCastToInt(buffer.Length()),
@@ -221,5 +274,4 @@ void UserDefinedFunction::JSValueToSqliteResult(sqlite3_context *ctx,
221
274
  }
222
275
  }
223
276
 
224
- } // namespace sqlite
225
- } // namespace photostructure
277
+ } // namespace photostructure::sqlite
@@ -17,7 +17,7 @@ class UserDefinedFunction {
17
17
  public:
18
18
  UserDefinedFunction(Napi::Env env, Napi::Function fn, DatabaseSync *db,
19
19
  bool use_bigint_args);
20
- ~UserDefinedFunction();
20
+ ~UserDefinedFunction() noexcept;
21
21
 
22
22
  // SQLite callback functions
23
23
  static void xFunc(sqlite3_context *ctx, int argc, sqlite3_value **argv);
@@ -28,6 +28,7 @@ private:
28
28
  Napi::FunctionReference fn_;
29
29
  DatabaseSync *db_;
30
30
  bool use_bigint_args_;
31
+ napi_async_context async_context_;
31
32
 
32
33
  // Helper methods
33
34
  Napi::Value SqliteValueToJS(sqlite3_value *value);