@photostructure/sqlite 0.0.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 (53) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +522 -0
  4. package/SECURITY.md +114 -0
  5. package/binding.gyp +94 -0
  6. package/dist/index.cjs +134 -0
  7. package/dist/index.cjs.map +1 -0
  8. package/dist/index.d.cts +408 -0
  9. package/dist/index.d.mts +408 -0
  10. package/dist/index.d.ts +408 -0
  11. package/dist/index.mjs +103 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/package.json +144 -0
  14. package/prebuilds/darwin-arm64/@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/win32-x64/@photostructure+sqlite.glibc.node +0 -0
  20. package/scripts/post-build.mjs +21 -0
  21. package/scripts/prebuild-linux-glibc.sh +108 -0
  22. package/src/aggregate_function.cpp +417 -0
  23. package/src/aggregate_function.h +116 -0
  24. package/src/binding.cpp +160 -0
  25. package/src/dirname.ts +13 -0
  26. package/src/index.ts +465 -0
  27. package/src/shims/base_object-inl.h +8 -0
  28. package/src/shims/base_object.h +50 -0
  29. package/src/shims/debug_utils-inl.h +23 -0
  30. package/src/shims/env-inl.h +19 -0
  31. package/src/shims/memory_tracker-inl.h +17 -0
  32. package/src/shims/napi_extensions.h +73 -0
  33. package/src/shims/node.h +16 -0
  34. package/src/shims/node_errors.h +66 -0
  35. package/src/shims/node_mem-inl.h +8 -0
  36. package/src/shims/node_mem.h +31 -0
  37. package/src/shims/node_url.h +23 -0
  38. package/src/shims/promise_resolver.h +31 -0
  39. package/src/shims/util-inl.h +18 -0
  40. package/src/shims/util.h +101 -0
  41. package/src/sqlite_impl.cpp +2440 -0
  42. package/src/sqlite_impl.h +314 -0
  43. package/src/stack_path.ts +64 -0
  44. package/src/types/node-gyp-build.d.ts +4 -0
  45. package/src/upstream/node_sqlite.cc +2706 -0
  46. package/src/upstream/node_sqlite.h +234 -0
  47. package/src/upstream/sqlite.gyp +38 -0
  48. package/src/upstream/sqlite.js +19 -0
  49. package/src/upstream/sqlite3.c +262809 -0
  50. package/src/upstream/sqlite3.h +13773 -0
  51. package/src/upstream/sqlite3ext.h +723 -0
  52. package/src/user_function.cpp +225 -0
  53. package/src/user_function.h +40 -0
