s3db.js 13.4.0 → 13.6.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 (110) hide show
  1. package/README.md +25 -10
  2. package/dist/{s3db.cjs.js → s3db.cjs} +38801 -32446
  3. package/dist/s3db.cjs.map +1 -0
  4. package/dist/s3db.es.js +38653 -32291
  5. package/dist/s3db.es.js.map +1 -1
  6. package/package.json +218 -22
  7. package/src/concerns/id.js +90 -6
  8. package/src/concerns/index.js +2 -1
  9. package/src/concerns/password-hashing.js +150 -0
  10. package/src/database.class.js +6 -2
  11. package/src/plugins/api/auth/basic-auth.js +40 -10
  12. package/src/plugins/api/auth/index.js +49 -3
  13. package/src/plugins/api/auth/oauth2-auth.js +171 -0
  14. package/src/plugins/api/auth/oidc-auth.js +789 -0
  15. package/src/plugins/api/auth/oidc-client.js +462 -0
  16. package/src/plugins/api/auth/path-auth-matcher.js +284 -0
  17. package/src/plugins/api/concerns/event-emitter.js +134 -0
  18. package/src/plugins/api/concerns/failban-manager.js +651 -0
  19. package/src/plugins/api/concerns/guards-helpers.js +402 -0
  20. package/src/plugins/api/concerns/metrics-collector.js +346 -0
  21. package/src/plugins/api/index.js +510 -57
  22. package/src/plugins/api/middlewares/failban.js +305 -0
  23. package/src/plugins/api/middlewares/rate-limit.js +301 -0
  24. package/src/plugins/api/middlewares/request-id.js +74 -0
  25. package/src/plugins/api/middlewares/security-headers.js +120 -0
  26. package/src/plugins/api/middlewares/session-tracking.js +194 -0
  27. package/src/plugins/api/routes/auth-routes.js +119 -78
  28. package/src/plugins/api/routes/resource-routes.js +73 -30
  29. package/src/plugins/api/server.js +1139 -45
  30. package/src/plugins/api/utils/custom-routes.js +102 -0
  31. package/src/plugins/api/utils/guards.js +213 -0
  32. package/src/plugins/api/utils/mime-types.js +154 -0
  33. package/src/plugins/api/utils/openapi-generator.js +91 -12
  34. package/src/plugins/api/utils/path-matcher.js +173 -0
  35. package/src/plugins/api/utils/static-filesystem.js +262 -0
  36. package/src/plugins/api/utils/static-s3.js +231 -0
  37. package/src/plugins/api/utils/template-engine.js +188 -0
  38. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
  39. package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
  40. package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
  41. package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
  42. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
  43. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
  44. package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
  45. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
  46. package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
  47. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
  48. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
  49. package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
  50. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
  51. package/src/plugins/cloud-inventory/index.js +20 -0
  52. package/src/plugins/cloud-inventory/registry.js +146 -0
  53. package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
  54. package/src/plugins/cloud-inventory.plugin.js +1333 -0
  55. package/src/plugins/concerns/plugin-dependencies.js +62 -2
  56. package/src/plugins/eventual-consistency/analytics.js +1 -0
  57. package/src/plugins/eventual-consistency/consolidation.js +2 -2
  58. package/src/plugins/eventual-consistency/garbage-collection.js +2 -2
  59. package/src/plugins/eventual-consistency/install.js +2 -2
  60. package/src/plugins/identity/README.md +335 -0
  61. package/src/plugins/identity/concerns/mfa-manager.js +204 -0
  62. package/src/plugins/identity/concerns/password.js +138 -0
  63. package/src/plugins/identity/concerns/resource-schemas.js +273 -0
  64. package/src/plugins/identity/concerns/token-generator.js +172 -0
  65. package/src/plugins/identity/email-service.js +422 -0
  66. package/src/plugins/identity/index.js +1052 -0
  67. package/src/plugins/identity/oauth2-server.js +1033 -0
  68. package/src/plugins/identity/oidc-discovery.js +285 -0
  69. package/src/plugins/identity/rsa-keys.js +323 -0
  70. package/src/plugins/identity/server.js +500 -0
  71. package/src/plugins/identity/session-manager.js +453 -0
  72. package/src/plugins/identity/ui/layouts/base.js +251 -0
  73. package/src/plugins/identity/ui/middleware.js +135 -0
  74. package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
  75. package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
  76. package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
  77. package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
  78. package/src/plugins/identity/ui/pages/admin/users.js +263 -0
  79. package/src/plugins/identity/ui/pages/consent.js +262 -0
  80. package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
  81. package/src/plugins/identity/ui/pages/login.js +144 -0
  82. package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
  83. package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
  84. package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
  85. package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
  86. package/src/plugins/identity/ui/pages/profile.js +361 -0
  87. package/src/plugins/identity/ui/pages/register.js +226 -0
  88. package/src/plugins/identity/ui/pages/reset-password.js +128 -0
  89. package/src/plugins/identity/ui/pages/verify-email.js +172 -0
  90. package/src/plugins/identity/ui/routes.js +2541 -0
  91. package/src/plugins/identity/ui/styles/main.css +465 -0
  92. package/src/plugins/index.js +4 -1
  93. package/src/plugins/ml/base-model.class.js +65 -16
  94. package/src/plugins/ml/classification-model.class.js +1 -1
  95. package/src/plugins/ml/timeseries-model.class.js +3 -1
  96. package/src/plugins/ml.plugin.js +584 -31
  97. package/src/plugins/shared/error-handler.js +147 -0
  98. package/src/plugins/shared/index.js +9 -0
  99. package/src/plugins/shared/middlewares/compression.js +117 -0
  100. package/src/plugins/shared/middlewares/cors.js +49 -0
  101. package/src/plugins/shared/middlewares/index.js +11 -0
  102. package/src/plugins/shared/middlewares/logging.js +54 -0
  103. package/src/plugins/shared/middlewares/rate-limit.js +73 -0
  104. package/src/plugins/shared/middlewares/security.js +158 -0
  105. package/src/plugins/shared/response-formatter.js +264 -0
  106. package/src/plugins/state-machine.plugin.js +57 -2
  107. package/src/resource.class.js +140 -12
  108. package/src/schema.class.js +30 -1
  109. package/src/validator.class.js +57 -6
  110. package/dist/s3db.cjs.js.map +0 -1
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "s3db.js",
3
- "version": "13.4.0",
3
+ "version": "13.6.0",
4
4
  "description": "Use AWS S3, the world's most reliable document storage, as a database with this ORM.",
