@concord-consortium/object-storage 1.0.0-pre.5 → 1.0.0-pre.7

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
@@ -39,7 +39,9 @@ function YourComponent() {
39
39
 
40
40
  // Use storage methods
41
41
  const handleAdd = async () => {
42
- const id = await storage.add({ metadata: {}, data: {} });
42
+ const object = new StoredObject();
43
+ const result = await storage.add(object);
44
+ console.log('Added object with id:', result.id);
43
45
  };
44
46
 
45
47
  return <div>Your component</div>;
@@ -1,44 +1,26 @@
1
- import { DemoObjectStorageConfig, IObjectStorage, ObjectMetadata, ObjectData, StoredObject, ObjectWithId, MonitorCallback, DemonitorFunction, AddOptions } from './types';
1
+ import { DemoObjectStorageConfig, IObjectStorage, MonitorCallback, DemonitorFunction, StoredObjectMetadataWithId } from './types';
2
+ import { StoredObject, StoredObjectMetadata, StoredObjectData } from './stored-object';
2
3
  export declare class DemoObjectStorage implements IObjectStorage {
3
4
  private config;
4
5
  private objects;
5
6
  private monitors;
6
7
  constructor(config: DemoObjectStorageConfig);
7
- /**
8
- * Lists metadata documents for objects owned by the current user
9
- */
10
- listMine(): Promise<ObjectWithId[]>;
11
- /**
12
- * Lists metadata documents for objects linked to the current user
13
- */
14
- listLinked(): Promise<ObjectWithId[]>;
8
+ private getQuestionMetadata;
15
9
  /**
16
10
  * Lists metadata documents for objects associated with specific question IDs
17
11
  */
18
- list(questionIds: string[]): Promise<ObjectWithId[]>;
19
- /**
20
- * Monitors metadata documents for objects owned by the current user
21
- * Invokes callback at start and on any change
22
- * Returns a function to stop monitoring
23
- */
24
- monitorMine(callback: MonitorCallback): DemonitorFunction;
25
- /**
26
- * Monitors metadata documents for objects linked to the current user
27
- * Invokes callback at start and on any change
28
- * Returns a function to stop monitoring
29
- */
30
- monitorLinked(callback: MonitorCallback): DemonitorFunction;
12
+ list(questionId: string): Promise<StoredObjectMetadataWithId[]>;
31
13
  /**
32
14
  * Monitors metadata documents for objects associated with specific question IDs
33
15
  * Invokes callback at start and on any change
34
16
  * Returns a function to stop monitoring
35
17
  */
36
- monitor(questionIds: string[], callback: MonitorCallback): DemonitorFunction;
18
+ monitor(questionId: string, callback: MonitorCallback): DemonitorFunction;
37
19
  /**
38
20
  * Adds both metadata and data documents for a new object
39
- * Returns the generated object ID (nanoid) or the provided ID if specified in options
21
+ * Returns the StoredObject that was added
40
22
  */
41
- add(object: StoredObject, options?: AddOptions): Promise<string>;
23
+ add(object: StoredObject): Promise<StoredObject>;
42
24
  /**
43
25
  * Reads both metadata and data documents for an object
44
26
  * Returns undefined if the object is not found
@@ -48,16 +30,17 @@ export declare class DemoObjectStorage implements IObjectStorage {
48
30
  * Reads only the metadata document for an object
49
31
  * Returns undefined if the object is not found
50
32
  */
51
- readMetadata(objectId: string): Promise<ObjectMetadata | undefined>;
33
+ readMetadata(objectId: string): Promise<StoredObjectMetadata | undefined>;
52
34
  /**
53
35
  * Reads only the data document for an object
54
36
  * Returns undefined if the object is not found
55
37
  */
56
- readData(objectId: string): Promise<ObjectData | undefined>;
38
+ readData(objectId: string): Promise<StoredObjectData | undefined>;
57
39
  /**
58
- * Generates a new unique ID using nanoid
40
+ * Reads a single data item for an object
41
+ * Returns undefined if the object or item is not found
59
42
  */
60
- genId(): string;
43
+ readDataItem(objectId: string, itemId: string): Promise<any | undefined>;
61
44
  /**
62
45
  * Notifies all active monitors of changes
63
46
  */
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DemoObjectStorage = void 0;
4
- const nanoid_1 = require("nanoid");
5
4
  class DemoObjectStorage {
6
5
  constructor(config) {
7
6
  if (config.version !== 1) {
@@ -11,76 +10,33 @@ class DemoObjectStorage {
11
10
  this.objects = new Map();
12
11
  this.monitors = new Map();
13
12
  }
14
- /**
15
- * Lists metadata documents for objects owned by the current user
16
- */
17
- async listMine() {
18
- return Array.from(this.objects.values());
19
- }
20
- /**
21
- * Lists metadata documents for objects linked to the current user
22
- */
23
- async listLinked() {
24
- // not applicable in demo storage
25
- return [];
13
+ getQuestionMetadata() {
14
+ // In demo mode, just return all objects since there are no other questions
15
+ // that can use this storage instance
16
+ return Array.from(this.objects.entries()).map(([id, obj]) => {
17
+ return { id, metadata: obj.metadata };
18
+ });
26
19
  }
27
20
  /**
28
21
  * Lists metadata documents for objects associated with specific question IDs
29
22
  */
30
- async list(questionIds) {
31
- // In demo mode, just return all objects
32
- return Array.from(this.objects.values());
33
- }
34
- /**
35
- * Monitors metadata documents for objects owned by the current user
36
- * Invokes callback at start and on any change
37
- * Returns a function to stop monitoring
38
- */
39
- monitorMine(callback) {
40
- const key = 'mine';
41
- if (!this.monitors.has(key)) {
42
- this.monitors.set(key, []);
43
- }
44
- this.monitors.get(key).push(callback);
45
- // Invoke callback immediately with current state
46
- callback(Array.from(this.objects.values()));
47
- return () => {
48
- const callbacks = this.monitors.get(key);
49
- if (callbacks) {
50
- const index = callbacks.indexOf(callback);
51
- if (index > -1) {
52
- callbacks.splice(index, 1);
53
- }
54
- }
55
- };
56
- }
57
- /**
58
- * Monitors metadata documents for objects linked to the current user
59
- * Invokes callback at start and on any change
60
- * Returns a function to stop monitoring
61
- */
62
- monitorLinked(callback) {
63
- // not applicable in demo storage
64
- callback([]);
65
- return () => {
66
- // no-op
67
- };
23
+ async list(questionId) {
24
+ return this.getQuestionMetadata();
68
25
  }
69
26
  /**
70
27
  * Monitors metadata documents for objects associated with specific question IDs
71
28
  * Invokes callback at start and on any change
72
29
  * Returns a function to stop monitoring
73
30
  */
74
- monitor(questionIds, callback) {
75
- const key = `monitor-${questionIds.join(',')}`;
76
- if (!this.monitors.has(key)) {
77
- this.monitors.set(key, []);
31
+ monitor(questionId, callback) {
32
+ if (!this.monitors.has(questionId)) {
33
+ this.monitors.set(questionId, []);
78
34
  }
79
- this.monitors.get(key).push(callback);
35
+ this.monitors.get(questionId).push(callback);
80
36
  // Invoke callback immediately with current state
81
- callback(Array.from(this.objects.values()));
37
+ callback(this.getQuestionMetadata());
82
38
  return () => {
83
- const callbacks = this.monitors.get(key);
39
+ const callbacks = this.monitors.get(questionId);
84
40
  if (callbacks) {
85
41
  const index = callbacks.indexOf(callback);
86
42
  if (index > -1) {
@@ -91,33 +47,21 @@ class DemoObjectStorage {
91
47
  }
92
48
  /**
93
49
  * Adds both metadata and data documents for a new object
94
- * Returns the generated object ID (nanoid) or the provided ID if specified in options
50
+ * Returns the StoredObject that was added
95
51
  */
96
- async add(object, options) {
97
- const id = options?.id ?? (0, nanoid_1.nanoid)();
98
- const objectWithId = {
99
- id,
100
- metadata: object.metadata,
101
- data: object.data
102
- };
103
- this.objects.set(id, objectWithId);
52
+ async add(object) {
53
+ const id = object.id;
54
+ this.objects.set(id, object);
104
55
  // Notify all monitors
105
56
  this.notifyMonitors();
106
- return id;
57
+ return object;
107
58
  }
108
59
  /**
109
60
  * Reads both metadata and data documents for an object
110
61
  * Returns undefined if the object is not found
111
62
  */
112
63
  async read(objectId) {
113
- const obj = this.objects.get(objectId);
114
- if (!obj) {
115
- return undefined;
116
- }
117
- return {
118
- metadata: obj.metadata,
119
- data: obj.data
120
- };
64
+ return this.objects.get(objectId);
121
65
  }
122
66
  /**
123
67
  * Reads only the metadata document for an object
@@ -142,16 +86,21 @@ class DemoObjectStorage {
142
86
  return obj.data;
143
87
  }
144
88
  /**
145
- * Generates a new unique ID using nanoid
89
+ * Reads a single data item for an object
90
+ * Returns undefined if the object or item is not found
146
91
  */
147
- genId() {
148
- return (0, nanoid_1.nanoid)();
92
+ async readDataItem(objectId, itemId) {
93
+ const obj = this.objects.get(objectId);
94
+ if (!obj) {
95
+ return undefined;
96
+ }
97
+ return obj.data[itemId];
149
98
  }
150
99
  /**
151
100
  * Notifies all active monitors of changes
152
101
  */
153
102
  notifyMonitors() {
154
- const allObjects = Array.from(this.objects.values());
103
+ const allObjects = this.getQuestionMetadata();
155
104
  this.monitors.forEach(callbacks => {
156
105
  callbacks.forEach(callback => callback(allObjects));
157
106
  });
@@ -1,6 +1,7 @@
1
1
  import "firebase/compat/auth";
2
2
  import "firebase/compat/firestore";
3
- import { FirebaseObjectStorageConfig, IObjectStorage, ObjectMetadata, ObjectData, StoredObject, ObjectWithId, MonitorCallback, DemonitorFunction, AddOptions } from './types';
3
+ import { FirebaseObjectStorageConfig, IObjectStorage, MonitorCallback, DemonitorFunction, StoredObjectMetadataWithId } from './types';
4
+ import { StoredObject, StoredObjectMetadata, StoredObjectData } from './stored-object';
4
5
  export declare class FirebaseObjectStorage implements IObjectStorage {
5
6
  private config;
6
7
  private initPromise;
@@ -12,41 +13,32 @@ export declare class FirebaseObjectStorage implements IObjectStorage {
12
13
  private createDocument;
13
14
  private getPaths;
14
15
  private getRefs;
16
+ private getItemDataPath;
17
+ private getMetadataQuery;
15
18
  /**
16
- * Lists metadata documents for objects owned by the current user
19
+ * Ensures the provided ID is in question ID format as opposed to reference ID format
20
+ *
21
+ * eg: "404-MWInteractive" transforms to "mw_interactive_404"
22
+ *
23
+ * @param questionOrRefId
24
+ * @returns
17
25
  */
18
- listMine(): Promise<ObjectWithId[]>;
19
- /**
20
- * Lists metadata documents for objects linked to the current user
21
- */
22
- listLinked(): Promise<ObjectWithId[]>;
26
+ private ensureIdIsQuestionId;
23
27
  /**
24
28
  * Lists metadata documents for objects associated with specific question IDs
25
29
  */
26
- list(questionIds: string[]): Promise<ObjectWithId[]>;
27
- /**
28
- * Monitors metadata documents for objects owned by the current user
29
- * Invokes callback at start and on any change
30
- * Returns a function to stop monitoring
31
- */
32
- monitorMine(callback: MonitorCallback): DemonitorFunction;
33
- /**
34
- * Monitors metadata documents for objects linked to the current user
35
- * Invokes callback at start and on any change
36
- * Returns a function to stop monitoring
37
- */
38
- monitorLinked(callback: MonitorCallback): DemonitorFunction;
30
+ list(questionOrRefId: string): Promise<StoredObjectMetadataWithId[]>;
39
31
  /**
40
32
  * Monitors metadata documents for objects associated with specific question IDs
41
33
  * Invokes callback at start and on any change
42
34
  * Returns a function to stop monitoring
43
35
  */
44
- monitor(questionIds: string[], callback: MonitorCallback): DemonitorFunction;
36
+ monitor(questionOrRefId: string, callback: MonitorCallback): DemonitorFunction;
45
37
  /**
46
38
  * Adds both metadata and data documents for a new object
47
- * Returns the generated object ID (nanoid) or the provided ID if specified in options
39
+ * Returns the StoredObject that was added
48
40
  */
49
- add(object: StoredObject, options?: AddOptions): Promise<string>;
41
+ add(object: StoredObject): Promise<StoredObject>;
50
42
  /**
51
43
  * Reads both metadata and data documents for an object
52
44
  */
@@ -54,13 +46,13 @@ export declare class FirebaseObjectStorage implements IObjectStorage {
54
46
  /**
55
47
  * Reads only the metadata document for an object
56
48
  */
57
- readMetadata(objectId: string): Promise<ObjectMetadata | undefined>;
49
+ readMetadata(objectId: string): Promise<StoredObjectMetadata | undefined>;
58
50
  /**
59
51
  * Reads only the data document for an object
60
52
  */
61
- readData(objectId: string): Promise<ObjectData | undefined>;
53
+ readData(objectId: string): Promise<StoredObjectData | undefined>;
62
54
  /**
63
- * Generates a new unique ID using nanoid
55
+ * Reads a single data item for an object
64
56
  */
65
- genId(): string;
57
+ readDataItem(objectId: string, itemId: string): Promise<any | undefined>;
66
58
  }
@@ -4,17 +4,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.FirebaseObjectStorage = void 0;
7
- const nanoid_1 = require("nanoid");
8
7
  const app_1 = __importDefault(require("firebase/compat/app"));
9
8
  require("firebase/compat/auth");
10
9
  require("firebase/compat/firestore");
10
+ const stored_object_1 = require("./stored-object");
11
11
  class FirebaseObjectStorage {
12
12
  constructor(config) {
13
13
  this.initialized = false;
14
14
  if (config.version !== 1) {
15
15
  throw new Error(`Unsupported config version: ${config.version}. Expected version 1.`);
16
16
  }
17
- this.config = config;
17
+ this.config = { ...config };
18
+ // ensure the question id is in the correct format
19
+ this.config.questionId = this.ensureIdIsQuestionId(this.config.questionId);
18
20
  this.app = app_1.default.initializeApp(this.config.app);
19
21
  this.app.firestore().settings({
20
22
  ignoreUndefinedProperties: true,
@@ -36,15 +38,17 @@ class FirebaseObjectStorage {
36
38
  await this.initPromise;
37
39
  }
38
40
  createDocument(contents) {
41
+ const { questionId } = this.config;
39
42
  if (this.config.user.type === "authenticated") {
40
43
  const { contextId, platformId, platformUserId, resourceLinkId } = this.config.user;
41
44
  return {
42
45
  created_at: app_1.default.firestore.FieldValue.serverTimestamp(),
43
- platform_id: platformId,
44
- platform_user_id: platformUserId.toString(),
45
46
  context_id: contextId,
47
+ platform_id: platformId,
48
+ platform_user_id: platformUserId,
46
49
  resource_link_id: resourceLinkId,
47
50
  run_key: "",
51
+ question_id: questionId,
48
52
  ...contents
49
53
  };
50
54
  }
@@ -54,13 +58,14 @@ class FirebaseObjectStorage {
54
58
  created_at: app_1.default.firestore.FieldValue.serverTimestamp(),
55
59
  run_key: runKey,
56
60
  platform_user_id: runKey,
61
+ question_id: questionId,
57
62
  ...contents
58
63
  };
59
64
  }
60
65
  }
61
66
  getPaths(objectId) {
62
- const metadataPath = `${this.config.root}/object_store_metadata/${objectId}`;
63
- const dataPath = `${this.config.root}/object_store_data/${objectId}`;
67
+ const metadataPath = `${this.config.root}/object_store_metadata${objectId ? `/${objectId}` : ''}`;
68
+ const dataPath = `${this.config.root}/object_store_data${objectId ? `/${objectId}` : ''}`;
64
69
  return { metadataPath, dataPath };
65
70
  }
66
71
  getRefs(objectId) {
@@ -69,119 +74,167 @@ class FirebaseObjectStorage {
69
74
  const dataRef = this.app.firestore().doc(dataPath);
70
75
  return { metadataRef, dataRef };
71
76
  }
72
- /**
73
- * Lists metadata documents for objects owned by the current user
74
- */
75
- async listMine() {
76
- await this.ensureInitialized();
77
- // TODO: Implement Firebase query for user's own objects
78
- return [];
77
+ getItemDataPath(objectId, itemId) {
78
+ return `${this.config.root}/object_store_data/${objectId}-${itemId}`;
79
+ }
80
+ getMetadataQuery(questionOrRefId) {
81
+ const questionId = this.ensureIdIsQuestionId(questionOrRefId);
82
+ const { metadataPath } = this.getPaths();
83
+ let query = this.app.firestore().collection(metadataPath)
84
+ .where("question_id", "==", questionId);
85
+ if (this.config.user.type === "authenticated") { // logged in user
86
+ const { contextId, platformId, resourceLinkId, platformUserId } = this.config.user;
87
+ query = query
88
+ .where("context_id", "==", contextId)
89
+ .where("platform_id", "==", platformId)
90
+ .where("platform_user_id", "==", platformUserId.toString())
91
+ .where("resource_link_id", "==", resourceLinkId);
92
+ }
93
+ else {
94
+ query = query.where("run_key", "==", this.config.user.runKey);
95
+ }
96
+ query = query.orderBy("created_at", "asc");
97
+ return query;
79
98
  }
80
99
  /**
81
- * Lists metadata documents for objects linked to the current user
100
+ * Ensures the provided ID is in question ID format as opposed to reference ID format
101
+ *
102
+ * eg: "404-MWInteractive" transforms to "mw_interactive_404"
103
+ *
104
+ * @param questionOrRefId
105
+ * @returns
82
106
  */
83
- async listLinked() {
84
- await this.ensureInitialized();
85
- // TODO: Implement Firebase query for linked objects
86
- return [];
107
+ ensureIdIsQuestionId(questionOrRefId) {
108
+ const refIdRegEx = /(\d*)-(\D*)/g;
109
+ const parsed = refIdRegEx.exec(questionOrRefId);
110
+ if (parsed?.length) {
111
+ const [, embeddableId, embeddableType] = parsed;
112
+ const snakeCased = embeddableType.replace(/(?!^)([A-Z])/g, "_$1").toLowerCase();
113
+ return `${snakeCased}_${embeddableId}`;
114
+ }
115
+ return questionOrRefId;
87
116
  }
88
117
  /**
89
118
  * Lists metadata documents for objects associated with specific question IDs
90
119
  */
91
- async list(questionIds) {
120
+ async list(questionOrRefId) {
92
121
  await this.ensureInitialized();
93
- // TODO: Implement Firebase query for objects by question IDs
94
- return [];
95
- }
96
- /**
97
- * Monitors metadata documents for objects owned by the current user
98
- * Invokes callback at start and on any change
99
- * Returns a function to stop monitoring
100
- */
101
- monitorMine(callback) {
102
- // await this.ensureInitialized();
103
- // TODO: Implement Firebase realtime listener for user's own objects
104
- callback([]);
105
- return () => {
106
- // TODO: Implement cleanup
107
- };
108
- }
109
- /**
110
- * Monitors metadata documents for objects linked to the current user
111
- * Invokes callback at start and on any change
112
- * Returns a function to stop monitoring
113
- */
114
- monitorLinked(callback) {
115
- // await this.ensureInitialized();
116
- // TODO: Implement Firebase realtime listener for linked objects
117
- callback([]);
118
- return () => {
119
- // TODO: Implement cleanup
120
- };
122
+ const query = this.getMetadataQuery(questionOrRefId);
123
+ const querySnapshot = await query.get();
124
+ const results = [];
125
+ querySnapshot.forEach(doc => {
126
+ results.push({ id: doc.id, metadata: doc.data().metadata });
127
+ });
128
+ return results;
121
129
  }
122
130
  /**
123
131
  * Monitors metadata documents for objects associated with specific question IDs
124
132
  * Invokes callback at start and on any change
125
133
  * Returns a function to stop monitoring
126
134
  */
127
- monitor(questionIds, callback) {
135
+ monitor(questionOrRefId, callback) {
128
136
  // await this.ensureInitialized();
129
- // TODO: Implement Firebase realtime listener for objects by question IDs
130
- callback([]);
137
+ const query = this.getMetadataQuery(questionOrRefId);
138
+ const unsub = query.onSnapshot(snapshot => {
139
+ const results = [];
140
+ snapshot.forEach(doc => {
141
+ results.push({ id: doc.id, metadata: doc.data().metadata });
142
+ });
143
+ callback(results);
144
+ });
131
145
  return () => {
132
- // TODO: Implement cleanup
146
+ unsub();
133
147
  };
134
148
  }
135
149
  /**
136
150
  * Adds both metadata and data documents for a new object
137
- * Returns the generated object ID (nanoid) or the provided ID if specified in options
151
+ * Returns the StoredObject that was added
138
152
  */
139
- async add(object, options) {
153
+ async add(object) {
140
154
  await this.ensureInitialized();
141
- const newObjectId = options?.id ?? (0, nanoid_1.nanoid)();
142
- const { data, metadata } = object;
143
- const { metadataRef, dataRef } = this.getRefs(newObjectId);
144
- const dataDoc = this.createDocument({ data });
155
+ const newObjectId = object.id;
156
+ const { metadata } = object;
157
+ const { metadataRef } = this.getRefs(newObjectId);
145
158
  const metadataDoc = this.createDocument({ metadata });
146
159
  const batch = this.app.firestore().batch();
147
- batch.set(dataRef, dataDoc);
148
160
  batch.set(metadataRef, metadataDoc);
161
+ // Write each item as a separate document
162
+ for (const itemId in object.data) {
163
+ const itemPath = this.getItemDataPath(newObjectId, itemId);
164
+ const itemRef = this.app.firestore().doc(itemPath);
165
+ const itemDoc = this.createDocument({
166
+ objectId: newObjectId,
167
+ itemId: itemId,
168
+ itemData: JSON.stringify(object.data[itemId])
169
+ });
170
+ batch.set(itemRef, itemDoc);
171
+ }
149
172
  await batch.commit();
150
- return newObjectId;
173
+ return object;
151
174
  }
152
175
  /**
153
176
  * Reads both metadata and data documents for an object
154
177
  */
155
178
  async read(objectId) {
156
179
  await this.ensureInitialized();
157
- // TODO: Read metadata document from Firebase
158
- // TODO: Read data document from Firebase
159
- return {
160
- metadata: {},
161
- data: {}
162
- };
180
+ const metadata = await this.readMetadata(objectId);
181
+ if (!metadata) {
182
+ return undefined;
183
+ }
184
+ const data = await this.readData(objectId);
185
+ if (!data) {
186
+ return undefined;
187
+ }
188
+ return stored_object_1.StoredObject.FromParts(objectId, metadata, data);
163
189
  }
164
190
  /**
165
191
  * Reads only the metadata document for an object
166
192
  */
167
193
  async readMetadata(objectId) {
168
194
  await this.ensureInitialized();
169
- // TODO: Read metadata document from Firebase
170
- return {};
195
+ const { metadataRef } = this.getRefs(objectId);
196
+ const metadataSnapshot = await metadataRef.get();
197
+ if (!metadataSnapshot.exists) {
198
+ return undefined;
199
+ }
200
+ return metadataSnapshot.data()?.metadata;
171
201
  }
172
202
  /**
173
203
  * Reads only the data document for an object
174
204
  */
175
205
  async readData(objectId) {
176
206
  await this.ensureInitialized();
177
- // TODO: Read data document from Firebase
178
- return {};
207
+ const dataPath = `${this.config.root}/object_store_data`;
208
+ const query = this.app.firestore().collection(dataPath)
209
+ .where("objectId", "==", objectId);
210
+ const querySnapshot = await query.get();
211
+ if (querySnapshot.empty) {
212
+ return undefined;
213
+ }
214
+ const data = {};
215
+ querySnapshot.forEach(doc => {
216
+ const docData = doc.data();
217
+ const itemId = docData.itemId;
218
+ const itemDataString = docData.itemData;
219
+ if (itemId && itemDataString) {
220
+ data[itemId] = JSON.parse(itemDataString);
221
+ }
222
+ });
223
+ return data;
179
224
  }
180
225
  /**
181
- * Generates a new unique ID using nanoid
226
+ * Reads a single data item for an object
182
227
  */
183
- genId() {
184
- return (0, nanoid_1.nanoid)();
228
+ async readDataItem(objectId, itemId) {
229
+ await this.ensureInitialized();
230
+ const itemPath = this.getItemDataPath(objectId, itemId);
231
+ const itemRef = this.app.firestore().doc(itemPath);
232
+ const itemSnapshot = await itemRef.get();
233
+ if (!itemSnapshot.exists) {
234
+ return undefined;
235
+ }
236
+ const itemDataString = itemSnapshot.data()?.itemData;
237
+ return itemDataString ? JSON.parse(itemDataString) : undefined;
185
238
  }
186
239
  }
187
240
  exports.FirebaseObjectStorage = FirebaseObjectStorage;
package/dist/index.d.ts CHANGED
@@ -2,5 +2,5 @@ export { DemoObjectStorage } from './demo-object-storage';
2
2
  export { FirebaseObjectStorage } from './firebase-object-storage';
3
3
  export { createObjectStorage } from './object-storage';
4
4
  export { ObjectStorageProvider, useObjectStorage } from './object-storage-context';
5
- export { IObjectStorage, ObjectStorageConfig, DemoObjectStorageConfig, FirebaseObjectStorageUser, FirebaseObjectStorageConfig, ObjectMetadata, ObjectData, StoredObject, ObjectWithId, MonitorCallback, DemonitorFunction } from './types';
6
- export * from './typed-object';
5
+ export { IObjectStorage, ObjectStorageConfig, DemoObjectStorageConfig, FirebaseObjectStorageUser, FirebaseObjectStorageConfig, StoredObjectWithId as ObjectWithId, MonitorCallback, DemonitorFunction } from './types';
6
+ export * from './stored-object';
package/dist/index.js CHANGED
@@ -24,4 +24,4 @@ Object.defineProperty(exports, "createObjectStorage", { enumerable: true, get: f
24
24
  var object_storage_context_1 = require("./object-storage-context");
25
25
  Object.defineProperty(exports, "ObjectStorageProvider", { enumerable: true, get: function () { return object_storage_context_1.ObjectStorageProvider; } });
26
26
  Object.defineProperty(exports, "useObjectStorage", { enumerable: true, get: function () { return object_storage_context_1.useObjectStorage; } });
27
- __exportStar(require("./typed-object"), exports);
27
+ __exportStar(require("./stored-object"), exports);
@@ -0,0 +1,74 @@
1
+ export type AddImageOptions = {
2
+ id?: string;
3
+ name: string;
4
+ url: string;
5
+ width?: number;
6
+ height?: number;
7
+ description?: string;
8
+ subType?: string;
9
+ };
10
+ export type AddDataTableOptions = {
11
+ id?: string;
12
+ name: string;
13
+ cols: string[];
14
+ rows: any[][];
15
+ description?: string;
16
+ subType?: string;
17
+ };
18
+ export type AddTextOptions = {
19
+ id?: string;
20
+ name: string;
21
+ text: string;
22
+ description?: string;
23
+ subType?: string;
24
+ };
25
+ export type AddStoredObjectOptions = {
26
+ id?: string;
27
+ name: string;
28
+ data: Record<string, any>;
29
+ description?: string;
30
+ subType?: string;
31
+ };
32
+ export type StoredImageMetadata = {
33
+ type: "image";
34
+ } & Omit<AddImageOptions, "id" | "url">;
35
+ export type StoredObjectDataTableMetadata = {
36
+ type: "dataTable";
37
+ } & Omit<AddDataTableOptions, "id" | "rows">;
38
+ export type StoredTextMetadata = {
39
+ type: "text";
40
+ } & Omit<AddTextOptions, "id" | "text">;
41
+ export type StoredObjectItemMetadata = {
42
+ type: "object";
43
+ keys: string[];
44
+ } & Omit<AddStoredObjectOptions, "id" | "data">;
45
+ export type StoredMetadataItem = StoredImageMetadata | StoredObjectDataTableMetadata | StoredTextMetadata | StoredObjectItemMetadata;
46
+ export type StoredMetadataItems = Record<string, StoredMetadataItem>;
47
+ export type StoredObjectType = "simulation-recording" | "untyped";
48
+ export type StoredObjectMetadata = {
49
+ version: 1;
50
+ type: StoredObjectType;
51
+ subType?: string;
52
+ items: StoredMetadataItems;
53
+ name?: string;
54
+ description?: string;
55
+ };
56
+ export type StoredObjectData = Record<string, any>;
57
+ export type StoredObjectOptions = {
58
+ id?: string;
59
+ name?: string;
60
+ description?: string;
61
+ type?: StoredObjectType;
62
+ subType?: string;
63
+ };
64
+ export declare class StoredObject {
65
+ id: string;
66
+ metadata: StoredObjectMetadata;
67
+ data: StoredObjectData;
68
+ constructor(options?: StoredObjectOptions);
69
+ static FromParts(id: string, metadata: StoredObjectMetadata, data: StoredObjectData): StoredObject;
70
+ addImage(options: AddImageOptions): void;
71
+ addDataTable(options: AddDataTableOptions): void;
72
+ addText(options: AddTextOptions): void;
73
+ addObject(options: AddStoredObjectOptions): void;
74
+ }
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StoredObject = void 0;
4
+ const nanoid_1 = require("nanoid");
5
+ class StoredObject {
6
+ constructor(options) {
7
+ this.id = options?.id || (0, nanoid_1.nanoid)();
8
+ this.metadata = {
9
+ version: 1,
10
+ type: options?.type || "untyped",
11
+ items: {},
12
+ };
13
+ if (options?.name !== undefined) {
14
+ this.metadata.name = options.name;
15
+ }
16
+ if (options?.description !== undefined) {
17
+ this.metadata.description = options.description;
18
+ }
19
+ if (options?.subType !== undefined) {
20
+ this.metadata.subType = options.subType;
21
+ }
22
+ this.data = {};
23
+ }
24
+ static FromParts(id, metadata, data) {
25
+ const typedObject = new StoredObject({ id });
26
+ typedObject.metadata = metadata;
27
+ typedObject.data = data;
28
+ return typedObject;
29
+ }
30
+ addImage(options) {
31
+ const id = options.id || (0, nanoid_1.nanoid)();
32
+ this.metadata.items[id] = {
33
+ type: "image",
34
+ name: options.name
35
+ };
36
+ if (options.subType !== undefined) {
37
+ this.metadata.items[id].subType = options.subType;
38
+ }
39
+ if (options.width !== undefined) {
40
+ this.metadata.items[id].width = options.width;
41
+ }
42
+ if (options.height !== undefined) {
43
+ this.metadata.items[id].height = options.height;
44
+ }
45
+ if (options.description !== undefined) {
46
+ this.metadata.items[id].description = options.description;
47
+ }
48
+ this.data[id] = {
49
+ url: options.url
50
+ };
51
+ }
52
+ addDataTable(options) {
53
+ const id = options.id || (0, nanoid_1.nanoid)();
54
+ this.metadata.items[id] = {
55
+ type: "dataTable",
56
+ name: options.name,
57
+ cols: options.cols,
58
+ };
59
+ if (options.subType !== undefined) {
60
+ this.metadata.items[id].subType = options.subType;
61
+ }
62
+ if (options.description !== undefined) {
63
+ this.metadata.items[id].description = options.description;
64
+ }
65
+ this.data[id] = {
66
+ rows: options.rows
67
+ };
68
+ }
69
+ addText(options) {
70
+ const id = options.id || (0, nanoid_1.nanoid)();
71
+ this.metadata.items[id] = {
72
+ type: "text",
73
+ name: options.name,
74
+ };
75
+ if (options.subType !== undefined) {
76
+ this.metadata.items[id].subType = options.subType;
77
+ }
78
+ if (options.description !== undefined) {
79
+ this.metadata.items[id].description = options.description;
80
+ }
81
+ this.data[id] = {
82
+ text: options.text
83
+ };
84
+ }
85
+ addObject(options) {
86
+ const id = options.id || (0, nanoid_1.nanoid)();
87
+ this.metadata.items[id] = {
88
+ type: "object",
89
+ name: options.name,
90
+ keys: Object.keys(options.data),
91
+ };
92
+ if (options.subType !== undefined) {
93
+ this.metadata.items[id].subType = options.subType;
94
+ }
95
+ if (options.description !== undefined) {
96
+ this.metadata.items[id].description = options.description;
97
+ }
98
+ this.data[id] = options.data;
99
+ }
100
+ }
101
+ exports.StoredObject = StoredObject;
@@ -64,7 +64,7 @@ export declare class TypedObject implements StoredObject {
64
64
  data: TypedData;
65
65
  constructor(options?: TypedObjectOptions);
66
66
  static IsSupportedTypedObject(storedObject: StoredObject): boolean;
67
- static IsSupportedTypedObjectMetadata(storedObjectMetadata?: StoredObject["metadata"]): boolean;
67
+ static IsSupportedTypedObjectMetadata(storedObjectMetadata?: StoredObject["metadata"]): storedObjectMetadata is TypedMetadata;
68
68
  static FromStoredObject(id: string, storedObject: StoredObject): TypedObject;
69
69
  addImage(options: AddImageOptions): void;
70
70
  addDataTable(options: AddDataTableOptions): void;
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { StoredObject, StoredObjectMetadata, StoredObjectData } from './stored-object';
1
2
  export interface DemoObjectStorageConfig {
2
3
  version: 1;
3
4
  type: "demo";
@@ -7,8 +8,8 @@ export interface AuthenticatedUser {
7
8
  jwt: string;
8
9
  contextId: string;
9
10
  platformId: string;
10
- platformUserId: string;
11
11
  resourceLinkId: string;
12
+ platformUserId: string;
12
13
  }
13
14
  export interface AnonymousUser {
14
15
  type: "anonymous";
@@ -18,39 +19,33 @@ export type FirebaseObjectStorageUser = AuthenticatedUser | AnonymousUser;
18
19
  export interface FirebaseObjectStorageConfig {
19
20
  version: 1;
20
21
  type: "firebase";
21
- app: any;
22
+ app: Object;
22
23
  root: string;
23
24
  user: FirebaseObjectStorageUser;
25
+ questionId: string;
24
26
  }
25
27
  export type ObjectStorageConfig = DemoObjectStorageConfig | FirebaseObjectStorageConfig;
26
- export interface AddOptions {
27
- id?: string;
28
- }
29
28
  export interface IObjectStorage {
30
- listMine(): Promise<ObjectWithId[]>;
31
- listLinked(): Promise<ObjectWithId[]>;
32
- list(questionIds: string[]): Promise<ObjectWithId[]>;
33
- monitorMine(callback: MonitorCallback): DemonitorFunction;
34
- monitorLinked(callback: MonitorCallback): DemonitorFunction;
35
- monitor(questionIds: string[], callback: MonitorCallback): DemonitorFunction;
36
- add(object: StoredObject, options?: AddOptions): Promise<string>;
29
+ list(questionOrRefId: string): Promise<StoredObjectMetadataWithId[]>;
30
+ monitor(questionOrRefId: string, callback: MonitorCallback): DemonitorFunction;
31
+ add(object: StoredObject): Promise<StoredObject>;
37
32
  read(objectId: string): Promise<StoredObject | undefined>;
38
- readMetadata(objectId: string): Promise<ObjectMetadata | undefined>;
39
- readData(objectId: string): Promise<ObjectData | undefined>;
40
- genId(): string;
33
+ readMetadata(objectId: string): Promise<StoredObjectMetadata | undefined>;
34
+ readData(objectId: string): Promise<StoredObjectData | undefined>;
35
+ readDataItem(objectId: string, itemId: string): Promise<any | undefined>;
41
36
  }
42
- export interface ObjectMetadata {
43
- [key: string]: any;
44
- }
45
- export interface ObjectData {
46
- [key: string]: any;
37
+ export interface StoredObjectMetadataWithId {
38
+ id: string;
39
+ metadata: StoredObjectMetadata;
47
40
  }
48
- export interface StoredObject {
49
- metadata: ObjectMetadata;
50
- data: ObjectData;
41
+ export interface StoredObjectDataWithId {
42
+ id: string;
43
+ data: StoredObjectData;
51
44
  }
52
- export interface ObjectWithId extends StoredObject {
45
+ export interface StoredObjectWithId {
53
46
  id: string;
47
+ metadata: StoredObjectMetadata;
48
+ data: StoredObjectData;
54
49
  }
55
- export type MonitorCallback = (objects: ObjectWithId[]) => void;
50
+ export type MonitorCallback = (objects: StoredObjectMetadataWithId[]) => void;
56
51
  export type DemonitorFunction = () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@concord-consortium/object-storage",
3
- "version": "1.0.0-pre.5",
3
+ "version": "1.0.0-pre.7",
4
4
  "description": "A TypeScript library for object storage",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",