@workglow/test-contract 0.6.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/README.md +232 -0
- package/dist/ai-provider/assertions/capabilityHonesty.d.ts +7 -0
- package/dist/ai-provider/assertions/dispose.d.ts +7 -0
- package/dist/ai-provider/assertions/inferAdvertisesRegistered.d.ts +15 -0
- package/dist/ai-provider/assertions/inferServesInferred.d.ts +20 -0
- package/dist/ai-provider/assertions/pricingMatchesModality.d.ts +36 -0
- package/dist/ai-provider/assertions/registryCoverage.d.ts +13 -0
- package/dist/ai-provider/assertions/sessionReuse.d.ts +7 -0
- package/dist/ai-provider/assertions/signalHonoring.d.ts +7 -0
- package/dist/ai-provider/assertions/structuredGeneration.d.ts +7 -0
- package/dist/ai-provider/assertions/textGenerationSmoke.d.ts +7 -0
- package/dist/ai-provider/assertions/toolCallAccumulator.d.ts +7 -0
- package/dist/ai-provider/assertions/toolCallMultiTurn.d.ts +7 -0
- package/dist/ai-provider/assertions/usageNormalization.d.ts +61 -0
- package/dist/ai-provider/fixtures.d.ts +8 -0
- package/dist/ai-provider/runAiProviderConformance.d.ts +7 -0
- package/dist/ai-provider/types.d.ts +75 -0
- package/dist/ai-provider.d.ts +25 -0
- package/dist/ai-provider.js +876 -0
- package/dist/ai-provider.js.map +26 -0
- package/dist/browser-context/ConformanceMockContext.d.ts +54 -0
- package/dist/browser-context/assertions/ariaRoundTrip.d.ts +7 -0
- package/dist/browser-context/assertions/capabilityHonesty.d.ts +7 -0
- package/dist/browser-context/assertions/itExpectFail.d.ts +8 -0
- package/dist/browser-context/assertions/networkIntrospection.d.ts +7 -0
- package/dist/browser-context/assertions/tabsLifecycle.d.ts +7 -0
- package/dist/browser-context/fixtures.d.ts +8 -0
- package/dist/browser-context/runIBrowserContextConformance.d.ts +7 -0
- package/dist/browser-context/types.d.ts +48 -0
- package/dist/browser-context.d.ts +18 -0
- package/dist/browser-context.js +567 -0
- package/dist/browser-context.js.map +17 -0
- package/dist/creditExhaustedSkip.d.ts +39 -0
- package/dist/creditExhaustedSkip.js +163 -0
- package/dist/creditExhaustedSkip.js.map +10 -0
- package/dist/entitlement-profile/assertions/denialShape.d.ts +13 -0
- package/dist/entitlement-profile/assertions/dispose.d.ts +16 -0
- package/dist/entitlement-profile/assertions/hierarchyHonoring.d.ts +11 -0
- package/dist/entitlement-profile/assertions/optionalNeverDenied.d.ts +11 -0
- package/dist/entitlement-profile/assertions/requestEntitlementShape.d.ts +12 -0
- package/dist/entitlement-profile/assertions/resourceScoping.d.ts +16 -0
- package/dist/entitlement-profile/assertions/subscribeGrant.d.ts +11 -0
- package/dist/entitlement-profile/assertions/subscribeReload.d.ts +11 -0
- package/dist/entitlement-profile/assertions/subscribeRevocation.d.ts +12 -0
- package/dist/entitlement-profile/assertions/surfaceCoverage.d.ts +7 -0
- package/dist/entitlement-profile/assertions/unsubscribeIdempotent.d.ts +7 -0
- package/dist/entitlement-profile/fixtures.d.ts +21 -0
- package/dist/entitlement-profile/runEntitlementProfileConformance.d.ts +7 -0
- package/dist/entitlement-profile/types.d.ts +45 -0
- package/dist/entitlement-profile.d.ts +23 -0
- package/dist/entitlement-profile.js +436 -0
- package/dist/entitlement-profile.js.map +22 -0
- package/dist/human-connector/MockHumanConnector.d.ts +29 -0
- package/dist/human-connector/assertions/abort.d.ts +7 -0
- package/dist/human-connector/assertions/capabilityHonesty.d.ts +7 -0
- package/dist/human-connector/assertions/concurrentIsolation.d.ts +7 -0
- package/dist/human-connector/assertions/multiTurnFollowUp.d.ts +7 -0
- package/dist/human-connector/assertions/notifyDisplayFastResolve.d.ts +7 -0
- package/dist/human-connector/assertions/roundtrip.d.ts +7 -0
- package/dist/human-connector/fixtures.d.ts +8 -0
- package/dist/human-connector/runHumanConnectorConformance.d.ts +9 -0
- package/dist/human-connector/types.d.ts +91 -0
- package/dist/human-connector.d.ts +19 -0
- package/dist/human-connector.js +843 -0
- package/dist/human-connector.js.map +20 -0
- package/dist/itExpectFail.d.ts +15 -0
- package/dist/itExpectFail.js +180 -0
- package/dist/itExpectFail.js.map +11 -0
- package/dist/storage-migrations/assertions/appliesAndRecords.d.ts +7 -0
- package/dist/storage-migrations/assertions/concurrentRunsSerialize.d.ts +18 -0
- package/dist/storage-migrations/assertions/ensureBookkeepingIdempotent.d.ts +7 -0
- package/dist/storage-migrations/assertions/failedMigrationLeavesNoPartialSchema.d.ts +14 -0
- package/dist/storage-migrations/assertions/failedMigrationNotRecorded.d.ts +7 -0
- package/dist/storage-migrations/assertions/idempotentRun.d.ts +7 -0
- package/dist/storage-migrations/assertions/incrementalApplication.d.ts +7 -0
- package/dist/storage-migrations/runMigrationRunnerContract.d.ts +9 -0
- package/dist/storage-migrations/types.d.ts +78 -0
- package/dist/storage-migrations.d.ts +18 -0
- package/dist/storage-migrations.js +473 -0
- package/dist/storage-migrations.js.map +20 -0
- package/dist/tabular-migrations/assertions/addAndDropIndex.d.ts +7 -0
- package/dist/tabular-migrations/assertions/addColumn.d.ts +7 -0
- package/dist/tabular-migrations/assertions/backfill.d.ts +7 -0
- package/dist/tabular-migrations/assertions/dropColumn.d.ts +7 -0
- package/dist/tabular-migrations/assertions/failedMigrationNotRecorded.d.ts +7 -0
- package/dist/tabular-migrations/assertions/freshDbFastPath.d.ts +7 -0
- package/dist/tabular-migrations/assertions/incrementalApplication.d.ts +7 -0
- package/dist/tabular-migrations/assertions/renameColumn.d.ts +7 -0
- package/dist/tabular-migrations/runTabularMigrationContract.d.ts +8 -0
- package/dist/tabular-migrations/types.d.ts +28 -0
- package/dist/tabular-migrations.d.ts +19 -0
- package/dist/tabular-migrations.js +257 -0
- package/dist/tabular-migrations.js.map +18 -0
- package/dist/tabular-storage/assertions/countMatchesQuery.d.ts +7 -0
- package/dist/tabular-storage/assertions/guardParity.d.ts +25 -0
- package/dist/tabular-storage/assertions/inListCriterion.d.ts +14 -0
- package/dist/tabular-storage/assertions/joinBoundedLeftRead.d.ts +29 -0
- package/dist/tabular-storage/assertions/joinPushdown.d.ts +13 -0
- package/dist/tabular-storage/assertions/notInListCriterion.d.ts +15 -0
- package/dist/tabular-storage/assertions/strategyParity.d.ts +31 -0
- package/dist/tabular-storage/assertions/subscribeToChanges.d.ts +21 -0
- package/dist/tabular-storage/assertions/vectorColumnFormat.d.ts +7 -0
- package/dist/tabular-storage/assertions/withConnectionTransaction.d.ts +7 -0
- package/dist/tabular-storage/assertions/withTransactionRollback.d.ts +7 -0
- package/dist/tabular-storage/fixtures.d.ts +33 -0
- package/dist/tabular-storage/joinFixtures.d.ts +66 -0
- package/dist/tabular-storage/runTabularJoinContract.d.ts +24 -0
- package/dist/tabular-storage/runTabularStorageContract.d.ts +9 -0
- package/dist/tabular-storage/types.d.ts +102 -0
- package/dist/tabular-storage.d.ts +25 -0
- package/dist/tabular-storage.js +1477 -0
- package/dist/tabular-storage.js.map +27 -0
- package/dist/vector-storage/assertions/dimensionValidation.d.ts +17 -0
- package/dist/vector-storage/assertions/legacyEncoding.d.ts +16 -0
- package/dist/vector-storage/assertions/metadataFilter.d.ts +16 -0
- package/dist/vector-storage/assertions/searchOptions.d.ts +16 -0
- package/dist/vector-storage/assertions/shared.d.ts +23 -0
- package/dist/vector-storage/assertions/similarityRanking.d.ts +18 -0
- package/dist/vector-storage/assertions/similaritySearchEvent.d.ts +16 -0
- package/dist/vector-storage/assertions/vectorRoundTrip.d.ts +17 -0
- package/dist/vector-storage/runVectorStorageContract.d.ts +20 -0
- package/dist/vector-storage/types.d.ts +87 -0
- package/dist/vector-storage.d.ts +19 -0
- package/dist/vector-storage.js +412 -0
- package/dist/vector-storage.js.map +21 -0
- package/dist/web-search/assertions/capabilityAgreement.d.ts +23 -0
- package/dist/web-search/assertions/credentialNaming.d.ts +21 -0
- package/dist/web-search/assertions/dateFiltering.d.ts +22 -0
- package/dist/web-search/assertions/domainNormalization.d.ts +24 -0
- package/dist/web-search/assertions/maxResultsCeiling.d.ts +22 -0
- package/dist/web-search/assertions/reportedQuery.d.ts +18 -0
- package/dist/web-search/assertions/signalThreading.d.ts +20 -0
- package/dist/web-search/fixtures.d.ts +40 -0
- package/dist/web-search/runWebSearchProviderConformance.d.ts +24 -0
- package/dist/web-search/types.d.ts +79 -0
- package/dist/web-search.d.ts +19 -0
- package/dist/web-search.js +560 -0
- package/dist/web-search.js.map +18 -0
- package/dist/worker-proxy/assertions/backlogOrdering.d.ts +7 -0
- package/dist/worker-proxy/assertions/disposeTerminatesWorker.d.ts +8 -0
- package/dist/worker-proxy/assertions/errorPropagation.d.ts +7 -0
- package/dist/worker-proxy/assertions/providerCallHelpers.d.ts +23 -0
- package/dist/worker-proxy/browserOnlyStub.d.ts +7 -0
- package/dist/worker-proxy/runWorkerProxyBoundary.d.ts +7 -0
- package/dist/worker-proxy/types.d.ts +47 -0
- package/dist/worker-proxy.d.ts +16 -0
- package/dist/worker-proxy.js +389 -0
- package/dist/worker-proxy.js.map +17 -0
- package/package.json +120 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/tabular-storage/assertions/countMatchesQuery.ts", "../src/creditExhaustedSkip.ts", "../src/itExpectFail.ts", "../src/tabular-storage/assertions/guardParity.ts", "../src/tabular-storage/assertions/inListCriterion.ts", "../src/tabular-storage/assertions/joinBoundedLeftRead.ts", "../src/tabular-storage/assertions/joinPushdown.ts", "../src/tabular-storage/fixtures.ts", "../src/tabular-storage/assertions/notInListCriterion.ts", "../src/tabular-storage/assertions/strategyParity.ts", "../src/tabular-storage/assertions/subscribeToChanges.ts", "../src/tabular-storage/assertions/vectorColumnFormat.ts", "../src/tabular-storage/assertions/withConnectionTransaction.ts", "../src/tabular-storage/assertions/withTransactionRollback.ts", "../src/tabular-storage/joinFixtures.ts", "../src/tabular-storage/runTabularJoinContract.ts", "../src/tabular-storage/runTabularStorageContract.ts", "../src/tabular-storage/types.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ITabularStorage } from \"@workglow/storage\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport type { CompoundPrimaryKeyNames, CompoundSchema } from \"../fixtures\";\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type { TabularStorageContractOpts } from \"../types\";\n\nexport function countMatchesQueryBlock(opts: TabularStorageContractOpts): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itImpl = expectFails.has(\"countMatchesQuery\") ? itExpectFail : it;\n\n describe.skipIf(!opts.capabilities.supportsQuery)(\"countMatchesQuery\", () => {\n let storage: ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>;\n\n beforeEach(async () => {\n storage = await opts.createStorage();\n await storage.setupDatabase?.();\n });\n\n afterEach(async () => {\n await storage.deleteAll();\n storage.destroy?.();\n await opts.releaseStorage?.(storage);\n });\n\n itImpl(\n \"count(predicate) equals query(predicate).length for every predicate\",\n async () => {\n const rows = [\n { name: \"a\", type: \"t\", option: \"x\", success: true },\n { name: \"b\", type: \"t\", option: \"x\", success: true },\n { name: \"c\", type: \"t\", option: \"x\", success: true },\n { name: \"d\", type: \"t\", option: \"y\", success: true },\n { name: \"e\", type: \"t\", option: \"y\", success: true },\n { name: \"f\", type: \"t\", option: \"z\", success: true },\n ];\n for (const row of rows) {\n await storage.put(row);\n }\n\n const expected: Record<string, number> = { x: 3, y: 2, z: 1, missing: 0 };\n for (const [option, expectedCount] of Object.entries(expected)) {\n const counted = await storage.count({ option });\n const queried = (await storage.query({ option })) ?? [];\n expect(\n counted,\n `count({option:\"${option}\"}) disagrees with query({option:\"${option}\"}).length`\n ).toBe(queried.length);\n expect(counted).toBe(expectedCount);\n }\n\n const totalCount = await storage.count();\n const allRows = (await storage.getAll()) ?? [];\n expect(totalCount).toBe(allRows.length);\n expect(totalCount).toBe(rows.length);\n },\n opts.timeout\n );\n });\n}\n",
|
|
6
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { it as vitestIt } from \"vitest\";\n\n/**\n * Live provider tests treat \"the account is out of credits\" as a billing\n * condition rather than a product bug. A key being present is not the same as\n * the account being able to pay; HuggingFace / OpenRouter 402s, Anthropic's\n * credit-balance error, DeepSeek's `Insufficient Balance`, and OpenAI's\n * `insufficient_quota` are all billing.\n *\n * Rate limits (429 + `rate_limit_exceeded`) are NOT this: those are transient\n * and the existing retry policy handles them.\n *\n * Where it skips and where it fails is a deliberate split\n * ({@link creditExhaustionSkipsTest}): on CI it skips for every provider,\n * because a red build tells a reviewer nothing they can act on; locally it\n * fails, because the developer running the suite is the one who can top the\n * account up. `WORKGLOW_CREDIT_EXHAUSTED_SKIP=1` forces the skip locally\n * (and `=0` forces the failure on CI).\n */\n\nconst BILLING_CODES = new Set([\n \"insufficient_quota\",\n \"billing_hard_limit_reached\",\n \"insufficient_credits\",\n \"insufficient_balance\",\n]);\n\nconst CREDIT_MESSAGE =\n /insufficient credits|not enough credits|no credits remaining|out of credits|insufficient[\\s_-]balance|balance is (?:too low|insufficient)|exceeded[\\s\\S]{0,80}credits|monthly included credits|payment required|exceeded your current quota|check your plan and billing|billing_hard_limit/i;\n\n/**\n * A 402 that survives only as text. `classifyProviderError` rebuilds the error\n * as a `PermanentJobError` carrying the message and no `cause`, so by the time\n * a live test sees a DeepSeek billing failure the numeric `status` is gone and\n * the OpenAI-SDK message shape (`402 Insufficient Balance`) is all that is\n * left. Anchored to an HTTP-shaped position — start of a line, or after a\n * summary line's colon — so prose that merely contains the number\n * (\"Item 402(c)\") is not scavenged as a status.\n */\nconst HTTP_402_MESSAGE =\n /\\bHTTP\\/?\\d?(?:\\.\\d)?\\s*402\\b|\\bstatus(?:\\s*code)?\\s*[:=]\\s*402\\b|\\berror code:?\\s*402\\b|(?:^|:\\s+)402\\s+[a-z]/im;\n\nexport function isCreditExhaustedError(err: unknown): boolean {\n const seen = new Set<unknown>();\n let current: unknown = err;\n while (current !== undefined && current !== null && !seen.has(current)) {\n seen.add(current);\n if (matchesCreditExhaustion(current)) return true;\n current = causeOf(current);\n }\n return false;\n}\n\nfunction matchesCreditExhaustion(err: unknown): boolean {\n if (typeof err === \"string\") return messageLooksLikeCreditExhaustion(err);\n\n if (!err || typeof err !== \"object\") return false;\n\n const rec = err as Record<string, unknown>;\n const status = numericStatus(rec);\n if (status === 402) return true;\n\n const code = stringish(rec.code) ?? stringish(nested(rec.error, \"code\"));\n const type = stringish(rec.type) ?? stringish(nested(rec.error, \"type\"));\n if (code && BILLING_CODES.has(code)) return true;\n if (type && BILLING_CODES.has(type)) return true;\n\n const message = [\n stringish(rec.message),\n stringish(nested(rec.error, \"message\")),\n stringifyUnknown(err),\n ]\n .filter((part): part is string => typeof part === \"string\")\n .join(\"\\n\");\n return messageLooksLikeCreditExhaustion(message);\n}\n\nfunction messageLooksLikeCreditExhaustion(message: string): boolean {\n return CREDIT_MESSAGE.test(message) || HTTP_402_MESSAGE.test(message);\n}\n\nfunction numericStatus(rec: Record<string, unknown>): number | undefined {\n for (const key of [\"status\", \"statusCode\"] as const) {\n const value = rec[key];\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\n }\n const nestedStatus = nested(rec.error, \"status\");\n if (typeof nestedStatus === \"number\" && Number.isFinite(nestedStatus)) return nestedStatus;\n return undefined;\n}\n\nfunction nested(value: unknown, key: string): unknown {\n if (!value || typeof value !== \"object\") return undefined;\n return (value as Record<string, unknown>)[key];\n}\n\nfunction stringish(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction stringifyUnknown(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\nfunction causeOf(err: unknown): unknown {\n if (!err || typeof err !== \"object\") return undefined;\n return (err as { cause?: unknown }).cause;\n}\n\nexport function creditSkipNote(err: unknown): string {\n const message = err instanceof Error ? err.message : String(err);\n return `skipped: provider out of credits (${message})`;\n}\n\nconst SKIP_OVERRIDE_ENV = \"WORKGLOW_CREDIT_EXHAUSTED_SKIP\";\n\nfunction isTruthyFlag(value: string): boolean {\n return !/^(?:0|false|off|no)$/i.test(value.trim());\n}\n\n/**\n * Whether a billing failure skips the test or fails it.\n *\n * CI skips: nobody watching a build can top an account up, and one exhausted\n * key would otherwise turn every provider suite red for a change that touched\n * none of them — so the leniency is provider-agnostic by construction, keyed on\n * the environment rather than on which vendor ran out.\n *\n * Locally it fails, loudly, because the developer running the suite IS the\n * person who can act on it — and a silently green local run is how an\n * exhausted account goes unnoticed until CI is the only thing exercising the\n * provider at all.\n *\n * `WORKGLOW_CREDIT_EXHAUSTED_SKIP` overrides in both directions.\n */\nexport function creditExhaustionSkipsTest(\n env: Record<string, string | undefined> = process.env\n): boolean {\n const override = env[SKIP_OVERRIDE_ENV];\n if (override !== undefined && override.trim() !== \"\") return isTruthyFlag(override);\n for (const key of [\"CI\", \"GITHUB_ACTIONS\"] as const) {\n const value = env[key];\n if (value !== undefined && value.trim() !== \"\" && isTruthyFlag(value)) return true;\n }\n return false;\n}\n\n/**\n * The local-failure path. Rethrows the provider's own error unchanged — its\n * identity and stack are what makes the failure diagnosable — after one line\n * saying why it was not skipped and how to skip it.\n */\nfunction reportLocalCreditFailure(err: unknown): void {\n const message = err instanceof Error ? err.message : String(err);\n console.error(\n `provider out of credits (${message})\\n` +\n ` Failing because this is not CI. Top the account up, or set ${SKIP_OVERRIDE_ENV}=1 to skip instead.`\n );\n}\n\nexport async function runWithCreditSkip<T>(\n ctx: { skip: (note?: string) => void },\n fn: () => Promise<T> | T\n): Promise<T> {\n try {\n return await fn();\n } catch (err) {\n if (isCreditExhaustedError(err)) {\n if (!creditExhaustionSkipsTest()) {\n reportLocalCreditFailure(err);\n throw err;\n }\n ctx.skip(creditSkipNote(err));\n // vitest's skip() is `never` (throws). Mocks used in unit tests return.\n return undefined as T;\n }\n throw err;\n }\n}\n\n/**\n * Wraps one test body. This is the seam every conformance assertion actually\n * runs through (the exported {@link it} proxies each callback through it), so\n * the CI-skips / locally-fails policy is applied here rather than at each\n * call site.\n */\nexport function wrapTestBodyForCreditSkip(\n fn: (...args: never[]) => unknown\n): (...args: never[]) => unknown {\n return async function wrapped(this: unknown, ...args: never[]): Promise<unknown> {\n try {\n return await fn.apply(this, args);\n } catch (err) {\n if (!isCreditExhaustedError(err)) throw err;\n if (!creditExhaustionSkipsTest()) {\n reportLocalCreditFailure(err);\n throw err;\n }\n const ctx = args[0] as { skip?: (note?: string) => void } | undefined;\n if (ctx && typeof ctx.skip === \"function\") {\n ctx.skip(creditSkipNote(err));\n return undefined;\n }\n // bun:test has no TestContext.skip; passing is \"don't fail the test\".\n console.warn(creditSkipNote(err));\n return undefined;\n }\n };\n}\n\nfunction wrapCallable(fn: CallableFunction): CallableFunction {\n return new Proxy(fn, {\n apply(target, thisArg, argArray: unknown[]) {\n const wrapped = argArray.map((arg) =>\n typeof arg === \"function\" ? wrapTestBodyForCreditSkip(arg as never) : arg\n );\n const result = Reflect.apply(target, thisArg, wrapped);\n return typeof result === \"function\" ? wrapCallable(result) : result;\n },\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n if (typeof value === \"function\") {\n return wrapCallable((value as CallableFunction).bind(target));\n }\n return value;\n },\n });\n}\n\n/**\n * Drop-in `it` for live provider tests: a credit/billing error skips the test on\n * CI and fails it locally (see {@link creditExhaustionSkipsTest}).\n */\nexport const it = wrapCallable(vitestIt) as typeof vitestIt;\n",
|
|
7
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { isCreditExhaustedError, it } from \"./creditExhaustedSkip\";\n\ntype ItFn = (name: string, fn: () => Promise<void> | void, timeout?: number) => void;\n\n/**\n * Cross-runner polyfill for `it.fails`. Vitest exposes it natively; bun's\n * native `bun test` runner does not. Under bun we wrap the body in a\n * try/catch and assert that it threw — so a passing-when-expected-fail\n * test still surfaces as a CI failure (signalling \"remove from\n * expectedFailures\").\n */\nexport const itExpectFail: ItFn = (name, fn, timeout) => {\n const native = (it as unknown as { fails?: ItFn }).fails;\n if (typeof native === \"function\") {\n native(name, fn, timeout);\n return;\n }\n it(\n `${name} [expected-fail]`,\n async () => {\n let passed = false;\n try {\n await fn();\n passed = true;\n } catch (err) {\n // Billing exhaustion is not the adapter bug this marker records.\n if (isCreditExhaustedError(err)) throw err;\n // expected; the test was supposed to fail\n }\n if (passed) {\n throw new Error(\n `Test \"${name}\" was marked as expected-fail but passed. Remove its name from opts.expectedFailures.`\n );\n }\n },\n timeout\n );\n};\n",
|
|
8
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ITabularStorage } from \"@workglow/storage\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport type { CompoundPrimaryKeyNames, CompoundSchema } from \"../fixtures\";\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type { TabularStorageContractOpts } from \"../types\";\n\ntype Storage = ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>;\n\n/**\n * What one call did, reduced to what can be compared across the two paths.\n *\n * Only the error's class and message, not the instance: the two calls throw\n * from the same guard but not the same throw site, and identity would make the\n * comparison vacuous.\n */\ntype Outcome =\n | { readonly kind: \"returned\" }\n | { readonly kind: \"threw\"; readonly name: string; readonly message: string };\n\nasync function outcomeOf(call: () => Promise<unknown>): Promise<Outcome> {\n try {\n await call();\n return { kind: \"returned\" };\n } catch (error) {\n return {\n kind: \"threw\",\n name: error instanceof Error ? error.constructor.name : typeof error,\n message: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\ninterface GuardParityCase {\n /** The public method, which is also the name the `tx` handle is called by. */\n readonly method: string;\n /** What makes the input bad, phrased as the test name reads it. */\n readonly label: string;\n readonly run: (target: Storage) => Promise<unknown>;\n}\n\n/**\n * The inputs a guard is supposed to reject, one per guard rather than one per\n * method: `queryIndex` has two ways to fail `validateSelect` and both are here,\n * because a fix that reinstates one and not the other is the same bug again.\n */\nconst CASES: readonly GuardParityCase[] = [\n {\n method: \"getAll\",\n label: \"a limit of zero\",\n run: (t) => t.getAll({ limit: 0 }),\n },\n {\n method: \"getAll\",\n label: \"a negative offset\",\n run: (t) => t.getAll({ offset: -1 }),\n },\n {\n method: \"getPage\",\n label: \"a limit of zero\",\n run: (t) => t.getPage({ limit: 0 }),\n },\n {\n method: \"count\",\n label: \"empty criteria\",\n run: (t) => t.count({}),\n },\n {\n method: \"query\",\n label: \"an operator outside the allow-list\",\n run: (t) => t.query({ name: { operator: \"regex\", value: \"a\" } } as never),\n },\n {\n method: \"query\",\n label: \"a column that is not in the schema\",\n run: (t) => t.query({ nope: \"a\" } as never),\n },\n {\n method: \"query\",\n label: \"a list operator whose value is not an array\",\n run: (t) => t.query({ name: { operator: \"in\", value: \"a\" } } as never),\n },\n {\n method: \"queryPage\",\n label: \"empty criteria\",\n run: (t) => t.queryPage({}, { limit: 5 }),\n },\n {\n method: \"queryIndex\",\n label: \"an empty select\",\n run: (t) => t.queryIndex({ name: \"a\" }, { select: [] } as never),\n },\n {\n method: \"queryIndex\",\n label: \"a select column that is not in the schema\",\n run: (t) => t.queryIndex({ name: \"a\" }, { select: [\"nope\"] } as never),\n },\n {\n method: \"deleteSearch\",\n label: \"empty criteria\",\n run: (t) => t.deleteSearch({}),\n },\n {\n method: \"deleteSearch\",\n label: \"criteria that exclude nothing\",\n run: (t) => t.deleteSearch({ option: { operator: \"not-in\", value: [] } } as never),\n },\n {\n method: \"updateWhere\",\n label: \"a patch that rewrites a primary-key column\",\n run: (t) => t.updateWhere({ name: \"guard\", type: \"parity\" }, { name: \"moved\" } as never),\n },\n {\n // Joined to itself, so the case needs no second storage and the guard is\n // reached on whichever path the planner picks: the pushdown validates in\n // `runSqlJoin`, the hash fallback in `BaseTabularStorage.join`.\n method: \"join\",\n label: \"a join column that is not in the schema\",\n run: (t) => t.join({ type: \"inner\", on: [{ left: \"nope\", right: \"name\" }] } as never, t),\n },\n];\n\n/**\n * Methods routed to a private implementation that no guard rejects an input\n * to, and why.\n *\n * Not an exemption list — a name here is a claim that the method was read and\n * has nothing to check, and the coverage assertion below is what forces the\n * claim to be made rather than skipped.\n */\nconst UNGUARDED: Readonly<Record<string, string>> = {\n put: \"an entity is coerced against the schema, not rejected by a guard\",\n putBulk: \"as `put`\",\n get: \"a primary key, and a malformed one reads as a miss on every backend\",\n getBulk: \"as `get`\",\n delete: \"as `get`\",\n deleteAll: \"takes no arguments\",\n size: \"takes no arguments\",\n getOffsetPage: \"takes two numbers and the SQL backends validate neither\",\n};\n\n/** The public method `_fooInternal` implements. */\nfunction publicNameOf(internal: string): string | undefined {\n const match = /^_(.+)Internal$/.exec(internal);\n return match?.[1];\n}\n\n/**\n * Every method that reaches the database as a public wrapper over a private\n * `_fooInternal`, read off the instance rather than listed here.\n *\n * That split is where a guard has two places it could go, and the shape a\n * method arrives in when someone adds one — so asking the object which methods\n * have it is what makes a method added later show up without anyone\n * remembering this file exists.\n */\nfunction routedMethods(storage: Storage): string[] {\n const found = new Set<string>();\n for (\n let proto: object | null = Object.getPrototypeOf(storage);\n proto !== null && proto !== Object.prototype;\n proto = Object.getPrototypeOf(proto)\n ) {\n for (const key of Object.getOwnPropertyNames(proto)) {\n const name = publicNameOf(key);\n if (name !== undefined && typeof (storage as never)[key] === \"function\") found.add(name);\n }\n }\n return [...found].sort();\n}\n\n/**\n * A guard on a public method is a guard on the transaction path.\n *\n * It was not always. `withTransaction` used to hand its callback a Proxy that\n * resolved `tx.foo(...)` to `_fooInternal(...)` by name, skipping the public\n * method, so a check written on `foo` was silently absent from `tx.foo`. That\n * cost three fixes — `tx.deleteSearch({})` turning from a no-op into a `DELETE\n * FROM t WHERE` syntax error and criteria excluding nothing into a truncated\n * table; `tx.updateWhere` rewriting a row's identity where the same call\n * outside a transaction threw; `tx.getBulk([])` reaching DuckDB as `IN ()` —\n * and each could only restate the guard in the private method.\n *\n * `createTxView` now leaves the public methods in place and neutralizes only\n * the mutex, so `tx.foo(args)` runs `foo`. Parity is a property of the routing\n * rather than of anyone's diligence, and this block is what says so out loud:\n * every guard below is written once, on the public method, and asserted on both\n * paths, over a set of methods derived from the object rather than written down.\n */\nexport function guardParityBlock(opts: TabularStorageContractOpts): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itImpl = expectFails.has(\"guardParity\") ? itExpectFail : it;\n\n describe.skipIf(!opts.capabilities.supportsTransactions || !opts.capabilities.supportsQuery)(\n \"guardParity\",\n () => {\n let storage: Storage;\n\n beforeEach(async () => {\n storage = await opts.createStorage();\n await storage.setupDatabase?.();\n await storage.put({ name: \"guard\", type: \"parity\", option: \"base\", success: true });\n });\n\n afterEach(async () => {\n await storage.deleteAll();\n storage.destroy?.();\n await opts.releaseStorage?.(storage);\n });\n\n for (const testCase of CASES) {\n itImpl(\n `${testCase.method} rejects ${testCase.label} the same way inside a transaction`,\n async () => {\n const direct = await outcomeOf(() => testCase.run(storage));\n\n let inside: Outcome | undefined;\n await storage.withTransaction(async (tx) => {\n // Captured rather than rethrown: a guard firing is the expected\n // result here, and letting it out would roll the transaction back\n // and hide which of the two paths differed.\n inside = await outcomeOf(() => testCase.run(tx as unknown as Storage));\n });\n\n expect(inside).toEqual(direct);\n },\n opts.timeout\n );\n }\n\n itImpl(\n \"deleteSearch with empty criteria deletes nothing on either path\",\n async () => {\n // The one case whose parity is a non-event: both paths return, and\n // the guard is only observable in the rows that are still there.\n await storage.deleteSearch({});\n await storage.withTransaction(async (tx) => {\n await tx.deleteSearch({});\n });\n\n expect(await storage.get({ name: \"guard\", type: \"parity\" })).toBeDefined();\n },\n opts.timeout\n );\n\n itImpl(\n \"runs the public method on the transaction path, not only its implementation\",\n async () => {\n // What every case above now rests on, asserted on its own so a\n // regression to name-based routing fails here first and says why.\n //\n // `getBulk` emits its event from the public method and nowhere else,\n // so observing the event through the `tx` handle is observing the\n // public method run — the guard-carrying half that resolving\n // `tx.getBulk` to `_getBulkInternal` used to skip.\n const emitted: Array<readonly unknown[]> = [];\n const listener = (keys: readonly unknown[]): void => {\n emitted.push(keys);\n };\n storage.on(\"getBulk\", listener);\n try {\n await storage.withTransaction(async (tx) => {\n await (tx as unknown as Storage).getBulk([{ name: \"guard\", type: \"parity\" }]);\n });\n } finally {\n storage.off(\"getBulk\", listener);\n }\n\n expect(emitted).toEqual([[{ name: \"guard\", type: \"parity\" }]]);\n },\n opts.timeout\n );\n\n itImpl(\n \"states, for every method with a private implementation, what its guard is\",\n async () => {\n // The ratchet. A method added later reaches the transaction path the\n // moment it exists, whether or not anyone thought about its guard —\n // and this is where that is noticed, rather than in the diff of the\n // commit that drops one.\n const covered = new Set(CASES.map((testCase) => testCase.method));\n const unstated = routedMethods(storage).filter(\n (method) => !covered.has(method) && UNGUARDED[method] === undefined\n );\n expect(unstated).toEqual([]);\n },\n opts.timeout\n );\n }\n );\n}\n",
|
|
9
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ITabularStorage } from \"@workglow/storage\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport type { CompoundPrimaryKeyNames, CompoundSchema } from \"../fixtures\";\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type { TabularStorageContractOpts } from \"../types\";\n\n/**\n * The `{ operator: \"in\" }` criterion, which every backend renders differently —\n * expanded placeholders on SQLite/DuckDB, a single `= ANY($n)` array parameter\n * on Postgres, a PostgREST `in.()` filter on Supabase, and an in-memory\n * membership test elsewhere. That divergence is exactly why the semantics are\n * pinned here rather than per backend.\n */\nexport function inListCriterionBlock(opts: TabularStorageContractOpts): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itImpl = expectFails.has(\"inListCriterion\") ? itExpectFail : it;\n\n describe.skipIf(!opts.capabilities.supportsQuery)(\"inListCriterion\", () => {\n let storage: ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>;\n\n const rows = [\n { name: \"a\", type: \"t\", option: \"x\", success: true },\n { name: \"b\", type: \"t\", option: \"y\", success: true },\n { name: \"c\", type: \"t\", option: \"z\", success: false },\n { name: \"d\", type: \"u\", option: \"x\", success: false },\n ];\n\n beforeEach(async () => {\n storage = await opts.createStorage();\n await storage.setupDatabase?.();\n for (const row of rows) await storage.put(row);\n });\n\n afterEach(async () => {\n await storage.deleteAll();\n storage.destroy?.();\n await opts.releaseStorage?.(storage);\n });\n\n const names = (found: readonly { name: string }[]): string[] => found.map((r) => r.name).sort();\n\n itImpl(\n \"matches every listed value and nothing else\",\n async () => {\n const found =\n (await storage.query({ option: { value: [\"x\", \"z\"], operator: \"in\" } })) ?? [];\n expect(names(found)).toEqual([\"a\", \"c\", \"d\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"ANDs with the other criteria columns\",\n async () => {\n const found =\n (await storage.query({ option: { value: [\"x\", \"z\"], operator: \"in\" }, type: \"t\" })) ?? [];\n expect(names(found)).toEqual([\"a\", \"c\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"ignores values that match no row, and never duplicates a row for a repeated value\",\n async () => {\n const found =\n (await storage.query({ option: { value: [\"x\", \"nope\", \"x\"], operator: \"in\" } })) ?? [];\n expect(names(found)).toEqual([\"a\", \"d\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"matches nothing for an empty list\",\n async () => {\n // `IN ()` is a syntax error in SQL, so every backend has to special-case\n // this; the contract is that it matches no row rather than throwing or\n // (much worse) degrading to no filter at all and matching everything.\n const found = (await storage.query({ option: { value: [], operator: \"in\" } })) ?? [];\n expect(found).toEqual([]);\n expect(await storage.count({ option: { value: [], operator: \"in\" } })).toBe(0);\n },\n opts.timeout\n );\n\n itImpl(\n \"count agrees with query for an in-list\",\n async () => {\n const criteria = { option: { value: [\"x\", \"z\"], operator: \"in\" as const } };\n const queried = (await storage.query(criteria)) ?? [];\n expect(await storage.count(criteria)).toBe(queried.length);\n },\n opts.timeout\n );\n\n itImpl(\n \"deleteSearch removes exactly the listed rows\",\n async () => {\n await storage.deleteSearch({ option: { value: [\"x\", \"z\"], operator: \"in\" } });\n const left = (await storage.getAll()) ?? [];\n expect(names(left)).toEqual([\"b\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"deleteSearch with an empty list deletes nothing\",\n async () => {\n await storage.deleteSearch({ option: { value: [], operator: \"in\" } });\n const left = (await storage.getAll()) ?? [];\n expect(left.length).toBe(rows.length);\n },\n opts.timeout\n );\n\n itImpl(\n \"matches on a boolean column\",\n async () => {\n // Booleans are stored as 0/1 by some backends, so the list elements must\n // go through the same coercion a scalar `=` value would.\n const found = (await storage.query({ success: { value: [false], operator: \"in\" } })) ?? [];\n expect(names(found)).toEqual([\"c\", \"d\"]);\n },\n opts.timeout\n );\n });\n}\n",
|
|
10
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { QueryOptions } from \"@workglow/storage\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport type { AuthorStorage, JoinFixturePost, PostStorage } from \"../joinFixtures\";\n\n/**\n * How many left rows a call asked for, or `undefined` when it asked for all of\n * them. Read off the options the join handed the left read rather than off the\n * rows that came back, so a table that happens to be smaller than the bound\n * cannot pass for a bound that was never applied.\n */\nfunction limitsOf(spy: { mock: { calls: unknown[][] } }): Array<number | undefined> {\n return spy.mock.calls.map(\n (call) => (call[call.length - 1] as QueryOptions<JoinFixturePost> | undefined)?.limit\n );\n}\n\n/**\n * What the hash join reads off the left side, and when it is allowed to stop.\n *\n * `join`'s own docstring makes a promise with a condition attached: `limit` and\n * `offset` apply to the joined rows, and the fallback \"bounds the left read —\n * and stops early — only when the joined order does not need a sort\", meaning\n * `orderBy` is absent or names left columns the left query can serve. An\n * `orderBy` touching the right side is sorted in memory, and that sort needs\n * every joined row, so such a join reads the whole left table however small its\n * `limit`.\n *\n * Both halves matter and they fail in opposite directions. Losing the bound is\n * a performance cliff a caller cannot see: over a remote backend a `limit: 2`\n * join quietly puts the whole table on the wire. Applying it when the order is\n * NOT already final is a wrong answer: the first N left rows in arbitrary order\n * are sorted afterwards and returned as though they were the top N. Nothing\n * else in this contract asserts either one — the pushdown is bounded by the\n * database, so only the fallback can be caught getting this wrong.\n */\nexport function joinBoundedLeftReadBlock(\n createPosts: () => Promise<PostStorage>,\n createAuthors: () => Promise<AuthorStorage>,\n seed: (posts: PostStorage, authors: AuthorStorage) => Promise<void>,\n opts: { readonly expectSqlPushdown?: boolean; readonly timeout?: number }\n): void {\n const on = [{ left: \"author_id\", right: \"id\" }] as const;\n\n describe.skipIf(opts.expectSqlPushdown !== false)(\"join bounded left read\", () => {\n let posts: PostStorage;\n let authors: AuthorStorage;\n\n beforeEach(async () => {\n posts = await createPosts();\n authors = await createAuthors();\n await posts.setupDatabase?.();\n await authors.setupDatabase?.();\n await seed(posts, authors);\n });\n\n afterEach(async () => {\n await posts.deleteAll();\n await authors.deleteAll();\n posts.destroy?.();\n authors.destroy?.();\n vi.restoreAllMocks();\n });\n\n it(\n \"bounds the left read when no order is asked for\",\n async () => {\n const leftRead = vi.spyOn(posts, \"getAll\");\n const rows = await posts.join({ type: \"left\", on, limit: 2 }, authors);\n\n expect(rows).toHaveLength(2);\n // A left join emits at least one row per left row, so the first bounded\n // pass already has what it needs and no widening pass follows.\n expect(limitsOf(leftRead)).toEqual([2]);\n },\n opts.timeout\n );\n\n it(\n \"bounds the left read when the left query can serve the order\",\n async () => {\n // `views` is non-nullable, which is what lets the order be pushed into\n // the left query — the two layers only agree about where nulls sort\n // when there are none.\n const leftRead = vi.spyOn(posts, \"getAll\");\n const rows = await posts.join(\n {\n type: \"left\",\n on,\n orderBy: [{ side: \"left\", column: \"views\", direction: \"DESC\" }],\n limit: 2,\n },\n authors\n );\n\n expect(rows.map((r) => r.left.views)).toEqual([10, 9]);\n expect(limitsOf(leftRead)).toEqual([2]);\n },\n opts.timeout\n );\n\n it(\n \"counts offset toward the bound rather than ignoring it\",\n async () => {\n const leftRead = vi.spyOn(posts, \"getAll\");\n const rows = await posts.join(\n {\n type: \"left\",\n on,\n orderBy: [{ side: \"left\", column: \"views\", direction: \"DESC\" }],\n offset: 2,\n limit: 2,\n },\n authors\n );\n\n expect(rows.map((r) => r.left.views)).toEqual([7, 5]);\n // Reading only `limit` rows would have returned the rows the offset was\n // meant to skip.\n expect(limitsOf(leftRead)).toEqual([4]);\n },\n opts.timeout\n );\n\n it(\n \"widens the left read when an inner join drops too many rows\",\n async () => {\n // Two of six posts have no author, so a bounded inner join produces\n // fewer joined rows than it read and has to come back for more.\n const leftRead = vi.spyOn(posts, \"getAll\");\n const rows = await posts.join({ type: \"inner\", on, limit: 4 }, authors);\n\n expect(rows).toHaveLength(4);\n const limits = limitsOf(leftRead);\n expect(limits.length).toBeGreaterThan(1);\n expect(limits[0]).toBe(4);\n // Geometric, not one row at a time: a linear widening makes the number\n // of round trips proportional to how selective the join turned out.\n expect(limits[1]).toBeGreaterThan(4);\n },\n opts.timeout\n );\n\n it(\n \"reads the whole left side when the joined order needs a sort\",\n async () => {\n const leftRead = vi.spyOn(posts, \"getAll\");\n const rows = await posts.join(\n {\n type: \"left\",\n on,\n orderBy: [\n { side: \"right\", column: \"name\", direction: \"ASC\" },\n { side: \"left\", column: \"id\", direction: \"ASC\" },\n ],\n limit: 2,\n },\n authors\n );\n\n // The two unmatched posts sort first under ASC, which is only true if\n // every joined row was in hand before the sort ran.\n expect(rows.map((r) => r.left.id)).toEqual([\"p4\", \"p5\"]);\n expect(limitsOf(leftRead)).toEqual([undefined]);\n },\n opts.timeout\n );\n\n it(\n \"reads the whole left side when the order names a nullable left column\",\n async () => {\n // `author_id` is nullable, so the order cannot be pushed into the left\n // query; it is sorted here instead, and that sort needs every row.\n const leftRead = vi.spyOn(posts, \"getAll\");\n const rows = await posts.join(\n {\n type: \"left\",\n on,\n orderBy: [\n { side: \"left\", column: \"author_id\", direction: \"ASC\" },\n { side: \"left\", column: \"id\", direction: \"ASC\" },\n ],\n limit: 2,\n },\n authors\n );\n\n expect(rows.map((r) => r.left.id)).toEqual([\"p5\", \"p1\"]);\n expect(limitsOf(leftRead)).toEqual([undefined]);\n },\n opts.timeout\n );\n\n it(\n \"bounds the filtered left read the same way\",\n async () => {\n // A `where.left` moves the read from `getAll` to `query`, and the bound\n // has to travel with it — this is the path a caller reaches for\n // precisely when the table is too big to read whole.\n const leftQuery = vi.spyOn(posts, \"query\");\n const rows = await posts.join(\n { type: \"left\", on, where: { left: { tenant: \"t1\" } }, limit: 2 },\n authors\n );\n\n expect(rows).toHaveLength(2);\n expect(limitsOf(leftQuery)).toEqual([2]);\n },\n opts.timeout\n );\n });\n}\n",
|
|
11
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ITabularStorage } from \"@workglow/storage\";\nimport { InMemoryTabularStorage } from \"@workglow/storage\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { CompoundPrimaryKeyNames, CompoundSchema } from \"../fixtures\";\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type { TabularStorageContractOpts } from \"../types\";\n\ntype CompoundStorage = ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>;\n\n/**\n * Which path a join takes. A backend that can share a connection handle must\n * run a same-handle join as one statement — proven by the right side's `query`\n * never being called — and must fall back to the hash join, which does call\n * it, when the right side lives elsewhere.\n */\nexport function joinPushdownBlock(opts: TabularStorageContractOpts): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itImpl = expectFails.has(\"joinPushdown\") ? itExpectFail : it;\n\n describe.skipIf(!opts.capabilities.supportsQuery || !opts.createSiblingStorage)(\n \"joinPushdown\",\n () => {\n let primary: CompoundStorage;\n let sibling: CompoundStorage;\n\n beforeEach(async () => {\n primary = await opts.createStorage();\n await primary.setupDatabase?.();\n sibling = await opts.createSiblingStorage!(primary);\n await sibling.setupDatabase?.();\n await primary.putBulk([\n { name: \"a\", type: \"t\", option: \"x\", success: true },\n { name: \"b\", type: \"t\", option: \"y\", success: false },\n ]);\n await sibling.putBulk([\n { name: \"a\", type: \"u\", option: \"p\", success: true },\n { name: \"c\", type: \"u\", option: \"q\", success: true },\n ]);\n });\n\n afterEach(async () => {\n await primary.deleteAll();\n await sibling.deleteAll();\n primary.destroy?.();\n sibling.destroy?.();\n await opts.releaseStorage?.(primary);\n await opts.releaseStorage?.(sibling);\n vi.restoreAllMocks();\n });\n\n itImpl(\n \"runs a same-connection join without querying the right side\",\n async () => {\n const rightQuery = vi.spyOn(sibling, \"query\");\n const rows = await primary.join(\n {\n type: \"left\",\n on: [{ left: \"name\", right: \"name\" }],\n orderBy: [{ side: \"left\", column: \"name\", direction: \"ASC\" }],\n },\n sibling\n );\n expect(rightQuery).not.toHaveBeenCalled();\n expect(rows.map((r) => `${r.left.name}:${r.right?.type ?? \"-\"}`)).toEqual([\"a:u\", \"b:-\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"falls back to the hash join for a right side on another connection\",\n async () => {\n const elsewhere = new InMemoryTabularStorage<\n typeof CompoundSchema,\n typeof CompoundPrimaryKeyNames\n >(CompoundSchema, CompoundPrimaryKeyNames);\n await elsewhere.putBulk([{ name: \"b\", type: \"v\", option: \"z\", success: true }]);\n const rightQuery = vi.spyOn(elsewhere, \"query\");\n const rows = await primary.join(\n {\n type: \"left\",\n on: [{ left: \"name\", right: \"name\" }],\n orderBy: [{ side: \"left\", column: \"name\", direction: \"ASC\" }],\n },\n elsewhere\n );\n expect(rightQuery).toHaveBeenCalled();\n expect(rows.map((r) => `${r.left.name}:${r.right?.type ?? \"-\"}`)).toEqual([\"a:-\", \"b:v\"]);\n },\n opts.timeout\n );\n }\n );\n}\n",
|
|
12
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { DataPortSchemaObject } from \"@workglow/util/schema\";\n\n/**\n * The row shape every tabular assertion in this contract reads and writes.\n *\n * A compound primary key (`name` + `type`) rather than a single column, because\n * a backend that gets one key right and the pair wrong is the common failure —\n * cursor encoding, `getBulk` chunking and the join's tuple fingerprint all\n * behave differently once the key has two parts.\n */\nexport const CompoundPrimaryKeyNames = [\"name\", \"type\"] as const;\nexport const CompoundSchema = {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n type: { type: \"string\" },\n option: { type: \"string\" },\n success: { type: \"boolean\" },\n },\n required: [\"name\", \"type\", \"option\", \"success\"],\n additionalProperties: false,\n} as const satisfies DataPortSchemaObject;\n",
|
|
13
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ITabularStorage } from \"@workglow/storage\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport type { CompoundPrimaryKeyNames, CompoundSchema } from \"../fixtures\";\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type { TabularStorageContractOpts } from \"../types\";\n\n/**\n * The `{ operator: \"not-in\" }` criterion. Pinned here for the same reason its\n * complement is — `NOT IN (…)` on SQLite/DuckDB, `<> ALL($n)` on Postgres, a\n * PostgREST `not.in.()` filter on Supabase, a JS predicate elsewhere — plus one\n * of its own: an exclusion filter that silently matches nothing looks like an\n * empty result set rather than a bug, so the empty-list and null cases need a\n * test that says which way they are supposed to fall.\n */\nexport function notInListCriterionBlock(opts: TabularStorageContractOpts): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itImpl = expectFails.has(\"notInListCriterion\") ? itExpectFail : it;\n\n describe.skipIf(!opts.capabilities.supportsQuery)(\"notInListCriterion\", () => {\n let storage: ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>;\n\n const rows = [\n { name: \"a\", type: \"t\", option: \"x\", success: true },\n { name: \"b\", type: \"t\", option: \"y\", success: true },\n { name: \"c\", type: \"t\", option: \"z\", success: false },\n { name: \"d\", type: \"u\", option: \"x\", success: false },\n ];\n\n beforeEach(async () => {\n storage = await opts.createStorage();\n await storage.setupDatabase?.();\n for (const row of rows) await storage.put(row);\n });\n\n afterEach(async () => {\n await storage.deleteAll();\n storage.destroy?.();\n await opts.releaseStorage?.(storage);\n });\n\n const names = (found: readonly { name: string }[]): string[] => found.map((r) => r.name).sort();\n\n itImpl(\n \"excludes every listed value and keeps the rest\",\n async () => {\n const found =\n (await storage.query({ option: { value: [\"x\", \"z\"], operator: \"not-in\" } })) ?? [];\n expect(names(found)).toEqual([\"b\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"is the exact complement of the same in-list\",\n async () => {\n const included = (await storage.query({ option: { value: [\"x\"], operator: \"in\" } })) ?? [];\n const excluded =\n (await storage.query({ option: { value: [\"x\"], operator: \"not-in\" } })) ?? [];\n // Every row lands on exactly one side. This only holds because no row\n // here has a null `option`; the null case is pinned separately below.\n expect([...names(included), ...names(excluded)].sort()).toEqual([\"a\", \"b\", \"c\", \"d\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"ANDs with the other criteria columns\",\n async () => {\n const found =\n (await storage.query({ option: { value: [\"x\"], operator: \"not-in\" }, type: \"t\" })) ?? [];\n expect(names(found)).toEqual([\"b\", \"c\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"ignores values that match no row, and tolerates a repeated value\",\n async () => {\n const found =\n (await storage.query({ option: { value: [\"x\", \"nope\", \"x\"], operator: \"not-in\" } })) ??\n [];\n expect(names(found)).toEqual([\"b\", \"c\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"matches everything for an empty list\",\n async () => {\n // The inverse of the empty `in` list. Excluding nothing excludes\n // nothing — a backend that reused its always-false `IN ()` predicate\n // here would invert the caller's filter and return no rows.\n const found = (await storage.query({ option: { value: [], operator: \"not-in\" } })) ?? [];\n expect(names(found)).toEqual([\"a\", \"b\", \"c\", \"d\"]);\n expect(await storage.count({ option: { value: [], operator: \"not-in\" } })).toBe(\n rows.length\n );\n },\n opts.timeout\n );\n\n itImpl(\n \"count agrees with query for a not-in list\",\n async () => {\n const criteria = { option: { value: [\"x\", \"z\"], operator: \"not-in\" as const } };\n const queried = (await storage.query(criteria)) ?? [];\n expect(await storage.count(criteria)).toBe(queried.length);\n },\n opts.timeout\n );\n\n itImpl(\n \"deleteSearch removes exactly the unlisted rows\",\n async () => {\n await storage.deleteSearch({ option: { value: [\"x\", \"z\"], operator: \"not-in\" } });\n const left = (await storage.getAll()) ?? [];\n expect(names(left)).toEqual([\"a\", \"c\", \"d\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"deleteSearch refuses criteria that reduce to an empty exclusion\",\n async () => {\n // The empty list matches every row, so this delete would empty the\n // table — and an exclusion list is usually built from caller input, so\n // the shape that reads as a bug is the one that destroys everything.\n // `query` and `count` still answer it; only the delete refuses.\n await expect(\n storage.deleteSearch({ option: { value: [], operator: \"not-in\" } })\n ).rejects.toThrow(/delete the whole table/);\n const left = (await storage.getAll()) ?? [];\n expect(names(left)).toEqual([\"a\", \"b\", \"c\", \"d\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"deleteSearch still runs when another column narrows the empty exclusion\",\n async () => {\n // The refusal is about criteria that name no rows in particular, not\n // about the empty list itself: `type` still restricts this one.\n await storage.deleteSearch({\n type: \"u\",\n option: { value: [], operator: \"not-in\" },\n });\n const left = (await storage.getAll()) ?? [];\n expect(names(left)).toEqual([\"a\", \"b\", \"c\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"matches on a boolean column\",\n async () => {\n // Booleans are stored as 0/1 by some backends, so the list elements\n // must go through the same coercion a scalar `=` value would.\n const found =\n (await storage.query({ success: { value: [false], operator: \"not-in\" } })) ?? [];\n expect(names(found)).toEqual([\"a\", \"b\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"matches nothing when the list contains null\",\n async () => {\n // SQL's three-valued logic: `col NOT IN (…, NULL)` is UNKNOWN for every\n // row that is not already excluded, so no row survives. Reproduced\n // rather than papered over, so the abstraction and the database it\n // stands in for cannot disagree.\n // Cast because `option` is a non-nullable column, so the typed API\n // cannot express this — but criteria arrive as untyped JSON at the\n // HTTP-proxy boundary, so a backend still has to answer for it.\n const found =\n (await storage.query({\n option: { value: [\"x\", null], operator: \"not-in\" },\n } as unknown as Parameters<typeof storage.query>[0])) ?? [];\n expect(found).toEqual([]);\n },\n opts.timeout\n );\n\n // The SQL backends hand `fn` a Proxy that routes `tx.<method>` to the\n // private `_<method>Internal`, so a guard placed only on the public\n // `deleteSearch` is skipped by every call made through `tx`.\n describe.skipIf(!opts.capabilities.supportsTransactions)(\"through a transaction handle\", () => {\n itImpl(\n \"refuses an empty exclusion there too\",\n async () => {\n // Unguarded this renders `WHERE 1 = 1` and empties the table.\n await expect(\n storage.withTransaction(async (tx) => {\n await tx.deleteSearch({ option: { value: [], operator: \"not-in\" } });\n })\n ).rejects.toThrow(/delete the whole table/);\n const left = (await storage.getAll()) ?? [];\n expect(names(left)).toEqual([\"a\", \"b\", \"c\", \"d\"]);\n },\n opts.timeout\n );\n\n itImpl(\n \"keeps empty criteria a no-op there too\",\n async () => {\n // The other half of the same bypass: unguarded this builds\n // `DELETE FROM t WHERE ` and fails with a SQL syntax error.\n await storage.withTransaction(async (tx) => {\n await tx.deleteSearch({});\n });\n const left = (await storage.getAll()) ?? [];\n expect(names(left)).toEqual([\"a\", \"b\", \"c\", \"d\"]);\n },\n opts.timeout\n );\n });\n });\n}\n",
|
|
14
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { JoinedRow, JoinSpec, JoinType } from \"@workglow/storage\";\nimport { BaseTabularStorage } from \"@workglow/storage\";\nimport { describe, expect, it } from \"vitest\";\nimport type {\n AuthorStorage,\n JoinFixtureAuthor,\n JoinFixturePost,\n PostStorage,\n} from \"../joinFixtures\";\n\ntype Spec = JoinSpec<JoinFixturePost, JoinFixtureAuthor, JoinType>;\n\n/**\n * One value a `JoinSpec` field can take, with a name the test title reads.\n *\n * `undefined` is a value like any other here — \"no `orderBy`\" is the case that\n * decides whether the hash join may bound its left read, so leaving it out\n * would drop the axis's most load-bearing point.\n */\ninterface Variant<K extends keyof Spec> {\n readonly label: string;\n readonly value: Spec[K];\n}\n\n/**\n * Every field of `JoinSpec`, with the values this block varies it over.\n *\n * Mapped over `Required<Spec>` rather than written as a loose record: a field\n * added to `JoinSpec` makes this object fail to typecheck until it declares\n * what the two strategies should agree about for it. That is the whole point —\n * a new join option is covered because it cannot compile otherwise, not\n * because someone remembered this file.\n */\ntype SpecAxes = { readonly [K in keyof Required<Spec>]: ReadonlyArray<Variant<K>> };\n\nconst AXES: SpecAxes = {\n type: [\n { label: \"inner\", value: \"inner\" },\n { label: \"left\", value: \"left\" },\n ],\n on: [\n { label: \"one key\", value: [{ left: \"author_id\", right: \"id\" }] },\n {\n label: \"a compound key\",\n value: [\n { left: \"tenant\", right: \"tenant\" },\n { left: \"author_id\", right: \"id\" },\n ],\n },\n ],\n where: [\n { label: \"no filter\", value: undefined },\n { label: \"a left filter\", value: { left: { views: { value: 5, operator: \">\" } } } },\n { label: \"a right filter\", value: { right: { name: \"Ann\" } } },\n { label: \"a null right filter\", value: { right: { country: null } } },\n {\n label: \"both filters\",\n value: { left: { tenant: \"t1\" }, right: { name: \"Ann\" } },\n },\n ],\n orderBy: [\n // No order at all: the one case where the hash join is free to bound its\n // left read, so its result has to match a pushdown that read everything.\n { label: \"no order\", value: undefined },\n {\n label: \"a left order\",\n value: [{ side: \"left\", column: \"views\", direction: \"DESC\" }],\n },\n {\n // A nullable left column. The hash join refuses to push this into the\n // left query because the backends disagree with it about where nulls\n // sort — so this is the pair most likely to come back differently.\n label: \"a nullable left order\",\n value: [\n { side: \"left\", column: \"author_id\", direction: \"ASC\" },\n { side: \"left\", column: \"id\", direction: \"ASC\" },\n ],\n },\n {\n label: \"a right order\",\n value: [\n { side: \"right\", column: \"name\", direction: \"ASC\" },\n { side: \"left\", column: \"id\", direction: \"ASC\" },\n ],\n },\n {\n label: \"a descending right order\",\n value: [\n { side: \"right\", column: \"name\", direction: \"DESC\" },\n { side: \"left\", column: \"id\", direction: \"DESC\" },\n ],\n },\n ],\n limit: [\n { label: \"no limit\", value: undefined },\n { label: \"a limit of 2\", value: 2 },\n // Larger than the joined row count, so the two paths agree on what\n // \"bounded by more rows than exist\" means.\n { label: \"a limit of 50\", value: 50 },\n ],\n offset: [\n { label: \"no offset\", value: undefined },\n { label: \"an offset of 1\", value: 1 },\n { label: \"an offset past the end\", value: 99 },\n ],\n};\n\n/** The first variant of each axis — what every other axis is varied against. */\nconst BASE: Spec = {\n type: AXES.type[0].value,\n on: AXES.on[0].value,\n};\n\n/**\n * One spec per axis value, holding every other axis at its first variant, plus\n * the combinations where two axes interact.\n *\n * A full cross product is 600 specs and most of them prove the same thing\n * twice. What is NOT dropped is the pairing of `orderBy` with `limit`/`offset`:\n * the joined order decides whether the hash join may stop reading early, and a\n * bound is what makes stopping early observable, so a one-axis-at-a-time sweep\n * would never run the two together and would miss exactly the divergence this\n * block exists for.\n */\nfunction buildSpecs(): Array<{ readonly label: string; readonly spec: Spec }> {\n const specs: Array<{ label: string; spec: Spec }> = [];\n const seen = new Set<string>();\n const add = (label: string, spec: Spec): void => {\n const key = JSON.stringify(spec);\n if (seen.has(key)) return;\n seen.add(key);\n specs.push({ label, spec });\n };\n\n for (const key of Object.keys(AXES) as Array<keyof SpecAxes>) {\n for (const variant of AXES[key]) {\n add(variant.label, { ...BASE, [key]: variant.value } as Spec);\n }\n }\n\n for (const type of AXES.type) {\n for (const order of AXES.orderBy) {\n for (const limit of AXES.limit) {\n for (const offset of AXES.offset) {\n add(`${type.label} join, ${order.label}, ${limit.label}, ${offset.label}`, {\n ...BASE,\n type: type.value,\n orderBy: order.value,\n limit: limit.value,\n offset: offset.value,\n } as Spec);\n }\n }\n }\n }\n\n return specs;\n}\n\nconst SPECS = buildSpecs();\n\n/**\n * Rows of `part` that `whole` cannot account for, counting repeats.\n *\n * Membership alone is too weak: every element of `[p1, p1]` is a member of a\n * `whole` holding one `p1`, so a strategy that returned a row twice where the\n * join produces it once would pass a containment check while having invented a\n * row.\n */\nfunction multisetRemainder(part: readonly string[], whole: readonly string[]): string[] {\n const available = new Map<string, number>();\n for (const row of whole) available.set(row, (available.get(row) ?? 0) + 1);\n const unaccounted: string[] = [];\n for (const row of part) {\n const left = available.get(row) ?? 0;\n if (left === 0) unaccounted.push(row);\n else available.set(row, left - 1);\n }\n return unaccounted;\n}\n\n/** How many rows `spec`'s window leaves of a join producing `total` of them. */\nfunction windowLength(total: number, spec: Spec): number {\n const remaining = Math.max(0, total - (spec.offset ?? 0));\n return spec.limit === undefined ? remaining : Math.min(spec.limit, remaining);\n}\n\n/** Rows reduced to what both strategies must agree on. */\nfunction shape(\n rows: ReadonlyArray<JoinedRow<JoinFixturePost, JoinFixtureAuthor, JoinType>>\n): string[] {\n return rows.map((row) => `${row.left.id}:${row.right?.id ?? \"-\"}`);\n}\n\n/**\n * How much of a result the two strategies are actually held to, given the spec.\n *\n * A join with no `orderBy` has no defined row order — `join` promises that\n * `orderBy`, `limit` and `offset` apply to the joined rows, never what order\n * the joined rows arrive in when nothing asked. SQLite and Postgres happen to\n * return them in a stable order and DuckDB, being columnar and parallel, does\n * not; holding the strategies to a shared order there would assert the storage\n * engine's row layout rather than the contract, and would fail on a backend\n * that is behaving correctly.\n *\n * So an unordered join is compared as a SET, and an unordered join that is also\n * windowed is compared only by how many rows came back and whether each is a\n * row the unwindowed join would have returned. That is the whole of what is\n * determined: a window into an undefined order names no particular rows.\n */\nfunction sortRows(rows: JoinRows): JoinRows {\n return [...rows].sort((a, b) =>\n `${a.left.id}:${a.right?.id ?? \"-\"}`.localeCompare(`${b.left.id}:${b.right?.id ?? \"-\"}`)\n );\n}\n\nfunction comparisonFor(spec: Spec): \"ordered\" | \"set\" | \"window\" {\n if ((spec.orderBy?.length ?? 0) > 0) return \"ordered\";\n return spec.limit === undefined && spec.offset === undefined ? \"set\" : \"window\";\n}\n\ntype JoinRows = Array<JoinedRow<JoinFixturePost, JoinFixtureAuthor, JoinType>>;\n\n/**\n * The inherited hash join, typed for these two fixtures.\n *\n * One cast, at the one place the erased generics of a prototype method have to\n * be restated, rather than a cast per call site.\n */\ntype HashJoinFn = (this: PostStorage, spec: Spec, right: AuthorStorage) => Promise<JoinRows>;\n\n/**\n * Runs `spec` through the inherited hash join, on the same two instances the\n * pushdown just ran on.\n *\n * Reaching for the base implementation rather than standing a second storage up\n * on another connection is what keeps the comparison honest: same data, same\n * backend, same driver, one difference. A second connection would also change\n * which rows are visible, and a divergence would no longer say which half\n * caused it.\n */\nconst viaHashJoin = BaseTabularStorage.prototype.join as unknown as HashJoinFn;\n\n/**\n * The two join strategies answer the same question the same way.\n *\n * `ITabularStorage.join` has one documented semantics and two implementations —\n * a single `JOIN` statement when both tables share a connection, and an\n * application-side hash join otherwise — and which one runs is decided by the\n * planner, not by the caller. So a caller cannot tell them apart, and any\n * disagreement between them is a wrong answer that no error reports and that\n * changes with where the right-hand storage happens to live.\n *\n * That has already happened once: a pushdown chosen against a right side\n * enlisted in a transaction it could not see returned zero rows from an inner\n * join where the same call through the fallback returned the row. The block\n * that covered `join` at the time stayed green throughout, because it asserted\n * which path ran and never compared what the two paths returned.\n *\n * This asserts the second thing. It runs only where both strategies are\n * reachable — a pair the planner pushes down — and compares them over specs\n * derived from the option space rather than a list, so an option added to\n * `JoinSpec` is covered by construction.\n */\nexport function joinStrategyParityBlock(\n createPosts: () => Promise<PostStorage>,\n createAuthors: () => Promise<AuthorStorage>,\n seed: (posts: PostStorage, authors: AuthorStorage) => Promise<void>,\n opts: { readonly expectSqlPushdown?: boolean; readonly timeout?: number }\n): void {\n describe.skipIf(opts.expectSqlPushdown !== true)(\"join strategy parity\", () => {\n it(\n \"states, for every field of a join spec, what the two strategies agree about\",\n () => {\n // The ratchet's runtime half. `SpecAxes` already makes a new `JoinSpec`\n // field a compile error; this catches the other way of emptying an\n // axis — declaring it with no variants — which would type fine and\n // silently stop varying the field.\n const empty = (Object.keys(AXES) as Array<keyof SpecAxes>).filter(\n (key) => AXES[key].length === 0\n );\n expect(empty).toEqual([]);\n expect(SPECS.length).toBeGreaterThan(AXES.orderBy.length * AXES.limit.length);\n },\n opts.timeout\n );\n\n for (const { label, spec } of SPECS) {\n it(\n `the pushdown and the hash join agree on ${label}`,\n async () => {\n const posts = await createPosts();\n const authors = await createAuthors();\n try {\n await posts.setupDatabase?.();\n await authors.setupDatabase?.();\n await seed(posts, authors);\n\n const pushedDown = await posts.join(spec, authors);\n const hashed = await viaHashJoin.call(posts, spec, authors);\n\n switch (comparisonFor(spec)) {\n case \"ordered\": {\n expect(shape(pushedDown)).toEqual(shape(hashed));\n // Not just the pairing: a strategy that returns the right rows\n // with a column missing or a value round-tripped differently is\n // the same wrong answer one layer down.\n expect(pushedDown).toEqual(hashed);\n break;\n }\n case \"set\": {\n expect(shape(pushedDown).sort()).toEqual(shape(hashed).sort());\n expect(sortRows(pushedDown)).toEqual(sortRows(hashed));\n break;\n }\n case \"window\": {\n const whole = shape(\n await posts.join({ ...spec, limit: undefined, offset: undefined }, authors)\n );\n // WHICH rows a window over an undefined order selects is not\n // determined, so the two are not held to the same ones — that\n // is what a columnar backend fails while behaving correctly.\n // Two things are determined. How many rows come back, measured\n // against the unwindowed join rather than against each other:\n // agreeing on a wrong count is still wrong, and a strategy that\n // bounded its left read instead of the joined rows gets it\n // wrong. And that every row returned is one the unwindowed join\n // produced, counted WITH its repeats, so a duplicated row is\n // caught rather than passing as a member.\n //\n // That the two agree on the unwindowed rows at all is asserted\n // by the \"set\" comparison, which the same axis sweep reaches\n // with this spec's limit and offset dropped.\n const expectedLength = windowLength(whole.length, spec);\n expect(shape(pushedDown)).toHaveLength(expectedLength);\n expect(shape(hashed)).toHaveLength(expectedLength);\n expect(multisetRemainder(shape(pushedDown), whole)).toEqual([]);\n expect(multisetRemainder(shape(hashed), whole)).toEqual([]);\n break;\n }\n }\n } finally {\n await posts.deleteAll();\n await authors.deleteAll();\n posts.destroy?.();\n authors.destroy?.();\n }\n },\n opts.timeout\n );\n }\n });\n}\n",
|
|
15
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ITabularStorage, TabularChangePayload } from \"@workglow/storage\";\nimport { sleep } from \"@workglow/util\";\nimport type { FromSchema } from \"@workglow/util/schema\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport type { CompoundPrimaryKeyNames, CompoundSchema } from \"../fixtures\";\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type { TabularStorageContractOpts } from \"../types\";\n\n/**\n * Subscription contract. Selection between the two blocks below is driven by\n * `opts.usesPolling`:\n *\n * - `usesPolling: false` → `subscribeToChanges.eventDriven` — strict commit\n * order (event-driven subscriptions like Postgres LISTEN/NOTIFY or the\n * in-memory broadcast bus emit one event per write, in write order).\n * - `usesPolling: true` → `subscribeToChanges.polling` — set equality plus\n * event count (polling-based subscriptions diff a snapshot and have no\n * way to preserve commit order).\n *\n * When `capabilities.supportsSubscriptions` is true, `usesPolling` is\n * required on the contract opts; the type guarantees this at compile time.\n */\nexport function subscribeToChangesBlock(opts: TabularStorageContractOpts): void {\n if (!opts.capabilities.supportsSubscriptions) {\n describe.skip(\"subscribeToChanges\", () => {\n it(\"skipped: capabilities.supportsSubscriptions === false\", () => {});\n });\n return;\n }\n\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itImpl = expectFails.has(\"subscribeToChanges\") ? itExpectFail : it;\n // `usesPolling` is required when `supportsSubscriptions: true` (enforced by\n // the discriminated `TabularStorageContractOpts` union). The non-null\n // assertion below is safe given the early-return on `supportsSubscriptions`.\n const usesPolling = opts.usesPolling!;\n const pollingIntervalMs = opts.pollingIntervalMs ?? 1;\n const waitTime = usesPolling ? Math.max(pollingIntervalMs * 8, 200) : 50;\n const initWaitTime = usesPolling ? Math.max(pollingIntervalMs * 10, 300) : 10;\n\n if (usesPolling) {\n describe(\"subscribeToChanges.polling\", () => {\n let storage: ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>;\n\n beforeEach(async () => {\n storage = await opts.createStorage();\n await storage.setupDatabase?.();\n });\n\n afterEach(async () => {\n await storage.deleteAll();\n storage.destroy?.();\n await opts.releaseStorage?.(storage);\n });\n\n itImpl(\n \"observes every write (set equality + count, order unspecified)\",\n async () => {\n const changes: TabularChangePayload<FromSchema<typeof CompoundSchema>>[] = [];\n const unsubscribe = storage.subscribeToChanges((change) => changes.push(change), {\n pollingIntervalMs,\n });\n\n await sleep(initWaitTime);\n\n await storage.put({ name: \"t1\", type: \"s1\", option: \"v1\", success: true });\n await storage.put({ name: \"t2\", type: \"s2\", option: \"v2\", success: false });\n await storage.put({ name: \"t3\", type: \"s3\", option: \"v3\", success: true });\n\n await sleep(waitTime);\n\n const writeEvents = changes.filter((c) => c.type === \"INSERT\" || c.type === \"UPDATE\");\n // Polling diffs a snapshot: every write must be visible exactly once,\n // but commit order is unspecified.\n expect(writeEvents.length).toBe(3);\n const options = writeEvents\n .map((e) => e.new?.option)\n .sort((a, b) => String(a).localeCompare(String(b)));\n expect(options).toEqual([\"v1\", \"v2\", \"v3\"]);\n\n unsubscribe();\n },\n opts.timeout\n );\n });\n return;\n }\n\n describe(\"subscribeToChanges.eventDriven\", () => {\n let storage: ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>;\n\n beforeEach(async () => {\n storage = await opts.createStorage();\n await storage.setupDatabase?.();\n });\n\n afterEach(async () => {\n await storage.deleteAll();\n storage.destroy?.();\n await opts.releaseStorage?.(storage);\n });\n\n itImpl(\n \"fires exactly once per write in commit order\",\n async () => {\n const changes: TabularChangePayload<FromSchema<typeof CompoundSchema>>[] = [];\n const unsubscribe = storage.subscribeToChanges((change) => changes.push(change), {\n pollingIntervalMs,\n });\n\n await sleep(initWaitTime);\n\n await storage.put({ name: \"t1\", type: \"s1\", option: \"v1\", success: true });\n await storage.put({ name: \"t2\", type: \"s2\", option: \"v2\", success: false });\n await storage.put({ name: \"t3\", type: \"s3\", option: \"v3\", success: true });\n\n await sleep(waitTime);\n\n const writeEvents = changes.filter((c) => c.type === \"INSERT\" || c.type === \"UPDATE\");\n // Event-driven backends must emit one event per write in commit order.\n expect(writeEvents.length).toBe(3);\n expect(writeEvents[0].new?.option).toBe(\"v1\");\n expect(writeEvents[1].new?.option).toBe(\"v2\");\n expect(writeEvents[2].new?.option).toBe(\"v3\");\n\n unsubscribe();\n },\n opts.timeout\n );\n });\n}\n",
|
|
16
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ITabularStorage } from \"@workglow/storage\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type {\n TabularStorageContractOpts,\n VectorItemPrimaryKeyNames,\n VectorItemSchema,\n} from \"../types\";\n\nexport function vectorColumnFormatBlock(opts: TabularStorageContractOpts): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itImpl = expectFails.has(\"vectorColumnFormat\") ? itExpectFail : it;\n\n describe.skipIf(!opts.capabilities.supportsVectorColumns)(\"vectorColumnFormat\", () => {\n let storage: ITabularStorage<typeof VectorItemSchema, typeof VectorItemPrimaryKeyNames>;\n\n beforeEach(async () => {\n if (!opts.createVectorStorage) {\n throw new Error(\n \"createVectorStorage is required when capabilities.supportsVectorColumns is true\"\n );\n }\n storage = await opts.createVectorStorage();\n await storage.setupDatabase?.();\n });\n\n afterEach(async () => {\n await storage.deleteAll();\n storage.destroy?.();\n await opts.releaseStorage?.(storage);\n });\n\n itImpl(\n \"round-trip preserves Float32Array type and values\",\n async () => {\n const original = new Float32Array([0.1, 0.2, 0.3]);\n\n await storage.put({ id: \"v1\", embedding: original as any });\n\n const retrieved = await storage.get({ id: \"v1\" });\n expect(retrieved).toBeDefined();\n\n const embedding = (retrieved as any)?.embedding;\n expect(embedding).toBeInstanceOf(Float32Array);\n expect(embedding[0]).toBeCloseTo(0.1, 5);\n expect(embedding[1]).toBeCloseTo(0.2, 5);\n expect(embedding[2]).toBeCloseTo(0.3, 5);\n },\n opts.timeout\n );\n });\n}\n",
|
|
17
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n ConnectionReentryError,\n withConnectionTransaction,\n type ITabularStorage,\n} from \"@workglow/storage\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport type { CompoundPrimaryKeyNames, CompoundSchema } from \"../fixtures\";\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type { TabularStorageContractOpts } from \"../types\";\n\nexport function withConnectionTransactionBlock(opts: TabularStorageContractOpts): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itImpl = expectFails.has(\"withConnectionTransaction\") ? itExpectFail : it;\n\n describe.skipIf(\n !opts.capabilities.supportsTransactions || opts.createSiblingStorage === undefined\n )(\"withConnectionTransaction\", () => {\n let primary: ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>;\n let sibling: ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>;\n let outsider: ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>;\n\n beforeEach(async () => {\n primary = await opts.createStorage();\n await primary.setupDatabase?.();\n sibling = await opts.createSiblingStorage!(primary);\n await sibling.setupDatabase?.();\n outsider = await opts.createSiblingStorage!(primary);\n await outsider.setupDatabase?.();\n });\n\n afterEach(async () => {\n await outsider?.deleteAll();\n outsider?.destroy?.();\n await sibling?.deleteAll();\n sibling?.destroy?.();\n await primary?.deleteAll();\n primary?.destroy?.();\n // Siblings share `primary`'s handle, so they are released with it —\n // release last, after every storage on that handle is destroyed.\n await opts.releaseStorage?.(primary);\n });\n\n itImpl(\n \"commits writes across two tables\",\n async () => {\n await withConnectionTransaction([primary, sibling], async () => {\n await primary.put({ name: \"from-a\", type: \"x\", option: \"va\", success: true });\n await sibling.put({ name: \"from-b\", type: \"x\", option: \"vb\", success: true });\n });\n expect(await primary.get({ name: \"from-a\", type: \"x\" })).toMatchObject({ option: \"va\" });\n expect(await sibling.get({ name: \"from-b\", type: \"x\" })).toMatchObject({ option: \"vb\" });\n },\n opts.timeout\n );\n\n itImpl(\n \"rolls back both tables when the callback throws\",\n async () => {\n await primary.put({ name: \"kept\", type: \"x\", option: \"base\", success: true });\n\n await expect(\n withConnectionTransaction([primary, sibling], async () => {\n await primary.put({ name: \"from-a\", type: \"x\", option: \"va\", success: true });\n await sibling.put({ name: \"from-b\", type: \"x\", option: \"vb\", success: true });\n throw new Error(\"forced rollback\");\n })\n ).rejects.toThrow(\"forced rollback\");\n\n expect(await primary.get({ name: \"kept\", type: \"x\" })).toBeDefined();\n expect(await primary.get({ name: \"from-a\", type: \"x\" })).toBeUndefined();\n expect(await sibling.get({ name: \"from-b\", type: \"x\" })).toBeUndefined();\n },\n opts.timeout\n );\n\n itImpl(\n \"does not refuse an unrelated concurrent caller\",\n async () => {\n // Issued from outside the transaction body, so it is not an async\n // descendant and its write cannot escape the BEGIN. Refusing it would\n // land the error in a caller that did nothing wrong. Whether it waits\n // for COMMIT (single-session backends chain) or runs at once (a pool\n // hands it another client) is a backend detail — that it completes is\n // not.\n let releaseBody: () => void = () => {};\n const bodyCanFinish = new Promise<void>((resolve) => {\n releaseBody = resolve;\n });\n let signalStarted: () => void = () => {};\n const bodyStarted = new Promise<void>((resolve) => {\n signalStarted = resolve;\n });\n\n const txPromise = withConnectionTransaction([primary, sibling], async () => {\n await primary.put({ name: \"enlisted\", type: \"x\", option: \"ok\", success: true });\n signalStarted();\n await bodyCanFinish;\n });\n await bodyStarted;\n\n let outsiderError: unknown;\n const concurrent = outsider\n .put({ name: \"concurrent\", type: \"x\", option: \"yes\", success: true })\n .catch((err: unknown) => {\n outsiderError = err;\n });\n\n releaseBody();\n await txPromise;\n await concurrent;\n\n expect(outsiderError).toBeUndefined();\n expect(await outsider.get({ name: \"concurrent\", type: \"x\" })).toBeDefined();\n expect(await primary.get({ name: \"enlisted\", type: \"x\" })).toBeDefined();\n },\n opts.timeout\n );\n\n itImpl(\n \"does not refuse a concurrent transaction whose participant set merely differs\",\n async () => {\n // Two units of work on one handle, neither inside the other, opening\n // transactions over overlapping-but-unequal participant sets — the\n // shape any concurrent sweep produces. On a single-session backend only\n // one BEGIN can be open at a time, so the second waits; on a pool it\n // takes another client. Either way it must not be refused.\n let releaseFirst: () => void = () => {};\n const firstCanFinish = new Promise<void>((resolve) => {\n releaseFirst = resolve;\n });\n let signalStarted: () => void = () => {};\n const firstStarted = new Promise<void>((resolve) => {\n signalStarted = resolve;\n });\n\n const first = withConnectionTransaction([primary, sibling], async () => {\n await primary.put({ name: \"first\", type: \"x\", option: \"one\", success: true });\n signalStarted();\n await firstCanFinish;\n });\n await firstStarted;\n\n let secondError: unknown;\n const second = withConnectionTransaction([sibling, outsider], async () => {\n await outsider.put({ name: \"second\", type: \"x\", option: \"two\", success: true });\n }).catch((err: unknown) => {\n secondError = err;\n });\n\n releaseFirst();\n await first;\n await second;\n\n expect(secondError).toBeUndefined();\n expect(await primary.get({ name: \"first\", type: \"x\" })).toBeDefined();\n expect(await outsider.get({ name: \"second\", type: \"x\" })).toBeDefined();\n },\n opts.timeout\n );\n\n itImpl(\n \"does not show uncommitted rows to a concurrent reader\",\n async () => {\n // A connection transaction takes the CONNECTION's chain slot and flags\n // its participants; reads used to take only the per-instance mutex,\n // which the transaction never holds. The two locks never intersected,\n // so on a single-session backend a concurrent read ran on the very\n // session sitting inside the open BEGIN and returned rows the ROLLBACK\n // below erases — a caller could act on a ban, a balance or a lock that\n // never existed.\n //\n // How a backend keeps the reader honest is its business: the\n // single-session ones queue the read behind COMMIT, a real pool hands\n // it another client and answers at once. Both must report the\n // committed value, which is the pre-transaction one here.\n await primary.put({ name: \"iso\", type: \"x\", option: \"before\", success: true });\n\n let releaseBody: () => void = () => {};\n const bodyCanFinish = new Promise<void>((resolve) => {\n releaseBody = resolve;\n });\n let signalStarted: () => void = () => {};\n const bodyStarted = new Promise<void>((resolve) => {\n signalStarted = resolve;\n });\n\n let rollbackError: unknown;\n const txPromise = withConnectionTransaction([primary, sibling], async () => {\n await primary.put({ name: \"iso\", type: \"x\", option: \"dirty\", success: true });\n signalStarted();\n await bodyCanFinish;\n throw new Error(\"forced rollback\");\n }).catch((err: unknown) => {\n rollbackError = err;\n });\n await bodyStarted;\n\n // Issued from the test's own task, so it is not an async descendant of\n // the body — the caller the transaction is supposed to be invisible to.\n const readPromise = primary.get({ name: \"iso\", type: \"x\" });\n // Long enough that a read which is NOT held back has finished.\n await new Promise((resolve) => setTimeout(resolve, 25));\n\n releaseBody();\n await txPromise;\n expect((rollbackError as Error | undefined)?.message).toBe(\"forced rollback\");\n\n expect(await readPromise).toMatchObject({ option: \"before\" });\n expect(await primary.get({ name: \"iso\", type: \"x\" })).toMatchObject({ option: \"before\" });\n },\n opts.timeout\n );\n\n itImpl(\n \"does not show uncommitted rows to a concurrent pushed-down join\",\n async () => {\n // The single-statement join path used to take only the per-instance\n // mutex, so once every other read moved onto the connection chain it\n // was the one entry point left reading off the session inside the open\n // BEGIN — and the worst one to leave there, since it reads two tables\n // in a single statement.\n //\n // Both storages are enlisted, so the join is not handed to the hash\n // fallback (which reads through the already-guarded `query`) and the\n // pushdown is what actually runs.\n await primary.put({ name: \"join-iso\", type: \"j\", option: \"before\", success: true });\n await sibling.put({ name: \"join-key\", type: \"j\", option: \"rhs\", success: true });\n\n let releaseBody: () => void = () => {};\n const bodyCanFinish = new Promise<void>((resolve) => {\n releaseBody = resolve;\n });\n let signalStarted: () => void = () => {};\n const bodyStarted = new Promise<void>((resolve) => {\n signalStarted = resolve;\n });\n\n let rollbackError: unknown;\n const txPromise = withConnectionTransaction([primary, sibling], async () => {\n await primary.put({ name: \"join-iso\", type: \"j\", option: \"dirty\", success: true });\n signalStarted();\n await bodyCanFinish;\n throw new Error(\"forced rollback\");\n }).catch((err: unknown) => {\n rollbackError = err;\n });\n await bodyStarted;\n\n // Issued from the test's own task, so it is not an async descendant of\n // the body — the caller the transaction is supposed to be invisible to.\n const joinPromise = primary.join(\n { type: \"inner\", on: [{ left: \"type\", right: \"type\" }] },\n sibling\n );\n // Long enough that a join which is NOT held back has finished.\n await new Promise((resolve) => setTimeout(resolve, 25));\n\n releaseBody();\n await txPromise;\n expect((rollbackError as Error | undefined)?.message).toBe(\"forced rollback\");\n\n const rows = await joinPromise;\n expect(rows).toHaveLength(1);\n expect(rows[0]!.left).toMatchObject({ name: \"join-iso\", option: \"before\" });\n expect(rows[0]!.right).toMatchObject({ name: \"join-key\", option: \"rhs\" });\n },\n opts.timeout\n );\n\n itImpl(\n \"throws sibling-op for a storage that was not enlisted\",\n async () => {\n let error: unknown;\n await withConnectionTransaction([primary, sibling], async () => {\n await primary.put({ name: \"enlisted\", type: \"x\", option: \"ok\", success: true });\n try {\n await outsider.put({ name: \"outsider\", type: \"x\", option: \"no\", success: true });\n } catch (err) {\n error = err;\n }\n });\n\n expect(error).toBeInstanceOf(ConnectionReentryError);\n expect((error as ConnectionReentryError).mode).toBe(\"sibling-op\");\n expect(await outsider.get({ name: \"outsider\", type: \"x\" })).toBeUndefined();\n expect(await primary.get({ name: \"enlisted\", type: \"x\" })).toBeDefined();\n },\n opts.timeout\n );\n });\n}\n",
|
|
18
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ITabularStorage } from \"@workglow/storage\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport type { CompoundPrimaryKeyNames, CompoundSchema } from \"../fixtures\";\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type { TabularStorageContractOpts } from \"../types\";\n\nexport function withTransactionRollbackBlock(opts: TabularStorageContractOpts): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itImpl = expectFails.has(\"withTransactionRollback\") ? itExpectFail : it;\n\n describe.skipIf(!opts.capabilities.supportsTransactions)(\"withTransactionRollback\", () => {\n let storage: ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>;\n\n beforeEach(async () => {\n storage = await opts.createStorage();\n await storage.setupDatabase?.();\n });\n\n afterEach(async () => {\n await storage.deleteAll();\n storage.destroy?.();\n await opts.releaseStorage?.(storage);\n });\n\n itImpl(\n \"rolls back writes when callback throws\",\n async () => {\n const baseline = { name: \"before\", type: \"tx\", option: \"base\", success: true };\n await storage.put(baseline);\n\n await expect(\n storage.withTransaction(async (tx) => {\n await tx.put({ name: \"inside\", type: \"tx\", option: \"intra\", success: false });\n throw new Error(\"forced rollback\");\n })\n ).rejects.toBeDefined();\n\n const intraRow = await storage.get({ name: \"inside\", type: \"tx\" });\n expect(intraRow).toBeUndefined();\n\n const beforeRow = await storage.get({ name: \"before\", type: \"tx\" });\n expect(beforeRow).toBeDefined();\n expect(beforeRow?.option).toBe(\"base\");\n },\n opts.timeout\n );\n });\n}\n",
|
|
19
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ITabularStorage } from \"@workglow/storage\";\nimport type { DataPortSchemaObject, FromSchema } from \"@workglow/util/schema\";\n\nexport const AuthorPrimaryKeyNames = [\"id\"] as const;\nexport const AuthorSchema = {\n type: \"object\",\n properties: {\n id: { type: \"string\" },\n tenant: { type: \"string\" },\n name: { type: \"string\" },\n country: { anyOf: [{ type: \"string\" }, { type: \"null\" }] },\n },\n required: [\"id\", \"tenant\", \"name\", \"country\"],\n additionalProperties: false,\n} as const satisfies DataPortSchemaObject;\n\nexport const PostPrimaryKeyNames = [\"id\"] as const;\nexport const PostSchema = {\n type: \"object\",\n properties: {\n id: { type: \"string\" },\n tenant: { type: \"string\" },\n author_id: { anyOf: [{ type: \"string\" }, { type: \"null\" }] },\n title: { type: \"string\" },\n views: { type: \"number\" },\n },\n required: [\"id\", \"tenant\", \"author_id\", \"title\", \"views\"],\n additionalProperties: false,\n} as const satisfies DataPortSchemaObject;\n\nexport type AuthorStorage = ITabularStorage<typeof AuthorSchema, typeof AuthorPrimaryKeyNames>;\nexport type PostStorage = ITabularStorage<typeof PostSchema, typeof PostPrimaryKeyNames>;\nexport type JoinFixtureAuthor = FromSchema<typeof AuthorSchema>;\nexport type JoinFixturePost = FromSchema<typeof PostSchema>;\n\nexport const JOIN_FIXTURE_AUTHORS: JoinFixtureAuthor[] = [\n { id: \"a1\", tenant: \"t1\", name: \"Ann\", country: \"US\" },\n { id: \"a2\", tenant: \"t1\", name: \"Bob\", country: null },\n { id: \"a3\", tenant: \"t2\", name: \"Cid\", country: \"FR\" },\n];\nexport const JOIN_FIXTURE_POSTS: JoinFixturePost[] = [\n { id: \"p1\", tenant: \"t1\", author_id: \"a1\", title: \"one\", views: 10 },\n { id: \"p2\", tenant: \"t1\", author_id: \"a1\", title: \"two\", views: 5 },\n { id: \"p3\", tenant: \"t1\", author_id: \"a2\", title: \"three\", views: 7 },\n { id: \"p4\", tenant: \"t1\", author_id: \"zz\", title: \"orphan\", views: 1 },\n { id: \"p5\", tenant: \"t1\", author_id: null, title: \"anon\", views: 3 },\n { id: \"p6\", tenant: \"t2\", author_id: \"a1\", title: \"cross\", views: 9 },\n];\n\n/** Loads both fixture tables, right side first so a left read never races it. */\nexport async function seedJoinFixtures(\n posts: ITabularStorage<typeof PostSchema, typeof PostPrimaryKeyNames>,\n authors: ITabularStorage<typeof AuthorSchema, typeof AuthorPrimaryKeyNames>\n): Promise<void> {\n await authors.putBulk(JOIN_FIXTURE_AUTHORS);\n await posts.putBulk(JOIN_FIXTURE_POSTS);\n}\n",
|
|
20
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { JoinedRow } from \"@workglow/storage\";\nimport { StorageInvalidColumnError, StorageValidationError } from \"@workglow/storage\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { joinBoundedLeftReadBlock } from \"./assertions/joinBoundedLeftRead\";\nimport { joinStrategyParityBlock } from \"./assertions/strategyParity\";\nimport type {\n AuthorStorage,\n JoinFixtureAuthor,\n JoinFixturePost,\n PostStorage,\n} from \"./joinFixtures\";\nimport { JOIN_FIXTURE_AUTHORS, JOIN_FIXTURE_POSTS, seedJoinFixtures } from \"./joinFixtures\";\n\nexport {\n AuthorPrimaryKeyNames,\n AuthorSchema,\n JOIN_FIXTURE_AUTHORS,\n JOIN_FIXTURE_POSTS,\n PostPrimaryKeyNames,\n PostSchema,\n seedJoinFixtures,\n} from \"./joinFixtures\";\nexport type {\n AuthorStorage,\n JoinFixtureAuthor,\n JoinFixturePost,\n PostStorage,\n} from \"./joinFixtures\";\n\ntype Author = JoinFixtureAuthor;\ntype Post = JoinFixturePost;\n\nexport interface JoinTestOptions {\n /**\n * Which path the pair is expected to take. The proof is backend-agnostic:\n * the right storage's `query` is never called on the pushed-down path\n * (`true`) and always called on the hash-join path (`false`). Leave it unset\n * for a wrapper pair — a wrapper hands its inner storage to the join, so\n * the call lands on the inner's `query`, not the wrapper's.\n */\n readonly expectSqlPushdown?: boolean;\n readonly timeout?: number;\n}\n\nconst pairs = (rows: ReadonlyArray<JoinedRow<Post, Author, any>>): string[] =>\n rows.map((r) => `${r.left.id}:${r.right?.id ?? \"-\"}`);\n\n/**\n * Shared join behaviour, run per backend pair. `createPosts` is the left\n * side; `createAuthors` the right. Both are created fresh for every test.\n */\nexport function runTabularJoinContract(\n createPosts: () => Promise<PostStorage>,\n createAuthors: () => Promise<AuthorStorage>,\n opts: JoinTestOptions = {}\n): void {\n const on = [{ left: \"author_id\", right: \"id\" }] as const;\n\n describe(`join (${opts.expectSqlPushdown ? \"SQL pushdown\" : \"hash join\"})`, () => {\n let posts: PostStorage;\n let authors: AuthorStorage;\n\n beforeEach(async () => {\n posts = await createPosts();\n authors = await createAuthors();\n await posts.setupDatabase?.();\n await authors.setupDatabase?.();\n await seedJoinFixtures(posts, authors);\n });\n\n afterEach(async () => {\n await posts.deleteAll();\n await authors.deleteAll();\n posts.destroy?.();\n authors.destroy?.();\n vi.restoreAllMocks();\n });\n\n it.skipIf(opts.expectSqlPushdown === undefined)(\n \"takes the expected execution path\",\n async () => {\n const rightQuery = vi.spyOn(authors, \"query\");\n await posts.join({ type: \"inner\", on }, authors);\n if (opts.expectSqlPushdown) {\n expect(rightQuery).not.toHaveBeenCalled();\n } else {\n expect(rightQuery).toHaveBeenCalled();\n }\n },\n opts.timeout\n );\n\n it(\n \"inner join pairs every match and drops orphans and null keys\",\n async () => {\n const rows = await posts.join({ type: \"inner\", on }, authors);\n expect(pairs(rows).sort()).toEqual([\"p1:a1\", \"p2:a1\", \"p3:a2\", \"p6:a1\"]);\n const row = rows.find((r) => r.left.id === \"p1\");\n expect(row?.left).toEqual(JOIN_FIXTURE_POSTS[0]);\n expect(row?.right).toEqual(JOIN_FIXTURE_AUTHORS[0]);\n },\n opts.timeout\n );\n\n it(\n \"left join keeps orphans and null keys with right undefined\",\n async () => {\n const rows = await posts.join({ type: \"left\", on }, authors);\n expect(pairs(rows).sort()).toEqual([\"p1:a1\", \"p2:a1\", \"p3:a2\", \"p4:-\", \"p5:-\", \"p6:a1\"]);\n expect(rows.find((r) => r.left.id === \"p4\")?.right).toBeUndefined();\n expect(rows.find((r) => r.left.id === \"p5\")?.right).toBeUndefined();\n },\n opts.timeout\n );\n\n it(\n \"a right-side filter under a left join does not drop unmatched left rows\",\n async () => {\n const rows = await posts.join(\n { type: \"left\", on, where: { right: { name: \"Ann\" } } },\n authors\n );\n expect(pairs(rows).sort()).toEqual([\"p1:a1\", \"p2:a1\", \"p3:-\", \"p4:-\", \"p5:-\", \"p6:a1\"]);\n },\n opts.timeout\n );\n\n it(\n \"a right-side filter under an inner join narrows the result\",\n async () => {\n const rows = await posts.join(\n { type: \"inner\", on, where: { right: { country: null } } },\n authors\n );\n expect(pairs(rows)).toEqual([\"p3:a2\"]);\n },\n opts.timeout\n );\n\n it(\n \"a left-side filter narrows the left rows\",\n async () => {\n const rows = await posts.join(\n { type: \"left\", on, where: { left: { views: { value: 5, operator: \">\" } } } },\n authors\n );\n expect(pairs(rows).sort()).toEqual([\"p1:a1\", \"p3:a2\", \"p6:a1\"]);\n },\n opts.timeout\n );\n\n it(\n \"joins on a compound key\",\n async () => {\n const rows = await posts.join(\n {\n type: \"inner\",\n on: [\n { left: \"tenant\", right: \"tenant\" },\n { left: \"author_id\", right: \"id\" },\n ],\n },\n authors\n );\n expect(pairs(rows).sort()).toEqual([\"p1:a1\", \"p2:a1\", \"p3:a2\"]);\n },\n opts.timeout\n );\n\n it(\n \"orders by a left column\",\n async () => {\n const rows = await posts.join(\n { type: \"left\", on, orderBy: [{ side: \"left\", column: \"views\", direction: \"DESC\" }] },\n authors\n );\n expect(rows.map((r) => r.left.views)).toEqual([10, 9, 7, 5, 3, 1]);\n },\n opts.timeout\n );\n\n it(\n \"orders by a right column with unmatched rows first under ASC\",\n async () => {\n const rows = await posts.join(\n {\n type: \"left\",\n on,\n orderBy: [\n { side: \"right\", column: \"name\", direction: \"ASC\" },\n { side: \"left\", column: \"id\", direction: \"ASC\" },\n ],\n },\n authors\n );\n expect(pairs(rows)).toEqual([\"p4:-\", \"p5:-\", \"p1:a1\", \"p2:a1\", \"p6:a1\", \"p3:a2\"]);\n },\n opts.timeout\n );\n\n it(\n \"orders by a right column with unmatched rows last under DESC\",\n async () => {\n const rows = await posts.join(\n {\n type: \"left\",\n on,\n orderBy: [\n { side: \"right\", column: \"name\", direction: \"DESC\" },\n { side: \"left\", column: \"id\", direction: \"DESC\" },\n ],\n },\n authors\n );\n expect(pairs(rows)).toEqual([\"p3:a2\", \"p6:a1\", \"p2:a1\", \"p1:a1\", \"p5:-\", \"p4:-\"]);\n },\n opts.timeout\n );\n\n it(\n \"applies limit and offset to the joined rows\",\n async () => {\n const rows = await posts.join(\n {\n type: \"inner\",\n on,\n orderBy: [{ side: \"left\", column: \"id\", direction: \"ASC\" }],\n offset: 1,\n limit: 2,\n },\n authors\n );\n expect(pairs(rows)).toEqual([\"p2:a1\", \"p3:a2\"]);\n },\n opts.timeout\n );\n\n it(\n \"offset without limit skips rows\",\n async () => {\n const rows = await posts.join(\n {\n type: \"inner\",\n on,\n orderBy: [{ side: \"left\", column: \"id\", direction: \"ASC\" }],\n offset: 3,\n },\n authors\n );\n expect(pairs(rows)).toEqual([\"p6:a1\"]);\n },\n opts.timeout\n );\n\n it(\n \"honours a caller's own criterion on the join column\",\n async () => {\n const rows = await posts.join(\n { type: \"inner\", on, where: { right: { id: \"a2\" } } },\n authors\n );\n expect(pairs(rows)).toEqual([\"p3:a2\"]);\n },\n opts.timeout\n );\n\n it(\n \"returns [] rather than undefined when nothing matches\",\n async () => {\n const rows = await posts.join(\n { type: \"inner\", on, where: { left: { title: \"no such title\" } } },\n authors\n );\n expect(rows).toEqual([]);\n },\n opts.timeout\n );\n\n it(\n \"joins many distinct keys\",\n async () => {\n const n = 1100;\n await authors.putBulk(\n Array.from({ length: n }, (_, i) => ({\n id: `b${i}`,\n tenant: \"t9\",\n name: `n${i}`,\n country: null,\n }))\n );\n await posts.putBulk(\n Array.from({ length: n }, (_, i) => ({\n id: `q${i}`,\n tenant: \"t9\",\n author_id: `b${i}`,\n title: `t${i}`,\n views: i,\n }))\n );\n const rows = await posts.join(\n { type: \"inner\", on, where: { left: { tenant: \"t9\" } } },\n authors\n );\n expect(rows).toHaveLength(n);\n expect(rows.every((r) => r.right.id === r.left.author_id)).toBe(true);\n },\n opts.timeout\n );\n\n it(\n \"rejects an unknown column on either side before running\",\n async () => {\n await expect(\n posts.join({ type: \"inner\", on: [{ left: \"nope\" as any, right: \"id\" }] }, authors)\n ).rejects.toBeInstanceOf(StorageInvalidColumnError);\n await expect(\n posts.join({ type: \"inner\", on: [{ left: \"author_id\", right: \"nope\" as any }] }, authors)\n ).rejects.toBeInstanceOf(StorageInvalidColumnError);\n await expect(\n posts.join(\n { type: \"inner\", on, orderBy: [{ side: \"right\", column: \"views\", direction: \"ASC\" }] },\n authors\n )\n ).rejects.toBeInstanceOf(StorageInvalidColumnError);\n },\n opts.timeout\n );\n\n it(\n \"rejects a forged join type, side, direction, limit or offset\",\n async () => {\n await expect(\n posts.join({ type: \"cross; DROP TABLE x\" as any, on }, authors)\n ).rejects.toBeInstanceOf(StorageValidationError);\n await expect(\n posts.join(\n { type: \"inner\", on, orderBy: [{ side: \"x\" as any, column: \"id\", direction: \"ASC\" }] },\n authors\n )\n ).rejects.toBeInstanceOf(StorageValidationError);\n await expect(\n posts.join(\n {\n type: \"inner\",\n on,\n orderBy: [{ side: \"left\", column: \"id\", direction: \"ASC; --\" as any }],\n },\n authors\n )\n ).rejects.toBeInstanceOf(StorageValidationError);\n await expect(posts.join({ type: \"inner\", on, limit: 0 }, authors)).rejects.toBeInstanceOf(\n StorageValidationError\n );\n await expect(posts.join({ type: \"inner\", on, offset: -1 }, authors)).rejects.toBeInstanceOf(\n StorageValidationError\n );\n await expect(posts.join({ type: \"inner\", on: [] }, authors)).rejects.toBeInstanceOf(\n StorageValidationError\n );\n },\n opts.timeout\n );\n });\n\n joinBoundedLeftReadBlock(createPosts, createAuthors, seedJoinFixtures, opts);\n joinStrategyParityBlock(createPosts, createAuthors, seedJoinFixtures, opts);\n}\n",
|
|
21
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { describe } from \"vitest\";\nimport { countMatchesQueryBlock } from \"./assertions/countMatchesQuery\";\nimport { guardParityBlock } from \"./assertions/guardParity\";\nimport { inListCriterionBlock } from \"./assertions/inListCriterion\";\nimport { joinPushdownBlock } from \"./assertions/joinPushdown\";\nimport { notInListCriterionBlock } from \"./assertions/notInListCriterion\";\nimport { subscribeToChangesBlock } from \"./assertions/subscribeToChanges\";\nimport { vectorColumnFormatBlock } from \"./assertions/vectorColumnFormat\";\nimport { withConnectionTransactionBlock } from \"./assertions/withConnectionTransaction\";\nimport { withTransactionRollbackBlock } from \"./assertions/withTransactionRollback\";\nimport type { TabularStorageContractOpts } from \"./types\";\n\nexport function runTabularStorageContract(opts: TabularStorageContractOpts): void {\n describe.skipIf(opts.skip)(`Tabular storage contract: ${opts.name}`, () => {\n subscribeToChangesBlock(opts);\n vectorColumnFormatBlock(opts);\n withTransactionRollbackBlock(opts);\n withConnectionTransactionBlock(opts);\n countMatchesQueryBlock(opts);\n inListCriterionBlock(opts);\n notInListCriterionBlock(opts);\n joinPushdownBlock(opts);\n guardParityBlock(opts);\n });\n}\n\nexport { VectorItemPrimaryKeyNames, VectorItemSchema } from \"./types\";\nexport type { TabularStorageContractAssertion, TabularStorageContractOpts } from \"./types\";\n",
|
|
22
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ITabularStorage } from \"@workglow/storage\";\nimport type { DataPortSchemaObject } from \"@workglow/util/schema\";\nimport type { CompoundPrimaryKeyNames, CompoundSchema } from \"./fixtures\";\n\nexport const VectorItemPrimaryKeyNames = [\"id\"] as const;\nexport const VectorItemSchema = {\n type: \"object\",\n properties: {\n id: { type: \"string\" },\n embedding: { type: \"array\", format: \"TypedArray:Float32Array\" },\n },\n required: [\"id\", \"embedding\"],\n additionalProperties: false,\n} as const satisfies DataPortSchemaObject;\n\n/**\n * Closed string-literal union of tabular-storage contract assertions that\n * adapters can mark as known failing. A typo in `expectedFailures` becomes\n * a TS error rather than a silently-ignored entry.\n */\nexport type TabularStorageContractAssertion =\n | \"subscribeToChanges\"\n | \"vectorColumnFormat\"\n | \"withTransactionRollback\"\n | \"withConnectionTransaction\"\n | \"countMatchesQuery\"\n | \"inListCriterion\"\n | \"notInListCriterion\"\n | \"joinPushdown\"\n | \"guardParity\";\n\ninterface TabularStorageContractBaseOpts {\n readonly name: string;\n readonly skip?: boolean;\n readonly timeout?: number;\n readonly createStorage: () => Promise<\n ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>\n >;\n /**\n * Required when capabilities.supportsVectorColumns is true. Creates a fresh\n * storage instance typed to VectorItemSchema for the round-trip assertion.\n */\n readonly createVectorStorage?: () => Promise<\n ITabularStorage<typeof VectorItemSchema, typeof VectorItemPrimaryKeyNames>\n >;\n /**\n * Creates a second table on the same connection handle as `primary`.\n * Required for the `withConnectionTransaction` assertion; backends that\n * cannot share a handle (InMemory, FsFolder, IndexedDB, Supabase) omit it\n * and that block is skipped.\n */\n readonly createSiblingStorage?: (\n primary: ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>\n ) => Promise<ITabularStorage<typeof CompoundSchema, typeof CompoundPrimaryKeyNames>>;\n /**\n * Releases whatever a factory allocated that the storage itself does not\n * own — typically a connection handle the adapter opened so `createStorage`\n * and `createSiblingStorage` could share one. Called from every `afterEach`\n * after `destroy()`, and passed the same instance the factory returned.\n *\n * Without it an adapter that opens its own handle has nowhere to close it:\n * `destroy()` deliberately leaves a caller-provided handle open, so the\n * handles pile up until the file ends. The parameter is `object` because\n * the hook is identity-keyed — the contract creates storages of two\n * different schema types and a release hook only ever looks its argument up.\n */\n readonly releaseStorage?: (storage: object) => void | Promise<void>;\n /**\n * Names of contract assertions currently broken in this adapter; each\n * named test wraps with `itExpectFail`. The string-literal union catches\n * typos at compile time.\n */\n readonly expectedFailures?: ReadonlyArray<TabularStorageContractAssertion>;\n}\n\ninterface SubscriptionCapableCapabilities {\n readonly supportsSubscriptions: true;\n readonly supportsVectorColumns: boolean;\n readonly supportsTransactions: boolean;\n /** Whether `query(criteria)` is supported. False for FsFolder etc. */\n readonly supportsQuery: boolean;\n}\n\ninterface SubscriptionCapableOpts extends TabularStorageContractBaseOpts {\n readonly capabilities: SubscriptionCapableCapabilities;\n /**\n * Required when supportsSubscriptions is true. Selects between the two\n * subscribeToChanges contract blocks:\n * - false → eventDriven (strict commit order)\n * - true → polling (set equality + count)\n */\n readonly usesPolling: boolean;\n /** Polling interval forwarded to subscribeToChanges for polling-based implementations. */\n readonly pollingIntervalMs?: number;\n}\n\ninterface SubscriptionIncapableCapabilities {\n readonly supportsSubscriptions: false;\n readonly supportsVectorColumns: boolean;\n readonly supportsTransactions: boolean;\n /** Whether `query(criteria)` is supported. False for FsFolder etc. */\n readonly supportsQuery: boolean;\n}\n\ninterface SubscriptionIncapableOpts extends TabularStorageContractBaseOpts {\n readonly capabilities: SubscriptionIncapableCapabilities;\n /**\n * Only meaningful when supportsSubscriptions is true. Permitted (but\n * ignored) on incapable backends to keep wiring ergonomic.\n */\n readonly usesPolling?: boolean;\n readonly pollingIntervalMs?: number;\n}\n\nexport type TabularStorageContractOpts = SubscriptionCapableOpts | SubscriptionIncapableOpts;\n"
|
|
23
|
+
],
|
|
24
|
+
"mappings": "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",
|
|
25
|
+
"debugId": "FCE7FDB7D993910B64756E2164756E21",
|
|
26
|
+
"names": []
|
|
27
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { VectorStorageContractOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* A vector of the wrong width is refused, and refused BEFORE the row lands.
|
|
9
|
+
*
|
|
10
|
+
* The store's width is fixed at construction and every row has to have it, so a
|
|
11
|
+
* row of another width is not a bad read waiting to happen — it is a bad read
|
|
12
|
+
* arbitrarily far away, surfacing in whichever query first reaches it, with
|
|
13
|
+
* nothing left to say where it came from. Rejecting late is barely better than
|
|
14
|
+
* not rejecting: the assertion below reads the table back, because a throw
|
|
15
|
+
* after the insert satisfies `rejects` and still leaves the row.
|
|
16
|
+
*/
|
|
17
|
+
export declare function dimensionValidationBlock(opts: VectorStorageContractOpts): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { VectorStorageContractOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* A row written in the column's stored form — by an older release, or by
|
|
9
|
+
* anything that is not this class's writer — is still searchable.
|
|
10
|
+
*
|
|
11
|
+
* This is the other half of the decode that broke: a search path may not assume
|
|
12
|
+
* every row it reads was encoded by the writer it ships with. Only a backend
|
|
13
|
+
* that has a stored form to write can be asked, so the block is skipped where
|
|
14
|
+
* the adapter supplies no `writeRawRow`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function legacyEncodingBlock(opts: VectorStorageContractOpts): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { VectorStorageContractOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* A metadata filter narrows the search, and narrows it to what matches rather
|
|
9
|
+
* than to what happens to be near.
|
|
10
|
+
*
|
|
11
|
+
* The case worth stating is the second one: filtering to a row that is NOT the
|
|
12
|
+
* nearest. A backend that takes the top `k` and then filters returns nothing
|
|
13
|
+
* there, while one that filters and then ranks returns the row — and the first
|
|
14
|
+
* behaviour is a silently empty result for every scoped RAG query.
|
|
15
|
+
*/
|
|
16
|
+
export declare function metadataFilterBlock(opts: VectorStorageContractOpts): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { VectorStorageContractOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* `topK` truncates and `scoreThreshold` filters — and they mean the same thing
|
|
9
|
+
* on a backend that computes the scores in SQL as on one that scans in memory.
|
|
10
|
+
*
|
|
11
|
+
* That parity is not free: it has already had to be fixed once, when the two
|
|
12
|
+
* paths disagreed about which side of the threshold an exact match fell on.
|
|
13
|
+
* Stating it here is what makes the next backend inherit the answer instead of
|
|
14
|
+
* rediscovering the question.
|
|
15
|
+
*/
|
|
16
|
+
export declare function searchOptionsBlock(opts: VectorStorageContractOpts): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { AnyVectorStorage } from "@workglow/storage";
|
|
7
|
+
import { it } from "vitest";
|
|
8
|
+
import type { VectorStorageContractAssertion, VectorStorageContractOpts } from "../types";
|
|
9
|
+
/** A hit, as much of one as every assertion here needs to read. */
|
|
10
|
+
export interface Hit {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly score: number;
|
|
13
|
+
readonly vector: unknown;
|
|
14
|
+
}
|
|
15
|
+
export declare function itFor(opts: VectorStorageContractOpts, assertion: VectorStorageContractAssertion): typeof it;
|
|
16
|
+
/**
|
|
17
|
+
* The three-row fixture every block searches over: two axis-aligned unit
|
|
18
|
+
* vectors and the bisector between them, so a cosine search from `EAST` has one
|
|
19
|
+
* exact match, one at `SQRT1_2` and one orthogonal — three distinct scores in a
|
|
20
|
+
* known order, which is what makes "ranked by similarity" checkable rather than
|
|
21
|
+
* merely non-empty.
|
|
22
|
+
*/
|
|
23
|
+
export declare function seededStore(opts: VectorStorageContractOpts): () => AnyVectorStorage;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { VectorStorageContractOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* A search returns the rows, most similar first.
|
|
9
|
+
*
|
|
10
|
+
* This is the assertion the tree did not have, and the one that cost: a release
|
|
11
|
+
* shipped in which `similaritySearch` re-decoded a vector column the inherited
|
|
12
|
+
* tabular read had already turned into a `Float32Array`, so `JSON.parse` threw
|
|
13
|
+
* on the first row of every search. Writes, dimension validation and `getAll`
|
|
14
|
+
* were all fine — a store that accepts writes and fails every query looks
|
|
15
|
+
* healthy from anywhere except a query, and only one backend had a test that
|
|
16
|
+
* issued one.
|
|
17
|
+
*/
|
|
18
|
+
export declare function similarityRankingBlock(opts: VectorStorageContractOpts): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { VectorStorageContractOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* The `similaritySearch` event is the one thing `IVectorStorage` adds to the
|
|
9
|
+
* tabular event surface, and it carries the results — not a count, and not the
|
|
10
|
+
* rows before the options were applied.
|
|
11
|
+
*
|
|
12
|
+
* Telemetry and cache layers wrap these stores and read that payload as the
|
|
13
|
+
* answer the caller got. An event that fires with the unfiltered set reports
|
|
14
|
+
* work nobody received.
|
|
15
|
+
*/
|
|
16
|
+
export declare function similaritySearchEventBlock(opts: VectorStorageContractOpts): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { VectorStorageContractOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* A hit's vector comes back as the declared `TypedArray`, not as whatever the
|
|
9
|
+
* column stores.
|
|
10
|
+
*
|
|
11
|
+
* The stored form is a backend's own business — JSON text, a native vector
|
|
12
|
+
* type, a blob — and every one of them has a decode step on the search path
|
|
13
|
+
* that the tabular read does not exercise. Handing back a string or a plain
|
|
14
|
+
* array is a break for every caller doing arithmetic on the result, and it is
|
|
15
|
+
* invisible to a test that only reads `id` and `score`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function vectorRoundTripBlock(opts: VectorStorageContractOpts): void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { VectorStorageContractOpts } from "./types";
|
|
7
|
+
/**
|
|
8
|
+
* The conformance suite for {@link IVectorStorage}, in the shape
|
|
9
|
+
* `runTabularStorageContract` already has.
|
|
10
|
+
*
|
|
11
|
+
* Eight classes implement the interface and each had its own hand-written file,
|
|
12
|
+
* no two checking the same things — which is how a release shipped in which
|
|
13
|
+
* every single query failed on one backend: its sibling suite only ever wrote
|
|
14
|
+
* and read back, so nothing there issued a search. The fix added seven cases,
|
|
15
|
+
* all against that one backend, and the other seven implementations gained
|
|
16
|
+
* nothing.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runVectorStorageContract(opts: VectorStorageContractOpts): void;
|
|
19
|
+
export { EAST, NORTH, NORTHEAST, VECTOR_DIMENSIONS, VectorItemPrimaryKeyNames, VectorItemSchema, } from "./types";
|
|
20
|
+
export type { VectorStorageCapabilities, VectorStorageContractAssertion, VectorStorageContractOpts, } from "./types";
|