5
- "main": "dist/s3db.cjs.js",
5
+ "main": "dist/s3db.cjs",
6
6
  "module": "dist/s3db.es.js",
7
7
  "types": "dist/s3db.d.ts",
8
8
  "author": "@stone/martech",
@@ -51,12 +51,16 @@
51
51
  ".": {
52
52
  "types": "./dist/s3db.d.ts",
53
53
  "import": "./dist/s3db.es.js",
54
- "require": "./dist/s3db.cjs.js"
54
+ "require": "./dist/s3db.cjs"
55
55
  },
56
56
  "./typescript-generator": {
57
57
  "types": "./src/concerns/typescript-generator.d.ts",
58
58
  "import": "./src/concerns/typescript-generator.js",
59
59
  "require": "./src/concerns/typescript-generator.js"
60
+ },
61
+ "./concerns/guards-helpers": {
62
+ "import": "./src/concerns/guards-helpers.js",
63
+ "require": "./src/concerns/guards-helpers.js"
60
64
  }
61
65
  },
62
66
  "files": [
@@ -70,12 +74,13 @@
70
74
  "UNLICENSE"
71
75
  ],
72
76
  "dependencies": {
73
- "@aws-sdk/client-s3": "^3.914.0",
74
- "@modelcontextprotocol/sdk": "^1.20.1",
75
- "@smithy/node-http-handler": "^4.4.2",
77
+ "@aws-sdk/client-s3": "^3.920.0",
78
+ "@aws-sdk/credential-providers": "^3.920.0",
79
+ "@aws-sdk/s3-request-presigner": "^3.920.0",
80
+ "@modelcontextprotocol/sdk": "^1.20.2",
81
+ "@smithy/node-http-handler": "^4.4.3",
76
82
  "@supercharge/promise-pool": "^3.2.0",
77
83
  "dotenv": "^17.2.3",
78
- "express": "^5.1.0",
79
84
  "fastest-validator": "^1.19.1",
80
85
  "flat": "^6.0.1",
81
86
  "glob": "^11.0.3",
@@ -84,25 +89,165 @@
84
89
  "nanoid": "5.1.6"
85
90
  },
