ydb-qdrant 8.1.0 → 9.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +20 -18
  2. package/dist/SmokeTest.js +2 -2
  3. package/dist/compute/ComputePool.d.ts +5 -0
  4. package/dist/compute/ComputePool.js +64 -0
  5. package/dist/compute/ComputeWorker.d.ts +36 -0
  6. package/dist/compute/ComputeWorker.js +84 -0
  7. package/dist/config/env.d.ts +24 -7
  8. package/dist/config/env.js +65 -35
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +92 -2
  11. package/dist/logging/DeployLogFormatter.d.ts +2 -0
  12. package/dist/logging/DeployLogFormatter.js +131 -0
  13. package/dist/logging/logger.js +13 -1
  14. package/dist/logging/requestContext.d.ts +17 -0
  15. package/dist/logging/requestContext.js +43 -0
  16. package/dist/middleware/requestLogger.js +134 -6
  17. package/dist/middleware/upsertBodyPhase.d.ts +6 -0
  18. package/dist/middleware/upsertBodyPhase.js +184 -0
  19. package/dist/middleware/upsertRequestTimeout.d.ts +16 -0
  20. package/dist/middleware/upsertRequestTimeout.js +158 -0
  21. package/dist/package/api.d.ts +20 -12
  22. package/dist/package/api.js +57 -28
  23. package/dist/qdrant/QdrantRestTypes.d.ts +4 -0
  24. package/dist/qdrant/Requests.d.ts +97 -0
  25. package/dist/qdrant/Requests.js +72 -0
  26. package/dist/repositories/collectionsRepo.d.ts +18 -2
  27. package/dist/repositories/collectionsRepo.js +103 -7
  28. package/dist/repositories/collectionsRepo.one-table.d.ts +4 -3
  29. package/dist/repositories/collectionsRepo.one-table.js +99 -36
  30. package/dist/repositories/collectionsRepo.shared.d.ts +2 -2
  31. package/dist/repositories/collectionsRepo.shared.js +9 -4
  32. package/dist/repositories/pointsRepo.d.ts +6 -4
  33. package/dist/repositories/pointsRepo.js +8 -7
  34. package/dist/repositories/pointsRepo.one-table/Delete.d.ts +2 -2
  35. package/dist/repositories/pointsRepo.one-table/Delete.js +157 -60
  36. package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.d.ts +7 -5
  37. package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.js +44 -13
  38. package/dist/repositories/pointsRepo.one-table/Retrieve.d.ts +6 -0
  39. package/dist/repositories/pointsRepo.one-table/Retrieve.js +69 -0
  40. package/dist/repositories/pointsRepo.one-table/Search.d.ts +2 -3
  41. package/dist/repositories/pointsRepo.one-table/Search.js +102 -124
  42. package/dist/repositories/pointsRepo.one-table/Upsert.d.ts +2 -2
  43. package/dist/repositories/pointsRepo.one-table/Upsert.js +244 -48
  44. package/dist/repositories/pointsRepo.one-table.d.ts +1 -0
  45. package/dist/repositories/pointsRepo.one-table.js +1 -0
  46. package/dist/routes/collections.js +45 -36
  47. package/dist/routes/points.js +145 -56
  48. package/dist/server.js +42 -6
  49. package/dist/services/CollectionService.d.ts +7 -5
  50. package/dist/services/CollectionService.js +12 -9
  51. package/dist/services/CollectionService.one-table.js +1 -2
  52. package/dist/services/CollectionService.shared.d.ts +6 -5
  53. package/dist/services/CollectionService.shared.js +28 -12
  54. package/dist/services/PointsService.d.ts +8 -0
  55. package/dist/services/PointsService.js +132 -15
  56. package/dist/types.d.ts +4 -94
  57. package/dist/types.js +1 -54
  58. package/dist/utils/EnvParsers.d.ts +5 -0
  59. package/dist/utils/EnvParsers.js +30 -0
  60. package/dist/utils/PayloadSign.d.ts +4 -0
  61. package/dist/utils/PayloadSign.js +18 -0
  62. package/dist/utils/distance.d.ts +1 -12
  63. package/dist/utils/distance.js +0 -21
  64. package/dist/utils/pathPrefix.d.ts +3 -0
  65. package/dist/utils/pathPrefix.js +47 -0
  66. package/dist/utils/prefixExpansion.d.ts +1 -0
  67. package/dist/utils/prefixExpansion.js +11 -0
  68. package/dist/utils/qdrantResponse.d.ts +13 -0
  69. package/dist/utils/qdrantResponse.js +12 -0
  70. package/dist/utils/requestIdentity.d.ts +8 -0
  71. package/dist/utils/requestIdentity.js +52 -0
  72. package/dist/utils/retry.d.ts +2 -0
  73. package/dist/utils/retry.js +55 -11
  74. package/dist/utils/tenant.d.ts +12 -6
  75. package/dist/utils/tenant.js +41 -32
  76. package/dist/utils/vectorBinary.d.ts +0 -1
  77. package/dist/utils/vectorBinary.js +0 -98
  78. package/dist/utils/ydbErrors.d.ts +1 -0
  79. package/dist/utils/ydbErrors.js +14 -0
  80. package/dist/ydb/bootstrapMetaTable.js +14 -2
  81. package/dist/ydb/client.d.ts +10 -2
  82. package/dist/ydb/client.js +83 -24
  83. package/dist/ydb/helpers.d.ts +0 -1
  84. package/dist/ydb/helpers.js +1 -2
  85. package/dist/ydb/schema.d.ts +2 -0
  86. package/dist/ydb/schema.js +84 -7
  87. package/package.json +10 -5
