couchset 0.5.0 → 0.5.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 +93 -1
- package/dist/database.d.ts +6 -0
- package/dist/database.js +51 -2
- package/dist/database.js.map +1 -1
- package/dist/model/include.d.ts +36 -3
- package/dist/model/include.js +154 -49
- package/dist/model/include.js.map +1 -1
- package/dist/model/index.d.ts +4 -1
- package/dist/model/index.js +8 -1
- package/dist/model/index.js.map +1 -1
- package/dist/model/read-helpers.d.ts +6 -2
- package/dist/model/read-helpers.js +96 -23
- package/dist/model/read-helpers.js.map +1 -1
- package/dist/model/read-types.d.ts +51 -0
- package/dist/model/read-types.js +3 -0
- package/dist/model/read-types.js.map +1 -0
- package/dist/model/safe-query.d.ts +3 -0
- package/dist/model/safe-query.js +22 -2
- package/dist/model/safe-query.js.map +1 -1
- package/dist/next.d.ts +12 -2
- package/dist/next.js +3 -1
- package/dist/next.js.map +1 -1
- package/dist/next.types.js +149 -1
- package/dist/next.types.js.map +1 -1
- package/dist/pagination/pagination.js +3 -1
- package/dist/pagination/pagination.js.map +1 -1
- package/dist/pagination/types.d.ts +2 -0
- package/dist/pagination/types.js.map +1 -1
- package/dist/search/customQuery.d.ts +2 -0
- package/dist/search/customQuery.js +4 -1
- package/dist/search/customQuery.js.map +1 -1
- package/docs/beta-migration.md +1 -1
- package/docs/document-modeling.md +5 -1
- package/docs/joins-and-consistency.md +228 -0
- package/docs/next-primitives.md +4 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ The additive client-owned primitives—typed definitions, explicit provisioning,
|
|
|
20
20
|
- [Connection Lifecycle](#connection-lifecycle)
|
|
21
21
|
- [Models](#models)
|
|
22
22
|
- [Reads](#reads)
|
|
23
|
+
- [Typed Joins and Read Consistency](#typed-joins-and-read-consistency)
|
|
23
24
|
- [Document Modeling](./docs/document-modeling.md)
|
|
24
25
|
- [Writes](#writes)
|
|
25
26
|
- [Queries](#queries)
|
|
@@ -233,7 +234,15 @@ await startCouchbase();
|
|
|
233
234
|
await startCouchbaseServerless();
|
|
234
235
|
```
|
|
235
236
|
|
|
236
|
-
The starters
|
|
237
|
+
The starters accept one PostgreSQL-style `DB_URL` in place of the four connection settings. It takes precedence over `COUCHBASE_URL`, `COUCHBASE_BUCKET`, `COUCHBASE_USERNAME`, and `COUCHBASE_PASSWORD`; explicit `startCouchbase({...})` options still take precedence over either environment form.
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
DB_URL=couchbase://user:password@localhost/bucket
|
|
241
|
+
DB_URL=couchbase://user:password@localhost:11210/bucket
|
|
242
|
+
DB_URL=couchbases://user:password@cb.example.com/bucket
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Use `couchbases://` for TLS. Percent-encode reserved characters in usernames, passwords, and bucket names (for example, `p%40ss` for `p@ss`). `COUCHBASE_PROXY` remains available as a separate optional setting.
|
|
237
246
|
|
|
238
247
|
## Models
|
|
239
248
|
|
|
@@ -313,6 +322,89 @@ await users.onlyDeleted().findMany<User>();
|
|
|
313
322
|
await users.withoutDefaultWhere().findMany<User>();
|
|
314
323
|
```
|
|
315
324
|
|
|
325
|
+
## Typed Joins and Read Consistency
|
|
326
|
+
|
|
327
|
+
Client-bound models infer root and related fields. Using the client-owned `db`
|
|
328
|
+
above, join profiles to battles by arbitrary fields:
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import {dateCodec, defineModel, joinField} from 'couchset/next';
|
|
332
|
+
import {QueryScanConsistency} from 'couchbase';
|
|
333
|
+
|
|
334
|
+
const battles = db.model(defineModel<{
|
|
335
|
+
createdByUserId: string;
|
|
336
|
+
startsAt: Date;
|
|
337
|
+
}>({name: 'Battle', scope: 'app', collection: 'battles', codecs: {startsAt: dateCodec}}));
|
|
338
|
+
|
|
339
|
+
const profiles = db.model(defineModel<{
|
|
340
|
+
ownerUserId: string;
|
|
341
|
+
displayName: string;
|
|
342
|
+
birthday: Date;
|
|
343
|
+
}>({name: 'Profile', scope: 'app', collection: 'profiles', codecs: {birthday: dateCodec}}));
|
|
344
|
+
|
|
345
|
+
const joined = await battles.page({
|
|
346
|
+
sourceAlias: 'battle',
|
|
347
|
+
include: [{
|
|
348
|
+
as: 'creator',
|
|
349
|
+
model: profiles,
|
|
350
|
+
type: 'leftJoin',
|
|
351
|
+
on: {
|
|
352
|
+
left: joinField('creator.ownerUserId'),
|
|
353
|
+
op: '$eq',
|
|
354
|
+
right: joinField('battle.createdByUserId'),
|
|
355
|
+
},
|
|
356
|
+
}],
|
|
357
|
+
limit: 20,
|
|
358
|
+
queryOptions: {scanConsistency: QueryScanConsistency.RequestPlus},
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
joined.items[0]?.startsAt; // Date | undefined
|
|
362
|
+
joined.items[0]?.creator?.birthday; // Date | undefined; related codec applied
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Provision the collections and appropriate indexes explicitly before querying.
|
|
366
|
+
`joinField()` denotes a field reference; an ordinary string is a bound value.
|
|
367
|
+
Use `$and` / `$or` to combine predicates. Existing `key` / `keys` includes still
|
|
368
|
+
use `ON KEYS`.
|
|
369
|
+
|
|
370
|
+
Use NEST for arrays, and select just the fields needed:
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
const summaries = await battles.findMany({
|
|
374
|
+
select: ['startsAt'],
|
|
375
|
+
include: [{
|
|
376
|
+
as: 'creators',
|
|
377
|
+
model: profiles,
|
|
378
|
+
type: 'leftNest',
|
|
379
|
+
on: {left: joinField('creators.ownerUserId'), op: '$eq', right: joinField('doc.createdByUserId')},
|
|
380
|
+
select: ['displayName', 'birthday'],
|
|
381
|
+
}],
|
|
382
|
+
});
|
|
383
|
+
// {startsAt: Date; creators: {displayName: string; birthday: Date}[]}[]
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
LEFT JOIN may omit its related property; LEFT NEST returns `[]` when unmatched.
|
|
387
|
+
JOIN preserves one row per match, including repeated root IDs. Choose an alias
|
|
388
|
+
that does not overwrite a root field. NEST does not promise source-key ordering.
|
|
389
|
+
|
|
390
|
+
For read-your-writes, pass tokens from successful SDK mutations:
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
import {MutationState, MutationToken} from 'couchbase';
|
|
394
|
+
|
|
395
|
+
async function readAfterWrites(tokens: MutationToken[]) {
|
|
396
|
+
return battles.findMany({
|
|
397
|
+
queryOptions: {consistentWith: new MutationState(...tokens)},
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
// Call with SDK mutationResult.token values, not whole mutation results.
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
`request_plus` and `consistentWith` are alternatives; neither is a multi-document
|
|
404
|
+
transaction. Defaults are unchanged when no consistency option is supplied.
|
|
405
|
+
See [joined reads and consistency](./docs/joins-and-consistency.md) for predicates,
|
|
406
|
+
projection limits, codecs, compatibility changes, and live validation results.
|
|
407
|
+
|
|
316
408
|
## Writes
|
|
317
409
|
|
|
318
410
|
```ts
|
package/dist/database.d.ts
CHANGED
|
@@ -3,6 +3,12 @@ export type CouchbaseStarterLogger = ((...args: any[]) => void) | false;
|
|
|
3
3
|
export interface StartCouchbaseOptions extends Partial<CouchsetArgs> {
|
|
4
4
|
logger?: CouchbaseStarterLogger;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Convert a PostgreSQL-style DB_URL into Couchbase SDK connection settings.
|
|
8
|
+
* The bucket lives in the URL path; it is not part of Couchbase's connection
|
|
9
|
+
* string, which contains only the scheme, host, optional port, and options.
|
|
10
|
+
*/
|
|
11
|
+
export declare const parseDatabaseUrl: (value: string) => Pick<CouchsetArgs, "bucketName" | "connectionString" | "password" | "username">;
|
|
6
12
|
export declare const getConnectionOptions: (overrides?: Partial<CouchsetArgs>) => CouchsetArgs;
|
|
7
13
|
export declare const connectionOptions: CouchsetArgs;
|
|
8
14
|
export declare const startCouchbase: (options?: StartCouchbaseOptions) => Promise<boolean>;
|
package/dist/database.js
CHANGED
|
@@ -86,13 +86,61 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
86
86
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
87
87
|
};
|
|
88
88
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
89
|
-
exports.startCouchbaseServerless = exports.startCouchbase = exports.connectionOptions = exports.getConnectionOptions = void 0;
|
|
89
|
+
exports.startCouchbaseServerless = exports.startCouchbase = exports.connectionOptions = exports.getConnectionOptions = exports.parseDatabaseUrl = void 0;
|
|
90
90
|
var connection_1 = __importDefault(require("./connection"));
|
|
91
91
|
var envValue = function (key, fallback) {
|
|
92
92
|
if (fallback === void 0) { fallback = ''; }
|
|
93
93
|
var value = typeof process !== 'undefined' && process.env ? process.env[key] : undefined;
|
|
94
94
|
return value === undefined ? fallback : value;
|
|
95
95
|
};
|
|
96
|
+
var databaseUrlError = function (reason) {
|
|
97
|
+
return new Error("DB_URL must use couchbase:// or couchbases://user:password@host/bucket: ".concat(reason));
|
|
98
|
+
};
|
|
99
|
+
var decodeDatabaseUrlComponent = function (value, label) {
|
|
100
|
+
try {
|
|
101
|
+
return decodeURIComponent(value);
|
|
102
|
+
}
|
|
103
|
+
catch (_error) {
|
|
104
|
+
throw databaseUrlError("".concat(label, " contains invalid percent-encoding"));
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Convert a PostgreSQL-style DB_URL into Couchbase SDK connection settings.
|
|
109
|
+
* The bucket lives in the URL path; it is not part of Couchbase's connection
|
|
110
|
+
* string, which contains only the scheme, host, optional port, and options.
|
|
111
|
+
*/
|
|
112
|
+
var parseDatabaseUrl = function (value) {
|
|
113
|
+
var url;
|
|
114
|
+
try {
|
|
115
|
+
url = new URL(value);
|
|
116
|
+
}
|
|
117
|
+
catch (_error) {
|
|
118
|
+
throw databaseUrlError('the value is not a valid URL');
|
|
119
|
+
}
|
|
120
|
+
if (url.protocol !== 'couchbase:' && url.protocol !== 'couchbases:') {
|
|
121
|
+
throw databaseUrlError("unsupported protocol ".concat(url.protocol || '(missing)'));
|
|
122
|
+
}
|
|
123
|
+
if (!url.hostname) {
|
|
124
|
+
throw databaseUrlError('a host is required');
|
|
125
|
+
}
|
|
126
|
+
if (!url.username || !url.password) {
|
|
127
|
+
throw databaseUrlError('a username and password are required');
|
|
128
|
+
}
|
|
129
|
+
if (url.hash) {
|
|
130
|
+
throw databaseUrlError('fragments are not supported');
|
|
131
|
+
}
|
|
132
|
+
var bucketName = decodeDatabaseUrlComponent(url.pathname.replace(/^\//, ''), 'the bucket name');
|
|
133
|
+
if (!bucketName || bucketName.indexOf('/') !== -1) {
|
|
134
|
+
throw databaseUrlError('the path must contain exactly one bucket name');
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
bucketName: bucketName,
|
|
138
|
+
connectionString: "".concat(url.protocol, "//").concat(url.host).concat(url.search),
|
|
139
|
+
password: decodeDatabaseUrlComponent(url.password, 'the password'),
|
|
140
|
+
username: decodeDatabaseUrlComponent(url.username, 'the username'),
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
exports.parseDatabaseUrl = parseDatabaseUrl;
|
|
96
144
|
var maskPassword = function (password) {
|
|
97
145
|
return password ? 'xxxxxx' : 'empty';
|
|
98
146
|
};
|
|
@@ -104,6 +152,7 @@ var splitStartOptions = function (options) {
|
|
|
104
152
|
var getConnectionOptions = function (overrides) {
|
|
105
153
|
if (overrides === void 0) { overrides = {}; }
|
|
106
154
|
var proxy = envValue('COUCHBASE_PROXY');
|
|
155
|
+
var databaseUrl = envValue('DB_URL');
|
|
107
156
|
var options = {
|
|
108
157
|
bucketName: envValue('COUCHBASE_BUCKET', 'dev'),
|
|
109
158
|
connectionString: envValue('COUCHBASE_URL', 'couchbase://localhost'),
|
|
@@ -113,7 +162,7 @@ var getConnectionOptions = function (overrides) {
|
|
|
113
162
|
if (proxy) {
|
|
114
163
|
options.proxy = proxy;
|
|
115
164
|
}
|
|
116
|
-
return __assign(__assign({}, options), overrides);
|
|
165
|
+
return __assign(__assign(__assign({}, options), (databaseUrl ? (0, exports.parseDatabaseUrl)(databaseUrl) : {})), overrides);
|
|
117
166
|
};
|
|
118
167
|
exports.getConnectionOptions = getConnectionOptions;
|
|
119
168
|
exports.connectionOptions = (0, exports.getConnectionOptions)();
|
package/dist/database.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"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","sourcesContent":["import CouchbaseConnection, {CouchsetArgs} from './connection';\n\nexport type CouchbaseStarterLogger = ((...args: any[]) => void) | false;\n\nexport interface StartCouchbaseOptions extends Partial<CouchsetArgs> {\n logger?: CouchbaseStarterLogger;\n}\n\nconst envValue = (key: string, fallback = ''): string => {\n const value = typeof process !== 'undefined' && process.env ? process.env[key] : undefined;\n\n return value === undefined ? fallback : value;\n};\n\nconst maskPassword = (password: string): string => {\n return password ? 'xxxxxx' : 'empty';\n};\n\nconst splitStartOptions = (options: StartCouchbaseOptions = {}) => {\n const {logger, ...connectionOverrides} = options;\n\n return {connectionOverrides, logger};\n};\n\nexport const getConnectionOptions = (overrides: Partial<CouchsetArgs> = {}): CouchsetArgs => {\n const proxy = envValue('COUCHBASE_PROXY');\n const options: CouchsetArgs = {\n bucketName: envValue('COUCHBASE_BUCKET', 'dev'),\n connectionString: envValue('COUCHBASE_URL', 'couchbase://localhost'),\n password: envValue('COUCHBASE_PASSWORD', '1234'),\n username: envValue('COUCHBASE_USERNAME', 'admin'),\n };\n\n if (proxy) {\n options.proxy = proxy;\n }\n\n return {\n ...options,\n ...overrides,\n };\n};\n\nexport const connectionOptions: CouchsetArgs = getConnectionOptions();\n\nconst refreshConnectionOptions = (overrides: Partial<CouchsetArgs> = {}): CouchsetArgs => {\n const nextOptions = getConnectionOptions(overrides);\n\n Object.keys(connectionOptions).forEach((key) => {\n delete (connectionOptions as any)[key];\n });\n Object.assign(connectionOptions, nextOptions);\n\n return connectionOptions;\n};\n\nconst logConnectionStart = (\n options: CouchsetArgs,\n logger: CouchbaseStarterLogger | undefined,\n mode: string\n): void => {\n if (logger === false) {\n return;\n }\n\n const log = logger || console.log;\n\n log(\n 'Couchbase',\n 'starting',\n mode,\n JSON.stringify({\n bucket: options.bucketName,\n host: options.connectionString,\n password: maskPassword(options.password),\n username: options.username,\n })\n );\n};\n\nconst logConnectionStarted = (\n options: CouchsetArgs,\n logger: CouchbaseStarterLogger | undefined,\n mode: string\n): void => {\n if (logger === false) {\n return;\n }\n\n const log = logger || console.log;\n\n log(\n 'Couchbase',\n 'started',\n mode,\n JSON.stringify({bucket: options.bucketName, host: options.connectionString})\n );\n};\n\nexport const startCouchbase = async (options: StartCouchbaseOptions = {}): Promise<boolean> => {\n const {connectionOverrides, logger} = splitStartOptions(options);\n const resolvedOptions = refreshConnectionOptions(connectionOverrides);\n\n logConnectionStart(resolvedOptions, logger, 'standard');\n await CouchbaseConnection.Instance.init(resolvedOptions);\n logConnectionStarted(resolvedOptions, logger, 'standard');\n\n return true;\n};\n\nexport const startCouchbaseServerless = async (\n options: StartCouchbaseOptions = {}\n): Promise<boolean> => {\n const {connectionOverrides, logger} = splitStartOptions(options);\n const resolvedOptions = refreshConnectionOptions(connectionOverrides);\n\n logConnectionStart(resolvedOptions, logger, 'serverless');\n await CouchbaseConnection.Instance.initServerless(resolvedOptions);\n logConnectionStarted(resolvedOptions, logger, 'serverless');\n\n return true;\n};\n"]}
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"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","sourcesContent":["import CouchbaseConnection, {CouchsetArgs} from './connection';\n\nexport type CouchbaseStarterLogger = ((...args: any[]) => void) | false;\n\nexport interface StartCouchbaseOptions extends Partial<CouchsetArgs> {\n logger?: CouchbaseStarterLogger;\n}\n\nconst envValue = (key: string, fallback = ''): string => {\n const value = typeof process !== 'undefined' && process.env ? process.env[key] : undefined;\n\n return value === undefined ? fallback : value;\n};\n\nconst databaseUrlError = (reason: string): Error =>\n new Error(`DB_URL must use couchbase:// or couchbases://user:password@host/bucket: ${reason}`);\n\nconst decodeDatabaseUrlComponent = (value: string, label: string): string => {\n try {\n return decodeURIComponent(value);\n } catch (_error) {\n throw databaseUrlError(`${label} contains invalid percent-encoding`);\n }\n};\n\n/**\n * Convert a PostgreSQL-style DB_URL into Couchbase SDK connection settings.\n * The bucket lives in the URL path; it is not part of Couchbase's connection\n * string, which contains only the scheme, host, optional port, and options.\n */\nexport const parseDatabaseUrl = (\n value: string\n): Pick<CouchsetArgs, 'bucketName' | 'connectionString' | 'password' | 'username'> => {\n let url: URL;\n\n try {\n url = new URL(value);\n } catch (_error) {\n throw databaseUrlError('the value is not a valid URL');\n }\n\n if (url.protocol !== 'couchbase:' && url.protocol !== 'couchbases:') {\n throw databaseUrlError(`unsupported protocol ${url.protocol || '(missing)'}`);\n }\n if (!url.hostname) {\n throw databaseUrlError('a host is required');\n }\n if (!url.username || !url.password) {\n throw databaseUrlError('a username and password are required');\n }\n if (url.hash) {\n throw databaseUrlError('fragments are not supported');\n }\n\n const bucketName = decodeDatabaseUrlComponent(\n url.pathname.replace(/^\\//, ''),\n 'the bucket name'\n );\n if (!bucketName || bucketName.indexOf('/') !== -1) {\n throw databaseUrlError('the path must contain exactly one bucket name');\n }\n\n return {\n bucketName,\n connectionString: `${url.protocol}//${url.host}${url.search}`,\n password: decodeDatabaseUrlComponent(url.password, 'the password'),\n username: decodeDatabaseUrlComponent(url.username, 'the username'),\n };\n};\n\nconst maskPassword = (password: string): string => {\n return password ? 'xxxxxx' : 'empty';\n};\n\nconst splitStartOptions = (options: StartCouchbaseOptions = {}) => {\n const {logger, ...connectionOverrides} = options;\n\n return {connectionOverrides, logger};\n};\n\nexport const getConnectionOptions = (overrides: Partial<CouchsetArgs> = {}): CouchsetArgs => {\n const proxy = envValue('COUCHBASE_PROXY');\n const databaseUrl = envValue('DB_URL');\n const options: CouchsetArgs = {\n bucketName: envValue('COUCHBASE_BUCKET', 'dev'),\n connectionString: envValue('COUCHBASE_URL', 'couchbase://localhost'),\n password: envValue('COUCHBASE_PASSWORD', '1234'),\n username: envValue('COUCHBASE_USERNAME', 'admin'),\n };\n\n if (proxy) {\n options.proxy = proxy;\n }\n\n return {\n ...options,\n ...(databaseUrl ? parseDatabaseUrl(databaseUrl) : {}),\n ...overrides,\n };\n};\n\nexport const connectionOptions: CouchsetArgs = getConnectionOptions();\n\nconst refreshConnectionOptions = (overrides: Partial<CouchsetArgs> = {}): CouchsetArgs => {\n const nextOptions = getConnectionOptions(overrides);\n\n Object.keys(connectionOptions).forEach((key) => {\n delete (connectionOptions as any)[key];\n });\n Object.assign(connectionOptions, nextOptions);\n\n return connectionOptions;\n};\n\nconst logConnectionStart = (\n options: CouchsetArgs,\n logger: CouchbaseStarterLogger | undefined,\n mode: string\n): void => {\n if (logger === false) {\n return;\n }\n\n const log = logger || console.log;\n\n log(\n 'Couchbase',\n 'starting',\n mode,\n JSON.stringify({\n bucket: options.bucketName,\n host: options.connectionString,\n password: maskPassword(options.password),\n username: options.username,\n })\n );\n};\n\nconst logConnectionStarted = (\n options: CouchsetArgs,\n logger: CouchbaseStarterLogger | undefined,\n mode: string\n): void => {\n if (logger === false) {\n return;\n }\n\n const log = logger || console.log;\n\n log(\n 'Couchbase',\n 'started',\n mode,\n JSON.stringify({bucket: options.bucketName, host: options.connectionString})\n );\n};\n\nexport const startCouchbase = async (options: StartCouchbaseOptions = {}): Promise<boolean> => {\n const {connectionOverrides, logger} = splitStartOptions(options);\n const resolvedOptions = refreshConnectionOptions(connectionOverrides);\n\n logConnectionStart(resolvedOptions, logger, 'standard');\n await CouchbaseConnection.Instance.init(resolvedOptions);\n logConnectionStarted(resolvedOptions, logger, 'standard');\n\n return true;\n};\n\nexport const startCouchbaseServerless = async (\n options: StartCouchbaseOptions = {}\n): Promise<boolean> => {\n const {connectionOverrides, logger} = splitStartOptions(options);\n const resolvedOptions = refreshConnectionOptions(connectionOverrides);\n\n logConnectionStart(resolvedOptions, logger, 'serverless');\n await CouchbaseConnection.Instance.initServerless(resolvedOptions);\n logConnectionStarted(resolvedOptions, logger, 'serverless');\n\n return true;\n};\n"]}
|
package/dist/model/include.d.ts
CHANGED
|
@@ -2,22 +2,47 @@ import { LogicalWhereExpr, SortType } from '../query/interface/query.types';
|
|
|
2
2
|
export type IncludeType = 'join' | 'leftJoin' | 'nest' | 'leftNest';
|
|
3
3
|
export interface IncludeDefinition {
|
|
4
4
|
as: string;
|
|
5
|
+
/** ANSI predicate; mutually exclusive with key/keys. */
|
|
6
|
+
on?: JoinPredicate;
|
|
7
|
+
/** Explicit trusted SQL++ escape hatch. Values still use positional placeholders. */
|
|
8
|
+
onRaw?: {
|
|
9
|
+
sql: string;
|
|
10
|
+
values?: readonly unknown[];
|
|
11
|
+
};
|
|
12
|
+
/** Top-level fields retained from the related document. */
|
|
13
|
+
select?: readonly string[];
|
|
5
14
|
key?: string;
|
|
6
15
|
keys?: string;
|
|
7
16
|
keyspace?: string;
|
|
8
17
|
model?: string | {
|
|
9
18
|
keyspace: () => string;
|
|
19
|
+
parse?: <T>(data: T) => T;
|
|
20
|
+
parseProjection?: <T>(data: T) => T;
|
|
10
21
|
};
|
|
11
22
|
type?: IncludeType;
|
|
12
23
|
optional?: boolean;
|
|
13
24
|
}
|
|
25
|
+
/** A field operand is explicit; bare strings are always parameter values. */
|
|
26
|
+
export interface JoinField {
|
|
27
|
+
readonly $field: string;
|
|
28
|
+
}
|
|
29
|
+
export type JoinPredicate = {
|
|
30
|
+
$and: readonly JoinPredicate[];
|
|
31
|
+
} | {
|
|
32
|
+
$or: readonly JoinPredicate[];
|
|
33
|
+
} | {
|
|
34
|
+
left: JoinField;
|
|
35
|
+
op: '$eq' | '$neq' | '$gt' | '$gte' | '$lt' | '$lte';
|
|
36
|
+
right: unknown;
|
|
37
|
+
};
|
|
38
|
+
export declare const joinField: (path: string) => JoinField;
|
|
14
39
|
export interface IncludedSelectionQueryArgs {
|
|
15
40
|
keyspace: string;
|
|
16
41
|
collectionName: string;
|
|
17
|
-
select?: any[] | string;
|
|
42
|
+
select?: readonly any[] | string;
|
|
18
43
|
where?: LogicalWhereExpr;
|
|
19
44
|
orderBy?: Record<string, SortType>;
|
|
20
|
-
include?: IncludeDefinition[];
|
|
45
|
+
include?: readonly IncludeDefinition[];
|
|
21
46
|
limit?: number;
|
|
22
47
|
page?: number;
|
|
23
48
|
offset?: number;
|
|
@@ -29,5 +54,13 @@ export interface IncludedSelectionQuery {
|
|
|
29
54
|
selectAll: boolean;
|
|
30
55
|
resultKey?: string;
|
|
31
56
|
}
|
|
32
|
-
|
|
57
|
+
declare class IncludeParameterStore {
|
|
58
|
+
parameters: Record<string, any>;
|
|
59
|
+
private index;
|
|
60
|
+
add(value: any): string;
|
|
61
|
+
set(key: string, value: any): string;
|
|
62
|
+
}
|
|
63
|
+
export declare const includeOperator: (include: IncludeDefinition) => string;
|
|
64
|
+
export declare const buildIncludeClauses: (keyspace: string, include?: readonly IncludeDefinition[], sourceAlias?: string, store?: IncludeParameterStore) => string;
|
|
33
65
|
export declare const buildIncludedSelectionQuery: (args: IncludedSelectionQueryArgs) => IncludedSelectionQuery;
|
|
66
|
+
export {};
|
package/dist/model/include.js
CHANGED
|
@@ -25,10 +25,16 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
25
25
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
26
26
|
};
|
|
27
27
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
-
exports.buildIncludedSelectionQuery = exports.buildIncludeClauses = void 0;
|
|
29
|
-
var helpers_1 = require("../query/helpers");
|
|
30
|
-
var utils_1 = require("../query/utils");
|
|
28
|
+
exports.buildIncludedSelectionQuery = exports.buildIncludeClauses = exports.includeOperator = exports.joinField = void 0;
|
|
31
29
|
var keyspace_1 = require("./keyspace");
|
|
30
|
+
var joinField = function (path) { return ({ $field: path }); };
|
|
31
|
+
exports.joinField = joinField;
|
|
32
|
+
var identifier = function (name) {
|
|
33
|
+
if (!name || name.indexOf('\0') !== -1)
|
|
34
|
+
throw new Error('Empty or invalid identifier');
|
|
35
|
+
return (0, keyspace_1.escapeIdentifier)(name);
|
|
36
|
+
};
|
|
37
|
+
var fieldPath = function (path) { return path.split('.').map(identifier).join('.'); };
|
|
32
38
|
var comparisonOperators = {
|
|
33
39
|
$eq: '=',
|
|
34
40
|
$neq: '!=',
|
|
@@ -74,26 +80,30 @@ var IncludeParameterStore = /** @class */ (function () {
|
|
|
74
80
|
};
|
|
75
81
|
return IncludeParameterStore;
|
|
76
82
|
}());
|
|
77
|
-
var safeAlias =
|
|
78
|
-
|
|
83
|
+
var safeAlias = identifier;
|
|
84
|
+
var safeKeyspace = function (target) {
|
|
85
|
+
// Accept existing model.keyspace() output and simple dotted identifiers, never expressions.
|
|
86
|
+
var token = '(?:`(?:[^`]|``)+`|[A-Za-z_][A-Za-z0-9_-]*)';
|
|
87
|
+
if (!new RegExp("^(?:default:)?".concat(token, "(?:\\.").concat(token, "){0,2}$")).test(target))
|
|
88
|
+
throw new Error('Invalid include keyspace; use a model or identifier path');
|
|
89
|
+
return target.replace(/[A-Za-z_][A-Za-z0-9_-]*|`(?:[^`]|``)+`/g, function (part, offset) {
|
|
90
|
+
return target.slice(offset + part.length, offset + part.length + 1) === ':' || part.startsWith('`')
|
|
91
|
+
? part
|
|
92
|
+
: identifier(part);
|
|
93
|
+
});
|
|
79
94
|
};
|
|
80
95
|
var keyspaceFromInclude = function (include, defaultKeyspace) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
if (include.model) {
|
|
88
|
-
return include.model.keyspace();
|
|
89
|
-
}
|
|
90
|
-
return defaultKeyspace;
|
|
96
|
+
var _a;
|
|
97
|
+
var target = include.keyspace ||
|
|
98
|
+
(typeof include.model === 'string' ? include.model : (_a = include.model) === null || _a === void 0 ? void 0 : _a.keyspace()) ||
|
|
99
|
+
defaultKeyspace;
|
|
100
|
+
return safeKeyspace(target);
|
|
91
101
|
};
|
|
92
102
|
var sourceField = function (field, sourceAlias) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
var prefix = "".concat(sourceAlias, ".");
|
|
104
|
+
return field.indexOf(prefix) === 0
|
|
105
|
+
? fieldPath(field)
|
|
106
|
+
: "".concat(identifier(sourceAlias), ".").concat(fieldPath(field));
|
|
97
107
|
};
|
|
98
108
|
var includeOperator = function (include) {
|
|
99
109
|
if (include.type === 'leftNest') {
|
|
@@ -105,22 +115,92 @@ var includeOperator = function (include) {
|
|
|
105
115
|
if (include.type === 'leftJoin') {
|
|
106
116
|
return 'LEFT JOIN';
|
|
107
117
|
}
|
|
118
|
+
if (include.type === 'join') {
|
|
119
|
+
return include.optional ? 'LEFT JOIN' : 'JOIN';
|
|
120
|
+
}
|
|
108
121
|
if (include.keys) {
|
|
109
122
|
return include.optional ? 'LEFT NEST' : 'NEST';
|
|
110
123
|
}
|
|
111
124
|
return include.optional ? 'LEFT JOIN' : 'JOIN';
|
|
112
125
|
};
|
|
113
|
-
|
|
126
|
+
exports.includeOperator = includeOperator;
|
|
127
|
+
var predicateExpr = function (predicate, store, aliases) {
|
|
128
|
+
if (!predicate || typeof predicate !== 'object')
|
|
129
|
+
throw new Error('Invalid ON predicate');
|
|
130
|
+
var keys = Object.keys(predicate);
|
|
131
|
+
if (keys.length === 1 && (keys[0] === '$and' || keys[0] === '$or')) {
|
|
132
|
+
var children = predicate[keys[0]];
|
|
133
|
+
if (!Array.isArray(children) || !children.length)
|
|
134
|
+
throw new Error('ON logical groups must be nonempty');
|
|
135
|
+
return "(".concat(children
|
|
136
|
+
.map(function (child) { return predicateExpr(child, store, aliases); })
|
|
137
|
+
.join(keys[0] === '$and' ? ' AND ' : ' OR '), ")");
|
|
138
|
+
}
|
|
139
|
+
var comparison = predicate;
|
|
140
|
+
if (keys.length !== 3 ||
|
|
141
|
+
!keys.includes('left') ||
|
|
142
|
+
!keys.includes('right') ||
|
|
143
|
+
!keys.includes('op') ||
|
|
144
|
+
!Object.prototype.hasOwnProperty.call(comparisonOperators, comparison.op))
|
|
145
|
+
throw new Error('Unsupported ON comparison');
|
|
146
|
+
var reference = function (value) {
|
|
147
|
+
if (!value || typeof value.$field !== 'string' || Object.keys(value).length !== 1)
|
|
148
|
+
throw new Error('Expected explicit joinField reference');
|
|
149
|
+
var parts = value.$field.split('.');
|
|
150
|
+
if (parts.length < 2 || !aliases.has(parts[0]))
|
|
151
|
+
throw new Error("Unknown ON alias: ".concat(parts[0]));
|
|
152
|
+
return fieldPath(value.$field);
|
|
153
|
+
};
|
|
154
|
+
var right = comparison.right;
|
|
155
|
+
return "".concat(reference(comparison.left), " ").concat(comparisonOperators[comparison.op], " ").concat(right && typeof right === 'object' && Object.prototype.hasOwnProperty.call(right, '$field')
|
|
156
|
+
? reference(right)
|
|
157
|
+
: store.add(right));
|
|
158
|
+
};
|
|
159
|
+
var buildIncludeClauses = function (keyspace, include, sourceAlias, store) {
|
|
114
160
|
if (include === void 0) { include = []; }
|
|
115
161
|
if (sourceAlias === void 0) { sourceAlias = 'doc'; }
|
|
162
|
+
if (store === void 0) { store = new IncludeParameterStore(); }
|
|
163
|
+
var aliases = new Set([sourceAlias]);
|
|
164
|
+
var reservedAliases = new Set(['__cs_root', '__proto__', 'constructor', 'prototype']);
|
|
165
|
+
var modes = new Set(include.map(function (item) { return (item.on || item.onRaw ? 'ansi' : 'keys'); }));
|
|
166
|
+
// Couchbase does not permit mixing lookup and ANSI joins in the same FROM clause.
|
|
167
|
+
if (modes.size > 1)
|
|
168
|
+
throw new Error('Cannot mix ANSI ON and ON KEYS includes');
|
|
116
169
|
return include
|
|
117
170
|
.map(function (item) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
171
|
+
if (aliases.has(item.as) || reservedAliases.has(item.as))
|
|
172
|
+
throw new Error("Duplicate or reserved include alias: ".concat(item.as));
|
|
173
|
+
aliases.add(item.as);
|
|
174
|
+
if (item.type && !['join', 'leftJoin', 'nest', 'leftNest'].includes(item.type))
|
|
175
|
+
throw new Error('Unsupported include type');
|
|
176
|
+
if ([
|
|
177
|
+
item.key !== undefined,
|
|
178
|
+
item.keys !== undefined,
|
|
179
|
+
item.on !== undefined,
|
|
180
|
+
item.onRaw !== undefined,
|
|
181
|
+
].filter(Boolean).length !== 1) {
|
|
182
|
+
throw new Error("include ".concat(item.as, " requires exactly one of key, keys, on, onRaw"));
|
|
183
|
+
}
|
|
184
|
+
var condition;
|
|
185
|
+
if (item.on)
|
|
186
|
+
condition = "ON ".concat(predicateExpr(item.on, store, aliases));
|
|
187
|
+
else if (item.onRaw) {
|
|
188
|
+
var values_1 = item.onRaw.values || [];
|
|
189
|
+
var index_1 = 0;
|
|
190
|
+
if (!item.onRaw.sql.trim())
|
|
191
|
+
throw new Error('onRaw requires SQL');
|
|
192
|
+
var sql = item.onRaw.sql.replace(/\?/g, function () {
|
|
193
|
+
if (index_1 >= values_1.length)
|
|
194
|
+
throw new Error('onRaw placeholder/value mismatch');
|
|
195
|
+
return store.add(values_1[index_1++]);
|
|
196
|
+
});
|
|
197
|
+
if (index_1 !== values_1.length)
|
|
198
|
+
throw new Error('onRaw placeholder/value mismatch');
|
|
199
|
+
condition = "ON (".concat(sql, ")");
|
|
121
200
|
}
|
|
122
|
-
|
|
123
|
-
|
|
201
|
+
else
|
|
202
|
+
condition = "ON KEYS ".concat(sourceField((item.key || item.keys), sourceAlias));
|
|
203
|
+
return "".concat((0, exports.includeOperator)(item), " ").concat(keyspaceFromInclude(item, keyspace), " AS ").concat(safeAlias(item.as), " ").concat(condition);
|
|
124
204
|
})
|
|
125
205
|
.join(' ');
|
|
126
206
|
};
|
|
@@ -201,46 +281,71 @@ var buildWhereExpr = function (expr, store, sourceAlias) {
|
|
|
201
281
|
var buildOrderByExpr = function (orderBy, sourceAlias) {
|
|
202
282
|
return orderBy
|
|
203
283
|
? " ORDER BY ".concat(Object.keys(orderBy)
|
|
204
|
-
.map(function (field) {
|
|
284
|
+
.map(function (field) {
|
|
285
|
+
if (!['ASC', 'DESC'].includes(orderBy[field]))
|
|
286
|
+
throw new Error('Unsupported order direction');
|
|
287
|
+
return "".concat(sourceField(field, sourceAlias), " ").concat(orderBy[field]);
|
|
288
|
+
})
|
|
205
289
|
.join(','))
|
|
206
290
|
: '';
|
|
207
291
|
};
|
|
292
|
+
var selectedObject = function (alias, fields) {
|
|
293
|
+
if (!fields)
|
|
294
|
+
return identifier(alias);
|
|
295
|
+
if (!fields.length || new Set(fields).size !== fields.length)
|
|
296
|
+
throw new Error('Projection must contain distinct fields');
|
|
297
|
+
return "{".concat(fields
|
|
298
|
+
.map(function (field) {
|
|
299
|
+
if (field.includes('.'))
|
|
300
|
+
throw new Error('Include projections support top-level fields only');
|
|
301
|
+
return "".concat(JSON.stringify(field), ": ").concat(identifier(alias), ".").concat(identifier(field));
|
|
302
|
+
})
|
|
303
|
+
.join(', '), "}");
|
|
304
|
+
};
|
|
208
305
|
var buildSelectExpr = function (select, include, sourceAlias) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
306
|
+
var all = !select || select === '*';
|
|
307
|
+
var structured = Array.isArray(select) &&
|
|
308
|
+
select.every(function (field) { return typeof field === 'string' && !field.includes('.'); });
|
|
309
|
+
var root = all
|
|
310
|
+
? identifier(sourceAlias)
|
|
311
|
+
: structured
|
|
312
|
+
? selectedObject(sourceAlias, select)
|
|
313
|
+
: undefined;
|
|
314
|
+
var relations = include.map(function (item) {
|
|
315
|
+
var alias = identifier(item.as);
|
|
316
|
+
if (!item.select)
|
|
317
|
+
return "".concat(alias, " AS ").concat(alias);
|
|
318
|
+
var object = selectedObject('__cs_item', item.select);
|
|
319
|
+
var expression = (0, exports.includeOperator)(item).includes('NEST')
|
|
320
|
+
? "ARRAY ".concat(object, " FOR ").concat(identifier('__cs_item'), " IN ").concat(alias, " END")
|
|
321
|
+
: "CASE WHEN ".concat(alias, " IS MISSING THEN MISSING WHEN ").concat(alias, " IS NULL THEN NULL ELSE ").concat(selectedObject(item.as, item.select), " END");
|
|
322
|
+
return "".concat(expression, " AS ").concat(alias);
|
|
323
|
+
});
|
|
324
|
+
if (structured &&
|
|
325
|
+
select.some(function (field) { return include.some(function (item) { return item.as === field; }); }))
|
|
326
|
+
throw new Error('Include alias conflicts with projected root field');
|
|
327
|
+
if (root)
|
|
212
328
|
return {
|
|
213
|
-
expr:
|
|
214
|
-
selectAll:
|
|
215
|
-
resultKey:
|
|
329
|
+
expr: __spreadArray(["".concat(root, " AS __cs_root")], __read(relations), false).join(', '),
|
|
330
|
+
selectAll: all,
|
|
331
|
+
resultKey: '__cs_root',
|
|
216
332
|
};
|
|
217
|
-
|
|
218
|
-
var includeSelect = include.map(function (item) {
|
|
219
|
-
var alias = safeAlias(item.as);
|
|
220
|
-
return "".concat(alias, " AS ").concat(alias);
|
|
221
|
-
});
|
|
222
|
-
if (typeof select === 'string') {
|
|
223
|
-
return { expr: __spreadArray([select], __read(includeSelect), false).join(','), selectAll: false };
|
|
224
|
-
}
|
|
225
|
-
var modelSelect = select.map(function (item) {
|
|
226
|
-
return typeof item === 'string'
|
|
227
|
-
? sourceField(item, sourceAlias)
|
|
228
|
-
: (0, helpers_1.buildSelectArrayExpr)([item]);
|
|
229
|
-
});
|
|
230
|
-
return { expr: __spreadArray(__spreadArray([], __read(modelSelect), false), __read(includeSelect), false).join(','), selectAll: false };
|
|
333
|
+
throw new Error('Includes support only * or top-level field-array projections; use queryRows for arbitrary SQL++ projections');
|
|
231
334
|
};
|
|
232
335
|
var buildIncludedSelectionQuery = function (args) {
|
|
233
|
-
var sourceAlias =
|
|
336
|
+
var sourceAlias = args.sourceAlias || 'doc';
|
|
337
|
+
if (sourceAlias === '__cs_root')
|
|
338
|
+
throw new Error('Reserved source alias');
|
|
234
339
|
var include = args.include || [];
|
|
235
340
|
var store = new IncludeParameterStore();
|
|
236
341
|
var limit = typeof args.limit === 'number' ? args.limit : 10;
|
|
237
342
|
var page = typeof args.page === 'number' ? args.page : 0;
|
|
238
343
|
var offset = typeof args.offset === 'number' ? args.offset : page * limit;
|
|
239
344
|
var _a = buildSelectExpr(args.select, include, sourceAlias), expr = _a.expr, selectAll = _a.selectAll, resultKey = _a.resultKey;
|
|
240
|
-
var joins = (0, exports.buildIncludeClauses)(args.keyspace, include, sourceAlias);
|
|
345
|
+
var joins = (0, exports.buildIncludeClauses)(args.keyspace, include, sourceAlias, store);
|
|
241
346
|
var limitExpr = " LIMIT ".concat(store.set('cs_limit', limit), " OFFSET ").concat(store.set('cs_offset', offset));
|
|
242
347
|
return {
|
|
243
|
-
query: "SELECT ".concat(expr, " FROM ").concat(args.keyspace, " AS ").concat(sourceAlias, " ").concat(joins).concat(buildWhereExpr(args.where, store, sourceAlias)).concat(buildOrderByExpr(args.orderBy, sourceAlias)).concat(limitExpr),
|
|
348
|
+
query: "SELECT ".concat(expr, " FROM ").concat(args.keyspace, " AS ").concat(identifier(sourceAlias), " ").concat(joins).concat(buildWhereExpr(args.where, store, sourceAlias)).concat(buildOrderByExpr(args.orderBy, sourceAlias)).concat(limitExpr),
|
|
244
349
|
parameters: store.parameters,
|
|
245
350
|
resultKey: resultKey,
|
|
246
351
|
selectAll: selectAll,
|