86
91
  "peerDependencies": {
92
+ "@aws-sdk/client-acm": "^3.0.0",
93
+ "@aws-sdk/client-api-gateway": "^3.0.0",
94
+ "@aws-sdk/client-apigatewayv2": "^3.0.0",
95
+ "@aws-sdk/client-backup": "^3.0.0",
96
+ "@aws-sdk/client-cloudfront": "^3.0.0",
97
+ "@aws-sdk/client-cloudtrail": "^3.0.0",
98
+ "@aws-sdk/client-cloudwatch": "^3.0.0",
99
+ "@aws-sdk/client-cloudwatch-logs": "^3.0.0",
100
+ "@aws-sdk/client-cognito-identity-provider": "^3.0.0",
101
+ "@aws-sdk/client-config-service": "^3.0.0",
102
+ "@aws-sdk/client-dynamodb": "^3.0.0",
103
+ "@aws-sdk/client-ec2": "^3.0.0",
104
+ "@aws-sdk/client-ecr": "^3.0.0",
105
+ "@aws-sdk/client-ecs": "^3.0.0",
106
+ "@aws-sdk/client-efs": "^3.0.0",
107
+ "@aws-sdk/client-eks": "^3.0.0",
108
+ "@aws-sdk/client-elastic-load-balancing": "^3.0.0",
109
+ "@aws-sdk/client-elastic-load-balancing-v2": "^3.0.0",
110
+ "@aws-sdk/client-elasticache": "^3.0.0",
111
+ "@aws-sdk/client-eventbridge": "^3.0.0",
112
+ "@aws-sdk/client-iam": "^3.0.0",
113
+ "@aws-sdk/client-kinesis": "^3.0.0",
114
+ "@aws-sdk/client-kms": "^3.0.0",
115
+ "@aws-sdk/client-lambda": "^3.0.0",
116
+ "@aws-sdk/client-rds": "^3.0.0",
117
+ "@aws-sdk/client-route-53": "^3.0.0",
118
+ "@aws-sdk/client-secrets-manager": "^3.0.0",
119
+ "@aws-sdk/client-sfn": "^3.0.0",
120
+ "@aws-sdk/client-sns": "^3.0.0",
87
121
  "@aws-sdk/client-sqs": "^3.0.0",
122
+ "@aws-sdk/client-ssm": "^3.0.0",
123
+ "@aws-sdk/client-sts": "^3.0.0",
124
+ "@aws-sdk/client-waf": "^3.0.0",
125
+ "@aws-sdk/client-wafv2": "^3.0.0",
88
126
  "@google-cloud/bigquery": "^7.0.0",
127
+ "google-auth-library": "^9.0.0 || ^10.0.0",
89
128
  "@hono/node-server": "^1.0.0",
90
- "@hono/swagger-ui": "^0.5.0",
129
+ "@hono/swagger-ui": "^0.5.2",
91
130
  "@libsql/client": "^0.14.0",
92
131
  "@planetscale/database": "^1.0.0",
93
132
  "@tensorflow/tfjs-node": "^4.0.0",
94
133
  "amqplib": "^0.10.8",
134
+ "bcrypt": "^5.0.0 || ^6.0.0",
135
+ "express": "^4.0.0 || ^5.0.0",
95
136
  "hono": "^4.0.0",
137
+ "jose": "^5.0.0 || ^6.0.0",
96
138
  "node-cron": "^4.0.0",
139
+ "nodemailer": "^6.0.0 || ^7.0.0",
97
140
  "pg": "^8.0.0"
98
141
  },
