@photostructure/sqlite 0.0.1 → 0.2.0

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 (56) hide show
  1. package/CHANGELOG.md +36 -2
  2. package/README.md +45 -484
  3. package/SECURITY.md +27 -84
  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-x64/@photostructure+sqlite.glibc.node +0 -0
  21. package/src/aggregate_function.cpp +503 -235
  22. package/src/aggregate_function.h +57 -42
  23. package/src/binding.cpp +117 -14
  24. package/src/dirname.ts +1 -1
  25. package/src/index.ts +122 -332
  26. package/src/lru-cache.ts +84 -0
  27. package/src/shims/env-inl.h +6 -15
  28. package/src/shims/node_errors.h +4 -0
  29. package/src/shims/sqlite_errors.h +162 -0
  30. package/src/shims/util.h +29 -4
  31. package/src/sql-tag-store.ts +140 -0
  32. package/src/sqlite_exception.h +49 -0
  33. package/src/sqlite_impl.cpp +711 -127
  34. package/src/sqlite_impl.h +84 -6
  35. package/src/{stack_path.ts → stack-path.ts} +7 -1
  36. package/src/types/aggregate-options.ts +22 -0
  37. package/src/types/changeset-apply-options.ts +18 -0
  38. package/src/types/database-sync-instance.ts +203 -0
  39. package/src/types/database-sync-options.ts +69 -0
  40. package/src/types/session-options.ts +10 -0
  41. package/src/types/sql-tag-store-instance.ts +51 -0
  42. package/src/types/sqlite-authorization-actions.ts +77 -0
  43. package/src/types/sqlite-authorization-results.ts +15 -0
  44. package/src/types/sqlite-changeset-conflict-types.ts +19 -0
  45. package/src/types/sqlite-changeset-resolution.ts +15 -0
  46. package/src/types/sqlite-open-flags.ts +50 -0
  47. package/src/types/statement-sync-instance.ts +73 -0
  48. package/src/types/user-functions-options.ts +14 -0
  49. package/src/upstream/node_sqlite.cc +960 -259
  50. package/src/upstream/node_sqlite.h +127 -2
  51. package/src/upstream/sqlite.js +1 -14
  52. package/src/upstream/sqlite3.c +4510 -1411
  53. package/src/upstream/sqlite3.h +390 -195
  54. package/src/upstream/sqlite3ext.h +7 -0
  55. package/src/user_function.cpp +88 -36
  56. package/src/user_function.h +2 -1
@@ -4,8 +4,11 @@
4
4
  #include <napi.h>
5
5
  #include <sqlite3.h>
6
6
 
7
+ #include <atomic>
7
8
  #include <memory>
9
+ #include <mutex>
8
10
  #include <string>
11
+ #include <unordered_map>
9
12
 
