couchset 0.4.1 → 0.5.0
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 +84 -1
- package/dist/eventing.d.ts +179 -0
- package/dist/eventing.js +822 -0
- package/dist/eventing.js.map +1 -0
- package/dist/next.d.ts +8 -0
- package/dist/next.js +23 -0
- package/dist/next.js.map +1 -1
- package/docs/next-primitives.md +33 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
CouchSet is a Couchbase model layer for TypeScript and Node.js. The default `couchset` entrypoint keeps the legacy API for safe upgrades; the modern API is available from `couchset/next`.
|
|
12
12
|
|
|
13
|
-
The additive client-owned primitives—typed definitions, explicit provisioning, transaction-bound models, CAS helpers,
|
|
13
|
+
The additive client-owned primitives—typed definitions, explicit provisioning, transaction-bound models, CAS helpers, safe index plans, and Eventing—are summarized below. Their complete operational guide is [Next primitives](./docs/next-primitives.md).
|
|
14
14
|
|
|
15
15
|
- [Install](#install)
|
|
16
16
|
- [Legacy Default](#legacy-default)
|
|
17
17
|
- [Modern API](#modern-api)
|
|
18
|
+
- [Modern Client Primitives](#modern-client-primitives)
|
|
18
19
|
- [Next primitives](./docs/next-primitives.md)
|
|
19
20
|
- [Connection Lifecycle](#connection-lifecycle)
|
|
20
21
|
- [Models](#models)
|
|
@@ -113,6 +114,88 @@ const page = await users.page<User>({
|
|
|
113
114
|
await users.deleteById(created.id, {hard: true});
|
|
114
115
|
```
|
|
115
116
|
|
|
117
|
+
## Modern Client Primitives
|
|
118
|
+
|
|
119
|
+
`couchset/next` also offers a client-owned API for typed manifests, explicit administrative work, safe operational plans, and Eventing. These are deliberately separate from the singleton `Model` API above.
|
|
120
|
+
|
|
121
|
+
### Declarative models
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import {createCouchsetClient, defineModel} from 'couchset/next';
|
|
125
|
+
|
|
126
|
+
const sessions = defineModel({name: 'Session', scope: 'auth', collection: 'sessions'});
|
|
127
|
+
const db = createCouchsetClient({bucketName: 'app', models: [sessions]});
|
|
128
|
+
const sessionModel = db.model(sessions); // registers and binds; no DDL
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Read the [full client and model-definition guide](./docs/next-primitives.md#couchset-next-primitives).
|
|
132
|
+
|
|
133
|
+
### Provisioning and dynamic models
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
await db.ensureCollections(); // creates only missing scopes and collections
|
|
137
|
+
await db.ensureIndexes(); // create-only index DDL
|
|
138
|
+
|
|
139
|
+
const reports = await db.registerModel(reportDefinition, {provision: {collections: true}});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Read about [provisioning and dynamic models](./docs/next-primitives.md#provisioning-and-dynamic-models).
|
|
143
|
+
|
|
144
|
+
### Transactions and CAS
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
await db.transaction(async (tx) => {
|
|
148
|
+
await tx.model(sessions).insert({id: 'session::1', userId: 'user::1'});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const outcome = await sessionModel.consumeOnce('session::1', knownCas);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Read the [transaction and CAS safety notes](./docs/next-primitives.md#transactions-and-cas).
|
|
155
|
+
|
|
156
|
+
### Safe index plans
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const plan = await db.planIndexes(); // inspect missing, matching, and drifted indexes
|
|
160
|
+
await db.applyIndexPlan(plan); // creates safe replacements and waits for them
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Read about [index drift and opt-in cleanup](./docs/next-primitives.md#index-drift).
|
|
164
|
+
|
|
165
|
+
### Eventing functions
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import {defineEventingFunction} from 'couchset/next';
|
|
169
|
+
|
|
170
|
+
const auditOrders = defineEventingFunction({
|
|
171
|
+
name: 'audit_orders',
|
|
172
|
+
code: 'function OnUpdate(doc, meta) { log(meta.id); }',
|
|
173
|
+
sourceKeyspace: {bucket: 'app', scope: 'sales', collection: 'orders'},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const eventing = db.eventing({
|
|
177
|
+
namespace: 'billing',
|
|
178
|
+
metadataKeyspace: {bucket: 'app', scope: 'eventing', collection: 'billing_metadata'},
|
|
179
|
+
definitions: [auditOrders],
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await eventing.apply();
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Read the [Eventing lifecycle and safety guide](./docs/next-primitives.md#eventing-functions).
|
|
186
|
+
|
|
187
|
+
### Test fixtures
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import {createCouchsetTestFixture} from 'couchset/next';
|
|
191
|
+
|
|
192
|
+
const {model, cleanup} = await createCouchsetTestFixture(db, {name: 'Invoice'});
|
|
193
|
+
// use model in the test
|
|
194
|
+
await cleanup();
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Read about [isolated CouchSet test fixtures](./docs/next-primitives.md#tests).
|
|
198
|
+
|
|
116
199
|
## Connection Lifecycle
|
|
117
200
|
|
|
118
201
|
Models can be declared before connecting. Model operations wait for the shared connection before binding to the Couchbase bucket and collection.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { EventingFunction, EventingFunctionManager, EventingFunctionSettings, EventingFunctionState } from 'couchbase';
|
|
2
|
+
/** A Couchbase bucket/scope/collection used by an Eventing function. */
|
|
3
|
+
export interface EventingKeyspace {
|
|
4
|
+
bucket: string;
|
|
5
|
+
scope?: string;
|
|
6
|
+
collection?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A declarative Eventing handler. `name` is logical: CouchSet derives the
|
|
10
|
+
* physical Couchbase name from the Eventing namespace.
|
|
11
|
+
*/
|
|
12
|
+
export interface EventingDefinition {
|
|
13
|
+
name: string;
|
|
14
|
+
code: string;
|
|
15
|
+
sourceKeyspace: EventingKeyspace;
|
|
16
|
+
enforceSchema?: boolean;
|
|
17
|
+
bucketBindings?: EventingFunction['bucketBindings'];
|
|
18
|
+
urlBindings?: EventingFunction['urlBindings'];
|
|
19
|
+
constantBindings?: EventingFunction['constantBindings'];
|
|
20
|
+
settings?: Partial<EventingFunctionSettings>;
|
|
21
|
+
}
|
|
22
|
+
export type EventingFunctionDefinition = EventingDefinition;
|
|
23
|
+
/**
|
|
24
|
+
* The metadata collection is used only by Couchbase Eventing for checkpoints
|
|
25
|
+
* and timers. CouchSet never opens it as an application collection or writes
|
|
26
|
+
* documents to it.
|
|
27
|
+
*/
|
|
28
|
+
export interface EventingOptions {
|
|
29
|
+
/** Required ownership boundary for physical Couchbase function names. */
|
|
30
|
+
namespace: string;
|
|
31
|
+
/** A dedicated collection reserved for Couchbase Eventing metadata. */
|
|
32
|
+
metadataKeyspace: EventingKeyspace;
|
|
33
|
+
/** Definitions registered when the control plane is constructed. */
|
|
34
|
+
definitions?: EventingDefinition[];
|
|
35
|
+
/** Alias for definitions, useful for manifest-shaped configuration. */
|
|
36
|
+
functions?: EventingDefinition[];
|
|
37
|
+
/** Explicit SDK manager injection, primarily for tests or owned clusters. */
|
|
38
|
+
manager?: EventingFunctionManagerLike;
|
|
39
|
+
/** Maximum time to wait for Couchbase Eventing lifecycle convergence. */
|
|
40
|
+
lifecycleTimeoutMs?: number;
|
|
41
|
+
/** Delay between lifecycle status checks. Defaults to 250ms. */
|
|
42
|
+
lifecyclePollIntervalMs?: number;
|
|
43
|
+
}
|
|
44
|
+
export interface EventingApplyOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Permit source or metadata keyspace changes. Couchbase must undeploy first,
|
|
47
|
+
* which erases the function's timers and checkpoints.
|
|
48
|
+
*/
|
|
49
|
+
allowRecreate?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export type EventingOutcomeAction = 'created' | 'updated' | 'resumed' | 'unchanged' | 'pruned' | 'paused' | 'removed' | 'requires-recreate';
|
|
52
|
+
export interface EventingOutcome {
|
|
53
|
+
action: EventingOutcomeAction;
|
|
54
|
+
/** Lifecycle operations performed for this function, in order. */
|
|
55
|
+
actions: EventingOutcomeAction[];
|
|
56
|
+
name: string;
|
|
57
|
+
physicalName: string;
|
|
58
|
+
/** Present whenever undeploying can erase Eventing timers/checkpoints. */
|
|
59
|
+
timerStateLost?: boolean;
|
|
60
|
+
message?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Each status array contains the matching outcome, so callers can inspect a
|
|
64
|
+
* report directly without parsing text. An update can also be in `paused` and
|
|
65
|
+
* `resumed`, because that is the safe lifecycle used to perform the update.
|
|
66
|
+
*/
|
|
67
|
+
export interface EventingReport {
|
|
68
|
+
outcomes: EventingOutcome[];
|
|
69
|
+
created: EventingOutcome[];
|
|
70
|
+
updated: EventingOutcome[];
|
|
71
|
+
resumed: EventingOutcome[];
|
|
72
|
+
unchanged: EventingOutcome[];
|
|
73
|
+
pruned: EventingOutcome[];
|
|
74
|
+
paused: EventingOutcome[];
|
|
75
|
+
removed: EventingOutcome[];
|
|
76
|
+
requiresRecreate: EventingOutcome[];
|
|
77
|
+
}
|
|
78
|
+
/** The portion of SDK 4.7's EventingFunctionManager used by CouchSet. */
|
|
79
|
+
export interface EventingFunctionManagerLike {
|
|
80
|
+
upsertFunction(functionDefinition: EventingFunction): Promise<void>;
|
|
81
|
+
dropFunction(name: string): Promise<void>;
|
|
82
|
+
getAllFunctions(): Promise<EventingFunction[]>;
|
|
83
|
+
deployFunction(name: string): Promise<void>;
|
|
84
|
+
undeployFunction(name: string): Promise<void>;
|
|
85
|
+
pauseFunction(name: string): Promise<void>;
|
|
86
|
+
resumeFunction(name: string): Promise<void>;
|
|
87
|
+
functionsStatus(): Promise<{
|
|
88
|
+
functions: EventingFunctionState[];
|
|
89
|
+
}>;
|
|
90
|
+
}
|
|
91
|
+
/** A minimal client shape that lets Eventing acquire the connected SDK cluster. */
|
|
92
|
+
export interface EventingClient {
|
|
93
|
+
ready(): Promise<any>;
|
|
94
|
+
getConnection(): {
|
|
95
|
+
cluster: {
|
|
96
|
+
eventingFunctions?: () => EventingFunctionManager;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Declarative Eventing reconciler backed by the SDK 4.7 EventingFunctionManager.
|
|
102
|
+
* It only manages names starting with its namespace prefix.
|
|
103
|
+
*/
|
|
104
|
+
export declare class Eventing {
|
|
105
|
+
private readonly definitionsByName;
|
|
106
|
+
private readonly eventingMetadataKeyspace;
|
|
107
|
+
private readonly lifecyclePollIntervalMs;
|
|
108
|
+
private readonly lifecycleTimeoutMs;
|
|
109
|
+
private readonly prefix;
|
|
110
|
+
private readonly client?;
|
|
111
|
+
private readonly explicitManager?;
|
|
112
|
+
constructor(client: EventingClient | undefined, options: EventingOptions);
|
|
113
|
+
/** Logical namespace that owns this controller's physical functions. */
|
|
114
|
+
get namespace(): string;
|
|
115
|
+
/** A copy of registered, code-only declarations. This performs no I/O. */
|
|
116
|
+
definitions(): EventingDefinition[];
|
|
117
|
+
/** Converts a logical declaration name into its Couchbase function name. */
|
|
118
|
+
physicalName(name: string): string;
|
|
119
|
+
/** Registers or replaces an in-memory manifest declaration without I/O. */
|
|
120
|
+
register(definition: EventingDefinition): EventingDefinition;
|
|
121
|
+
/** Alias for register(), matching the declaration-oriented API vocabulary. */
|
|
122
|
+
define(definition: EventingDefinition): EventingDefinition;
|
|
123
|
+
apply(options?: EventingApplyOptions): Promise<EventingReport>;
|
|
124
|
+
apply(definition: EventingDefinition, options?: EventingApplyOptions): Promise<EventingReport>;
|
|
125
|
+
/** Temporarily disables one namespace-owned Eventing function. */
|
|
126
|
+
pause(name: string): Promise<EventingReport>;
|
|
127
|
+
/**
|
|
128
|
+
* Intentionally undeploys then deletes one namespace-owned function.
|
|
129
|
+
* Undeployment erases Couchbase Eventing timers and checkpoints.
|
|
130
|
+
*/
|
|
131
|
+
remove(name: string): Promise<EventingReport>;
|
|
132
|
+
private reconcile;
|
|
133
|
+
private prune;
|
|
134
|
+
private matches;
|
|
135
|
+
private sdkDefinition;
|
|
136
|
+
private metadataKeyspace;
|
|
137
|
+
private manager;
|
|
138
|
+
private states;
|
|
139
|
+
/**
|
|
140
|
+
* Management requests acknowledge receipt, not necessarily completion. Do
|
|
141
|
+
* not issue the next incompatible Eventing operation until status confirms
|
|
142
|
+
* that Couchbase has converged.
|
|
143
|
+
*/
|
|
144
|
+
private waitForLifecycle;
|
|
145
|
+
/**
|
|
146
|
+
* Couchbase Server may turn a paused function into an undeployed function
|
|
147
|
+
* when upsert persists new code/settings/bindings. Inspect that post-upsert
|
|
148
|
+
* state rather than assuming resume is legal; deploy is the compatible
|
|
149
|
+
* activation operation in that case.
|
|
150
|
+
*/
|
|
151
|
+
private activateAfterUpsert;
|
|
152
|
+
/**
|
|
153
|
+
* A live function with no status row is unknown, not safely undeployed.
|
|
154
|
+
* Poll for a positive row; a known transitional row remains an immediate
|
|
155
|
+
* blocker so callers do not race an operator's in-progress lifecycle call.
|
|
156
|
+
*/
|
|
157
|
+
private stableLifecycle;
|
|
158
|
+
/** Waits for a stable state caused by this controller's own upsert call. */
|
|
159
|
+
private waitForStableLifecycle;
|
|
160
|
+
private record;
|
|
161
|
+
private outcome;
|
|
162
|
+
private owns;
|
|
163
|
+
private logicalName;
|
|
164
|
+
private assertDefinition;
|
|
165
|
+
private assertLogicalName;
|
|
166
|
+
private assertStable;
|
|
167
|
+
private isDefinition;
|
|
168
|
+
private positiveNumber;
|
|
169
|
+
private nonNegativeNumber;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Construct an Eventing control plane from a CouchSet client. The client is
|
|
173
|
+
* used only to obtain the SDK EventingFunctionManager after `ready()`.
|
|
174
|
+
*/
|
|
175
|
+
export declare const createEventing: (client: EventingClient, options: EventingOptions) => Eventing;
|
|
176
|
+
/** Construct an Eventing control plane around an application-owned SDK manager. */
|
|
177
|
+
export declare const createEventingWithManager: (options: EventingOptions) => Eventing;
|
|
178
|
+
/** A small helper that makes Eventing definitions read as manifest declarations. */
|
|
179
|
+
export declare const defineEventingFunction: (definition: EventingDefinition) => EventingDefinition;
|