99
142
  "peerDependenciesMeta": {
143
+ "@aws-sdk/client-acm": {
144
+ "optional": true
145
+ },
146
+ "@aws-sdk/client-api-gateway": {
147
+ "optional": true
148
+ },
149
+ "@aws-sdk/client-apigatewayv2": {
150
+ "optional": true
151
+ },
152
+ "@aws-sdk/client-backup": {
153
+ "optional": true
154
+ },
155
+ "@aws-sdk/client-cloudfront": {
156
+ "optional": true
157
+ },
158
+ "@aws-sdk/client-cloudtrail": {
159
+ "optional": true
160
+ },
161
+ "@aws-sdk/client-cloudwatch": {
162
+ "optional": true
163
+ },
164
+ "@aws-sdk/client-cloudwatch-logs": {
165
+ "optional": true
166
+ },
167
+ "@aws-sdk/client-cognito-identity-provider": {
168
+ "optional": true
169
+ },
170
+ "@aws-sdk/client-config-service": {
171
+ "optional": true
172
+ },
173
+ "@aws-sdk/client-dynamodb": {
174
+ "optional": true
175
+ },
176
+ "@aws-sdk/client-ec2": {
177
+ "optional": true
178
+ },
179
+ "@aws-sdk/client-ecr": {
180
+ "optional": true
181
+ },
182
+ "@aws-sdk/client-ecs": {
183
+ "optional": true
184
+ },
185
+ "@aws-sdk/client-efs": {
186
+ "optional": true
187
+ },
188
+ "@aws-sdk/client-eks": {
189
+ "optional": true
190
+ },
191
+ "@aws-sdk/client-elasticache": {
192
+ "optional": true
193
+ },
194
+ "@aws-sdk/client-elastic-load-balancing": {
195
+ "optional": true
196
+ },
197
+ "@aws-sdk/client-elastic-load-balancing-v2": {
198
+ "optional": true
199
+ },
200
+ "@aws-sdk/client-eventbridge": {
201
+ "optional": true
202
+ },
203
+ "@aws-sdk/client-iam": {
204
+ "optional": true
205
+ },
206
+ "@aws-sdk/client-kinesis": {
207
+ "optional": true
208
+ },
209
+ "@aws-sdk/client-kms": {
210
+ "optional": true
211
+ },
212
+ "@aws-sdk/client-lambda": {
213
+ "optional": true
214
+ },
215
+ "@aws-sdk/client-rds": {
216
+ "optional": true
217
+ },
218
+ "@aws-sdk/client-route-53": {
219
+ "optional": true
220
+ },
221
+ "@aws-sdk/client-secrets-manager": {
222
+ "optional": true
223
+ },
224
+ "@aws-sdk/client-sfn": {
225
+ "optional": true
226
+ },
227
+ "@aws-sdk/client-sns": {
228
+ "optional": true
229
+ },
100
230
  "@aws-sdk/client-sqs": {
101
231
  "optional": true
102
232
  },
233
+ "@aws-sdk/client-ssm": {
234
+ "optional": true
235
+ },
236
+ "@aws-sdk/client-sts": {
237
+ "optional": true
238
+ },
239
+ "@aws-sdk/client-waf": {
240
+ "optional": true
241
+ },
242
+ "@aws-sdk/client-wafv2": {
243
+ "optional": true
244
+ },
103
245
  "@google-cloud/bigquery": {
104
246
  "optional": true
105
247
  },
248
+ "google-auth-library": {
249
+ "optional": true
250
+ },
106
251
  "@hono/node-server": {
107
252
  "optional": true
108
253
  },
@@ -118,48 +263,99 @@
118
263
  "@tensorflow/tfjs-node": {
119
264
  "optional": true
120
265
  },
121
- "pg": {
266
+ "amqplib": {
122
267
  "optional": true
123
268
  },
124
- "amqplib": {
269
+ "bcrypt": {
270
+ "optional": true
271
+ },
272
+ "express": {
125
273
  "optional": true
126
274
  },
127
275
  "hono": {
128
276
  "optional": true
129
277
  },
278
+ "jose": {
279
+ "optional": true
280
+ },
130
281
  "node-cron": {
131
282
  "optional": true
283
+ },
284
+ "nodemailer": {
285
+ "optional": true
286
+ },
287
+ "pg": {
288
+ "optional": true
132
289
  }
133
290
  },
134
291
  "devDependencies": {
135
- "@aws-sdk/client-sqs": "^3.914.0",
136
- "@babel/core": "^7.28.4",
137
- "@babel/preset-env": "^7.28.3",
292
+ "@aws-sdk/client-acm": "^3.920.0",
293
+ "@aws-sdk/client-api-gateway": "^3.920.0",
294
+ "@aws-sdk/client-apigatewayv2": "^3.920.0",
295
+ "@aws-sdk/client-backup": "^3.920.0",
296
+ "@aws-sdk/client-cloudfront": "^3.920.0",
297
+ "@aws-sdk/client-cloudtrail": "^3.920.0",
298
+ "@aws-sdk/client-cloudwatch": "^3.920.0",
299
+ "@aws-sdk/client-cloudwatch-logs": "^3.920.0",
300
+ "@aws-sdk/client-cognito-identity-provider": "^3.920.0",
301
+ "@aws-sdk/client-config-service": "^3.920.0",
302
+ "@aws-sdk/client-dynamodb": "^3.920.0",
303
+ "@aws-sdk/client-ec2": "^3.920.0",
304
+ "@aws-sdk/client-ecr": "^3.920.0",
305
+ "@aws-sdk/client-ecs": "^3.920.0",
306
+ "@aws-sdk/client-efs": "^3.920.0",
307
+ "@aws-sdk/client-eks": "^3.920.0",
308
+ "@aws-sdk/client-elastic-load-balancing": "^3.920.0",
309
+ "@aws-sdk/client-elastic-load-balancing-v2": "^3.920.0",
310
+ "@aws-sdk/client-elasticache": "^3.920.0",
311
+ "@aws-sdk/client-eventbridge": "^3.920.0",
312
+ "@aws-sdk/client-iam": "^3.920.0",
313
+ "@aws-sdk/client-kinesis": "^3.920.0",
314
+ "@aws-sdk/client-kms": "^3.920.0",
315
+ "@aws-sdk/client-lambda": "^3.920.0",
316
+ "@aws-sdk/client-rds": "^3.920.0",
317
+ "@aws-sdk/client-route-53": "^3.920.0",
318
+ "@aws-sdk/client-secrets-manager": "^3.920.0",
319
+ "@aws-sdk/client-sfn": "^3.920.0",
320
+ "@aws-sdk/client-sns": "^3.920.0",
321
+ "@aws-sdk/client-sqs": "^3.920.0",
322
+ "@aws-sdk/client-ssm": "^3.920.0",
323
+ "@aws-sdk/client-sts": "^3.920.0",
324
+ "@aws-sdk/client-waf": "^3.920.0",
325
+ "@aws-sdk/client-wafv2": "^3.920.0",
326
+ "@babel/core": "^7.28.5",
327
+ "@babel/preset-env": "^7.28.5",
138
328
  "@google-cloud/bigquery": "^7.9.4",
139
- "@hono/node-server": "^1.13.7",
140
- "@hono/swagger-ui": "^0.5.3",
329
+ "@hono/node-server": "^1.19.5",
330
+ "@hono/swagger-ui": "^0.5.2",
141
331
  "@libsql/client": "^0.14.0",
142
332
  "@planetscale/database": "^1.19.0",
143
- "@rollup/plugin-commonjs": "^28.0.8",
333
+ "@rollup/plugin-commonjs": "^28.0.9",
144
334
  "@rollup/plugin-json": "^6.1.0",
145
335
  "@rollup/plugin-node-resolve": "^16.0.3",
146
- "@rollup/plugin-replace": "^6.0.2",
336
+ "@rollup/plugin-replace": "^6.0.3",
147
337
  "@rollup/plugin-terser": "^0.4.4",
148
338
  "@tensorflow/tfjs-node": "^4.22.0",
149
339
  "@types/node": "24.7.0",
150
340
  "@xenova/transformers": "^2.17.2",
151
- "@yao-pkg/pkg": "^6.9.0",
341
+ "@yao-pkg/pkg": "^6.10.0",
152
342
  "amqplib": "^0.10.9",
153
343
  "babel-loader": "^10.0.0",
344
+ "bcrypt": "^6.0.0",
154
345
  "chalk": "^5.6.2",
155
346
  "cli-table3": "^0.6.5",
156
- "commander": "^14.0.1",
347
+ "commander": "^14.0.2",
157
348
  "esbuild": "^0.25.11",
158
- "hono": "^4.7.11",
349
+ "express": "^5.1.0",
350
+ "google-auth-library": "^10.4.2",
351
+ "hono": "^4.10.4",
159
352
  "inquirer": "^12.10.0",
160
353
  "jest": "^30.2.0",
354
+ "jose": "^6.1.0",
161
355
  "node-cron": "^4.2.1",
162
356
  "node-loader": "^2.1.0",
357
+ "nodemailer": "^7.0.10",
358
+ "nodemon": "^3.1.10",
163
359
  "ora": "^9.0.0",
164
360
  "pg": "^8.16.3",
165
361
  "rollup": "^4.52.5",
@@ -188,7 +384,7 @@
188
384
  "test:js": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js",
189
385
  "test:quick": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --maxWorkers=2 --bail",
190
386
  "test:ts": "tsc --noEmit --project tests/typescript/tsconfig.json",
191
- "test:coverage": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --coverage --maxWorkers=1",
387
+ "test:coverage": "pnpm run test:js -- --coverage --runInBand && pnpm run test:ts",
192
388
  "test:serial": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand",
193
389
  "test:plugins": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js tests/plugins/ --testTimeout=60000",
194
390
  "test:full": "pnpm run test:js && pnpm run test:ts",
@@ -198,7 +394,7 @@
198
394
  "validate:types": "pnpm run test:ts && echo 'TypeScript definitions are valid!'",
199
395
  "test:ts:runtime": "tsx tests/typescript/types-runtime-simple.ts",
200
396
  "test:mcp": "node mcp/entrypoint.js --help",
201
- "install:peers": "pnpm add -D @aws-sdk/client-sqs @google-cloud/bigquery @hono/node-server @hono/swagger-ui @libsql/client @planetscale/database @tensorflow/tfjs-node amqplib hono node-cron pg",
397
+ "install:peers": "pnpm add -D @aws-sdk/client-acm @aws-sdk/client-api-gateway @aws-sdk/client-apigatewayv2 @aws-sdk/client-backup @aws-sdk/client-cloudfront @aws-sdk/client-cloudtrail @aws-sdk/client-cloudwatch @aws-sdk/client-cloudwatch-logs @aws-sdk/client-cognito-identity-provider @aws-sdk/client-config-service @aws-sdk/client-dynamodb @aws-sdk/client-ec2 @aws-sdk/client-ecr @aws-sdk/client-ecs @aws-sdk/client-efs @aws-sdk/client-eks @aws-sdk/client-elasticache @aws-sdk/client-elastic-load-balancing @aws-sdk/client-elastic-load-balancing-v2 @aws-sdk/client-eventbridge @aws-sdk/client-iam @aws-sdk/client-kinesis @aws-sdk/client-kms @aws-sdk/client-lambda @aws-sdk/client-rds @aws-sdk/client-route-53 @aws-sdk/client-secrets-manager @aws-sdk/client-sfn @aws-sdk/client-sns @aws-sdk/client-sqs @aws-sdk/client-ssm @aws-sdk/client-sts @aws-sdk/client-waf @aws-sdk/client-wafv2 @google-cloud/bigquery google-auth-library @hono/node-server @hono/swagger-ui @libsql/client @planetscale/database @tensorflow/tfjs-node amqplib bcrypt express hono jose node-cron nodemailer pg",
202
398
  "install:peers:script": "./scripts/install-peer-deps.sh"
203
399
  }
204
400
  }
@@ -1,8 +1,92 @@
1
- import { customAlphabet, urlAlphabet } from 'nanoid'
1
+ import { randomFillSync } from 'node:crypto';
2
2
 
3
- export const idGenerator = customAlphabet(urlAlphabet, 22)
3
+ // Fallback URL alphabet taken from nanoid's source. Using it keeps generated IDs stable
4
+ // even while we await the official nanoid implementation to load.
5
+ const FALLBACK_URL_ALPHABET =
6
+ 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
4
7
 
5
- // Password generator using nanoid with custom alphabet for better readability
6
- // Excludes similar characters (0, O, 1, l, I) to avoid confusion
7
- const passwordAlphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789'
8
- export const passwordGenerator = customAlphabet(passwordAlphabet, 16)
8
+ // Password generator using nanoid-style alphabet, excluding visually similar characters.
9
+ const PASSWORD_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789';
10
+
11
+ const POOL_SIZE_MULTIPLIER = 128;
12
+ let pool;
13
+ let poolOffset = 0;
14
+
15
+ function fillPool(bytes) {
16
+ if (!pool || pool.length < bytes) {
17
+ pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
18
+ randomFillSync(pool);
19
+ poolOffset = 0;
20
+ } else if (poolOffset + bytes > pool.length) {
21
+ randomFillSync(pool);
22
+ poolOffset = 0;
23
+ }
24
+ poolOffset += bytes;
25
+ }
26
+
27
+ function randomFromPool(bytes) {
28
+ fillPool((bytes |= 0));
29
+ return pool.subarray(poolOffset - bytes, poolOffset);
30
+ }
31
+
32
+ function customRandomFallback(alphabet, defaultSize, getRandom) {
33
+ const mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1;
34
+ const step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length);
35
+
36
+ return (size = defaultSize) => {
37
+ if (!size) return '';
38
+
39
+ let id = '';
40
+ while (true) {
41
+ const bytes = getRandom(step);
42
+ let i = step;
43
+ while (i--) {
44
+ id += alphabet[bytes[i] & mask] || '';
45
+ if (id.length >= size) return id;
46
+ }
47
+ }
48
+ };
49
+ }
50
+
51
+ function customAlphabetFallback(alphabet, size = 21) {
52
+ return customRandomFallback(alphabet, size, randomFromPool);
53
+ }
54
+
55
+ let activeCustomAlphabet = customAlphabetFallback;
56
+ let activeUrlAlphabet = FALLBACK_URL_ALPHABET;
57
+ let idGeneratorImpl = activeCustomAlphabet(activeUrlAlphabet, 22);
58
+ let passwordGeneratorImpl = activeCustomAlphabet(PASSWORD_ALPHABET, 16);
59
+ let nanoidInitializationError = null;
60
+
61
+ const nanoidReadyPromise = import('nanoid')
62
+ .then((mod) => {
63
+ const resolvedCustomAlphabet = mod?.customAlphabet ?? activeCustomAlphabet;
64
+ const resolvedUrlAlphabet = mod?.urlAlphabet ?? activeUrlAlphabet;
65
+
66
+ activeCustomAlphabet = resolvedCustomAlphabet;
67
+ activeUrlAlphabet = resolvedUrlAlphabet;
68
+ idGeneratorImpl = activeCustomAlphabet(activeUrlAlphabet, 22);
69
+ passwordGeneratorImpl = activeCustomAlphabet(PASSWORD_ALPHABET, 16);
70
+ })
71
+ .catch((error) => {
72
+ nanoidInitializationError = error;
73
+ if (typeof process !== 'undefined' && process?.env?.S3DB_DEBUG) {
74
+ console.warn('[s3db] Failed to dynamically import "nanoid". Using fallback implementation.', error);
75
+ }
76
+ });
77
+
78
+ export function initializeNanoid() {
79
+ return nanoidReadyPromise;
80
+ }
81
+
82
+ export function getNanoidInitializationError() {
83
+ return nanoidInitializationError;
84
+ }
85
+
86
+ export const idGenerator = (...args) => idGeneratorImpl(...args);
87
+
88
+ export const passwordGenerator = (...args) => passwordGeneratorImpl(...args);
89
+
90
+ export const getUrlAlphabet = () => activeUrlAlphabet;
91
+
92
+ export const createCustomGenerator = (alphabet, size) => activeCustomAlphabet(alphabet, size);
@@ -1,5 +1,6 @@
1
1
  export * from './base62.js';