@@ -0,0 +1,158 @@
1
+ import { UPSERT_BODY_TIMEOUT_MS, UPSERT_HTTP_TIMEOUT_MS, } from "../config/env.js";
2
+ const UPSERT_REQUEST_TIMEOUT_STATE = Symbol("upsertRequestTimeoutState");
3
+ const RETRY_AFTER_SECONDS = "1";
4
+ const TIMEOUT_ERROR_MESSAGE = "upsert request timed out";
5
+ const UPSERT_POINTS_PATH_RE = /^\/collections\/[^/]+\/points\/?$/;
6
+ const UPSERT_POINTS_ALIAS_PATH_RE = /^\/collections\/[^/]+\/points\/upsert\/?$/;
7
+ function asTimeoutAwareRequest(req) {
8
+ return req;
9
+ }
10
+ function getRequestPath(req) {
11
+ const originalUrl = req.originalUrl;
12
+ if (typeof originalUrl !== "string" || originalUrl.length === 0) {
13
+ return "";
14
+ }
15
+ return originalUrl.split("?", 1)[0] ?? "";
16
+ }
17
+ function getOrCreateTimeoutState(req) {
18
+ const request = asTimeoutAwareRequest(req);
19
+ request[UPSERT_REQUEST_TIMEOUT_STATE] ??= {
20
+ timedOut: false,
21
+ };
22
+ return request[UPSERT_REQUEST_TIMEOUT_STATE];
23
+ }
24
+ function getTimeoutState(req) {
25
+ return asTimeoutAwareRequest(req)[UPSERT_REQUEST_TIMEOUT_STATE];
26
+ }
27
+ export function isUpsertRequestTarget(req) {
28
+ const path = getRequestPath(req);
29
+ if (req.method === "PUT") {
30
+ return UPSERT_POINTS_PATH_RE.test(path);
31
+ }
32
+ if (req.method === "POST") {
33
+ return UPSERT_POINTS_ALIAS_PATH_RE.test(path);
34
+ }
35
+ return false;
36
+ }
37
+ export function markUpsertRequestTimedOut(req, timeoutMs, timeoutPhase) {
38
+ const state = getOrCreateTimeoutState(req);
39
+ // Preserve armed clear callbacks on the existing state object.
40
+ state.timedOut = true;
41
+ state.timeoutMs = timeoutMs;
42
+ state.timeoutPhase = timeoutPhase;
43
+ }
44
+ export function isUpsertRequestTimedOut(req) {
45
+ return getTimeoutState(req)?.timedOut === true;
46
+ }
47
+ export function getUpsertRequestTimeoutMs(req) {
48
+ return getTimeoutState(req)?.timeoutMs;
49
+ }
50
+ export function getUpsertRequestTimeoutPhase(req) {
51
+ return getTimeoutState(req)?.timeoutPhase;
52
+ }
53
+ function clearUpsertTimeout(req, phase) {
54
+ const state = getTimeoutState(req);
55
+ if (!state) {
56
+ return;
57
+ }
58
+ const clearFn = phase === "body" ? state.clearBodyTimeout : state.clearProcessingTimeout;
59
+ if (clearFn) {
60
+ clearFn();
61
+ }
62
+ if (phase === "body") {
63
+ state.clearBodyTimeout = undefined;
64
+ return;
65
+ }
66
+ state.clearProcessingTimeout = undefined;
67
+ }
68
+ function closeSocketAfterResponse(res) {
69
+ const socket = res.socket;
70
+ if (!socket || socket.destroyed) {
71
+ return;
72
+ }
73
+ if (typeof socket.destroySoon === "function") {
74
+ socket.destroySoon();
75
+ return;
76
+ }
77
+ socket.end();
78
+ const forceDestroyTimer = setTimeout(() => {
79
+ if (!socket.destroyed) {
80
+ socket.destroy();
81
+ }
82
+ }, 50);
83
+ forceDestroyTimer.unref?.();
84
+ }
85
+ export function respondUpsertRequestTimedOut(args) {
86
+ const state = getOrCreateTimeoutState(args.req);
87
+ if (args.res.headersSent || args.res.writableEnded || state.timedOut) {
88
+ return false;
89
+ }
90
+ clearUpsertTimeout(args.req, args.timeoutPhase === "processing" ? "processing" : "body");
91
+ markUpsertRequestTimedOut(args.req, args.timeoutMs, args.timeoutPhase);
92
+ args.res.setHeader("Retry-After", RETRY_AFTER_SECONDS);
93
+ args.res.setHeader("Connection", "close");
94
+ args.res.status(503).json({
95
+ status: "error",
96
+ error: TIMEOUT_ERROR_MESSAGE,
97
+ });
98
+ args.res.once("finish", () => {
99
+ closeSocketAfterResponse(args.res);
100
+ });
101
+ return true;
102
+ }
103
+ function installUpsertTimeout(args) {
104
+ if (args.timeoutMs <= 0) {
105
+ return;
106
+ }
107
+ const state = getOrCreateTimeoutState(args.req);
108
+ let timer;
109
+ const clearTimer = () => {
110
+ if (timer) {
111
+ clearTimeout(timer);
112
+ timer = undefined;
113
+ }
114
+ };
115
+ timer = setTimeout(() => {
116
+ respondUpsertRequestTimedOut(args);
117
+ }, args.timeoutMs);
118
+ timer.unref?.();
119
+ args.res.once("finish", clearTimer);
120
+ args.res.once("close", clearTimer);
121
+ if (args.timeoutPhase === "body") {
122
+ state.clearBodyTimeout = clearTimer;
123
+ return;
124
+ }
125
+ state.clearProcessingTimeout = clearTimer;
126
+ }
127
+ export function markUpsertBodyPhaseCompleted(req) {
128
+ clearUpsertTimeout(req, "body");
129
+ }
130
+ export function upsertBodyTimeout(req, res, next) {
131
+ if (!isUpsertRequestTarget(req)) {
132
+ next();
133
+ return;
134
+ }
135
+ installUpsertTimeout({
136
+ req,
137
+ res,
138
+ timeoutMs: UPSERT_BODY_TIMEOUT_MS,
139
+ timeoutPhase: "body",
140
+ });
141
+ next();
142
+ }
143
+ export function upsertProcessingTimeout(req, res, next) {
144
+ if (!isUpsertRequestTarget(req)) {
145
+ next();
146
+ return;
147
+ }
148
+ if (res.headersSent || res.writableEnded) {
149
+ return;
150
+ }
151
+ installUpsertTimeout({
152
+ req,
153
+ res,
154
+ timeoutMs: UPSERT_HTTP_TIMEOUT_MS,
155
+ timeoutPhase: "processing",
156
+ });
157
+ next();
158
+ }
@@ -1,9 +1,10 @@
1
1
  import type { IAuthService } from "ydb-sdk";
