@nats-io/obj 3.0.0-4 → 3.0.0-5

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/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "@nats-io/obj",
3
- "version": "3.0.0-4",
3
+ "version": "3.0.0-5",
4
4
  "files": [
5
5
  "lib/",
6
- "build/src/",
7
6
  "LICENSE",
8
7
  "README.md"
9
8
  ],
@@ -33,8 +32,8 @@
33
32
  },
34
33
  "description": "obj library - this library implements all the base functionality for NATS objectstore for javascript clients",
35
34
  "dependencies": {
36
- "@nats-io/jetstream": "~3.0.0-8",
37
- "@nats-io/nats-core": "~3.0.0-23"
35
+ "@nats-io/jetstream": "~3.0.0-9",
36
+ "@nats-io/nats-core": "~3.0.0-24"
38
37
  },
39
38
  "devDependencies": {
40
39
  "@types/node": "^22.0.0",
@@ -42,4 +41,4 @@
42
41
  "typedoc": "^0.26.5",
43
42
  "typescript": "^5.5.4"
44
43
  }
45
- }
44
+ }
@@ -1,13 +0,0 @@
1
- export type {
2
- ObjectInfo,
3
- ObjectResult,
4
- ObjectStore,
5
- ObjectStoreLink,
6
- ObjectStoreMeta,
7
- ObjectStoreMetaOptions,
8
- ObjectStoreOptions,
9
- ObjectStorePutOpts,
10
- ObjectStoreStatus,
11
- } from "./types";
12
-
13
- export { Objm } from "./objectstore";
package/build/src/mod.ts DELETED
@@ -1,13 +0,0 @@
1
- export type {
2
- ObjectInfo,
3
- ObjectResult,
4
- ObjectStore,
5
- ObjectStoreLink,
6
- ObjectStoreMeta,
7
- ObjectStoreMetaOptions,
8
- ObjectStoreOptions,
9
- ObjectStorePutOpts,
10
- ObjectStoreStatus,
11
- } from "./internal_mod";
12
-
13
- export { Objm } from "./objectstore";
@@ -1,922 +0,0 @@
1
- /*
2
- * Copyright 2022-2024 The NATS Authors
3
- * Licensed under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License.
5
- * You may obtain a copy of the License at
6
- *
7
- * http://www.apache.org/licenses/LICENSE-2.0
8
- *
9
- * Unless required by applicable law or agreed to in writing, software
10
- * distributed under the License is distributed on an "AS IS" BASIS,
11
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- * See the License for the specific language governing permissions and
13
- * limitations under the License.
14
- */
15
-
16
- import {
17
- Base64UrlPaddedCodec,
18
- DataBuffer,
19
- deferred,
20
- Feature,
21
- headers,
22
- JSONCodec,
23
- MsgHdrsImpl,
24
- nuid,
25
- QueuedIteratorImpl,
26
- SHA256,
27
- } from "@nats-io/nats-core/internal";
28
-
29
- import type {
30
- MsgHdrs,
31
- NatsConnection,
32
- NatsError,
33
- QueuedIterator,
34
- } from "@nats-io/nats-core/internal";
35
-
36
- import {
37
- consumerOpts,
38
- DiscardPolicy,
39
- JsHeaders,
40
- ListerImpl,
41
- PubHeaders,
42
- StoreCompression,
43
- toJetStreamClient,
44
- } from "@nats-io/jetstream/internal";
45
-
46
- import type {
47
- JetStreamClient,
48
- JetStreamClientImpl,
49
- JetStreamManager,
50
- JsMsg,
51
- Lister,
52
- ListerFieldFilter,
53
- PubAck,
54
- PurgeResponse,
55
- StorageType,
56
- StreamConfig,
57
- StreamInfo,
58
- StreamInfoRequestOptions,
59
- StreamListResponse,
60
- } from "@nats-io/jetstream/internal";
61
-
62
- import type {
63
- ObjectInfo,
64
- ObjectResult,
65
- ObjectStore,
66
- ObjectStoreMeta,
67
- ObjectStoreMetaOptions,
68
- ObjectStoreOptions,
69
- ObjectStorePutOpts,
70
- ObjectStoreStatus,
71
- } from "./types";
72
-
73
- export const osPrefix = "OBJ_";
74
- export const digestType = "SHA-256=";
75
-
76
- export function objectStoreStreamName(bucket: string): string {
77
- validateBucket(bucket);
78
- return `${osPrefix}${bucket}`;
79
- }
80
-
81
- export function objectStoreBucketName(stream: string): string {
82
- if (stream.startsWith(osPrefix)) {
83
- return stream.substring(4);
84
- }
85
- return stream;
86
- }
87
-
88
- /**
89
- * The entry point to creating and managing new ObjectStore instances.
90
- */
91
- export class Objm {
92
- js: JetStreamClientImpl;
93
-
94
- /**
95
- * Creates an instance of the Objm that allows you to create and access ObjectStore.
96
- * Note that if the argument is a NatsConnection, default JetStream Options are
97
- * used. If you want to set some options, please provide a JetStreamClient instead.
98
- * @param nc
99
- */
100
- constructor(nc: JetStreamClient | NatsConnection) {
101
- this.js = toJetStreamClient(nc) as JetStreamClientImpl;
102
- }
103
-
104
- /**
105
- * Creates and opens the specified ObjectStore. If the Object already exists, it opens the existing ObjectStore.
106
- * @param name
107
- * @param opts
108
- */
109
- create(
110
- name: string,
111
- opts: Partial<ObjectStoreOptions> = {},
112
- ): Promise<ObjectStore> {
113
- return this.#maybeCreate(name, opts);
114
- }
115
-
116
- #maybeCreate(
117
- name: string,
118
- opts: Partial<ObjectStoreOptions> = {},
119
- ): Promise<ObjectStore> {
120
- if (typeof crypto?.subtle?.digest !== "function") {
121
- return Promise.reject(
122
- new Error(
123
- "objectstore: unable to calculate hashes - crypto.subtle.digest with sha256 support is required",
124
- ),
125
- );
126
- }
127
- const { ok, min } = this.js.nc.features.get(Feature.JS_OBJECTSTORE);
128
- if (!ok) {
129
- return Promise.reject(
130
- new Error(`objectstore is only supported on servers ${min} or better`),
131
- );
132
- }
133
-
134
- return ObjectStoreImpl.create(this.js, name, opts);
135
- }
136
-
137
- /**
138
- * Returns a list of ObjectStoreInfo for all streams that are identified as
139
- * being a ObjectStore (that is having names that have the prefix `OBJ_`)
140
- */
141
- list(): Lister<ObjectStoreStatus> {
142
- const filter: ListerFieldFilter<ObjectStoreStatus> = (
143
- v: unknown,
144
- ): ObjectStoreStatus[] => {
145
- const slr = v as StreamListResponse;
146
- const streams = slr.streams.filter((v) => {
147
- return v.config.name.startsWith(osPrefix);
148
- });
149
- streams.forEach((si) => {
150
- si.config.sealed = si.config.sealed || false;
151
- si.config.deny_delete = si.config.deny_delete || false;
152
- si.config.deny_purge = si.config.deny_purge || false;
153
- si.config.allow_rollup_hdrs = si.config.allow_rollup_hdrs || false;
154
- });
155
- return streams.map((si) => {
156
- return new ObjectStoreStatusImpl(si);
157
- });
158
- };
159
- const subj = `${this.js.prefix}.STREAM.LIST`;
160
- return new ListerImpl<ObjectStoreStatus>(subj, filter, this.js);
161
- }
162
- }
163
-
164
- export class ObjectStoreStatusImpl implements ObjectStoreStatus {
165
- si: StreamInfo;
166
- backingStore: string;
167
-
168
- constructor(si: StreamInfo) {
169
- this.si = si;
170
- this.backingStore = "JetStream";
171
- }
172
- get bucket(): string {
173
- return objectStoreBucketName(this.si.config.name);
174
- }
175
- get description(): string {
176
- return this.si.config.description ?? "";
177
- }
178
- get ttl(): number {
179
- return this.si.config.max_age;
180
- }
181
- get storage(): StorageType {
182
- return this.si.config.storage;
183
- }
184
- get replicas(): number {
185
- return this.si.config.num_replicas;
186
- }
187
- get sealed(): boolean {
188
- return this.si.config.sealed;
189
- }
190
- get size(): number {
191
- return this.si.state.bytes;
192
- }
193
- get streamInfo(): StreamInfo {
194
- return this.si;
195
- }
196
- get metadata(): Record<string, string> | undefined {
197
- return this.si.config.metadata;
198
- }
199
-
200
- get compression(): boolean {
201
- if (this.si.config.compression) {
202
- return this.si.config.compression !== StoreCompression.None;
203
- }
204
- return false;
205
- }
206
- }
207
- export function validateBucket(name: string) {
208
- const validBucketRe = /^[-\w]+$/;
209
- if (!validBucketRe.test(name)) {
210
- throw new Error(`invalid bucket name: ${name}`);
211
- }
212
- }
213
-
214
- export type ServerObjectStoreMeta = {
215
- name: string;
216
- description?: string;
217
- headers?: Record<string, string[]>;
218
- options?: ObjectStoreMetaOptions;
219
- };
220
-
221
- export type ServerObjectInfo = {
222
- bucket: string;
223
- nuid: string;
224
- size: number;
225
- chunks: number;
226
- digest: string;
227
- deleted?: boolean;
228
- mtime: string;
229
- revision: number;
230
- metadata?: Record<string, string>;
231
- } & ServerObjectStoreMeta;
232
-
233
- class ObjectInfoImpl implements ObjectInfo {
234
- info: ServerObjectInfo;
235
- hdrs!: MsgHdrs;
236
- constructor(oi: ServerObjectInfo) {
237
- this.info = oi;
238
- }
239
- get name(): string {
240
- return this.info.name;
241
- }
242
- get description(): string {
243
- return this.info.description ?? "";
244
- }
245
- get headers(): MsgHdrs {
246
- if (!this.hdrs) {
247
- this.hdrs = MsgHdrsImpl.fromRecord(this.info.headers || {});
248
- }
249
- return this.hdrs;
250
- }
251
- get options(): ObjectStoreMetaOptions | undefined {
252
- return this.info.options;
253
- }
254
- get bucket(): string {
255
- return this.info.bucket;
256
- }
257
- get chunks(): number {
258
- return this.info.chunks;
259
- }
260
- get deleted(): boolean {
261
- return this.info.deleted ?? false;
262
- }
263
- get digest(): string {
264
- return this.info.digest;
265
- }
266
- get mtime(): string {
267
- return this.info.mtime;
268
- }
269
- get nuid(): string {
270
- return this.info.nuid;
271
- }
272
- get size(): number {
273
- return this.info.size;
274
- }
275
- get revision(): number {
276
- return this.info.revision;
277
- }
278
- get metadata(): Record<string, string> {
279
- return this.info.metadata || {};
280
- }
281
- isLink() {
282
- return (this.info.options?.link !== undefined) &&
283
- (this.info.options?.link !== null);
284
- }
285
- }
286
-
287
- function toServerObjectStoreMeta(
288
- meta: Partial<ObjectStoreMeta>,
289
- ): ServerObjectStoreMeta {
290
- const v = {
291
- name: meta.name,
292
- description: meta.description ?? "",
293
- options: meta.options,
294
- metadata: meta.metadata,
295
- } as ServerObjectStoreMeta;
296
-
297
- if (meta.headers) {
298
- const mhi = meta.headers as MsgHdrsImpl;
299
- v.headers = mhi.toRecord();
300
- }
301
- return v;
302
- }
303
-
304
- function emptyReadableStream(): ReadableStream {
305
- return new ReadableStream({
306
- pull(c) {
307
- c.enqueue(new Uint8Array(0));
308
- c.close();
309
- },
310
- });
311
- }
312
-
313
- export class ObjectStoreImpl implements ObjectStore {
314
- jsm: JetStreamManager;
315
- js: JetStreamClient;
316
- stream!: string;
317
- name: string;
318
-
319
- constructor(name: string, jsm: JetStreamManager, js: JetStreamClient) {
320
- this.name = name;
321
- this.jsm = jsm;
322
- this.js = js;
323
- }
324
-
325
- _checkNotEmpty(name: string): { name: string; error?: Error } {
326
- if (!name || name.length === 0) {
327
- return { name, error: new Error("name cannot be empty") };
328
- }
329
- return { name };
330
- }
331
-
332
- async info(name: string): Promise<ObjectInfo | null> {
333
- const info = await this.rawInfo(name);
334
- return info ? new ObjectInfoImpl(info) : null;
335
- }
336
-
337
- async list(): Promise<ObjectInfo[]> {
338
- const buf: ObjectInfo[] = [];
339
- const iter = await this.watch({
340
- ignoreDeletes: true,
341
- includeHistory: true,
342
- });
343
- for await (const info of iter) {
344
- // watch will give a null when it has initialized
345
- // for us that is the hint we are done
346
- if (info === null) {
347
- break;
348
- }
349
- buf.push(info);
350
- }
351
- return Promise.resolve(buf);
352
- }
353
-
354
- async rawInfo(name: string): Promise<ServerObjectInfo | null> {
355
- const { name: obj, error } = this._checkNotEmpty(name);
356
- if (error) {
357
- return Promise.reject(error);
358
- }
359
-
360
- const meta = this._metaSubject(obj);
361
- try {
362
- const m = await this.jsm.streams.getMessage(this.stream, {
363
- last_by_subj: meta,
364
- });
365
- const jc = JSONCodec<ServerObjectInfo>();
366
- const soi = jc.decode(m.data) as ServerObjectInfo;
367
- soi.revision = m.seq;
368
- return soi;
369
- } catch (err) {
370
- if (err.code === "404") {
371
- return null;
372
- }
373
- return Promise.reject(err);
374
- }
375
- }
376
-
377
- async _si(
378
- opts?: Partial<StreamInfoRequestOptions>,
379
- ): Promise<StreamInfo | null> {
380
- try {
381
- return await this.jsm.streams.info(this.stream, opts);
382
- } catch (err) {
383
- const nerr = err as NatsError;
384
- if (nerr.code === "404") {
385
- return null;
386
- }
387
- return Promise.reject(err);
388
- }
389
- }
390
-
391
- async seal(): Promise<ObjectStoreStatus> {
392
- let info = await this._si();
393
- if (info === null) {
394
- return Promise.reject(new Error("object store not found"));
395
- }
396
- info.config.sealed = true;
397
- info = await this.jsm.streams.update(this.stream, info.config);
398
- return Promise.resolve(new ObjectStoreStatusImpl(info));
399
- }
400
-
401
- async status(
402
- opts?: Partial<StreamInfoRequestOptions>,
403
- ): Promise<ObjectStoreStatus> {
404
- const info = await this._si(opts);
405
- if (info === null) {
406
- return Promise.reject(new Error("object store not found"));
407
- }
408
- return Promise.resolve(new ObjectStoreStatusImpl(info));
409
- }
410
-
411
- destroy(): Promise<boolean> {
412
- return this.jsm.streams.delete(this.stream);
413
- }
414
-
415
- async _put(
416
- meta: ObjectStoreMeta,
417
- rs: ReadableStream<Uint8Array> | null,
418
- opts?: ObjectStorePutOpts,
419
- ): Promise<ObjectInfo> {
420
- const jsopts = this.js.getOptions();
421
- opts = opts || { timeout: jsopts.timeout };
422
- opts.timeout = opts.timeout || jsopts.timeout;
423
- opts.previousRevision = opts.previousRevision ?? undefined;
424
- const { timeout, previousRevision } = opts;
425
- const si = (this.js as unknown as { nc: NatsConnection }).nc.info;
426
- const maxPayload = si?.max_payload || 1024;
427
- meta = meta || {} as ObjectStoreMeta;
428
- meta.options = meta.options || {};
429
- let maxChunk = meta.options?.max_chunk_size || 128 * 1024;
430
- maxChunk = maxChunk > maxPayload ? maxPayload : maxChunk;
431
- meta.options.max_chunk_size = maxChunk;
432
-
433
- const old = await this.info(meta.name);
434
- const { name: n, error } = this._checkNotEmpty(meta.name);
435
- if (error) {
436
- return Promise.reject(error);
437
- }
438
-
439
- const id = nuid.next();
440
- const chunkSubj = this._chunkSubject(id);
441
- const metaSubj = this._metaSubject(n);
442
-
443
- const info = Object.assign({
444
- bucket: this.name,
445
- nuid: id,
446
- size: 0,
447
- chunks: 0,
448
- }, toServerObjectStoreMeta(meta)) as ServerObjectInfo;
449
-
450
- const d = deferred<ObjectInfo>();
451
-
452
- const proms: Promise<unknown>[] = [];
453
- const db = new DataBuffer();
454
- try {
455
- const reader = rs ? rs.getReader() : null;
456
- const sha = new SHA256();
457
-
458
- while (true) {
459
- const { done, value } = reader
460
- ? await reader.read()
461
- : { done: true, value: undefined };
462
- if (done) {
463
- // put any partial chunk in
464
- if (db.size() > 0) {
465
- const payload = db.drain();
466
- sha.update(payload);
467
- info.chunks!++;
468
- info.size! += payload.length;
469
- proms.push(this.js.publish(chunkSubj, payload, { timeout }));
470
- }
471
- // wait for all the chunks to write
472
- await Promise.all(proms);
473
- proms.length = 0;
474
-
475
- // prepare the metadata
476
- info.mtime = new Date().toISOString();
477
- const digest = sha.digest("base64");
478
- const pad = digest.length % 3;
479
- const padding = pad > 0 ? "=".repeat(pad) : "";
480
- info.digest = `${digestType}${digest}${padding}`;
481
- info.deleted = false;
482
-
483
- // trailing md for the object
484
- const h = headers();
485
- if (typeof previousRevision === "number") {
486
- h.set(
487
- PubHeaders.ExpectedLastSubjectSequenceHdr,
488
- `${previousRevision}`,
489
- );
490
- }
491
- h.set(JsHeaders.RollupHdr, JsHeaders.RollupValueSubject);
492
-
493
- // try to update the metadata
494
- const pa = await this.js.publish(metaSubj, JSONCodec().encode(info), {
495
- headers: h,
496
- timeout,
497
- });
498
- // update the revision to point to the sequence where we inserted
499
- info.revision = pa.seq;
500
-
501
- // if we are here, the new entry is live
502
- if (old) {
503
- try {
504
- await this.jsm.streams.purge(this.stream, {
505
- filter: `$O.${this.name}.C.${old.nuid}`,
506
- });
507
- } catch (_err) {
508
- // rejecting here, would mean send the wrong signal
509
- // the update succeeded, but cleanup of old chunks failed.
510
- }
511
- }
512
-
513
- // resolve the ObjectInfo
514
- d.resolve(new ObjectInfoImpl(info!));
515
- // stop
516
- break;
517
- }
518
- if (value) {
519
- db.fill(value);
520
- while (db.size() > maxChunk) {
521
- info.chunks!++;
522
- info.size! += maxChunk;
523
- const payload = db.drain(meta.options.max_chunk_size);
524
- sha.update(payload);
525
- proms.push(
526
- this.js.publish(chunkSubj, payload, { timeout }),
527
- );
528
- }
529
- }
530
- }
531
- } catch (err) {
532
- // we failed, remove any partials
533
- await this.jsm.streams.purge(this.stream, { filter: chunkSubj });
534
- d.reject(err);
535
- }
536
-
537
- return d;
538
- }
539
-
540
- putBlob(
541
- meta: ObjectStoreMeta,
542
- data: Uint8Array | null,
543
- opts?: ObjectStorePutOpts,
544
- ): Promise<ObjectInfo> {
545
- function readableStreamFrom(data: Uint8Array): ReadableStream<Uint8Array> {
546
- return new ReadableStream<Uint8Array>({
547
- pull(controller) {
548
- controller.enqueue(data);
549
- controller.close();
550
- },
551
- });
552
- }
553
- if (data === null) {
554
- data = new Uint8Array(0);
555
- }
556
- return this.put(meta, readableStreamFrom(data), opts);
557
- }
558
-
559
- put(
560
- meta: ObjectStoreMeta,
561
- rs: ReadableStream<Uint8Array> | null,
562
- opts?: ObjectStorePutOpts,
563
- ): Promise<ObjectInfo> {
564
- if (meta?.options?.link) {
565
- return Promise.reject(
566
- new Error("link cannot be set when putting the object in bucket"),
567
- );
568
- }
569
- return this._put(meta, rs, opts);
570
- }
571
-
572
- async getBlob(name: string): Promise<Uint8Array | null> {
573
- async function fromReadableStream(
574
- rs: ReadableStream<Uint8Array>,
575
- ): Promise<Uint8Array> {
576
- const buf = new DataBuffer();
577
- const reader = rs.getReader();
578
- while (true) {
579
- const { done, value } = await reader.read();
580
- if (done) {
581
- return buf.drain();
582
- }
583
- if (value && value.length) {
584
- buf.fill(value);
585
- }
586
- }
587
- }
588
-
589
- const r = await this.get(name);
590
- if (r === null) {
591
- return Promise.resolve(null);
592
- }
593
-
594
- const vs = await Promise.all([r.error, fromReadableStream(r.data)]);
595
- if (vs[0]) {
596
- return Promise.reject(vs[0]);
597
- } else {
598
- return Promise.resolve(vs[1]);
599
- }
600
- }
601
-
602
- async get(name: string): Promise<ObjectResult | null> {
603
- const info = await this.rawInfo(name);
604
- if (info === null) {
605
- return Promise.resolve(null);
606
- }
607
-
608
- if (info.deleted) {
609
- return Promise.resolve(null);
610
- }
611
-
612
- if (info.options && info.options.link) {
613
- const ln = info.options.link.name || "";
614
- if (ln === "") {
615
- throw new Error("link is a bucket");
616
- }
617
- const os = info.options.link.bucket !== this.name
618
- ? await ObjectStoreImpl.create(
619
- this.js,
620
- info.options.link.bucket,
621
- )
622
- : this;
623
- return os.get(ln);
624
- }
625
-
626
- const d = deferred<Error | null>();
627
-
628
- const r: Partial<ObjectResult> = {
629
- info: new ObjectInfoImpl(info),
630
- error: d,
631
- };
632
- if (info.size === 0) {
633
- r.data = emptyReadableStream();
634
- d.resolve(null);
635
- return Promise.resolve(r as ObjectResult);
636
- }
637
-
638
- let controller: ReadableStreamDefaultController;
639
-
640
- const oc = consumerOpts();
641
- oc.orderedConsumer();
642
- const sha = new SHA256();
643
- const subj = `$O.${this.name}.C.${info.nuid}`;
644
- const sub = await this.js.subscribe(subj, oc);
645
- (async () => {
646
- for await (const jm of sub) {
647
- if (jm.data.length > 0) {
648
- sha.update(jm.data);
649
- controller!.enqueue(jm.data);
650
- }
651
- if (jm.info.pending === 0) {
652
- const hash = sha.digest("base64");
653
- // go pads the hash - which should be multiple of 3 - otherwise pads with '='
654
- const pad = hash.length % 3;
655
- const padding = pad > 0 ? "=".repeat(pad) : "";
656
- const digest = `${digestType}${hash}${padding}`;
657
- if (digest !== info.digest) {
658
- controller!.error(
659
- new Error(
660
- `received a corrupt object, digests do not match received: ${info.digest} calculated ${digest}`,
661
- ),
662
- );
663
- } else {
664
- controller!.close();
665
- }
666
- sub.unsubscribe();
667
- }
668
- }
669
- })()
670
- .then(() => {
671
- d.resolve();
672
- })
673
- .catch((err) => {
674
- controller!.error(err);
675
- d.reject(err);
676
- });
677
-
678
- r.data = new ReadableStream({
679
- start(c) {
680
- controller = c;
681
- },
682
- cancel() {
683
- sub.unsubscribe();
684
- },
685
- });
686
-
687
- return r as ObjectResult;
688
- }
689
-
690
- linkStore(name: string, bucket: ObjectStore): Promise<ObjectInfo> {
691
- if (!(bucket instanceof ObjectStoreImpl)) {
692
- return Promise.reject("bucket required");
693
- }
694
- const osi = bucket as ObjectStoreImpl;
695
- const { name: n, error } = this._checkNotEmpty(name);
696
- if (error) {
697
- return Promise.reject(error);
698
- }
699
-
700
- const meta = {
701
- name: n,
702
- options: { link: { bucket: osi.name } },
703
- };
704
- return this._put(meta, null);
705
- }
706
-
707
- async link(name: string, info: ObjectInfo): Promise<ObjectInfo> {
708
- const { name: n, error } = this._checkNotEmpty(name);
709
- if (error) {
710
- return Promise.reject(error);
711
- }
712
- if (info.deleted) {
713
- return Promise.reject(new Error("src object is deleted"));
714
- }
715
- if ((info as ObjectInfoImpl).isLink()) {
716
- return Promise.reject(new Error("src object is a link"));
717
- }
718
- const dest = await this.rawInfo(name);
719
- if (dest !== null && !dest.deleted) {
720
- return Promise.reject(
721
- new Error("an object already exists with that name"),
722
- );
723
- }
724
-
725
- const link = { bucket: info.bucket, name: info.name };
726
- const mm = {
727
- name: n,
728
- bucket: info.bucket,
729
- options: { link: link },
730
- } as ObjectStoreMeta;
731
- await this.js.publish(this._metaSubject(name), JSON.stringify(mm));
732
- const i = await this.info(name);
733
- return Promise.resolve(i!);
734
- }
735
-
736
- async delete(name: string): Promise<PurgeResponse> {
737
- const info = await this.rawInfo(name);
738
- if (info === null) {
739
- return Promise.resolve({ purged: 0, success: false });
740
- }
741
- info.deleted = true;
742
- info.size = 0;
743
- info.chunks = 0;
744
- info.digest = "";
745
-
746
- const jc = JSONCodec();
747
- const h = headers();
748
- h.set(JsHeaders.RollupHdr, JsHeaders.RollupValueSubject);
749
-
750
- await this.js.publish(this._metaSubject(info.name), jc.encode(info), {
751
- headers: h,
752
- });
753
- return this.jsm.streams.purge(this.stream, {
754
- filter: this._chunkSubject(info.nuid),
755
- });
756
- }
757
-
758
- async update(
759
- name: string,
760
- meta: Partial<ObjectStoreMeta> = {},
761
- ): Promise<PubAck> {
762
- const info = await this.rawInfo(name);
763
- if (info === null) {
764
- return Promise.reject(new Error("object not found"));
765
- }
766
- if (info.deleted) {
767
- return Promise.reject(
768
- new Error("cannot update meta for a deleted object"),
769
- );
770
- }
771
- meta.name = meta.name ?? info.name;
772
- const { name: n, error } = this._checkNotEmpty(meta.name);
773
- if (error) {
774
- return Promise.reject(error);
775
- }
776
- if (name !== meta.name) {
777
- const i = await this.info(meta.name);
778
- if (i && !i.deleted) {
779
- return Promise.reject(
780
- new Error("an object already exists with that name"),
781
- );
782
- }
783
- }
784
- meta.name = n;
785
- const ii = Object.assign({}, info, toServerObjectStoreMeta(meta!));
786
- // if the name changed, delete the old meta
787
- const ack = await this.js.publish(
788
- this._metaSubject(ii.name),
789
- JSON.stringify(ii),
790
- );
791
- if (name !== meta.name) {
792
- await this.jsm.streams.purge(this.stream, {
793
- filter: this._metaSubject(name),
794
- });
795
- }
796
- return Promise.resolve(ack);
797
- }
798
-
799
- async watch(opts: Partial<
800
- {
801
- ignoreDeletes?: boolean;
802
- includeHistory?: boolean;
803
- }
804
- > = {}): Promise<QueuedIterator<ObjectInfo | null>> {
805
- opts.includeHistory = opts.includeHistory ?? false;
806
- opts.ignoreDeletes = opts.ignoreDeletes ?? false;
807
- let initialized = false;
808
- const qi = new QueuedIteratorImpl<ObjectInfo | null>();
809
- const subj = this._metaSubjectAll();
810
- try {
811
- await this.jsm.streams.getMessage(this.stream, { last_by_subj: subj });
812
- } catch (err) {
813
- if (err.code === "404") {
814
- qi.push(null);
815
- initialized = true;
816
- } else {
817
- qi.stop(err);
818
- }
819
- }
820
- const jc = JSONCodec<ObjectInfo>();
821
- const copts = consumerOpts();
822
- copts.orderedConsumer();
823
- if (opts.includeHistory) {
824
- copts.deliverLastPerSubject();
825
- } else {
826
- // FIXME: Go's implementation doesn't seem correct - if history is not desired
827
- // the watch should only be giving notifications on new entries
828
- initialized = true;
829
- copts.deliverNew();
830
- }
831
- copts.callback((err: NatsError | null, jm: JsMsg | null) => {
832
- if (err) {
833
- qi.stop(err);
834
- return;
835
- }
836
- if (jm !== null) {
837
- const oi = jc.decode(jm.data);
838
- if (oi.deleted && opts.ignoreDeletes === true) {
839
- // do nothing
840
- } else {
841
- qi.push(oi);
842
- }
843
- if (jm.info?.pending === 0 && !initialized) {
844
- initialized = true;
845
- qi.push(null);
846
- }
847
- }
848
- });
849
-
850
- const sub = await this.js.subscribe(subj, copts);
851
- qi._data = sub;
852
- qi.iterClosed.then(() => {
853
- sub.unsubscribe();
854
- });
855
- sub.closed.then(() => {
856
- qi.stop();
857
- }).catch((err) => {
858
- qi.stop(err);
859
- });
860
-
861
- return qi;
862
- }
863
-
864
- _chunkSubject(id: string) {
865
- return `$O.${this.name}.C.${id}`;
866
- }
867
-
868
- _metaSubject(n: string): string {
869
- return `$O.${this.name}.M.${Base64UrlPaddedCodec.encode(n)}`;
870
- }
871
-
872
- _metaSubjectAll(): string {
873
- return `$O.${this.name}.M.>`;
874
- }
875
-
876
- async init(opts: Partial<ObjectStoreOptions> = {}): Promise<void> {
877
- try {
878
- this.stream = objectStoreStreamName(this.name);
879
- } catch (err) {
880
- return Promise.reject(err);
881
- }
882
- const max_age = opts?.ttl || 0;
883
- delete opts.ttl;
884
- // pacify the tsc compiler downstream
885
- const sc = Object.assign({ max_age }, opts) as unknown as StreamConfig;
886
- sc.name = this.stream;
887
- sc.allow_direct = true;
888
- sc.allow_rollup_hdrs = true;
889
- sc.discard = DiscardPolicy.New;
890
- sc.subjects = [`$O.${this.name}.C.>`, `$O.${this.name}.M.>`];
891
- if (opts.placement) {
892
- sc.placement = opts.placement;
893
- }
894
- if (opts.metadata) {
895
- sc.metadata = opts.metadata;
896
- }
897
- if (typeof opts.compression === "boolean") {
898
- sc.compression = opts.compression
899
- ? StoreCompression.S2
900
- : StoreCompression.None;
901
- }
902
-
903
- try {
904
- await this.jsm.streams.info(sc.name);
905
- } catch (err) {
906
- if (err.message === "stream not found") {
907
- await this.jsm.streams.add(sc);
908
- }
909
- }
910
- }
911
-
912
- static async create(
913
- js: JetStreamClient,
914
- name: string,
915
- opts: Partial<ObjectStoreOptions> = {},
916
- ): Promise<ObjectStore> {
917
- const jsm = await js.jetstreamManager();
918
- const os = new ObjectStoreImpl(name, jsm, js);
919
- await os.init(opts);
920
- return Promise.resolve(os);
921
- }
922
- }
@@ -1,340 +0,0 @@
1
- /*
2
- * Copyright 2023-2024 The NATS Authors
3
- * Licensed under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License.
5
- * You may obtain a copy of the License at
6
- *
7
- * http://www.apache.org/licenses/LICENSE-2.0
8
- *
9
- * Unless required by applicable law or agreed to in writing, software
10
- * distributed under the License is distributed on an "AS IS" BASIS,
11
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- * See the License for the specific language governing permissions and
13
- * limitations under the License.
14
- */
15
- import type {
16
- Placement,
17
- PubAck,
18
- PurgeResponse,
19
- StorageType,
20
- StreamInfo,
21
- StreamInfoRequestOptions,
22
- } from "@nats-io/jetstream";
23
- import type { MsgHdrs, Nanos, QueuedIterator } from "@nats-io/nats-core";
24
-
25
- export type ObjectStoreLink = {
26
- /**
27
- * name of object store storing the data
28
- */
29
- bucket: string;
30
- /**
31
- * link to single object, when empty this means the whole store
32
- */
33
- name?: string;
34
- };
35
- export type ObjectStoreMetaOptions = {
36
- /**
37
- * If set, the object is a reference to another entry.
38
- */
39
- link?: ObjectStoreLink;
40
- /**
41
- * The maximum size in bytes for each chunk.
42
- * Note that if the size exceeds the maximum size of a stream
43
- * entry, the number will be clamped to the streams maximum.
44
- */
45
- max_chunk_size?: number;
46
- };
47
- export type ObjectStoreMeta = {
48
- name: string;
49
- description?: string;
50
- headers?: MsgHdrs;
51
- options?: ObjectStoreMetaOptions;
52
- metadata?: Record<string, string>;
53
- };
54
-
55
- export interface ObjectInfo extends ObjectStoreMeta {
56
- /**
57
- * The name of the bucket where the object is stored.
58
- */
59
- bucket: string;
60
- /**
61
- * The current ID of the entries holding the data for the object.
62
- */
63
- nuid: string;
64
- /**
65
- * The size in bytes of the object.
66
- */
67
- size: number;
68
- /**
69
- * The number of entries storing the object.
70
- */
71
- chunks: number;
72
- /**
73
- * A cryptographic checksum of the data as a whole.
74
- */
75
- digest: string;
76
- /**
77
- * True if the object was deleted.
78
- */
79
- deleted: boolean;
80
- /**
81
- * An UTC timestamp
82
- */
83
- mtime: string;
84
- /**
85
- * The revision number for the entry
86
- */
87
- revision: number;
88
- }
89
-
90
- /**
91
- * A link reference
92
- */
93
- export interface ObjectLink {
94
- /**
95
- * The object store the source data
96
- */
97
- bucket: string;
98
- /**
99
- * The name of the entry holding the data. If not
100
- * set it is a complete object store reference.
101
- */
102
- name?: string;
103
- }
104
-
105
- export type ObjectStoreStatus = {
106
- /**
107
- * The bucket name
108
- */
109
- bucket: string;
110
- /**
111
- * the description associated with the object store.
112
- */
113
- description: string;
114
- /**
115
- * The time to live for entries in the object store in nanoseconds.
116
- * Convert to millis using the `millis()` function.
117
- */
118
- ttl: Nanos;
119
- /**
120
- * The object store's underlying stream storage type.
121
- */
122
- storage: StorageType;
123
- /**
124
- * The number of replicas associated with this object store.
125
- */
126
- replicas: number;
127
- /**
128
- * Set to true if the object store is sealed and will reject edits.
129
- */
130
- sealed: boolean;
131
- /**
132
- * The size in bytes that the object store occupies.
133
- */
134
- size: number;
135
- /**
136
- * The underlying storage for the object store. Currently, this always
137
- * returns "JetStream".
138
- */
139
- backingStore: string;
140
- /**
141
- * The StreamInfo backing up the ObjectStore
142
- */
143
- streamInfo: StreamInfo;
144
- /**
145
- * Metadata the object store. Note that
146
- * keys starting with `_nats` are reserved. This feature only supported on servers
147
- * 2.10.x and better.
148
- */
149
- metadata?: Record<string, string> | undefined;
150
- /**
151
- * Compression level of the stream. This feature is only supported in
152
- * servers 2.10.x and better.
153
- */
154
- compression: boolean;
155
- };
156
- /**
157
- * @deprecated {@link ObjectStoreStatus}
158
- */
159
- export type ObjectStoreInfo = ObjectStoreStatus;
160
- export type ObjectStoreOptions = {
161
- /**
162
- * A description for the object store
163
- */
164
- description?: string;
165
- /**
166
- * The time to live for entries in the object store specified
167
- * as nanoseconds. Use the `nanos()` function to convert millis to
168
- * nanos.
169
- */
170
- ttl?: Nanos;
171
- /**
172
- * The underlying stream storage type for the object store.
173
- */
174
- storage: StorageType;
175
- /**
176
- * The number of replicas to create.
177
- */
178
- replicas: number;
179
- /**
180
- * The maximum amount of data that the object store should store in bytes.
181
- */
182
- "max_bytes": number;
183
- /**
184
- * Placement hints for the underlying object store stream
185
- */
186
- placement: Placement; /**
187
- * Metadata field to store additional information about the stream. Note that
188
- * keys starting with `_nats` are reserved. This feature only supported on servers
189
- * 2.10.x and better.
190
- */
191
- metadata?: Record<string, string>;
192
- /**
193
- * Sets the compression level of the stream. This feature is only supported in
194
- * servers 2.10.x and better.
195
- */
196
- compression?: boolean;
197
- };
198
- /**
199
- * An object that allows reading the object stored under a specified name.
200
- */
201
- export type ObjectResult = {
202
- /**
203
- * The info of the object that was retrieved.
204
- */
205
- info: ObjectInfo;
206
- /**
207
- * The readable stream where you can read the data.
208
- */
209
- data: ReadableStream<Uint8Array>;
210
- /**
211
- * A promise that will resolve to an error if the readable stream failed
212
- * to process the entire response. Should be checked when the readable stream
213
- * has finished yielding data.
214
- */
215
- error: Promise<Error | null>;
216
- };
217
- export type ObjectStorePutOpts = {
218
- /**
219
- * maximum number of millis for the put requests to succeed
220
- */
221
- timeout?: number;
222
- /**
223
- * If set the ObjectStore must be at the current sequence or the
224
- * put will fail. Note the sequence accounts where the metadata
225
- * for the entry is stored.
226
- */
227
- previousRevision?: number;
228
- };
229
-
230
- export interface ObjectStore {
231
- /**
232
- * Returns the ObjectInfo of the named entry. Or null if the
233
- * entry doesn't exist.
234
- * @param name
235
- */
236
- info(name: string): Promise<ObjectInfo | null>;
237
-
238
- /**
239
- * Returns a list of the entries in the ObjectStore
240
- */
241
- list(): Promise<ObjectInfo[]>;
242
-
243
- /**
244
- * Returns an object you can use for reading the data from the
245
- * named stored object or null if the entry doesn't exist.
246
- * @param name
247
- */
248
- get(name: string): Promise<ObjectResult | null>;
249
-
250
- /**
251
- * Returns the data stored for the named entry.
252
- * @param name
253
- */
254
- getBlob(name: string): Promise<Uint8Array | null>;
255
-
256
- /**
257
- * Adds an object to the store with the specified meta
258
- * and using the specified ReadableStream to stream the data.
259
- * @param meta
260
- * @param rs
261
- * @param opts
262
- */
263
- put(
264
- meta: ObjectStoreMeta,
265
- rs: ReadableStream<Uint8Array>,
266
- opts?: ObjectStorePutOpts,
267
- ): Promise<ObjectInfo>;
268
-
269
- /**
270
- * Puts the specified bytes into the store with the specified meta.
271
- * @param meta
272
- * @param data
273
- * @param opts
274
- */
275
- putBlob(
276
- meta: ObjectStoreMeta,
277
- data: Uint8Array | null,
278
- opts?: ObjectStorePutOpts,
279
- ): Promise<ObjectInfo>;
280
-
281
- /**
282
- * Deletes the specified entry from the object store.
283
- * @param name
284
- */
285
- delete(name: string): Promise<PurgeResponse>;
286
-
287
- /**
288
- * Adds a link to another object in the same store or a different one.
289
- * Note that links of links are rejected.
290
- * object.
291
- * @param name
292
- * @param meta
293
- */
294
- link(name: string, meta: ObjectInfo): Promise<ObjectInfo>;
295
-
296
- /**
297
- * Add a link to another object store
298
- * @param name
299
- * @param bucket
300
- */
301
- linkStore(name: string, bucket: ObjectStore): Promise<ObjectInfo>;
302
-
303
- /**
304
- * Watch an object store and receive updates of modifications via
305
- * an iterator.
306
- * @param opts
307
- */
308
- watch(
309
- opts?: Partial<
310
- {
311
- ignoreDeletes?: boolean;
312
- includeHistory?: boolean;
313
- }
314
- >,
315
- ): Promise<QueuedIterator<ObjectInfo | null>>;
316
-
317
- /**
318
- * Seals the object store preventing any further modifications.
319
- */
320
- seal(): Promise<ObjectStoreStatus>;
321
-
322
- /**
323
- * Returns the runtime status of the object store.
324
- * @param opts
325
- */
326
- status(opts?: Partial<StreamInfoRequestOptions>): Promise<ObjectStoreStatus>;
327
-
328
- /**
329
- * Update the metadata for an object. If the name is modified, the object
330
- * is effectively renamed and will only be accessible by its new name.
331
- * @param name
332
- * @param meta
333
- */
334
- update(name: string, meta: Partial<ObjectStoreMeta>): Promise<PubAck>;
335
-
336
- /**
337
- * Destroys the object store and all its entries.
338
- */
339
- destroy(): Promise<boolean>;
340
- }