@travetto/model 5.0.0-rc.1 → 5.0.0-rc.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +85 -147
  2. package/__index__.ts +2 -4
  3. package/package.json +7 -7
  4. package/src/error/exists.ts +1 -1
  5. package/src/error/invalid-index.ts +1 -1
  6. package/src/error/invalid-sub-type.ts +1 -1
  7. package/src/error/not-found.ts +1 -1
  8. package/src/internal/service/blob.ts +5 -0
  9. package/src/internal/service/bulk.ts +1 -1
  10. package/src/internal/service/common.ts +20 -26
  11. package/src/internal/service/crud.ts +15 -21
  12. package/src/internal/service/expiry.ts +2 -5
  13. package/src/internal/service/indexed.ts +13 -26
  14. package/src/internal/service/storage.ts +3 -7
  15. package/src/registry/decorator.ts +7 -13
  16. package/src/registry/model.ts +13 -8
  17. package/src/registry/types.ts +1 -2
  18. package/src/service/basic.ts +1 -1
  19. package/src/service/blob.ts +41 -0
  20. package/src/service/bulk.ts +1 -1
  21. package/src/service/crud.ts +1 -1
  22. package/src/service/expiry.ts +1 -1
  23. package/src/service/indexed.ts +2 -2
  24. package/src/service/storage.ts +1 -1
  25. package/src/util/blob.ts +60 -0
  26. package/support/base-command.ts +1 -1
  27. package/support/bin/candidate.ts +2 -3
  28. package/support/bin/export.ts +1 -1
  29. package/support/bin/install.ts +1 -1
  30. package/support/cli.model_export.ts +1 -1
  31. package/support/cli.model_install.ts +1 -1
  32. package/support/doc.support.tsx +5 -11
  33. package/support/fixtures/alpha.txt +1 -0
  34. package/support/fixtures/empty +0 -0
  35. package/support/fixtures/empty.m4a +0 -0
  36. package/support/fixtures/logo.gif +0 -0
  37. package/support/fixtures/logo.png +0 -0
  38. package/support/fixtures/small-audio +0 -0
  39. package/support/fixtures/small-audio.mp3 +0 -0
  40. package/support/test/base.ts +3 -3
  41. package/support/test/blob.ts +117 -0
  42. package/support/test/crud.ts +5 -4
  43. package/support/test/expiry.ts +1 -1
  44. package/support/test/indexed.ts +1 -1
  45. package/support/test/polymorphism.ts +7 -6
  46. package/support/test/suite.ts +6 -6
  47. package/src/internal/service/stream.ts +0 -22
  48. package/src/provider/file.ts +0 -233
  49. package/src/provider/memory.ts +0 -341
  50. package/src/service/stream.ts +0 -72
  51. package/support/test/stream.ts +0 -110
@@ -1,5 +1,4 @@
1
- import { Class } from '@travetto/base';
2
- import { DeepPartial } from '@travetto/schema';
1
+ import { castTo, Class, DeepPartial, TypedObject } from '@travetto/runtime';
3
2
 
4
3
  import { IndexNotSupported } from '../../error/invalid-index';
5
4
  import { NotFoundError } from '../../error/not-found';
