mongo.do 0.1.0 → 0.1.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.
- package/dist/cli/index.d.ts +1 -15
- package/dist/cli/index.js +4083 -196
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +2740 -20
- package/dist/index.js +9375 -27
- package/dist/index.js.map +1 -1
- package/dist/worker-entrypoint-BEW23Gmp.d.ts +1331 -0
- package/dist/worker.d.ts +6 -5
- package/dist/worker.js +5477 -18
- package/dist/worker.js.map +1 -1
- package/package.json +3 -2
- package/dist/agentfs/adapters/anthropic.d.ts +0 -176
- package/dist/agentfs/adapters/anthropic.d.ts.map +0 -1
- package/dist/agentfs/adapters/anthropic.js +0 -629
- package/dist/agentfs/adapters/anthropic.js.map +0 -1
- package/dist/agentfs/adapters/index.d.ts +0 -21
- package/dist/agentfs/adapters/index.d.ts.map +0 -1
- package/dist/agentfs/adapters/index.js +0 -23
- package/dist/agentfs/adapters/index.js.map +0 -1
- package/dist/agentfs/adapters/vercel.d.ts +0 -260
- package/dist/agentfs/adapters/vercel.d.ts.map +0 -1
- package/dist/agentfs/adapters/vercel.js +0 -288
- package/dist/agentfs/adapters/vercel.js.map +0 -1
- package/dist/agentfs/glob.d.ts +0 -116
- package/dist/agentfs/glob.d.ts.map +0 -1
- package/dist/agentfs/glob.js +0 -270
- package/dist/agentfs/glob.js.map +0 -1
- package/dist/agentfs/grep.d.ts +0 -83
- package/dist/agentfs/grep.d.ts.map +0 -1
- package/dist/agentfs/grep.js +0 -193
- package/dist/agentfs/grep.js.map +0 -1
- package/dist/agentfs/index.d.ts +0 -22
- package/dist/agentfs/index.d.ts.map +0 -1
- package/dist/agentfs/index.js +0 -18
- package/dist/agentfs/index.js.map +0 -1
- package/dist/agentfs/kv-store.d.ts +0 -128
- package/dist/agentfs/kv-store.d.ts.map +0 -1
- package/dist/agentfs/kv-store.js +0 -227
- package/dist/agentfs/kv-store.js.map +0 -1
- package/dist/agentfs/mondo-agent.d.ts +0 -255
- package/dist/agentfs/mondo-agent.d.ts.map +0 -1
- package/dist/agentfs/mondo-agent.js +0 -879
- package/dist/agentfs/mondo-agent.js.map +0 -1
- package/dist/agentfs/toolcalls.d.ts +0 -130
- package/dist/agentfs/toolcalls.d.ts.map +0 -1
- package/dist/agentfs/toolcalls.js +0 -178
- package/dist/agentfs/toolcalls.js.map +0 -1
- package/dist/agentfs/types.d.ts +0 -171
- package/dist/agentfs/types.d.ts.map +0 -1
- package/dist/agentfs/types.js +0 -7
- package/dist/agentfs/types.js.map +0 -1
- package/dist/agentfs/vfs.d.ts +0 -249
- package/dist/agentfs/vfs.d.ts.map +0 -1
- package/dist/agentfs/vfs.js +0 -469
- package/dist/agentfs/vfs.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/mcp.d.ts +0 -119
- package/dist/cli/mcp.d.ts.map +0 -1
- package/dist/cli/mcp.js +0 -418
- package/dist/cli/mcp.js.map +0 -1
- package/dist/cli/server.d.ts +0 -179
- package/dist/cli/server.d.ts.map +0 -1
- package/dist/cli/server.js +0 -441
- package/dist/cli/server.js.map +0 -1
- package/dist/client/Collection.d.ts +0 -199
- package/dist/client/Collection.d.ts.map +0 -1
- package/dist/client/Collection.js +0 -256
- package/dist/client/Collection.js.map +0 -1
- package/dist/client/Database.d.ts +0 -68
- package/dist/client/Database.d.ts.map +0 -1
- package/dist/client/Database.js +0 -105
- package/dist/client/Database.js.map +0 -1
- package/dist/client/MongoClient.d.ts +0 -165
- package/dist/client/MongoClient.d.ts.map +0 -1
- package/dist/client/MongoClient.js +0 -307
- package/dist/client/MongoClient.js.map +0 -1
- package/dist/client/aggregation-cursor.d.ts +0 -210
- package/dist/client/aggregation-cursor.d.ts.map +0 -1
- package/dist/client/aggregation-cursor.js +0 -509
- package/dist/client/aggregation-cursor.js.map +0 -1
- package/dist/client/bulk-write.d.ts +0 -216
- package/dist/client/bulk-write.d.ts.map +0 -1
- package/dist/client/bulk-write.js +0 -63
- package/dist/client/bulk-write.js.map +0 -1
- package/dist/client/change-stream.d.ts +0 -245
- package/dist/client/change-stream.d.ts.map +0 -1
- package/dist/client/change-stream.js +0 -429
- package/dist/client/change-stream.js.map +0 -1
- package/dist/client/cursor.d.ts +0 -85
- package/dist/client/cursor.d.ts.map +0 -1
- package/dist/client/cursor.js +0 -156
- package/dist/client/cursor.js.map +0 -1
- package/dist/client/http-cursor.d.ts +0 -233
- package/dist/client/http-cursor.d.ts.map +0 -1
- package/dist/client/http-cursor.js +0 -496
- package/dist/client/http-cursor.js.map +0 -1
- package/dist/client/index.d.ts +0 -18
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js +0 -24
- package/dist/client/index.js.map +0 -1
- package/dist/client/mongo-client.d.ts +0 -60
- package/dist/client/mongo-client.d.ts.map +0 -1
- package/dist/client/mongo-client.js +0 -190
- package/dist/client/mongo-client.js.map +0 -1
- package/dist/client/mongo-collection.d.ts +0 -359
- package/dist/client/mongo-collection.d.ts.map +0 -1
- package/dist/client/mongo-collection.js +0 -1641
- package/dist/client/mongo-collection.js.map +0 -1
- package/dist/client/mongo-cursor.d.ts +0 -257
- package/dist/client/mongo-cursor.d.ts.map +0 -1
- package/dist/client/mongo-cursor.js +0 -621
- package/dist/client/mongo-cursor.js.map +0 -1
- package/dist/client/mongo-database.d.ts +0 -88
- package/dist/client/mongo-database.d.ts.map +0 -1
- package/dist/client/mongo-database.js +0 -139
- package/dist/client/mongo-database.js.map +0 -1
- package/dist/client/session.d.ts +0 -210
- package/dist/client/session.d.ts.map +0 -1
- package/dist/client/session.js +0 -326
- package/dist/client/session.js.map +0 -1
- package/dist/durable-object/index-manager.d.ts +0 -173
- package/dist/durable-object/index-manager.d.ts.map +0 -1
- package/dist/durable-object/index-manager.js +0 -764
- package/dist/durable-object/index-manager.js.map +0 -1
- package/dist/durable-object/index.d.ts +0 -12
- package/dist/durable-object/index.d.ts.map +0 -1
- package/dist/durable-object/index.js +0 -8
- package/dist/durable-object/index.js.map +0 -1
- package/dist/durable-object/mcp-handler.d.ts +0 -52
- package/dist/durable-object/mcp-handler.d.ts.map +0 -1
- package/dist/durable-object/mcp-handler.js +0 -186
- package/dist/durable-object/mcp-handler.js.map +0 -1
- package/dist/durable-object/migrations.d.ts +0 -40
- package/dist/durable-object/migrations.d.ts.map +0 -1
- package/dist/durable-object/migrations.js +0 -121
- package/dist/durable-object/migrations.js.map +0 -1
- package/dist/durable-object/mondo-database.d.ts +0 -148
- package/dist/durable-object/mondo-database.d.ts.map +0 -1
- package/dist/durable-object/mondo-database.js +0 -621
- package/dist/durable-object/mondo-database.js.map +0 -1
- package/dist/durable-object/schema.d.ts +0 -192
- package/dist/durable-object/schema.d.ts.map +0 -1
- package/dist/durable-object/schema.js +0 -186
- package/dist/durable-object/schema.js.map +0 -1
- package/dist/embedding/document-serializer.d.ts +0 -118
- package/dist/embedding/document-serializer.d.ts.map +0 -1
- package/dist/embedding/document-serializer.js +0 -339
- package/dist/embedding/document-serializer.js.map +0 -1
- package/dist/embedding/embedding-manager.d.ts +0 -136
- package/dist/embedding/embedding-manager.d.ts.map +0 -1
- package/dist/embedding/embedding-manager.js +0 -176
- package/dist/embedding/embedding-manager.js.map +0 -1
- package/dist/embedding/index.d.ts +0 -9
- package/dist/embedding/index.d.ts.map +0 -1
- package/dist/embedding/index.js +0 -9
- package/dist/embedding/index.js.map +0 -1
- package/dist/executor/aggregation-executor.d.ts +0 -93
- package/dist/executor/aggregation-executor.d.ts.map +0 -1
- package/dist/executor/aggregation-executor.js +0 -275
- package/dist/executor/aggregation-executor.js.map +0 -1
- package/dist/executor/function-executor.d.ts +0 -39
- package/dist/executor/function-executor.d.ts.map +0 -1
- package/dist/executor/function-executor.js +0 -168
- package/dist/executor/function-executor.js.map +0 -1
- package/dist/executor/index.d.ts +0 -4
- package/dist/executor/index.d.ts.map +0 -1
- package/dist/executor/index.js +0 -4
- package/dist/executor/index.js.map +0 -1
- package/dist/executor/vector-search-executor.d.ts +0 -71
- package/dist/executor/vector-search-executor.d.ts.map +0 -1
- package/dist/executor/vector-search-executor.js +0 -113
- package/dist/executor/vector-search-executor.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/mcp/adapters/anthropic-adapter.d.ts +0 -256
- package/dist/mcp/adapters/anthropic-adapter.d.ts.map +0 -1
- package/dist/mcp/adapters/anthropic-adapter.js +0 -409
- package/dist/mcp/adapters/anthropic-adapter.js.map +0 -1
- package/dist/mcp/adapters/base-adapter.d.ts +0 -164
- package/dist/mcp/adapters/base-adapter.d.ts.map +0 -1
- package/dist/mcp/adapters/base-adapter.js +0 -277
- package/dist/mcp/adapters/base-adapter.js.map +0 -1
- package/dist/mcp/adapters/errors.d.ts +0 -173
- package/dist/mcp/adapters/errors.d.ts.map +0 -1
- package/dist/mcp/adapters/errors.js +0 -305
- package/dist/mcp/adapters/errors.js.map +0 -1
- package/dist/mcp/adapters/index.d.ts +0 -65
- package/dist/mcp/adapters/index.d.ts.map +0 -1
- package/dist/mcp/adapters/index.js +0 -92
- package/dist/mcp/adapters/index.js.map +0 -1
- package/dist/mcp/adapters/streaming.d.ts +0 -200
- package/dist/mcp/adapters/streaming.d.ts.map +0 -1
- package/dist/mcp/adapters/streaming.js +0 -381
- package/dist/mcp/adapters/streaming.js.map +0 -1
- package/dist/mcp/adapters/vercel-adapter.d.ts +0 -321
- package/dist/mcp/adapters/vercel-adapter.d.ts.map +0 -1
- package/dist/mcp/adapters/vercel-adapter.js +0 -487
- package/dist/mcp/adapters/vercel-adapter.js.map +0 -1
- package/dist/mcp/agent.d.ts +0 -192
- package/dist/mcp/agent.d.ts.map +0 -1
- package/dist/mcp/agent.js +0 -338
- package/dist/mcp/agent.js.map +0 -1
- package/dist/mcp/cli.d.ts +0 -71
- package/dist/mcp/cli.d.ts.map +0 -1
- package/dist/mcp/cli.js +0 -218
- package/dist/mcp/cli.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -15
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -20
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/sandbox/database-proxy.d.ts +0 -118
- package/dist/mcp/sandbox/database-proxy.d.ts.map +0 -1
- package/dist/mcp/sandbox/database-proxy.js +0 -154
- package/dist/mcp/sandbox/database-proxy.js.map +0 -1
- package/dist/mcp/sandbox/index.d.ts +0 -8
- package/dist/mcp/sandbox/index.d.ts.map +0 -1
- package/dist/mcp/sandbox/index.js +0 -7
- package/dist/mcp/sandbox/index.js.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -72
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.js +0 -379
- package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
- package/dist/mcp/sandbox/template.d.ts +0 -48
- package/dist/mcp/sandbox/template.d.ts.map +0 -1
- package/dist/mcp/sandbox/template.js +0 -147
- package/dist/mcp/sandbox/template.js.map +0 -1
- package/dist/mcp/sandbox/worker-evaluator.d.ts +0 -160
- package/dist/mcp/sandbox/worker-evaluator.d.ts.map +0 -1
- package/dist/mcp/sandbox/worker-evaluator.js +0 -217
- package/dist/mcp/sandbox/worker-evaluator.js.map +0 -1
- package/dist/mcp/server.d.ts +0 -75
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -278
- package/dist/mcp/server.js.map +0 -1
- package/dist/mcp/tool-call-auditor.d.ts +0 -188
- package/dist/mcp/tool-call-auditor.d.ts.map +0 -1
- package/dist/mcp/tool-call-auditor.js +0 -198
- package/dist/mcp/tool-call-auditor.js.map +0 -1
- package/dist/mcp/tools/do.d.ts +0 -51
- package/dist/mcp/tools/do.d.ts.map +0 -1
- package/dist/mcp/tools/do.js +0 -113
- package/dist/mcp/tools/do.js.map +0 -1
- package/dist/mcp/tools/fetch.d.ts +0 -43
- package/dist/mcp/tools/fetch.d.ts.map +0 -1
- package/dist/mcp/tools/fetch.js +0 -127
- package/dist/mcp/tools/fetch.js.map +0 -1
- package/dist/mcp/tools/index.d.ts +0 -9
- package/dist/mcp/tools/index.d.ts.map +0 -1
- package/dist/mcp/tools/index.js +0 -9
- package/dist/mcp/tools/index.js.map +0 -1
- package/dist/mcp/tools/search.d.ts +0 -60
- package/dist/mcp/tools/search.d.ts.map +0 -1
- package/dist/mcp/tools/search.js +0 -278
- package/dist/mcp/tools/search.js.map +0 -1
- package/dist/mcp/transport/http.d.ts +0 -144
- package/dist/mcp/transport/http.d.ts.map +0 -1
- package/dist/mcp/transport/http.js +0 -545
- package/dist/mcp/transport/http.js.map +0 -1
- package/dist/mcp/transport/index.d.ts +0 -10
- package/dist/mcp/transport/index.d.ts.map +0 -1
- package/dist/mcp/transport/index.js +0 -12
- package/dist/mcp/transport/index.js.map +0 -1
- package/dist/mcp/transport/stdio.d.ts +0 -132
- package/dist/mcp/transport/stdio.d.ts.map +0 -1
- package/dist/mcp/transport/stdio.js +0 -466
- package/dist/mcp/transport/stdio.js.map +0 -1
- package/dist/mcp/types.d.ts +0 -476
- package/dist/mcp/types.d.ts.map +0 -1
- package/dist/mcp/types.js +0 -178
- package/dist/mcp/types.js.map +0 -1
- package/dist/olap/cdc/cdc-buffer.d.ts +0 -92
- package/dist/olap/cdc/cdc-buffer.d.ts.map +0 -1
- package/dist/olap/cdc/cdc-buffer.js +0 -146
- package/dist/olap/cdc/cdc-buffer.js.map +0 -1
- package/dist/olap/cdc/cdc-emitter.d.ts +0 -118
- package/dist/olap/cdc/cdc-emitter.d.ts.map +0 -1
- package/dist/olap/cdc/cdc-emitter.js +0 -217
- package/dist/olap/cdc/cdc-emitter.js.map +0 -1
- package/dist/olap/cdc/cdc-schema.d.ts +0 -119
- package/dist/olap/cdc/cdc-schema.d.ts.map +0 -1
- package/dist/olap/cdc/cdc-schema.js +0 -253
- package/dist/olap/cdc/cdc-schema.js.map +0 -1
- package/dist/olap/cdc/index.d.ts +0 -10
- package/dist/olap/cdc/index.d.ts.map +0 -1
- package/dist/olap/cdc/index.js +0 -10
- package/dist/olap/cdc/index.js.map +0 -1
- package/dist/olap/clickhouse/iceberg.d.ts +0 -164
- package/dist/olap/clickhouse/iceberg.d.ts.map +0 -1
- package/dist/olap/clickhouse/iceberg.js +0 -138
- package/dist/olap/clickhouse/iceberg.js.map +0 -1
- package/dist/olap/clickhouse/index.d.ts +0 -14
- package/dist/olap/clickhouse/index.d.ts.map +0 -1
- package/dist/olap/clickhouse/index.js +0 -14
- package/dist/olap/clickhouse/index.js.map +0 -1
- package/dist/olap/clickhouse/mapper.d.ts +0 -170
- package/dist/olap/clickhouse/mapper.d.ts.map +0 -1
- package/dist/olap/clickhouse/mapper.js +0 -654
- package/dist/olap/clickhouse/mapper.js.map +0 -1
- package/dist/olap/clickhouse/olap-backend.d.ts +0 -181
- package/dist/olap/clickhouse/olap-backend.d.ts.map +0 -1
- package/dist/olap/clickhouse/olap-backend.js +0 -1083
- package/dist/olap/clickhouse/olap-backend.js.map +0 -1
- package/dist/olap/clickhouse/query-executor.d.ts +0 -163
- package/dist/olap/clickhouse/query-executor.d.ts.map +0 -1
- package/dist/olap/clickhouse/query-executor.js +0 -560
- package/dist/olap/clickhouse/query-executor.js.map +0 -1
- package/dist/olap/clickhouse/query.d.ts +0 -134
- package/dist/olap/clickhouse/query.d.ts.map +0 -1
- package/dist/olap/clickhouse/query.js +0 -512
- package/dist/olap/clickhouse/query.js.map +0 -1
- package/dist/olap/stage/index.d.ts +0 -6
- package/dist/olap/stage/index.d.ts.map +0 -1
- package/dist/olap/stage/index.js +0 -6
- package/dist/olap/stage/index.js.map +0 -1
- package/dist/olap/stage/parser.d.ts +0 -68
- package/dist/olap/stage/parser.d.ts.map +0 -1
- package/dist/olap/stage/parser.js +0 -293
- package/dist/olap/stage/parser.js.map +0 -1
- package/dist/olap/stage/router.d.ts +0 -94
- package/dist/olap/stage/router.d.ts.map +0 -1
- package/dist/olap/stage/router.js +0 -390
- package/dist/olap/stage/router.js.map +0 -1
- package/dist/rpc/endpoint.d.ts +0 -52
- package/dist/rpc/endpoint.d.ts.map +0 -1
- package/dist/rpc/endpoint.js +0 -734
- package/dist/rpc/endpoint.js.map +0 -1
- package/dist/rpc/index.d.ts +0 -34
- package/dist/rpc/index.d.ts.map +0 -1
- package/dist/rpc/index.js +0 -45
- package/dist/rpc/index.js.map +0 -1
- package/dist/rpc/rpc-client.d.ts +0 -275
- package/dist/rpc/rpc-client.d.ts.map +0 -1
- package/dist/rpc/rpc-client.js +0 -735
- package/dist/rpc/rpc-client.js.map +0 -1
- package/dist/rpc/rpc-target.d.ts +0 -220
- package/dist/rpc/rpc-target.d.ts.map +0 -1
- package/dist/rpc/rpc-target.js +0 -500
- package/dist/rpc/rpc-target.js.map +0 -1
- package/dist/rpc/worker-entrypoint.d.ts +0 -159
- package/dist/rpc/worker-entrypoint.d.ts.map +0 -1
- package/dist/rpc/worker-entrypoint.js +0 -212
- package/dist/rpc/worker-entrypoint.js.map +0 -1
- package/dist/server.d.ts +0 -18
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -129
- package/dist/server.js.map +0 -1
- package/dist/studio/components/browser/CollectionItem.d.ts +0 -26
- package/dist/studio/components/browser/CollectionItem.d.ts.map +0 -1
- package/dist/studio/components/browser/CollectionItem.js +0 -143
- package/dist/studio/components/browser/CollectionItem.js.map +0 -1
- package/dist/studio/components/browser/CollectionTree.d.ts +0 -45
- package/dist/studio/components/browser/CollectionTree.d.ts.map +0 -1
- package/dist/studio/components/browser/CollectionTree.js +0 -207
- package/dist/studio/components/browser/CollectionTree.js.map +0 -1
- package/dist/studio/components/browser/ConnectedDatabaseBrowser.d.ts +0 -51
- package/dist/studio/components/browser/ConnectedDatabaseBrowser.d.ts.map +0 -1
- package/dist/studio/components/browser/ConnectedDatabaseBrowser.js +0 -185
- package/dist/studio/components/browser/ConnectedDatabaseBrowser.js.map +0 -1
- package/dist/studio/components/browser/DatabaseBrowser.d.ts +0 -46
- package/dist/studio/components/browser/DatabaseBrowser.d.ts.map +0 -1
- package/dist/studio/components/browser/DatabaseBrowser.js +0 -304
- package/dist/studio/components/browser/DatabaseBrowser.js.map +0 -1
- package/dist/studio/components/browser/__tests__/CollectionItem.test.d.ts +0 -5
- package/dist/studio/components/browser/__tests__/CollectionItem.test.d.ts.map +0 -1
- package/dist/studio/components/browser/__tests__/CollectionItem.test.js +0 -169
- package/dist/studio/components/browser/__tests__/CollectionItem.test.js.map +0 -1
- package/dist/studio/components/browser/__tests__/CollectionTree.test.d.ts +0 -5
- package/dist/studio/components/browser/__tests__/CollectionTree.test.d.ts.map +0 -1
- package/dist/studio/components/browser/__tests__/CollectionTree.test.js +0 -203
- package/dist/studio/components/browser/__tests__/CollectionTree.test.js.map +0 -1
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.e2e.test.d.ts +0 -8
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.e2e.test.d.ts.map +0 -1
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.e2e.test.js +0 -522
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.e2e.test.js.map +0 -1
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.test.d.ts +0 -5
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.test.d.ts.map +0 -1
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.test.js +0 -518
- package/dist/studio/components/browser/__tests__/DatabaseBrowser.test.js.map +0 -1
- package/dist/studio/components/browser/__tests__/setup.d.ts +0 -5
- package/dist/studio/components/browser/__tests__/setup.d.ts.map +0 -1
- package/dist/studio/components/browser/__tests__/setup.js +0 -22
- package/dist/studio/components/browser/__tests__/setup.js.map +0 -1
- package/dist/studio/components/browser/index.d.ts +0 -15
- package/dist/studio/components/browser/index.d.ts.map +0 -1
- package/dist/studio/components/browser/index.js +0 -10
- package/dist/studio/components/browser/index.js.map +0 -1
- package/dist/studio/components/browser/types.d.ts +0 -33
- package/dist/studio/components/browser/types.d.ts.map +0 -1
- package/dist/studio/components/browser/types.js +0 -5
- package/dist/studio/components/browser/types.js.map +0 -1
- package/dist/studio/components/connection/ConnectionForm.d.ts +0 -59
- package/dist/studio/components/connection/ConnectionForm.d.ts.map +0 -1
- package/dist/studio/components/connection/ConnectionForm.js +0 -274
- package/dist/studio/components/connection/ConnectionForm.js.map +0 -1
- package/dist/studio/components/connection/ConnectionList.d.ts +0 -59
- package/dist/studio/components/connection/ConnectionList.d.ts.map +0 -1
- package/dist/studio/components/connection/ConnectionList.js +0 -286
- package/dist/studio/components/connection/ConnectionList.js.map +0 -1
- package/dist/studio/components/connection/ConnectionPanel.d.ts +0 -132
- package/dist/studio/components/connection/ConnectionPanel.d.ts.map +0 -1
- package/dist/studio/components/connection/ConnectionPanel.js +0 -293
- package/dist/studio/components/connection/ConnectionPanel.js.map +0 -1
- package/dist/studio/components/connection/__tests__/ConnectionPanel.test.d.ts +0 -8
- package/dist/studio/components/connection/__tests__/ConnectionPanel.test.d.ts.map +0 -1
- package/dist/studio/components/connection/__tests__/ConnectionPanel.test.js +0 -632
- package/dist/studio/components/connection/__tests__/ConnectionPanel.test.js.map +0 -1
- package/dist/studio/components/connection/__tests__/setup.d.ts +0 -5
- package/dist/studio/components/connection/__tests__/setup.d.ts.map +0 -1
- package/dist/studio/components/connection/__tests__/setup.js +0 -11
- package/dist/studio/components/connection/__tests__/setup.js.map +0 -1
- package/dist/studio/components/connection/index.d.ts +0 -10
- package/dist/studio/components/connection/index.d.ts.map +0 -1
- package/dist/studio/components/connection/index.js +0 -7
- package/dist/studio/components/connection/index.js.map +0 -1
- package/dist/studio/components/crud/DeleteDocumentDialog.d.ts +0 -91
- package/dist/studio/components/crud/DeleteDocumentDialog.d.ts.map +0 -1
- package/dist/studio/components/crud/DeleteDocumentDialog.js +0 -273
- package/dist/studio/components/crud/DeleteDocumentDialog.js.map +0 -1
- package/dist/studio/components/crud/DocumentEditor.d.ts +0 -32
- package/dist/studio/components/crud/DocumentEditor.d.ts.map +0 -1
- package/dist/studio/components/crud/DocumentEditor.js +0 -546
- package/dist/studio/components/crud/DocumentEditor.js.map +0 -1
- package/dist/studio/components/crud/InsertDocumentDialog.d.ts +0 -78
- package/dist/studio/components/crud/InsertDocumentDialog.d.ts.map +0 -1
- package/dist/studio/components/crud/InsertDocumentDialog.js +0 -323
- package/dist/studio/components/crud/InsertDocumentDialog.js.map +0 -1
- package/dist/studio/components/crud/__tests__/DeleteDocumentDialog.test.d.ts +0 -5
- package/dist/studio/components/crud/__tests__/DeleteDocumentDialog.test.d.ts.map +0 -1
- package/dist/studio/components/crud/__tests__/DeleteDocumentDialog.test.js +0 -298
- package/dist/studio/components/crud/__tests__/DeleteDocumentDialog.test.js.map +0 -1
- package/dist/studio/components/crud/__tests__/DocumentEditor.test.d.ts +0 -8
- package/dist/studio/components/crud/__tests__/DocumentEditor.test.d.ts.map +0 -1
- package/dist/studio/components/crud/__tests__/DocumentEditor.test.js +0 -368
- package/dist/studio/components/crud/__tests__/DocumentEditor.test.js.map +0 -1
- package/dist/studio/components/crud/__tests__/InsertDocumentDialog.test.d.ts +0 -2
- package/dist/studio/components/crud/__tests__/InsertDocumentDialog.test.d.ts.map +0 -1
- package/dist/studio/components/crud/__tests__/InsertDocumentDialog.test.js +0 -352
- package/dist/studio/components/crud/__tests__/InsertDocumentDialog.test.js.map +0 -1
- package/dist/studio/components/crud/__tests__/setup.d.ts +0 -5
- package/dist/studio/components/crud/__tests__/setup.d.ts.map +0 -1
- package/dist/studio/components/crud/__tests__/setup.js +0 -22
- package/dist/studio/components/crud/__tests__/setup.js.map +0 -1
- package/dist/studio/components/crud/index.d.ts +0 -12
- package/dist/studio/components/crud/index.d.ts.map +0 -1
- package/dist/studio/components/crud/index.js +0 -11
- package/dist/studio/components/crud/index.js.map +0 -1
- package/dist/studio/hooks/useConnection.d.ts +0 -127
- package/dist/studio/hooks/useConnection.d.ts.map +0 -1
- package/dist/studio/hooks/useConnection.js +0 -414
- package/dist/studio/hooks/useConnection.js.map +0 -1
- package/dist/studio/hooks/useDatabaseBrowser.d.ts +0 -107
- package/dist/studio/hooks/useDatabaseBrowser.d.ts.map +0 -1
- package/dist/studio/hooks/useDatabaseBrowser.js +0 -294
- package/dist/studio/hooks/useDatabaseBrowser.js.map +0 -1
- package/dist/studio/index.d.ts +0 -16
- package/dist/studio/index.d.ts.map +0 -1
- package/dist/studio/index.js +0 -14
- package/dist/studio/index.js.map +0 -1
- package/dist/studio/types/connection.d.ts +0 -266
- package/dist/studio/types/connection.d.ts.map +0 -1
- package/dist/studio/types/connection.js +0 -159
- package/dist/studio/types/connection.js.map +0 -1
- package/dist/studio/vitest.config.d.ts +0 -3
- package/dist/studio/vitest.config.d.ts.map +0 -1
- package/dist/studio/vitest.config.js +0 -33
- package/dist/studio/vitest.config.js.map +0 -1
- package/dist/translator/aggregation-translator.d.ts +0 -51
- package/dist/translator/aggregation-translator.d.ts.map +0 -1
- package/dist/translator/aggregation-translator.js +0 -324
- package/dist/translator/aggregation-translator.js.map +0 -1
- package/dist/translator/dialect.d.ts +0 -131
- package/dist/translator/dialect.d.ts.map +0 -1
- package/dist/translator/dialect.js +0 -276
- package/dist/translator/dialect.js.map +0 -1
- package/dist/translator/geo-translator.d.ts +0 -91
- package/dist/translator/geo-translator.d.ts.map +0 -1
- package/dist/translator/geo-translator.js +0 -587
- package/dist/translator/geo-translator.js.map +0 -1
- package/dist/translator/hybrid-translator.d.ts +0 -70
- package/dist/translator/hybrid-translator.d.ts.map +0 -1
- package/dist/translator/hybrid-translator.js +0 -193
- package/dist/translator/hybrid-translator.js.map +0 -1
- package/dist/translator/index.d.ts +0 -13
- package/dist/translator/index.d.ts.map +0 -1
- package/dist/translator/index.js +0 -11
- package/dist/translator/index.js.map +0 -1
- package/dist/translator/query-translator.d.ts +0 -211
- package/dist/translator/query-translator.d.ts.map +0 -1
- package/dist/translator/query-translator.js +0 -1276
- package/dist/translator/query-translator.js.map +0 -1
- package/dist/translator/search-highlight.d.ts +0 -83
- package/dist/translator/search-highlight.d.ts.map +0 -1
- package/dist/translator/search-highlight.js +0 -83
- package/dist/translator/search-highlight.js.map +0 -1
- package/dist/translator/search-translator.d.ts +0 -155
- package/dist/translator/search-translator.d.ts.map +0 -1
- package/dist/translator/search-translator.js +0 -241
- package/dist/translator/search-translator.js.map +0 -1
- package/dist/translator/stages/add-fields-stage.d.ts +0 -7
- package/dist/translator/stages/add-fields-stage.d.ts.map +0 -1
- package/dist/translator/stages/add-fields-stage.js +0 -72
- package/dist/translator/stages/add-fields-stage.js.map +0 -1
- package/dist/translator/stages/bucket-stage.d.ts +0 -7
- package/dist/translator/stages/bucket-stage.d.ts.map +0 -1
- package/dist/translator/stages/bucket-stage.js +0 -87
- package/dist/translator/stages/bucket-stage.js.map +0 -1
- package/dist/translator/stages/count-stage.d.ts +0 -7
- package/dist/translator/stages/count-stage.d.ts.map +0 -1
- package/dist/translator/stages/count-stage.js +0 -12
- package/dist/translator/stages/count-stage.js.map +0 -1
- package/dist/translator/stages/expression-translator.d.ts +0 -68
- package/dist/translator/stages/expression-translator.d.ts.map +0 -1
- package/dist/translator/stages/expression-translator.js +0 -467
- package/dist/translator/stages/expression-translator.js.map +0 -1
- package/dist/translator/stages/facet-stage.d.ts +0 -13
- package/dist/translator/stages/facet-stage.d.ts.map +0 -1
- package/dist/translator/stages/facet-stage.js +0 -26
- package/dist/translator/stages/facet-stage.js.map +0 -1
- package/dist/translator/stages/fusion-stages.d.ts +0 -118
- package/dist/translator/stages/fusion-stages.d.ts.map +0 -1
- package/dist/translator/stages/fusion-stages.js +0 -201
- package/dist/translator/stages/fusion-stages.js.map +0 -1
- package/dist/translator/stages/group-stage.d.ts +0 -8
- package/dist/translator/stages/group-stage.d.ts.map +0 -1
- package/dist/translator/stages/group-stage.js +0 -123
- package/dist/translator/stages/group-stage.js.map +0 -1
- package/dist/translator/stages/index.d.ts +0 -24
- package/dist/translator/stages/index.d.ts.map +0 -1
- package/dist/translator/stages/index.js +0 -24
- package/dist/translator/stages/index.js.map +0 -1
- package/dist/translator/stages/join-optimizer.d.ts +0 -37
- package/dist/translator/stages/join-optimizer.d.ts.map +0 -1
- package/dist/translator/stages/join-optimizer.js +0 -93
- package/dist/translator/stages/join-optimizer.js.map +0 -1
- package/dist/translator/stages/limit-stage.d.ts +0 -7
- package/dist/translator/stages/limit-stage.d.ts.map +0 -1
- package/dist/translator/stages/limit-stage.js +0 -11
- package/dist/translator/stages/limit-stage.js.map +0 -1
- package/dist/translator/stages/lookup-stage.d.ts +0 -7
- package/dist/translator/stages/lookup-stage.d.ts.map +0 -1
- package/dist/translator/stages/lookup-stage.js +0 -73
- package/dist/translator/stages/lookup-stage.js.map +0 -1
- package/dist/translator/stages/match-stage.d.ts +0 -7
- package/dist/translator/stages/match-stage.d.ts.map +0 -1
- package/dist/translator/stages/match-stage.js +0 -14
- package/dist/translator/stages/match-stage.js.map +0 -1
- package/dist/translator/stages/optimizer.d.ts +0 -15
- package/dist/translator/stages/optimizer.d.ts.map +0 -1
- package/dist/translator/stages/optimizer.js +0 -249
- package/dist/translator/stages/optimizer.js.map +0 -1
- package/dist/translator/stages/parallel-facet.d.ts +0 -47
- package/dist/translator/stages/parallel-facet.d.ts.map +0 -1
- package/dist/translator/stages/parallel-facet.js +0 -57
- package/dist/translator/stages/parallel-facet.js.map +0 -1
- package/dist/translator/stages/project-stage.d.ts +0 -8
- package/dist/translator/stages/project-stage.d.ts.map +0 -1
- package/dist/translator/stages/project-stage.js +0 -145
- package/dist/translator/stages/project-stage.js.map +0 -1
- package/dist/translator/stages/search-stage.d.ts +0 -60
- package/dist/translator/stages/search-stage.d.ts.map +0 -1
- package/dist/translator/stages/search-stage.js +0 -89
- package/dist/translator/stages/search-stage.js.map +0 -1
- package/dist/translator/stages/skip-stage.d.ts +0 -7
- package/dist/translator/stages/skip-stage.d.ts.map +0 -1
- package/dist/translator/stages/skip-stage.js +0 -11
- package/dist/translator/stages/skip-stage.js.map +0 -1
- package/dist/translator/stages/sort-stage.d.ts +0 -7
- package/dist/translator/stages/sort-stage.d.ts.map +0 -1
- package/dist/translator/stages/sort-stage.js +0 -21
- package/dist/translator/stages/sort-stage.js.map +0 -1
- package/dist/translator/stages/types.d.ts +0 -136
- package/dist/translator/stages/types.d.ts.map +0 -1
- package/dist/translator/stages/types.js +0 -5
- package/dist/translator/stages/types.js.map +0 -1
- package/dist/translator/stages/unwind-stage.d.ts +0 -7
- package/dist/translator/stages/unwind-stage.d.ts.map +0 -1
- package/dist/translator/stages/unwind-stage.js +0 -61
- package/dist/translator/stages/unwind-stage.js.map +0 -1
- package/dist/translator/stages/vector-search-stage.d.ts +0 -53
- package/dist/translator/stages/vector-search-stage.d.ts.map +0 -1
- package/dist/translator/stages/vector-search-stage.js +0 -62
- package/dist/translator/stages/vector-search-stage.js.map +0 -1
- package/dist/translator/update-translator.d.ts +0 -148
- package/dist/translator/update-translator.d.ts.map +0 -1
- package/dist/translator/update-translator.js +0 -819
- package/dist/translator/update-translator.js.map +0 -1
- package/dist/translator/vector-translator.d.ts +0 -89
- package/dist/translator/vector-translator.d.ts.map +0 -1
- package/dist/translator/vector-translator.js +0 -106
- package/dist/translator/vector-translator.js.map +0 -1
- package/dist/types/env.d.ts +0 -31
- package/dist/types/env.d.ts.map +0 -1
- package/dist/types/env.js +0 -5
- package/dist/types/env.js.map +0 -1
- package/dist/types/function.d.ts +0 -65
- package/dist/types/function.d.ts.map +0 -1
- package/dist/types/function.js +0 -5
- package/dist/types/function.js.map +0 -1
- package/dist/types/index.d.ts +0 -137
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -13
- package/dist/types/index.js.map +0 -1
- package/dist/types/mongodb.d.ts +0 -258
- package/dist/types/mongodb.d.ts.map +0 -1
- package/dist/types/mongodb.js +0 -5
- package/dist/types/mongodb.js.map +0 -1
- package/dist/types/objectid.d.ts +0 -130
- package/dist/types/objectid.d.ts.map +0 -1
- package/dist/types/objectid.js +0 -314
- package/dist/types/objectid.js.map +0 -1
- package/dist/types/rpc.d.ts +0 -313
- package/dist/types/rpc.d.ts.map +0 -1
- package/dist/types/rpc.js +0 -136
- package/dist/types/rpc.js.map +0 -1
- package/dist/types/vectorize.d.ts +0 -136
- package/dist/types/vectorize.d.ts.map +0 -1
- package/dist/types/vectorize.js +0 -8
- package/dist/types/vectorize.js.map +0 -1
- package/dist/utils/sql-safety.d.ts +0 -64
- package/dist/utils/sql-safety.d.ts.map +0 -1
- package/dist/utils/sql-safety.js +0 -112
- package/dist/utils/sql-safety.js.map +0 -1
- package/dist/validation/document-validator.d.ts +0 -195
- package/dist/validation/document-validator.d.ts.map +0 -1
- package/dist/validation/document-validator.js +0 -529
- package/dist/validation/document-validator.js.map +0 -1
- package/dist/vectorize/document-serializer.d.ts +0 -119
- package/dist/vectorize/document-serializer.d.ts.map +0 -1
- package/dist/vectorize/document-serializer.js +0 -320
- package/dist/vectorize/document-serializer.js.map +0 -1
- package/dist/wire/auth/index.d.ts +0 -5
- package/dist/wire/auth/index.d.ts.map +0 -1
- package/dist/wire/auth/index.js +0 -5
- package/dist/wire/auth/index.js.map +0 -1
- package/dist/wire/auth/scram.d.ts +0 -160
- package/dist/wire/auth/scram.d.ts.map +0 -1
- package/dist/wire/auth/scram.js +0 -425
- package/dist/wire/auth/scram.js.map +0 -1
- package/dist/wire/backend/interface.d.ts +0 -168
- package/dist/wire/backend/interface.d.ts.map +0 -1
- package/dist/wire/backend/interface.js +0 -10
- package/dist/wire/backend/interface.js.map +0 -1
- package/dist/wire/backend/local-sqlite.d.ts +0 -89
- package/dist/wire/backend/local-sqlite.d.ts.map +0 -1
- package/dist/wire/backend/local-sqlite.js +0 -1002
- package/dist/wire/backend/local-sqlite.js.map +0 -1
- package/dist/wire/backend/query-router.d.ts +0 -197
- package/dist/wire/backend/query-router.d.ts.map +0 -1
- package/dist/wire/backend/query-router.js +0 -590
- package/dist/wire/backend/query-router.js.map +0 -1
- package/dist/wire/backend/validation.d.ts +0 -26
- package/dist/wire/backend/validation.d.ts.map +0 -1
- package/dist/wire/backend/validation.js +0 -79
- package/dist/wire/backend/validation.js.map +0 -1
- package/dist/wire/backend/workers-proxy.d.ts +0 -95
- package/dist/wire/backend/workers-proxy.d.ts.map +0 -1
- package/dist/wire/backend/workers-proxy.js +0 -429
- package/dist/wire/backend/workers-proxy.js.map +0 -1
- package/dist/wire/commands/admin.d.ts +0 -49
- package/dist/wire/commands/admin.d.ts.map +0 -1
- package/dist/wire/commands/admin.js +0 -272
- package/dist/wire/commands/admin.js.map +0 -1
- package/dist/wire/commands/aggregate.d.ts +0 -15
- package/dist/wire/commands/aggregate.d.ts.map +0 -1
- package/dist/wire/commands/aggregate.js +0 -98
- package/dist/wire/commands/aggregate.js.map +0 -1
- package/dist/wire/commands/auth.d.ts +0 -58
- package/dist/wire/commands/auth.d.ts.map +0 -1
- package/dist/wire/commands/auth.js +0 -158
- package/dist/wire/commands/auth.js.map +0 -1
- package/dist/wire/commands/crud.d.ts +0 -49
- package/dist/wire/commands/crud.d.ts.map +0 -1
- package/dist/wire/commands/crud.js +0 -336
- package/dist/wire/commands/crud.js.map +0 -1
- package/dist/wire/commands/hello.d.ts +0 -35
- package/dist/wire/commands/hello.d.ts.map +0 -1
- package/dist/wire/commands/hello.js +0 -204
- package/dist/wire/commands/hello.js.map +0 -1
- package/dist/wire/commands/index.d.ts +0 -24
- package/dist/wire/commands/index.d.ts.map +0 -1
- package/dist/wire/commands/index.js +0 -145
- package/dist/wire/commands/index.js.map +0 -1
- package/dist/wire/commands/router.d.ts +0 -46
- package/dist/wire/commands/router.d.ts.map +0 -1
- package/dist/wire/commands/router.js +0 -151
- package/dist/wire/commands/router.js.map +0 -1
- package/dist/wire/commands/types.d.ts +0 -51
- package/dist/wire/commands/types.d.ts.map +0 -1
- package/dist/wire/commands/types.js +0 -15
- package/dist/wire/commands/types.js.map +0 -1
- package/dist/wire/index.d.ts +0 -15
- package/dist/wire/index.d.ts.map +0 -1
- package/dist/wire/index.js +0 -19
- package/dist/wire/index.js.map +0 -1
- package/dist/wire/message.d.ts +0 -49
- package/dist/wire/message.d.ts.map +0 -1
- package/dist/wire/message.js +0 -299
- package/dist/wire/message.js.map +0 -1
- package/dist/wire/server.d.ts +0 -145
- package/dist/wire/server.d.ts.map +0 -1
- package/dist/wire/server.js +0 -284
- package/dist/wire/server.js.map +0 -1
- package/dist/wire/types.d.ts +0 -140
- package/dist/wire/types.d.ts.map +0 -1
- package/dist/wire/types.js +0 -64
- package/dist/wire/types.js.map +0 -1
- package/dist/worker.d.ts.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,219 +1,4106 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import { parseArgs, validateOptions, printHelp, printShutdownMessage, runServer, } from './server.js';
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// ANSI Color Codes (no external dependencies)
|
|
18
|
-
// ============================================================================
|
|
19
|
-
const colors = {
|
|
20
|
-
reset: '\x1b[0m',
|
|
21
|
-
bold: '\x1b[1m',
|
|
22
|
-
dim: '\x1b[2m',
|
|
23
|
-
// Foreground colors
|
|
24
|
-
red: '\x1b[31m',
|
|
25
|
-
green: '\x1b[32m',
|
|
26
|
-
yellow: '\x1b[33m',
|
|
27
|
-
blue: '\x1b[34m',
|
|
28
|
-
magenta: '\x1b[35m',
|
|
29
|
-
cyan: '\x1b[36m',
|
|
30
|
-
white: '\x1b[37m',
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Database } from 'bun:sqlite';
|
|
3
|
+
import { ObjectId, BSON, Binary, Long } from 'bson';
|
|
4
|
+
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
13
|
+
var __esm = (fn, res) => function __init() {
|
|
14
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
31
15
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
16
|
+
var __export = (target, all) => {
|
|
17
|
+
for (var name in all)
|
|
18
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/utils/sql-safety.ts
|
|
22
|
+
function validateFieldPath(field) {
|
|
23
|
+
if (!field || field.length === 0) {
|
|
24
|
+
throw new Error("Field name cannot be empty");
|
|
25
|
+
}
|
|
26
|
+
if (field.includes("\0")) {
|
|
27
|
+
throw new Error("Field name cannot contain null characters");
|
|
28
|
+
}
|
|
29
|
+
if (!SAFE_FIELD_PATTERN.test(field)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Invalid field name: "${field}". Field names can only contain alphanumeric characters, underscores, dots, hyphens, and dollar signs.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (field.includes("..") || field.startsWith(".") || field.endsWith(".")) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Invalid field path: "${field}". Field paths cannot have consecutive, leading, or trailing dots.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return field;
|
|
40
|
+
}
|
|
41
|
+
function safeJsonPath(field) {
|
|
42
|
+
const validField = validateFieldPath(field);
|
|
43
|
+
return validField.startsWith("$") ? validField : `$.${validField}`;
|
|
44
|
+
}
|
|
45
|
+
var SAFE_FIELD_PATTERN;
|
|
46
|
+
var init_sql_safety = __esm({
|
|
47
|
+
"src/utils/sql-safety.ts"() {
|
|
48
|
+
SAFE_FIELD_PATTERN = /^[a-zA-Z0-9_.$-]+$/;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// src/wire/backend/validation.ts
|
|
53
|
+
function sanitizeDatabaseName(name) {
|
|
54
|
+
if (!name || typeof name !== "string") {
|
|
55
|
+
throw new Error("Database name must be a non-empty string");
|
|
56
|
+
}
|
|
57
|
+
if (name.includes("..") || name.includes("/") || name.includes("\\")) {
|
|
58
|
+
throw new Error(`Invalid database name "${name}": contains path traversal characters`);
|
|
59
|
+
}
|
|
60
|
+
if (name.includes("\0")) {
|
|
61
|
+
throw new Error("Invalid database name: contains null byte");
|
|
62
|
+
}
|
|
63
|
+
if (name.startsWith(".")) {
|
|
64
|
+
throw new Error(`Invalid database name "${name}": cannot start with a dot`);
|
|
65
|
+
}
|
|
66
|
+
if (name.length > 255) {
|
|
67
|
+
throw new Error(`Database name too long: ${name.length} characters (max 255)`);
|
|
68
|
+
}
|
|
69
|
+
const validNameRegex = /^[a-zA-Z0-9_-]+$/;
|
|
70
|
+
if (!validNameRegex.test(name)) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Invalid database name "${name}": only alphanumeric characters, underscores, and hyphens are allowed`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return name;
|
|
76
|
+
}
|
|
77
|
+
function validateCollectionName(name) {
|
|
78
|
+
if (!name || typeof name !== "string") {
|
|
79
|
+
throw new Error("Collection name must be a non-empty string");
|
|
80
|
+
}
|
|
81
|
+
if (name.includes("\0")) {
|
|
82
|
+
throw new Error("Invalid collection name: contains null byte");
|
|
83
|
+
}
|
|
84
|
+
if (name.length > 255) {
|
|
85
|
+
throw new Error(`Collection name too long: ${name.length} characters (max 255)`);
|
|
86
|
+
}
|
|
87
|
+
const validNameRegex = /^[a-zA-Z_][a-zA-Z0-9_.-]*$/;
|
|
88
|
+
if (!validNameRegex.test(name)) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Invalid collection name "${name}": must start with a letter or underscore, and contain only alphanumeric characters, underscores, hyphens, and dots`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (name.startsWith("system.") && !["system.users", "system.indexes", "system.namespaces"].includes(name)) {
|
|
94
|
+
throw new Error(`Invalid collection name "${name}": cannot use reserved 'system.' prefix`);
|
|
95
|
+
}
|
|
96
|
+
return name;
|
|
97
|
+
}
|
|
98
|
+
var init_validation = __esm({
|
|
99
|
+
"src/wire/backend/validation.ts"() {
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// src/wire/backend/local-sqlite.ts
|
|
104
|
+
var local_sqlite_exports = {};
|
|
105
|
+
__export(local_sqlite_exports, {
|
|
106
|
+
LocalSQLiteBackend: () => LocalSQLiteBackend,
|
|
107
|
+
sanitizeDatabaseName: () => sanitizeDatabaseName,
|
|
108
|
+
validateCollectionName: () => validateCollectionName
|
|
109
|
+
});
|
|
110
|
+
var DEFAULT_BATCH_SIZE, CURSOR_TIMEOUT_MS2, LocalSQLiteBackend;
|
|
111
|
+
var init_local_sqlite = __esm({
|
|
112
|
+
"src/wire/backend/local-sqlite.ts"() {
|
|
113
|
+
init_sql_safety();
|
|
114
|
+
init_validation();
|
|
115
|
+
init_validation();
|
|
116
|
+
DEFAULT_BATCH_SIZE = 101;
|
|
117
|
+
CURSOR_TIMEOUT_MS2 = 10 * 60 * 1e3;
|
|
118
|
+
LocalSQLiteBackend = class {
|
|
119
|
+
databases = /* @__PURE__ */ new Map();
|
|
120
|
+
cursors = /* @__PURE__ */ new Map();
|
|
121
|
+
nextCursorId = 1n;
|
|
122
|
+
dataDir;
|
|
123
|
+
constructor(dataDir = ".mongodo") {
|
|
124
|
+
this.dataDir = dataDir;
|
|
125
|
+
const fs = __require("fs");
|
|
126
|
+
if (!fs.existsSync(dataDir)) {
|
|
127
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
setInterval(() => this.cleanupExpiredCursors(), 6e4);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get or create a database connection
|
|
133
|
+
*/
|
|
134
|
+
getDatabase(name) {
|
|
135
|
+
const safeName = sanitizeDatabaseName(name);
|
|
136
|
+
let db = this.databases.get(safeName);
|
|
137
|
+
if (!db) {
|
|
138
|
+
const dbPath = `${this.dataDir}/${safeName}.sqlite`;
|
|
139
|
+
db = new Database(dbPath);
|
|
140
|
+
this.initializeSchema(db);
|
|
141
|
+
this.databases.set(safeName, db);
|
|
142
|
+
}
|
|
143
|
+
return db;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Initialize the database schema
|
|
147
|
+
*/
|
|
148
|
+
initializeSchema(db) {
|
|
149
|
+
db.exec(`
|
|
150
|
+
CREATE TABLE IF NOT EXISTS collections (
|
|
151
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
152
|
+
name TEXT NOT NULL UNIQUE,
|
|
153
|
+
options TEXT DEFAULT '{}'
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
CREATE TABLE IF NOT EXISTS documents (
|
|
157
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
158
|
+
collection_id INTEGER NOT NULL,
|
|
159
|
+
_id TEXT NOT NULL,
|
|
160
|
+
data TEXT NOT NULL DEFAULT '{}',
|
|
161
|
+
FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE,
|
|
162
|
+
UNIQUE(collection_id, _id)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
CREATE INDEX IF NOT EXISTS idx_documents_id ON documents(_id);
|
|
166
|
+
CREATE INDEX IF NOT EXISTS idx_documents_collection_id ON documents(collection_id, _id);
|
|
167
|
+
|
|
168
|
+
CREATE TABLE IF NOT EXISTS indexes (
|
|
169
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
170
|
+
collection_id INTEGER NOT NULL,
|
|
171
|
+
name TEXT NOT NULL,
|
|
172
|
+
key TEXT NOT NULL,
|
|
173
|
+
options TEXT DEFAULT '{}',
|
|
174
|
+
FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE,
|
|
175
|
+
UNIQUE(collection_id, name)
|
|
176
|
+
);
|
|
177
|
+
`);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get collection ID, creating collection if it doesn't exist
|
|
181
|
+
*/
|
|
182
|
+
getOrCreateCollectionId(db, name) {
|
|
183
|
+
const safeName = validateCollectionName(name);
|
|
184
|
+
const existing = db.query("SELECT id FROM collections WHERE name = ?").get(safeName);
|
|
185
|
+
if (existing) {
|
|
186
|
+
return existing.id;
|
|
187
|
+
}
|
|
188
|
+
const result = db.query("INSERT INTO collections (name) VALUES (?) RETURNING id").get(safeName);
|
|
189
|
+
return result.id;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get collection ID or null if not found
|
|
193
|
+
*/
|
|
194
|
+
getCollectionId(db, name) {
|
|
195
|
+
const safeName = validateCollectionName(name);
|
|
196
|
+
const result = db.query("SELECT id FROM collections WHERE name = ?").get(safeName);
|
|
197
|
+
return result?.id ?? null;
|
|
198
|
+
}
|
|
199
|
+
// ============ Database Operations ============
|
|
200
|
+
async listDatabases() {
|
|
201
|
+
const fs = __require("fs");
|
|
202
|
+
const path = __require("path");
|
|
203
|
+
let files = [];
|
|
204
|
+
try {
|
|
205
|
+
files = fs.readdirSync(this.dataDir);
|
|
206
|
+
} catch {
|
|
207
|
+
}
|
|
208
|
+
const databases = [];
|
|
209
|
+
for (const file of files) {
|
|
210
|
+
if (file.endsWith(".sqlite")) {
|
|
211
|
+
const name = file.replace(".sqlite", "");
|
|
212
|
+
const filePath = path.join(this.dataDir, file);
|
|
213
|
+
const stats = fs.statSync(filePath);
|
|
214
|
+
databases.push({
|
|
215
|
+
name,
|
|
216
|
+
sizeOnDisk: stats.size,
|
|
217
|
+
empty: stats.size < 1e3
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (!databases.find((d) => d.name === "admin")) {
|
|
222
|
+
databases.unshift({ name: "admin", sizeOnDisk: 0, empty: true });
|
|
223
|
+
}
|
|
224
|
+
return databases;
|
|
225
|
+
}
|
|
226
|
+
async createDatabase(name) {
|
|
227
|
+
this.getDatabase(name);
|
|
228
|
+
}
|
|
229
|
+
async dropDatabase(name) {
|
|
230
|
+
const safeName = sanitizeDatabaseName(name);
|
|
231
|
+
const db = this.databases.get(safeName);
|
|
232
|
+
if (db) {
|
|
233
|
+
db.close();
|
|
234
|
+
this.databases.delete(safeName);
|
|
235
|
+
}
|
|
236
|
+
const fs = __require("fs");
|
|
237
|
+
const dbPath = `${this.dataDir}/${safeName}.sqlite`;
|
|
238
|
+
if (fs.existsSync(dbPath)) {
|
|
239
|
+
fs.unlinkSync(dbPath);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async databaseExists(name) {
|
|
243
|
+
const safeName = sanitizeDatabaseName(name);
|
|
244
|
+
const fs = __require("fs");
|
|
245
|
+
return fs.existsSync(`${this.dataDir}/${safeName}.sqlite`);
|
|
246
|
+
}
|
|
247
|
+
// ============ Collection Operations ============
|
|
248
|
+
async listCollections(dbName, filter) {
|
|
249
|
+
const db = this.getDatabase(dbName);
|
|
250
|
+
let query = "SELECT name, options FROM collections";
|
|
251
|
+
const params = [];
|
|
252
|
+
if (filter?.name && typeof filter.name === "string") {
|
|
253
|
+
query += " WHERE name = ?";
|
|
254
|
+
params.push(filter.name);
|
|
255
|
+
}
|
|
256
|
+
const rows = db.query(query).all(...params);
|
|
257
|
+
return rows.map((row) => ({
|
|
258
|
+
name: row.name,
|
|
259
|
+
type: "collection",
|
|
260
|
+
options: JSON.parse(row.options),
|
|
261
|
+
info: { readOnly: false }
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
async createCollection(dbName, name, options) {
|
|
265
|
+
const safeName = validateCollectionName(name);
|
|
266
|
+
const db = this.getDatabase(dbName);
|
|
267
|
+
const optionsJson = JSON.stringify(options || {});
|
|
268
|
+
db.query("INSERT OR IGNORE INTO collections (name, options) VALUES (?, ?)").run(safeName, optionsJson);
|
|
269
|
+
}
|
|
270
|
+
async dropCollection(dbName, name) {
|
|
271
|
+
const safeName = validateCollectionName(name);
|
|
272
|
+
const db = this.getDatabase(dbName);
|
|
273
|
+
db.query("DELETE FROM collections WHERE name = ?").run(safeName);
|
|
274
|
+
}
|
|
275
|
+
async collectionExists(dbName, name) {
|
|
276
|
+
const safeName = validateCollectionName(name);
|
|
277
|
+
const db = this.getDatabase(dbName);
|
|
278
|
+
const result = db.query("SELECT 1 FROM collections WHERE name = ? LIMIT 1").get(safeName);
|
|
279
|
+
return result !== null;
|
|
280
|
+
}
|
|
281
|
+
async collStats(dbName, collection) {
|
|
282
|
+
const db = this.getDatabase(dbName);
|
|
283
|
+
const collectionId = this.getCollectionId(db, collection);
|
|
284
|
+
if (!collectionId) {
|
|
285
|
+
return {
|
|
286
|
+
ns: `${dbName}.${collection}`,
|
|
287
|
+
count: 0,
|
|
288
|
+
size: 0,
|
|
289
|
+
avgObjSize: 0,
|
|
290
|
+
storageSize: 0,
|
|
291
|
+
totalIndexSize: 0,
|
|
292
|
+
nindexes: 1,
|
|
293
|
+
indexSizes: { _id_: 0 }
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const stats = db.query(`
|
|
297
|
+
SELECT COUNT(*) as count, COALESCE(SUM(LENGTH(data)), 0) as size
|
|
298
|
+
FROM documents WHERE collection_id = ?
|
|
299
|
+
`).get(collectionId);
|
|
300
|
+
return {
|
|
301
|
+
ns: `${dbName}.${collection}`,
|
|
302
|
+
count: stats.count,
|
|
303
|
+
size: stats.size,
|
|
304
|
+
avgObjSize: stats.count > 0 ? stats.size / stats.count : 0,
|
|
305
|
+
storageSize: stats.size,
|
|
306
|
+
totalIndexSize: stats.count * 50,
|
|
307
|
+
nindexes: 1,
|
|
308
|
+
indexSizes: { _id_: stats.count * 50 }
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
async dbStats(dbName) {
|
|
312
|
+
const db = this.getDatabase(dbName);
|
|
313
|
+
const collectionStats = db.query("SELECT COUNT(DISTINCT id) as collections FROM collections").get();
|
|
314
|
+
const docStats = db.query("SELECT COUNT(*) as objects, COALESCE(SUM(LENGTH(data)), 0) as dataSize FROM documents").get();
|
|
315
|
+
return {
|
|
316
|
+
db: dbName,
|
|
317
|
+
collections: collectionStats.collections,
|
|
318
|
+
views: 0,
|
|
319
|
+
objects: docStats.objects,
|
|
320
|
+
avgObjSize: docStats.objects > 0 ? docStats.dataSize / docStats.objects : 0,
|
|
321
|
+
dataSize: docStats.dataSize,
|
|
322
|
+
storageSize: docStats.dataSize,
|
|
323
|
+
indexes: collectionStats.collections,
|
|
324
|
+
indexSize: docStats.objects * 50
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
// ============ CRUD Operations ============
|
|
328
|
+
async find(dbName, collection, options) {
|
|
329
|
+
const db = this.getDatabase(dbName);
|
|
330
|
+
const collectionId = this.getCollectionId(db, collection);
|
|
331
|
+
if (!collectionId) {
|
|
332
|
+
return { documents: [], cursorId: 0n, hasMore: false };
|
|
333
|
+
}
|
|
334
|
+
let sql = "SELECT _id, data FROM documents WHERE collection_id = ?";
|
|
335
|
+
const params = [collectionId];
|
|
336
|
+
if (options.filter && Object.keys(options.filter).length > 0) {
|
|
337
|
+
const filterSql = this.buildFilterSql(options.filter, params);
|
|
338
|
+
if (filterSql) {
|
|
339
|
+
sql += ` AND (${filterSql})`;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (options.sort) {
|
|
343
|
+
const sortClauses = [];
|
|
344
|
+
for (const [field, direction] of Object.entries(options.sort)) {
|
|
345
|
+
const dir = direction === -1 ? "DESC" : "ASC";
|
|
346
|
+
if (field === "_id") {
|
|
347
|
+
sortClauses.push(`_id ${dir}`);
|
|
348
|
+
} else {
|
|
349
|
+
const safePath = safeJsonPath(validateFieldPath(field));
|
|
350
|
+
sortClauses.push(`json_extract(data, '${safePath}') ${dir}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (sortClauses.length > 0) {
|
|
354
|
+
sql += ` ORDER BY ${sortClauses.join(", ")}`;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (options.limit && options.limit > 0) {
|
|
358
|
+
sql += ` LIMIT ${options.limit}`;
|
|
359
|
+
}
|
|
360
|
+
if (options.skip && options.skip > 0) {
|
|
361
|
+
sql += ` OFFSET ${options.skip}`;
|
|
362
|
+
}
|
|
363
|
+
const rows = db.query(sql).all(...params);
|
|
364
|
+
let documents = rows.map((row) => {
|
|
365
|
+
const doc = JSON.parse(row.data);
|
|
366
|
+
doc._id = this.parseId(row._id);
|
|
367
|
+
return doc;
|
|
368
|
+
});
|
|
369
|
+
if (options.projection) {
|
|
370
|
+
documents = documents.map((doc) => this.applyProjection(doc, options.projection));
|
|
371
|
+
}
|
|
372
|
+
const batchSize = options.batchSize || DEFAULT_BATCH_SIZE;
|
|
373
|
+
if (documents.length > batchSize) {
|
|
374
|
+
const cursorId = this.nextCursorId++;
|
|
375
|
+
this.cursors.set(cursorId, {
|
|
376
|
+
id: cursorId,
|
|
377
|
+
namespace: `${dbName}.${collection}`,
|
|
378
|
+
documents,
|
|
379
|
+
position: batchSize,
|
|
380
|
+
batchSize,
|
|
381
|
+
createdAt: Date.now()
|
|
382
|
+
});
|
|
383
|
+
return {
|
|
384
|
+
documents: documents.slice(0, batchSize),
|
|
385
|
+
cursorId,
|
|
386
|
+
hasMore: true
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
return { documents, cursorId: 0n, hasMore: false };
|
|
390
|
+
}
|
|
391
|
+
async insertOne(dbName, collection, doc) {
|
|
392
|
+
return this.insertMany(dbName, collection, [doc]);
|
|
393
|
+
}
|
|
394
|
+
async insertMany(dbName, collection, docs) {
|
|
395
|
+
const db = this.getDatabase(dbName);
|
|
396
|
+
const collectionId = this.getOrCreateCollectionId(db, collection);
|
|
397
|
+
const insertedIds = /* @__PURE__ */ new Map();
|
|
398
|
+
let insertedCount = 0;
|
|
399
|
+
const stmt = db.prepare("INSERT INTO documents (collection_id, _id, data) VALUES (?, ?, ?)");
|
|
400
|
+
db.transaction(() => {
|
|
401
|
+
for (let i = 0; i < docs.length; i++) {
|
|
402
|
+
const doc = { ...docs[i] };
|
|
403
|
+
if (!doc._id) {
|
|
404
|
+
doc._id = new ObjectId();
|
|
405
|
+
}
|
|
406
|
+
const idStr = this.serializeId(doc._id);
|
|
407
|
+
const dataJson = JSON.stringify(doc);
|
|
408
|
+
stmt.run(collectionId, idStr, dataJson);
|
|
409
|
+
insertedIds.set(i, doc._id);
|
|
410
|
+
insertedCount++;
|
|
411
|
+
}
|
|
412
|
+
})();
|
|
413
|
+
return { acknowledged: true, insertedIds, insertedCount };
|
|
414
|
+
}
|
|
415
|
+
async updateOne(dbName, collection, filter, update, options) {
|
|
416
|
+
const db = this.getDatabase(dbName);
|
|
417
|
+
const collectionId = this.getCollectionId(db, collection);
|
|
418
|
+
if (!collectionId && !options?.upsert) {
|
|
419
|
+
return { acknowledged: true, matchedCount: 0, modifiedCount: 0, upsertedCount: 0 };
|
|
420
|
+
}
|
|
421
|
+
let sql = "SELECT id, _id, data FROM documents WHERE collection_id = ?";
|
|
422
|
+
const params = [collectionId || 0];
|
|
423
|
+
const filterSql = this.buildFilterSql(filter, params);
|
|
424
|
+
if (filterSql) {
|
|
425
|
+
sql += ` AND (${filterSql})`;
|
|
426
|
+
}
|
|
427
|
+
sql += " LIMIT 1";
|
|
428
|
+
const row = db.query(sql).get(...params);
|
|
429
|
+
if (!row) {
|
|
430
|
+
if (options?.upsert) {
|
|
431
|
+
const cid = this.getOrCreateCollectionId(db, collection);
|
|
432
|
+
const newDoc = { ...filter };
|
|
433
|
+
const updatedDoc2 = this.applyUpdate(newDoc, update);
|
|
434
|
+
if (!updatedDoc2._id) {
|
|
435
|
+
updatedDoc2._id = new ObjectId();
|
|
436
|
+
}
|
|
437
|
+
const idStr = this.serializeId(updatedDoc2._id);
|
|
438
|
+
db.query("INSERT INTO documents (collection_id, _id, data) VALUES (?, ?, ?)").run(
|
|
439
|
+
cid,
|
|
440
|
+
idStr,
|
|
441
|
+
JSON.stringify(updatedDoc2)
|
|
442
|
+
);
|
|
443
|
+
return {
|
|
444
|
+
acknowledged: true,
|
|
445
|
+
matchedCount: 0,
|
|
446
|
+
modifiedCount: 0,
|
|
447
|
+
upsertedId: updatedDoc2._id,
|
|
448
|
+
upsertedCount: 1
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
return { acknowledged: true, matchedCount: 0, modifiedCount: 0, upsertedCount: 0 };
|
|
452
|
+
}
|
|
453
|
+
const doc = JSON.parse(row.data);
|
|
454
|
+
doc._id = this.parseId(row._id);
|
|
455
|
+
const updatedDoc = this.applyUpdate(doc, update);
|
|
456
|
+
db.query("UPDATE documents SET data = ? WHERE id = ?").run(JSON.stringify(updatedDoc), row.id);
|
|
457
|
+
return { acknowledged: true, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 };
|
|
458
|
+
}
|
|
459
|
+
async updateMany(dbName, collection, filter, update, options) {
|
|
460
|
+
const db = this.getDatabase(dbName);
|
|
461
|
+
const collectionId = this.getCollectionId(db, collection);
|
|
462
|
+
if (!collectionId) {
|
|
463
|
+
if (options?.upsert) {
|
|
464
|
+
const result = await this.updateOne(dbName, collection, filter, update, options);
|
|
465
|
+
return result;
|
|
466
|
+
}
|
|
467
|
+
return { acknowledged: true, matchedCount: 0, modifiedCount: 0, upsertedCount: 0 };
|
|
468
|
+
}
|
|
469
|
+
let sql = "SELECT id, _id, data FROM documents WHERE collection_id = ?";
|
|
470
|
+
const params = [collectionId];
|
|
471
|
+
const filterSql = this.buildFilterSql(filter, params);
|
|
472
|
+
if (filterSql) {
|
|
473
|
+
sql += ` AND (${filterSql})`;
|
|
474
|
+
}
|
|
475
|
+
const rows = db.query(sql).all(...params);
|
|
476
|
+
if (rows.length === 0 && options?.upsert) {
|
|
477
|
+
const result = await this.updateOne(dbName, collection, filter, update, options);
|
|
478
|
+
return result;
|
|
479
|
+
}
|
|
480
|
+
const stmt = db.prepare("UPDATE documents SET data = ? WHERE id = ?");
|
|
481
|
+
db.transaction(() => {
|
|
482
|
+
for (const row of rows) {
|
|
483
|
+
const doc = JSON.parse(row.data);
|
|
484
|
+
doc._id = this.parseId(row._id);
|
|
485
|
+
const updatedDoc = this.applyUpdate(doc, update);
|
|
486
|
+
stmt.run(JSON.stringify(updatedDoc), row.id);
|
|
487
|
+
}
|
|
488
|
+
})();
|
|
489
|
+
return {
|
|
490
|
+
acknowledged: true,
|
|
491
|
+
matchedCount: rows.length,
|
|
492
|
+
modifiedCount: rows.length,
|
|
493
|
+
upsertedCount: 0
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
async deleteOne(dbName, collection, filter) {
|
|
497
|
+
const db = this.getDatabase(dbName);
|
|
498
|
+
const collectionId = this.getCollectionId(db, collection);
|
|
499
|
+
if (!collectionId) {
|
|
500
|
+
return { acknowledged: true, deletedCount: 0 };
|
|
501
|
+
}
|
|
502
|
+
let sql = "SELECT id FROM documents WHERE collection_id = ?";
|
|
503
|
+
const params = [collectionId];
|
|
504
|
+
const filterSql = this.buildFilterSql(filter, params);
|
|
505
|
+
if (filterSql) {
|
|
506
|
+
sql += ` AND (${filterSql})`;
|
|
507
|
+
}
|
|
508
|
+
sql += " LIMIT 1";
|
|
509
|
+
const row = db.query(sql).get(...params);
|
|
510
|
+
if (!row) {
|
|
511
|
+
return { acknowledged: true, deletedCount: 0 };
|
|
512
|
+
}
|
|
513
|
+
db.query("DELETE FROM documents WHERE id = ?").run(row.id);
|
|
514
|
+
return { acknowledged: true, deletedCount: 1 };
|
|
515
|
+
}
|
|
516
|
+
async deleteMany(dbName, collection, filter) {
|
|
517
|
+
const db = this.getDatabase(dbName);
|
|
518
|
+
const collectionId = this.getCollectionId(db, collection);
|
|
519
|
+
if (!collectionId) {
|
|
520
|
+
return { acknowledged: true, deletedCount: 0 };
|
|
521
|
+
}
|
|
522
|
+
let sql = "DELETE FROM documents WHERE collection_id = ?";
|
|
523
|
+
const params = [collectionId];
|
|
524
|
+
const filterSql = this.buildFilterSql(filter, params);
|
|
525
|
+
if (filterSql) {
|
|
526
|
+
sql += ` AND (${filterSql})`;
|
|
527
|
+
}
|
|
528
|
+
const result = db.query(sql).run(...params);
|
|
529
|
+
return { acknowledged: true, deletedCount: result.changes };
|
|
530
|
+
}
|
|
531
|
+
// ============ Count & Distinct ============
|
|
532
|
+
async count(dbName, collection, query) {
|
|
533
|
+
const db = this.getDatabase(dbName);
|
|
534
|
+
const collectionId = this.getCollectionId(db, collection);
|
|
535
|
+
if (!collectionId) {
|
|
536
|
+
return 0;
|
|
537
|
+
}
|
|
538
|
+
let sql = "SELECT COUNT(*) as count FROM documents WHERE collection_id = ?";
|
|
539
|
+
const params = [collectionId];
|
|
540
|
+
if (query && Object.keys(query).length > 0) {
|
|
541
|
+
const filterSql = this.buildFilterSql(query, params);
|
|
542
|
+
if (filterSql) {
|
|
543
|
+
sql += ` AND (${filterSql})`;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const result = db.query(sql).get(...params);
|
|
547
|
+
return result.count;
|
|
548
|
+
}
|
|
549
|
+
async distinct(dbName, collection, field, query) {
|
|
550
|
+
const db = this.getDatabase(dbName);
|
|
551
|
+
const collectionId = this.getCollectionId(db, collection);
|
|
552
|
+
if (!collectionId) {
|
|
553
|
+
return [];
|
|
554
|
+
}
|
|
555
|
+
let sql;
|
|
556
|
+
if (field === "_id") {
|
|
557
|
+
sql = "SELECT DISTINCT _id as value FROM documents WHERE collection_id = ?";
|
|
558
|
+
} else {
|
|
559
|
+
const safePath = safeJsonPath(validateFieldPath(field));
|
|
560
|
+
sql = `SELECT DISTINCT json_extract(data, '${safePath}') as value FROM documents WHERE collection_id = ?`;
|
|
561
|
+
}
|
|
562
|
+
const params = [collectionId];
|
|
563
|
+
if (query && Object.keys(query).length > 0) {
|
|
564
|
+
const filterSql = this.buildFilterSql(query, params);
|
|
565
|
+
if (filterSql) {
|
|
566
|
+
sql += ` AND (${filterSql})`;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const rows = db.query(sql).all(...params);
|
|
570
|
+
return rows.map((r) => r.value).filter((v) => v !== null);
|
|
571
|
+
}
|
|
572
|
+
// ============ Aggregation ============
|
|
573
|
+
async aggregate(dbName, collection, pipeline, options) {
|
|
574
|
+
const db = this.getDatabase(dbName);
|
|
575
|
+
const collectionId = this.getCollectionId(db, collection);
|
|
576
|
+
if (!collectionId) {
|
|
577
|
+
return { documents: [], cursorId: 0n, hasMore: false };
|
|
578
|
+
}
|
|
579
|
+
const rows = db.query("SELECT _id, data FROM documents WHERE collection_id = ?").all(collectionId);
|
|
580
|
+
let documents = rows.map((row) => {
|
|
581
|
+
const doc = JSON.parse(row.data);
|
|
582
|
+
doc._id = this.parseId(row._id);
|
|
583
|
+
return doc;
|
|
584
|
+
});
|
|
585
|
+
documents = this.applyPipeline(documents, pipeline);
|
|
586
|
+
const batchSize = options?.batchSize || DEFAULT_BATCH_SIZE;
|
|
587
|
+
if (documents.length > batchSize) {
|
|
588
|
+
const cursorId = this.nextCursorId++;
|
|
589
|
+
this.cursors.set(cursorId, {
|
|
590
|
+
id: cursorId,
|
|
591
|
+
namespace: `${dbName}.${collection}`,
|
|
592
|
+
documents,
|
|
593
|
+
position: batchSize,
|
|
594
|
+
batchSize,
|
|
595
|
+
createdAt: Date.now()
|
|
596
|
+
});
|
|
597
|
+
return {
|
|
598
|
+
documents: documents.slice(0, batchSize),
|
|
599
|
+
cursorId,
|
|
600
|
+
hasMore: true
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
return { documents, cursorId: 0n, hasMore: false };
|
|
604
|
+
}
|
|
605
|
+
// ============ Index Operations ============
|
|
606
|
+
async listIndexes(dbName, collection) {
|
|
607
|
+
const db = this.getDatabase(dbName);
|
|
608
|
+
const collectionId = this.getCollectionId(db, collection);
|
|
609
|
+
const indexes = [{ v: 2, key: { _id: 1 }, name: "_id_" }];
|
|
610
|
+
if (!collectionId) {
|
|
611
|
+
return indexes;
|
|
612
|
+
}
|
|
613
|
+
const rows = db.query("SELECT name, key, options FROM indexes WHERE collection_id = ?").all(collectionId);
|
|
614
|
+
for (const row of rows) {
|
|
615
|
+
indexes.push({
|
|
616
|
+
v: 2,
|
|
617
|
+
key: JSON.parse(row.key),
|
|
618
|
+
name: row.name,
|
|
619
|
+
...JSON.parse(row.options)
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
return indexes;
|
|
623
|
+
}
|
|
624
|
+
async createIndexes(dbName, collection, indexes) {
|
|
625
|
+
const db = this.getDatabase(dbName);
|
|
626
|
+
const collectionId = this.getOrCreateCollectionId(db, collection);
|
|
627
|
+
const createdNames = [];
|
|
628
|
+
const stmt = db.prepare(
|
|
629
|
+
"INSERT OR IGNORE INTO indexes (collection_id, name, key, options) VALUES (?, ?, ?, ?)"
|
|
630
|
+
);
|
|
631
|
+
for (const spec of indexes) {
|
|
632
|
+
const name = spec.name || this.generateIndexName(spec.key);
|
|
633
|
+
const keyJson = JSON.stringify(spec.key);
|
|
634
|
+
const options = {};
|
|
635
|
+
if (spec.unique) options.unique = true;
|
|
636
|
+
if (spec.sparse) options.sparse = true;
|
|
637
|
+
const result = stmt.run(collectionId, name, keyJson, JSON.stringify(options));
|
|
638
|
+
if (result.changes > 0) {
|
|
639
|
+
createdNames.push(name);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return createdNames;
|
|
643
|
+
}
|
|
644
|
+
async dropIndex(dbName, collection, indexName) {
|
|
645
|
+
const db = this.getDatabase(dbName);
|
|
646
|
+
const collectionId = this.getCollectionId(db, collection);
|
|
647
|
+
if (collectionId) {
|
|
648
|
+
db.query("DELETE FROM indexes WHERE collection_id = ? AND name = ?").run(
|
|
649
|
+
collectionId,
|
|
650
|
+
indexName
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
async dropIndexes(dbName, collection) {
|
|
655
|
+
const db = this.getDatabase(dbName);
|
|
656
|
+
const collectionId = this.getCollectionId(db, collection);
|
|
657
|
+
if (collectionId) {
|
|
658
|
+
db.query("DELETE FROM indexes WHERE collection_id = ? AND name != '_id_'").run(collectionId);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
// ============ Cursor Management ============
|
|
662
|
+
createCursor(state) {
|
|
663
|
+
this.cursors.set(state.id, state);
|
|
664
|
+
}
|
|
665
|
+
getCursor(id) {
|
|
666
|
+
return this.cursors.get(id);
|
|
667
|
+
}
|
|
668
|
+
advanceCursor(id, count) {
|
|
669
|
+
const cursor = this.cursors.get(id);
|
|
670
|
+
if (!cursor) {
|
|
671
|
+
return [];
|
|
672
|
+
}
|
|
673
|
+
const start = cursor.position;
|
|
674
|
+
const end = Math.min(start + count, cursor.documents.length);
|
|
675
|
+
cursor.position = end;
|
|
676
|
+
return cursor.documents.slice(start, end);
|
|
677
|
+
}
|
|
678
|
+
closeCursor(id) {
|
|
679
|
+
return this.cursors.delete(id);
|
|
680
|
+
}
|
|
681
|
+
cleanupExpiredCursors() {
|
|
682
|
+
const now = Date.now();
|
|
683
|
+
for (const [id, cursor] of this.cursors) {
|
|
684
|
+
if (now - cursor.createdAt > CURSOR_TIMEOUT_MS2) {
|
|
685
|
+
this.cursors.delete(id);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
// ============ Helper Methods ============
|
|
690
|
+
serializeId(id) {
|
|
691
|
+
if (id instanceof ObjectId) {
|
|
692
|
+
return id.toHexString();
|
|
693
|
+
}
|
|
694
|
+
if (typeof id === "object" && id !== null && "$oid" in id) {
|
|
695
|
+
return id.$oid;
|
|
696
|
+
}
|
|
697
|
+
return String(id);
|
|
698
|
+
}
|
|
699
|
+
parseId(idStr) {
|
|
700
|
+
if (/^[0-9a-f]{24}$/i.test(idStr)) {
|
|
701
|
+
return new ObjectId(idStr);
|
|
702
|
+
}
|
|
703
|
+
return idStr;
|
|
704
|
+
}
|
|
705
|
+
generateIndexName(key) {
|
|
706
|
+
return Object.entries(key).map(([field, dir]) => `${field}_${dir}`).join("_");
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Build simplified SQL filter from MongoDB query
|
|
710
|
+
*/
|
|
711
|
+
buildFilterSql(filter, params) {
|
|
712
|
+
const conditions = [];
|
|
713
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
714
|
+
if (key === "_id") {
|
|
715
|
+
params.push(this.serializeId(value));
|
|
716
|
+
conditions.push("_id = ?");
|
|
717
|
+
} else if (key === "$and" && Array.isArray(value)) {
|
|
718
|
+
const subConditions = value.map((sub) => this.buildFilterSql(sub, params)).filter(Boolean);
|
|
719
|
+
if (subConditions.length > 0) {
|
|
720
|
+
conditions.push(`(${subConditions.join(" AND ")})`);
|
|
721
|
+
}
|
|
722
|
+
} else if (key === "$or" && Array.isArray(value)) {
|
|
723
|
+
const subConditions = value.map((sub) => this.buildFilterSql(sub, params)).filter(Boolean);
|
|
724
|
+
if (subConditions.length > 0) {
|
|
725
|
+
conditions.push(`(${subConditions.join(" OR ")})`);
|
|
726
|
+
}
|
|
727
|
+
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
728
|
+
const safePath = safeJsonPath(validateFieldPath(key));
|
|
729
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
730
|
+
const path = `json_extract(data, '${safePath}')`;
|
|
731
|
+
switch (op) {
|
|
732
|
+
case "$eq":
|
|
733
|
+
params.push(opValue);
|
|
734
|
+
conditions.push(`${path} = ?`);
|
|
735
|
+
break;
|
|
736
|
+
case "$ne":
|
|
737
|
+
params.push(opValue);
|
|
738
|
+
conditions.push(`${path} != ?`);
|
|
739
|
+
break;
|
|
740
|
+
case "$gt":
|
|
741
|
+
params.push(opValue);
|
|
742
|
+
conditions.push(`${path} > ?`);
|
|
743
|
+
break;
|
|
744
|
+
case "$gte":
|
|
745
|
+
params.push(opValue);
|
|
746
|
+
conditions.push(`${path} >= ?`);
|
|
747
|
+
break;
|
|
748
|
+
case "$lt":
|
|
749
|
+
params.push(opValue);
|
|
750
|
+
conditions.push(`${path} < ?`);
|
|
751
|
+
break;
|
|
752
|
+
case "$lte":
|
|
753
|
+
params.push(opValue);
|
|
754
|
+
conditions.push(`${path} <= ?`);
|
|
755
|
+
break;
|
|
756
|
+
case "$exists":
|
|
757
|
+
conditions.push(opValue ? `${path} IS NOT NULL` : `${path} IS NULL`);
|
|
758
|
+
break;
|
|
759
|
+
case "$in":
|
|
760
|
+
if (Array.isArray(opValue) && opValue.length > 0) {
|
|
761
|
+
const placeholders = opValue.map(() => "?").join(", ");
|
|
762
|
+
params.push(...opValue);
|
|
763
|
+
conditions.push(`${path} IN (${placeholders})`);
|
|
764
|
+
}
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
} else {
|
|
769
|
+
const safePath = safeJsonPath(validateFieldPath(key));
|
|
770
|
+
const sqlValue = typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
771
|
+
params.push(sqlValue);
|
|
772
|
+
conditions.push(`json_extract(data, '${safePath}') = ?`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return conditions.join(" AND ");
|
|
776
|
+
}
|
|
777
|
+
applyProjection(doc, projection) {
|
|
778
|
+
const includeFields = /* @__PURE__ */ new Set();
|
|
779
|
+
const excludeFields = /* @__PURE__ */ new Set();
|
|
780
|
+
let isInclusion = false;
|
|
781
|
+
for (const [field, value] of Object.entries(projection)) {
|
|
782
|
+
if (field === "_id" && (value === 0 || value === false)) {
|
|
783
|
+
excludeFields.add("_id");
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
if (value === 1 || value === true) {
|
|
787
|
+
includeFields.add(field);
|
|
788
|
+
isInclusion = true;
|
|
789
|
+
} else if (value === 0 || value === false) {
|
|
790
|
+
excludeFields.add(field);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (isInclusion) {
|
|
794
|
+
const result2 = {};
|
|
795
|
+
if (!excludeFields.has("_id")) {
|
|
796
|
+
result2._id = doc._id;
|
|
797
|
+
}
|
|
798
|
+
for (const field of includeFields) {
|
|
799
|
+
if (field in doc) {
|
|
800
|
+
result2[field] = doc[field];
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return result2;
|
|
804
|
+
}
|
|
805
|
+
const result = { ...doc };
|
|
806
|
+
for (const field of excludeFields) {
|
|
807
|
+
delete result[field];
|
|
808
|
+
}
|
|
809
|
+
return result;
|
|
810
|
+
}
|
|
811
|
+
applyUpdate(doc, update) {
|
|
812
|
+
const result = { ...doc };
|
|
813
|
+
if (update.$set) {
|
|
814
|
+
Object.assign(result, update.$set);
|
|
815
|
+
}
|
|
816
|
+
if (update.$unset) {
|
|
817
|
+
for (const key of Object.keys(update.$unset)) {
|
|
818
|
+
delete result[key];
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
if (update.$inc) {
|
|
822
|
+
for (const [key, value] of Object.entries(update.$inc)) {
|
|
823
|
+
const current = result[key] || 0;
|
|
824
|
+
result[key] = current + value;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (update.$push) {
|
|
828
|
+
for (const [key, value] of Object.entries(update.$push)) {
|
|
829
|
+
const current = result[key] || [];
|
|
830
|
+
if (typeof value === "object" && value !== null && "$each" in value) {
|
|
831
|
+
current.push(...value.$each);
|
|
832
|
+
} else {
|
|
833
|
+
current.push(value);
|
|
834
|
+
}
|
|
835
|
+
result[key] = current;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
const hasOperators = Object.keys(update).some((k) => k.startsWith("$"));
|
|
839
|
+
if (!hasOperators) {
|
|
840
|
+
const id = result._id;
|
|
841
|
+
for (const key of Object.keys(result)) {
|
|
842
|
+
if (key !== "_id") delete result[key];
|
|
843
|
+
}
|
|
844
|
+
Object.assign(result, update);
|
|
845
|
+
result._id = id;
|
|
846
|
+
}
|
|
847
|
+
return result;
|
|
848
|
+
}
|
|
849
|
+
applyPipeline(documents, pipeline) {
|
|
850
|
+
let result = [...documents];
|
|
851
|
+
for (const stage of pipeline) {
|
|
852
|
+
const entry = Object.entries(stage)[0];
|
|
853
|
+
if (!entry) continue;
|
|
854
|
+
const [op, value] = entry;
|
|
855
|
+
switch (op) {
|
|
856
|
+
case "$match":
|
|
857
|
+
result = result.filter((doc) => this.matchDocument(doc, value));
|
|
858
|
+
break;
|
|
859
|
+
case "$project":
|
|
860
|
+
result = result.map((doc) => this.applyProjection(doc, value));
|
|
861
|
+
break;
|
|
862
|
+
case "$sort":
|
|
863
|
+
result = this.sortDocuments(result, value);
|
|
864
|
+
break;
|
|
865
|
+
case "$limit":
|
|
866
|
+
result = result.slice(0, value);
|
|
867
|
+
break;
|
|
868
|
+
case "$skip":
|
|
869
|
+
result = result.slice(value);
|
|
870
|
+
break;
|
|
871
|
+
case "$count":
|
|
872
|
+
result = [{ [value]: result.length }];
|
|
873
|
+
break;
|
|
874
|
+
case "$sample":
|
|
875
|
+
const size = value.size || 100;
|
|
876
|
+
result = this.shuffleArray(result).slice(0, size);
|
|
877
|
+
break;
|
|
878
|
+
case "$group":
|
|
879
|
+
result = this.groupDocuments(result, value);
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return result;
|
|
884
|
+
}
|
|
885
|
+
matchDocument(doc, query) {
|
|
886
|
+
for (const [key, value] of Object.entries(query)) {
|
|
887
|
+
if (key === "$and") {
|
|
888
|
+
if (!value.every((q) => this.matchDocument(doc, q))) {
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
if (key === "$or") {
|
|
894
|
+
if (!value.some((q) => this.matchDocument(doc, q))) {
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
const docValue = doc[key];
|
|
900
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
901
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
902
|
+
switch (op) {
|
|
903
|
+
case "$eq":
|
|
904
|
+
if (docValue !== opValue) return false;
|
|
905
|
+
break;
|
|
906
|
+
case "$ne":
|
|
907
|
+
if (docValue === opValue) return false;
|
|
908
|
+
break;
|
|
909
|
+
case "$gt":
|
|
910
|
+
if (!(docValue > opValue)) return false;
|
|
911
|
+
break;
|
|
912
|
+
case "$gte":
|
|
913
|
+
if (!(docValue >= opValue)) return false;
|
|
914
|
+
break;
|
|
915
|
+
case "$lt":
|
|
916
|
+
if (!(docValue < opValue)) return false;
|
|
917
|
+
break;
|
|
918
|
+
case "$lte":
|
|
919
|
+
if (!(docValue <= opValue)) return false;
|
|
920
|
+
break;
|
|
921
|
+
case "$in":
|
|
922
|
+
if (!opValue.includes(docValue)) return false;
|
|
923
|
+
break;
|
|
924
|
+
case "$exists":
|
|
925
|
+
if (opValue && docValue === void 0 || !opValue && docValue !== void 0)
|
|
926
|
+
return false;
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
} else {
|
|
931
|
+
if (docValue !== value) return false;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
42
934
|
return true;
|
|
935
|
+
}
|
|
936
|
+
sortDocuments(documents, sort) {
|
|
937
|
+
return [...documents].sort((a, b) => {
|
|
938
|
+
for (const [field, direction] of Object.entries(sort)) {
|
|
939
|
+
const aVal = a[field];
|
|
940
|
+
const bVal = b[field];
|
|
941
|
+
const dir = direction === -1 ? -1 : 1;
|
|
942
|
+
if (aVal === bVal) continue;
|
|
943
|
+
if (aVal === void 0 || aVal === null) return dir;
|
|
944
|
+
if (bVal === void 0 || bVal === null) return -dir;
|
|
945
|
+
if (aVal < bVal) return -dir;
|
|
946
|
+
if (aVal > bVal) return dir;
|
|
947
|
+
}
|
|
948
|
+
return 0;
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
shuffleArray(array) {
|
|
952
|
+
const result = [...array];
|
|
953
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
954
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
955
|
+
const temp = result[i];
|
|
956
|
+
result[i] = result[j];
|
|
957
|
+
result[j] = temp;
|
|
958
|
+
}
|
|
959
|
+
return result;
|
|
960
|
+
}
|
|
961
|
+
groupDocuments(documents, groupSpec) {
|
|
962
|
+
const groups = /* @__PURE__ */ new Map();
|
|
963
|
+
const idSpec = groupSpec._id;
|
|
964
|
+
for (const doc of documents) {
|
|
965
|
+
let groupKey;
|
|
966
|
+
if (idSpec === null) {
|
|
967
|
+
groupKey = "__all__";
|
|
968
|
+
} else if (typeof idSpec === "string" && idSpec.startsWith("$")) {
|
|
969
|
+
groupKey = JSON.stringify(doc[idSpec.slice(1)]);
|
|
970
|
+
} else {
|
|
971
|
+
groupKey = JSON.stringify(idSpec);
|
|
972
|
+
}
|
|
973
|
+
if (!groups.has(groupKey)) {
|
|
974
|
+
groups.set(groupKey, []);
|
|
975
|
+
}
|
|
976
|
+
groups.get(groupKey).push(doc);
|
|
977
|
+
}
|
|
978
|
+
const result = [];
|
|
979
|
+
for (const [key, docs] of groups) {
|
|
980
|
+
const grouped = { _id: key === "__all__" ? null : JSON.parse(key) };
|
|
981
|
+
for (const [field, spec] of Object.entries(groupSpec)) {
|
|
982
|
+
if (field === "_id") continue;
|
|
983
|
+
if (typeof spec === "object" && spec !== null) {
|
|
984
|
+
const [op, value] = Object.entries(spec)[0];
|
|
985
|
+
const fieldPath = typeof value === "string" && value.startsWith("$") ? value.slice(1) : null;
|
|
986
|
+
switch (op) {
|
|
987
|
+
case "$sum":
|
|
988
|
+
if (value === 1) {
|
|
989
|
+
grouped[field] = docs.length;
|
|
990
|
+
} else if (fieldPath) {
|
|
991
|
+
grouped[field] = docs.reduce((sum, doc) => sum + (doc[fieldPath] || 0), 0);
|
|
992
|
+
}
|
|
993
|
+
break;
|
|
994
|
+
case "$avg":
|
|
995
|
+
if (fieldPath) {
|
|
996
|
+
const values = docs.map((doc) => doc[fieldPath]).filter((v) => typeof v === "number");
|
|
997
|
+
grouped[field] = values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : null;
|
|
998
|
+
}
|
|
999
|
+
break;
|
|
1000
|
+
case "$first":
|
|
1001
|
+
if (fieldPath && docs.length > 0) {
|
|
1002
|
+
const firstDoc = docs[0];
|
|
1003
|
+
if (firstDoc) {
|
|
1004
|
+
grouped[field] = firstDoc[fieldPath];
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
break;
|
|
1008
|
+
case "$last":
|
|
1009
|
+
if (fieldPath && docs.length > 0) {
|
|
1010
|
+
const lastDoc = docs[docs.length - 1];
|
|
1011
|
+
if (lastDoc) {
|
|
1012
|
+
grouped[field] = lastDoc[fieldPath];
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
break;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
result.push(grouped);
|
|
1020
|
+
}
|
|
1021
|
+
return result;
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Close all database connections
|
|
1025
|
+
*/
|
|
1026
|
+
close() {
|
|
1027
|
+
for (const db of this.databases.values()) {
|
|
1028
|
+
db.close();
|
|
1029
|
+
}
|
|
1030
|
+
this.databases.clear();
|
|
1031
|
+
this.cursors.clear();
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
// src/wire/types.ts
|
|
1038
|
+
var OpCode, OpMsgFlags, DEFAULT_CAPABILITIES;
|
|
1039
|
+
var init_types = __esm({
|
|
1040
|
+
"src/wire/types.ts"() {
|
|
1041
|
+
OpCode = {
|
|
1042
|
+
/** Standard message format for all operations (MongoDB 3.6+) */
|
|
1043
|
+
OP_MSG: 2013,
|
|
1044
|
+
/** Compressed message wrapper */
|
|
1045
|
+
OP_COMPRESSED: 2012,
|
|
1046
|
+
/** Legacy reply to client (removed in 5.1, but still used for backwards compat) */
|
|
1047
|
+
OP_REPLY: 1,
|
|
1048
|
+
/** Legacy query (deprecated, but still used for initial handshake) */
|
|
1049
|
+
OP_QUERY: 2004,
|
|
1050
|
+
/** Legacy get more (removed in 5.1) */
|
|
1051
|
+
OP_GET_MORE: 2005,
|
|
1052
|
+
/** Legacy kill cursors (removed in 5.1) */
|
|
1053
|
+
OP_KILL_CURSORS: 2007
|
|
1054
|
+
};
|
|
1055
|
+
OpMsgFlags = {
|
|
1056
|
+
/** Message ends with CRC-32C checksum */
|
|
1057
|
+
CHECKSUM_PRESENT: 1 << 0,
|
|
1058
|
+
/** More messages follow; don't respond until bit=0 */
|
|
1059
|
+
MORE_TO_COME: 1 << 1,
|
|
1060
|
+
/** Client accepts multiple replies (exhaust cursors) */
|
|
1061
|
+
EXHAUST_ALLOWED: 1 << 16
|
|
1062
|
+
};
|
|
1063
|
+
DEFAULT_CAPABILITIES = {
|
|
1064
|
+
maxBsonObjectSize: 16 * 1024 * 1024,
|
|
1065
|
+
// 16 MiB
|
|
1066
|
+
maxMessageSizeBytes: 48 * 1024 * 1024,
|
|
1067
|
+
// 48 MiB
|
|
1068
|
+
maxWriteBatchSize: 1e5,
|
|
1069
|
+
minWireVersion: 0,
|
|
1070
|
+
maxWireVersion: 17,
|
|
1071
|
+
// MongoDB 6.0
|
|
1072
|
+
logicalSessionTimeoutMinutes: 30,
|
|
1073
|
+
readOnly: false
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
function parseHeader(buffer) {
|
|
1078
|
+
if (buffer.length < HEADER_SIZE) {
|
|
1079
|
+
throw new Error(`Buffer too small for header: ${buffer.length} < ${HEADER_SIZE}`);
|
|
1080
|
+
}
|
|
1081
|
+
return {
|
|
1082
|
+
messageLength: buffer.readInt32LE(0),
|
|
1083
|
+
requestID: buffer.readInt32LE(4),
|
|
1084
|
+
responseTo: buffer.readInt32LE(8),
|
|
1085
|
+
opCode: buffer.readInt32LE(12)
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
function parseCString(buffer, offset) {
|
|
1089
|
+
let end = offset;
|
|
1090
|
+
while (end < buffer.length && buffer[end] !== 0) {
|
|
1091
|
+
end++;
|
|
1092
|
+
}
|
|
1093
|
+
const str = buffer.toString("utf8", offset, end);
|
|
1094
|
+
return [str, end + 1];
|
|
1095
|
+
}
|
|
1096
|
+
function parseBSONDocument(buffer, offset) {
|
|
1097
|
+
const length = buffer.readInt32LE(offset);
|
|
1098
|
+
const doc = BSON.deserialize(buffer.subarray(offset, offset + length));
|
|
1099
|
+
return [doc, offset + length];
|
|
1100
|
+
}
|
|
1101
|
+
function parseOpMsg(buffer) {
|
|
1102
|
+
const header = parseHeader(buffer);
|
|
1103
|
+
if (header.opCode !== OpCode.OP_MSG) {
|
|
1104
|
+
throw new Error(`Expected OP_MSG (${OpCode.OP_MSG}), got ${header.opCode}`);
|
|
1105
|
+
}
|
|
1106
|
+
let offset = HEADER_SIZE;
|
|
1107
|
+
const flagBits = buffer.readUInt32LE(offset);
|
|
1108
|
+
offset += 4;
|
|
1109
|
+
const hasChecksum = (flagBits & OpMsgFlags.CHECKSUM_PRESENT) !== 0;
|
|
1110
|
+
const messageEnd = hasChecksum ? buffer.length - 4 : buffer.length;
|
|
1111
|
+
const sections = [];
|
|
1112
|
+
while (offset < messageEnd) {
|
|
1113
|
+
const kind = buffer.readUInt8(offset);
|
|
1114
|
+
offset += 1;
|
|
1115
|
+
if (kind === 0) {
|
|
1116
|
+
const [body, newOffset] = parseBSONDocument(buffer, offset);
|
|
1117
|
+
sections.push({ kind: 0, body });
|
|
1118
|
+
offset = newOffset;
|
|
1119
|
+
} else if (kind === 1) {
|
|
1120
|
+
const sectionSize = buffer.readInt32LE(offset);
|
|
1121
|
+
const sectionEnd = offset + sectionSize;
|
|
1122
|
+
offset += 4;
|
|
1123
|
+
const [identifier, docStart] = parseCString(buffer, offset);
|
|
1124
|
+
offset = docStart;
|
|
1125
|
+
const documents = [];
|
|
1126
|
+
while (offset < sectionEnd) {
|
|
1127
|
+
const [doc, newOffset] = parseBSONDocument(buffer, offset);
|
|
1128
|
+
documents.push(doc);
|
|
1129
|
+
offset = newOffset;
|
|
1130
|
+
}
|
|
1131
|
+
sections.push({ kind: 1, identifier, documents });
|
|
1132
|
+
} else {
|
|
1133
|
+
throw new Error(`Unknown section kind: ${kind}`);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
if (hasChecksum) {
|
|
1137
|
+
return { header, flagBits, sections, checksum: buffer.readUInt32LE(messageEnd) };
|
|
1138
|
+
}
|
|
1139
|
+
return { header, flagBits, sections };
|
|
1140
|
+
}
|
|
1141
|
+
function parseOpQuery(buffer) {
|
|
1142
|
+
const header = parseHeader(buffer);
|
|
1143
|
+
if (header.opCode !== OpCode.OP_QUERY) {
|
|
1144
|
+
throw new Error(`Expected OP_QUERY (${OpCode.OP_QUERY}), got ${header.opCode}`);
|
|
1145
|
+
}
|
|
1146
|
+
let offset = HEADER_SIZE;
|
|
1147
|
+
const flags = buffer.readInt32LE(offset);
|
|
1148
|
+
offset += 4;
|
|
1149
|
+
const [fullCollectionName, afterName] = parseCString(buffer, offset);
|
|
1150
|
+
offset = afterName;
|
|
1151
|
+
const numberToSkip = buffer.readInt32LE(offset);
|
|
1152
|
+
offset += 4;
|
|
1153
|
+
const numberToReturn = buffer.readInt32LE(offset);
|
|
1154
|
+
offset += 4;
|
|
1155
|
+
const [query, afterQuery] = parseBSONDocument(buffer, offset);
|
|
1156
|
+
offset = afterQuery;
|
|
1157
|
+
if (offset < buffer.length) {
|
|
1158
|
+
const [returnFieldsSelector] = parseBSONDocument(buffer, offset);
|
|
1159
|
+
return {
|
|
1160
|
+
header,
|
|
1161
|
+
flags,
|
|
1162
|
+
fullCollectionName,
|
|
1163
|
+
numberToSkip,
|
|
1164
|
+
numberToReturn,
|
|
1165
|
+
query,
|
|
1166
|
+
returnFieldsSelector
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
return {
|
|
1170
|
+
header,
|
|
1171
|
+
flags,
|
|
1172
|
+
fullCollectionName,
|
|
1173
|
+
numberToSkip,
|
|
1174
|
+
numberToReturn,
|
|
1175
|
+
query
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
function serializeOpMsg(requestID, responseTo, body, additionalSections) {
|
|
1179
|
+
const bodyBson = Buffer.from(BSON.serialize(body));
|
|
1180
|
+
let sectionsSize = 1 + bodyBson.length;
|
|
1181
|
+
const serializedSections = [];
|
|
1182
|
+
const messageLength = HEADER_SIZE + 4 + sectionsSize;
|
|
1183
|
+
const buffer = Buffer.allocUnsafe(messageLength);
|
|
1184
|
+
let offset = 0;
|
|
1185
|
+
buffer.writeInt32LE(messageLength, offset);
|
|
1186
|
+
offset += 4;
|
|
1187
|
+
buffer.writeInt32LE(requestID, offset);
|
|
1188
|
+
offset += 4;
|
|
1189
|
+
buffer.writeInt32LE(responseTo, offset);
|
|
1190
|
+
offset += 4;
|
|
1191
|
+
buffer.writeInt32LE(OpCode.OP_MSG, offset);
|
|
1192
|
+
offset += 4;
|
|
1193
|
+
buffer.writeUInt32LE(0, offset);
|
|
1194
|
+
offset += 4;
|
|
1195
|
+
buffer.writeUInt8(0, offset);
|
|
1196
|
+
offset += 1;
|
|
1197
|
+
bodyBson.copy(buffer, offset);
|
|
1198
|
+
offset += bodyBson.length;
|
|
1199
|
+
for (const sectionBuf of serializedSections) {
|
|
1200
|
+
sectionBuf.copy(buffer, offset);
|
|
1201
|
+
offset += sectionBuf.length;
|
|
1202
|
+
}
|
|
1203
|
+
return buffer;
|
|
1204
|
+
}
|
|
1205
|
+
function serializeOpReply(requestID, responseTo, documents, cursorID = 0n, responseFlags = 0) {
|
|
1206
|
+
const docBuffers = documents.map((doc) => Buffer.from(BSON.serialize(doc)));
|
|
1207
|
+
const docsSize = docBuffers.reduce((sum, buf) => sum + buf.length, 0);
|
|
1208
|
+
const messageLength = HEADER_SIZE + 20 + docsSize;
|
|
1209
|
+
const buffer = Buffer.allocUnsafe(messageLength);
|
|
1210
|
+
let offset = 0;
|
|
1211
|
+
buffer.writeInt32LE(messageLength, offset);
|
|
1212
|
+
offset += 4;
|
|
1213
|
+
buffer.writeInt32LE(requestID, offset);
|
|
1214
|
+
offset += 4;
|
|
1215
|
+
buffer.writeInt32LE(responseTo, offset);
|
|
1216
|
+
offset += 4;
|
|
1217
|
+
buffer.writeInt32LE(OpCode.OP_REPLY, offset);
|
|
1218
|
+
offset += 4;
|
|
1219
|
+
buffer.writeInt32LE(responseFlags, offset);
|
|
1220
|
+
offset += 4;
|
|
1221
|
+
buffer.writeBigInt64LE(cursorID, offset);
|
|
1222
|
+
offset += 8;
|
|
1223
|
+
buffer.writeInt32LE(0, offset);
|
|
1224
|
+
offset += 4;
|
|
1225
|
+
buffer.writeInt32LE(documents.length, offset);
|
|
1226
|
+
offset += 4;
|
|
1227
|
+
for (const docBuf of docBuffers) {
|
|
1228
|
+
docBuf.copy(buffer, offset);
|
|
1229
|
+
offset += docBuf.length;
|
|
1230
|
+
}
|
|
1231
|
+
return buffer;
|
|
1232
|
+
}
|
|
1233
|
+
function parseMessage(buffer) {
|
|
1234
|
+
const header = parseHeader(buffer);
|
|
1235
|
+
switch (header.opCode) {
|
|
1236
|
+
case OpCode.OP_MSG:
|
|
1237
|
+
return parseOpMsg(buffer);
|
|
1238
|
+
case OpCode.OP_QUERY:
|
|
1239
|
+
return parseOpQuery(buffer);
|
|
1240
|
+
default:
|
|
1241
|
+
throw new Error(`Unsupported opcode: ${header.opCode}`);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
function extractCommand(message) {
|
|
1245
|
+
if ("sections" in message) {
|
|
1246
|
+
const section0 = message.sections.find((s) => s.kind === 0);
|
|
1247
|
+
if (!section0) {
|
|
1248
|
+
throw new Error("OP_MSG missing section 0 (body)");
|
|
1249
|
+
}
|
|
1250
|
+
const command = section0.body;
|
|
1251
|
+
const db = command.$db;
|
|
1252
|
+
if (!db) {
|
|
1253
|
+
throw new Error("OP_MSG command missing $db field");
|
|
1254
|
+
}
|
|
1255
|
+
const documentSequences = /* @__PURE__ */ new Map();
|
|
1256
|
+
for (const section of message.sections) {
|
|
1257
|
+
if (section.kind === 1) {
|
|
1258
|
+
documentSequences.set(section.identifier, section.documents);
|
|
1259
|
+
}
|
|
43
1260
|
}
|
|
44
|
-
|
|
45
|
-
|
|
1261
|
+
return { db, command, documentSequences };
|
|
1262
|
+
} else {
|
|
1263
|
+
const parts = message.fullCollectionName.split(".");
|
|
1264
|
+
const db = parts[0] || "admin";
|
|
1265
|
+
const command = message.query;
|
|
1266
|
+
return { db, command, documentSequences: /* @__PURE__ */ new Map() };
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
var HEADER_SIZE;
|
|
1270
|
+
var init_message = __esm({
|
|
1271
|
+
"src/wire/message.ts"() {
|
|
1272
|
+
init_types();
|
|
1273
|
+
HEADER_SIZE = 16;
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
// src/types/rpc.ts
|
|
1278
|
+
function getErrorCodeName(code) {
|
|
1279
|
+
for (const [name, value] of Object.entries(ErrorCode)) {
|
|
1280
|
+
if (value === code) {
|
|
1281
|
+
return name.split("_").map((word) => word.charAt(0) + word.slice(1).toLowerCase()).join("");
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
return "UnknownError";
|
|
1285
|
+
}
|
|
1286
|
+
function successResponse(data = {}) {
|
|
1287
|
+
return { ok: 1, ...data };
|
|
46
1288
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
1289
|
+
function errorResponse(code, errmsg, codeName) {
|
|
1290
|
+
return {
|
|
1291
|
+
ok: 0,
|
|
1292
|
+
code,
|
|
1293
|
+
codeName: codeName || getErrorCodeName(code),
|
|
1294
|
+
errmsg
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
var ErrorCode;
|
|
1298
|
+
var init_rpc = __esm({
|
|
1299
|
+
"src/types/rpc.ts"() {
|
|
1300
|
+
ErrorCode = {
|
|
1301
|
+
OK: 0,
|
|
1302
|
+
INTERNAL_ERROR: 1,
|
|
1303
|
+
BAD_VALUE: 2,
|
|
1304
|
+
NO_SUCH_KEY: 4,
|
|
1305
|
+
GRAPH_CONTAINS_CYCLE: 5,
|
|
1306
|
+
HOST_UNREACHABLE: 6,
|
|
1307
|
+
HOST_NOT_FOUND: 7,
|
|
1308
|
+
UNKNOWN_ERROR: 8,
|
|
1309
|
+
FAILED_TO_PARSE: 9,
|
|
1310
|
+
CANNOT_MUTATE_OBJECT: 10,
|
|
1311
|
+
USER_NOT_FOUND: 11,
|
|
1312
|
+
UNSUPPORTED_FORMAT: 12,
|
|
1313
|
+
UNAUTHORIZED: 13,
|
|
1314
|
+
TYPE_MISMATCH: 14,
|
|
1315
|
+
OVERFLOW: 15,
|
|
1316
|
+
INVALID_LENGTH: 16,
|
|
1317
|
+
PROTOCOL_ERROR: 17,
|
|
1318
|
+
AUTHENTICATION_FAILED: 18,
|
|
1319
|
+
CANNOT_REUSE_OBJECT: 19,
|
|
1320
|
+
ILLEGAL_OPERATION: 20,
|
|
1321
|
+
EMPTY_ARRAY_OPERATION: 21,
|
|
1322
|
+
INVALID_BSON: 22,
|
|
1323
|
+
ALREADY_INITIALIZED: 23,
|
|
1324
|
+
LOCK_TIMEOUT: 24,
|
|
1325
|
+
REMOTE_VALIDATION_ERROR: 25,
|
|
1326
|
+
NAMESPACE_NOT_FOUND: 26,
|
|
1327
|
+
INDEX_NOT_FOUND: 27,
|
|
1328
|
+
PATH_NOT_VIABLE: 28,
|
|
1329
|
+
NON_EXISTENT_PATH: 29,
|
|
1330
|
+
INVALID_PATH: 30,
|
|
1331
|
+
ROLE_NOT_FOUND: 31,
|
|
1332
|
+
ROLES_NOT_RELATED: 32,
|
|
1333
|
+
PRIVILEGE_NOT_FOUND: 33,
|
|
1334
|
+
CANNOT_BACKFILL_ARRAY: 34,
|
|
1335
|
+
COMMAND_NOT_FOUND: 59,
|
|
1336
|
+
DATABASE_NOT_FOUND: 60,
|
|
1337
|
+
LOCATION_ERROR: 16755,
|
|
1338
|
+
DUPLICATE_KEY: 11e3
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
// src/wire/commands/types.ts
|
|
1344
|
+
function successResponse2(data = {}) {
|
|
1345
|
+
return successResponse(data);
|
|
1346
|
+
}
|
|
1347
|
+
function errorResponse2(code, errmsg, codeName) {
|
|
1348
|
+
return errorResponse(code, errmsg, codeName);
|
|
1349
|
+
}
|
|
1350
|
+
var init_types2 = __esm({
|
|
1351
|
+
"src/wire/commands/types.ts"() {
|
|
1352
|
+
init_rpc();
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
var HelloCommand, PingCommand, BuildInfoCommand, HostInfoCommand, WhatsmyuriCommand, GetLogCommand, GetParameterCommand, GetCmdLineOptsCommand;
|
|
1356
|
+
var init_hello = __esm({
|
|
1357
|
+
"src/wire/commands/hello.ts"() {
|
|
1358
|
+
init_types2();
|
|
1359
|
+
init_types();
|
|
1360
|
+
HelloCommand = class {
|
|
1361
|
+
processId = new ObjectId();
|
|
1362
|
+
counter = 0n;
|
|
1363
|
+
async execute(command, context) {
|
|
1364
|
+
const now = /* @__PURE__ */ new Date();
|
|
1365
|
+
const response = {
|
|
1366
|
+
// Primary fields
|
|
1367
|
+
ismaster: true,
|
|
1368
|
+
isWritablePrimary: true,
|
|
1369
|
+
// Topology version (changes when server state changes)
|
|
1370
|
+
topologyVersion: {
|
|
1371
|
+
processId: this.processId,
|
|
1372
|
+
counter: Long.fromBigInt(this.counter++)
|
|
1373
|
+
},
|
|
1374
|
+
// Size limits
|
|
1375
|
+
maxBsonObjectSize: DEFAULT_CAPABILITIES.maxBsonObjectSize,
|
|
1376
|
+
maxMessageSizeBytes: DEFAULT_CAPABILITIES.maxMessageSizeBytes,
|
|
1377
|
+
maxWriteBatchSize: DEFAULT_CAPABILITIES.maxWriteBatchSize,
|
|
1378
|
+
// Time
|
|
1379
|
+
localTime: now,
|
|
1380
|
+
// Session support
|
|
1381
|
+
logicalSessionTimeoutMinutes: DEFAULT_CAPABILITIES.logicalSessionTimeoutMinutes,
|
|
1382
|
+
// Connection info
|
|
1383
|
+
connectionId: context.connectionId,
|
|
1384
|
+
// Wire version (MongoDB 6.0 = 17)
|
|
1385
|
+
minWireVersion: DEFAULT_CAPABILITIES.minWireVersion,
|
|
1386
|
+
maxWireVersion: DEFAULT_CAPABILITIES.maxWireVersion,
|
|
1387
|
+
// Not read-only
|
|
1388
|
+
readOnly: DEFAULT_CAPABILITIES.readOnly,
|
|
1389
|
+
// Success
|
|
1390
|
+
ok: 1
|
|
1391
|
+
};
|
|
1392
|
+
if (command.hello || command.helloOk) {
|
|
1393
|
+
response.helloOk = true;
|
|
1394
|
+
}
|
|
1395
|
+
if (command.saslSupportedMechs) {
|
|
1396
|
+
response.saslSupportedMechs = ["SCRAM-SHA-256", "SCRAM-SHA-1"];
|
|
1397
|
+
}
|
|
1398
|
+
if (command.compression && Array.isArray(command.compression)) {
|
|
1399
|
+
response.compression = [];
|
|
1400
|
+
}
|
|
1401
|
+
if (command.client) ;
|
|
1402
|
+
return { response };
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
PingCommand = class {
|
|
1406
|
+
async execute(_command, _context) {
|
|
1407
|
+
return { response: successResponse2() };
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
BuildInfoCommand = class {
|
|
1411
|
+
async execute(_command, _context) {
|
|
1412
|
+
const response = successResponse2({
|
|
1413
|
+
version: "6.0.0-mongo.do",
|
|
1414
|
+
gitVersion: "mongo.do-0.1.0",
|
|
1415
|
+
modules: [],
|
|
1416
|
+
allocator: "system",
|
|
1417
|
+
javascriptEngine: "none",
|
|
1418
|
+
sysInfo: "mongo.do-on-workers",
|
|
1419
|
+
versionArray: [6, 0, 0, 0],
|
|
1420
|
+
openssl: {
|
|
1421
|
+
running: "not-applicable",
|
|
1422
|
+
compiled: "not-applicable"
|
|
1423
|
+
},
|
|
1424
|
+
buildEnvironment: {
|
|
1425
|
+
target_os: "cloudflare-workers",
|
|
1426
|
+
target_arch: "wasm"
|
|
1427
|
+
},
|
|
1428
|
+
bits: 64,
|
|
1429
|
+
debug: false,
|
|
1430
|
+
maxBsonObjectSize: DEFAULT_CAPABILITIES.maxBsonObjectSize,
|
|
1431
|
+
storageEngines: ["sqlite"]
|
|
1432
|
+
});
|
|
1433
|
+
return { response };
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
HostInfoCommand = class {
|
|
1437
|
+
async execute(_command, _context) {
|
|
1438
|
+
const now = /* @__PURE__ */ new Date();
|
|
1439
|
+
const response = successResponse2({
|
|
1440
|
+
system: {
|
|
1441
|
+
currentTime: now,
|
|
1442
|
+
hostname: "mongo.do-server",
|
|
1443
|
+
cpuAddrSize: 64,
|
|
1444
|
+
memSizeMB: 512,
|
|
1445
|
+
memLimitMB: 512,
|
|
1446
|
+
numCores: 1,
|
|
1447
|
+
cpuArch: "wasm",
|
|
1448
|
+
numaEnabled: false
|
|
1449
|
+
},
|
|
1450
|
+
os: {
|
|
1451
|
+
type: "cloudflare-workers",
|
|
1452
|
+
name: "MondoDB",
|
|
1453
|
+
version: "0.1.0"
|
|
1454
|
+
},
|
|
1455
|
+
extra: {
|
|
1456
|
+
note: "Running on Cloudflare Workers Durable Objects"
|
|
1457
|
+
}
|
|
1458
|
+
});
|
|
1459
|
+
return { response };
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
WhatsmyuriCommand = class {
|
|
1463
|
+
async execute(_command, _context) {
|
|
1464
|
+
return {
|
|
1465
|
+
response: successResponse2({
|
|
1466
|
+
you: "127.0.0.1:0"
|
|
1467
|
+
})
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
GetLogCommand = class {
|
|
1472
|
+
async execute(command, _context) {
|
|
1473
|
+
const logType = command.getLog;
|
|
1474
|
+
if (logType === "*") {
|
|
1475
|
+
return {
|
|
1476
|
+
response: successResponse2({
|
|
1477
|
+
names: ["global", "startupWarnings"]
|
|
1478
|
+
})
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
if (logType === "startupWarnings") {
|
|
1482
|
+
return {
|
|
1483
|
+
response: successResponse2({
|
|
1484
|
+
totalLinesWritten: 1,
|
|
1485
|
+
log: [
|
|
1486
|
+
JSON.stringify({
|
|
1487
|
+
t: { $date: (/* @__PURE__ */ new Date()).toISOString() },
|
|
1488
|
+
s: "I",
|
|
1489
|
+
c: "STORAGE",
|
|
1490
|
+
msg: "MondoDB using SQLite storage engine"
|
|
1491
|
+
})
|
|
1492
|
+
]
|
|
1493
|
+
})
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
return {
|
|
1497
|
+
response: successResponse2({
|
|
1498
|
+
totalLinesWritten: 0,
|
|
1499
|
+
log: []
|
|
1500
|
+
})
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
GetParameterCommand = class {
|
|
1505
|
+
async execute(command, _context) {
|
|
1506
|
+
const param = command.getParameter;
|
|
1507
|
+
const parameters = {
|
|
1508
|
+
featureCompatibilityVersion: { version: "6.0" },
|
|
1509
|
+
authenticationMechanisms: ["SCRAM-SHA-256", "SCRAM-SHA-1"]
|
|
1510
|
+
};
|
|
1511
|
+
if (param === "*") {
|
|
1512
|
+
return { response: successResponse2(parameters) };
|
|
1513
|
+
}
|
|
1514
|
+
if (typeof param === "string" && param in parameters) {
|
|
1515
|
+
return {
|
|
1516
|
+
response: successResponse2({
|
|
1517
|
+
[param]: parameters[param]
|
|
1518
|
+
})
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
return { response: successResponse2({}) };
|
|
1522
|
+
}
|
|
1523
|
+
};
|
|
1524
|
+
GetCmdLineOptsCommand = class {
|
|
1525
|
+
async execute(_command, _context) {
|
|
1526
|
+
return {
|
|
1527
|
+
response: successResponse2({
|
|
1528
|
+
argv: ["mongo.do-server"],
|
|
1529
|
+
parsed: {
|
|
1530
|
+
storage: {
|
|
1531
|
+
engine: "sqlite"
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
})
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
// src/wire/commands/admin.ts
|
|
1542
|
+
var ListDatabasesCommand, ListCollectionsCommand, CreateCommand, DropCommand, DropDatabaseCommand, CollStatsCommand, DbStatsCommand, ServerStatusCommand;
|
|
1543
|
+
var init_admin = __esm({
|
|
1544
|
+
"src/wire/commands/admin.ts"() {
|
|
1545
|
+
init_types2();
|
|
1546
|
+
ListDatabasesCommand = class {
|
|
1547
|
+
constructor(backend) {
|
|
1548
|
+
this.backend = backend;
|
|
1549
|
+
}
|
|
1550
|
+
async execute(command, _context) {
|
|
1551
|
+
const databases = await this.backend.listDatabases();
|
|
1552
|
+
const nameOnly = command.nameOnly === true;
|
|
1553
|
+
let filteredDbs = databases;
|
|
1554
|
+
if (command.filter && typeof command.filter === "object") {
|
|
1555
|
+
const filter = command.filter;
|
|
1556
|
+
if (filter.name) {
|
|
1557
|
+
if (typeof filter.name === "string") {
|
|
1558
|
+
filteredDbs = databases.filter((db) => db.name === filter.name);
|
|
1559
|
+
} else if (filter.name.$regex) {
|
|
1560
|
+
const regex = new RegExp(filter.name.$regex, filter.name.$options);
|
|
1561
|
+
filteredDbs = databases.filter((db) => regex.test(db.name));
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
const totalSize = filteredDbs.reduce((sum, db) => sum + db.sizeOnDisk, 0);
|
|
1566
|
+
if (nameOnly) {
|
|
1567
|
+
return {
|
|
1568
|
+
response: successResponse2({
|
|
1569
|
+
databases: filteredDbs.map((db) => ({ name: db.name }))
|
|
1570
|
+
})
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
return {
|
|
1574
|
+
response: successResponse2({
|
|
1575
|
+
databases: filteredDbs,
|
|
1576
|
+
totalSize,
|
|
1577
|
+
totalSizeMb: totalSize / (1024 * 1024)
|
|
1578
|
+
})
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
ListCollectionsCommand = class {
|
|
1583
|
+
constructor(backend) {
|
|
1584
|
+
this.backend = backend;
|
|
1585
|
+
}
|
|
1586
|
+
async execute(command, context) {
|
|
1587
|
+
const db = context.db;
|
|
1588
|
+
const filter = command.filter;
|
|
1589
|
+
const nameOnly = command.nameOnly === true;
|
|
1590
|
+
const collections = await this.backend.listCollections(db, filter);
|
|
1591
|
+
if (nameOnly) {
|
|
1592
|
+
return {
|
|
1593
|
+
response: successResponse2({
|
|
1594
|
+
cursor: {
|
|
1595
|
+
id: 0n,
|
|
1596
|
+
ns: `${db}.$cmd.listCollections`,
|
|
1597
|
+
firstBatch: collections.map((c) => ({ name: c.name, type: c.type }))
|
|
1598
|
+
}
|
|
1599
|
+
})
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
return {
|
|
1603
|
+
response: successResponse2({
|
|
1604
|
+
cursor: {
|
|
1605
|
+
id: 0n,
|
|
1606
|
+
ns: `${db}.$cmd.listCollections`,
|
|
1607
|
+
firstBatch: collections
|
|
1608
|
+
}
|
|
1609
|
+
})
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
CreateCommand = class {
|
|
1614
|
+
constructor(backend) {
|
|
1615
|
+
this.backend = backend;
|
|
1616
|
+
}
|
|
1617
|
+
async execute(command, context) {
|
|
1618
|
+
const collectionName = command.create;
|
|
1619
|
+
if (!collectionName || typeof collectionName !== "string") {
|
|
1620
|
+
return {
|
|
1621
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "create requires a string collection name")
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
const options = {};
|
|
1625
|
+
if (command.capped) options.capped = command.capped;
|
|
1626
|
+
if (command.size) options.size = command.size;
|
|
1627
|
+
if (command.max) options.max = command.max;
|
|
1628
|
+
if (command.validator) options.validator = command.validator;
|
|
1629
|
+
if (command.validationLevel) options.validationLevel = command.validationLevel;
|
|
1630
|
+
if (command.validationAction) options.validationAction = command.validationAction;
|
|
1631
|
+
await this.backend.createCollection(context.db, collectionName, options);
|
|
1632
|
+
return { response: successResponse2() };
|
|
1633
|
+
}
|
|
1634
|
+
};
|
|
1635
|
+
DropCommand = class {
|
|
1636
|
+
constructor(backend) {
|
|
1637
|
+
this.backend = backend;
|
|
1638
|
+
}
|
|
1639
|
+
async execute(command, context) {
|
|
1640
|
+
const collectionName = command.drop;
|
|
1641
|
+
if (!collectionName || typeof collectionName !== "string") {
|
|
1642
|
+
return {
|
|
1643
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "drop requires a string collection name")
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
const exists = await this.backend.collectionExists(context.db, collectionName);
|
|
1647
|
+
if (!exists) {
|
|
1648
|
+
return {
|
|
1649
|
+
response: errorResponse2(
|
|
1650
|
+
ErrorCode.NAMESPACE_NOT_FOUND,
|
|
1651
|
+
`ns not found: ${context.db}.${collectionName}`
|
|
1652
|
+
)
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
await this.backend.dropCollection(context.db, collectionName);
|
|
1656
|
+
return {
|
|
1657
|
+
response: successResponse2({
|
|
1658
|
+
nIndexesWas: 1,
|
|
1659
|
+
ns: `${context.db}.${collectionName}`
|
|
1660
|
+
})
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
};
|
|
1664
|
+
DropDatabaseCommand = class {
|
|
1665
|
+
constructor(backend) {
|
|
1666
|
+
this.backend = backend;
|
|
1667
|
+
}
|
|
1668
|
+
async execute(_command, context) {
|
|
1669
|
+
await this.backend.dropDatabase(context.db);
|
|
1670
|
+
return {
|
|
1671
|
+
response: successResponse2({
|
|
1672
|
+
dropped: context.db
|
|
1673
|
+
})
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
};
|
|
1677
|
+
CollStatsCommand = class {
|
|
1678
|
+
constructor(backend) {
|
|
1679
|
+
this.backend = backend;
|
|
1680
|
+
}
|
|
1681
|
+
async execute(command, context) {
|
|
1682
|
+
const collectionName = command.collStats;
|
|
1683
|
+
if (!collectionName || typeof collectionName !== "string") {
|
|
1684
|
+
return {
|
|
1685
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "collStats requires a collection name")
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
const exists = await this.backend.collectionExists(context.db, collectionName);
|
|
1689
|
+
if (!exists) {
|
|
1690
|
+
return {
|
|
1691
|
+
response: errorResponse2(
|
|
1692
|
+
ErrorCode.NAMESPACE_NOT_FOUND,
|
|
1693
|
+
`Collection [${context.db}.${collectionName}] not found`
|
|
1694
|
+
)
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
const stats = await this.backend.collStats(context.db, collectionName);
|
|
1698
|
+
return {
|
|
1699
|
+
response: successResponse2({
|
|
1700
|
+
...stats,
|
|
1701
|
+
wiredTiger: {},
|
|
1702
|
+
// Empty for SQLite
|
|
1703
|
+
indexDetails: {},
|
|
1704
|
+
scaleFactor: 1
|
|
1705
|
+
})
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
DbStatsCommand = class {
|
|
1710
|
+
constructor(backend) {
|
|
1711
|
+
this.backend = backend;
|
|
1712
|
+
}
|
|
1713
|
+
async execute(command, context) {
|
|
1714
|
+
const scale = typeof command.scale === "number" ? command.scale : 1;
|
|
1715
|
+
const stats = await this.backend.dbStats(context.db);
|
|
1716
|
+
return {
|
|
1717
|
+
response: successResponse2({
|
|
1718
|
+
db: stats.db,
|
|
1719
|
+
collections: stats.collections,
|
|
1720
|
+
views: stats.views,
|
|
1721
|
+
objects: stats.objects,
|
|
1722
|
+
avgObjSize: stats.avgObjSize,
|
|
1723
|
+
dataSize: stats.dataSize / scale,
|
|
1724
|
+
storageSize: stats.storageSize / scale,
|
|
1725
|
+
indexes: stats.indexes,
|
|
1726
|
+
indexSize: stats.indexSize / scale,
|
|
1727
|
+
totalSize: (stats.dataSize + stats.indexSize) / scale,
|
|
1728
|
+
scaleFactor: scale,
|
|
1729
|
+
fsUsedSize: stats.storageSize / scale,
|
|
1730
|
+
fsTotalSize: stats.storageSize * 10 / scale
|
|
1731
|
+
// Estimate
|
|
1732
|
+
})
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
ServerStatusCommand = class {
|
|
1737
|
+
startTime = /* @__PURE__ */ new Date();
|
|
1738
|
+
constructor(_backend) {
|
|
1739
|
+
}
|
|
1740
|
+
async execute(_command, _context) {
|
|
1741
|
+
const now = /* @__PURE__ */ new Date();
|
|
1742
|
+
const uptimeSeconds = Math.floor((now.getTime() - this.startTime.getTime()) / 1e3);
|
|
1743
|
+
return {
|
|
1744
|
+
response: successResponse2({
|
|
1745
|
+
host: "mongo.do-server",
|
|
1746
|
+
version: "6.0.0-mongo.do",
|
|
1747
|
+
process: "mongo.do-server",
|
|
1748
|
+
pid: process.pid || 1,
|
|
1749
|
+
uptime: uptimeSeconds,
|
|
1750
|
+
uptimeMillis: uptimeSeconds * 1e3,
|
|
1751
|
+
uptimeEstimate: uptimeSeconds,
|
|
1752
|
+
localTime: now,
|
|
1753
|
+
connections: {
|
|
1754
|
+
current: 1,
|
|
1755
|
+
available: 100,
|
|
1756
|
+
totalCreated: 1,
|
|
1757
|
+
active: 1
|
|
1758
|
+
},
|
|
1759
|
+
opcounters: {
|
|
1760
|
+
insert: 0,
|
|
1761
|
+
query: 0,
|
|
1762
|
+
update: 0,
|
|
1763
|
+
delete: 0,
|
|
1764
|
+
getmore: 0,
|
|
1765
|
+
command: 0
|
|
1766
|
+
},
|
|
1767
|
+
mem: {
|
|
1768
|
+
bits: 64,
|
|
1769
|
+
resident: 50,
|
|
1770
|
+
virtual: 100,
|
|
1771
|
+
supported: true
|
|
1772
|
+
},
|
|
1773
|
+
storageEngine: {
|
|
1774
|
+
name: "sqlite",
|
|
1775
|
+
supportsCommittedReads: true,
|
|
1776
|
+
oldestRequiredTimestampForCrashRecovery: null,
|
|
1777
|
+
supportsPendingDrops: false,
|
|
1778
|
+
dropPendingIdents: 0,
|
|
1779
|
+
supportsSnapshotReadConcern: true,
|
|
1780
|
+
readOnly: false,
|
|
1781
|
+
persistent: true,
|
|
1782
|
+
backupCursorOpen: false
|
|
1783
|
+
},
|
|
1784
|
+
asserts: {
|
|
1785
|
+
regular: 0,
|
|
1786
|
+
warning: 0,
|
|
1787
|
+
msg: 0,
|
|
1788
|
+
user: 0,
|
|
1789
|
+
tripwire: 0,
|
|
1790
|
+
rollovers: 0
|
|
1791
|
+
},
|
|
1792
|
+
network: {
|
|
1793
|
+
bytesIn: 0,
|
|
1794
|
+
bytesOut: 0,
|
|
1795
|
+
numRequests: 0
|
|
1796
|
+
}
|
|
1797
|
+
})
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
});
|
|
1803
|
+
var FindCommand, InsertCommand, UpdateCommand, DeleteCommand, CountCommand, DistinctCommand, GetMoreCommand, KillCursorsCommand;
|
|
1804
|
+
var init_crud = __esm({
|
|
1805
|
+
"src/wire/commands/crud.ts"() {
|
|
1806
|
+
init_types2();
|
|
1807
|
+
FindCommand = class {
|
|
1808
|
+
constructor(backend) {
|
|
1809
|
+
this.backend = backend;
|
|
1810
|
+
}
|
|
1811
|
+
async execute(command, context) {
|
|
1812
|
+
const collection = command.find;
|
|
1813
|
+
if (!collection || typeof collection !== "string") {
|
|
1814
|
+
return {
|
|
1815
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "find requires a collection name")
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
const options = {
|
|
1819
|
+
filter: command.filter,
|
|
1820
|
+
projection: command.projection,
|
|
1821
|
+
sort: command.sort,
|
|
1822
|
+
limit: command.limit,
|
|
1823
|
+
skip: command.skip,
|
|
1824
|
+
batchSize: command.batchSize || 101,
|
|
1825
|
+
hint: command.hint,
|
|
1826
|
+
collation: command.collation,
|
|
1827
|
+
allowDiskUse: command.allowDiskUse
|
|
1828
|
+
};
|
|
1829
|
+
if (command.singleBatch === true) {
|
|
1830
|
+
options.batchSize = options.limit || 1e6;
|
|
1831
|
+
}
|
|
1832
|
+
const result = await this.backend.find(context.db, collection, options);
|
|
1833
|
+
return {
|
|
1834
|
+
response: successResponse2({
|
|
1835
|
+
cursor: {
|
|
1836
|
+
id: Long.fromBigInt(result.cursorId),
|
|
1837
|
+
ns: `${context.db}.${collection}`,
|
|
1838
|
+
firstBatch: result.documents
|
|
1839
|
+
}
|
|
1840
|
+
})
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
InsertCommand = class {
|
|
1845
|
+
constructor(backend) {
|
|
1846
|
+
this.backend = backend;
|
|
1847
|
+
}
|
|
1848
|
+
async execute(command, context) {
|
|
1849
|
+
const collection = command.insert;
|
|
1850
|
+
if (!collection || typeof collection !== "string") {
|
|
1851
|
+
return {
|
|
1852
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "insert requires a collection name")
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
let documents = command.documents;
|
|
1856
|
+
if (!documents && context.documentSequences.has("documents")) {
|
|
1857
|
+
documents = context.documentSequences.get("documents");
|
|
1858
|
+
}
|
|
1859
|
+
if (!documents || !Array.isArray(documents) || documents.length === 0) {
|
|
1860
|
+
return {
|
|
1861
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "insert requires documents array")
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
const ordered = command.ordered !== false;
|
|
1865
|
+
try {
|
|
1866
|
+
const result = await this.backend.insertMany(context.db, collection, documents);
|
|
1867
|
+
return {
|
|
1868
|
+
response: successResponse2({
|
|
1869
|
+
n: result.insertedCount
|
|
1870
|
+
})
|
|
1871
|
+
};
|
|
1872
|
+
} catch (error) {
|
|
1873
|
+
if (ordered) {
|
|
1874
|
+
return {
|
|
1875
|
+
response: errorResponse2(
|
|
1876
|
+
ErrorCode.INTERNAL_ERROR,
|
|
1877
|
+
error instanceof Error ? error.message : "Insert failed"
|
|
1878
|
+
)
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
throw error;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1885
|
+
UpdateCommand = class {
|
|
1886
|
+
constructor(backend) {
|
|
1887
|
+
this.backend = backend;
|
|
1888
|
+
}
|
|
1889
|
+
async execute(command, context) {
|
|
1890
|
+
const collection = command.update;
|
|
1891
|
+
if (!collection || typeof collection !== "string") {
|
|
1892
|
+
return {
|
|
1893
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "update requires a collection name")
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
let updates = command.updates;
|
|
1897
|
+
if (!updates && context.documentSequences.has("updates")) {
|
|
1898
|
+
updates = context.documentSequences.get("updates");
|
|
1899
|
+
}
|
|
1900
|
+
if (!updates || !Array.isArray(updates) || updates.length === 0) {
|
|
1901
|
+
return {
|
|
1902
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "update requires updates array")
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
let totalMatched = 0;
|
|
1906
|
+
let totalModified = 0;
|
|
1907
|
+
let upserted = [];
|
|
1908
|
+
for (let i = 0; i < updates.length; i++) {
|
|
1909
|
+
const op = updates[i];
|
|
1910
|
+
const filter = op?.q;
|
|
1911
|
+
const update = op?.u;
|
|
1912
|
+
const multi = op?.multi === true;
|
|
1913
|
+
const upsert = op?.upsert === true;
|
|
1914
|
+
const arrayFilters = op?.arrayFilters;
|
|
1915
|
+
const result = multi ? await this.backend.updateMany(context.db, collection, filter, update, {
|
|
1916
|
+
upsert,
|
|
1917
|
+
arrayFilters
|
|
1918
|
+
}) : await this.backend.updateOne(context.db, collection, filter, update, {
|
|
1919
|
+
upsert,
|
|
1920
|
+
arrayFilters
|
|
1921
|
+
});
|
|
1922
|
+
totalMatched += result.matchedCount;
|
|
1923
|
+
totalModified += result.modifiedCount;
|
|
1924
|
+
if (result.upsertedId !== void 0) {
|
|
1925
|
+
upserted.push({ index: i, _id: result.upsertedId });
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
const response = {
|
|
1929
|
+
n: totalMatched,
|
|
1930
|
+
nModified: totalModified,
|
|
1931
|
+
ok: 1
|
|
1932
|
+
};
|
|
1933
|
+
if (upserted.length > 0) {
|
|
1934
|
+
response.upserted = upserted;
|
|
1935
|
+
}
|
|
1936
|
+
return { response };
|
|
1937
|
+
}
|
|
1938
|
+
};
|
|
1939
|
+
DeleteCommand = class {
|
|
1940
|
+
constructor(backend) {
|
|
1941
|
+
this.backend = backend;
|
|
1942
|
+
}
|
|
1943
|
+
async execute(command, context) {
|
|
1944
|
+
const collection = command.delete;
|
|
1945
|
+
if (!collection || typeof collection !== "string") {
|
|
1946
|
+
return {
|
|
1947
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "delete requires a collection name")
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
let deletes = command.deletes;
|
|
1951
|
+
if (!deletes && context.documentSequences.has("deletes")) {
|
|
1952
|
+
deletes = context.documentSequences.get("deletes");
|
|
1953
|
+
}
|
|
1954
|
+
if (!deletes || !Array.isArray(deletes) || deletes.length === 0) {
|
|
1955
|
+
return {
|
|
1956
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "delete requires deletes array")
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
let totalDeleted = 0;
|
|
1960
|
+
for (const op of deletes) {
|
|
1961
|
+
const filter = op.q;
|
|
1962
|
+
const limit = op.limit;
|
|
1963
|
+
const result = limit === 0 || limit === void 0 ? await this.backend.deleteMany(context.db, collection, filter) : await this.backend.deleteOne(context.db, collection, filter);
|
|
1964
|
+
totalDeleted += result.deletedCount;
|
|
1965
|
+
}
|
|
1966
|
+
return {
|
|
1967
|
+
response: successResponse2({
|
|
1968
|
+
n: totalDeleted
|
|
1969
|
+
})
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
CountCommand = class {
|
|
1974
|
+
constructor(backend) {
|
|
1975
|
+
this.backend = backend;
|
|
1976
|
+
}
|
|
1977
|
+
async execute(command, context) {
|
|
1978
|
+
const collection = command.count;
|
|
1979
|
+
if (!collection || typeof collection !== "string") {
|
|
1980
|
+
return {
|
|
1981
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "count requires a collection name")
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
const query = command.query;
|
|
1985
|
+
const count = await this.backend.count(context.db, collection, query);
|
|
1986
|
+
let adjustedCount = count;
|
|
1987
|
+
if (command.skip && typeof command.skip === "number") {
|
|
1988
|
+
adjustedCount = Math.max(0, adjustedCount - command.skip);
|
|
1989
|
+
}
|
|
1990
|
+
if (command.limit && typeof command.limit === "number" && command.limit > 0) {
|
|
1991
|
+
adjustedCount = Math.min(adjustedCount, command.limit);
|
|
1992
|
+
}
|
|
1993
|
+
return {
|
|
1994
|
+
response: successResponse2({
|
|
1995
|
+
n: adjustedCount
|
|
1996
|
+
})
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
};
|
|
2000
|
+
DistinctCommand = class {
|
|
2001
|
+
constructor(backend) {
|
|
2002
|
+
this.backend = backend;
|
|
2003
|
+
}
|
|
2004
|
+
async execute(command, context) {
|
|
2005
|
+
const collection = command.distinct;
|
|
2006
|
+
if (!collection || typeof collection !== "string") {
|
|
2007
|
+
return {
|
|
2008
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "distinct requires a collection name")
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
const key = command.key;
|
|
2012
|
+
if (!key || typeof key !== "string") {
|
|
2013
|
+
return {
|
|
2014
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "distinct requires a key field")
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
const query = command.query;
|
|
2018
|
+
const values = await this.backend.distinct(context.db, collection, key, query);
|
|
2019
|
+
return {
|
|
2020
|
+
response: successResponse2({
|
|
2021
|
+
values
|
|
2022
|
+
})
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
};
|
|
2026
|
+
GetMoreCommand = class {
|
|
2027
|
+
constructor(backend) {
|
|
2028
|
+
this.backend = backend;
|
|
2029
|
+
}
|
|
2030
|
+
async execute(command, context) {
|
|
2031
|
+
const cursorId = command.getMore;
|
|
2032
|
+
const collection = command.collection;
|
|
2033
|
+
const batchSize = command.batchSize || 101;
|
|
2034
|
+
if (cursorId === void 0) {
|
|
2035
|
+
return {
|
|
2036
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "getMore requires a cursor id")
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
let cursorBigInt;
|
|
2040
|
+
if (typeof cursorId === "bigint") {
|
|
2041
|
+
cursorBigInt = cursorId;
|
|
2042
|
+
} else if (cursorId instanceof Long) {
|
|
2043
|
+
cursorBigInt = cursorId.toBigInt();
|
|
2044
|
+
} else {
|
|
2045
|
+
cursorBigInt = BigInt(cursorId);
|
|
2046
|
+
}
|
|
2047
|
+
const cursor = this.backend.getCursor(cursorBigInt);
|
|
2048
|
+
if (!cursor) {
|
|
2049
|
+
return {
|
|
2050
|
+
response: errorResponse2(
|
|
2051
|
+
ErrorCode.NAMESPACE_NOT_FOUND,
|
|
2052
|
+
`cursor id ${cursorId} not found`
|
|
2053
|
+
)
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
const documents = this.backend.advanceCursor(cursorBigInt, batchSize);
|
|
2057
|
+
const updatedCursor = this.backend.getCursor(cursorBigInt);
|
|
2058
|
+
const hasMore = updatedCursor ? updatedCursor.position < updatedCursor.documents.length : false;
|
|
2059
|
+
if (!hasMore) {
|
|
2060
|
+
this.backend.closeCursor(cursorBigInt);
|
|
2061
|
+
}
|
|
2062
|
+
return {
|
|
2063
|
+
response: successResponse2({
|
|
2064
|
+
cursor: {
|
|
2065
|
+
id: hasMore ? Long.fromBigInt(cursorBigInt) : Long.ZERO,
|
|
2066
|
+
ns: `${context.db}.${collection}`,
|
|
2067
|
+
nextBatch: documents
|
|
2068
|
+
}
|
|
2069
|
+
})
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
};
|
|
2073
|
+
KillCursorsCommand = class {
|
|
2074
|
+
constructor(backend) {
|
|
2075
|
+
this.backend = backend;
|
|
2076
|
+
}
|
|
2077
|
+
async execute(command, _context) {
|
|
2078
|
+
const cursors = command.cursors;
|
|
2079
|
+
if (!cursors || !Array.isArray(cursors)) {
|
|
2080
|
+
return {
|
|
2081
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "killCursors requires cursors array")
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
const cursorsKilled = [];
|
|
2085
|
+
const cursorsNotFound = [];
|
|
2086
|
+
const cursorsAlive = [];
|
|
2087
|
+
const cursorsUnknown = [];
|
|
2088
|
+
for (const cursorId of cursors) {
|
|
2089
|
+
let cursorBigInt;
|
|
2090
|
+
if (typeof cursorId === "bigint") {
|
|
2091
|
+
cursorBigInt = cursorId;
|
|
2092
|
+
} else if (cursorId instanceof Long) {
|
|
2093
|
+
cursorBigInt = cursorId.toBigInt();
|
|
2094
|
+
} else {
|
|
2095
|
+
cursorBigInt = BigInt(cursorId);
|
|
2096
|
+
}
|
|
2097
|
+
const killed = this.backend.closeCursor(cursorBigInt);
|
|
2098
|
+
const longId = Long.fromBigInt(cursorBigInt);
|
|
2099
|
+
if (killed) {
|
|
2100
|
+
cursorsKilled.push(longId);
|
|
2101
|
+
} else {
|
|
2102
|
+
cursorsNotFound.push(longId);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
return {
|
|
2106
|
+
response: successResponse2({
|
|
2107
|
+
cursorsKilled,
|
|
2108
|
+
cursorsNotFound,
|
|
2109
|
+
cursorsAlive,
|
|
2110
|
+
cursorsUnknown
|
|
2111
|
+
})
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
});
|
|
2117
|
+
var AggregateCommand;
|
|
2118
|
+
var init_aggregate = __esm({
|
|
2119
|
+
"src/wire/commands/aggregate.ts"() {
|
|
2120
|
+
init_types2();
|
|
2121
|
+
AggregateCommand = class {
|
|
2122
|
+
constructor(backend) {
|
|
2123
|
+
this.backend = backend;
|
|
2124
|
+
}
|
|
2125
|
+
async execute(command, context) {
|
|
2126
|
+
const collection = command.aggregate;
|
|
2127
|
+
if (!collection || typeof collection !== "string") {
|
|
2128
|
+
return {
|
|
2129
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "aggregate requires a collection name")
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
const pipeline = command.pipeline;
|
|
2133
|
+
if (!pipeline || !Array.isArray(pipeline)) {
|
|
2134
|
+
return {
|
|
2135
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "aggregate requires a pipeline array")
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
if (collection === "1" || collection === 1) {
|
|
2139
|
+
return this.handleDatabaseAggregation(pipeline, context);
|
|
2140
|
+
}
|
|
2141
|
+
const batchSize = command.cursor?.batchSize || 101;
|
|
2142
|
+
const allowDiskUse = command.allowDiskUse;
|
|
2143
|
+
const lastStage = pipeline[pipeline.length - 1];
|
|
2144
|
+
const hasOutputStage = lastStage && ("$out" in lastStage || "$merge" in lastStage);
|
|
2145
|
+
try {
|
|
2146
|
+
const result = await this.backend.aggregate(context.db, collection, pipeline, {
|
|
2147
|
+
batchSize,
|
|
2148
|
+
allowDiskUse
|
|
2149
|
+
});
|
|
2150
|
+
if (hasOutputStage) {
|
|
2151
|
+
return {
|
|
2152
|
+
response: successResponse2()
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
return {
|
|
2156
|
+
response: successResponse2({
|
|
2157
|
+
cursor: {
|
|
2158
|
+
id: Long.fromBigInt(result.cursorId),
|
|
2159
|
+
ns: `${context.db}.${collection}`,
|
|
2160
|
+
firstBatch: result.documents
|
|
2161
|
+
}
|
|
2162
|
+
})
|
|
2163
|
+
};
|
|
2164
|
+
} catch (error) {
|
|
2165
|
+
return {
|
|
2166
|
+
response: errorResponse2(
|
|
2167
|
+
ErrorCode.INTERNAL_ERROR,
|
|
2168
|
+
error instanceof Error ? error.message : "Aggregation failed"
|
|
2169
|
+
)
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
async handleDatabaseAggregation(pipeline, context) {
|
|
2174
|
+
const firstStage = pipeline[0];
|
|
2175
|
+
if (firstStage && "$listLocalSessions" in firstStage) {
|
|
2176
|
+
return {
|
|
2177
|
+
response: successResponse2({
|
|
2178
|
+
cursor: {
|
|
2179
|
+
id: Long.ZERO,
|
|
2180
|
+
ns: `${context.db}.$cmd.aggregate`,
|
|
2181
|
+
firstBatch: []
|
|
2182
|
+
}
|
|
2183
|
+
})
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
if (firstStage && "$currentOp" in firstStage) {
|
|
2187
|
+
return {
|
|
2188
|
+
response: successResponse2({
|
|
2189
|
+
cursor: {
|
|
2190
|
+
id: Long.ZERO,
|
|
2191
|
+
ns: `${context.db}.$cmd.aggregate`,
|
|
2192
|
+
firstBatch: []
|
|
2193
|
+
}
|
|
2194
|
+
})
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
return {
|
|
2198
|
+
response: errorResponse2(
|
|
2199
|
+
ErrorCode.COMMAND_NOT_FOUND,
|
|
2200
|
+
"Database-level aggregation not supported"
|
|
2201
|
+
)
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
});
|
|
2207
|
+
var ListIndexesCommand, CreateIndexesCommand, DropIndexesCommand;
|
|
2208
|
+
var init_commands = __esm({
|
|
2209
|
+
"src/wire/commands/index.ts"() {
|
|
2210
|
+
init_types2();
|
|
2211
|
+
ListIndexesCommand = class {
|
|
2212
|
+
constructor(backend) {
|
|
2213
|
+
this.backend = backend;
|
|
2214
|
+
}
|
|
2215
|
+
async execute(command, context) {
|
|
2216
|
+
const collection = command.listIndexes;
|
|
2217
|
+
if (!collection || typeof collection !== "string") {
|
|
2218
|
+
return {
|
|
2219
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "listIndexes requires a collection name")
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
const exists = await this.backend.collectionExists(context.db, collection);
|
|
2223
|
+
if (!exists) {
|
|
2224
|
+
return {
|
|
2225
|
+
response: errorResponse2(
|
|
2226
|
+
ErrorCode.NAMESPACE_NOT_FOUND,
|
|
2227
|
+
`ns not found: ${context.db}.${collection}`
|
|
2228
|
+
)
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
const indexes = await this.backend.listIndexes(context.db, collection);
|
|
2232
|
+
return {
|
|
2233
|
+
response: successResponse2({
|
|
2234
|
+
cursor: {
|
|
2235
|
+
id: Long.ZERO,
|
|
2236
|
+
ns: `${context.db}.${collection}`,
|
|
2237
|
+
firstBatch: indexes
|
|
2238
|
+
}
|
|
2239
|
+
})
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2243
|
+
CreateIndexesCommand = class {
|
|
2244
|
+
constructor(backend) {
|
|
2245
|
+
this.backend = backend;
|
|
2246
|
+
}
|
|
2247
|
+
async execute(command, context) {
|
|
2248
|
+
const collection = command.createIndexes;
|
|
2249
|
+
if (!collection || typeof collection !== "string") {
|
|
2250
|
+
return {
|
|
2251
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "createIndexes requires a collection name")
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
const indexes = command.indexes;
|
|
2255
|
+
if (!indexes || !Array.isArray(indexes) || indexes.length === 0) {
|
|
2256
|
+
return {
|
|
2257
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "createIndexes requires indexes array")
|
|
2258
|
+
};
|
|
2259
|
+
}
|
|
2260
|
+
const indexSpecs = indexes.map((idx) => ({
|
|
2261
|
+
key: idx.key,
|
|
2262
|
+
name: idx.name,
|
|
2263
|
+
unique: idx.unique,
|
|
2264
|
+
sparse: idx.sparse,
|
|
2265
|
+
background: idx.background,
|
|
2266
|
+
expireAfterSeconds: idx.expireAfterSeconds,
|
|
2267
|
+
partialFilterExpression: idx.partialFilterExpression
|
|
2268
|
+
}));
|
|
2269
|
+
let existingIndexes = [];
|
|
2270
|
+
try {
|
|
2271
|
+
const existing = await this.backend.listIndexes(context.db, collection);
|
|
2272
|
+
existingIndexes = existing.map((idx) => idx.name);
|
|
2273
|
+
} catch {
|
|
2274
|
+
}
|
|
2275
|
+
const createdNames = await this.backend.createIndexes(context.db, collection, indexSpecs);
|
|
2276
|
+
const numIndexesBefore = existingIndexes.length;
|
|
2277
|
+
const numIndexesAfter = numIndexesBefore + createdNames.length;
|
|
2278
|
+
return {
|
|
2279
|
+
response: successResponse2({
|
|
2280
|
+
numIndexesBefore,
|
|
2281
|
+
numIndexesAfter,
|
|
2282
|
+
createdCollectionAutomatically: false,
|
|
2283
|
+
note: createdNames.length > 0 ? void 0 : "all indexes already exist"
|
|
2284
|
+
})
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
};
|
|
2288
|
+
DropIndexesCommand = class {
|
|
2289
|
+
constructor(backend) {
|
|
2290
|
+
this.backend = backend;
|
|
2291
|
+
}
|
|
2292
|
+
async execute(command, context) {
|
|
2293
|
+
const collection = command.dropIndexes;
|
|
2294
|
+
if (!collection || typeof collection !== "string") {
|
|
2295
|
+
return {
|
|
2296
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "dropIndexes requires a collection name")
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
2299
|
+
const index = command.index;
|
|
2300
|
+
if (index === "*") {
|
|
2301
|
+
await this.backend.dropIndexes(context.db, collection);
|
|
2302
|
+
return {
|
|
2303
|
+
response: successResponse2({
|
|
2304
|
+
msg: "non-_id indexes dropped"
|
|
2305
|
+
})
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
if (typeof index === "string") {
|
|
2309
|
+
if (index === "_id_") {
|
|
2310
|
+
return {
|
|
2311
|
+
response: errorResponse2(
|
|
2312
|
+
ErrorCode.ILLEGAL_OPERATION,
|
|
2313
|
+
"cannot drop _id index"
|
|
2314
|
+
)
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
await this.backend.dropIndex(context.db, collection, index);
|
|
2318
|
+
return {
|
|
2319
|
+
response: successResponse2({
|
|
2320
|
+
nIndexesWas: 1
|
|
2321
|
+
})
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
if (Array.isArray(index)) {
|
|
2325
|
+
for (const name of index) {
|
|
2326
|
+
if (name === "_id_") continue;
|
|
2327
|
+
await this.backend.dropIndex(context.db, collection, name);
|
|
2328
|
+
}
|
|
2329
|
+
return { response: successResponse2() };
|
|
2330
|
+
}
|
|
2331
|
+
if (typeof index === "object") {
|
|
2332
|
+
return {
|
|
2333
|
+
response: errorResponse2(
|
|
2334
|
+
ErrorCode.BAD_VALUE,
|
|
2335
|
+
"dropping index by key specification not yet supported"
|
|
2336
|
+
)
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
return {
|
|
2340
|
+
response: errorResponse2(ErrorCode.BAD_VALUE, "invalid index specification")
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
});
|
|
2346
|
+
function requiresAuthentication(commandName) {
|
|
2347
|
+
return !UNAUTHENTICATED_COMMANDS.has(commandName);
|
|
2348
|
+
}
|
|
2349
|
+
var SaslStartCommand, SaslContinueCommand, AuthenticateCommand, LogoutCommand, UNAUTHENTICATED_COMMANDS;
|
|
2350
|
+
var init_auth = __esm({
|
|
2351
|
+
"src/wire/commands/auth.ts"() {
|
|
2352
|
+
init_types2();
|
|
2353
|
+
SaslStartCommand = class {
|
|
2354
|
+
constructor(authenticator) {
|
|
2355
|
+
this.authenticator = authenticator;
|
|
2356
|
+
}
|
|
2357
|
+
async execute(command, context) {
|
|
2358
|
+
const mechanism = command.mechanism;
|
|
2359
|
+
const payload = command.payload;
|
|
2360
|
+
const options = command.options;
|
|
2361
|
+
if (!mechanism) {
|
|
2362
|
+
return {
|
|
2363
|
+
response: errorResponse2(
|
|
2364
|
+
ErrorCode.BAD_VALUE,
|
|
2365
|
+
"saslStart requires mechanism field"
|
|
2366
|
+
)
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
if (!payload) {
|
|
2370
|
+
return {
|
|
2371
|
+
response: errorResponse2(
|
|
2372
|
+
ErrorCode.BAD_VALUE,
|
|
2373
|
+
"saslStart requires payload field"
|
|
2374
|
+
)
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
let authDb = context.db;
|
|
2378
|
+
if (options?.authdb) {
|
|
2379
|
+
authDb = options.authdb;
|
|
2380
|
+
}
|
|
2381
|
+
const result = await this.authenticator.saslStart(mechanism, payload, authDb);
|
|
2382
|
+
if (!result.success) {
|
|
2383
|
+
return {
|
|
2384
|
+
response: errorResponse2(
|
|
2385
|
+
ErrorCode.AUTHENTICATION_FAILED,
|
|
2386
|
+
result.error || "Authentication failed"
|
|
2387
|
+
)
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
return {
|
|
2391
|
+
response: successResponse2({
|
|
2392
|
+
conversationId: result.conversationId,
|
|
2393
|
+
payload: new Binary(result.payload),
|
|
2394
|
+
done: result.done
|
|
2395
|
+
})
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
};
|
|
2399
|
+
SaslContinueCommand = class {
|
|
2400
|
+
constructor(authenticator) {
|
|
2401
|
+
this.authenticator = authenticator;
|
|
2402
|
+
}
|
|
2403
|
+
async execute(command, context) {
|
|
2404
|
+
const conversationId = command.conversationId;
|
|
2405
|
+
const payload = command.payload;
|
|
2406
|
+
if (conversationId === void 0 || conversationId === null) {
|
|
2407
|
+
return {
|
|
2408
|
+
response: errorResponse2(
|
|
2409
|
+
ErrorCode.BAD_VALUE,
|
|
2410
|
+
"saslContinue requires conversationId field"
|
|
2411
|
+
)
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
if (!payload) {
|
|
2415
|
+
return {
|
|
2416
|
+
response: errorResponse2(
|
|
2417
|
+
ErrorCode.BAD_VALUE,
|
|
2418
|
+
"saslContinue requires payload field"
|
|
2419
|
+
)
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
const result = await this.authenticator.saslContinue(conversationId, payload);
|
|
2423
|
+
if (!result.success) {
|
|
2424
|
+
return {
|
|
2425
|
+
response: errorResponse2(
|
|
2426
|
+
ErrorCode.AUTHENTICATION_FAILED,
|
|
2427
|
+
result.error || "Authentication failed"
|
|
2428
|
+
)
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
if (result.done) {
|
|
2432
|
+
const user = this.authenticator.getConversationUser(conversationId);
|
|
2433
|
+
if (user && context.setAuthenticated) {
|
|
2434
|
+
context.setAuthenticated(user.username, user.db);
|
|
2435
|
+
}
|
|
2436
|
+
this.authenticator.cleanupConversation(conversationId);
|
|
2437
|
+
}
|
|
2438
|
+
return {
|
|
2439
|
+
response: successResponse2({
|
|
2440
|
+
conversationId: result.conversationId,
|
|
2441
|
+
payload: new Binary(result.payload),
|
|
2442
|
+
done: result.done
|
|
2443
|
+
})
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
};
|
|
2447
|
+
AuthenticateCommand = class {
|
|
2448
|
+
async execute(_command, _context) {
|
|
2449
|
+
return {
|
|
2450
|
+
response: errorResponse2(
|
|
2451
|
+
ErrorCode.AUTHENTICATION_FAILED,
|
|
2452
|
+
"Legacy authenticate command is not supported. Use SCRAM-SHA-256 via saslStart/saslContinue."
|
|
2453
|
+
)
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
LogoutCommand = class {
|
|
2458
|
+
constructor(clearAuthentication) {
|
|
2459
|
+
this.clearAuthentication = clearAuthentication;
|
|
2460
|
+
}
|
|
2461
|
+
async execute(_command, context) {
|
|
2462
|
+
this.clearAuthentication(context.connectionId);
|
|
2463
|
+
return {
|
|
2464
|
+
response: successResponse2({
|
|
2465
|
+
// MongoDB returns ok: 1 for logout
|
|
2466
|
+
})
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
};
|
|
2470
|
+
UNAUTHENTICATED_COMMANDS = /* @__PURE__ */ new Set([
|
|
2471
|
+
// Handshake commands
|
|
2472
|
+
"hello",
|
|
2473
|
+
"ismaster",
|
|
2474
|
+
"isMaster",
|
|
2475
|
+
"buildInfo",
|
|
2476
|
+
"buildinfo",
|
|
2477
|
+
// Authentication commands
|
|
2478
|
+
"saslStart",
|
|
2479
|
+
"saslContinue",
|
|
2480
|
+
"authenticate",
|
|
2481
|
+
"logout",
|
|
2482
|
+
// Basic connectivity
|
|
2483
|
+
"ping",
|
|
2484
|
+
"whatsmyuri",
|
|
2485
|
+
// Required for driver initialization
|
|
2486
|
+
"getParameter",
|
|
2487
|
+
"getCmdLineOpts"
|
|
2488
|
+
]);
|
|
2489
|
+
}
|
|
2490
|
+
});
|
|
2491
|
+
async function createScramCredentials(username, password, db, iterationCount = 15e3) {
|
|
2492
|
+
const saltBytes = new Uint8Array(16);
|
|
2493
|
+
crypto.getRandomValues(saltBytes);
|
|
2494
|
+
const salt = Buffer.from(saltBytes).toString("base64");
|
|
2495
|
+
const saltedPassword = await pbkdf2Sha256(password, saltBytes, iterationCount);
|
|
2496
|
+
const clientKey = await hmacSha256(saltedPassword, "Client Key");
|
|
2497
|
+
const storedKey = await sha256(clientKey);
|
|
2498
|
+
const serverKey = await hmacSha256(saltedPassword, "Server Key");
|
|
2499
|
+
return {
|
|
2500
|
+
username,
|
|
2501
|
+
salt,
|
|
2502
|
+
storedKey: storedKey.toString("base64"),
|
|
2503
|
+
serverKey: serverKey.toString("base64"),
|
|
2504
|
+
iterationCount,
|
|
2505
|
+
db
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
async function pbkdf2Sha256(password, salt, iterations) {
|
|
2509
|
+
const encoder = new TextEncoder();
|
|
2510
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
2511
|
+
"raw",
|
|
2512
|
+
encoder.encode(password),
|
|
2513
|
+
"PBKDF2",
|
|
2514
|
+
false,
|
|
2515
|
+
["deriveBits"]
|
|
2516
|
+
);
|
|
2517
|
+
const derivedBits = await crypto.subtle.deriveBits(
|
|
2518
|
+
{
|
|
2519
|
+
name: "PBKDF2",
|
|
2520
|
+
salt: new Uint8Array(salt),
|
|
2521
|
+
iterations,
|
|
2522
|
+
hash: "SHA-256"
|
|
2523
|
+
},
|
|
2524
|
+
keyMaterial,
|
|
2525
|
+
256
|
|
2526
|
+
// 32 bytes
|
|
2527
|
+
);
|
|
2528
|
+
return Buffer.from(derivedBits);
|
|
2529
|
+
}
|
|
2530
|
+
async function sha256(data) {
|
|
2531
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", new Uint8Array(data));
|
|
2532
|
+
return Buffer.from(hashBuffer);
|
|
2533
|
+
}
|
|
2534
|
+
async function hmacSha256(key, message) {
|
|
2535
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
2536
|
+
"raw",
|
|
2537
|
+
new Uint8Array(key),
|
|
2538
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
2539
|
+
false,
|
|
2540
|
+
["sign"]
|
|
2541
|
+
);
|
|
2542
|
+
const signature = await crypto.subtle.sign(
|
|
2543
|
+
"HMAC",
|
|
2544
|
+
cryptoKey,
|
|
2545
|
+
new TextEncoder().encode(message)
|
|
2546
|
+
);
|
|
2547
|
+
return Buffer.from(signature);
|
|
2548
|
+
}
|
|
2549
|
+
var ScramAuthenticator, InMemoryCredentialsProvider;
|
|
2550
|
+
var init_scram = __esm({
|
|
2551
|
+
"src/wire/auth/scram.ts"() {
|
|
2552
|
+
ScramAuthenticator = class {
|
|
2553
|
+
constructor(credentialsProvider) {
|
|
2554
|
+
this.credentialsProvider = credentialsProvider;
|
|
2555
|
+
}
|
|
2556
|
+
conversations = /* @__PURE__ */ new Map();
|
|
2557
|
+
nextConversationId = 1;
|
|
2558
|
+
/**
|
|
2559
|
+
* Handle saslStart command - begin authentication
|
|
2560
|
+
*
|
|
2561
|
+
* @param mechanism - Should be "SCRAM-SHA-256"
|
|
2562
|
+
* @param payload - Client's first message (Binary or Buffer)
|
|
2563
|
+
* @param db - Authentication database
|
|
2564
|
+
*/
|
|
2565
|
+
async saslStart(mechanism, payload, db) {
|
|
2566
|
+
if (mechanism !== "SCRAM-SHA-256") {
|
|
2567
|
+
return {
|
|
2568
|
+
success: false,
|
|
2569
|
+
done: true,
|
|
2570
|
+
error: `Mechanism ${mechanism} is not supported. Only SCRAM-SHA-256 is available.`
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
const payloadBytes = payload instanceof Binary ? payload.buffer : Buffer.from(payload);
|
|
2574
|
+
const clientFirstMessage = payloadBytes.toString("utf-8");
|
|
2575
|
+
const parsed = this.parseClientFirstMessage(clientFirstMessage);
|
|
2576
|
+
if (!parsed) {
|
|
2577
|
+
return {
|
|
2578
|
+
success: false,
|
|
2579
|
+
done: true,
|
|
2580
|
+
error: "Invalid client-first-message format"
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
const { username, clientNonce, clientFirstMessageBare } = parsed;
|
|
2584
|
+
const credentials = await this.credentialsProvider.getCredentials(username, db);
|
|
2585
|
+
if (!credentials) {
|
|
2586
|
+
const fakeSalt = await this.generateSalt();
|
|
2587
|
+
const serverNonce2 = clientNonce + await this.generateNonce();
|
|
2588
|
+
const iterationCount = 15e3;
|
|
2589
|
+
const conversationId2 = this.nextConversationId++;
|
|
2590
|
+
this.conversations.set(conversationId2, {
|
|
2591
|
+
username,
|
|
2592
|
+
db,
|
|
2593
|
+
clientNonce,
|
|
2594
|
+
serverNonce: serverNonce2,
|
|
2595
|
+
salt: fakeSalt,
|
|
2596
|
+
iterationCount,
|
|
2597
|
+
clientFirstMessageBare,
|
|
2598
|
+
serverFirstMessage: "",
|
|
2599
|
+
step: "init"
|
|
2600
|
+
});
|
|
2601
|
+
const serverFirstMessage2 = `r=${serverNonce2},s=${fakeSalt},i=${iterationCount}`;
|
|
2602
|
+
const conv2 = this.conversations.get(conversationId2);
|
|
2603
|
+
conv2.serverFirstMessage = serverFirstMessage2;
|
|
2604
|
+
conv2.step = "challenge";
|
|
2605
|
+
return {
|
|
2606
|
+
success: true,
|
|
2607
|
+
conversationId: conversationId2,
|
|
2608
|
+
payload: Buffer.from(serverFirstMessage2, "utf-8"),
|
|
2609
|
+
done: false
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2612
|
+
const serverNonce = clientNonce + await this.generateNonce();
|
|
2613
|
+
const conversationId = this.nextConversationId++;
|
|
2614
|
+
this.conversations.set(conversationId, {
|
|
2615
|
+
username,
|
|
2616
|
+
db,
|
|
2617
|
+
clientNonce,
|
|
2618
|
+
serverNonce,
|
|
2619
|
+
salt: credentials.salt,
|
|
2620
|
+
iterationCount: credentials.iterationCount,
|
|
2621
|
+
clientFirstMessageBare,
|
|
2622
|
+
serverFirstMessage: "",
|
|
2623
|
+
step: "init"
|
|
2624
|
+
});
|
|
2625
|
+
const serverFirstMessage = `r=${serverNonce},s=${credentials.salt},i=${credentials.iterationCount}`;
|
|
2626
|
+
const conv = this.conversations.get(conversationId);
|
|
2627
|
+
conv.serverFirstMessage = serverFirstMessage;
|
|
2628
|
+
conv.step = "challenge";
|
|
2629
|
+
return {
|
|
2630
|
+
success: true,
|
|
2631
|
+
conversationId,
|
|
2632
|
+
payload: Buffer.from(serverFirstMessage, "utf-8"),
|
|
2633
|
+
done: false
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* Handle saslContinue command - continue authentication
|
|
2638
|
+
*
|
|
2639
|
+
* @param conversationId - Conversation ID from saslStart
|
|
2640
|
+
* @param payload - Client's response message
|
|
2641
|
+
*/
|
|
2642
|
+
async saslContinue(conversationId, payload) {
|
|
2643
|
+
const conv = this.conversations.get(conversationId);
|
|
2644
|
+
if (!conv) {
|
|
2645
|
+
return {
|
|
2646
|
+
success: false,
|
|
2647
|
+
done: true,
|
|
2648
|
+
error: "Invalid conversation ID"
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
const payloadBytes = payload instanceof Binary ? payload.buffer : Buffer.from(payload);
|
|
2652
|
+
const clientMessage = payloadBytes.toString("utf-8");
|
|
2653
|
+
if (conv.step === "challenge") {
|
|
2654
|
+
const parsed = this.parseClientFinalMessage(clientMessage);
|
|
2655
|
+
if (!parsed) {
|
|
2656
|
+
this.conversations.delete(conversationId);
|
|
2657
|
+
return {
|
|
2658
|
+
success: false,
|
|
2659
|
+
done: true,
|
|
2660
|
+
error: "Invalid client-final-message format"
|
|
2661
|
+
};
|
|
2662
|
+
}
|
|
2663
|
+
const { clientProof, nonce, clientFinalMessageWithoutProof } = parsed;
|
|
2664
|
+
if (nonce !== conv.serverNonce) {
|
|
2665
|
+
this.conversations.delete(conversationId);
|
|
2666
|
+
return {
|
|
2667
|
+
success: false,
|
|
2668
|
+
done: true,
|
|
2669
|
+
error: "Authentication failed: nonce mismatch"
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
const credentials = await this.credentialsProvider.getCredentials(
|
|
2673
|
+
conv.username,
|
|
2674
|
+
conv.db
|
|
2675
|
+
);
|
|
2676
|
+
if (!credentials) {
|
|
2677
|
+
this.conversations.delete(conversationId);
|
|
2678
|
+
return {
|
|
2679
|
+
success: false,
|
|
2680
|
+
done: true,
|
|
2681
|
+
error: "Authentication failed"
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2684
|
+
const verified = await this.verifyClientProof(
|
|
2685
|
+
conv,
|
|
2686
|
+
clientProof,
|
|
2687
|
+
clientFinalMessageWithoutProof,
|
|
2688
|
+
credentials
|
|
2689
|
+
);
|
|
2690
|
+
if (!verified.success) {
|
|
2691
|
+
this.conversations.delete(conversationId);
|
|
2692
|
+
return {
|
|
2693
|
+
success: false,
|
|
2694
|
+
done: true,
|
|
2695
|
+
error: "Authentication failed"
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
conv.step = "complete";
|
|
2699
|
+
const serverFinalMessage = `v=${verified.serverSignature}`;
|
|
2700
|
+
return {
|
|
2701
|
+
success: true,
|
|
2702
|
+
conversationId,
|
|
2703
|
+
payload: Buffer.from(serverFinalMessage, "utf-8"),
|
|
2704
|
+
done: true
|
|
2705
|
+
};
|
|
2706
|
+
}
|
|
2707
|
+
this.conversations.delete(conversationId);
|
|
2708
|
+
return {
|
|
2709
|
+
success: false,
|
|
2710
|
+
done: true,
|
|
2711
|
+
error: "Unexpected conversation state"
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2714
|
+
/**
|
|
2715
|
+
* Parse client-first-message
|
|
2716
|
+
* Format: gs2-header n=username,r=client-nonce
|
|
2717
|
+
* gs2-header is typically "n,," (no channel binding, no authzid)
|
|
2718
|
+
*/
|
|
2719
|
+
parseClientFirstMessage(message) {
|
|
2720
|
+
const match = message.match(/^([nyp])(=([^,]*))?,,(.+)$/);
|
|
2721
|
+
if (!match) return null;
|
|
2722
|
+
const clientFirstMessageBare = match[4];
|
|
2723
|
+
if (!clientFirstMessageBare) return null;
|
|
2724
|
+
const parts = /* @__PURE__ */ new Map();
|
|
2725
|
+
for (const part of clientFirstMessageBare.split(",")) {
|
|
2726
|
+
const eqIdx = part.indexOf("=");
|
|
2727
|
+
if (eqIdx > 0) {
|
|
2728
|
+
parts.set(part.slice(0, eqIdx), part.slice(eqIdx + 1));
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
const username = parts.get("n");
|
|
2732
|
+
const clientNonce = parts.get("r");
|
|
2733
|
+
if (!username || !clientNonce) return null;
|
|
2734
|
+
const decodedUsername = username.replace(/=2C/g, ",").replace(/=3D/g, "=");
|
|
2735
|
+
return { username: decodedUsername, clientNonce, clientFirstMessageBare };
|
|
2736
|
+
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Parse client-final-message
|
|
2739
|
+
* Format: c=channel-binding,r=nonce,p=client-proof
|
|
2740
|
+
*/
|
|
2741
|
+
parseClientFinalMessage(message) {
|
|
2742
|
+
const proofMatch = message.match(/,p=([A-Za-z0-9+/=]+)$/);
|
|
2743
|
+
if (!proofMatch) return null;
|
|
2744
|
+
const clientProof = proofMatch[1];
|
|
2745
|
+
const clientFinalMessageWithoutProof = message.slice(
|
|
2746
|
+
0,
|
|
2747
|
+
message.length - proofMatch[0].length
|
|
2748
|
+
);
|
|
2749
|
+
const parts = /* @__PURE__ */ new Map();
|
|
2750
|
+
for (const part of clientFinalMessageWithoutProof.split(",")) {
|
|
2751
|
+
const eqIdx = part.indexOf("=");
|
|
2752
|
+
if (eqIdx > 0) {
|
|
2753
|
+
parts.set(part.slice(0, eqIdx), part.slice(eqIdx + 1));
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
const channelBinding = parts.get("c");
|
|
2757
|
+
const nonce = parts.get("r");
|
|
2758
|
+
if (!channelBinding || !nonce || !clientProof) return null;
|
|
2759
|
+
return { channelBinding, nonce, clientProof, clientFinalMessageWithoutProof };
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* Verify client proof and generate server signature
|
|
2763
|
+
*/
|
|
2764
|
+
async verifyClientProof(conv, clientProofBase64, clientFinalMessageWithoutProof, credentials) {
|
|
2765
|
+
try {
|
|
2766
|
+
const authMessage = `${conv.clientFirstMessageBare},${conv.serverFirstMessage},${clientFinalMessageWithoutProof}`;
|
|
2767
|
+
const storedKey = Buffer.from(credentials.storedKey, "base64");
|
|
2768
|
+
const serverKey = Buffer.from(credentials.serverKey, "base64");
|
|
2769
|
+
const clientSignature = await this.hmacSha256(storedKey, authMessage);
|
|
2770
|
+
const clientProof = Buffer.from(clientProofBase64, "base64");
|
|
2771
|
+
const clientKey = Buffer.alloc(clientProof.length);
|
|
2772
|
+
for (let i = 0; i < clientProof.length; i++) {
|
|
2773
|
+
clientKey[i] = (clientProof[i] ?? 0) ^ (clientSignature[i] ?? 0);
|
|
2774
|
+
}
|
|
2775
|
+
const computedStoredKey = await this.sha256(clientKey);
|
|
2776
|
+
if (!this.timingSafeEqual(computedStoredKey, storedKey)) {
|
|
2777
|
+
return { success: false };
|
|
2778
|
+
}
|
|
2779
|
+
const serverSignature = await this.hmacSha256(serverKey, authMessage);
|
|
2780
|
+
return {
|
|
2781
|
+
success: true,
|
|
2782
|
+
serverSignature: serverSignature.toString("base64")
|
|
2783
|
+
};
|
|
2784
|
+
} catch {
|
|
2785
|
+
return { success: false };
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* Generate a random nonce
|
|
2790
|
+
*/
|
|
2791
|
+
async generateNonce() {
|
|
2792
|
+
const bytes = new Uint8Array(24);
|
|
2793
|
+
crypto.getRandomValues(bytes);
|
|
2794
|
+
return Buffer.from(bytes).toString("base64");
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Generate a random salt
|
|
2798
|
+
*/
|
|
2799
|
+
async generateSalt() {
|
|
2800
|
+
const bytes = new Uint8Array(16);
|
|
2801
|
+
crypto.getRandomValues(bytes);
|
|
2802
|
+
return Buffer.from(bytes).toString("base64");
|
|
2803
|
+
}
|
|
2804
|
+
/**
|
|
2805
|
+
* Compute SHA-256 hash
|
|
2806
|
+
*/
|
|
2807
|
+
async sha256(data) {
|
|
2808
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", new Uint8Array(data));
|
|
2809
|
+
return Buffer.from(hashBuffer);
|
|
2810
|
+
}
|
|
2811
|
+
/**
|
|
2812
|
+
* Compute HMAC-SHA-256
|
|
2813
|
+
*/
|
|
2814
|
+
async hmacSha256(key, message) {
|
|
2815
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
2816
|
+
"raw",
|
|
2817
|
+
new Uint8Array(key),
|
|
2818
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
2819
|
+
false,
|
|
2820
|
+
["sign"]
|
|
2821
|
+
);
|
|
2822
|
+
const signature = await crypto.subtle.sign(
|
|
2823
|
+
"HMAC",
|
|
2824
|
+
cryptoKey,
|
|
2825
|
+
new TextEncoder().encode(message)
|
|
2826
|
+
);
|
|
2827
|
+
return Buffer.from(signature);
|
|
2828
|
+
}
|
|
2829
|
+
/**
|
|
2830
|
+
* Timing-safe comparison
|
|
2831
|
+
*/
|
|
2832
|
+
timingSafeEqual(a, b) {
|
|
2833
|
+
if (a.length !== b.length) return false;
|
|
2834
|
+
let result = 0;
|
|
2835
|
+
for (let i = 0; i < a.length; i++) {
|
|
2836
|
+
result |= (a[i] ?? 0) ^ (b[i] ?? 0);
|
|
2837
|
+
}
|
|
2838
|
+
return result === 0;
|
|
2839
|
+
}
|
|
2840
|
+
/**
|
|
2841
|
+
* Clean up a conversation
|
|
2842
|
+
*/
|
|
2843
|
+
cleanupConversation(conversationId) {
|
|
2844
|
+
this.conversations.delete(conversationId);
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
* Get conversation username for successful auth
|
|
2848
|
+
*/
|
|
2849
|
+
getConversationUser(conversationId) {
|
|
2850
|
+
const conv = this.conversations.get(conversationId);
|
|
2851
|
+
if (!conv || conv.step !== "complete") return null;
|
|
2852
|
+
return { username: conv.username, db: conv.db };
|
|
2853
|
+
}
|
|
2854
|
+
};
|
|
2855
|
+
InMemoryCredentialsProvider = class {
|
|
2856
|
+
credentials = /* @__PURE__ */ new Map();
|
|
2857
|
+
/**
|
|
2858
|
+
* Add a user with a password
|
|
2859
|
+
*/
|
|
2860
|
+
async addUser(username, password, db, roles) {
|
|
2861
|
+
const creds = await createScramCredentials(username, password, db);
|
|
2862
|
+
if (roles) {
|
|
2863
|
+
creds.roles = roles;
|
|
2864
|
+
}
|
|
2865
|
+
this.credentials.set(`${db}.${username}`, creds);
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* Add pre-computed credentials
|
|
2869
|
+
*/
|
|
2870
|
+
addCredentials(credentials) {
|
|
2871
|
+
this.credentials.set(`${credentials.db}.${credentials.username}`, credentials);
|
|
2872
|
+
}
|
|
2873
|
+
/**
|
|
2874
|
+
* Get credentials for a user
|
|
2875
|
+
*/
|
|
2876
|
+
async getCredentials(username, db) {
|
|
2877
|
+
return this.credentials.get(`${db}.${username}`) || null;
|
|
2878
|
+
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Remove a user
|
|
2881
|
+
*/
|
|
2882
|
+
removeUser(username, db) {
|
|
2883
|
+
return this.credentials.delete(`${db}.${username}`);
|
|
2884
|
+
}
|
|
2885
|
+
/**
|
|
2886
|
+
* Check if a user exists
|
|
2887
|
+
*/
|
|
2888
|
+
hasUser(username, db) {
|
|
2889
|
+
return this.credentials.has(`${db}.${username}`);
|
|
2890
|
+
}
|
|
2891
|
+
};
|
|
2892
|
+
}
|
|
2893
|
+
});
|
|
2894
|
+
|
|
2895
|
+
// src/wire/commands/router.ts
|
|
2896
|
+
var CommandRouter;
|
|
2897
|
+
var init_router = __esm({
|
|
2898
|
+
"src/wire/commands/router.ts"() {
|
|
2899
|
+
init_types2();
|
|
2900
|
+
init_hello();
|
|
2901
|
+
init_admin();
|
|
2902
|
+
init_crud();
|
|
2903
|
+
init_aggregate();
|
|
2904
|
+
init_commands();
|
|
2905
|
+
init_auth();
|
|
2906
|
+
init_scram();
|
|
2907
|
+
CommandRouter = class {
|
|
2908
|
+
handlers;
|
|
2909
|
+
authEnabled;
|
|
2910
|
+
authenticator;
|
|
2911
|
+
getConnectionState;
|
|
2912
|
+
setConnectionAuthenticated;
|
|
2913
|
+
clearConnectionAuthentication;
|
|
2914
|
+
constructor(backend, options = {}) {
|
|
2915
|
+
this.authEnabled = options.authEnabled ?? false;
|
|
2916
|
+
this.getConnectionState = options.getConnectionState;
|
|
2917
|
+
this.setConnectionAuthenticated = options.setConnectionAuthenticated;
|
|
2918
|
+
this.clearConnectionAuthentication = options.clearConnectionAuthentication;
|
|
2919
|
+
if (options.credentialsProvider) {
|
|
2920
|
+
this.authenticator = new ScramAuthenticator(options.credentialsProvider);
|
|
2921
|
+
}
|
|
2922
|
+
this.handlers = /* @__PURE__ */ new Map([
|
|
2923
|
+
// Handshake & discovery
|
|
2924
|
+
["hello", new HelloCommand()],
|
|
2925
|
+
["ismaster", new HelloCommand()],
|
|
2926
|
+
["isMaster", new HelloCommand()],
|
|
2927
|
+
// System info
|
|
2928
|
+
["ping", new PingCommand()],
|
|
2929
|
+
["buildInfo", new BuildInfoCommand()],
|
|
2930
|
+
["buildinfo", new BuildInfoCommand()],
|
|
2931
|
+
["hostInfo", new HostInfoCommand()],
|
|
2932
|
+
["whatsmyuri", new WhatsmyuriCommand()],
|
|
2933
|
+
["getLog", new GetLogCommand()],
|
|
2934
|
+
["getParameter", new GetParameterCommand()],
|
|
2935
|
+
["getCmdLineOpts", new GetCmdLineOptsCommand()],
|
|
2936
|
+
// Admin commands
|
|
2937
|
+
["listDatabases", new ListDatabasesCommand(backend)],
|
|
2938
|
+
["listCollections", new ListCollectionsCommand(backend)],
|
|
2939
|
+
["create", new CreateCommand(backend)],
|
|
2940
|
+
["drop", new DropCommand(backend)],
|
|
2941
|
+
["dropDatabase", new DropDatabaseCommand(backend)],
|
|
2942
|
+
["collStats", new CollStatsCommand(backend)],
|
|
2943
|
+
["dbStats", new DbStatsCommand(backend)],
|
|
2944
|
+
["serverStatus", new ServerStatusCommand(backend)],
|
|
2945
|
+
// CRUD
|
|
2946
|
+
["find", new FindCommand(backend)],
|
|
2947
|
+
["insert", new InsertCommand(backend)],
|
|
2948
|
+
["update", new UpdateCommand(backend)],
|
|
2949
|
+
["delete", new DeleteCommand(backend)],
|
|
2950
|
+
["count", new CountCommand(backend)],
|
|
2951
|
+
["distinct", new DistinctCommand(backend)],
|
|
2952
|
+
["getMore", new GetMoreCommand(backend)],
|
|
2953
|
+
["killCursors", new KillCursorsCommand(backend)],
|
|
2954
|
+
// Aggregation
|
|
2955
|
+
["aggregate", new AggregateCommand(backend)],
|
|
2956
|
+
// Indexes
|
|
2957
|
+
["listIndexes", new ListIndexesCommand(backend)],
|
|
2958
|
+
["createIndexes", new CreateIndexesCommand(backend)],
|
|
2959
|
+
["dropIndexes", new DropIndexesCommand(backend)]
|
|
2960
|
+
]);
|
|
2961
|
+
if (this.authenticator) {
|
|
2962
|
+
this.handlers.set("saslStart", new SaslStartCommand(this.authenticator));
|
|
2963
|
+
this.handlers.set("saslContinue", new SaslContinueCommand(this.authenticator));
|
|
2964
|
+
}
|
|
2965
|
+
this.handlers.set("authenticate", new AuthenticateCommand());
|
|
2966
|
+
if (this.clearConnectionAuthentication) {
|
|
2967
|
+
this.handlers.set("logout", new LogoutCommand(this.clearConnectionAuthentication));
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
/**
|
|
2971
|
+
* Route a command to its handler
|
|
2972
|
+
*/
|
|
2973
|
+
async route(command, context) {
|
|
2974
|
+
const commandName = Object.keys(command).find((key) => !key.startsWith("$"));
|
|
2975
|
+
if (!commandName) {
|
|
2976
|
+
return {
|
|
2977
|
+
response: errorResponse2(
|
|
2978
|
+
ErrorCode.COMMAND_NOT_FOUND,
|
|
2979
|
+
"no command found in request"
|
|
2980
|
+
)
|
|
2981
|
+
};
|
|
2982
|
+
}
|
|
2983
|
+
if (this.authEnabled && requiresAuthentication(commandName)) {
|
|
2984
|
+
const connState = this.getConnectionState?.(context.connectionId);
|
|
2985
|
+
if (!connState?.authenticated) {
|
|
2986
|
+
return {
|
|
2987
|
+
response: errorResponse2(
|
|
2988
|
+
ErrorCode.UNAUTHORIZED,
|
|
2989
|
+
`command ${commandName} requires authentication`
|
|
2990
|
+
)
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
const handler = this.handlers.get(commandName);
|
|
2995
|
+
if (!handler) {
|
|
2996
|
+
const lowerName = commandName.toLowerCase();
|
|
2997
|
+
for (const [name, h] of this.handlers) {
|
|
2998
|
+
if (name.toLowerCase() === lowerName) {
|
|
2999
|
+
return this.executeHandler(h, command, context, commandName);
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
return {
|
|
3003
|
+
response: errorResponse2(
|
|
3004
|
+
ErrorCode.COMMAND_NOT_FOUND,
|
|
3005
|
+
`no such command: '${commandName}'`
|
|
3006
|
+
)
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
return this.executeHandler(handler, command, context, commandName);
|
|
3010
|
+
}
|
|
3011
|
+
async executeHandler(handler, command, context, commandName) {
|
|
3012
|
+
try {
|
|
3013
|
+
if (commandName === "saslStart" || commandName === "saslContinue") {
|
|
3014
|
+
const authContext = {
|
|
3015
|
+
...context,
|
|
3016
|
+
setAuthenticated: (username, db) => {
|
|
3017
|
+
this.setConnectionAuthenticated?.(context.connectionId, username, db);
|
|
3018
|
+
}
|
|
3019
|
+
};
|
|
3020
|
+
return await handler.execute(command, authContext);
|
|
3021
|
+
}
|
|
3022
|
+
return await handler.execute(command, context);
|
|
3023
|
+
} catch (error) {
|
|
3024
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3025
|
+
console.error(`Error executing command '${commandName}':`, message);
|
|
3026
|
+
return {
|
|
3027
|
+
response: errorResponse2(ErrorCode.INTERNAL_ERROR, message)
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
/**
|
|
3032
|
+
* Check if a command exists
|
|
3033
|
+
*/
|
|
3034
|
+
hasCommand(name) {
|
|
3035
|
+
return this.handlers.has(name);
|
|
3036
|
+
}
|
|
3037
|
+
/**
|
|
3038
|
+
* Register a new command handler
|
|
3039
|
+
*/
|
|
3040
|
+
registerCommand(name, handler) {
|
|
3041
|
+
this.handlers.set(name, handler);
|
|
3042
|
+
}
|
|
3043
|
+
};
|
|
3044
|
+
}
|
|
3045
|
+
});
|
|
3046
|
+
|
|
3047
|
+
// src/wire/server.ts
|
|
3048
|
+
var server_exports = {};
|
|
3049
|
+
__export(server_exports, {
|
|
3050
|
+
WireProtocolServer: () => WireProtocolServer,
|
|
3051
|
+
createServer: () => createServer
|
|
3052
|
+
});
|
|
3053
|
+
async function createServer(backend, options = {}) {
|
|
3054
|
+
const server = new WireProtocolServer(backend, options);
|
|
3055
|
+
await server.start();
|
|
3056
|
+
return server;
|
|
3057
|
+
}
|
|
3058
|
+
var DEFAULT_OPTIONS, WireProtocolServer;
|
|
3059
|
+
var init_server = __esm({
|
|
3060
|
+
"src/wire/server.ts"() {
|
|
3061
|
+
init_message();
|
|
3062
|
+
init_router();
|
|
3063
|
+
init_types();
|
|
3064
|
+
init_auth();
|
|
3065
|
+
init_scram();
|
|
3066
|
+
init_auth();
|
|
3067
|
+
DEFAULT_OPTIONS = {
|
|
3068
|
+
port: 27017,
|
|
3069
|
+
host: "localhost",
|
|
3070
|
+
verbose: false
|
|
3071
|
+
};
|
|
3072
|
+
WireProtocolServer = class {
|
|
3073
|
+
options;
|
|
3074
|
+
router;
|
|
3075
|
+
connections = /* @__PURE__ */ new Map();
|
|
3076
|
+
nextConnectionId = 1;
|
|
3077
|
+
nextRequestId = 1;
|
|
3078
|
+
// Use unknown to avoid typing issues with Bun.listen union types
|
|
3079
|
+
server = null;
|
|
3080
|
+
authenticator = null;
|
|
3081
|
+
constructor(backend, options = {}) {
|
|
3082
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
3083
|
+
this.router = new CommandRouter(backend);
|
|
3084
|
+
if (this.options.auth?.enabled) {
|
|
3085
|
+
const credentialsProvider = new InMemoryCredentialsProvider();
|
|
3086
|
+
credentialsProvider.addUser(
|
|
3087
|
+
this.options.auth.username,
|
|
3088
|
+
this.options.auth.password,
|
|
3089
|
+
"admin"
|
|
3090
|
+
// Default auth database
|
|
3091
|
+
).then(() => {
|
|
3092
|
+
console.log(`Authentication enabled for user: ${this.options.auth.username}`);
|
|
3093
|
+
});
|
|
3094
|
+
this.authenticator = new ScramAuthenticator(credentialsProvider);
|
|
3095
|
+
this.router.registerCommand("saslStart", new SaslStartCommand(this.authenticator));
|
|
3096
|
+
this.router.registerCommand("saslContinue", new SaslContinueCommand(this.authenticator));
|
|
3097
|
+
this.router.registerCommand("logout", new LogoutCommand((connId) => {
|
|
3098
|
+
const conn = this.connections.get(connId);
|
|
3099
|
+
if (conn) {
|
|
3100
|
+
conn.authenticated = false;
|
|
3101
|
+
}
|
|
3102
|
+
}));
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
/**
|
|
3106
|
+
* Start the server
|
|
3107
|
+
*/
|
|
3108
|
+
async start() {
|
|
3109
|
+
const { port, host, verbose, tls } = this.options;
|
|
3110
|
+
const socketHandlers = {
|
|
3111
|
+
open: (socket) => {
|
|
3112
|
+
const connectionId = this.nextConnectionId++;
|
|
3113
|
+
socket.data = { connectionId, buffer: Buffer.alloc(0) };
|
|
3114
|
+
this.connections.set(connectionId, {
|
|
3115
|
+
id: connectionId,
|
|
3116
|
+
authenticated: false,
|
|
3117
|
+
compressionEnabled: false,
|
|
3118
|
+
cursors: /* @__PURE__ */ new Map()
|
|
3119
|
+
});
|
|
3120
|
+
if (verbose) {
|
|
3121
|
+
const connType = tls ? "TLS connection" : "Connection";
|
|
3122
|
+
console.log(`[${connectionId}] ${connType} opened`);
|
|
3123
|
+
}
|
|
3124
|
+
},
|
|
3125
|
+
data: async (socket, data) => {
|
|
3126
|
+
socket.data.buffer = Buffer.concat([socket.data.buffer, data]);
|
|
3127
|
+
await this.processBuffer(socket);
|
|
3128
|
+
},
|
|
3129
|
+
close: (socket) => {
|
|
3130
|
+
const connId = socket.data.connectionId;
|
|
3131
|
+
if (verbose) {
|
|
3132
|
+
console.log(`[${connId}] Connection closed`);
|
|
3133
|
+
}
|
|
3134
|
+
this.connections.delete(connId);
|
|
3135
|
+
},
|
|
3136
|
+
error: (socket, error) => {
|
|
3137
|
+
const { connectionId } = socket.data;
|
|
3138
|
+
console.error(`[${connectionId}] Socket error:`, error);
|
|
3139
|
+
}
|
|
3140
|
+
};
|
|
3141
|
+
if (tls) {
|
|
3142
|
+
this.server = Bun.listen({
|
|
3143
|
+
hostname: host,
|
|
3144
|
+
port,
|
|
3145
|
+
socket: socketHandlers,
|
|
3146
|
+
tls: this.buildTlsConfig(tls)
|
|
3147
|
+
});
|
|
3148
|
+
console.log(`mongo.do wire protocol server listening on TLS ${host}:${port}`);
|
|
3149
|
+
} else {
|
|
3150
|
+
this.server = Bun.listen({
|
|
3151
|
+
hostname: host,
|
|
3152
|
+
port,
|
|
3153
|
+
socket: socketHandlers
|
|
3154
|
+
});
|
|
3155
|
+
console.log(`mongo.do wire protocol server listening on TCP ${host}:${port}`);
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
/**
|
|
3159
|
+
* Build TLS configuration object from TlsOptions
|
|
3160
|
+
*/
|
|
3161
|
+
buildTlsConfig(tlsOptions) {
|
|
3162
|
+
const config = {
|
|
3163
|
+
key: tlsOptions.key,
|
|
3164
|
+
cert: tlsOptions.cert
|
|
3165
|
+
};
|
|
3166
|
+
if (tlsOptions.ca !== void 0) {
|
|
3167
|
+
config.ca = tlsOptions.ca;
|
|
3168
|
+
}
|
|
3169
|
+
if (tlsOptions.passphrase !== void 0) {
|
|
3170
|
+
config.passphrase = tlsOptions.passphrase;
|
|
3171
|
+
}
|
|
3172
|
+
if (tlsOptions.requestCert !== void 0) {
|
|
3173
|
+
config.requestCert = tlsOptions.requestCert;
|
|
3174
|
+
}
|
|
3175
|
+
if (tlsOptions.rejectUnauthorized !== void 0) {
|
|
3176
|
+
config.rejectUnauthorized = tlsOptions.rejectUnauthorized;
|
|
3177
|
+
}
|
|
3178
|
+
if (tlsOptions.minVersion !== void 0) {
|
|
3179
|
+
config.minVersion = tlsOptions.minVersion;
|
|
3180
|
+
}
|
|
3181
|
+
if (tlsOptions.maxVersion !== void 0) {
|
|
3182
|
+
config.maxVersion = tlsOptions.maxVersion;
|
|
3183
|
+
}
|
|
3184
|
+
if (tlsOptions.serverName !== void 0) {
|
|
3185
|
+
config.serverName = tlsOptions.serverName;
|
|
3186
|
+
}
|
|
3187
|
+
if (tlsOptions.ALPNProtocols !== void 0) {
|
|
3188
|
+
config.ALPNProtocols = tlsOptions.ALPNProtocols;
|
|
3189
|
+
}
|
|
3190
|
+
return config;
|
|
3191
|
+
}
|
|
3192
|
+
/**
|
|
3193
|
+
* Stop the server
|
|
3194
|
+
*/
|
|
3195
|
+
async stop() {
|
|
3196
|
+
if (this.server) {
|
|
3197
|
+
this.server.stop();
|
|
3198
|
+
this.server = null;
|
|
3199
|
+
console.log("Server stopped");
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
/**
|
|
3203
|
+
* Process accumulated buffer for complete messages
|
|
3204
|
+
*/
|
|
3205
|
+
async processBuffer(socket) {
|
|
3206
|
+
const { connectionId, buffer } = socket.data;
|
|
3207
|
+
while (buffer.length >= 4) {
|
|
3208
|
+
const messageLength = buffer.readInt32LE(0);
|
|
3209
|
+
if (buffer.length < messageLength) {
|
|
3210
|
+
break;
|
|
3211
|
+
}
|
|
3212
|
+
const messageBuffer = buffer.subarray(0, messageLength);
|
|
3213
|
+
socket.data.buffer = buffer.subarray(messageLength);
|
|
3214
|
+
try {
|
|
3215
|
+
const response = await this.handleMessage(connectionId, messageBuffer);
|
|
3216
|
+
if (response) {
|
|
3217
|
+
socket.write(response);
|
|
3218
|
+
}
|
|
3219
|
+
} catch (error) {
|
|
3220
|
+
console.error(`[${connectionId}] Error handling message:`, error);
|
|
3221
|
+
const errorResponse3 = serializeOpMsg(
|
|
3222
|
+
this.nextRequestId++,
|
|
3223
|
+
messageBuffer.readInt32LE(4),
|
|
3224
|
+
// responseTo = requestID
|
|
3225
|
+
{ ok: 0, errmsg: error instanceof Error ? error.message : "Unknown error", code: 1 }
|
|
3226
|
+
);
|
|
3227
|
+
socket.write(errorResponse3);
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
/**
|
|
3232
|
+
* Handle a single message
|
|
3233
|
+
*/
|
|
3234
|
+
async handleMessage(connectionId, buffer) {
|
|
3235
|
+
const message = parseMessage(buffer);
|
|
3236
|
+
const { db, command, documentSequences } = extractCommand(message);
|
|
3237
|
+
const cmdName = Object.keys(command).find((k) => !k.startsWith("$"));
|
|
3238
|
+
if (this.options.verbose) {
|
|
3239
|
+
console.log(`[${connectionId}] ${cmdName} on ${db}`);
|
|
3240
|
+
}
|
|
3241
|
+
const connection = this.connections.get(connectionId);
|
|
3242
|
+
if (this.options.auth?.enabled && connection) {
|
|
3243
|
+
const isAuthRequired = requiresAuthentication(cmdName || "");
|
|
3244
|
+
if (isAuthRequired && !connection.authenticated) {
|
|
3245
|
+
const errorResult = {
|
|
3246
|
+
ok: 0,
|
|
3247
|
+
errmsg: "Authentication required. Use SCRAM-SHA-256 to authenticate.",
|
|
3248
|
+
code: 13,
|
|
3249
|
+
// Unauthorized
|
|
3250
|
+
codeName: "Unauthorized"
|
|
3251
|
+
};
|
|
3252
|
+
if (message.header.opCode === OpCode.OP_QUERY) {
|
|
3253
|
+
return serializeOpReply(this.nextRequestId++, message.header.requestID, [errorResult]);
|
|
3254
|
+
} else {
|
|
3255
|
+
return serializeOpMsg(this.nextRequestId++, message.header.requestID, errorResult);
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
const context = {
|
|
3260
|
+
db,
|
|
3261
|
+
connectionId,
|
|
3262
|
+
requestId: message.header.requestID,
|
|
3263
|
+
documentSequences,
|
|
3264
|
+
// Pass auth info for SASL handlers
|
|
3265
|
+
auth: this.options.auth
|
|
3266
|
+
};
|
|
3267
|
+
if (connection !== void 0) {
|
|
3268
|
+
context.connection = connection;
|
|
3269
|
+
}
|
|
3270
|
+
const result = await this.router.route(command, context);
|
|
3271
|
+
if (cmdName === "saslContinue" && result.response.ok === 1 && result.response.done === true) {
|
|
3272
|
+
if (connection) {
|
|
3273
|
+
connection.authenticated = true;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
if (message.header.opCode === OpCode.OP_QUERY) {
|
|
3277
|
+
return serializeOpReply(
|
|
3278
|
+
this.nextRequestId++,
|
|
3279
|
+
message.header.requestID,
|
|
3280
|
+
[result.response]
|
|
3281
|
+
);
|
|
3282
|
+
} else {
|
|
3283
|
+
return serializeOpMsg(
|
|
3284
|
+
this.nextRequestId++,
|
|
3285
|
+
message.header.requestID,
|
|
3286
|
+
result.response
|
|
3287
|
+
);
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
/**
|
|
3291
|
+
* Get server address info
|
|
3292
|
+
*/
|
|
3293
|
+
get address() {
|
|
3294
|
+
return {
|
|
3295
|
+
host: this.options.host,
|
|
3296
|
+
port: this.options.port,
|
|
3297
|
+
tls: this.isTls
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
/**
|
|
3301
|
+
* Check if server is using TLS
|
|
3302
|
+
*/
|
|
3303
|
+
get isTls() {
|
|
3304
|
+
return this.options.tls !== void 0;
|
|
3305
|
+
}
|
|
3306
|
+
/**
|
|
3307
|
+
* Get the MongoDB connection string for this server
|
|
3308
|
+
* Returns mongodb:// for non-TLS or mongodb+srv:// style for TLS
|
|
3309
|
+
*/
|
|
3310
|
+
get connectionString() {
|
|
3311
|
+
const { host, port } = this.options;
|
|
3312
|
+
const protocol = this.isTls ? "mongodb+ssl" : "mongodb";
|
|
3313
|
+
return `${protocol}://${host}:${port}`;
|
|
3314
|
+
}
|
|
3315
|
+
};
|
|
3316
|
+
}
|
|
3317
|
+
});
|
|
3318
|
+
|
|
3319
|
+
// package.json
|
|
3320
|
+
var version = "0.1.1";
|
|
3321
|
+
|
|
3322
|
+
// src/wire/backend/workers-proxy.ts
|
|
3323
|
+
var MongoProxyError = class extends Error {
|
|
3324
|
+
code;
|
|
3325
|
+
codeName;
|
|
3326
|
+
constructor(message, code, codeName) {
|
|
3327
|
+
super(message);
|
|
3328
|
+
this.name = "MongoProxyError";
|
|
3329
|
+
this.code = code;
|
|
3330
|
+
if (codeName) {
|
|
3331
|
+
this.codeName = codeName;
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
};
|
|
3335
|
+
var CURSOR_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3336
|
+
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
3337
|
+
var NON_RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
3338
|
+
2,
|
|
3339
|
+
// BadValue
|
|
3340
|
+
13,
|
|
3341
|
+
// Unauthorized
|
|
3342
|
+
26,
|
|
3343
|
+
// NamespaceNotFound
|
|
3344
|
+
59,
|
|
3345
|
+
// CommandNotFound
|
|
3346
|
+
11e3
|
|
3347
|
+
// DuplicateKey
|
|
3348
|
+
]);
|
|
3349
|
+
var WorkersProxyBackend = class {
|
|
3350
|
+
endpoint;
|
|
3351
|
+
authToken;
|
|
3352
|
+
timeout;
|
|
3353
|
+
retries;
|
|
3354
|
+
retryDelay;
|
|
3355
|
+
cursors = /* @__PURE__ */ new Map();
|
|
3356
|
+
constructor(options) {
|
|
3357
|
+
if (!options.endpoint) {
|
|
3358
|
+
throw new Error("WorkersProxyBackend requires an endpoint URL");
|
|
3359
|
+
}
|
|
3360
|
+
try {
|
|
3361
|
+
new URL(options.endpoint);
|
|
3362
|
+
} catch {
|
|
3363
|
+
throw new Error(`WorkersProxyBackend endpoint is not a valid URL: ${options.endpoint}`);
|
|
3364
|
+
}
|
|
3365
|
+
this.endpoint = options.endpoint;
|
|
3366
|
+
if (options.authToken) {
|
|
3367
|
+
this.authToken = options.authToken;
|
|
3368
|
+
}
|
|
3369
|
+
this.timeout = options.timeout ?? 3e4;
|
|
3370
|
+
this.retries = options.retries ?? 0;
|
|
3371
|
+
this.retryDelay = options.retryDelay ?? 1e3;
|
|
3372
|
+
}
|
|
3373
|
+
// ==========================================================================
|
|
3374
|
+
// Private Helper Methods
|
|
3375
|
+
// ==========================================================================
|
|
3376
|
+
/**
|
|
3377
|
+
* Make an RPC call to the Workers endpoint
|
|
3378
|
+
*/
|
|
3379
|
+
async rpc(request, attempt = 0) {
|
|
3380
|
+
const headers = {
|
|
3381
|
+
"Content-Type": "application/json"
|
|
3382
|
+
};
|
|
3383
|
+
if (this.authToken) {
|
|
3384
|
+
headers["Authorization"] = `Bearer ${this.authToken}`;
|
|
3385
|
+
}
|
|
3386
|
+
try {
|
|
3387
|
+
const response = await fetch(this.endpoint, {
|
|
3388
|
+
method: "POST",
|
|
3389
|
+
headers,
|
|
3390
|
+
body: JSON.stringify(request),
|
|
3391
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
3392
|
+
});
|
|
3393
|
+
if (!response.ok && !response.headers.get("Content-Type")?.includes("application/json")) {
|
|
3394
|
+
const error = new Error(`HTTP error: ${response.status} ${response.statusText}`);
|
|
3395
|
+
if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < this.retries) {
|
|
3396
|
+
await this.delay(this.retryDelay);
|
|
3397
|
+
return this.rpc(request, attempt + 1);
|
|
3398
|
+
}
|
|
3399
|
+
throw error;
|
|
3400
|
+
}
|
|
3401
|
+
let data;
|
|
3402
|
+
try {
|
|
3403
|
+
data = await response.json();
|
|
3404
|
+
} catch {
|
|
3405
|
+
throw new Error("Invalid JSON response from RPC endpoint");
|
|
3406
|
+
}
|
|
3407
|
+
if (data.ok === 0) {
|
|
3408
|
+
const error = new MongoProxyError(data.error, data.code, data.codeName);
|
|
3409
|
+
if (NON_RETRYABLE_ERROR_CODES.has(data.code)) {
|
|
3410
|
+
throw error;
|
|
3411
|
+
}
|
|
3412
|
+
throw error;
|
|
3413
|
+
}
|
|
3414
|
+
return data.result;
|
|
3415
|
+
} catch (error) {
|
|
3416
|
+
if (attempt < this.retries && error instanceof Error && !("code" in error && NON_RETRYABLE_ERROR_CODES.has(error.code))) {
|
|
3417
|
+
await this.delay(this.retryDelay);
|
|
3418
|
+
return this.rpc(request, attempt + 1);
|
|
3419
|
+
}
|
|
3420
|
+
throw error;
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
/**
|
|
3424
|
+
* Delay for a specified number of milliseconds
|
|
3425
|
+
*/
|
|
3426
|
+
delay(ms) {
|
|
3427
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3428
|
+
}
|
|
3429
|
+
/**
|
|
3430
|
+
* Convert cursor ID from string to bigint
|
|
3431
|
+
*/
|
|
3432
|
+
parseCursorId(cursorId) {
|
|
3433
|
+
if (typeof cursorId === "bigint") return cursorId;
|
|
3434
|
+
return BigInt(cursorId);
|
|
3435
|
+
}
|
|
3436
|
+
// ==========================================================================
|
|
3437
|
+
// Database Operations
|
|
3438
|
+
// ==========================================================================
|
|
3439
|
+
async listDatabases() {
|
|
3440
|
+
return this.rpc({ method: "listDatabases" });
|
|
3441
|
+
}
|
|
3442
|
+
async createDatabase(name) {
|
|
3443
|
+
await this.rpc({ method: "createDatabase", db: name });
|
|
3444
|
+
}
|
|
3445
|
+
async dropDatabase(name) {
|
|
3446
|
+
await this.rpc({ method: "dropDatabase", db: name });
|
|
3447
|
+
}
|
|
3448
|
+
async databaseExists(name) {
|
|
3449
|
+
return this.rpc({ method: "databaseExists", db: name });
|
|
3450
|
+
}
|
|
3451
|
+
// ==========================================================================
|
|
3452
|
+
// Collection Operations
|
|
3453
|
+
// ==========================================================================
|
|
3454
|
+
async listCollections(db, filter) {
|
|
3455
|
+
const request = {
|
|
3456
|
+
method: "listCollections",
|
|
3457
|
+
db
|
|
3458
|
+
};
|
|
3459
|
+
if (filter) {
|
|
3460
|
+
request.filter = filter;
|
|
3461
|
+
}
|
|
3462
|
+
return this.rpc(request);
|
|
3463
|
+
}
|
|
3464
|
+
async createCollection(db, name, options) {
|
|
3465
|
+
const request = {
|
|
3466
|
+
method: "createCollection",
|
|
3467
|
+
db,
|
|
3468
|
+
collection: name
|
|
3469
|
+
};
|
|
3470
|
+
if (options) {
|
|
3471
|
+
request.options = options;
|
|
3472
|
+
}
|
|
3473
|
+
await this.rpc(request);
|
|
3474
|
+
}
|
|
3475
|
+
async dropCollection(db, name) {
|
|
3476
|
+
await this.rpc({
|
|
3477
|
+
method: "dropCollection",
|
|
3478
|
+
db,
|
|
3479
|
+
collection: name
|
|
3480
|
+
});
|
|
3481
|
+
}
|
|
3482
|
+
async collectionExists(db, name) {
|
|
3483
|
+
return this.rpc({
|
|
3484
|
+
method: "collectionExists",
|
|
3485
|
+
db,
|
|
3486
|
+
collection: name
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3489
|
+
async collStats(db, collection) {
|
|
3490
|
+
return this.rpc({
|
|
3491
|
+
method: "collStats",
|
|
3492
|
+
db,
|
|
3493
|
+
collection
|
|
3494
|
+
});
|
|
3495
|
+
}
|
|
3496
|
+
async dbStats(db) {
|
|
3497
|
+
return this.rpc({
|
|
3498
|
+
method: "dbStats",
|
|
3499
|
+
db
|
|
3500
|
+
});
|
|
3501
|
+
}
|
|
3502
|
+
// ==========================================================================
|
|
3503
|
+
// CRUD Operations
|
|
3504
|
+
// ==========================================================================
|
|
3505
|
+
async find(db, collection, options) {
|
|
3506
|
+
const request = {
|
|
3507
|
+
method: "find",
|
|
3508
|
+
db,
|
|
3509
|
+
collection
|
|
3510
|
+
};
|
|
3511
|
+
if (options.filter) {
|
|
3512
|
+
request.filter = options.filter;
|
|
3513
|
+
}
|
|
3514
|
+
const findOptions = {};
|
|
3515
|
+
if (options.projection) findOptions.projection = options.projection;
|
|
3516
|
+
if (options.sort) findOptions.sort = options.sort;
|
|
3517
|
+
if (options.limit !== void 0) findOptions.limit = options.limit;
|
|
3518
|
+
if (options.skip !== void 0) findOptions.skip = options.skip;
|
|
3519
|
+
if (options.batchSize !== void 0) findOptions.batchSize = options.batchSize;
|
|
3520
|
+
if (options.hint) findOptions.hint = options.hint;
|
|
3521
|
+
if (options.comment) findOptions.comment = options.comment;
|
|
3522
|
+
if (options.allowDiskUse !== void 0) findOptions.allowDiskUse = options.allowDiskUse;
|
|
3523
|
+
if (options.collation) findOptions.collation = options.collation;
|
|
3524
|
+
if (Object.keys(findOptions).length > 0) {
|
|
3525
|
+
request.options = findOptions;
|
|
3526
|
+
}
|
|
3527
|
+
const result = await this.rpc(request);
|
|
3528
|
+
return {
|
|
3529
|
+
documents: result.documents,
|
|
3530
|
+
cursorId: this.parseCursorId(result.cursorId),
|
|
3531
|
+
hasMore: result.hasMore
|
|
3532
|
+
};
|
|
3533
|
+
}
|
|
3534
|
+
async insertOne(db, collection, doc) {
|
|
3535
|
+
const result = await this.rpc({
|
|
3536
|
+
method: "insertOne",
|
|
3537
|
+
db,
|
|
3538
|
+
collection,
|
|
3539
|
+
document: doc
|
|
3540
|
+
});
|
|
3541
|
+
return {
|
|
3542
|
+
acknowledged: result.acknowledged,
|
|
3543
|
+
insertedIds: new Map(Object.entries(result.insertedIds).map(([k, v]) => [Number(k), v])),
|
|
3544
|
+
insertedCount: result.insertedCount
|
|
3545
|
+
};
|
|
3546
|
+
}
|
|
3547
|
+
async insertMany(db, collection, docs) {
|
|
3548
|
+
const result = await this.rpc({
|
|
3549
|
+
method: "insertMany",
|
|
3550
|
+
db,
|
|
3551
|
+
collection,
|
|
3552
|
+
documents: docs
|
|
3553
|
+
});
|
|
3554
|
+
return {
|
|
3555
|
+
acknowledged: result.acknowledged,
|
|
3556
|
+
insertedIds: new Map(Object.entries(result.insertedIds).map(([k, v]) => [Number(k), v])),
|
|
3557
|
+
insertedCount: result.insertedCount
|
|
3558
|
+
};
|
|
3559
|
+
}
|
|
3560
|
+
async updateOne(db, collection, filter, update, options) {
|
|
3561
|
+
const request = {
|
|
3562
|
+
method: "updateOne",
|
|
3563
|
+
db,
|
|
3564
|
+
collection,
|
|
3565
|
+
filter,
|
|
3566
|
+
update
|
|
3567
|
+
};
|
|
3568
|
+
if (options) {
|
|
3569
|
+
request.options = options;
|
|
3570
|
+
}
|
|
3571
|
+
return this.rpc(request);
|
|
3572
|
+
}
|
|
3573
|
+
async updateMany(db, collection, filter, update, options) {
|
|
3574
|
+
const request = {
|
|
3575
|
+
method: "updateMany",
|
|
3576
|
+
db,
|
|
3577
|
+
collection,
|
|
3578
|
+
filter,
|
|
3579
|
+
update
|
|
3580
|
+
};
|
|
3581
|
+
if (options) {
|
|
3582
|
+
request.options = options;
|
|
3583
|
+
}
|
|
3584
|
+
return this.rpc(request);
|
|
3585
|
+
}
|
|
3586
|
+
async deleteOne(db, collection, filter) {
|
|
3587
|
+
return this.rpc({
|
|
3588
|
+
method: "deleteOne",
|
|
3589
|
+
db,
|
|
3590
|
+
collection,
|
|
3591
|
+
filter
|
|
3592
|
+
});
|
|
3593
|
+
}
|
|
3594
|
+
async deleteMany(db, collection, filter) {
|
|
3595
|
+
return this.rpc({
|
|
3596
|
+
method: "deleteMany",
|
|
3597
|
+
db,
|
|
3598
|
+
collection,
|
|
3599
|
+
filter
|
|
3600
|
+
});
|
|
3601
|
+
}
|
|
3602
|
+
// ==========================================================================
|
|
3603
|
+
// Count and Distinct
|
|
3604
|
+
// ==========================================================================
|
|
3605
|
+
async count(db, collection, query) {
|
|
3606
|
+
const request = {
|
|
3607
|
+
method: "count",
|
|
3608
|
+
db,
|
|
3609
|
+
collection
|
|
3610
|
+
};
|
|
3611
|
+
if (query) {
|
|
3612
|
+
request.query = query;
|
|
3613
|
+
}
|
|
3614
|
+
return this.rpc(request);
|
|
3615
|
+
}
|
|
3616
|
+
async distinct(db, collection, field, query) {
|
|
3617
|
+
const request = {
|
|
3618
|
+
method: "distinct",
|
|
3619
|
+
db,
|
|
3620
|
+
collection,
|
|
3621
|
+
field
|
|
3622
|
+
};
|
|
3623
|
+
if (query) {
|
|
3624
|
+
request.query = query;
|
|
3625
|
+
}
|
|
3626
|
+
return this.rpc(request);
|
|
3627
|
+
}
|
|
3628
|
+
// ==========================================================================
|
|
3629
|
+
// Aggregation
|
|
3630
|
+
// ==========================================================================
|
|
3631
|
+
async aggregate(db, collection, pipeline, options) {
|
|
3632
|
+
const request = {
|
|
3633
|
+
method: "aggregate",
|
|
3634
|
+
db,
|
|
3635
|
+
collection,
|
|
3636
|
+
pipeline
|
|
3637
|
+
};
|
|
3638
|
+
if (options) {
|
|
3639
|
+
request.options = options;
|
|
3640
|
+
}
|
|
3641
|
+
const result = await this.rpc(request);
|
|
3642
|
+
return {
|
|
3643
|
+
documents: result.documents,
|
|
3644
|
+
cursorId: this.parseCursorId(result.cursorId),
|
|
3645
|
+
hasMore: result.hasMore
|
|
3646
|
+
};
|
|
3647
|
+
}
|
|
3648
|
+
// ==========================================================================
|
|
3649
|
+
// Index Operations
|
|
3650
|
+
// ==========================================================================
|
|
3651
|
+
async listIndexes(db, collection) {
|
|
3652
|
+
return this.rpc({
|
|
3653
|
+
method: "listIndexes",
|
|
3654
|
+
db,
|
|
3655
|
+
collection
|
|
3656
|
+
});
|
|
3657
|
+
}
|
|
3658
|
+
async createIndexes(db, collection, indexes) {
|
|
3659
|
+
return this.rpc({
|
|
3660
|
+
method: "createIndexes",
|
|
3661
|
+
db,
|
|
3662
|
+
collection,
|
|
3663
|
+
options: { indexes }
|
|
3664
|
+
});
|
|
3665
|
+
}
|
|
3666
|
+
async dropIndex(db, collection, indexName) {
|
|
3667
|
+
await this.rpc({
|
|
3668
|
+
method: "dropIndex",
|
|
3669
|
+
db,
|
|
3670
|
+
collection,
|
|
3671
|
+
options: { indexName }
|
|
3672
|
+
});
|
|
3673
|
+
}
|
|
3674
|
+
async dropIndexes(db, collection) {
|
|
3675
|
+
await this.rpc({
|
|
3676
|
+
method: "dropIndexes",
|
|
3677
|
+
db,
|
|
3678
|
+
collection
|
|
3679
|
+
});
|
|
3680
|
+
}
|
|
3681
|
+
// ==========================================================================
|
|
3682
|
+
// Cursor Management
|
|
3683
|
+
// ==========================================================================
|
|
3684
|
+
createCursor(state) {
|
|
3685
|
+
this.cursors.set(state.id, state);
|
|
3686
|
+
}
|
|
3687
|
+
getCursor(id) {
|
|
3688
|
+
return this.cursors.get(id);
|
|
3689
|
+
}
|
|
3690
|
+
advanceCursor(id, count) {
|
|
3691
|
+
const cursor = this.cursors.get(id);
|
|
3692
|
+
if (!cursor) {
|
|
3693
|
+
return [];
|
|
3694
|
+
}
|
|
3695
|
+
const start = cursor.position;
|
|
3696
|
+
const end = Math.min(start + count, cursor.documents.length);
|
|
3697
|
+
cursor.position = end;
|
|
3698
|
+
return cursor.documents.slice(start, end);
|
|
3699
|
+
}
|
|
3700
|
+
closeCursor(id) {
|
|
3701
|
+
return this.cursors.delete(id);
|
|
3702
|
+
}
|
|
3703
|
+
cleanupExpiredCursors() {
|
|
3704
|
+
const now = Date.now();
|
|
3705
|
+
for (const [id, cursor] of this.cursors) {
|
|
3706
|
+
if (now - cursor.createdAt > CURSOR_TIMEOUT_MS) {
|
|
3707
|
+
this.cursors.delete(id);
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
};
|
|
3712
|
+
|
|
3713
|
+
// src/cli/server.ts
|
|
3714
|
+
var LocalSQLiteBackend2;
|
|
3715
|
+
var WireProtocolServer2;
|
|
3716
|
+
async function loadBunModules() {
|
|
3717
|
+
if (!LocalSQLiteBackend2) {
|
|
3718
|
+
const localModule = await Promise.resolve().then(() => (init_local_sqlite(), local_sqlite_exports));
|
|
3719
|
+
LocalSQLiteBackend2 = localModule.LocalSQLiteBackend;
|
|
3720
|
+
}
|
|
3721
|
+
if (!WireProtocolServer2) {
|
|
3722
|
+
const serverModule = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
3723
|
+
WireProtocolServer2 = serverModule.WireProtocolServer;
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
function parseArgs(args) {
|
|
3727
|
+
const options = {
|
|
3728
|
+
port: 27017,
|
|
3729
|
+
host: "localhost",
|
|
3730
|
+
dataDir: "./data",
|
|
3731
|
+
verbose: false,
|
|
3732
|
+
help: false
|
|
3733
|
+
};
|
|
3734
|
+
for (let i = 0; i < args.length; i++) {
|
|
3735
|
+
const arg = args[i];
|
|
3736
|
+
if (!arg) continue;
|
|
3737
|
+
if (arg === "--help" || arg === "-h") {
|
|
3738
|
+
options.help = true;
|
|
3739
|
+
continue;
|
|
3740
|
+
}
|
|
3741
|
+
if (arg === "--verbose" || arg === "-v") {
|
|
3742
|
+
options.verbose = true;
|
|
3743
|
+
continue;
|
|
3744
|
+
}
|
|
3745
|
+
if (arg.startsWith("--port=")) {
|
|
3746
|
+
options.port = parseInt(arg.slice(7), 10);
|
|
3747
|
+
continue;
|
|
3748
|
+
}
|
|
3749
|
+
if (arg.startsWith("-p=")) {
|
|
3750
|
+
options.port = parseInt(arg.slice(3), 10);
|
|
3751
|
+
continue;
|
|
3752
|
+
}
|
|
3753
|
+
if (arg === "--port" || arg === "-p") {
|
|
3754
|
+
const nextArg = args[++i];
|
|
3755
|
+
if (nextArg) {
|
|
3756
|
+
options.port = parseInt(nextArg, 10);
|
|
3757
|
+
}
|
|
3758
|
+
continue;
|
|
3759
|
+
}
|
|
3760
|
+
if (arg.startsWith("--host=")) {
|
|
3761
|
+
options.host = arg.slice(7);
|
|
3762
|
+
continue;
|
|
3763
|
+
}
|
|
3764
|
+
if (arg.startsWith("-H=")) {
|
|
3765
|
+
options.host = arg.slice(3);
|
|
3766
|
+
continue;
|
|
3767
|
+
}
|
|
3768
|
+
if (arg === "--host" || arg === "-H") {
|
|
3769
|
+
const nextArg = args[++i];
|
|
3770
|
+
if (nextArg) {
|
|
3771
|
+
options.host = nextArg;
|
|
3772
|
+
}
|
|
3773
|
+
continue;
|
|
3774
|
+
}
|
|
3775
|
+
if (arg.startsWith("--data=")) {
|
|
3776
|
+
options.dataDir = arg.slice(7);
|
|
3777
|
+
continue;
|
|
3778
|
+
}
|
|
3779
|
+
if (arg.startsWith("-d=")) {
|
|
3780
|
+
options.dataDir = arg.slice(3);
|
|
3781
|
+
continue;
|
|
3782
|
+
}
|
|
3783
|
+
if (arg === "--data" || arg === "-d") {
|
|
3784
|
+
const nextArg = args[++i];
|
|
3785
|
+
if (nextArg) {
|
|
3786
|
+
options.dataDir = nextArg;
|
|
3787
|
+
}
|
|
3788
|
+
continue;
|
|
3789
|
+
}
|
|
3790
|
+
if (arg.startsWith("--remote=")) {
|
|
3791
|
+
options.remote = arg.slice(9);
|
|
3792
|
+
continue;
|
|
3793
|
+
}
|
|
3794
|
+
if (arg.startsWith("-r=")) {
|
|
3795
|
+
options.remote = arg.slice(3);
|
|
3796
|
+
continue;
|
|
3797
|
+
}
|
|
3798
|
+
if (arg === "--remote" || arg === "-r") {
|
|
3799
|
+
const nextArg = args[++i];
|
|
3800
|
+
if (nextArg) {
|
|
3801
|
+
options.remote = nextArg;
|
|
3802
|
+
}
|
|
3803
|
+
continue;
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
return options;
|
|
3807
|
+
}
|
|
3808
|
+
function validateOptions(options) {
|
|
3809
|
+
const errors = [];
|
|
3810
|
+
if (typeof options.port !== "number" || isNaN(options.port) || !Number.isInteger(options.port) || options.port < 1 || options.port > 65535) {
|
|
3811
|
+
errors.push(`Invalid port: ${options.port}. Port must be an integer between 1 and 65535.`);
|
|
3812
|
+
}
|
|
3813
|
+
if (!options.host || typeof options.host !== "string" || options.host.trim() === "") {
|
|
3814
|
+
errors.push("Invalid host: host cannot be empty");
|
|
3815
|
+
}
|
|
3816
|
+
if (!options.remote) {
|
|
3817
|
+
if (!options.dataDir || typeof options.dataDir !== "string" || options.dataDir.trim() === "") {
|
|
3818
|
+
errors.push("Invalid data directory: path cannot be empty");
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
if (options.remote !== void 0) {
|
|
3822
|
+
if (!options.remote || typeof options.remote !== "string" || options.remote.trim() === "") {
|
|
3823
|
+
errors.push("Invalid remote URL: URL cannot be empty. Use https://your-worker.workers.dev");
|
|
3824
|
+
} else {
|
|
3825
|
+
try {
|
|
3826
|
+
const url = new URL(options.remote);
|
|
3827
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
3828
|
+
errors.push(`Invalid remote URL: protocol must be http or https, got "${url.protocol.slice(0, -1)}"`);
|
|
3829
|
+
}
|
|
3830
|
+
} catch {
|
|
3831
|
+
errors.push(`Invalid remote URL: "${options.remote}" is not a valid URL. Use https://your-worker.workers.dev`);
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
return {
|
|
3836
|
+
valid: errors.length === 0,
|
|
3837
|
+
errors
|
|
3838
|
+
};
|
|
3839
|
+
}
|
|
3840
|
+
async function createBackend(options) {
|
|
3841
|
+
if (options.remote) {
|
|
3842
|
+
return new WorkersProxyBackend({
|
|
3843
|
+
endpoint: options.remote
|
|
3844
|
+
});
|
|
3845
|
+
}
|
|
3846
|
+
await loadBunModules();
|
|
3847
|
+
return new LocalSQLiteBackend2(options.dataDir);
|
|
3848
|
+
}
|
|
3849
|
+
function printHelp() {
|
|
3850
|
+
console.log(`
|
|
3851
|
+
Usage: mongo.do serve [options]
|
|
3852
|
+
|
|
3853
|
+
Start a MongoDB wire protocol server backed by SQLite.
|
|
3854
|
+
|
|
3855
|
+
Options:
|
|
3856
|
+
-p, --port <port> Port to listen on (default: 27017)
|
|
3857
|
+
-H, --host <host> Host to bind to (default: localhost)
|
|
3858
|
+
-d, --data <path> Data directory for local SQLite storage (default: ./data)
|
|
3859
|
+
-r, --remote <url> Proxy/forward to remote Cloudflare Workers endpoint
|
|
3860
|
+
-v, --verbose Enable verbose logging
|
|
3861
|
+
-V, --version Show version number
|
|
3862
|
+
-h, --help Show this help message
|
|
3863
|
+
|
|
3864
|
+
Examples:
|
|
3865
|
+
# Start local server on default port
|
|
3866
|
+
mongo.do serve
|
|
3867
|
+
|
|
3868
|
+
# Start on custom port with verbose logging
|
|
3869
|
+
mongo.do serve --port 27018 --verbose
|
|
3870
|
+
|
|
3871
|
+
# Bind to all interfaces
|
|
3872
|
+
mongo.do serve --host 0.0.0.0
|
|
3873
|
+
|
|
3874
|
+
# Use custom data directory
|
|
3875
|
+
mongo.do serve --data /var/lib/mongodo
|
|
3876
|
+
|
|
3877
|
+
# Proxy to remote Cloudflare Workers
|
|
3878
|
+
mongo.do serve --remote https://my-mongo-do.workers.dev
|
|
3879
|
+
|
|
3880
|
+
# Combine options
|
|
3881
|
+
mongo.do serve --port 27018 --host 0.0.0.0 --verbose
|
|
3882
|
+
`);
|
|
3883
|
+
}
|
|
3884
|
+
function printShutdownMessage() {
|
|
3885
|
+
console.log("MondoDB server gracefully shutdown. Clean stop complete.");
|
|
3886
|
+
}
|
|
3887
|
+
async function runServer(options, backend) {
|
|
3888
|
+
const validationResult = validateOptions(options);
|
|
3889
|
+
if (!validationResult.valid) {
|
|
3890
|
+
throw new Error(validationResult.errors[0]);
|
|
3891
|
+
}
|
|
3892
|
+
const serverBackend = await createBackend(options);
|
|
3893
|
+
if (options.remote && true) {
|
|
3894
|
+
try {
|
|
3895
|
+
await serverBackend.listDatabases();
|
|
3896
|
+
} catch (e) {
|
|
3897
|
+
throw new Error(`Failed to connect to remote worker: ${options.remote}. Network unreachable or worker not responding.`);
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
await loadBunModules();
|
|
3901
|
+
const wireServer = new WireProtocolServer2(serverBackend, {
|
|
3902
|
+
port: options.port,
|
|
3903
|
+
host: options.host,
|
|
3904
|
+
verbose: options.verbose
|
|
3905
|
+
});
|
|
3906
|
+
let running = false;
|
|
3907
|
+
let acceptingConnections = false;
|
|
3908
|
+
let connectionCount = 0;
|
|
3909
|
+
const handleSignal = async () => {
|
|
3910
|
+
if (running) {
|
|
3911
|
+
await controller.stop();
|
|
3912
|
+
}
|
|
3913
|
+
};
|
|
3914
|
+
const sigintHandler = handleSignal;
|
|
3915
|
+
const sigtermHandler = handleSignal;
|
|
3916
|
+
const controller = {
|
|
3917
|
+
get isRunning() {
|
|
3918
|
+
return running;
|
|
3919
|
+
},
|
|
3920
|
+
get isAcceptingConnections() {
|
|
3921
|
+
return acceptingConnections;
|
|
3922
|
+
},
|
|
3923
|
+
get activeConnections() {
|
|
3924
|
+
return connectionCount;
|
|
3925
|
+
},
|
|
3926
|
+
get address() {
|
|
3927
|
+
return { host: options.host, port: options.port };
|
|
3928
|
+
},
|
|
3929
|
+
async stop() {
|
|
3930
|
+
if (!running) return;
|
|
3931
|
+
running = false;
|
|
3932
|
+
acceptingConnections = false;
|
|
3933
|
+
connectionCount = 0;
|
|
3934
|
+
await wireServer.stop();
|
|
3935
|
+
if ("close" in serverBackend && typeof serverBackend.close === "function") {
|
|
3936
|
+
await serverBackend.close();
|
|
3937
|
+
}
|
|
3938
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
3939
|
+
process.removeListener("SIGTERM", sigtermHandler);
|
|
3940
|
+
}
|
|
3941
|
+
};
|
|
3942
|
+
process.on("SIGINT", sigintHandler);
|
|
3943
|
+
process.on("SIGTERM", sigtermHandler);
|
|
3944
|
+
try {
|
|
3945
|
+
await wireServer.start();
|
|
3946
|
+
running = true;
|
|
3947
|
+
acceptingConnections = true;
|
|
3948
|
+
} catch (e) {
|
|
3949
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
3950
|
+
process.removeListener("SIGTERM", sigtermHandler);
|
|
3951
|
+
if (e instanceof Error && e.message.includes("EADDRINUSE")) {
|
|
3952
|
+
throw new Error(`Port ${options.port} is already in use (EADDRINUSE)`);
|
|
3953
|
+
}
|
|
3954
|
+
throw e;
|
|
3955
|
+
}
|
|
3956
|
+
return controller;
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
// src/cli/index.ts
|
|
3960
|
+
var colors = {
|
|
3961
|
+
reset: "\x1B[0m",
|
|
3962
|
+
bold: "\x1B[1m",
|
|
3963
|
+
dim: "\x1B[2m",
|
|
3964
|
+
// Foreground colors
|
|
3965
|
+
red: "\x1B[31m",
|
|
3966
|
+
green: "\x1B[32m",
|
|
3967
|
+
yellow: "\x1B[33m",
|
|
3968
|
+
blue: "\x1B[34m",
|
|
3969
|
+
magenta: "\x1B[35m",
|
|
3970
|
+
cyan: "\x1B[36m",
|
|
3971
|
+
white: "\x1B[37m"
|
|
3972
|
+
};
|
|
3973
|
+
function supportsColor() {
|
|
3974
|
+
if (process.env.NO_COLOR !== void 0) {
|
|
3975
|
+
return false;
|
|
3976
|
+
}
|
|
3977
|
+
if (process.env.FORCE_COLOR !== void 0) {
|
|
3978
|
+
return true;
|
|
3979
|
+
}
|
|
3980
|
+
return process.stdout.isTTY ?? false;
|
|
3981
|
+
}
|
|
3982
|
+
var useColors = supportsColor();
|
|
51
3983
|
function colorize(text, color) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
// ============================================================================
|
|
57
|
-
// Output Functions
|
|
58
|
-
// ============================================================================
|
|
59
|
-
/**
|
|
60
|
-
* Print the ASCII banner for MondoDB
|
|
61
|
-
*/
|
|
3984
|
+
if (!useColors) return text;
|
|
3985
|
+
return `${colors[color]}${text}${colors.reset}`;
|
|
3986
|
+
}
|
|
62
3987
|
function printBanner() {
|
|
63
|
-
|
|
64
|
-
${colorize(
|
|
65
|
-
${colorize(
|
|
3988
|
+
const banner = `
|
|
3989
|
+
${colorize("MondoDB", "cyan")} ${colorize(`v${version}`, "dim")}
|
|
3990
|
+
${colorize("MongoDB-compatible database backed by SQLite", "dim")}
|
|
66
3991
|
`;
|
|
67
|
-
|
|
3992
|
+
console.log(banner);
|
|
68
3993
|
}
|
|
69
|
-
/**
|
|
70
|
-
* Print version information
|
|
71
|
-
*/
|
|
72
3994
|
function printVersion() {
|
|
73
|
-
|
|
3995
|
+
console.log(`mongo.do version ${version}`);
|
|
74
3996
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Print an error message
|
|
77
|
-
*/
|
|
78
3997
|
function printError(message) {
|
|
79
|
-
|
|
3998
|
+
console.error(`${colorize("error:", "red")} ${message}`);
|
|
80
3999
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Print enhanced startup message with colors
|
|
83
|
-
*/
|
|
84
4000
|
function printColoredStartupMessage(options) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Parse command line arguments with version flag support
|
|
106
|
-
*/
|
|
4001
|
+
const connectionString = `mongodb://${options.host}:${options.port}`;
|
|
4002
|
+
console.log("");
|
|
4003
|
+
if (options.remote) {
|
|
4004
|
+
console.log(`${colorize("Mode:", "bold")} ${colorize("Proxy/Remote", "magenta")}`);
|
|
4005
|
+
console.log(`${colorize("Connect:", "bold")} ${colorize(connectionString, "cyan")}`);
|
|
4006
|
+
console.log(`${colorize("Proxying to:", "bold")} ${colorize(options.remote, "blue")}`);
|
|
4007
|
+
} else {
|
|
4008
|
+
console.log(`${colorize("Mode:", "bold")} ${colorize("Local/SQLite", "green")}`);
|
|
4009
|
+
console.log(`${colorize("Connect:", "bold")} ${colorize(connectionString, "cyan")}`);
|
|
4010
|
+
console.log(`${colorize("Data directory:", "bold")} ${options.dataDir}`);
|
|
4011
|
+
}
|
|
4012
|
+
if (options.verbose) {
|
|
4013
|
+
console.log(`${colorize("Verbose logging:", "bold")} ${colorize("enabled", "yellow")}`);
|
|
4014
|
+
}
|
|
4015
|
+
console.log("");
|
|
4016
|
+
console.log(`${colorize("Ready to accept connections.", "green")} Press ${colorize("Ctrl+C", "bold")} to stop.`);
|
|
4017
|
+
console.log("");
|
|
4018
|
+
}
|
|
107
4019
|
function parseArgsExtended(args) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
4020
|
+
const baseOptions = parseArgs(args);
|
|
4021
|
+
const hasVersion = args.some(
|
|
4022
|
+
(arg) => arg === "--version" || arg === "-V"
|
|
4023
|
+
);
|
|
4024
|
+
return {
|
|
4025
|
+
...baseOptions,
|
|
4026
|
+
version: hasVersion
|
|
4027
|
+
};
|
|
115
4028
|
}
|
|
116
|
-
// ============================================================================
|
|
117
|
-
// Main Entry Point
|
|
118
|
-
// ============================================================================
|
|
119
|
-
/**
|
|
120
|
-
* Main CLI function
|
|
121
|
-
*/
|
|
122
4029
|
async function main() {
|
|
4030
|
+
try {
|
|
4031
|
+
const options = parseArgsExtended(process.argv.slice(2));
|
|
4032
|
+
if (options.version) {
|
|
4033
|
+
printVersion();
|
|
4034
|
+
process.exit(0);
|
|
4035
|
+
}
|
|
4036
|
+
if (options.help) {
|
|
4037
|
+
printHelp();
|
|
4038
|
+
process.exit(0);
|
|
4039
|
+
}
|
|
123
4040
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
process.exit(0);
|
|
135
|
-
}
|
|
136
|
-
// Validate options
|
|
137
|
-
try {
|
|
138
|
-
validateOptions(options);
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
if (error instanceof Error) {
|
|
142
|
-
printError(error.message);
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
printError('Invalid options');
|
|
146
|
-
}
|
|
147
|
-
console.log('');
|
|
148
|
-
console.log(`Run ${colorize('mongo.do --help', 'cyan')} for usage information.`);
|
|
149
|
-
process.exit(1);
|
|
150
|
-
}
|
|
151
|
-
// Print banner
|
|
152
|
-
printBanner();
|
|
153
|
-
// Start the server
|
|
154
|
-
const controller = await runServer(options);
|
|
155
|
-
// Print startup message
|
|
156
|
-
printColoredStartupMessage(options);
|
|
157
|
-
// Handle graceful shutdown
|
|
158
|
-
const shutdown = async (signal) => {
|
|
159
|
-
console.log('');
|
|
160
|
-
console.log(`${colorize(`Received ${signal}, shutting down...`, 'yellow')}`);
|
|
161
|
-
try {
|
|
162
|
-
await controller.stop();
|
|
163
|
-
printShutdownMessage();
|
|
164
|
-
process.exit(0);
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
printError(`Error during shutdown: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
168
|
-
process.exit(1);
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
// Register signal handlers
|
|
172
|
-
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
173
|
-
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
174
|
-
// Handle uncaught exceptions
|
|
175
|
-
process.on('uncaughtException', (error) => {
|
|
176
|
-
printError(`Uncaught exception: ${error.message}`);
|
|
177
|
-
if (options.verbose) {
|
|
178
|
-
console.error(error.stack);
|
|
179
|
-
}
|
|
180
|
-
process.exit(1);
|
|
181
|
-
});
|
|
182
|
-
// Handle unhandled promise rejections
|
|
183
|
-
process.on('unhandledRejection', (reason) => {
|
|
184
|
-
printError(`Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
185
|
-
if (options.verbose && reason instanceof Error) {
|
|
186
|
-
console.error(reason.stack);
|
|
187
|
-
}
|
|
188
|
-
process.exit(1);
|
|
189
|
-
});
|
|
4041
|
+
validateOptions(options);
|
|
4042
|
+
} catch (error) {
|
|
4043
|
+
if (error instanceof Error) {
|
|
4044
|
+
printError(error.message);
|
|
4045
|
+
} else {
|
|
4046
|
+
printError("Invalid options");
|
|
4047
|
+
}
|
|
4048
|
+
console.log("");
|
|
4049
|
+
console.log(`Run ${colorize("mongo.do --help", "cyan")} for usage information.`);
|
|
4050
|
+
process.exit(1);
|
|
190
4051
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
console.log(` Try using a port number above 1024, e.g., ${colorize('--port=27017', 'cyan')}.`);
|
|
204
|
-
}
|
|
205
|
-
else if (error.message.includes('Failed to connect to remote')) {
|
|
206
|
-
console.log('');
|
|
207
|
-
console.log(`${colorize('Hint:', 'yellow')} Could not connect to the remote worker.`);
|
|
208
|
-
console.log(` Check that the URL is correct and the worker is running.`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
printError('An unexpected error occurred');
|
|
213
|
-
}
|
|
4052
|
+
printBanner();
|
|
4053
|
+
const controller = await runServer(options);
|
|
4054
|
+
printColoredStartupMessage(options);
|
|
4055
|
+
const shutdown = async (signal) => {
|
|
4056
|
+
console.log("");
|
|
4057
|
+
console.log(`${colorize(`Received ${signal}, shutting down...`, "yellow")}`);
|
|
4058
|
+
try {
|
|
4059
|
+
await controller.stop();
|
|
4060
|
+
printShutdownMessage();
|
|
4061
|
+
process.exit(0);
|
|
4062
|
+
} catch (error) {
|
|
4063
|
+
printError(`Error during shutdown: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
214
4064
|
process.exit(1);
|
|
4065
|
+
}
|
|
4066
|
+
};
|
|
4067
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
4068
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
4069
|
+
process.on("uncaughtException", (error) => {
|
|
4070
|
+
printError(`Uncaught exception: ${error.message}`);
|
|
4071
|
+
if (options.verbose) {
|
|
4072
|
+
console.error(error.stack);
|
|
4073
|
+
}
|
|
4074
|
+
process.exit(1);
|
|
4075
|
+
});
|
|
4076
|
+
process.on("unhandledRejection", (reason) => {
|
|
4077
|
+
printError(`Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
4078
|
+
if (options.verbose && reason instanceof Error) {
|
|
4079
|
+
console.error(reason.stack);
|
|
4080
|
+
}
|
|
4081
|
+
process.exit(1);
|
|
4082
|
+
});
|
|
4083
|
+
} catch (error) {
|
|
4084
|
+
if (error instanceof Error) {
|
|
4085
|
+
printError(error.message);
|
|
4086
|
+
if (error.message.includes("EADDRINUSE")) {
|
|
4087
|
+
console.log("");
|
|
4088
|
+
console.log(`${colorize("Hint:", "yellow")} The port is already in use. Try a different port with ${colorize("--port", "cyan")}.`);
|
|
4089
|
+
} else if (error.message.includes("EACCES")) {
|
|
4090
|
+
console.log("");
|
|
4091
|
+
console.log(`${colorize("Hint:", "yellow")} Permission denied. Ports below 1024 require root privileges.`);
|
|
4092
|
+
console.log(` Try using a port number above 1024, e.g., ${colorize("--port=27017", "cyan")}.`);
|
|
4093
|
+
} else if (error.message.includes("Failed to connect to remote")) {
|
|
4094
|
+
console.log("");
|
|
4095
|
+
console.log(`${colorize("Hint:", "yellow")} Could not connect to the remote worker.`);
|
|
4096
|
+
console.log(` Check that the URL is correct and the worker is running.`);
|
|
4097
|
+
}
|
|
4098
|
+
} else {
|
|
4099
|
+
printError("An unexpected error occurred");
|
|
215
4100
|
}
|
|
4101
|
+
process.exit(1);
|
|
4102
|
+
}
|
|
216
4103
|
}
|
|
217
|
-
// Run the CLI
|
|
218
4104
|
main();
|
|
4105
|
+
//# sourceMappingURL=index.js.map
|
|
219
4106
|
//# sourceMappingURL=index.js.map
|