@@ -0,0 +1,314 @@
1
+ #ifndef SRC_SQLITE_IMPL_H_
2
+ #define SRC_SQLITE_IMPL_H_
3
+
4
+ #include <napi.h>
5
+ #include <sqlite3.h>
6
+
7
+ #include <atomic>
8
+ #include <climits>
9
+ #include <map>
10
+ #include <memory>
11
+ #include <mutex>
12
+ #include <optional>
13
+ #include <set>
14
+ #include <stdexcept>
15
+ #include <string>
16
+ #include <thread>
17
+
18
+ // Include our shims
19
+ #include "shims/base_object.h"
20
+ #include "shims/napi_extensions.h"
21
+ #include "shims/node_errors.h"
22
+ #include "shims/promise_resolver.h"
23
+ // Removed threadpoolwork-inl.h - using Napi::AsyncWorker instead
24
+ #include "shims/util.h"
25
+
26
+ namespace photostructure {
27
+ namespace sqlite {
28
+
29
+ // Forward declarations
30
+ class DatabaseSync;
31
+ class StatementSync;
32
+ class StatementSyncIterator;
33
+ class Session;
34
+
35
+ // Per-worker instance data
36
+ struct AddonData {
37
+ std::mutex mutex;
38
+ // Track all database instances for proper cleanup
39
+ std::set<DatabaseSync *> databases;
40
+
41
+ // Store constructors per-instance instead of globally
42
+ Napi::FunctionReference databaseSyncConstructor;
43
+ Napi::FunctionReference statementSyncConstructor;
44
+ Napi::FunctionReference statementSyncIteratorConstructor;
45
+ Napi::FunctionReference sessionConstructor;
46
+ };
47
+
48
+ // Worker thread support functions
49
+ void RegisterDatabaseInstance(Napi::Env env, DatabaseSync *database);
50
+ void UnregisterDatabaseInstance(Napi::Env env, DatabaseSync *database);
51
+ AddonData *GetAddonData(napi_env env);
52
+
53
+ // Path validation function
54
+ std::optional<std::string> ValidateDatabasePath(Napi::Env env, Napi::Value path,
55
+ const std::string &field_name);
56
+
57
+ // Safe integer cast with bounds checking
58
+ inline int SafeCastToInt(size_t value) {
59
+ if (value > static_cast<size_t>(INT_MAX)) {
60
+ throw std::overflow_error("Value too large to safely cast to int");
61
+ }
62
+ return static_cast<int>(value);
63
+ }
64
+
65
+ // Database configuration
66
+ class DatabaseOpenConfiguration {
67
+ public:
68
+ explicit DatabaseOpenConfiguration(std::string &&location)
69
+ : location_(std::move(location)) {}
70
+
71
+ const std::string &location() const { return location_; }
72
+
73
+ bool get_read_only() const { return read_only_; }
74
+ void set_read_only(bool flag) { read_only_ = flag; }
75
+
76
+ bool get_enable_foreign_keys() const { return enable_foreign_keys_; }
77
+ void set_enable_foreign_keys(bool flag) { enable_foreign_keys_ = flag; }
78
+
79
+ bool get_enable_dqs() const { return enable_dqs_; }
80
+ void set_enable_dqs(bool flag) { enable_dqs_ = flag; }
81
+
82
+ void set_timeout(int timeout) { timeout_ = timeout; }
83
+ int get_timeout() const { return timeout_; }
84
+
85
+ private:
86
+ std::string location_;
87
+ bool read_only_ = false;
88
+ bool enable_foreign_keys_ = true;
89
+ bool enable_dqs_ = false;
90
+ int timeout_ = 0;
91
+ };
92
+
93
+ // Main database class
94
+ class DatabaseSync : public Napi::ObjectWrap<DatabaseSync> {
95
+ public:
96
+ static constexpr int kInternalFieldCount = 1;
97
+
98
+ static Napi::Object Init(Napi::Env env, Napi::Object exports);
99
+
100
+ explicit DatabaseSync(const Napi::CallbackInfo &info);
101
+ virtual ~DatabaseSync();
102
+
103
+ // Database operations
104
+ Napi::Value Open(const Napi::CallbackInfo &info);
105
+ Napi::Value Close(const Napi::CallbackInfo &info);
106
+ Napi::Value Prepare(const Napi::CallbackInfo &info);
107
+ Napi::Value Exec(const Napi::CallbackInfo &info);
108
+
109
+ // Properties
110
+ Napi::Value LocationMethod(const Napi::CallbackInfo &info);
111
+ Napi::Value IsOpenGetter(const Napi::CallbackInfo &info);
112
+ Napi::Value IsTransactionGetter(const Napi::CallbackInfo &info);
113
+
114
+ // SQLite handle access
115
+ sqlite3 *connection() const { return connection_; }
116
+ bool IsOpen() const { return connection_ != nullptr; }
117
+
118
+ // User-defined functions
119
+ Napi::Value CustomFunction(const Napi::CallbackInfo &info);
120
+
121
+ // Aggregate functions
122
+ Napi::Value AggregateFunction(const Napi::CallbackInfo &info);
123
+
124
+ // Extension loading
125
+ Napi::Value EnableLoadExtension(const Napi::CallbackInfo &info);
126
+ Napi::Value LoadExtension(const Napi::CallbackInfo &info);
127
+
128
+ // Session support
129
+ Napi::Value CreateSession(const Napi::CallbackInfo &info);
130
+ Napi::Value ApplyChangeset(const Napi::CallbackInfo &info);
131
+
132
+ // Backup support
133
+ Napi::Value Backup(const Napi::CallbackInfo &info);
134
+
135
+ // Session management
136
+ void AddSession(Session *session);
137
+ void RemoveSession(Session *session);
138
+ void DeleteAllSessions();
139
+
140
+ private:
141
+ void InternalOpen(DatabaseOpenConfiguration config);
142
+ void InternalClose();
143
+
144
+ sqlite3 *connection_ = nullptr;
145
+ std::string location_;
146
+ bool read_only_ = false;
147
+ bool allow_load_extension_ = false;
148
+ bool enable_load_extension_ = false;
149
+ std::map<std::string, std::unique_ptr<StatementSync>> prepared_statements_;
150
+ std::set<Session *> sessions_; // Track all active sessions
151
+ mutable std::mutex sessions_mutex_; // Protect sessions_ for thread safety
152
+ std::thread::id creation_thread_;
153
+ napi_env env_; // Store for cleanup purposes
154
+
155
+ bool ValidateThread(Napi::Env env) const;
156
+ friend class Session;
157
+ };
158
+
159
+ // Statement class
160
+ class StatementSync : public Napi::ObjectWrap<StatementSync> {
161
+ public:
162
+ static constexpr int kInternalFieldCount = 1;
163
+
164
+ static Napi::Object Init(Napi::Env env, Napi::Object exports);
165
+
166
+ explicit StatementSync(const Napi::CallbackInfo &info);
167
+ virtual ~StatementSync();
168
+
169
+ // Internal constructor for DatabaseSync to use
170
+ void InitStatement(DatabaseSync *database, const std::string &sql);
171
+
172
+ // Statement operations
173
+ Napi::Value Run(const Napi::CallbackInfo &info);
174
+ Napi::Value Get(const Napi::CallbackInfo &info);
175
+ Napi::Value All(const Napi::CallbackInfo &info);
176
+ Napi::Value Iterate(const Napi::CallbackInfo &info);
177
+ Napi::Value FinalizeStatement(const Napi::CallbackInfo &info);
178
+
179
+ // Properties
180
+ Napi::Value SourceSQLGetter(const Napi::CallbackInfo &info);
181
+ Napi::Value ExpandedSQLGetter(const Napi::CallbackInfo &info);
182
+
183
+ // Configuration methods
184
+ Napi::Value SetReadBigInts(const Napi::CallbackInfo &info);
185
+ Napi::Value SetReturnArrays(const Napi::CallbackInfo &info);
186
+ Napi::Value SetAllowBareNamedParameters(const Napi::CallbackInfo &info);
187
+
188
+ // Metadata methods
189
+ Napi::Value Columns(const Napi::CallbackInfo &info);
190
+
191
+ private:
192
+ void BindParameters(const Napi::CallbackInfo &info, size_t start_index = 0);
193
+ void BindSingleParameter(int param_index, Napi::Value param);
194
+ Napi::Value CreateResult();
195
+ void Reset();
196
+
197
+ DatabaseSync *database_;
198
+ sqlite3_stmt *statement_ = nullptr;
199
+ std::string source_sql_;
200
+ bool finalized_ = false;
201
+ std::thread::id creation_thread_;
202
+
203
+ // Configuration options
204
+ bool use_big_ints_ = false;
205
+ bool return_arrays_ = false;
206
+ bool allow_bare_named_params_ = false;
207
+
208
+ // Bare named parameters mapping (bare name -> full name with prefix)
209
+ std::optional<std::map<std::string, std::string>> bare_named_params_;
210
+
211
+ bool ValidateThread(Napi::Env env) const;
212
+ friend class StatementSyncIterator;
213
+ };
214
+
215
+ // Iterator class for StatementSync
216
+ class StatementSyncIterator : public Napi::ObjectWrap<StatementSyncIterator> {
217
+ public:
218
+ static Napi::Object Init(Napi::Env env, Napi::Object exports);
219
+ static Napi::Object Create(Napi::Env env, StatementSync *stmt);
220
+
221
+ explicit StatementSyncIterator(const Napi::CallbackInfo &info);
222
+ virtual ~StatementSyncIterator();
223
+
224
+ // Iterator methods
225
+ Napi::Value Next(const Napi::CallbackInfo &info);
226
+ Napi::Value Return(const Napi::CallbackInfo &info);
227
+
228
+ private:
229
+ void SetStatement(StatementSync *stmt);
230
+
231
+ StatementSync *stmt_;
232
+ bool done_;
233
+ };
234
+
235
+ // Session class for SQLite changesets
236
+ class Session : public Napi::ObjectWrap<Session> {
237
+ public:
238
+ static Napi::Object Init(Napi::Env env, Napi::Object exports);
239
+ static Napi::Object Create(Napi::Env env, DatabaseSync *database,
240
+ sqlite3_session *session);
241
+
242
+ explicit Session(const Napi::CallbackInfo &info);
243
+ virtual ~Session();
244
+
245
+ // Session methods
246
+ Napi::Value Changeset(const Napi::CallbackInfo &info);
247
+ Napi::Value Patchset(const Napi::CallbackInfo &info);
248
+ Napi::Value Close(const Napi::CallbackInfo &info);
249
+
250
+ // Get the underlying SQLite session
251
+ sqlite3_session *GetSession() const { return session_; }
252
+
253
+ private:
254
+ void SetSession(DatabaseSync *database, sqlite3_session *session);
255
+ void Delete();
256
+
257
+ template <int (*sqliteChangesetFunc)(sqlite3_session *, int *, void **)>
258
+ Napi::Value GenericChangeset(const Napi::CallbackInfo &info);
259
+
260
+ sqlite3_session *session_ = nullptr;
261
+ DatabaseSync *database_ = nullptr; // Direct pointer to database
262
+
263
+ friend class DatabaseSync;
264
+ };
265
+
266
+ // Progress data structure for backup progress updates
267
+ struct BackupProgress {
268
+ int current;
269
+ int total;
270
+ };
271
+
272
+ // Backup job for asynchronous database backup
273
+ class BackupJob : public Napi::AsyncProgressWorker<BackupProgress> {
274
+ public:
275
+ BackupJob(Napi::Env env, DatabaseSync *source,
276
+ const std::string &destination_path, const std::string &source_db,
277
+ const std::string &dest_db, int pages, Napi::Function progress_func,
278
+ Napi::Promise::Deferred deferred);
279
+ ~BackupJob();
280
+
281
+ void Execute(const ExecutionProgress &progress) override;
282
+ void OnOK() override;
283
+ void OnError(const Napi::Error &error) override;
284
+ void OnProgress(const BackupProgress *data, size_t count) override;
285
+
286
+ Napi::Promise GetPromise() { return deferred_.Promise(); }
287
+
288
+ private:
289
+ void Cleanup();
290
+
291
+ DatabaseSync *source_;
292
+ std::string destination_path_;
293
+ std::string source_db_;
294
+ std::string dest_db_;
295
+ int pages_;
296
+
297
+ // These are only accessed in Execute() on worker thread
298
+ int backup_status_ = SQLITE_OK;
299
+ sqlite3 *dest_ = nullptr;
300
+ sqlite3_backup *backup_ = nullptr;
301
+ int total_pages_ = 0;
302
+
303
+ Napi::FunctionReference progress_func_;
304
+ Napi::Promise::Deferred deferred_;
305
+
306
+ static std::atomic<int> active_jobs_;
307
+ static std::mutex active_jobs_mutex_;
308
+ static std::set<BackupJob *> active_job_instances_;
309
+ };
310
+
311
+ } // namespace sqlite
312
+ } // namespace photostructure
313
+
314
+ #endif // SRC_SQLITE_IMPL_H_
@@ -0,0 +1,64 @@
1
+ import { dirname } from "node:path";
2
+
3
+ export function getCallerDirname(): string {
4
+ const e = new Error();
5
+ if (e.stack == null) {
6
+ Error.captureStackTrace(e);
7
+ }
8
+ return dirname(extractCallerPath(e.stack as string));
9
+ }
10
+
11
+ // Comprehensive regex patterns for different stack frame formats
12
+ const patterns =
13
+ process.platform === "win32"
14
+ ? [
15
+ // Standard: "at functionName (C:\path\file.js:1:1)"
16
+ /\bat\s.+?\((?<path>[A-Z]:\\.+):\d+:\d+\)$/,
17
+ // direct: "at C:\path\file.js:1:1"
18
+ /\bat\s(?<path>[A-Z]:\\.+):\d+:\d+$/,
19
+ // UNC: "at functionName (\\server\share\path\file.js:1:1)"
20
+ /\bat\s.+?\((?<path>\\\\.+):\d+:\d+\)$/,
21
+ // direct: "at \\server\share\path\file.js:1:1"
22
+ /\bat\s(?<path>\\\\.+):\d+:\d+$/,
23
+ ]
24
+ : [
25
+ // Standard: "at functionName (/path/file.js:1:1)"
26
+ /\bat\s.+?\((?<path>\/.+?):\d+:\d+\)$/,
27
+ // Anonymous or direct: "at /path/file.js:1:1"
28
+ /\bat\s(.+[^/]\s)?(?<path>\/.+?):\d+:\d+$/,
29
+ ];
30
+
31
+ const MaybeUrlRE = /^[a-z]{2,5}:\/\//i;
32
+
33
+ // only exposed for tests:
34
+ export function extractCallerPath(stack: string): string {
35
+ const frames = stack.split("\n").filter(Boolean);
36
+
37
+ // First find getCallerDirname() in the stack:
38
+ const callerFrame = frames.findIndex((frame) =>
39
+ frame.includes("getCallerDirname"),
40
+ );
41
+ if (callerFrame === -1) {
42
+ throw new Error("Invalid stack trace format: missing caller frame");
43
+ }
44
+ for (let i = callerFrame + 1; i < frames.length; i++) {
45
+ const frame = frames[i];
46
+ for (const pattern of patterns) {
47
+ const g = frame?.trim().match(pattern)?.groups;
48
+ if (g != null && g["path"]) {
49
+ const path = g["path"];
50
+ // Windows requires us to check if it's a reasonable URL, as URL accepts
51
+ // "C:\\path\\file.txt" as valid (!!)
52
+ if (MaybeUrlRE.test(path)) {
53
+ try {
54
+ return new URL(path).pathname;
55
+ } catch {
56
+ // ignore
57
+ }
58
+ }
59
+ return path;
60
+ }
61
+ }
62
+ }
63
+ throw new Error("Invalid stack trace format: no parsable frames");
64
+ }
@@ -0,0 +1,4 @@
1
+ declare module "node-gyp-build" {
2
+ function nodeGypBuild(dir: string): any;
3
+ export = nodeGypBuild;
4
+ }