@photostructure/sqlite 0.3.0 → 0.5.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 (41) hide show
  1. package/CHANGELOG.md +65 -16
  2. package/README.md +5 -10
  3. package/binding.gyp +2 -2
  4. package/dist/index.cjs +314 -11
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +346 -89
  7. package/dist/index.d.mts +346 -89
  8. package/dist/index.d.ts +346 -89
  9. package/dist/index.mjs +311 -10
  10. package/dist/index.mjs.map +1 -1
  11. package/package.json +72 -63
  12. package/prebuilds/darwin-arm64/@photostructure+sqlite.glibc.node +0 -0
  13. package/prebuilds/darwin-x64/@photostructure+sqlite.glibc.node +0 -0
  14. package/prebuilds/linux-arm64/@photostructure+sqlite.glibc.node +0 -0
  15. package/prebuilds/linux-arm64/@photostructure+sqlite.musl.node +0 -0
  16. package/prebuilds/linux-x64/@photostructure+sqlite.glibc.node +0 -0
  17. package/prebuilds/linux-x64/@photostructure+sqlite.musl.node +0 -0
  18. package/prebuilds/test_extension.so +0 -0
  19. package/prebuilds/win32-arm64/@photostructure+sqlite.glibc.node +0 -0
  20. package/prebuilds/win32-x64/@photostructure+sqlite.glibc.node +0 -0
  21. package/src/aggregate_function.cpp +222 -114
  22. package/src/aggregate_function.h +5 -6
  23. package/src/binding.cpp +30 -21
  24. package/src/enhance.ts +552 -0
  25. package/src/index.ts +84 -9
  26. package/src/shims/node_errors.h +34 -15
  27. package/src/shims/sqlite_errors.h +34 -8
  28. package/src/sql-tag-store.ts +6 -9
  29. package/src/sqlite_impl.cpp +1044 -394
  30. package/src/sqlite_impl.h +46 -7
  31. package/src/transaction.ts +178 -0
  32. package/src/types/database-sync-instance.ts +6 -40
  33. package/src/types/pragma-options.ts +23 -0
  34. package/src/types/statement-sync-instance.ts +38 -12
  35. package/src/types/transaction.ts +72 -0
  36. package/src/upstream/node_sqlite.cc +143 -43
  37. package/src/upstream/node_sqlite.h +15 -11
  38. package/src/upstream/sqlite3.c +102 -58
  39. package/src/upstream/sqlite3.h +5 -5
  40. package/src/user_function.cpp +138 -141
  41. package/src/user_function.h +3 -0
