@verdant-web/store 5.4.4 → 5.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.
@@ -1,15 +1,14 @@
1
1
  var _a, _b;
2
- import { EventSubscriber } from '@verdant-web/common';
2
+ import { EventSubscriber, } from '@verdant-web/common';
3
3
  export const UPDATE = Symbol('entity-file-update');
4
4
  export const MARK_FAILED = Symbol('entity-file-mark-failed');
5
- // this one goes on Entity
6
- export const CHILD_FILE_CHANGED = Symbol('child-file-changed');
7
5
  /**
8
6
  * Provides a consistent interface for files used in an app via
9
7
  * Entity access.
10
8
  */
11
9
  export class EntityFile extends EventSubscriber {
12
- constructor(id, { downloadRemote = false, ctx, parent, }) {
10
+ constructor(id, { downloadRemote = false, ctx, fileContext, }) {
11
+ var _c, _d;
13
12
  super();
14
13
  this.id = id;
15
14
  // cached object URL for a local blob file, if applicable
@@ -19,7 +18,26 @@ export class EntityFile extends EventSubscriber {
19
18
  this._failed = false;
20
19
  this._downloadRemote = false;
21
20
  this._uploaded = false;
21
+ this._alt = null;
22
22
  this.unsubscribes = [];
23
+ this.setAlt = (alt) => {
24
+ var _c;
25
+ const file = this.fileContext.getFileRef();
26
+ if (!file || file.id !== this.id) {
27
+ throw new Error('Cannot set alt text on a file which is no longer present');
28
+ }
29
+ if (((_c = file.alt) !== null && _c !== void 0 ? _c : null) === alt)
30
+ return;
31
+ this.fileContext.applyFileRef(Object.assign(Object.assign({}, file), { alt }));
32
+ };
33
+ this.onContextChange = () => {
34
+ var _c, _d;
35
+ const nextAlt = (_d = (_c = this.fileContext.getFileRef()) === null || _c === void 0 ? void 0 : _c.alt) !== null && _d !== void 0 ? _d : null;
36
+ if (nextAlt !== this._alt) {
37
+ this._alt = nextAlt;
38
+ this.emitChange();
39
+ }
40
+ };
23
41
  this[_a] = (fileData) => {
24
42
  this.ctx.log('debug', 'EntityFile updated', this.id, fileData);
25
43
  this._loading = false;
@@ -55,8 +73,10 @@ export class EntityFile extends EventSubscriber {
55
73
  this.dispose();
56
74
  };
57
75
  this.ctx = ctx;
58
- this.parent = parent;
76
+ this.fileContext = fileContext;
59
77
  this._downloadRemote = downloadRemote;
78
+ this._alt = (_d = (_c = fileContext.getFileRef()) === null || _c === void 0 ? void 0 : _c.alt) !== null && _d !== void 0 ? _d : null;
79
+ this.unsubscribes.push(fileContext.subscribe(this.onContextChange));
60
80
  this.unsubscribes.push(this.ctx.internalEvents.subscribe(`fileUploaded:${id}`, this.onUploaded));
61
81
  }
62
82
  get downloadRemote() {
@@ -72,8 +92,11 @@ export class EntityFile extends EventSubscriber {
72
92
  get error() {
73
93
  return this._failedReason || null;
74
94
  }
95
+ get alt() {
96
+ return this._alt;
97
+ }
75
98
  emitChange() {
76
- this.parent[CHILD_FILE_CHANGED](this);
99
+ this.fileContext.onChange();
77
100
  this.emit('change');
78
101
  }
79
102
  get url() {
@@ -109,6 +132,7 @@ export class EntityFile extends EventSubscriber {
109
132
  remote: (_h = (_g = this._fileData) === null || _g === void 0 ? void 0 : _g.remote) !== null && _h !== void 0 ? _h : false,
110
133
  type: (_j = this.type) !== null && _j !== void 0 ? _j : '',
111
134
  file: (_k = this._fileData) === null || _k === void 0 ? void 0 : _k.file,
135
+ alt: this.alt,
112
136
  };
113
137
  }
114
138
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EntityFile.js","sourceRoot":"","sources":["../../../src/files/EntityFile.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EntityFile.js","sourceRoot":"","sources":["../../../src/files/EntityFile.ts"],"names":[],"mappings":"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"}
@@ -1,18 +1,18 @@
1
1
  import { FileData } from '@verdant-web/common';
2
2
  import { Context } from '../context/context.js';
3
- import { Entity } from '../entities/Entity.js';
4
3
  import { Disposable } from '../internal.js';
5
4
  import { Sync } from '../sync/Sync.js';
6
- import { EntityFile } from './EntityFile.js';
5
+ import { EntityFile, EntityFileContext } from './EntityFile.js';
7
6
  export declare class FileManager extends Disposable {
8
7
  private sync;
9
8
  private context;
10
9
  private cache;
10
+ private fileData;
11
11
  constructor({ sync, context }: {
12
12
  sync: Sync;
13
13
  context: Context;
14
14
  });
15
- add: (file: FileData, parent: Entity) => Promise<void>;
15
+ add: (file: FileData) => Promise<void>;
16
16
  /**
17
17
  * Immediately returns an EntityFile to use, then either loads
18
18
  * the file from cache, local database, or the server.
@@ -20,8 +20,9 @@ export declare class FileManager extends Disposable {
20
20
  get: (id: string, options: {
21
21
  downloadRemote?: boolean;
22
22
  ctx: Context;
23
- parent: Entity;
23
+ fileContext: EntityFileContext;
24
24
  }) => EntityFile;
25
+ private updateFileData;
25
26
  private load;
26
27
  private onFileUploaded;
27
28
  }
@@ -1,44 +1,63 @@
1
1
  import { Disposable } from '../internal.js';
2
- import { EntityFile, MARK_FAILED, UPDATE } from './EntityFile.js';
2
+ import { EntityFile, MARK_FAILED, UPDATE, } from './EntityFile.js';
3
3
  export class FileManager extends Disposable {
4
4
  constructor({ sync, context }) {
5
5
  super();
6
6
  this.cache = new Map();
7
- this.add = async (file, parent) => {
8
- // immediately cache the file
9
- let entityFile = this.cache.get(file.id);
10
- if (!entityFile) {
11
- entityFile = new EntityFile(file.id, { ctx: this.context, parent });
12
- this.cache.set(file.id, entityFile);
13
- }
7
+ this.fileData = new Map();
8
+ this.add = async (file) => {
14
9
  if (!file.remote) {
15
10
  // immediately update local files.
16
- entityFile[UPDATE](file);
11
+ this.updateFileData(file);
17
12
  }
18
13
  // this will download any original remote file and trigger a re-upload to the
19
14
  // new file's identity, in addition to storing it on disk
20
15
  const processedFile = await (await this.context.files).add(file, { cloneRemote: true });
21
- entityFile[UPDATE](processedFile);
16
+ this.updateFileData(processedFile);
22
17
  };
23
18
  /**
24
19
  * Immediately returns an EntityFile to use, then either loads
25
20
  * the file from cache, local database, or the server.
26
21
  */
27
22
  this.get = (id, options) => {
28
- if (this.cache.has(id)) {
29
- return this.cache.get(id);
23
+ var _a, _b;
24
+ const cacheKey = `${options.fileContext.oid}:${String(options.fileContext.key)}:${id}`;
25
+ const cachedFile = (_a = this.cache.get(cacheKey)) === null || _a === void 0 ? void 0 : _a.deref();
26
+ if (cachedFile) {
27
+ return cachedFile;
30
28
  }
29
+ this.cache.delete(cacheKey);
31
30
  const file = new EntityFile(id, options);
32
- this.cache.set(id, file);
33
- this.load(file);
31
+ this.cache.set(cacheKey, this.context.weakRef(file));
32
+ const data = (_b = this.fileData.get(id)) === null || _b === void 0 ? void 0 : _b.deref();
33
+ if (data) {
34
+ file[UPDATE](data);
35
+ }
36
+ else {
37
+ this.fileData.delete(id);
38
+ this.load(file);
39
+ }
34
40
  return file;
35
41
  };
42
+ this.updateFileData = (data) => {
43
+ this.fileData.set(data.id, this.context.weakRef(data));
44
+ for (const [key, fileRef] of this.cache) {
45
+ const file = fileRef.deref();
46
+ if (!file) {
47
+ this.cache.delete(key);
48
+ continue;
49
+ }
50
+ if (file.id === data.id) {
51
+ file[UPDATE](data);
52
+ }
53
+ }
54
+ };
36
55
  this.load = async (file) => {
37
56
  var _a;
38
57
  const fileData = await (await this.context.files).get(file.id);
39
58
  // immediately apply any file data we have
40
59
  if (fileData) {
41
- file[UPDATE](fileData);
60
+ this.updateFileData(fileData);
42
61
  }
43
62
  if (!(fileData === null || fileData === void 0 ? void 0 : fileData.url) && (!fileData || fileData.remote)) {
44
63
  // maybe we don't have it yet, it might be on the server still.
@@ -59,7 +78,7 @@ export class FileManager extends Disposable {
59
78
  // avoid overwriting local values except for url
60
79
  const copyWithUrl = Object.assign(Object.assign(Object.assign({}, result.data), fileData), { url: result.data.url });
61
80
  await (await this.context.files).update(copyWithUrl);
62
- file[UPDATE](result.data);
81
+ this.updateFileData(result.data);
63
82
  }
64
83
  else {
65
84
  this.context.log('error', 'Failed to load file', result);
@@ -1 +1 @@
1
- {"version":3,"file":"FileManager.js","sourceRoot":"","sources":["../../../src/files/FileManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FileManager.js","sourceRoot":"","sources":["../../../src/files/FileManager.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verdant-web/store",
3
- "version": "5.4.4",
3
+ "version": "5.5.0",
4
4
  "access": "public",
5
5
  "type": "module",
6
6
  "main": "dist/esm/index.js",
@@ -39,7 +39,7 @@
39
39
  "jszip": "^3.10.1",
40
40
  "jwt-decode": "^3.1.2",
41
41
  "weak-event": "^2.0.5",
42
- "@verdant-web/common": "3.2.1"
42
+ "@verdant-web/common": "3.2.2"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "20.10.5",
@@ -583,9 +583,9 @@ describe('entities', () => {
583
583
 
584
584
  fileList.moveItem(file1Ref, 2);
585
585
 
586
- expect(fileList.get(0)).toBe(file2Ref);
587
- expect(fileList.get(1)).toBe(file3Ref);
588
- expect(fileList.get(2)).toBe(file1Ref);
586
+ expect(fileList.get(0).uid).toBe(file2Ref.uid);
587
+ expect(fileList.get(1).uid).toBe(file3Ref.uid);
588
+ expect(fileList.get(2).uid).toBe(file1Ref.uid);
589
589
  });
590
590
 
591
591
  it('should return nullish for missing map items', async () => {
@@ -27,7 +27,7 @@ import {
27
27
  validateEntityField,
28
28
  } from '@verdant-web/common';
29
29
  import { Context } from '../context/context.js';
30
- import { CHILD_FILE_CHANGED } from '../files/EntityFile.js';
30
+ import { EntityFileContext } from '../files/EntityFile.js';
31
31
  import { FileManager } from '../files/FileManager.js';
32
32
  import { processValueFiles } from '../files/utils.js';
33
33
  import { ClientWithCollections, EntityFile } from '../index.js';
@@ -751,16 +751,38 @@ export class Entity<
751
751
  this.emit('changeDeep', target, ev);
752
752
  this.parent?.deepChange(target, ev);
753
753
  };
754
- [CHILD_FILE_CHANGED] = (file: EntityFile) => {
755
- // consistent with prior behavior, but kind of arbitrary.
756
- this.deepChange(this, { isLocal: false, oid: this.oid });
757
- };
754
+ private createFileContext = (key: any): EntityFileContext => ({
755
+ oid: this.oid,
756
+ key,
757
+ getFileRef: () => {
758
+ const file = this.rawView?.[key];
759
+ return isFileRef(file) ? file : null;
760
+ },
761
+ applyFileRef: (file) => {
762
+ if (this.readonlyKeys.includes(key as string)) {
763
+ throw new Error(`Cannot set readonly key ${key.toString()}`);
764
+ }
765
+ this.addPendingOperations(
766
+ this.isList
767
+ ? this.patchCreator.createListSet(this.oid, key, file)
768
+ : this.patchCreator.createSet(this.oid, key, file),
769
+ );
770
+ },
771
+ subscribe: (callback) => this.subscribe('change', callback),
772
+ onChange: () => this.deepChange(this, { isLocal: false, oid: this.oid }),
773
+ });
758
774
 
759
775
  private getChild = (key: any, oid: ObjectIdentifier) => {
760
776
  const schema = getChildFieldSchema(this.schema, key);
761
777
  if (!schema) {
778
+ let schemaString = '';
779
+ try {
780
+ schemaString = JSON.stringify(this.schema);
781
+ } catch {
782
+ schemaString = '<unable to stringify schema>';
783
+ }
762
784
  throw new Error(
763
- `No schema for key ${String(key)} in ${JSON.stringify(this.schema)}`,
785
+ `No schema for key ${String(key)} in ${schemaString} (${this.oid})`,
764
786
  );
765
787
  }
766
788
  return this.entityFamily.get({
@@ -828,7 +850,7 @@ export class Entity<
828
850
  const file = this.files.get(child.id, {
829
851
  downloadRemote: !!fieldSchema.downloadRemote,
830
852
  ctx: this.ctx,
831
- parent: this,
853
+ fileContext: this.createFileContext(key),
832
854
  });
833
855
 
834
856
  return file as KeyValue[Key];
@@ -1013,7 +1035,7 @@ export class Entity<
1013
1035
  });
1014
1036
  }
1015
1037
  }
1016
- return processValueFiles(value, (file) => this.files.add(file, this));
1038
+ return processValueFiles(value, (file) => this.files.add(file));
1017
1039
  };
1018
1040
 
1019
1041
  private getDeleteMode = (key: any) => {
@@ -389,7 +389,7 @@ export class EntityStore extends Disposable {
389
389
  'files to new entity',
390
390
  oid,
391
391
  );
392
- fileRefs.forEach((file) => this.files.add(file, entity));
392
+ fileRefs.forEach((file) => this.files.add(file));
393
393
  return entity;
394
394
  };
395
395
 
@@ -1,6 +1,11 @@
1
- import { EventSubscriber, FileData } from '@verdant-web/common';
1
+ import {
2
+ EventSubscriber,
3
+ FileData,
4
+ FileRef,
5
+ ObjectIdentifier,
6
+ PropertyName,
7
+ } from '@verdant-web/common';
2
8
  import { Context } from '../context/context.js';
3
- import { Entity } from '../entities/Entity.js';
4
9
 
5
10
  export type EntityFileEvents = {
6
11
  change: () => void;
@@ -9,12 +14,23 @@ export type EntityFileEvents = {
9
14
  export const UPDATE = Symbol('entity-file-update');
10
15
  export const MARK_FAILED = Symbol('entity-file-mark-failed');
11
16
 
12
- // this one goes on Entity
13
- export const CHILD_FILE_CHANGED = Symbol('child-file-changed');
17
+ export interface EntityFileContext {
18
+ readonly oid: ObjectIdentifier;
19
+ readonly key: PropertyName;
20
+ getFileRef(): FileRef | null;
21
+ applyFileRef(file: FileRef): void;
22
+ subscribe(callback: () => void): () => void;
23
+ onChange(): void;
24
+ }
14
25
 
15
26
  export type EntityFileSnapshot = {
16
27
  id: string;
17
28
  url?: string | null;
29
+ name: string;
30
+ remote: boolean;
31
+ type: string;
32
+ file?: Blob | null;
33
+ alt: string | null;
18
34
  };
19
35
 
20
36
  /**
@@ -30,26 +46,29 @@ export class EntityFile extends EventSubscriber<EntityFileEvents> {
30
46
  private _failedReason: string | undefined;
31
47
  private _downloadRemote = false;
32
48
  private _uploaded = false;
49
+ private _alt: string | null = null;
33
50
  private ctx: Context;
34
51
  private unsubscribes: (() => void)[] = [];
35
- private parent: Entity;
52
+ private fileContext: EntityFileContext;
36
53
 
37
54
  constructor(
38
55
  public readonly id: string,
39
56
  {
40
57
  downloadRemote = false,
41
58
  ctx,
42
- parent,
59
+ fileContext,
43
60
  }: {
44
61
  downloadRemote?: boolean;
45
62
  ctx: Context;
46
- parent: Entity;
63
+ fileContext: EntityFileContext;
47
64
  },
48
65
  ) {
49
66
  super();
50
67
  this.ctx = ctx;
51
- this.parent = parent;
68
+ this.fileContext = fileContext;
52
69
  this._downloadRemote = downloadRemote;
70
+ this._alt = fileContext.getFileRef()?.alt ?? null;
71
+ this.unsubscribes.push(fileContext.subscribe(this.onContextChange));
53
72
 
54
73
  this.unsubscribes.push(
55
74
  this.ctx.internalEvents.subscribe(`fileUploaded:${id}`, this.onUploaded),
@@ -68,9 +87,31 @@ export class EntityFile extends EventSubscriber<EntityFileEvents> {
68
87
  get error() {
69
88
  return this._failedReason || null;
70
89
  }
90
+ get alt() {
91
+ return this._alt;
92
+ }
93
+
94
+ setAlt = (alt: string | null) => {
95
+ const file = this.fileContext.getFileRef();
96
+ if (!file || file.id !== this.id) {
97
+ throw new Error(
98
+ 'Cannot set alt text on a file which is no longer present',
99
+ );
100
+ }
101
+ if ((file.alt ?? null) === alt) return;
102
+ this.fileContext.applyFileRef({ ...file, alt });
103
+ };
104
+
105
+ private onContextChange = () => {
106
+ const nextAlt = this.fileContext.getFileRef()?.alt ?? null;
107
+ if (nextAlt !== this._alt) {
108
+ this._alt = nextAlt;
109
+ this.emitChange();
110
+ }
111
+ };
71
112
 
72
113
  private emitChange() {
73
- this.parent[CHILD_FILE_CHANGED](this);
114
+ this.fileContext.onChange();
74
115
  this.emit('change');
75
116
  }
76
117
 
@@ -135,7 +176,7 @@ export class EntityFile extends EventSubscriber<EntityFileEvents> {
135
176
  this.dispose();
136
177
  };
137
178
 
138
- getSnapshot(): FileData {
179
+ getSnapshot(): EntityFileSnapshot {
139
180
  return {
140
181
  id: this.id,
141
182
  url: this._fileData?.url ?? this._objectUrl ?? undefined,
@@ -143,6 +184,7 @@ export class EntityFile extends EventSubscriber<EntityFileEvents> {
143
184
  remote: this._fileData?.remote ?? false,
144
185
  type: this.type ?? '',
145
186
  file: this._fileData?.file,
187
+ alt: this.alt,
146
188
  };
147
189
  }
148
190
  }
@@ -1,15 +1,20 @@
1
1
  import { FileData } from '@verdant-web/common';
2
2
  import { Context } from '../context/context.js';
3
- import { Entity } from '../entities/Entity.js';
4
3
  import { Disposable } from '../internal.js';
5
4
  import { Sync } from '../sync/Sync.js';
6
- import { EntityFile, MARK_FAILED, UPDATE } from './EntityFile.js';
5
+ import {
6
+ EntityFile,
7
+ EntityFileContext,
8
+ MARK_FAILED,
9
+ UPDATE,
10
+ } from './EntityFile.js';
7
11
 
8
12
  export class FileManager extends Disposable {
9
13
  private sync;
10
14
  private context;
11
15
 
12
- private cache = new Map<string, EntityFile>();
16
+ private cache = new Map<string, WeakRef<EntityFile>>();
17
+ private fileData = new Map<string, WeakRef<FileData>>();
13
18
 
14
19
  constructor({ sync, context }: { sync: Sync; context: Context }) {
15
20
  super();
@@ -23,24 +28,17 @@ export class FileManager extends Disposable {
23
28
  );
24
29
  }
25
30
 
26
- add = async (file: FileData, parent: Entity) => {
27
- // immediately cache the file
28
- let entityFile = this.cache.get(file.id);
29
- if (!entityFile) {
30
- entityFile = new EntityFile(file.id, { ctx: this.context, parent });
31
- this.cache.set(file.id, entityFile);
32
- }
33
-
31
+ add = async (file: FileData) => {
34
32
  if (!file.remote) {
35
33
  // immediately update local files.
36
- entityFile[UPDATE](file);
34
+ this.updateFileData(file);
37
35
  }
38
36
  // this will download any original remote file and trigger a re-upload to the
39
37
  // new file's identity, in addition to storing it on disk
40
38
  const processedFile = await (
41
39
  await this.context.files
42
40
  ).add(file, { cloneRemote: true });
43
- entityFile[UPDATE](processedFile);
41
+ this.updateFileData(processedFile);
44
42
  };
45
43
 
46
44
  /**
@@ -49,22 +47,49 @@ export class FileManager extends Disposable {
49
47
  */
50
48
  get = (
51
49
  id: string,
52
- options: { downloadRemote?: boolean; ctx: Context; parent: Entity },
50
+ options: {
51
+ downloadRemote?: boolean;
52
+ ctx: Context;
53
+ fileContext: EntityFileContext;
54
+ },
53
55
  ) => {
54
- if (this.cache.has(id)) {
55
- return this.cache.get(id)!;
56
+ const cacheKey = `${options.fileContext.oid}:${String(options.fileContext.key)}:${id}`;
57
+ const cachedFile = this.cache.get(cacheKey)?.deref();
58
+ if (cachedFile) {
59
+ return cachedFile;
56
60
  }
61
+ this.cache.delete(cacheKey);
57
62
  const file = new EntityFile(id, options);
58
- this.cache.set(id, file);
59
- this.load(file);
63
+ this.cache.set(cacheKey, this.context.weakRef(file));
64
+ const data = this.fileData.get(id)?.deref();
65
+ if (data) {
66
+ file[UPDATE](data);
67
+ } else {
68
+ this.fileData.delete(id);
69
+ this.load(file);
70
+ }
60
71
  return file;
61
72
  };
62
73
 
74
+ private updateFileData = (data: FileData) => {
75
+ this.fileData.set(data.id, this.context.weakRef(data));
76
+ for (const [key, fileRef] of this.cache) {
77
+ const file = fileRef.deref();
78
+ if (!file) {
79
+ this.cache.delete(key);
80
+ continue;
81
+ }
82
+ if (file.id === data.id) {
83
+ file[UPDATE](data);
84
+ }
85
+ }
86
+ };
87
+
63
88
  private load = async (file: EntityFile) => {
64
89
  const fileData = await (await this.context.files).get(file.id);
65
90
  // immediately apply any file data we have
66
91
  if (fileData) {
67
- file[UPDATE](fileData);
92
+ this.updateFileData(fileData);
68
93
  }
69
94
 
70
95
  if (!fileData?.url && (!fileData || fileData.remote)) {
@@ -96,7 +121,7 @@ export class FileManager extends Disposable {
96
121
  url: result.data.url,
97
122
  };
98
123
  await (await this.context.files).update(copyWithUrl);
99
- file[UPDATE](result.data);
124
+ this.updateFileData(result.data);
100
125
  } else {
101
126
  this.context.log('error', 'Failed to load file', result);
102
127
  if (!fileData) {