2
2
  import { createCollection as serviceCreateCollection, deleteCollection as serviceDeleteCollection, getCollection as serviceGetCollection, putCollectionIndex as servicePutCollectionIndex } from "../services/CollectionService.js";
3
- import { upsertPoints as serviceUpsertPoints, searchPoints as serviceSearchPoints, deletePoints as serviceDeletePoints } from "../services/PointsService.js";
3
+ import { upsertPoints as serviceUpsertPoints, searchPoints as serviceSearchPoints, deletePoints as serviceDeletePoints, retrievePoints as serviceRetrievePoints } from "../services/PointsService.js";
4
4
  export { QdrantServiceError } from "../services/errors.js";
5
- export { CreateCollectionReq, UpsertPointsReq, SearchReq, DeletePointsReq, } from "../types.js";
6
- export type { UpsertPointsBody, SearchPointsBody } from "../types.js";
5
+ export { CreateCollectionReq, UpsertPointsReq, SearchReq, DeletePointsReq, RetrievePointsReq, } from "../qdrant/Requests.js";
6
+ export type { UpsertPointsBody, SearchPointsBody } from "../qdrant/Requests.js";
7
+ import type { UpsertPointsBody, SearchPointsBody } from "../qdrant/Requests.js";
7
8
  type CreateCollectionResult = Awaited<ReturnType<typeof serviceCreateCollection>>;
8
9
  type GetCollectionResult = Awaited<ReturnType<typeof serviceGetCollection>>;
9
10
  type DeleteCollectionResult = Awaited<ReturnType<typeof serviceDeleteCollection>>;
@@ -11,25 +12,32 @@ type PutIndexResult = Awaited<ReturnType<typeof servicePutCollectionIndex>>;
11
12
  type UpsertPointsResult = Awaited<ReturnType<typeof serviceUpsertPoints>>;
12
13
  type SearchPointsResult = Awaited<ReturnType<typeof serviceSearchPoints>>;
13
14
  type DeletePointsResult = Awaited<ReturnType<typeof serviceDeletePoints>>;