@@ -11,25 +11,33 @@ namespace node {
11
11
  inline void THROW_ERR_INVALID_STATE(Napi::Env env,
12
12
  const char *message = nullptr) {
13
13
  const char *msg = message ? message : "Invalid state";
14
- Napi::Error::New(env, msg).ThrowAsJavaScriptException();
14
+ Napi::Error error = Napi::Error::New(env, msg);
15
+ error.Set("code", Napi::String::New(env, "ERR_INVALID_STATE"));
16
+ error.ThrowAsJavaScriptException();
15
17
  }
16
18
 
17
19
  inline void THROW_ERR_INVALID_ARG_TYPE(Napi::Env env,
18
20
  const char *message = nullptr) {
19
21
  const char *msg = message ? message : "Invalid argument type";
20
- Napi::TypeError::New(env, msg).ThrowAsJavaScriptException();
22
+ Napi::TypeError error = Napi::TypeError::New(env, msg);
23
+ error.Set("code", Napi::String::New(env, "ERR_INVALID_ARG_TYPE"));
24
+ error.ThrowAsJavaScriptException();
21
25
  }
22
26
 
23
27
  inline void THROW_ERR_OUT_OF_RANGE(Napi::Env env,
24
28
  const char *message = nullptr) {
25
29
  const char *msg = message ? message : "Value out of range";
26
- Napi::RangeError::New(env, msg).ThrowAsJavaScriptException();
30
+ Napi::RangeError error = Napi::RangeError::New(env, msg);
31
+ error.Set("code", Napi::String::New(env, "ERR_OUT_OF_RANGE"));
32
+ error.ThrowAsJavaScriptException();
27
33
  }
28
34
 
29
35
  inline void THROW_ERR_INVALID_ARG_VALUE(Napi::Env env,
30
36
  const char *message = nullptr) {
31
37
  const char *msg = message ? message : "Invalid argument value";
32
- Napi::Error::New(env, msg).ThrowAsJavaScriptException();
38
+ Napi::Error error = Napi::Error::New(env, msg);
39
+ error.Set("code", Napi::String::New(env, "ERR_INVALID_ARG_VALUE"));
40
+ error.ThrowAsJavaScriptException();
33
41
  }
34
42
 
35
43
  inline void THROW_ERR_SQLITE_ERROR(Napi::Env env,
@@ -37,7 +45,9 @@ inline void THROW_ERR_SQLITE_ERROR(Napi::Env env,
37
45
  // Check for both null and empty string - on Windows (MSVC),
38
46
  // std::exception::what() can sometimes return an empty string
39
47
  const char *msg = (message && message[0] != '\0') ? message : "SQLite error";
40
- Napi::Error::New(env, msg).ThrowAsJavaScriptException();
48
+ Napi::Error error = Napi::Error::New(env, msg);
49
+ error.Set("code", Napi::String::New(env, "ERR_SQLITE_ERROR"));
50
+ error.ThrowAsJavaScriptException();
41
51
  }
42
52
 
43
53
  // Database-aware version available when sqlite_impl.h is included
@@ -45,24 +55,33 @@ inline void THROW_ERR_SQLITE_ERROR(Napi::Env env,
45
55
  // issues
46
56
 
47
57
  inline void THROW_ERR_CONSTRUCT_CALL_REQUIRED(Napi::Env env) {
48
- Napi::TypeError::New(env, "Class constructor cannot be invoked without 'new'")
49
- .ThrowAsJavaScriptException();
58
+ Napi::TypeError error = Napi::TypeError::New(
59
+ env, "Class constructor cannot be invoked without 'new'");
60
+ error.Set("code", Napi::String::New(env, "ERR_CONSTRUCT_CALL_REQUIRED"));
61
+ error.ThrowAsJavaScriptException();
62
+ }
63
+
64
+ inline void THROW_ERR_ILLEGAL_CONSTRUCTOR(Napi::Env env) {
65
+ Napi::TypeError error = Napi::TypeError::New(env, "Illegal constructor");
66
+ error.Set("code", Napi::String::New(env, "ERR_ILLEGAL_CONSTRUCTOR"));
67
+ error.ThrowAsJavaScriptException();
50
68
  }
51
69
 
52
70
  inline void THROW_ERR_INVALID_URL_SCHEME(Napi::Env env,
53
- const char *scheme = nullptr) {
54
- std::string msg = "Invalid URL scheme";
55
- if (scheme) {
56
- msg += ": ";
57
- msg += scheme;
58
- }
59
- Napi::TypeError::New(env, msg).ThrowAsJavaScriptException();
71
+ const char * /*scheme*/ = nullptr) {
72
+ // Message must match Node.js exactly
73
+ Napi::TypeError error =
74
+ Napi::TypeError::New(env, "The URL must be of scheme file:");
75
+ error.Set("code", Napi::String::New(env, "ERR_INVALID_URL_SCHEME"));
76
+ error.ThrowAsJavaScriptException();
60
77
  }
61
78
 
62
79
  inline void THROW_ERR_LOAD_SQLITE_EXTENSION(Napi::Env env,
63
80
  const char *message = nullptr) {
64
81
  const char *msg = message ? message : "Failed to load SQLite extension";
65
- Napi::Error::New(env, msg).ThrowAsJavaScriptException();
82
+ Napi::Error error = Napi::Error::New(env, msg);
83
+ error.Set("code", Napi::String::New(env, "ERR_LOAD_SQLITE_EXTENSION"));
84
+ error.ThrowAsJavaScriptException();
66
85
  }
67
86
 
68
87
  // Macro wrappers for compatibility (removed to avoid conflicts)
@@ -81,6 +81,14 @@ inline const char *GetSqliteErrorCodeName(int code) {
81
81
  }
82
82
 
83
83
  // Enhanced SQLite error that includes system errno information
84
+ // Error format matches Node.js node:sqlite for API compatibility:
85
+ // - code: 'ERR_SQLITE_ERROR' (constant, matches Node.js)
86
+ // - errcode: number (SQLite error code, matches Node.js)
87
+ // - errstr: string (SQLite error string, matches Node.js)
88
+ // We also add extra properties for enhanced debugging:
89
+ // - sqliteCode: number (same as errcode, for backward compat)
90
+ // - sqliteExtendedCode: number (extended SQLite error code)
91
+ // - sqliteErrorString: string (same as errstr, for backward compat)
84
92
  inline void ThrowEnhancedSqliteError(Napi::Env env, sqlite3 *db,
85
93
  int sqlite_code,
86
94
  const std::string &message) {
@@ -89,7 +97,16 @@ inline void ThrowEnhancedSqliteError(Napi::Env env, sqlite3 *db,
89
97
  // truncated or corrupted error messages
90
98
  Napi::Error error = Napi::Error::New(env, message.c_str());
91
99
 
92
- // Add SQLite error code information
100
+ // Node.js compatible properties
101
+ error.Set("code", Napi::String::New(env, "ERR_SQLITE_ERROR"));
102
+ error.Set("errcode", Napi::Number::New(env, sqlite_code));
103
+
104
+ const char *err_str = sqlite3_errstr(sqlite_code);
105
+ if (err_str) {
106
+ error.Set("errstr", Napi::String::New(env, err_str));
107
+ }
108
+
109
+ // Our enhanced properties (for backward compatibility and debugging)
93
110
  error.Set("sqliteCode", Napi::Number::New(env, sqlite_code));
94
111
 
95
112
  if (db) {
@@ -104,12 +121,11 @@ inline void ThrowEnhancedSqliteError(Napi::Env env, sqlite3 *db,
104
121
  }
105
122
  }
106
123
 
107
- // Set a standard error code property for compatibility
124
+ // Keep original code name as sqliteCodeName for debugging
108
125
  const char *code_name = GetSqliteErrorCodeName(sqlite_code);
109
- error.Set("code", Napi::String::New(env, code_name));
126
+ error.Set("sqliteCodeName", Napi::String::New(env, code_name));
110
127
 
111
128
  // Also set the human-readable error string
112
- const char *err_str = sqlite3_errstr(sqlite_code);
113
129
  if (err_str) {
114
130
  error.Set("sqliteErrorString", Napi::String::New(env, err_str));
115
131
  }
@@ -126,7 +142,9 @@ inline void ThrowEnhancedSqliteError(Napi::Env env, sqlite3 *db,
126
142
  inline void ThrowSqliteError(Napi::Env env, sqlite3 *db,
127
143
  const std::string &message) {
128
144
  if (db) {
129
- int errcode = sqlite3_errcode(db);
145
+ // Use extended error code (e.g., 1555 for SQLITE_CONSTRAINT_PRIMARYKEY)
146
+ // instead of basic code (e.g., 19 for SQLITE_CONSTRAINT) to match Node.js
147
+ int errcode = sqlite3_extended_errcode(db);
130
148
  ThrowEnhancedSqliteError(env, db, errcode, message);
131
149
  } else {
132
150
  // Fallback to simple error when no db handle available
@@ -136,12 +154,20 @@ inline void ThrowSqliteError(Napi::Env env, sqlite3 *db,
136
154
  }
137
155
 
138
156
  // Helper to throw from a SqliteException with captured error info
157
+ // Uses same format as ThrowEnhancedSqliteError for consistency
139
158
  inline void
140
159
  ThrowFromSqliteException(Napi::Env env,
141
160
  const photostructure::sqlite::SqliteException &ex) {
142
161
  Napi::Error error = Napi::Error::New(env, ex.what());
143
162
 
144
- // Add all captured error information
163
+ // Node.js compatible properties
164
+ error.Set("code", Napi::String::New(env, "ERR_SQLITE_ERROR"));
165
+ error.Set("errcode", Napi::Number::New(env, ex.sqlite_code()));
166
+ if (!ex.error_string().empty()) {
167
+ error.Set("errstr", Napi::String::New(env, ex.error_string().c_str()));
168
+ }
169
+
170
+ // Our enhanced properties (for backward compatibility and debugging)
145
171
  error.Set("sqliteCode", Napi::Number::New(env, ex.sqlite_code()));
146
172
  error.Set("sqliteExtendedCode", Napi::Number::New(env, ex.extended_code()));
147
173
 
@@ -149,9 +175,9 @@ ThrowFromSqliteException(Napi::Env env,
149
175
  error.Set("systemErrno", Napi::Number::New(env, ex.system_errno()));
150
176
  }
151
177
 
152
- // Set the error code name
178
+ // Keep original code name as sqliteCodeName for debugging
153
179
  const char *code_name = GetSqliteErrorCodeName(ex.sqlite_code());
154
- error.Set("code", Napi::String::New(env, code_name));
180
+ error.Set("sqliteCodeName", Napi::String::New(env, code_name));
155
181
 
156
182
  // Also set the human-readable error string
157
183
  // Use c_str() explicitly to avoid potential ABI issues on Windows ARM
@@ -25,7 +25,9 @@ export class SQLTagStore {
25
25
 
26
26
  constructor(db: DatabaseSyncInstance, capacity: number = DEFAULT_CAPACITY) {
27
27
  if (!db.isOpen) {
28
- throw new Error("Database is not open");
28
+ const err = new Error("database is not open");
29
+ (err as NodeJS.ErrnoException).code = "ERR_INVALID_STATE";
30
+ throw err;
29
31
  }
30
32
  this.database = db;
31
33
  this.maxCapacity = capacity;
@@ -101,23 +103,18 @@ export class SQLTagStore {
101
103
 
102
104
  /**
103
105
  * Get a cached statement or prepare a new one.
104
- * If a cached statement has been finalized, it's evicted and a new one is prepared.
105
106
  */
106
107
  private getOrPrepare(strings: TemplateStringsArray): StatementSyncInstance {
107
108
  if (!this.database.isOpen) {
108
- throw new Error("Database is not open");
109
+ throw new Error("database is not open");
109
110
  }
110
111
 
111
112
  const sql = this.buildSQL(strings);
112
113
 
113
- // Check cache - evict if finalized
114
+ // Check cache
114
115
  const cached = this.cache.get(sql);
115
116
  if (cached) {
116
- if (!cached.finalized) {
117
- return cached;
118
- }
119
- // Statement was finalized externally - remove from cache
120
- this.cache.delete(sql);
117
+ return cached;
121
118
  }
122
119
 
123
120
  // Prepare new statement and cache it