@@ -45,38 +44,30 @@ export class ModelIndexedUtil {
45
44
  const parts = [];
46
45
 
47
46
  while (o !== undefined && o !== null) {
48
- const k = Object.keys(f)[0];
49
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
50
- o = (o[k] as Record<string, unknown>);
47
+ const k = TypedObject.keys(f)[0];
48
+ o = castTo(o[k]);
51
49
  parts.push(k);
52
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
53
- const fk = k as (keyof typeof f);
54
- if (typeof f[fk] === 'boolean' || typeof f[fk] === 'number') {
50
+ if (typeof f[k] === 'boolean' || typeof f[k] === 'number') {
55
51
  if (cfg.type === 'sorted') {
56
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
57
- sortDir = f[fk] === true ? 1 : f[fk] as number;
52
+ sortDir = f[k] === true ? 1 : f[k] === false ? 0 : f[k];
58
53
  }
59
54
  break; // At the bottom
60
55
  } else {
61
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
62
- f = f[fk] as Record<string, unknown>;
56
+ f = castTo(f[k]);
63
57
  }
64
58
  }
65
59
  if (field === sortField) {
66
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
67
- sorted = { path: parts, dir: sortDir, value: o as unknown as number | Date };
60
+ sorted = { path: parts, dir: sortDir, value: castTo(o) };
68
61
  }
69
62
  if (o === undefined || o === null) {
70
63
  const empty = field === sortField ? opts.emptySortValue : opts.emptyValue;
71
64
  if (empty === undefined || empty === Error) {
72
65
  throw new IndexNotSupported(cls, cfg, `Missing field value for ${parts.join('.')}`);
73
66
  }
74
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
75
- o = empty as Record<string, unknown>;
67
+ o = castTo(empty!);
76
68
  } else {
77
69
  if (field !== sortField || (opts.includeSortInFields ?? true)) {
78
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
79
- fields.push({ path: parts, value: o as unknown as string | boolean | Date | number });
70
+ fields.push({ path: parts, value: castTo(o) });
80
71
  }
81
72
  }
82
73
  }
@@ -84,7 +75,6 @@ export class ModelIndexedUtil {
84
75
  return { fields, sorted };
85
76
  }
86
77
 
87
-
88
78
  /**
89
79
  * Project item via index
90
80
  * @param cls Type to get index for
@@ -93,12 +83,11 @@ export class ModelIndexedUtil {
93
83
  static projectIndex<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, item?: DeepPartial<T>, cfg?: ComputeConfig): Record<string, unknown> {
94
84
  const res: Record<string, unknown> = {};
95
85
  for (const { path, value } of this.computeIndexParts(cls, idx, item ?? {}, cfg).fields) {
96
- let sub = res;
86
+ let sub: Record<string, unknown> = res;
97
87
  const all = path.slice(0);
98
88
  const last = all.pop()!;
99
89
  for (const k of all) {
100
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
101
- sub = (sub[k] ??= {}) as typeof res;
90
+ sub = castTo(sub[k] ??= {});
102
91
  }
103
92
  sub[last] = value;
104
93
  }
@@ -135,11 +124,9 @@ export class ModelIndexedUtil {
135
124
  cls: Class<T>, idx: string, body: OptionalId<T>
136
125
  ): Promise<T> {
137
126
  try {
138
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
139
- const { id } = await service.getByIndex(cls, idx, body as DeepPartial<T>);
127
+ const { id } = await service.getByIndex(cls, idx, castTo(body));
140
128
  body.id = id;
141
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
142
- return await service.update(cls, body as T);
129
+ return await service.update(cls, castTo(body));
143
130
  } catch (err) {
144
131
  if (err instanceof NotFoundError) {
145
132
  return await service.create(cls, body);
@@ -1,4 +1,4 @@
1
- import { Class, Env } from '@travetto/base';
1
+ import { Class, Runtime } from '@travetto/runtime';
2
2
  import { SchemaChangeListener } from '@travetto/schema';
3
3
 
4
4
  import { ModelRegistry } from '../../registry/model';
@@ -12,15 +12,11 @@ export class ModelStorageUtil {
12
12
  /**
13
13
  * Register change listener on startup
14
14
  */
15
- static async registerModelChangeListener(storage: ModelStorageSupport, target?: Class): Promise<void> {
16
- if (!Env.dynamic || !(storage?.config?.autoCreate ?? !Env.production)) {
15
+ static async registerModelChangeListener(storage: ModelStorageSupport): Promise<void> {
16
+ if (!Runtime.dynamic || !(storage?.config?.autoCreate ?? !Runtime.production)) {
17
17
  return;
18
18
  }
19
19
 
20
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
21
- target = target ?? storage.constructor as Class<ModelStorageSupport>;
22
-
23
-
24
20
  const checkType = (cls: Class, enforceBase = true): boolean => {
25
21
  if (enforceBase && ModelRegistry.getBaseModel(cls) !== cls) {
26
22
  return false;
@@ -1,4 +1,4 @@
1
- import { Class } from '@travetto/base';
1
+ import { asConstructable, castTo, Class } from '@travetto/runtime';
2
2
  import { SchemaRegistry } from '@travetto/schema';
3
3
 
4
4
  import { ModelType } from '../types/model';
@@ -24,8 +24,7 @@ export function Model(conf: Partial<ModelOptions<ModelType>> | string = {}) {
24
24
  /**
25
25
  * Defines an index on a model
26
26
  */
27
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
- export function Index<T>(...indices: IndexConfig<any>[]) {
27
+ export function Index<T extends ModelType>(...indices: IndexConfig<T>[]) {
29
28
  return function (target: Class<T>): void {
30
29
  ModelRegistry.getOrCreatePending(target).indices!.push(...indices);
31
30
  };
@@ -37,8 +36,7 @@ export function Index<T>(...indices: IndexConfig<any>[]) {
37
36
  */
38
37
  export function ExpiresAt() {
39
38
  return <K extends string, T extends Partial<Record<K, Date>>>(tgt: T, prop: K): void => {
40
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
41
- ModelRegistry.register(tgt.constructor as Class<T>, { expiresAt: prop });
39
+ ModelRegistry.register(asConstructable(tgt).constructor, { expiresAt: prop });
42
40
  };
43
41
  }
44
42
 
@@ -50,8 +48,7 @@ export function PrePersist<T>(handler: DataHandler<T>, scope: PrePersistScope =
50
48
  ModelRegistry.registerDataHandlers(tgt, {
51
49
  prePersist: [{
52
50
  scope,
53
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
54
- handler: handler as DataHandler
51
+ handler: castTo(handler)
55
52
  }]
56
53
  });
57
54
  };
@@ -62,13 +59,11 @@ export function PrePersist<T>(handler: DataHandler<T>, scope: PrePersistScope =
62
59
  */
63
60
  export function PersistValue<T>(handler: (curr: T | undefined) => T, scope: PrePersistScope = 'all') {
64
61
  return function <K extends string, C extends Partial<Record<K, T>>>(tgt: C, prop: K): void {
65
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
66
- ModelRegistry.registerDataHandlers(tgt.constructor as Class<C>, {
62
+ ModelRegistry.registerDataHandlers(asConstructable(tgt).constructor, {
67
63
  prePersist: [{
68
64
  scope,
69
65
  handler: (inst): void => {
70
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
71
- const cInst = (inst as unknown as Record<K, T>);
66
+ const cInst: Record<K, T> = castTo(inst);
72
67
  cInst[prop] = handler(cInst[prop]);
73
68
  }
74
69
  }]
@@ -82,7 +77,6 @@ export function PersistValue<T>(handler: (curr: T | undefined) => T, scope: PreP
82
77
  */
83
78
  export function PostLoad<T>(handler: DataHandler<T>) {
84
79
  return function (tgt: Class<T>): void {
85
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
86
- ModelRegistry.registerDataHandlers(tgt, { postLoad: [handler as DataHandler] });
80
+ ModelRegistry.registerDataHandlers(tgt, { postLoad: [castTo(handler)] });
87
81
  };
88
82
  }
@@ -1,8 +1,7 @@
1
- import { RuntimeIndex } from '@travetto/manifest';
2
1
  import { SchemaRegistry } from '@travetto/schema';
3
2
  import { MetadataRegistry } from '@travetto/registry';
4
3
  import { DependencyRegistry } from '@travetto/di';
5
- import { AppError, Class } from '@travetto/base';
4
+ import { AppError, castTo, Class, describeFunction, asFull } from '@travetto/runtime';
6
5
  import { AllViewⲐ } from '@travetto/schema/src/internal/types';
7
6
 
8
7
  import { IndexConfig, IndexType, ModelOptions } from './types';
@@ -52,7 +51,14 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
52
51
  }
53
52
 
54
53
  createPending(cls: Class): Partial<ModelOptions<ModelType>> {
55
- return { class: cls, indices: [], autoCreate: true, baseType: RuntimeIndex.getFunctionMetadata(cls)?.abstract, postLoad: [], prePersist: [] };
54
+ return {
55
+ class: cls,
56
+ indices: [],
57
+ autoCreate: true,
58
+ baseType: describeFunction(cls).abstract,
59
+ postLoad: [],
60
+ prePersist: []
61
+ };
56
62
  }
57
63
 
58
64
  registerDataHandlers(cls: Class, pConfig?: Partial<ModelOptions<ModelType>>): void {
@@ -65,8 +71,7 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
65
71
  }
66
72
 
67
73
  onInstallFinalize(cls: Class): ModelOptions<ModelType> {
68
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
69
- const config = this.pending.get(cls.Ⲑid)! as ModelOptions<ModelType>;
74
+ const config = asFull(this.pending.get(cls.Ⲑid)!);
70
75
 
71
76
  const schema = SchemaRegistry.get(cls);
72
77
  const view = schema.views[AllViewⲐ].schema;
@@ -97,7 +102,7 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
97
102
  /**
98
103
  * Find base class for a given model
99
104
  */
100
- getBaseModel(cls: Class): Class<ModelType> {
105
+ getBaseModel<T extends ModelType>(cls: Class<T>): Class<T> {
101
106
  if (!this.baseModels.has(cls)) {
102
107
  let conf = this.get(cls) ?? this.getOrCreatePending(cls);
103
108
  let parent = cls;
@@ -202,12 +207,12 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
202
207
  * Get expiry field
203
208
  * @param cls
204
209
  */
205
- getExpiry(cls: Class): string {
210
+ getExpiry<T extends ModelType>(cls: Class<T>): keyof T {
206
211
  const expiry = this.get(cls).expiresAt;
207
212
  if (!expiry) {
208
213
  throw new AppError(`${cls.name} is not configured with expiry support, please use @ExpiresAt to declare expiration behavior`, 'general');
209
214
  }
210
- return expiry;
215
+ return castTo(expiry);
211
216
  }
212
217
  }
213
218
 
@@ -1,5 +1,4 @@
1
- import { Class } from '@travetto/base';
2
- import { Primitive } from '@travetto/schema';
1
+ import type { Class, Primitive } from '@travetto/runtime';
3
2
 
4
3
  import { ModelType } from '../types/model';
5
4
 
@@ -1,4 +1,4 @@
1
- import { Class } from '@travetto/base';
1
+ import { Class } from '@travetto/runtime';
2
2
  import { ModelType, OptionalId } from '../types/model';
3
3
 
4
4
  /**
@@ -0,0 +1,41 @@
1
+ import { BinaryInput, BlobMeta, ByteRange } from '@travetto/runtime';
2
+
3
+ /**
4
+ * Support for Blobs CRD. Blob update is not supported.
5
+ *
6
+ * @concrete ../internal/service/common#ModelBlobSupportTarget
7
+ */
8
+ export interface ModelBlobSupport {
9
+
10
+ /**
11
+ * Insert blob to storage
12
+ * @param location The location of the blob
13
+ * @param input The actual blob to write
14
+ */
15
+ insertBlob(location: string, input: BinaryInput, meta?: BlobMeta, errorIfExisting?: boolean): Promise<void>;
16
+
17
+ /**
18
+ * Upsert blob to storage
19
+ * @param location The location of the blob
20
+ * @param input The actual blob to write
21
+ */
22
+ upsertBlob(location: string, input: BinaryInput, meta?: BlobMeta): Promise<void>;
23
+
24
+ /**
25
+ * Get blob from storage
26
+ * @param location The location of the blob
27
+ */
28
+ getBlob(location: string, range?: ByteRange): Promise<Blob>;
29
+
30
+ /**
31
+ * Get metadata for blob
32
+ * @param location The location of the blob
33
+ */
34
+ describeBlob(location: string): Promise<BlobMeta>;
35
+
36
+ /**
37
+ * Delete blob by location
38
+ * @param location The location of the blob
39
+ */
40
+ deleteBlob(location: string): Promise<void>;
41
+ }
@@ -1,4 +1,4 @@
1
- import { Class, AppError } from '@travetto/base';
1
+ import { Class, AppError } from '@travetto/runtime';
2
2
  import { ValidationResultError } from '@travetto/schema';
3
3
 
4
4
  import { ModelCrudSupport } from './crud';
@@ -1,4 +1,4 @@
1
- import { Class } from '@travetto/base';
1
+ import { Class } from '@travetto/runtime';
2
2
 
3
3
  import { ModelType, OptionalId, ModelIdSource } from '../types/model';
4
4
 
@@ -1,4 +1,4 @@
1
- import { Class } from '@travetto/base';
1
+ import { Class } from '@travetto/runtime';
2
2
 
3
3
  import { ModelType } from '../types/model';
4
4
  import { ModelCrudSupport } from './crud';
@@ -1,5 +1,4 @@
1
- import { Class } from '@travetto/base';
2
- import { DeepPartial } from '@travetto/schema';
1
+ import { Class, DeepPartial } from '@travetto/runtime';
3
2
 
4
3
  import { ModelType, OptionalId } from '../types/model';
5
4
  import { ModelBasicSupport } from './basic';
@@ -7,6 +6,7 @@ import { ModelBasicSupport } from './basic';
7
6
  /**
8
7
  * Support for simple indexed activity
9
8
  *
9
+ *
10
10
  * @concrete ../internal/service/common#ModelIndexedSupportTarget
11
11
  */
12
12
  export interface ModelIndexedSupport extends ModelBasicSupport {
@@ -1,4 +1,4 @@
1
- import { Class } from '@travetto/base';
1
+ import { Class } from '@travetto/runtime';
2
2
  import { SchemaChange } from '@travetto/schema';
3
3
 
4
4
  import { ModelType } from '../types/model';
@@ -0,0 +1,60 @@
1
+ import { Readable } from 'node:stream';
2
+ import path from 'node:path';
3
+
4
+ import { AppError, BinaryInput, BlobMeta, ByteRange, Util } from '@travetto/runtime';
5
+
6
+ /**
7
+ * Utilities for processing assets
8
+ */
9
+ export class ModelBlobUtil {
10
+
11
+ /**
12
+ * Get a hashed location/path for a blob
13
+ */
14
+ static getHashedLocation(meta: BlobMeta, prefix = ''): string {
15
+ const hash = meta.hash ?? Util.uuid();
16
+
17
+ let parts = hash.match(/(.{1,4})/g)!.slice();
18
+ if (parts.length > 4) {
19
+ parts = [...parts.slice(0, 4), parts.slice(4).join('')];
20
+ }
21
+
22
+ const ext = path.extname(meta.filename ?? '') || '.bin';
23
+ return `${parts.join('/')}${ext}`;
24
+ }
25
+
26
+ /**
27
+ * Convert input to a blob, containing all data in memory
28
+ */
29
+ static async getInput(src: BinaryInput, metadata: BlobMeta = {}): Promise<[Readable, BlobMeta]> {
30
+ let input: Readable;
31
+ if (src instanceof Blob) {
32
+ metadata = { ...src.meta, ...metadata };
33
+ metadata.size ??= src.size;
34
+ input = Readable.fromWeb(src.stream());
35
+ } else if (typeof src === 'object' && 'pipeThrough' in src) {
36
+ input = Readable.fromWeb(src);
37
+ } else if (typeof src === 'object' && 'pipe' in src) {
38
+ input = src;
39
+ } else {
40
+ metadata.size = src.length;
41
+ input = Readable.from(src);
42
+ }
43
+
44
+ return [input, metadata ?? {}];
45
+ }
46
+
47
+ /**
48
+ * Enforce byte range for stream stream/file of a certain size
49
+ */
50
+ static enforceRange({ start, end }: ByteRange, size: number): Required<ByteRange> {
51
+ // End is inclusive
52
+ end = Math.min(end ?? (size - 1), size - 1);
53
+
54
+ if (Number.isNaN(start) || Number.isNaN(end) || !Number.isFinite(start) || start >= size || start < 0 || start > end) {
55
+ throw new AppError('Invalid position, out of range', 'data');
56
+ }
57
+
58
+ return { start, end };
59
+ }
60
+ }
@@ -1,4 +1,4 @@
1
- import { Env } from '@travetto/base';
1
+ import { Env } from '@travetto/runtime';
2
2
  import { CliValidationError, CliCommandShape, cliTpl } from '@travetto/cli';
3
3
  import { RootRegistry } from '@travetto/registry';
4
4
 
@@ -1,4 +1,4 @@
1
- import { Class } from '@travetto/base';
1
+ import { castTo, Class } from '@travetto/runtime';
2
2
  import { ModelRegistry } from '@travetto/model/src/registry/model';
3
3
  import { InjectableConfig, DependencyRegistry } from '@travetto/di';
4
4
  import { ModelStorageSupportTarget } from '@travetto/model/src/internal/service/common';
@@ -40,8 +40,7 @@ export class ModelCandidateUtil {
40
40
  * Get all providers that are viable candidates
41
41
  */
42
42
  static async getProviders(op?: keyof ModelStorageSupport): Promise<InjectableConfig[]> {
43
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
44
- const types = DependencyRegistry.getCandidateTypes<ModelStorageSupport>(ModelStorageSupportTarget as unknown as Class<ModelStorageSupport>);
43
+ const types = DependencyRegistry.getCandidateTypes<ModelStorageSupport>(castTo(ModelStorageSupportTarget));
45
44
  return types.filter(x => !op || x.class.prototype?.[op]);
46
45
  }
47
46
 
@@ -1,4 +1,4 @@
1
- import type { Class } from '@travetto/base';
1
+ import type { Class } from '@travetto/runtime';
2
2
  import type { ModelStorageSupport } from '@travetto/model/src/service/storage';
3
3
  import type { ModelType } from '@travetto/model/src/types/model';
4
4
 
@@ -1,4 +1,4 @@
1
- import type { Class } from '@travetto/base';
1
+ import type { Class } from '@travetto/runtime';
2
2
  import type { ModelStorageSupport } from '@travetto/model/src/service/storage';
3
3
  import type { ModelType } from '@travetto/model/src/types/model';
4
4
 
@@ -7,7 +7,7 @@ import { ModelCandidateUtil } from './bin/candidate';
7
7
  /**
8
8
  * Exports model schemas
9
9
  */
10
- @CliCommand({ addEnv: true, addModule: true })
10
+ @CliCommand({ with: { env: true, module: true } })
11
11
  export class ModelExportCommand extends BaseModelCommand {
12
12
 
13
13
  getOp(): 'exportModel' { return 'exportModel'; }
@@ -7,7 +7,7 @@ import { ModelCandidateUtil } from './bin/candidate';
7
7
  /**
8
8
  * Installing models
9
9
  */
10
- @CliCommand({ addEnv: true, addModule: true })
10
+ @CliCommand({ with: { env: true, module: true } })
11
11
  export class ModelInstallCommand extends BaseModelCommand {
12
12
 
13
13
  getOp(): 'createModel' { return 'createModel'; }
@@ -1,8 +1,5 @@
1
1
  /** @jsxImportSource @travetto/doc */
2
- import { readFileSync } from 'node:fs';
3
-
4
- import { RuntimeIndex } from '@travetto/manifest';
5
- import { d, c, DocJSXElementByFn, DocJSXElement } from '@travetto/doc';
2
+ import { d, c, DocJSXElementByFn, DocJSXElement, DocFileUtil } from '@travetto/doc';
6
3
  import { Config } from '@travetto/config';
7
4
 
8
5
  export const Links = {
@@ -11,17 +8,14 @@ export const Links = {
11
8
  Expiry: d.codeLink('Expiry', '@travetto/model/src/service/expiry.ts', /export interface/),
12
9
  Indexed: d.codeLink('Indexed', '@travetto/model/src/service/indexed.ts', /export interface/),
13
10
  Bulk: d.codeLink('Bulk', '@travetto/model/src/service/bulk.ts', /export interface/),
14
- Stream: d.codeLink('Streaming', '@travetto/model/src/service/stream.ts', /export interface/),
11
+ Blob: d.codeLink('Blob', '@travetto/model/src/service/blob.ts', /export interface/),
15
12
  };
16
13
 
17
- export const ModelTypes = (file: string | Function): DocJSXElement[] => {
18
- if (typeof file !== 'string') {
19
- file = RuntimeIndex.getFunctionMetadata(file)!.source;
20
- }
21
- const contents = readFileSync(file, 'utf8');
14
+ export const ModelTypes = (fn: | Function): DocJSXElement[] => {
15
+ const { content } = DocFileUtil.readSource(fn);
22
16
  const found: DocJSXElementByFn<'CodeLink'>[] = [];
23
17
  const seen = new Set();
24
- for (const [, key] of contents.matchAll(/Model(Crud|Expiry|Indexed|Bulk|Stream)Support/g)) {
18
+ for (const [, key] of content.matchAll(/Model(Crud|Expiry|Indexed|Bulk|Blob)Support/g)) {
25
19
  if (!seen.has(key)) {
26
20
  seen.add(key);
27
21
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -0,0 +1 @@
1
+ abcdefghijklmnopqrstuvwxyz
File without changes
File without changes
Binary file
Binary file
Binary file
Binary file
@@ -1,5 +1,5 @@
1
1
  import { DependencyRegistry } from '@travetto/di';
2
- import { AppError, Class } from '@travetto/base';
2
+ import { AppError, castTo, Class, classConstruct } from '@travetto/runtime';
3
3
 
4
4
  import { isBulkSupported, isCrudSupported } from '../../src/internal/service/common';
5
5
  import { ModelType } from '../../src/types/model';
@@ -11,7 +11,7 @@ type ServiceClass = { serviceClass: { new(): unknown } };
11
11
  export abstract class BaseModelSuite<T> {
12
12
 
13
13
  static ifNot(pred: (svc: unknown) => boolean): (x: unknown) => Promise<boolean> {
14
- return async (x: unknown) => !pred(new (x as ServiceClass).serviceClass());
14
+ return async (x: unknown) => !pred(classConstruct(castTo<ServiceClass>(x).serviceClass));
15
15
  }
16
16
 
17
17
  serviceClass: Class<T>;
@@ -36,7 +36,7 @@ export abstract class BaseModelSuite<T> {
36
36
  const res = await svc.processBulk(cls, items.map(x => ({ insert: x })));
37
37
  return res.counts.insert;
38
38
  } else if (isCrudSupported(svc)) {
39
- const out = [] as Promise<M>[];
39
+ const out: Promise<M>[] = [];
40
40
  for (const el of items) {
41
41
  out.push(svc.create(cls, el));
42
42
  }
@@ -0,0 +1,117 @@
1
+ import assert from 'node:assert';
2
+
3
+ import { Suite, Test, TestFixtures } from '@travetto/test';
4
+ import { BaseModelSuite } from '@travetto/model/support/test/base';
5
+ import { Util } from '@travetto/runtime';
6
+
7
+ import { ModelBlobSupport } from '../../src/service/blob';
8
+ import { ModelBlobUtil } from '../../src/util/blob';
9
+
10
+ @Suite()
11
+ export abstract class ModelBlobSuite extends BaseModelSuite<ModelBlobSupport> {
12
+
13
+ fixture = new TestFixtures(['@travetto/model']);
14
+
15
+ @Test()
16
+ async writeBasic(): Promise<void> {
17
+ const service = await this.service;
18
+ const buffer = await this.fixture.read('/asset.yml', true);
19
+
20
+ const id = Util.uuid();
21
+
22
+ await service.upsertBlob(id, buffer);
23
+ const meta = await service.describeBlob(id);
24
+ const retrieved = await service.describeBlob(id);
25
+ assert.deepStrictEqual(meta, retrieved);
26
+ }
27
+
28
+ @Test()
29
+ async writeStream(): Promise<void> {
30
+ const service = await this.service;
31
+ const buffer = await this.fixture.read('/asset.yml', true);
32
+
33
+ const id = Util.uuid();
34
+ await service.upsertBlob(id, buffer);
35
+ const meta = await service.describeBlob(id);
36
+
37
+ const retrieved = await service.getBlob(id);
38
+ const retrievedMeta = retrieved.meta!;
39
+ assert(meta.hash === retrievedMeta.hash);
40
+ }
41
+
42
+ @Test()
43
+ async writeAndDelete(): Promise<void> {
44
+ const service = await this.service;
45
+ const buffer = await this.fixture.read('/asset.yml', true);
46
+
47
+ const id = Util.uuid();
48
+ await service.upsertBlob(id, buffer);
49
+
50
+ await service.deleteBlob(id);
51
+
52
+ await assert.rejects(async () => {
53
+ await service.getBlob(id);
54
+ });
55
+ }
56
+
57
+ @Test()
58
+ async partialStream(): Promise<void> {
59
+ const service = await this.service;
60
+ const buffer = await this.fixture.read('/text.txt', true);
61
+
62
+ const id = Util.uuid();
63
+ await service.upsertBlob(id, buffer);
64
+
65
+ const retrieved = await service.getBlob(id);
66
+ const content = await retrieved.text();
67
+ assert(content.startsWith('abc'));
68
+ assert(content.endsWith('xyz'));
69
+
70
+ const partial = await service.getBlob(id, { start: 10, end: 20 });
71
+ assert(partial.size === 11);
72
+ const partialMeta = partial.meta!;
73
+ const subContent = await partial.text();
74
+ const range = await ModelBlobUtil.enforceRange({ start: 10, end: 20 }, partialMeta.size!);
75
+ assert(subContent.length === (range.end - range.start) + 1);
76
+
77
+ const og = await this.fixture.read('/text.txt');
78
+
79
+ assert(subContent === og.substring(10, 21));
80
+
81
+ const partialUnbounded = await service.getBlob(id, { start: 10 });
82
+ const partialUnboundedMeta = partial.meta!;
83
+ const subContent2 = await partialUnbounded.text();
84
+ const range2 = await ModelBlobUtil.enforceRange({ start: 10 }, partialUnboundedMeta.size!);
85
+ assert(subContent2.length === (range2.end - range2.start) + 1);
86
+ assert(subContent2.startsWith('klm'));
87
+ assert(subContent2.endsWith('xyz'));
88
+
89
+ const partialSingle = await service.getBlob(id, { start: 10, end: 10 });
90
+ const subContent3 = await partialSingle.text();
91
+ assert(subContent3.length === 1);
92
+ assert(subContent3 === 'k');
93
+
94
+ const partialOverBounded = await service.getBlob(id, { start: 20, end: 40 });
95
+ const subContent4 = await partialOverBounded.text();
96
+ assert(subContent4.length === 6);
97
+ assert(subContent4.endsWith('xyz'));
98
+
99
+ await assert.rejects(() => service.getBlob(id, { start: -10, end: 10 }));
100
+ await assert.rejects(() => service.getBlob(id, { start: 30, end: 37 }));
101
+ }
102
+
103
+
104
+ @Test()
105
+ async writeAndGet() {
106
+ const service = await this.service;
107
+ const buffer = await this.fixture.read('/asset.yml', true);
108
+ await service.upsertBlob('orange', buffer, { contentType: 'text/yaml', filename: 'asset.yml' });
109
+ const saved = await service.getBlob('orange');
110
+ const savedMeta = saved.meta!;
111
+
112
+ assert('text/yaml' === savedMeta.contentType);
113
+ assert(buffer.length === savedMeta.size);
114
+ assert('asset.yml' === savedMeta.filename);
115
+ assert(undefined === savedMeta.hash);
116
+ }
117
+ }