@malloy-publisher/server 0.0.235 → 0.0.237
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/app/api-doc.yaml +83 -27
- package/dist/app/assets/{EnvironmentPage-BsAnavYN.js → EnvironmentPage-DaP6CpgK.js} +1 -1
- package/dist/app/assets/{HomePage-CADE138j.js → HomePage-C9yNw3iu.js} +1 -1
- package/dist/app/assets/{LightMode-Cfh7KzN8.js → LightMode-Be4-G-dX.js} +1 -1
- package/dist/app/assets/{MainPage-CO3pRlnV.js → MainPage-J_rU2SgI.js} +2 -2
- package/dist/app/assets/{MaterializationsPage-p9YjkRXZ.js → MaterializationsPage-DHgPI4MO.js} +1 -1
- package/dist/app/assets/ModelPage-BzXfjTu5.js +1 -0
- package/dist/app/assets/{PackagePage-e4kN75YR.js → PackagePage-ByoSTLJ4.js} +1 -1
- package/dist/app/assets/RouteError-CrSeKN2r.js +1 -0
- package/dist/app/assets/{ThemeEditorPage-CciagFTq.js → ThemeEditorPage-Btxnmhtk.js} +1 -1
- package/dist/app/assets/{WorkbookPage-DNWmkCXa.js → WorkbookPage-D6JLPnWO.js} +1 -1
- package/dist/app/assets/{core-Rj_4rRnA.es-BZyvITuO.js → core-BSRgt1mG.es-Bc_D2Lq4.js} +1 -1
- package/dist/app/assets/{index-CH2AcDzc.js → index-BRijWj6K.js} +15 -15
- package/dist/app/assets/{index-DQa463gC.js → index-BX4D94xw.js} +1 -1
- package/dist/app/assets/{index-VBbcc8s6.js → index-Cvy0xtb7.js} +1 -1
- package/dist/app/assets/{index-DQpV7MyA.js → index-DMAhIJNC.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/package_load_worker.mjs +59 -20
- package/dist/server.mjs +1342 -616
- package/package.json +8 -2
- package/.eslintrc.json +0 -44
- package/.prettierignore +0 -1
- package/.prettierrc +0 -4
- package/build.ts +0 -97
- package/dist/app/assets/ModelPage-C1OSTv-x.js +0 -1
- package/dist/app/assets/RouteError-CzbfOkng.js +0 -1
- package/eslint.config.mjs +0 -76
- package/k6-tests/README.md +0 -144
- package/k6-tests/bun.lock +0 -797
- package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
- package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
- package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
- package/k6-tests/load-test/load-test-crud.ts +0 -142
- package/k6-tests/load-test/load-test.ts +0 -784
- package/k6-tests/package.json +0 -27
- package/k6-tests/scripts/run-with-otel.sh +0 -50
- package/k6-tests/smoke-test/smoke-test.ts +0 -158
- package/k6-tests/tsconfig.json +0 -29
- package/k6-tests/utils/client_factory.ts +0 -128
- package/k6-tests/utils/common.ts +0 -489
- package/k6-tests/utils/logger.ts +0 -62
- package/k6-tests/utils/types.d.ts +0 -35
- package/malloy_mcp.dxt +0 -0
- package/publisher.config.example.bigquery.json +0 -33
- package/publisher.config.example.duckdb.json +0 -23
- package/publisher.config.json +0 -23
- package/scripts/bake-duckdb-extensions.js +0 -110
- package/src/config.spec.ts +0 -1535
- package/src/config.theme.spec.ts +0 -178
- package/src/config.ts +0 -1142
- package/src/constants.ts +0 -105
- package/src/controller/compile.controller.ts +0 -40
- package/src/controller/connection.controller.spec.ts +0 -849
- package/src/controller/connection.controller.ts +0 -901
- package/src/controller/database.controller.ts +0 -24
- package/src/controller/materialization.controller.spec.ts +0 -293
- package/src/controller/materialization.controller.ts +0 -319
- package/src/controller/model.controller.ts +0 -164
- package/src/controller/package.controller.spec.ts +0 -273
- package/src/controller/package.controller.ts +0 -306
- package/src/controller/query.controller.ts +0 -152
- package/src/controller/theme.controller.ts +0 -83
- package/src/controller/watch-mode.controller.ts +0 -243
- package/src/data_styles.ts +0 -71
- package/src/default-publisher.config.json +0 -23
- package/src/ducklake_version.spec.ts +0 -163
- package/src/ducklake_version.ts +0 -153
- package/src/errors.spec.ts +0 -119
- package/src/errors.ts +0 -237
- package/src/filter_deprecation.spec.ts +0 -64
- package/src/filter_deprecation.ts +0 -42
- package/src/health.spec.ts +0 -123
- package/src/health.ts +0 -233
- package/src/heap_check.spec.ts +0 -144
- package/src/heap_check.ts +0 -144
- package/src/instrumentation.ts +0 -214
- package/src/json_utils.spec.ts +0 -51
- package/src/json_utils.ts +0 -33
- package/src/logger.spec.ts +0 -210
- package/src/logger.ts +0 -251
- package/src/malloy_pin_prereqs.spec.ts +0 -25
- package/src/materialization_metrics.spec.ts +0 -169
- package/src/materialization_metrics.ts +0 -346
- package/src/mcp/error_messages.spec.ts +0 -35
- package/src/mcp/error_messages.ts +0 -183
- package/src/mcp/handler_utils.spec.ts +0 -108
- package/src/mcp/handler_utils.ts +0 -251
- package/src/mcp/mcp_constants.ts +0 -6
- package/src/mcp/query_envelope.spec.ts +0 -229
- package/src/mcp/query_envelope.ts +0 -240
- package/src/mcp/server.protocol.spec.ts +0 -250
- package/src/mcp/server.ts +0 -87
- package/src/mcp/skills/build_skills_bundle.spec.ts +0 -51
- package/src/mcp/skills/build_skills_bundle.ts +0 -176
- package/src/mcp/skills/skills_bundle.json +0 -1
- package/src/mcp/skills/skills_bundle.spec.ts +0 -171
- package/src/mcp/tool_response.spec.ts +0 -108
- package/src/mcp/tool_response.ts +0 -138
- package/src/mcp/tools/compile_tool.spec.ts +0 -315
- package/src/mcp/tools/compile_tool.ts +0 -208
- package/src/mcp/tools/docs_search/build_docs_index.ts +0 -132
- package/src/mcp/tools/docs_search/malloy_docs_index.json +0 -1
- package/src/mcp/tools/docs_search_tool.spec.ts +0 -32
- package/src/mcp/tools/docs_search_tool.ts +0 -128
- package/src/mcp/tools/embedding_index.spec.ts +0 -1236
- package/src/mcp/tools/embedding_index.ts +0 -808
- package/src/mcp/tools/execute_query_tool.spec.ts +0 -294
- package/src/mcp/tools/execute_query_tool.ts +0 -316
- package/src/mcp/tools/get_context_eval.ts +0 -275
- package/src/mcp/tools/get_context_tool.spec.ts +0 -592
- package/src/mcp/tools/get_context_tool.ts +0 -585
- package/src/mcp/tools/reload_package_tool.spec.ts +0 -232
- package/src/mcp/tools/reload_package_tool.ts +0 -132
- package/src/mcp_config.spec.ts +0 -919
- package/src/mcp_config.ts +0 -425
- package/src/oom_guards.integration.spec.ts +0 -269
- package/src/package_load/package_load_pool.spec.ts +0 -252
- package/src/package_load/package_load_pool.ts +0 -931
- package/src/package_load/package_load_worker.ts +0 -1034
- package/src/package_load/protocol.ts +0 -364
- package/src/package_load/rpc_wait_accountant.spec.ts +0 -109
- package/src/package_load/rpc_wait_accountant.ts +0 -76
- package/src/package_load_metrics.spec.ts +0 -114
- package/src/package_load_metrics.ts +0 -127
- package/src/path_safety.spec.ts +0 -158
- package/src/path_safety.ts +0 -146
- package/src/pg_helpers.spec.ts +0 -223
- package/src/pg_helpers.ts +0 -52
- package/src/query_cap_metrics.spec.ts +0 -89
- package/src/query_cap_metrics.ts +0 -116
- package/src/query_concurrency.spec.ts +0 -247
- package/src/query_concurrency.ts +0 -237
- package/src/query_metadata_metrics.ts +0 -49
- package/src/query_param_utils.ts +0 -29
- package/src/query_timeout.spec.ts +0 -224
- package/src/query_timeout.ts +0 -179
- package/src/runtime/publisher.js +0 -328
- package/src/server-old.ts +0 -988
- package/src/server.ts +0 -2059
- package/src/service/annotations.spec.ts +0 -118
- package/src/service/annotations.ts +0 -91
- package/src/service/authorize.spec.ts +0 -154
- package/src/service/authorize.ts +0 -498
- package/src/service/authorize_integration.spec.ts +0 -2041
- package/src/service/build_plan.spec.ts +0 -685
- package/src/service/build_plan.ts +0 -797
- package/src/service/compile_authorize.spec.ts +0 -85
- package/src/service/compile_fragment_techniques.spec.ts +0 -156
- package/src/service/connection.spec.ts +0 -2360
- package/src/service/connection.ts +0 -2187
- package/src/service/connection_config.spec.ts +0 -734
- package/src/service/connection_config.ts +0 -899
- package/src/service/connection_federation.spec.ts +0 -184
- package/src/service/connection_fingerprint.spec.ts +0 -102
- package/src/service/connection_service.spec.ts +0 -711
- package/src/service/connection_service.ts +0 -291
- package/src/service/cron_evaluator.spec.ts +0 -79
- package/src/service/cron_evaluator.ts +0 -105
- package/src/service/db_utils.spec.ts +0 -1085
- package/src/service/db_utils.ts +0 -1502
- package/src/service/duckdb_instance_isolation.spec.ts +0 -137
- package/src/service/ducklake_lazy_attach.spec.ts +0 -110
- package/src/service/embedding_provider.spec.ts +0 -329
- package/src/service/embedding_provider.ts +0 -236
- package/src/service/environment.ts +0 -2191
- package/src/service/environment_admission.spec.ts +0 -344
- package/src/service/environment_compile.spec.ts +0 -197
- package/src/service/environment_store.spec.ts +0 -2274
- package/src/service/environment_store.ts +0 -2434
- package/src/service/environment_store_anchoring.spec.ts +0 -107
- package/src/service/environment_store_clone.spec.ts +0 -350
- package/src/service/explore_visibility.spec.ts +0 -434
- package/src/service/exports_probe.spec.ts +0 -107
- package/src/service/extension_fetch_policy.spec.ts +0 -256
- package/src/service/filter.spec.ts +0 -458
- package/src/service/filter.ts +0 -341
- package/src/service/filter_bypass.spec.ts +0 -418
- package/src/service/filter_integration.spec.ts +0 -1004
- package/src/service/freshness.spec.ts +0 -183
- package/src/service/freshness.ts +0 -112
- package/src/service/gcs_s3_utils.ts +0 -427
- package/src/service/given.ts +0 -105
- package/src/service/givens_integration.spec.ts +0 -219
- package/src/service/manifest_loader.spec.ts +0 -189
- package/src/service/manifest_loader.ts +0 -156
- package/src/service/materialization_build_session.spec.ts +0 -435
- package/src/service/materialization_build_session.ts +0 -681
- package/src/service/materialization_config_validation.spec.ts +0 -99
- package/src/service/materialization_config_validation.ts +0 -120
- package/src/service/materialization_eligibility.spec.ts +0 -158
- package/src/service/materialization_eligibility.ts +0 -305
- package/src/service/materialization_schedule_surface.spec.ts +0 -256
- package/src/service/materialization_scheduler.spec.ts +0 -289
- package/src/service/materialization_scheduler.ts +0 -304
- package/src/service/materialization_serve_transform.spec.ts +0 -1003
- package/src/service/materialization_serve_transform.ts +0 -779
- package/src/service/materialization_service.spec.ts +0 -2373
- package/src/service/materialization_service.ts +0 -2342
- package/src/service/materialization_test_fixtures.ts +0 -249
- package/src/service/model.spec.ts +0 -1157
- package/src/service/model.ts +0 -3370
- package/src/service/model_limits.spec.ts +0 -209
- package/src/service/model_limits.ts +0 -131
- package/src/service/model_storage_serve.spec.ts +0 -193
- package/src/service/model_storage_serve_joins.spec.ts +0 -193
- package/src/service/package.spec.ts +0 -612
- package/src/service/package.ts +0 -1799
- package/src/service/package_manifest.spec.ts +0 -230
- package/src/service/package_manifest.ts +0 -241
- package/src/service/package_memory_governor.spec.ts +0 -173
- package/src/service/package_memory_governor.ts +0 -233
- package/src/service/package_quote_bound_tables.spec.ts +0 -171
- package/src/service/package_race.spec.ts +0 -208
- package/src/service/package_reload_safety.spec.ts +0 -193
- package/src/service/package_rollback.spec.ts +0 -190
- package/src/service/package_worker_path.spec.ts +0 -645
- package/src/service/path_injection.spec.ts +0 -39
- package/src/service/persist_annotation_validation.spec.ts +0 -89
- package/src/service/persist_annotation_validation.ts +0 -59
- package/src/service/persistence_policy.spec.ts +0 -374
- package/src/service/proxy.spec.ts +0 -414
- package/src/service/proxy.ts +0 -248
- package/src/service/query_boundary.spec.ts +0 -470
- package/src/service/query_metadata.spec.ts +0 -408
- package/src/service/query_metadata.ts +0 -492
- package/src/service/query_metadata_identity.spec.ts +0 -149
- package/src/service/query_text.spec.ts +0 -79
- package/src/service/query_text.ts +0 -43
- package/src/service/quoting.spec.ts +0 -135
- package/src/service/quoting.ts +0 -78
- package/src/service/resolve_environment.ts +0 -15
- package/src/service/restricted_mode.spec.ts +0 -299
- package/src/service/source_extraction.ts +0 -225
- package/src/service/theme_store.ts +0 -199
- package/src/storage/DatabaseInterface.ts +0 -198
- package/src/storage/StorageManager.ts +0 -113
- package/src/storage/duckdb/ConnectionRepository.ts +0 -143
- package/src/storage/duckdb/DuckDBConnection.ts +0 -123
- package/src/storage/duckdb/DuckDBRepository.ts +0 -208
- package/src/storage/duckdb/EnvironmentRepository.ts +0 -148
- package/src/storage/duckdb/MaterializationRepository.spec.ts +0 -191
- package/src/storage/duckdb/MaterializationRepository.ts +0 -336
- package/src/storage/duckdb/PackageRepository.ts +0 -135
- package/src/storage/duckdb/schema.ts +0 -229
- package/src/stream_helpers.spec.ts +0 -280
- package/src/stream_helpers.ts +0 -162
- package/src/telemetry.ts +0 -20
- package/src/test_helpers/metrics_harness.ts +0 -166
- package/src/theme_key_parity.spec.ts +0 -57
- package/src/utils.ts +0 -29
- package/tests/fixtures/authorize-compile/model.malloy +0 -9
- package/tests/fixtures/authorize-compile/publisher.json +0 -4
- package/tests/fixtures/html-pages-nopublic/model.malloy +0 -1
- package/tests/fixtures/html-pages-nopublic/publisher.json +0 -5
- package/tests/fixtures/html-pages-test/data.csv +0 -3
- package/tests/fixtures/html-pages-test/public/assets/app.css +0 -3
- package/tests/fixtures/html-pages-test/public/barehtml.html +0 -4
- package/tests/fixtures/html-pages-test/public/bodymeta.html +0 -8
- package/tests/fixtures/html-pages-test/public/data.json +0 -1
- package/tests/fixtures/html-pages-test/public/fitcomment.html +0 -11
- package/tests/fixtures/html-pages-test/public/index.html +0 -9
- package/tests/fixtures/html-pages-test/public/nohead.html +0 -8
- package/tests/fixtures/html-pages-test/public/notfit.html +0 -13
- package/tests/fixtures/html-pages-test/public/slides.html +0 -12
- package/tests/fixtures/html-pages-test/public/sub/page2.html +0 -9
- package/tests/fixtures/html-pages-test/public/unterminated.html +0 -10
- package/tests/fixtures/html-pages-test/publisher.json +0 -5
- package/tests/fixtures/html-pages-test/report.malloy +0 -1
- package/tests/fixtures/persist-multi-level/data/orders.csv +0 -5
- package/tests/fixtures/persist-multi-level/multi_level.malloy +0 -18
- package/tests/fixtures/persist-multi-level/publisher.json +0 -5
- package/tests/fixtures/persist-schedule-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-schedule-test/persist_schedule_test.malloy +0 -11
- package/tests/fixtures/persist-schedule-test/publisher.json +0 -7
- package/tests/fixtures/persist-schedule-warn-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-schedule-warn-test/persist_warn_test.malloy +0 -11
- package/tests/fixtures/persist-schedule-warn-test/publisher.json +0 -6
- package/tests/fixtures/persist-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-test/persist_test.malloy +0 -11
- package/tests/fixtures/persist-test/publisher.json +0 -5
- package/tests/fixtures/publisher.config.json +0 -15
- package/tests/fixtures/query-givens/data/orders.csv +0 -7
- package/tests/fixtures/query-givens/model.malloy +0 -34
- package/tests/fixtures/query-givens/publisher.json +0 -5
- package/tests/fixtures/xlsx/database.xlsx +0 -0
- package/tests/harness/mcp_test_setup.ts +0 -332
- package/tests/harness/rest_e2e.ts +0 -68
- package/tests/integration/authorize/compile_authorize_http.integration.spec.ts +0 -92
- package/tests/integration/concurrent_package/concurrent_package.integration.spec.ts +0 -280
- package/tests/integration/duckdb_storage/duckdb_storage.integration.spec.ts +0 -138
- package/tests/integration/first_boot/readiness_line.integration.spec.ts +0 -177
- package/tests/integration/html_pages/html_pages.integration.spec.ts +0 -440
- package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +0 -259
- package/tests/integration/materialization/environment_list.integration.spec.ts +0 -133
- package/tests/integration/materialization/freshness_gate.integration.spec.ts +0 -292
- package/tests/integration/materialization/manifest_binding.integration.spec.ts +0 -430
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -441
- package/tests/integration/materialization/orchestrated_rebind.integration.spec.ts +0 -150
- package/tests/integration/materialization/reference_manifest.integration.spec.ts +0 -251
- package/tests/integration/materialization/schedule_edit.integration.spec.ts +0 -126
- package/tests/integration/materialization/scheduler.integration.spec.ts +0 -200
- package/tests/integration/materialization/scheduler_recovery.integration.spec.ts +0 -172
- package/tests/integration/materialization/scheduler_transitions.integration.spec.ts +0 -256
- package/tests/integration/materialization/scheduler_wiring.integration.spec.ts +0 -143
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -438
- package/tests/integration/mcp/mcp_get_context_semantic.integration.spec.ts +0 -235
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -98
- package/tests/integration/query_givens/query_givens.integration.spec.ts +0 -146
- package/tests/integration/query_givens/query_givens_authorize.integration.spec.ts +0 -121
- package/tests/integration/sdk_givens/sdk_givens.integration.spec.ts +0 -110
- package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -421
- package/tests/unit/controller/theme.controller.spec.ts +0 -141
- package/tests/unit/duckdb/attached_databases.test.ts +0 -1243
- package/tests/unit/duckdb/duckdb_connection.test.ts +0 -181
- package/tests/unit/duckdb/legacy_schema_migration.test.ts +0 -197
- package/tests/unit/duckdb/repositories.test.ts +0 -208
- package/tests/unit/ducklake/ducklake.test.ts +0 -952
- package/tests/unit/service/theme_store.spec.ts +0 -139
- package/tsconfig.json +0 -32
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { Package } from "../DatabaseInterface";
|
|
2
|
-
import { DuckDBConnection } from "./DuckDBConnection";
|
|
3
|
-
|
|
4
|
-
export class PackageRepository {
|
|
5
|
-
constructor(private db: DuckDBConnection) {}
|
|
6
|
-
|
|
7
|
-
private generateId(): string {
|
|
8
|
-
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
private now(): Date {
|
|
12
|
-
return new Date();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async listPackages(environmentId: string): Promise<Package[]> {
|
|
16
|
-
const rows = await this.db.all<Record<string, unknown>>(
|
|
17
|
-
"SELECT * FROM packages WHERE environment_id = ? ORDER BY name",
|
|
18
|
-
[environmentId],
|
|
19
|
-
);
|
|
20
|
-
return rows.map(this.mapToPackage);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async getPackageById(id: string): Promise<Package | null> {
|
|
24
|
-
const row = await this.db.get<Record<string, unknown>>(
|
|
25
|
-
"SELECT * FROM packages WHERE id = ?",
|
|
26
|
-
[id],
|
|
27
|
-
);
|
|
28
|
-
return row ? this.mapToPackage(row) : null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async getPackageByName(
|
|
32
|
-
environmentId: string,
|
|
33
|
-
name: string,
|
|
34
|
-
): Promise<Package | null> {
|
|
35
|
-
const row = await this.db.get<Record<string, unknown>>(
|
|
36
|
-
"SELECT * FROM packages WHERE environment_id = ? AND name = ?",
|
|
37
|
-
[environmentId, name],
|
|
38
|
-
);
|
|
39
|
-
return row ? this.mapToPackage(row) : null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async createPackage(
|
|
43
|
-
pkg: Omit<Package, "id" | "createdAt" | "updatedAt">,
|
|
44
|
-
): Promise<Package> {
|
|
45
|
-
const id = this.generateId();
|
|
46
|
-
const now = this.now();
|
|
47
|
-
|
|
48
|
-
await this.db.run(
|
|
49
|
-
`INSERT INTO packages (id, environment_id, name, description, manifest_path, metadata, created_at, updated_at)
|
|
50
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
51
|
-
[
|
|
52
|
-
id,
|
|
53
|
-
pkg.environmentId,
|
|
54
|
-
pkg.name,
|
|
55
|
-
pkg.description || null,
|
|
56
|
-
pkg.manifestPath,
|
|
57
|
-
pkg.metadata ? JSON.stringify(pkg.metadata) : null,
|
|
58
|
-
now.toISOString(),
|
|
59
|
-
now.toISOString(),
|
|
60
|
-
],
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
id,
|
|
65
|
-
...pkg,
|
|
66
|
-
createdAt: now,
|
|
67
|
-
updatedAt: now,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async updatePackage(
|
|
72
|
-
id: string,
|
|
73
|
-
updates: Partial<Package>,
|
|
74
|
-
): Promise<Package> {
|
|
75
|
-
const existing = await this.getPackageById(id);
|
|
76
|
-
if (!existing) {
|
|
77
|
-
throw new Error(`Package with id ${id} not found`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const now = this.now();
|
|
81
|
-
const setClauses: string[] = [];
|
|
82
|
-
const params: unknown[] = [];
|
|
83
|
-
|
|
84
|
-
if (updates.name !== undefined) {
|
|
85
|
-
setClauses.push(`name = ?`);
|
|
86
|
-
params.push(updates.name);
|
|
87
|
-
}
|
|
88
|
-
if (updates.description !== undefined) {
|
|
89
|
-
setClauses.push(`description = ?`);
|
|
90
|
-
params.push(updates.description);
|
|
91
|
-
}
|
|
92
|
-
if (updates.manifestPath !== undefined) {
|
|
93
|
-
setClauses.push(`manifest_path = ?`);
|
|
94
|
-
params.push(updates.manifestPath);
|
|
95
|
-
}
|
|
96
|
-
if (updates.metadata !== undefined) {
|
|
97
|
-
setClauses.push(`metadata = ?`);
|
|
98
|
-
params.push(JSON.stringify(updates.metadata));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
setClauses.push(`updated_at = ?`);
|
|
102
|
-
params.push(now.toISOString());
|
|
103
|
-
params.push(id);
|
|
104
|
-
|
|
105
|
-
await this.db.run(
|
|
106
|
-
`UPDATE packages SET ${setClauses.join(", ")} WHERE id = ?`,
|
|
107
|
-
params,
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
return this.getPackageById(id) as Promise<Package>;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async deletePackage(id: string): Promise<void> {
|
|
114
|
-
await this.db.run("DELETE FROM packages WHERE id = ?", [id]);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async deletePackagesByEnvironmentId(id: string): Promise<void> {
|
|
118
|
-
await this.db.run("DELETE FROM packages WHERE environment_id = ?", [id]);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private mapToPackage(row: Record<string, unknown>): Package {
|
|
122
|
-
return {
|
|
123
|
-
id: row.id as string,
|
|
124
|
-
environmentId: row.environment_id as string,
|
|
125
|
-
name: row.name as string,
|
|
126
|
-
description: row.description as string | undefined,
|
|
127
|
-
manifestPath: row.manifest_path as string,
|
|
128
|
-
metadata: row.metadata
|
|
129
|
-
? JSON.parse(row.metadata as string)
|
|
130
|
-
: undefined,
|
|
131
|
-
createdAt: new Date(row.created_at as string),
|
|
132
|
-
updatedAt: new Date(row.updated_at as string),
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
}
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { logger } from "../../logger";
|
|
2
|
-
import { DuckDBConnection } from "./DuckDBConnection";
|
|
3
|
-
|
|
4
|
-
export async function initializeSchema(
|
|
5
|
-
db: DuckDBConnection,
|
|
6
|
-
force: boolean = false,
|
|
7
|
-
): Promise<void> {
|
|
8
|
-
const initialized = await db.isInitialized();
|
|
9
|
-
|
|
10
|
-
if (force) {
|
|
11
|
-
logger.info(
|
|
12
|
-
"Reinitializing database schema dropping and recreating all tables",
|
|
13
|
-
);
|
|
14
|
-
await dropAllTables(db);
|
|
15
|
-
} else if (!initialized) {
|
|
16
|
-
// TODO: Remove this during projects cleanup
|
|
17
|
-
// If a pre-rename `projects` schema is on disk, the new
|
|
18
|
-
// CREATE TABLE IF NOT EXISTS pass below would silently leave child
|
|
19
|
-
// tables on the old `project_id` column and the first query against
|
|
20
|
-
// `environment_id` would crash. Drop the legacy tables (with a loud
|
|
21
|
-
// warning) so the fresh schema can be created cleanly. This is
|
|
22
|
-
// destructive: operators upgrading should re-create their environments
|
|
23
|
-
// and packages via the API after the upgrade.
|
|
24
|
-
await dropLegacyProjectSchema(db);
|
|
25
|
-
logger.info("Creating database schema for the first time...");
|
|
26
|
-
}
|
|
27
|
-
// Always fall through to the CREATE TABLE IF NOT EXISTS pass below.
|
|
28
|
-
// The statements are idempotent and let an already-initialized DB pick
|
|
29
|
-
// up new tables added in later builds (e.g., the `themes` table for
|
|
30
|
-
// the in-app Theme Editor) without forcing operators to run --init
|
|
31
|
-
// and lose their existing environments / packages / materializations.
|
|
32
|
-
|
|
33
|
-
// Environments table
|
|
34
|
-
await db.run(`
|
|
35
|
-
CREATE TABLE IF NOT EXISTS environments (
|
|
36
|
-
id VARCHAR PRIMARY KEY,
|
|
37
|
-
name VARCHAR NOT NULL UNIQUE,
|
|
38
|
-
path VARCHAR NOT NULL,
|
|
39
|
-
description VARCHAR,
|
|
40
|
-
metadata JSON,
|
|
41
|
-
created_at TIMESTAMP NOT NULL,
|
|
42
|
-
updated_at TIMESTAMP NOT NULL
|
|
43
|
-
)
|
|
44
|
-
`);
|
|
45
|
-
|
|
46
|
-
// Packages table
|
|
47
|
-
await db.run(`
|
|
48
|
-
CREATE TABLE IF NOT EXISTS packages (
|
|
49
|
-
id VARCHAR PRIMARY KEY,
|
|
50
|
-
environment_id VARCHAR NOT NULL,
|
|
51
|
-
name VARCHAR NOT NULL,
|
|
52
|
-
description VARCHAR,
|
|
53
|
-
manifest_path VARCHAR NOT NULL,
|
|
54
|
-
metadata JSON,
|
|
55
|
-
created_at TIMESTAMP NOT NULL,
|
|
56
|
-
updated_at TIMESTAMP NOT NULL,
|
|
57
|
-
FOREIGN KEY (environment_id) REFERENCES environments(id),
|
|
58
|
-
UNIQUE (environment_id, name)
|
|
59
|
-
)
|
|
60
|
-
`);
|
|
61
|
-
|
|
62
|
-
// Connections table
|
|
63
|
-
await db.run(`
|
|
64
|
-
CREATE TABLE IF NOT EXISTS connections (
|
|
65
|
-
id VARCHAR PRIMARY KEY,
|
|
66
|
-
environment_id VARCHAR NOT NULL,
|
|
67
|
-
name VARCHAR NOT NULL,
|
|
68
|
-
type VARCHAR NOT NULL,
|
|
69
|
-
config JSON NOT NULL,
|
|
70
|
-
created_at TIMESTAMP NOT NULL,
|
|
71
|
-
updated_at TIMESTAMP NOT NULL,
|
|
72
|
-
FOREIGN KEY (environment_id) REFERENCES environments(id),
|
|
73
|
-
UNIQUE (environment_id, name)
|
|
74
|
-
)
|
|
75
|
-
`);
|
|
76
|
-
|
|
77
|
-
// Materializations table.
|
|
78
|
-
//
|
|
79
|
-
// `active_key` enforces at-most-one active (non-terminal) materialization
|
|
80
|
-
// per (environment, package) at the DB layer. It is set to
|
|
81
|
-
// `{environment_id}|{package_name}` while the row is active and cleared
|
|
82
|
-
// to NULL on transition to any terminal state. A unique index on
|
|
83
|
-
// `active_key` (see below) makes the insert-then-check race impossible —
|
|
84
|
-
// a second concurrent create fails with a constraint violation, which the
|
|
85
|
-
// service layer translates to `MaterializationConflictError`.
|
|
86
|
-
// `manifest` is a JSON blob holding the build output returned inline on the
|
|
87
|
-
// resource.
|
|
88
|
-
await db.run(`
|
|
89
|
-
CREATE TABLE IF NOT EXISTS materializations (
|
|
90
|
-
id VARCHAR PRIMARY KEY,
|
|
91
|
-
environment_id VARCHAR NOT NULL,
|
|
92
|
-
package_name VARCHAR NOT NULL,
|
|
93
|
-
status VARCHAR NOT NULL,
|
|
94
|
-
active_key VARCHAR,
|
|
95
|
-
started_at TIMESTAMP,
|
|
96
|
-
completed_at TIMESTAMP,
|
|
97
|
-
error TEXT,
|
|
98
|
-
metadata JSON,
|
|
99
|
-
manifest JSON,
|
|
100
|
-
created_at TIMESTAMP NOT NULL,
|
|
101
|
-
updated_at TIMESTAMP NOT NULL,
|
|
102
|
-
FOREIGN KEY (environment_id) REFERENCES environments(id)
|
|
103
|
-
)
|
|
104
|
-
`);
|
|
105
|
-
|
|
106
|
-
// Themes table.
|
|
107
|
-
//
|
|
108
|
-
// Singleton storage for the instance-wide theme edited by the in-app
|
|
109
|
-
// Theme Editor. At most one row (id = "default"); we use INSERT ON
|
|
110
|
-
// CONFLICT to keep the table effectively a key/value cell.
|
|
111
|
-
//
|
|
112
|
-
// publisher.config.json's optional `theme` block is a BOOT SEED only —
|
|
113
|
-
// on first run, if this table is empty, ThemeStore writes the seed
|
|
114
|
-
// here. Subsequent reads come from this table so the editor's saves
|
|
115
|
-
// win over the file.
|
|
116
|
-
await db.run(`
|
|
117
|
-
CREATE TABLE IF NOT EXISTS themes (
|
|
118
|
-
id VARCHAR PRIMARY KEY,
|
|
119
|
-
payload JSON NOT NULL,
|
|
120
|
-
created_at TIMESTAMP NOT NULL,
|
|
121
|
-
updated_at TIMESTAMP NOT NULL
|
|
122
|
-
)
|
|
123
|
-
`);
|
|
124
|
-
|
|
125
|
-
await createEntityEmbeddingsTable(db);
|
|
126
|
-
|
|
127
|
-
// Create indexes for better query performance
|
|
128
|
-
await db.run(
|
|
129
|
-
"CREATE INDEX IF NOT EXISTS idx_packages_environment_id ON packages(environment_id)",
|
|
130
|
-
);
|
|
131
|
-
await db.run(
|
|
132
|
-
"CREATE INDEX IF NOT EXISTS idx_connections_environment_id ON connections(environment_id)",
|
|
133
|
-
);
|
|
134
|
-
await db.run(
|
|
135
|
-
"CREATE INDEX IF NOT EXISTS idx_materializations_environment_package ON materializations(environment_id, package_name)",
|
|
136
|
-
);
|
|
137
|
-
await db.run(
|
|
138
|
-
"CREATE UNIQUE INDEX IF NOT EXISTS idx_materializations_active_key ON materializations(active_key)",
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Vector cache for semantic `malloy_getContext` retrieval (see
|
|
144
|
-
* mcp/tools/embedding_index.ts). One row per discoverable model entity;
|
|
145
|
-
* the primary key mirrors the tool's in-memory dedup key. Rows are
|
|
146
|
-
* content-addressed: the index sync compares `content_hash` (over the
|
|
147
|
-
* embedded text) plus `embedding_model` and re-embeds on mismatch, while
|
|
148
|
-
* a dimensionality change is detected at query time by the stale-row
|
|
149
|
-
* heal, so staleness always self-corrects and wiping the table only
|
|
150
|
-
* ever costs re-embedding. `embedding` is a LIST column searched with
|
|
151
|
-
* list_cosine_similarity; at the entity counts a single Publisher
|
|
152
|
-
* serves, a brute-force scan is faster and simpler than a vector-index
|
|
153
|
-
* extension.
|
|
154
|
-
*/
|
|
155
|
-
export async function createEntityEmbeddingsTable(
|
|
156
|
-
db: DuckDBConnection,
|
|
157
|
-
): Promise<void> {
|
|
158
|
-
await db.run(`
|
|
159
|
-
CREATE TABLE IF NOT EXISTS entity_embeddings (
|
|
160
|
-
environment_name VARCHAR NOT NULL,
|
|
161
|
-
package_name VARCHAR NOT NULL,
|
|
162
|
-
entity_kind VARCHAR NOT NULL,
|
|
163
|
-
entity_source VARCHAR NOT NULL,
|
|
164
|
-
entity_name VARCHAR NOT NULL,
|
|
165
|
-
model_path VARCHAR NOT NULL,
|
|
166
|
-
content_hash VARCHAR NOT NULL,
|
|
167
|
-
embedding_model VARCHAR NOT NULL,
|
|
168
|
-
dims INTEGER NOT NULL,
|
|
169
|
-
embedding FLOAT[] NOT NULL,
|
|
170
|
-
updated_at TIMESTAMP NOT NULL,
|
|
171
|
-
PRIMARY KEY (environment_name, package_name, entity_kind, entity_source, entity_name)
|
|
172
|
-
)
|
|
173
|
-
`);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// TODO: Remove this during projects cleanup
|
|
177
|
-
// Tables in the pre-rename schema, listed children-first so DROP order
|
|
178
|
-
// satisfies foreign-key dependencies on the legacy `projects` table.
|
|
179
|
-
const LEGACY_TABLES_DROP_ORDER = [
|
|
180
|
-
"build_manifests",
|
|
181
|
-
"materializations",
|
|
182
|
-
"packages",
|
|
183
|
-
"connections",
|
|
184
|
-
"projects",
|
|
185
|
-
] as const;
|
|
186
|
-
|
|
187
|
-
async function dropLegacyProjectSchema(db: DuckDBConnection): Promise<void> {
|
|
188
|
-
const legacy = await db.all<{ name: string }>(
|
|
189
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name='projects'",
|
|
190
|
-
);
|
|
191
|
-
if (!legacy || legacy.length === 0) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
logger.warn(
|
|
196
|
-
"Detected legacy 'projects' schema. Dropping legacy tables; existing environments/packages/connections/materializations data will be lost. Re-create them via the API after upgrade.",
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
for (const table of LEGACY_TABLES_DROP_ORDER) {
|
|
200
|
-
try {
|
|
201
|
-
await db.run(`DROP TABLE IF EXISTS ${table}`);
|
|
202
|
-
} catch (err) {
|
|
203
|
-
logger.warn(`Failed to drop legacy table ${table}:`, err);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async function dropAllTables(db: DuckDBConnection): Promise<void> {
|
|
209
|
-
const tables = [
|
|
210
|
-
"build_manifests",
|
|
211
|
-
"materializations",
|
|
212
|
-
"packages",
|
|
213
|
-
"connections",
|
|
214
|
-
"environments",
|
|
215
|
-
"themes",
|
|
216
|
-
"entity_embeddings",
|
|
217
|
-
];
|
|
218
|
-
|
|
219
|
-
logger.info("Dropping tables:", tables.join(", "));
|
|
220
|
-
|
|
221
|
-
for (const table of tables) {
|
|
222
|
-
try {
|
|
223
|
-
await db.run(`DROP TABLE IF EXISTS ${table} `);
|
|
224
|
-
logger.info(`Dropped table: ${table}`);
|
|
225
|
-
} catch (err) {
|
|
226
|
-
logger.warn(` Warning: Could not drop table ${table}:`, err);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
QueryRecord,
|
|
3
|
-
RunSQLOptions,
|
|
4
|
-
StreamingConnection,
|
|
5
|
-
} from "@malloydata/malloy";
|
|
6
|
-
import { describe, expect, it } from "bun:test";
|
|
7
|
-
|
|
8
|
-
import { PayloadTooLargeError } from "./errors";
|
|
9
|
-
import { isStreamingConnection, streamSqlWithBudget } from "./stream_helpers";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Build a fake StreamingConnection backed by a fixed row array. The
|
|
13
|
-
* fake records the abort signal so tests can confirm
|
|
14
|
-
* `streamSqlWithBudget` actually signaled the driver to stop —
|
|
15
|
-
* client-side counting alone is not enough; the whole point of the
|
|
16
|
-
* helper is to terminate fetching early.
|
|
17
|
-
*/
|
|
18
|
-
function fakeStreamingConnection(opts: {
|
|
19
|
-
rows: QueryRecord[];
|
|
20
|
-
/**
|
|
21
|
-
* When true (the default), the fake honors `RunSQLOptions.rowLimit`
|
|
22
|
-
* by slicing — same as real Postgres/DuckDB. Set false to drive
|
|
23
|
-
* the helper's own overflow detection (the (cap+1)-th row check).
|
|
24
|
-
*/
|
|
25
|
-
honorRowLimit?: boolean;
|
|
26
|
-
/**
|
|
27
|
-
* Test hook: set to `true` when the fake observes `signal.aborted`
|
|
28
|
-
* flip via the listener it installed at the start of streaming.
|
|
29
|
-
*/
|
|
30
|
-
abortObserved?: { value: boolean };
|
|
31
|
-
/**
|
|
32
|
-
* Test hook: captures the options the helper passed to
|
|
33
|
-
* `runSQLStream`, so tests can assert it preserved caller-supplied
|
|
34
|
-
* `rowLimit` (and the abortSignal it wired in).
|
|
35
|
-
*/
|
|
36
|
-
capturedOptions?: { value: RunSQLOptions | undefined };
|
|
37
|
-
}): StreamingConnection {
|
|
38
|
-
const { rows, honorRowLimit = true, abortObserved, capturedOptions } = opts;
|
|
39
|
-
return {
|
|
40
|
-
canStream(): true {
|
|
41
|
-
return true;
|
|
42
|
-
},
|
|
43
|
-
async *runSQLStream(
|
|
44
|
-
_sql: string,
|
|
45
|
-
options?: RunSQLOptions,
|
|
46
|
-
): AsyncIterableIterator<QueryRecord> {
|
|
47
|
-
if (capturedOptions) capturedOptions.value = options;
|
|
48
|
-
if (abortObserved) {
|
|
49
|
-
options?.abortSignal?.addEventListener("abort", () => {
|
|
50
|
-
abortObserved.value = true;
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
const limit =
|
|
54
|
-
honorRowLimit && typeof options?.rowLimit === "number"
|
|
55
|
-
? options.rowLimit
|
|
56
|
-
: rows.length;
|
|
57
|
-
for (let i = 0; i < Math.min(rows.length, limit); i += 1) {
|
|
58
|
-
yield rows[i];
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
} as unknown as StreamingConnection;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
describe("isStreamingConnection", () => {
|
|
65
|
-
it("returns true for a connection whose canStream() returns true", () => {
|
|
66
|
-
const conn = fakeStreamingConnection({ rows: [] });
|
|
67
|
-
expect(isStreamingConnection(conn)).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("returns false for a connection without canStream", () => {
|
|
71
|
-
expect(isStreamingConnection({} as never)).toBe(false);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("returns false for a connection whose canStream() returns false", () => {
|
|
75
|
-
const conn = {
|
|
76
|
-
canStream() {
|
|
77
|
-
return false;
|
|
78
|
-
},
|
|
79
|
-
} as never;
|
|
80
|
-
expect(isStreamingConnection(conn)).toBe(false);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe("streamSqlWithBudget", () => {
|
|
85
|
-
it("returns all rows when both budgets are comfortably above the stream", async () => {
|
|
86
|
-
const rows: QueryRecord[] = [{ a: 1 }, { a: 2 }, { a: 3 }];
|
|
87
|
-
const conn = fakeStreamingConnection({ rows });
|
|
88
|
-
const result = await streamSqlWithBudget(
|
|
89
|
-
conn,
|
|
90
|
-
"SELECT a FROM t",
|
|
91
|
-
{ rowLimit: 10 },
|
|
92
|
-
{ maxRows: 10, maxBytes: 1_000_000 },
|
|
93
|
-
);
|
|
94
|
-
expect(result.rows).toEqual(rows);
|
|
95
|
-
expect(result.totalRows).toBe(3);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("forwards caller-supplied runSQLOptions (rowLimit) to the driver", async () => {
|
|
99
|
-
const captured = { value: undefined as RunSQLOptions | undefined };
|
|
100
|
-
const conn = fakeStreamingConnection({
|
|
101
|
-
rows: [{ a: 1 }, { a: 2 }],
|
|
102
|
-
capturedOptions: captured,
|
|
103
|
-
});
|
|
104
|
-
await streamSqlWithBudget(
|
|
105
|
-
conn,
|
|
106
|
-
"SELECT 1",
|
|
107
|
-
{ rowLimit: 6 },
|
|
108
|
-
{ maxRows: 5, maxBytes: 0 },
|
|
109
|
-
);
|
|
110
|
-
expect(captured.value?.rowLimit).toBe(6);
|
|
111
|
-
// abortSignal must be wired in so the helper can abort the
|
|
112
|
-
// iterator on overflow.
|
|
113
|
-
expect(captured.value?.abortSignal).toBeDefined();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("composes the caller-supplied abortSignal with its internal cap-abort signal", async () => {
|
|
117
|
-
// Step 5: the caller's signal is the query timeout. Composing
|
|
118
|
-
// both sources means EITHER an external timeout OR an internal
|
|
119
|
-
// cap overflow terminates the iterator. The combined signal
|
|
120
|
-
// must be a fresh AbortSignal (not either input by reference);
|
|
121
|
-
// aborting either input must mark the composed signal aborted.
|
|
122
|
-
const captured = { value: undefined as RunSQLOptions | undefined };
|
|
123
|
-
const callerAc = new AbortController();
|
|
124
|
-
const conn = fakeStreamingConnection({
|
|
125
|
-
rows: [{ a: 1 }],
|
|
126
|
-
capturedOptions: captured,
|
|
127
|
-
});
|
|
128
|
-
await streamSqlWithBudget(
|
|
129
|
-
conn,
|
|
130
|
-
"SELECT 1",
|
|
131
|
-
{ rowLimit: 10, abortSignal: callerAc.signal },
|
|
132
|
-
{ maxRows: 5, maxBytes: 0 },
|
|
133
|
-
);
|
|
134
|
-
const observed = captured.value?.abortSignal;
|
|
135
|
-
expect(observed).toBeInstanceOf(AbortSignal);
|
|
136
|
-
// Composed signal is a new object (`AbortSignal.any` returns a
|
|
137
|
-
// fresh signal), not the caller's signal by reference.
|
|
138
|
-
expect(observed).not.toBe(callerAc.signal);
|
|
139
|
-
expect(observed?.aborted).toBe(false);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it("composed signal aborts when the caller's signal aborts", async () => {
|
|
143
|
-
// Drive the external (caller) signal manually and confirm the
|
|
144
|
-
// composed signal that reached the driver tracks it. This is the
|
|
145
|
-
// half of composition that runWithQueryTimeout depends on for
|
|
146
|
-
// 504 to actually cancel an in-flight query.
|
|
147
|
-
if (typeof AbortSignal.any !== "function") return;
|
|
148
|
-
const captured = { value: undefined as RunSQLOptions | undefined };
|
|
149
|
-
const callerAc = new AbortController();
|
|
150
|
-
const conn = fakeStreamingConnection({
|
|
151
|
-
rows: [{ a: 1 }],
|
|
152
|
-
capturedOptions: captured,
|
|
153
|
-
});
|
|
154
|
-
await streamSqlWithBudget(
|
|
155
|
-
conn,
|
|
156
|
-
"SELECT 1",
|
|
157
|
-
{ rowLimit: 10, abortSignal: callerAc.signal },
|
|
158
|
-
{ maxRows: 5, maxBytes: 0 },
|
|
159
|
-
);
|
|
160
|
-
const observed = captured.value?.abortSignal;
|
|
161
|
-
expect(observed?.aborted).toBe(false);
|
|
162
|
-
callerAc.abort();
|
|
163
|
-
expect(observed?.aborted).toBe(true);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("throws PayloadTooLargeError and aborts the iterator on the (cap+1)-th row", async () => {
|
|
167
|
-
const rows: QueryRecord[] = [
|
|
168
|
-
{ a: 1 },
|
|
169
|
-
{ a: 2 },
|
|
170
|
-
{ a: 3 },
|
|
171
|
-
{ a: 4 },
|
|
172
|
-
{ a: 5 },
|
|
173
|
-
];
|
|
174
|
-
const abortObserved = { value: false };
|
|
175
|
-
const conn = fakeStreamingConnection({
|
|
176
|
-
rows,
|
|
177
|
-
abortObserved,
|
|
178
|
-
honorRowLimit: false,
|
|
179
|
-
});
|
|
180
|
-
await expect(
|
|
181
|
-
streamSqlWithBudget(
|
|
182
|
-
conn,
|
|
183
|
-
"SELECT a FROM t",
|
|
184
|
-
{},
|
|
185
|
-
{ maxRows: 2, maxBytes: 1_000_000 },
|
|
186
|
-
),
|
|
187
|
-
).rejects.toBeInstanceOf(PayloadTooLargeError);
|
|
188
|
-
await expect(
|
|
189
|
-
streamSqlWithBudget(
|
|
190
|
-
conn,
|
|
191
|
-
"SELECT a FROM t",
|
|
192
|
-
{},
|
|
193
|
-
{ maxRows: 2, maxBytes: 1_000_000 },
|
|
194
|
-
),
|
|
195
|
-
).rejects.toThrow("more than 2 rows");
|
|
196
|
-
// The helper must have fired the abort signal so a real driver
|
|
197
|
-
// (pg-query-stream / duckdb) would stop producing rows server-
|
|
198
|
-
// side, not just be discarded client-side.
|
|
199
|
-
expect(abortObserved.value).toBe(true);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("throws PayloadTooLargeError when summed JSON byte size exceeds the cap", async () => {
|
|
203
|
-
const big = "x".repeat(40);
|
|
204
|
-
const rows: QueryRecord[] = [{ s: big }, { s: big }, { s: big }];
|
|
205
|
-
const abortObserved = { value: false };
|
|
206
|
-
const conn = fakeStreamingConnection({
|
|
207
|
-
rows,
|
|
208
|
-
abortObserved,
|
|
209
|
-
honorRowLimit: false,
|
|
210
|
-
});
|
|
211
|
-
await expect(
|
|
212
|
-
streamSqlWithBudget(
|
|
213
|
-
conn,
|
|
214
|
-
"SELECT s FROM t",
|
|
215
|
-
{},
|
|
216
|
-
{ maxRows: 100, maxBytes: 60 },
|
|
217
|
-
),
|
|
218
|
-
).rejects.toThrow("exceeded 60 bytes");
|
|
219
|
-
expect(abortObserved.value).toBe(true);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it("returns all rows when the byte cap is disabled (maxBytes = 0)", async () => {
|
|
223
|
-
const big = "x".repeat(10_000);
|
|
224
|
-
const rows: QueryRecord[] = Array.from({ length: 3 }, () => ({ s: big }));
|
|
225
|
-
const conn = fakeStreamingConnection({ rows, honorRowLimit: false });
|
|
226
|
-
const result = await streamSqlWithBudget(
|
|
227
|
-
conn,
|
|
228
|
-
"SELECT s FROM t",
|
|
229
|
-
{},
|
|
230
|
-
{ maxRows: 100, maxBytes: 0 },
|
|
231
|
-
);
|
|
232
|
-
expect(result.rows.length).toBe(3);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
it("returns all rows when the row cap is disabled (maxRows = 0)", async () => {
|
|
236
|
-
const rows: QueryRecord[] = Array.from({ length: 50 }, (_, i) => ({
|
|
237
|
-
a: i,
|
|
238
|
-
}));
|
|
239
|
-
const conn = fakeStreamingConnection({ rows, honorRowLimit: false });
|
|
240
|
-
const result = await streamSqlWithBudget(
|
|
241
|
-
conn,
|
|
242
|
-
"SELECT a FROM t",
|
|
243
|
-
{},
|
|
244
|
-
{ maxRows: 0, maxBytes: 1_000_000 },
|
|
245
|
-
);
|
|
246
|
-
expect(result.rows.length).toBe(50);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it("returns rows when count equals the cap exactly (not an overflow)", async () => {
|
|
250
|
-
const rows: QueryRecord[] = [{ a: 1 }, { a: 2 }, { a: 3 }];
|
|
251
|
-
const conn = fakeStreamingConnection({ rows, honorRowLimit: false });
|
|
252
|
-
const result = await streamSqlWithBudget(
|
|
253
|
-
conn,
|
|
254
|
-
"SELECT a FROM t",
|
|
255
|
-
{},
|
|
256
|
-
{ maxRows: 3, maxBytes: 1_000_000 },
|
|
257
|
-
);
|
|
258
|
-
expect(result.rows.length).toBe(3);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it("re-throws non-overflow errors from the driver", async () => {
|
|
262
|
-
const conn = {
|
|
263
|
-
canStream(): true {
|
|
264
|
-
return true;
|
|
265
|
-
},
|
|
266
|
-
// eslint-disable-next-line require-yield
|
|
267
|
-
async *runSQLStream(): AsyncIterableIterator<QueryRecord> {
|
|
268
|
-
throw new Error("connection reset");
|
|
269
|
-
},
|
|
270
|
-
} as unknown as StreamingConnection;
|
|
271
|
-
await expect(
|
|
272
|
-
streamSqlWithBudget(
|
|
273
|
-
conn,
|
|
274
|
-
"SELECT 1",
|
|
275
|
-
{},
|
|
276
|
-
{ maxRows: 10, maxBytes: 1_000 },
|
|
277
|
-
),
|
|
278
|
-
).rejects.toThrow("connection reset");
|
|
279
|
-
});
|
|
280
|
-
});
|