10
13
  namespace photostructure {
11
14
  namespace sqlite {
@@ -13,6 +16,20 @@ namespace sqlite {
13
16
  // Forward declarations
14
17
  class DatabaseSync;
15
18
 
19
+ // Thread-safe external storage for N-API values
20
+ // This solves the problem of storing N-API objects in SQLite-allocated memory
21
+ class ValueStorage {
22
+ private:
23
+ static std::unordered_map<int32_t, Napi::Reference<Napi::Value>> storage_;
24
+ static std::mutex mutex_;
25
+ static std::atomic<int32_t> next_id_;
26
+
27
+ public:
28
+ static int32_t Store(Napi::Env env, Napi::Value value);
29
+ static Napi::Value Get(Napi::Env env, int32_t id);
30
+ static void Remove(int32_t id);
31
+ };
32
+
16
33
  class CustomAggregate {
17
34
  public:
18
35
  explicit CustomAggregate(Napi::Env env, DatabaseSync *db,
@@ -29,63 +46,57 @@ public:
29
46
  static void xDestroy(void *self);
30
47
 
31
48
  private:
32
- struct AggregateData {
33
- // Store value as raw C++ data instead of JavaScript objects
34
- enum ValueType {
35
- TYPE_NULL,
36
- TYPE_UNDEFINED,
37
- TYPE_NUMBER,
38
- TYPE_STRING,
39
- TYPE_BOOLEAN,
40
- TYPE_BIGINT,
41
- TYPE_OBJECT // For complex objects, we'll need special handling
42
- } value_type;
43
-
49
+ // Comprehensive aggregate value storage (no Napi::Reference)
50
+ struct AggregateValue {
51
+ enum Type {
52
+ NUMBER,
53
+ STRING,
54
+ BIGINT,
55
+ BOOLEAN,
56
+ NULL_VAL,
57
+ OBJECT_JSON,
58
+ BUFFER
59
+ };
60
+ Type type;
61
+ bool is_initialized;
44
62
  union {
45
- double number_val;
46
- bool boolean_val;
47
- int64_t bigint_val;
63
+ double number_value;
64
+ bool bool_value;
65
+ int64_t bigint_value;
48
66
  };
49
- std::string string_val; // For strings
50
- Napi::Reference<Napi::Value> object_ref; // For complex objects (fallback)
67
+ char string_buffer[4096]; // Fixed-size buffer for string data (POD) -
68
+ // increased for complex JSON
69
+ size_t string_length; // Length of string data
70
+
71
+ // No constructor needed - this must be POD for SQLite context
72
+ };
51
73
 
74
+ // Simplified aggregate data structure matching Node.js pattern
75
+ // Uses external storage to avoid N-API object lifetime issues
76
+ struct AggregateData {
77
+ int32_t value_id; // Reference to external storage
52
78
  bool initialized;
53
79
  bool is_window;
54
- bool first_call; // True if this is the first call and we need to
55
- // initialize with start value
56
-
57
- // Default constructor
58
- AggregateData()
59
- : value_type(TYPE_NULL), number_val(0.0), initialized(false),
60
- is_window(false), first_call(false) {}
61
-
62
- // Destructor to properly clean up Napi::Reference
63
- ~AggregateData() {
64
- if (value_type == TYPE_OBJECT && !object_ref.IsEmpty()) {
65
- object_ref.Reset();
66
- }
67
- }
68
80
  };
69
81
 
70
- // Helper methods
82
+ // Helper methods - mirroring Node.js implementation exactly
71
83
  static void xStepBase(sqlite3_context *ctx, int argc, sqlite3_value **argv,
72
- bool use_inverse);
73
- static void xValueBase(sqlite3_context *ctx, bool finalize);
84
+ Napi::Reference<Napi::Function> CustomAggregate::*mptr);
85
+ static void xValueBase(sqlite3_context *ctx, bool is_final);
86
+
87
+ // Helper method for safe JSON serialization with circular reference handling
88
+ static std::string SafeJsonStringify(Napi::Env env, Napi::Value value);
89
+ static void DestroyAggregateData(sqlite3_context *ctx);
74
90
 
75
91
  AggregateData *GetAggregate(sqlite3_context *ctx);
76
92
  Napi::Value SqliteValueToJS(sqlite3_value *value);
77
93
  void JSValueToSqliteResult(sqlite3_context *ctx, Napi::Value value);
78
94
  Napi::Value GetStartValue();
79
95
 
80
- // New methods for raw C++ value handling
81
- void StoreJSValueAsRaw(AggregateData *agg, Napi::Value value);
82
- Napi::Value RawValueToJS(AggregateData *agg);
83
-
84
96
  Napi::Env env_;
85
- DatabaseSync *db_;
86
97
  bool use_bigint_args_;
87
98
 
88
- // Storage for start value - handle primitives differently
99
+ // Storage for start value - handle primitives and objects
89
100
  enum StartValueType {
90
101
  PRIMITIVE_NULL,
91
102
  PRIMITIVE_UNDEFINED,
@@ -93,7 +104,8 @@ private:
93
104
  PRIMITIVE_STRING,
94
105
  PRIMITIVE_BOOLEAN,
95
106
  PRIMITIVE_BIGINT,
96
- OBJECT
107
+ OBJECT,
108
+ FUNCTION
97
109
  };
98
110
  StartValueType start_type_;
99
111
  double number_value_;
@@ -101,13 +113,16 @@ private:
101
113
  bool boolean_value_;
102
114
  int64_t bigint_value_;
103
115
  Napi::Reference<Napi::Value> object_ref_;
116
+ Napi::Reference<Napi::Function> start_fn_;
104
117
 
118
+ // Function references
105
119
  Napi::Reference<Napi::Function> step_fn_;
106
120
  Napi::Reference<Napi::Function> inverse_fn_;
107
121
  Napi::Reference<Napi::Function> result_fn_;
108
122
 
109
- // Async context for callbacks
123
+ // Async context for callbacks (created lazily)
110
124
  napi_async_context async_context_;
125
+ napi_async_context GetAsyncContext();
111
126
  };
112
127
 
113
128
  } // namespace sqlite
package/src/binding.cpp CHANGED
@@ -1,20 +1,19 @@
1
- #include <memory>
2
1
  #include <mutex>
3
2
  #include <napi.h>
4
3
  #include <set>
5
4
 
6
5
  #include "sqlite_impl.h"
7
6
 
8
- namespace photostructure {
9
- namespace sqlite {
7
+ namespace photostructure::sqlite {
10
8
 
11
9
  // Cleanup function for worker termination
12
- void CleanupAddonData(napi_env env, void *finalize_data, void *finalize_hint) {
13
- AddonData *addon_data = static_cast<AddonData *>(finalize_data);
10
+ void CleanupAddonData([[maybe_unused]] napi_env env, void *finalize_data,
11
+ [[maybe_unused]] void *finalize_hint) {
12
+ auto *addon_data = static_cast<AddonData *>(finalize_data);
14
13
 
15
14
  // Clean up any remaining database connections
16
15
  {
17
- std::lock_guard<std::mutex> lock(addon_data->mutex);
16
+ const std::lock_guard<std::mutex> mutex_lock(addon_data->mutex);
18
17
  addon_data->databases.clear();
19
18
  }
20
19
 
@@ -38,7 +37,7 @@ void CleanupAddonData(napi_env env, void *finalize_data, void *finalize_hint) {
38
37
  // Helper to get addon data for current environment
39
38
  AddonData *GetAddonData(napi_env env) {
40
39
  void *data = nullptr;
41
- napi_status status = napi_get_instance_data(env, &data);
40
+ const napi_status status = napi_get_instance_data(env, &data);
42
41
  if (status != napi_ok || data == nullptr) {
43
42
  return nullptr;
44
43
  }
@@ -48,8 +47,8 @@ AddonData *GetAddonData(napi_env env) {
48
47
  // Register a database instance for cleanup tracking
49
48
  void RegisterDatabaseInstance(Napi::Env env, DatabaseSync *database) {
50
49
  AddonData *addon_data = GetAddonData(env);
51
- if (addon_data) {
52
- std::lock_guard<std::mutex> lock(addon_data->mutex);
50
+ if (addon_data != nullptr) {
51
+ const std::lock_guard<std::mutex> mutex_lock(addon_data->mutex);
53
52
  addon_data->databases.insert(database);
54
53
  }
55
54
  }
@@ -57,8 +56,8 @@ void RegisterDatabaseInstance(Napi::Env env, DatabaseSync *database) {
57
56
  // Unregister a database instance
58
57
  void UnregisterDatabaseInstance(Napi::Env env, DatabaseSync *database) {
59
58
  AddonData *addon_data = GetAddonData(env);
60
- if (addon_data) {
61
- std::lock_guard<std::mutex> lock(addon_data->mutex);
59
+ if (addon_data != nullptr) {
60
+ const std::lock_guard<std::mutex> mutex_lock(addon_data->mutex);
62
61
  addon_data->databases.erase(database);
63
62
  }
64
63
  }
@@ -141,15 +140,119 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
141
140
  constants.Set("SQLITE_CHANGESET_FOREIGN_KEY",
142
141
  Napi::Number::New(env, SQLITE_CHANGESET_FOREIGN_KEY));
143
142
 
143
+ // Authorization result codes
144
+ constants.Set("SQLITE_OK", Napi::Number::New(env, SQLITE_OK));
145
+ constants.Set("SQLITE_DENY", Napi::Number::New(env, SQLITE_DENY));
146
+ constants.Set("SQLITE_IGNORE", Napi::Number::New(env, SQLITE_IGNORE));
147
+
148
+ // Authorization action codes
149
+ constants.Set("SQLITE_CREATE_INDEX",
150
+ Napi::Number::New(env, SQLITE_CREATE_INDEX));
151
+ constants.Set("SQLITE_CREATE_TABLE",
152
+ Napi::Number::New(env, SQLITE_CREATE_TABLE));
153
+ constants.Set("SQLITE_CREATE_TEMP_INDEX",
154
+ Napi::Number::New(env, SQLITE_CREATE_TEMP_INDEX));
155
+ constants.Set("SQLITE_CREATE_TEMP_TABLE",
156
+ Napi::Number::New(env, SQLITE_CREATE_TEMP_TABLE));
157
+ constants.Set("SQLITE_CREATE_TEMP_TRIGGER",
158
+ Napi::Number::New(env, SQLITE_CREATE_TEMP_TRIGGER));
159
+ constants.Set("SQLITE_CREATE_TEMP_VIEW",
160
+ Napi::Number::New(env, SQLITE_CREATE_TEMP_VIEW));
161
+ constants.Set("SQLITE_CREATE_TRIGGER",
162
+ Napi::Number::New(env, SQLITE_CREATE_TRIGGER));
163
+ constants.Set("SQLITE_CREATE_VIEW",
164
+ Napi::Number::New(env, SQLITE_CREATE_VIEW));
165
+ constants.Set("SQLITE_DELETE", Napi::Number::New(env, SQLITE_DELETE));
166
+ constants.Set("SQLITE_DROP_INDEX", Napi::Number::New(env, SQLITE_DROP_INDEX));
167
+ constants.Set("SQLITE_DROP_TABLE", Napi::Number::New(env, SQLITE_DROP_TABLE));
168
+ constants.Set("SQLITE_DROP_TEMP_INDEX",
169
+ Napi::Number::New(env, SQLITE_DROP_TEMP_INDEX));
170
+ constants.Set("SQLITE_DROP_TEMP_TABLE",
171
+ Napi::Number::New(env, SQLITE_DROP_TEMP_TABLE));
172
+ constants.Set("SQLITE_DROP_TEMP_TRIGGER",
173
+ Napi::Number::New(env, SQLITE_DROP_TEMP_TRIGGER));
174
+ constants.Set("SQLITE_DROP_TEMP_VIEW",
175
+ Napi::Number::New(env, SQLITE_DROP_TEMP_VIEW));
176
+ constants.Set("SQLITE_DROP_TRIGGER",
177
+ Napi::Number::New(env, SQLITE_DROP_TRIGGER));
178
+ constants.Set("SQLITE_DROP_VIEW", Napi::Number::New(env, SQLITE_DROP_VIEW));
179
+ constants.Set("SQLITE_INSERT", Napi::Number::New(env, SQLITE_INSERT));
180
+ constants.Set("SQLITE_PRAGMA", Napi::Number::New(env, SQLITE_PRAGMA));
181
+ constants.Set("SQLITE_READ", Napi::Number::New(env, SQLITE_READ));
182
+ constants.Set("SQLITE_SELECT", Napi::Number::New(env, SQLITE_SELECT));
183
+ constants.Set("SQLITE_TRANSACTION",
184
+ Napi::Number::New(env, SQLITE_TRANSACTION));
185
+ constants.Set("SQLITE_UPDATE", Napi::Number::New(env, SQLITE_UPDATE));
186
+ constants.Set("SQLITE_ATTACH", Napi::Number::New(env, SQLITE_ATTACH));
187
+ constants.Set("SQLITE_DETACH", Napi::Number::New(env, SQLITE_DETACH));
188
+ constants.Set("SQLITE_ALTER_TABLE",
189
+ Napi::Number::New(env, SQLITE_ALTER_TABLE));
190
+ constants.Set("SQLITE_REINDEX", Napi::Number::New(env, SQLITE_REINDEX));
191
+ constants.Set("SQLITE_ANALYZE", Napi::Number::New(env, SQLITE_ANALYZE));
192
+ constants.Set("SQLITE_CREATE_VTABLE",
193
+ Napi::Number::New(env, SQLITE_CREATE_VTABLE));
194
+ constants.Set("SQLITE_DROP_VTABLE",
195
+ Napi::Number::New(env, SQLITE_DROP_VTABLE));
196
+ constants.Set("SQLITE_FUNCTION", Napi::Number::New(env, SQLITE_FUNCTION));
197
+ constants.Set("SQLITE_SAVEPOINT", Napi::Number::New(env, SQLITE_SAVEPOINT));
198
+ constants.Set("SQLITE_COPY", Napi::Number::New(env, SQLITE_COPY));
199
+ constants.Set("SQLITE_RECURSIVE", Napi::Number::New(env, SQLITE_RECURSIVE));
200
+
144
201
  exports.Set("constants", constants);
145
202
 
146
- // TODO: Add backup function
203
+ // Add standalone backup() function (Node.js API compatibility)
204
+ // Signature: backup(sourceDb, destination, options?) -> Promise
205
+ Napi::Function backupFunc = Napi::Function::New(
206
+ env,
207
+ [](const Napi::CallbackInfo &info) -> Napi::Value {
208
+ Napi::Env env = info.Env();
209
+
210
+ // Validate and unwrap DatabaseSync instance
211
+ DatabaseSync *db = nullptr;
212
+ if (info.Length() >= 1 && info[0].IsObject()) {
213
+ try {
214
+ db = DatabaseSync::Unwrap(info[0].As<Napi::Object>());
215
+ } catch (...) {
216
+ // Fall through to error below
217
+ }
218
+ }
219
+ if (db == nullptr) {
220
+ Napi::TypeError::New(
221
+ env, "The \"sourceDb\" argument must be a DatabaseSync object")
222
+ .ThrowAsJavaScriptException();
223
+ return env.Undefined();
224
+ }
225
+
226
+ // Validate destination path is provided
227
+ if (info.Length() < 2) {
228
+ Napi::TypeError::New(env, "The \"destination\" argument is required")
229
+ .ThrowAsJavaScriptException();
230
+ return env.Undefined();
231
+ }
232
+
233
+ // Delegate to instance method: db.backup(destination, options?)
234
+ std::vector<napi_value> args;
235
+ for (size_t i = 1; i < info.Length(); i++) {
236
+ args.push_back(info[i]);
237
+ }
238
+ Napi::Function backupMethod =
239
+ db->Value().Get("backup").As<Napi::Function>();
240
+ return backupMethod.Call(db->Value(), args);
241
+ },
242
+ "backup");
243
+
244
+ // Set function name and length properties (Node.js compatibility)
245
+ backupFunc.DefineProperty(Napi::PropertyDescriptor::Value(
246
+ "name", Napi::String::New(env, "backup"), napi_enumerable));
247
+ backupFunc.DefineProperty(Napi::PropertyDescriptor::Value(
248
+ "length", Napi::Number::New(env, 2), napi_enumerable));
249
+
250
+ exports.Set("backup", backupFunc);
147
251
 
148
252
  return exports;
149
253
  }
150
254
 
151
- } // namespace sqlite
152
- } // namespace photostructure
255
+ } // namespace photostructure::sqlite
153
256
 
154
257
  // Module initialization function
155
258
  Napi::Object InitSqlite(Napi::Env env, Napi::Object exports) {
package/src/dirname.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { getCallerDirname } from "./stack_path";
1
+ import { getCallerDirname } from "./stack-path";
2
2
 
3
3
  // Thanks to tsup shims, __dirname should always be defined except when run by
4
4
  // jest (which will use the stack_path shim)