14
- export interface YdbQdrantClientOptions {
15
- defaultTenant?: string;
15
+ type RetrievePointsResult = Awaited<ReturnType<typeof serviceRetrievePoints>>;
16
+ type YdbQdrantClientDriverOptions = {
16
17
  endpoint?: string;
17
18
  database?: string;
18
19
  connectionString?: string;
19
20
  authService?: IAuthService;
20
- }
21
- export interface YdbQdrantTenantClient {
21
+ };
22
+ type YdbQdrantClientApiKeyOptions = YdbQdrantClientDriverOptions & {
23
+ apiKey: string;
24
+ userUid?: never;
25
+ };
26
+ type YdbQdrantClientUserUidOptions = YdbQdrantClientDriverOptions & {
27
+ userUid: string;
28
+ apiKey?: never;
29
+ };
30
+ export type YdbQdrantClientOptions = YdbQdrantClientApiKeyOptions | YdbQdrantClientUserUidOptions;
31
+ export interface YdbQdrantClient {
22
32
  createCollection(collection: string, body: unknown): Promise<CreateCollectionResult>;
23
33
  getCollection(collection: string): Promise<GetCollectionResult>;
24
34
  deleteCollection(collection: string): Promise<DeleteCollectionResult>;
25
35
  putCollectionIndex(collection: string): Promise<PutIndexResult>;
26
- upsertPoints(collection: string, body: import("../types.js").UpsertPointsBody): Promise<UpsertPointsResult>;
36
+ upsertPoints(collection: string, body: UpsertPointsBody): Promise<UpsertPointsResult>;
27
37
  upsertPoints(collection: string, body: unknown): Promise<UpsertPointsResult>;
28
- searchPoints(collection: string, body: import("../types.js").SearchPointsBody): Promise<SearchPointsResult>;
38
+ searchPoints(collection: string, body: SearchPointsBody): Promise<SearchPointsResult>;
29
39
  searchPoints(collection: string, body: unknown): Promise<SearchPointsResult>;
30
40
  deletePoints(collection: string, body: unknown): Promise<DeletePointsResult>;
31
- }
32
- export interface YdbQdrantClient extends YdbQdrantTenantClient {
33
- forTenant(tenant: string): YdbQdrantTenantClient;
41
+ retrievePoints(collection: string, body: unknown): Promise<RetrievePointsResult>;
34
42
  }
35
43
  export declare function createYdbQdrantClient(options?: YdbQdrantClientOptions): Promise<YdbQdrantClient>;
@@ -1,55 +1,84 @@
1
1
  import { readyOrThrow, configureDriver } from "../ydb/client.js";
2
2
  import { ensureMetaTable } from "../ydb/schema.js";
3
3
  import { createCollection as serviceCreateCollection, deleteCollection as serviceDeleteCollection, getCollection as serviceGetCollection, putCollectionIndex as servicePutCollectionIndex, } from "../services/CollectionService.js";
4
- import { upsertPoints as serviceUpsertPoints, searchPoints as serviceSearchPoints, deletePoints as serviceDeletePoints, } from "../services/PointsService.js";
4
+ import { upsertPoints as serviceUpsertPoints, searchPoints as serviceSearchPoints, deletePoints as serviceDeletePoints, retrievePoints as serviceRetrievePoints, } from "../services/PointsService.js";
5
+ import { deriveUserUidFromApiKey } from "../utils/tenant.js";
5
6
  export { QdrantServiceError } from "../services/errors.js";
6
- export { CreateCollectionReq, UpsertPointsReq, SearchReq, DeletePointsReq, } from "../types.js";
7
- function buildTenantClient(resolveTenant) {
7
+ export { CreateCollectionReq, UpsertPointsReq, SearchReq, DeletePointsReq, RetrievePointsReq, } from "../qdrant/Requests.js";
8
+ function buildClient(userUid, apiKey) {
8
9
  return {
9
10
  createCollection(collection, body) {
10
- return serviceCreateCollection({ tenant: resolveTenant(), collection }, body);
11
+ return serviceCreateCollection({ userUid, collection, apiKey }, body);
11
12
  },
12
13
  getCollection(collection) {
13
- return serviceGetCollection({ tenant: resolveTenant(), collection });
14
+ return serviceGetCollection({
15
+ userUid,
16
+ collection,
17
+ apiKey,
18
+ });
14
19
  },
15
20
  deleteCollection(collection) {
16
- return serviceDeleteCollection({ tenant: resolveTenant(), collection });
21
+ return serviceDeleteCollection({
22
+ userUid,
23
+ collection,
24
+ apiKey,
25
+ });
17
26
  },
18
27
  putCollectionIndex(collection) {
19
- return servicePutCollectionIndex({ tenant: resolveTenant(), collection });
28
+ return servicePutCollectionIndex({
29
+ userUid,
30
+ collection,
31
+ apiKey,
32
+ });
20
33
  },
21
34
  upsertPoints(collection, body) {
22
- return serviceUpsertPoints({ tenant: resolveTenant(), collection }, body);
35
+ return serviceUpsertPoints({ userUid, collection, apiKey }, body);
23
36
  },
24
37
  searchPoints(collection, body) {
25
- return serviceSearchPoints({ tenant: resolveTenant(), collection }, body);
38
+ return serviceSearchPoints({ userUid, collection, apiKey }, body);
26
39
  },
27
40
  deletePoints(collection, body) {
28
- return serviceDeletePoints({ tenant: resolveTenant(), collection }, body);
41
+ return serviceDeletePoints({ userUid, collection, apiKey }, body);
42
+ },
43
+ retrievePoints(collection, body) {
44
+ return serviceRetrievePoints({ userUid, collection, apiKey }, body);
29
45
  },
30
46
  };
31
47
  }
32
- export async function createYdbQdrantClient(options = {}) {
33
- if (options.endpoint !== undefined ||
34
- options.database !== undefined ||
35
- options.connectionString !== undefined ||
36
- options.authService !== undefined) {
48
+ function resolveClientIdentity(options) {
49
+ const providedUserUid = options?.userUid?.trim();
50
+ const apiKey = options?.apiKey?.trim();
51
+ if (providedUserUid && apiKey) {
52
+ throw new Error("createYdbQdrantClient accepts exactly one of apiKey or userUid");
53
+ }
54
+ if (apiKey) {
55
+ return {
56
+ userUid: deriveUserUidFromApiKey(apiKey),
57
+ signingKey: apiKey,
58
+ };
59
+ }
60
+ if (providedUserUid) {
61
+ return {
62
+ userUid: providedUserUid,
63
+ signingKey: providedUserUid,
64
+ };
65
+ }
66
+ throw new Error("createYdbQdrantClient requires either userUid or apiKey");
67
+ }
68
+ export async function createYdbQdrantClient(options) {
69
+ if (options?.endpoint !== undefined ||
70
+ options?.database !== undefined ||
71
+ options?.connectionString !== undefined ||
72
+ options?.authService !== undefined) {
37
73
  configureDriver({
38
- endpoint: options.endpoint,
39
- database: options.database,
40
- connectionString: options.connectionString,
41
- authService: options.authService,
74
+ endpoint: options?.endpoint,
75
+ database: options?.database,
76
+ connectionString: options?.connectionString,
77
+ authService: options?.authService,
42
78
  });
43
79
  }
44
80
  await readyOrThrow();
45
81
  await ensureMetaTable();
46
- const defaultTenant = options.defaultTenant ?? "default";
47
- const baseClient = buildTenantClient(() => defaultTenant);
48
- const client = {
49
- ...baseClient,
50
- forTenant(tenantId) {
51
- return buildTenantClient(() => tenantId);
52
- },
53
- };
54
- return client;
82
+ const { userUid, signingKey } = resolveClientIdentity(options);
83
+ return buildClient(userUid, signingKey);
55
84
  }
@@ -21,6 +21,10 @@ export type QdrantWithPayloadInterface = Schemas["WithPayloadInterface"];
21
21
  * Project-specific narrowing for our Qdrant-compatible subset.
22
22
  * These types are intentionally tighter than Qdrant's full schema.
23
23
  */
24
+ export type DistanceKind = QdrantDistance;
25
+ export type VectorType = "float";
26
+ export type Payload = QdrantPayload;
27
+ export type WithPayload = QdrantWithPayloadInterface;
24
28
  export type YdbQdrantPointId = Extract<QdrantExtendedPointId, string | number>;
25
29
  export type YdbQdrantVector = number[];
26
30
  export type YdbQdrantUpsertPoint = Omit<QdrantPointStruct, "id" | "vector" | "payload"> & {
@@ -0,0 +1,97 @@
1
+ import { z } from "zod";
2
+ import type { DistanceKind, Payload, WithPayload, YdbQdrantPointId, YdbQdrantUpsertPoint } from "./QdrantRestTypes.js";
3
+ export declare const RETRIEVE_POINTS_IDS_MAX = 1000;
4
+ export declare const CreateCollectionReq: z.ZodObject<{
5
+ vectors: z.ZodObject<{
6
+ size: z.ZodNumber;
7
+ distance: z.ZodType<DistanceKind>;
8
+ data_type: z.ZodOptional<z.ZodEnum<{
9
+ float: "float";
10
+ }>>;
11
+ }, z.core.$strip>;
12
+ }, z.core.$strip>;
13
+ export declare const UpsertPointsReq: z.ZodObject<{
14
+ points: z.ZodArray<z.ZodObject<{
15
+ id: z.ZodType<YdbQdrantPointId>;
16
+ vector: z.ZodArray<z.ZodNumber>;
17
+ payload: z.ZodType<Payload | undefined>;
18
+ }, z.core.$strip>>;
19
+ }, z.core.$strip>;
20
+ export type UpsertPoint = YdbQdrantUpsertPoint;
21
+ export type UpsertPointsBody = {
22
+ points: UpsertPoint[];
23
+ };
24
+ export declare const SearchReq: z.ZodObject<{
25
+ vector: z.ZodArray<z.ZodNumber>;
26
+ top: z.ZodNumber;
27
+ with_payload: z.ZodOptional<z.ZodBoolean>;
28
+ }, z.core.$strip>;
29
+ export type SearchPointsBody = {
30
+ vector: number[];
31
+ top?: number;
32
+ limit?: number;
33
+ with_payload?: WithPayload;
34
+ score_threshold?: number | null;
35
+ };
36
+ export declare const DeletePointsByIdsReq: z.ZodObject<{
37
+ points: z.ZodArray<z.ZodType<YdbQdrantPointId, unknown, z.core.$ZodTypeInternals<YdbQdrantPointId, unknown>>>;
38
+ }, z.core.$strip>;
39
+ export declare const DeletePointsByFilterReq: z.ZodObject<{
40
+ filter: z.ZodUnion<readonly [z.ZodObject<{
41
+ must: z.ZodArray<z.ZodObject<{
42
+ key: z.ZodString;
43
+ match: z.ZodObject<{
44
+ value: z.ZodString;
45
+ }, z.core.$strip>;
46
+ }, z.core.$strip>>;
47
+ }, z.core.$strip>, z.ZodObject<{
48
+ should: z.ZodArray<z.ZodObject<{
49
+ must: z.ZodArray<z.ZodObject<{
50
+ key: z.ZodString;
51
+ match: z.ZodObject<{
52
+ value: z.ZodString;
53
+ }, z.core.$strip>;
54
+ }, z.core.$strip>>;
55
+ }, z.core.$strip>>;
56
+ }, z.core.$strip>, z.ZodObject<{
57
+ must: z.ZodArray<z.ZodObject<{
58
+ key: z.ZodString;
59
+ match: z.ZodObject<{
60
+ value: z.ZodString;
61
+ }, z.core.$strip>;
62
+ }, z.core.$strip>>;
63
+ }, z.core.$strip>]>;
64
+ }, z.core.$strip>;
65
+ export declare const DeletePointsReq: z.ZodUnion<readonly [z.ZodObject<{
66
+ points: z.ZodArray<z.ZodType<YdbQdrantPointId, unknown, z.core.$ZodTypeInternals<YdbQdrantPointId, unknown>>>;
67
+ }, z.core.$strip>, z.ZodObject<{
68
+ filter: z.ZodUnion<readonly [z.ZodObject<{
69
+ must: z.ZodArray<z.ZodObject<{
70
+ key: z.ZodString;
71
+ match: z.ZodObject<{
72
+ value: z.ZodString;
73
+ }, z.core.$strip>;
74
+ }, z.core.$strip>>;
75
+ }, z.core.$strip>, z.ZodObject<{
76
+ should: z.ZodArray<z.ZodObject<{
77
+ must: z.ZodArray<z.ZodObject<{
78
+ key: z.ZodString;
79
+ match: z.ZodObject<{
80
+ value: z.ZodString;
81
+ }, z.core.$strip>;
82
+ }, z.core.$strip>>;
83
+ }, z.core.$strip>>;
84
+ }, z.core.$strip>, z.ZodObject<{
85
+ must: z.ZodArray<z.ZodObject<{
86
+ key: z.ZodString;
87
+ match: z.ZodObject<{
88
+ value: z.ZodString;
89
+ }, z.core.$strip>;
90
+ }, z.core.$strip>>;
91
+ }, z.core.$strip>]>;
92
+ }, z.core.$strip>]>;
93
+ export declare const RetrievePointsReq: z.ZodObject<{
94
+ ids: z.ZodArray<z.ZodType<YdbQdrantPointId, unknown, z.core.$ZodTypeInternals<YdbQdrantPointId, unknown>>>;
95
+ with_payload: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
96
+ with_vector: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
97
+ }, z.core.$strip>;
@@ -0,0 +1,72 @@
1
+ import { z } from "zod";
2
+ export const RETRIEVE_POINTS_IDS_MAX = 1000;
3
+ export const CreateCollectionReq = z.object({
4
+ vectors: z.object({
5
+ size: z.number().int().positive(),
6
+ distance: z.enum([
7
+ "Cosine",
8
+ "Euclid",
9
+ "Dot",
10
+ "Manhattan",
11
+ ]),
12
+ data_type: z.enum(["float"]).optional(),
13
+ }),
14
+ });
15
+ export const UpsertPointsReq = z.object({
16
+ points: z
17
+ .array(z.object({
18
+ id: z.union([
19
+ z.string(),
20
+ z.number(),
21
+ ]),
22
+ vector: z.array(z.number()),
23
+ payload: z
24
+ .record(z.string(), z.unknown())
25
+ .optional(),
26
+ }))
27
+ .min(1),
28
+ });
29
+ export const SearchReq = z.object({
30
+ vector: z.array(z.number()).min(1),
31
+ top: z.number().int().positive().max(1000),
32
+ with_payload: z.boolean().optional(),
33
+ });
34
+ export const DeletePointsByIdsReq = z.object({
35
+ points: z
36
+ .array(z.union([z.string(), z.number()]))
37
+ .min(1),
38
+ });
39
+ const DeletePointsFilterCondition = z.object({
40
+ key: z.string(),
41
+ match: z.object({
42
+ value: z.string(),
43
+ }),
44
+ });
45
+ const DeletePointsFilterMust = z.object({
46
+ must: z.array(DeletePointsFilterCondition).min(1),
47
+ });
48
+ const DeletePointsEmptyFilter = z.object({
49
+ must: z.array(DeletePointsFilterCondition).max(0),
50
+ });
51
+ const DeletePointsFilter = z.union([
52
+ DeletePointsFilterMust,
53
+ z.object({
54
+ should: z.array(DeletePointsFilterMust).min(1),
55
+ }),
56
+ DeletePointsEmptyFilter,
57
+ ]);
58
+ export const DeletePointsByFilterReq = z.object({
59
+ filter: DeletePointsFilter,
60
+ });
61
+ export const DeletePointsReq = z.union([
62
+ DeletePointsByIdsReq,
63
+ DeletePointsByFilterReq,
64
+ ]);
65
+ export const RetrievePointsReq = z.object({
66
+ ids: z
67
+ .array(z.union([z.string(), z.number()]))
68
+ .min(1)
69
+ .max(RETRIEVE_POINTS_IDS_MAX),
70
+ with_payload: z.boolean().optional().default(true),
71
+ with_vector: z.boolean().optional().default(false),
72
+ });
@@ -1,8 +1,24 @@
1
- import type { DistanceKind, VectorType, CollectionMeta } from "../types";
2
- export declare function createCollection(metaKey: string, dim: number, distance: DistanceKind, vectorType: VectorType): Promise<void>;
1
+ import type { DistanceKind, VectorType } from "../qdrant/QdrantRestTypes.js";
2
+ /**
3
+ * Collection metadata from qdr__collections table.
4
+ *
5
+ * @property lastAccessedAt - Timestamp of last access; undefined for collections
6
+ * created before this feature, null if explicitly unset.
7
+ */
8
+ export interface CollectionMeta {
9
+ table: string;
10
+ dimension: number;
11
+ distance: DistanceKind;
12
+ vectorType: VectorType;
13
+ lastAccessedAt?: Date | null;
14
+ }
15
+ export declare function createCollection(metaKey: string, dim: number, distance: DistanceKind, vectorType: VectorType, userUid?: string): Promise<void>;
3
16
  export declare function getCollectionMeta(metaKey: string): Promise<CollectionMeta | null>;
4
17
  export declare function verifyCollectionsQueryCompilationForStartup(): Promise<void>;
5
18
  export declare function deleteCollection(metaKey: string, uid?: string): Promise<void>;
19
+ export declare function deleteAllPointsForCollection(collection: string): Promise<void>;
20
+ export declare function hasPointsForCollection(uid: string): Promise<boolean>;
21
+ export declare function countPointsForCollection(uid: string): Promise<number>;
6
22
  /**
7
23
  * Best-effort metadata update for a collection's last_accessed_at timestamp.
8
24
  *
@@ -2,8 +2,9 @@ import { TypedValues, withSession, createExecuteQuerySettings, withStartupProbeS
2
2
  import { STARTUP_PROBE_SESSION_TIMEOUT_MS, LAST_ACCESS_MIN_WRITE_INTERVAL_MS, } from "../config/env.js";
3
3
  import { logger } from "../logging/logger.js";
4
4
  import { uidFor } from "../utils/tenant.js";
5
- import { createCollectionOneTable, deleteCollectionOneTable, } from "./collectionsRepo.one-table.js";
5
+ import { createCollectionOneTable, deleteAllPointsForCollectionOneTable, deleteCollectionOneTable, } from "./collectionsRepo.one-table.js";
6
6
  import { withRetry, isTransientYdbError } from "../utils/retry.js";
7
+ import { GLOBAL_POINTS_TABLE } from "../ydb/schema.js";
7
8
  const lastAccessWriteCache = new Map();
8
9
  const LAST_ACCESS_CACHE_MAX_SIZE = 10000;
9
10
  function evictOldestLastAccessEntry() {
@@ -22,8 +23,8 @@ function shouldWriteLastAccess(nowMs, key) {
22
23
  }
23
24
  return nowMs - last >= LAST_ACCESS_MIN_WRITE_INTERVAL_MS;
24
25
  }
25
- export async function createCollection(metaKey, dim, distance, vectorType) {
26
- await createCollectionOneTable(metaKey, dim, distance, vectorType);
26
+ export async function createCollection(metaKey, dim, distance, vectorType, userUid) {
27
+ await createCollectionOneTable(metaKey, dim, distance, vectorType, userUid);
27
28
  }
28
29
  export async function getCollectionMeta(metaKey) {
29
30
  const qry = `
@@ -49,7 +50,8 @@ export async function getCollectionMeta(metaKey) {
49
50
  const row = rowset.rows[0];
50
51
  const table = row.items?.[0]?.textValue;
51
52
  const dimension = Number(row.items?.[1]?.uint32Value ?? row.items?.[1]?.textValue);
52
- const distance = row.items?.[2]?.textValue ?? "Cosine";
53
+ const distance = row.items?.[2]?.textValue ??
54
+ "Cosine";
53
55
  const vectorType = row.items?.[3]?.textValue ?? "float";
54
56
  const lastAccessRaw = row.items?.[4]?.textValue;
55
57
  const lastAccessedAt = typeof lastAccessRaw === "string" && lastAccessRaw.length > 0
@@ -103,14 +105,108 @@ export async function deleteCollection(metaKey, uid) {
103
105
  return;
104
106
  let effectiveUid = uid;
105
107
  if (!effectiveUid) {
106
- const [tenant, collection] = metaKey.split("/", 2);
107
- if (!tenant || !collection) {
108
+ const [userUid, collection] = metaKey.split("/", 2);
109
+ if (!userUid || !collection) {
108
110
  throw new Error(`deleteCollection: cannot derive uid from malformed metaKey=${metaKey}`);
109
111
  }
110
- effectiveUid = uidFor(tenant, collection);
112
+ effectiveUid = uidFor(userUid, collection);
111
113
  }
112
114
  await deleteCollectionOneTable(metaKey, effectiveUid);
113
115
  }
116
+ export async function deleteAllPointsForCollection(collection) {
117
+ await deleteAllPointsForCollectionOneTable(collection);
118
+ }
119
+ export async function hasPointsForCollection(uid) {
120
+ const qry = `
121
+ DECLARE $collection AS Utf8;
122
+ SELECT point_id
123
+ FROM ${GLOBAL_POINTS_TABLE}
124
+ WHERE collection = $collection
125
+ LIMIT 1;
126
+ `;
127
+ const res = await withSession(async (s) => {
128
+ const settings = createExecuteQuerySettings();
129
+ return await s.executeQuery(qry, {
130
+ $collection: TypedValues.utf8(uid),
131
+ }, undefined, settings);
132
+ });
133
+ const rowset = res.resultSets?.[0];
134
+ return (rowset?.rows?.length ?? 0) > 0;
135
+ }
136
+ const MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
137
+ function bigintToSafeNumberOrNull(value) {
138
+ if (value > MAX_SAFE_BIGINT || value < -MAX_SAFE_BIGINT) {
139
+ return null;
140
+ }
141
+ return Number(value);
142
+ }
143
+ function longLikeToBigInt(value) {
144
+ const low = BigInt(value.low >>> 0);
145
+ const high = BigInt(value.high >>> 0);
146
+ let n = low + (high << 32n);
147
+ const isUnsigned = value.unsigned === true;
148
+ const signBitSet = (value.high & 0x8000_0000) !== 0;
149
+ if (!isUnsigned && signBitSet) {
150
+ n -= 1n << 64n;
151
+ }
152
+ return n;
153
+ }
154
+ function parsePointCountValue(value) {
155
+ if (typeof value === "number" && Number.isFinite(value)) {
156
+ return value;
157
+ }
158
+ if (typeof value === "string") {
159
+ const parsed = Number(value);
160
+ if (Number.isFinite(parsed)) {
161
+ return parsed;
162
+ }
163
+ }
164
+ if (typeof value === "bigint") {
165
+ const parsed = bigintToSafeNumberOrNull(value);
166
+ if (parsed !== null) {
167
+ return parsed;
168
+ }
169
+ }
170
+ if (value && typeof value === "object") {
171
+ const v = value;
172
+ if (typeof v.low === "number" && typeof v.high === "number") {
173
+ const parsed = bigintToSafeNumberOrNull(longLikeToBigInt({
174
+ low: v.low,
175
+ high: v.high,
176
+ unsigned: v.unsigned === true,
177
+ }));
178
+ if (parsed !== null) {
179
+ return parsed;
180
+ }
181
+ }
182
+ }
183
+ throw new Error("Unable to parse points_count from YDB result.");
184
+ }
185
+ export async function countPointsForCollection(uid) {
186
+ const qry = `
187
+ DECLARE $collection AS Utf8;
188
+ SELECT CAST(COUNT(*) AS Uint64) AS points_count
189
+ FROM ${GLOBAL_POINTS_TABLE}
190
+ WHERE collection = $collection;
191
+ `;
192
+ const res = await withSession(async (s) => {
193
+ const settings = createExecuteQuerySettings();
194
+ return await s.executeQuery(qry, {
195
+ $collection: TypedValues.utf8(uid),
196
+ }, undefined, settings);
197
+ });
198
+ const rowset = res.resultSets?.[0];
199
+ const row = rowset?.rows?.[0];
200
+ const cell = row?.items?.[0];
201
+ if (!cell) {
202
+ return 0;
203
+ }
204
+ return parsePointCountValue(cell.uint64Value ??
205
+ cell.int64Value ??
206
+ cell.uint32Value ??
207
+ cell.int32Value ??
208
+ cell.textValue);
209
+ }
114
210
  /**
115
211
  * Best-effort metadata update for a collection's last_accessed_at timestamp.
116
212
  *