2
- export * from './calculator.js';
2
+ export * from './calculator.js';
3
3
  export * from './crypto.js';
4
+ export * from './password-hashing.js';
4
5
  export * from './id.js';
5
6
  export * from './try-fn.js';
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Password Hashing with bcrypt
3
+ *
4
+ * Provides secure one-way password hashing with space optimization.
5
+ * Bcrypt hashes are 60 bytes, but we compact them to 52 bytes by removing
6
+ * the version/rounds prefix ($2b$10$) which we can reconstruct.
7
+ */
8
+
9
+ import bcrypt from 'bcrypt';
10
+
11
+ /**
12
+ * Hash a password using bcrypt (synchronous)
13
+ * @param {string} password - Plaintext password
14
+ * @param {number} [rounds=10] - Bcrypt cost factor (4-31, default 10)
15
+ * @returns {string} Bcrypt hash (60 bytes)
16
+ */
17
+ export function hashPasswordSync(password, rounds = 10) {
18
+ if (!password || typeof password !== 'string') {
19
+ throw new Error('Password must be a non-empty string');
20
+ }
21
+
22
+ if (rounds < 4 || rounds > 31) {
23
+ throw new Error('Bcrypt rounds must be between 4 and 31');
24
+ }
25
+
26
+ return bcrypt.hashSync(password, rounds);
27
+ }
28
+
29
+ /**
30
+ * Hash a password using bcrypt
31
+ * @param {string} password - Plaintext password
32
+ * @param {number} [rounds=10] - Bcrypt cost factor (4-31, default 10)
33
+ * @returns {Promise<string>} Bcrypt hash (60 bytes)
34
+ */
35
+ export async function hashPassword(password, rounds = 10) {
36
+ if (!password || typeof password !== 'string') {
37
+ throw new Error('Password must be a non-empty string');
38
+ }
39
+
40
+ if (rounds < 4 || rounds > 31) {
41
+ throw new Error('Bcrypt rounds must be between 4 and 31');
42
+ }
43
+
44
+ return await bcrypt.hash(password, rounds);
45
+ }
46
+
47
+ /**
48
+ * Verify a password against a bcrypt hash
49
+ * @param {string} plaintext - Plaintext password to verify
50
+ * @param {string} hash - Bcrypt hash (can be full 60-byte or compact 52-byte)
51
+ * @returns {Promise<boolean>} True if password matches
52
+ */
53
+ export async function verifyPassword(plaintext, hash) {
54
+ if (!plaintext || typeof plaintext !== 'string') {
55
+ return false;
56
+ }
57
+
58
+ if (!hash || typeof hash !== 'string') {
59
+ return false;
60
+ }
61
+
62
+ try {
63
+ // If hash doesn't start with $, it's compacted - expand it first
64
+ const fullHash = hash.startsWith('$') ? hash : expandHash(hash);
65
+ return await bcrypt.compare(plaintext, fullHash);
66
+ } catch (error) {
67
+ // Invalid hash format
68
+ return false;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Compact a bcrypt hash by removing the prefix
74
+ *
75
+ * Bcrypt format: $2b$10$saltsaltsaltsaltsalthashhashhashhashhashhashhashh
76
+ * Compacted: saltsaltsaltsaltsalthashhashhashhashhashhashhashh
77
+ *
78
+ * Saves 7 bytes (11.6% reduction: 60 → 53 bytes)
79
+ *
80
+ * @param {string} bcryptHash - Full bcrypt hash (60 bytes)
81
+ * @returns {string} Compacted hash (53 bytes)
82
+ */
83
+ export function compactHash(bcryptHash) {
84
+ if (!bcryptHash || typeof bcryptHash !== 'string') {
85
+ throw new Error('Invalid bcrypt hash');
86
+ }
87
+
88
+ // Bcrypt format: $2a$10$ or $2b$10$ or $2y$10$
89
+ if (!bcryptHash.startsWith('$2')) {
90
+ throw new Error('Not a valid bcrypt hash');
91
+ }
92
+
93
+ // Remove prefix (e.g., "$2b$10$")
94
+ const parts = bcryptHash.split('$');
95
+ if (parts.length !== 4) {
96
+ throw new Error('Invalid bcrypt hash format');
97
+ }
98
+
99
+ // Return just the salt+hash part (last element after split)
100
+ return parts[3];
101
+ }
102
+
103
+ /**
104
+ * Expand a compacted bcrypt hash by restoring the prefix
105
+ *
106
+ * @param {string} compactHash - Compacted hash (53 bytes)
107
+ * @param {number} [rounds=10] - Bcrypt rounds used (default 10)
108
+ * @returns {string} Full bcrypt hash (60 bytes)
109
+ */
110
+ export function expandHash(compactHash, rounds = 10) {
111
+ if (!compactHash || typeof compactHash !== 'string') {
112
+ throw new Error('Invalid compacted hash');
113
+ }
114
+
115
+ // If it's already a full hash, return as-is
116
+ if (compactHash.startsWith('$')) {
117
+ return compactHash;
118
+ }
119
+
120
+ // Reconstruct prefix: $2b${rounds}$
121
+ const roundsStr = rounds.toString().padStart(2, '0');
122
+ return `$2b$${roundsStr}$${compactHash}`;
123
+ }
124
+
125
+ /**
126
+ * Check if a string is a bcrypt hash (full or compact)
127
+ * @param {string} str - String to check
128
+ * @returns {boolean} True if it looks like a bcrypt hash
129
+ */
130
+ export function isBcryptHash(str) {
131
+ if (!str || typeof str !== 'string') {
132
+ return false;
133
+ }
134
+
135
+ // Full hash: starts with $2
136
+ if (str.startsWith('$2')) {
137
+ return str.length === 60;
138
+ }
139
+
140
+ // Compact hash: 53 characters (22 salt + 31 hash)
141
+ return str.length === 53;
142
+ }
143
+
144
+ export default {
145
+ hashPassword,
146
+ verifyPassword,
147
+ compactHash,
148
+ expandHash,
149
+ isBcryptHash
150
+ };
@@ -66,6 +66,7 @@ export class Database extends EventEmitter {
66
66
  this.plugins = this.pluginRegistry; // Alias for plugin registry
67
67
  this.cache = options.cache;
68
68
  this.passphrase = options.passphrase || "secret";
69
+ this.bcryptRounds = options.bcryptRounds || 10;
69
70
  this.versioningEnabled = options.versioningEnabled || false;
70
71
  this.persistHooks = options.persistHooks || false; // New configuration for hook persistence
71
72
  this.strictValidation = options.strictValidation !== false; // Enable strict validation by default
@@ -235,6 +236,7 @@ export class Database extends EventEmitter {
235
236
  behavior: versionData.behavior || 'user-managed',
236
237
  parallelism: this.parallelism,
237
238
  passphrase: this.passphrase,
239
+ bcryptRounds: this.bcryptRounds,
238
240
  observers: [this],
239
241
  cache: this.cache,
240
242
  timestamps: versionData.timestamps !== undefined ? versionData.timestamps : false,
@@ -967,6 +969,7 @@ export class Database extends EventEmitter {
967
969
  client: this.client,
968
970
  version: existingResource.version,
969
971
  passphrase: this.passphrase,
972
+ bcryptRounds: this.bcryptRounds,
970
973
  versioningEnabled: this.versioningEnabled
971
974
  });
972
975
 
@@ -1091,7 +1094,7 @@ export class Database extends EventEmitter {
1091
1094
  if (!existingVersionData || existingVersionData.hash !== newHash) {
1092
1095
  await this.uploadMetadataFile();
1093
1096
  }
1094
- this.emit("s3db.resourceUpdated", name);
1097
+ this.emit("db:resource-updated", name);
1095
1098
  return existingResource;
1096
1099
  }
1097
1100
  const existingMetadata = this.savedMetadata?.resources?.[name];
@@ -1104,6 +1107,7 @@ export class Database extends EventEmitter {
1104
1107
  behavior,
1105
1108
  parallelism: this.parallelism,
1106
1109
  passphrase: config.passphrase !== undefined ? config.passphrase : this.passphrase,
1110
+ bcryptRounds: config.bcryptRounds !== undefined ? config.bcryptRounds : this.bcryptRounds,
1107
1111
  observers: [this],
1108
1112
  cache: config.cache !== undefined ? config.cache : this.cache,
1109
1113
  timestamps: config.timestamps !== undefined ? config.timestamps : false,
@@ -1131,7 +1135,7 @@ export class Database extends EventEmitter {
1131
1135
  }
1132
1136
 
1133
1137
  await this.uploadMetadataFile();
1134
- this.emit("s3db.resourceCreated", name);
1138
+ this.emit("db:resource-created", name);
1135
1139
  return resource;
1136
1140
  }
1137
1141