couchset 0.4.0 → 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 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, and safe index plans—are documented in [Next primitives](./docs/next-primitives.md).
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;