@speakableio/core 0.1.0 → 0.1.2
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/dist/index.d.mts +255 -20
- package/dist/index.d.ts +255 -20
- package/dist/index.js +285 -27
- package/dist/index.mjs +284 -26
- package/package.json +21 -11
package/dist/index.mjs
CHANGED
|
@@ -1,36 +1,290 @@
|
|
|
1
1
|
// src/providers/SpeakableProvider.tsx
|
|
2
2
|
import { createContext, useContext, useEffect, useState } from "react";
|
|
3
3
|
|
|
4
|
+
// src/utils/error-handler.ts
|
|
5
|
+
var ServiceError = class extends Error {
|
|
6
|
+
constructor(message, originalError, code) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.originalError = originalError;
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.name = "ServiceError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
function withErrorHandler(fn, serviceName) {
|
|
14
|
+
return async (...args) => {
|
|
15
|
+
try {
|
|
16
|
+
return await fn(...args);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error instanceof Error && "code" in error) {
|
|
19
|
+
const firebaseError = error;
|
|
20
|
+
throw new ServiceError(
|
|
21
|
+
`Error in ${serviceName}: ${firebaseError.message}`,
|
|
22
|
+
error,
|
|
23
|
+
firebaseError.code
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
if (error instanceof Error) {
|
|
27
|
+
throw new ServiceError(`Error in ${serviceName}: ${error.message}`, error);
|
|
28
|
+
}
|
|
29
|
+
throw new ServiceError(`Unknown error in ${serviceName}`, error);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/lib/firebase/api.ts
|
|
35
|
+
var FirebaseAPI = class _FirebaseAPI {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
37
|
+
constructor() {
|
|
38
|
+
this.config = null;
|
|
39
|
+
}
|
|
40
|
+
static getInstance() {
|
|
41
|
+
if (!_FirebaseAPI.instance) {
|
|
42
|
+
_FirebaseAPI.instance = new _FirebaseAPI();
|
|
43
|
+
}
|
|
44
|
+
return _FirebaseAPI.instance;
|
|
45
|
+
}
|
|
46
|
+
initialize(config) {
|
|
47
|
+
this.config = config;
|
|
48
|
+
}
|
|
49
|
+
get db() {
|
|
50
|
+
if (!this.config) throw new Error("FirebaseAPI not initialized");
|
|
51
|
+
return this.config.db;
|
|
52
|
+
}
|
|
53
|
+
get helpers() {
|
|
54
|
+
if (!this.config) throw new Error("FirebaseAPI not initialized");
|
|
55
|
+
return this.config.helpers;
|
|
56
|
+
}
|
|
57
|
+
accessQueryConstraints() {
|
|
58
|
+
const { query, orderBy, limit, startAt, startAfter, endAt, endBefore } = this.helpers;
|
|
59
|
+
return {
|
|
60
|
+
query,
|
|
61
|
+
orderBy,
|
|
62
|
+
limit,
|
|
63
|
+
startAt,
|
|
64
|
+
startAfter,
|
|
65
|
+
endAt,
|
|
66
|
+
endBefore
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async getDoc(path) {
|
|
70
|
+
const { getDoc, doc } = this.helpers;
|
|
71
|
+
const docRef = doc(this.db, path);
|
|
72
|
+
const docSnap = await getDoc(docRef);
|
|
73
|
+
return {
|
|
74
|
+
id: docSnap.id,
|
|
75
|
+
data: docSnap.exists() ? docSnap.data() : null
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async getDocs(path, ...queryConstraints) {
|
|
79
|
+
const { getDocs, query, collection } = this.helpers;
|
|
80
|
+
const collectionRef = collection(this.db, path);
|
|
81
|
+
const q = queryConstraints.length > 0 ? query(collectionRef, ...queryConstraints) : collectionRef;
|
|
82
|
+
const querySnapshot = await getDocs(q);
|
|
83
|
+
const data = querySnapshot.docs.map((doc) => ({
|
|
84
|
+
id: doc.id,
|
|
85
|
+
data: doc.data()
|
|
86
|
+
}));
|
|
87
|
+
return {
|
|
88
|
+
data,
|
|
89
|
+
querySnapshot
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async addDoc(path, data) {
|
|
93
|
+
const { addDoc, collection } = this.helpers;
|
|
94
|
+
const collectionRef = collection(this.db, path);
|
|
95
|
+
const docRef = await addDoc(collectionRef, data);
|
|
96
|
+
return docRef.id;
|
|
97
|
+
}
|
|
98
|
+
async setDoc(path, data) {
|
|
99
|
+
const { setDoc, doc } = this.helpers;
|
|
100
|
+
const docRef = doc(this.db, path);
|
|
101
|
+
await setDoc(docRef, data);
|
|
102
|
+
}
|
|
103
|
+
async updateDoc(path, data) {
|
|
104
|
+
const { updateDoc, doc } = this.helpers;
|
|
105
|
+
const docRef = doc(this.db, path);
|
|
106
|
+
await updateDoc(docRef, data);
|
|
107
|
+
}
|
|
108
|
+
async deleteDoc(path) {
|
|
109
|
+
const { deleteDoc, doc } = this.helpers;
|
|
110
|
+
const docRef = doc(this.db, path);
|
|
111
|
+
await deleteDoc(docRef);
|
|
112
|
+
}
|
|
113
|
+
async runTransaction(updateFunction) {
|
|
114
|
+
const { runTransaction } = this.helpers;
|
|
115
|
+
return runTransaction(this.db, updateFunction);
|
|
116
|
+
}
|
|
117
|
+
async runBatch(operations) {
|
|
118
|
+
const { writeBatch } = this.helpers;
|
|
119
|
+
const batch = writeBatch(this.db);
|
|
120
|
+
await Promise.all(operations.map((op) => op()));
|
|
121
|
+
await batch.commit();
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var api = FirebaseAPI.getInstance();
|
|
125
|
+
|
|
126
|
+
// src/domains/assignment/assignment.constants.ts
|
|
127
|
+
var ASSIGNMENT_ANALYTICS_TYPES = [
|
|
128
|
+
"macro" /* Macro */,
|
|
129
|
+
"gradebook" /* Gradebook */,
|
|
130
|
+
"cards" /* Cards */,
|
|
131
|
+
"student" /* Student */,
|
|
132
|
+
"student_summary" /* StudentSummary */
|
|
133
|
+
];
|
|
134
|
+
var ASSIGNMENTS_COLLECTION = "assignments";
|
|
135
|
+
var ANALYTICS_SUBCOLLECTION = "analytics";
|
|
136
|
+
var SCORES_SUBCOLLECTION = "scores";
|
|
137
|
+
var refsAssignmentFiresotre = {
|
|
138
|
+
allAssignments: () => ASSIGNMENTS_COLLECTION,
|
|
139
|
+
assignment: (id) => `${ASSIGNMENTS_COLLECTION}/${id}`,
|
|
140
|
+
assignmentAllAnalytics: (id) => `${ASSIGNMENTS_COLLECTION}/${id}/${ANALYTICS_SUBCOLLECTION}`,
|
|
141
|
+
assignmentAnalytics: (id, type) => `${ASSIGNMENTS_COLLECTION}/${id}/${ANALYTICS_SUBCOLLECTION}/${type}`,
|
|
142
|
+
assignmentScores: (id, userId) => `${ASSIGNMENTS_COLLECTION}/${id}/${SCORES_SUBCOLLECTION}/${userId}`
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// src/domains/assignment/services/get-assignments-score.service.ts
|
|
146
|
+
var _getAssignmentScores = async ({
|
|
147
|
+
assignmentId,
|
|
148
|
+
analyticType = "macro" /* Macro */,
|
|
149
|
+
studentId,
|
|
150
|
+
currentUserId
|
|
151
|
+
}) => {
|
|
152
|
+
if (analyticType === "student" /* Student */) {
|
|
153
|
+
const path = refsAssignmentFiresotre.assignmentScores(assignmentId, currentUserId);
|
|
154
|
+
const response = await api.getDoc(path);
|
|
155
|
+
return { scores: response.data, id: assignmentId };
|
|
156
|
+
}
|
|
157
|
+
if (analyticType === "student_summary" /* StudentSummary */ && studentId) {
|
|
158
|
+
const path = refsAssignmentFiresotre.assignmentScores(assignmentId, studentId);
|
|
159
|
+
const response = await api.getDoc(path);
|
|
160
|
+
return { scores: response.data, id: assignmentId };
|
|
161
|
+
}
|
|
162
|
+
if (analyticType !== "all" /* All */ && ASSIGNMENT_ANALYTICS_TYPES.includes(analyticType)) {
|
|
163
|
+
const ref = refsAssignmentFiresotre.assignmentAnalytics(assignmentId, analyticType);
|
|
164
|
+
const docData = await api.getDoc(ref);
|
|
165
|
+
return { scores: docData.data, id: assignmentId };
|
|
166
|
+
} else if (analyticType === "all" /* All */) {
|
|
167
|
+
const ref = refsAssignmentFiresotre.assignmentAllAnalytics(assignmentId);
|
|
168
|
+
const response = await api.getDocs(ref);
|
|
169
|
+
const data = response.data.reduce((acc, curr) => {
|
|
170
|
+
acc[curr.id] = curr;
|
|
171
|
+
return acc;
|
|
172
|
+
}, {});
|
|
173
|
+
return { scores: data, id: assignmentId };
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
var getAssignmentScores = withErrorHandler(_getAssignmentScores, "getAssignmentScores");
|
|
177
|
+
|
|
178
|
+
// src/domains/assignment/services/attach-score-assignment.service.ts
|
|
179
|
+
var _attachScoresAssignment = async ({
|
|
180
|
+
assignments,
|
|
181
|
+
analyticType,
|
|
182
|
+
studentId,
|
|
183
|
+
currentUserId
|
|
184
|
+
}) => {
|
|
185
|
+
const scoresPromises = assignments.map((a) => {
|
|
186
|
+
return getAssignmentScores({
|
|
187
|
+
assignmentId: a.id,
|
|
188
|
+
analyticType,
|
|
189
|
+
studentId,
|
|
190
|
+
currentUserId
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
const scores = await Promise.all(scoresPromises);
|
|
194
|
+
const scoresObject = scores.reduce((acc, curr) => {
|
|
195
|
+
acc[curr.id] = curr.scores;
|
|
196
|
+
return acc;
|
|
197
|
+
}, {});
|
|
198
|
+
const assignmentsWithScores = assignments.map((a) => {
|
|
199
|
+
return {
|
|
200
|
+
...a,
|
|
201
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/ban-ts-comment
|
|
202
|
+
// @ts-ignore
|
|
203
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
204
|
+
scores: scoresObject[a.id] ?? null
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
return assignmentsWithScores;
|
|
208
|
+
};
|
|
209
|
+
var attachScoresAssignment = withErrorHandler(
|
|
210
|
+
_attachScoresAssignment,
|
|
211
|
+
"attachScoresAssignment"
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// src/domains/assignment/services/get-all-assignment.service.ts
|
|
215
|
+
async function _getAllAssignments() {
|
|
216
|
+
const path = refsAssignmentFiresotre.allAssignments();
|
|
217
|
+
const response = await api.getDocs(path);
|
|
218
|
+
return response.data;
|
|
219
|
+
}
|
|
220
|
+
var getAllAssignments = withErrorHandler(_getAllAssignments, "getAllAssignments");
|
|
221
|
+
|
|
222
|
+
// src/domains/assignment/utils/check-assignment-availability.ts
|
|
223
|
+
import dayjs from "dayjs";
|
|
224
|
+
var checkAssignmentAvailability = (scheduledTime) => {
|
|
225
|
+
if (!scheduledTime) return true;
|
|
226
|
+
const scheduledDate = typeof scheduledTime === "string" ? dayjs(scheduledTime) : dayjs(scheduledTime.toDate());
|
|
227
|
+
if (!scheduledDate.isValid()) return true;
|
|
228
|
+
return dayjs().isAfter(scheduledDate);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// src/domains/assignment/services/get-assignment.service.ts
|
|
232
|
+
async function _getAssignment(params) {
|
|
233
|
+
const path = refsAssignmentFiresotre.assignment(params.assignmentId);
|
|
234
|
+
const response = await api.getDoc(path);
|
|
235
|
+
if (!response.data) return null;
|
|
236
|
+
const assignment = response.data;
|
|
237
|
+
const isAvailable = checkAssignmentAvailability(assignment.scheduledTime);
|
|
238
|
+
const assignmentWithId = {
|
|
239
|
+
...assignment,
|
|
240
|
+
isAvailable,
|
|
241
|
+
id: params.assignmentId,
|
|
242
|
+
scheduledTime: assignment.scheduledTime ?? null
|
|
243
|
+
};
|
|
244
|
+
if (params.analyticType) {
|
|
245
|
+
const assignmentsWithScores = await attachScoresAssignment({
|
|
246
|
+
assignments: [assignmentWithId],
|
|
247
|
+
analyticType: params.analyticType,
|
|
248
|
+
currentUserId: params.currentUserId
|
|
249
|
+
});
|
|
250
|
+
return assignmentsWithScores.length > 0 ? assignmentsWithScores[0] : assignmentWithId;
|
|
251
|
+
}
|
|
252
|
+
return assignmentWithId;
|
|
253
|
+
}
|
|
254
|
+
var getAssignment = withErrorHandler(_getAssignment, "getAssignment");
|
|
255
|
+
|
|
4
256
|
// src/domains/assignment/assignment.repo.ts
|
|
5
|
-
var createAssignmentRepo = (
|
|
6
|
-
const { doc, collection, getDoc, getDocs } = h;
|
|
257
|
+
var createAssignmentRepo = () => {
|
|
7
258
|
return {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return { id, ...data };
|
|
13
|
-
},
|
|
14
|
-
async list() {
|
|
15
|
-
const col = collection(db, "assignments");
|
|
16
|
-
const snap = await getDocs(col);
|
|
17
|
-
return snap.docs.map((d) => ({ id: d.id, ...d.data() }));
|
|
18
|
-
}
|
|
259
|
+
getAssignment,
|
|
260
|
+
attachScoresAssignment,
|
|
261
|
+
getAssignmentScores,
|
|
262
|
+
getAllAssignments
|
|
19
263
|
};
|
|
20
264
|
};
|
|
21
265
|
|
|
22
|
-
// src/domains/assignment/hooks.ts
|
|
266
|
+
// src/domains/assignment/assignment.hooks.ts
|
|
23
267
|
import { useQuery } from "@tanstack/react-query";
|
|
24
268
|
var assignmentQueryKeys = {
|
|
25
269
|
all: ["assignments"],
|
|
26
270
|
byId: (id) => [...assignmentQueryKeys.all, id],
|
|
27
271
|
list: () => [...assignmentQueryKeys.all, "list"]
|
|
28
272
|
};
|
|
29
|
-
function useAssignment(
|
|
30
|
-
|
|
273
|
+
function useAssignment({
|
|
274
|
+
assignmentId,
|
|
275
|
+
enabled = true,
|
|
276
|
+
analyticType,
|
|
277
|
+
userId
|
|
278
|
+
}) {
|
|
279
|
+
const { speakableApi } = useSpeakableApi();
|
|
31
280
|
return useQuery({
|
|
32
|
-
queryKey: assignmentQueryKeys.byId(
|
|
33
|
-
queryFn: () =>
|
|
281
|
+
queryKey: assignmentQueryKeys.byId(assignmentId),
|
|
282
|
+
queryFn: () => speakableApi.assignmentRepo.getAssignment({
|
|
283
|
+
assignmentId,
|
|
284
|
+
analyticType,
|
|
285
|
+
currentUserId: userId
|
|
286
|
+
}),
|
|
287
|
+
enabled
|
|
34
288
|
});
|
|
35
289
|
}
|
|
36
290
|
|
|
@@ -38,8 +292,12 @@ function useAssignment(id) {
|
|
|
38
292
|
async function createFsClient(db, platform) {
|
|
39
293
|
const dbAsFirestore = db;
|
|
40
294
|
const helpers = platform === "web" ? await import("firebase/firestore") : await import("@react-native-firebase/firestore");
|
|
295
|
+
api.initialize({
|
|
296
|
+
db: dbAsFirestore,
|
|
297
|
+
helpers
|
|
298
|
+
});
|
|
41
299
|
return {
|
|
42
|
-
assignmentRepo: createAssignmentRepo(
|
|
300
|
+
assignmentRepo: createAssignmentRepo()
|
|
43
301
|
};
|
|
44
302
|
}
|
|
45
303
|
|
|
@@ -52,27 +310,27 @@ function SpeakableProvider({
|
|
|
52
310
|
children,
|
|
53
311
|
queryClient
|
|
54
312
|
}) {
|
|
55
|
-
const [
|
|
313
|
+
const [speakableApi, setSpeakableApi] = useState(null);
|
|
56
314
|
useEffect(() => {
|
|
57
315
|
createFsClient(db, platform).then((repos) => {
|
|
58
|
-
|
|
316
|
+
setSpeakableApi(repos);
|
|
59
317
|
});
|
|
60
318
|
}, [db, platform]);
|
|
61
|
-
if (!
|
|
319
|
+
if (!speakableApi) return null;
|
|
62
320
|
return /* @__PURE__ */ jsx(
|
|
63
321
|
FsCtx.Provider,
|
|
64
322
|
{
|
|
65
323
|
value: {
|
|
66
|
-
|
|
324
|
+
speakableApi,
|
|
67
325
|
queryClient
|
|
68
326
|
},
|
|
69
327
|
children
|
|
70
328
|
}
|
|
71
329
|
);
|
|
72
330
|
}
|
|
73
|
-
function
|
|
331
|
+
function useSpeakableApi() {
|
|
74
332
|
const ctx = useContext(FsCtx);
|
|
75
|
-
if (!ctx) throw new Error("
|
|
333
|
+
if (!ctx) throw new Error("useSpeakableApi must be used within a SpeakableProvider");
|
|
76
334
|
return ctx;
|
|
77
335
|
}
|
|
78
336
|
export {
|
|
@@ -82,5 +340,5 @@ export {
|
|
|
82
340
|
createAssignmentRepo,
|
|
83
341
|
createFsClient,
|
|
84
342
|
useAssignment,
|
|
85
|
-
|
|
343
|
+
useSpeakableApi
|
|
86
344
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@speakableio/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/Speakable-io/speakable-core.git"
|
|
7
7
|
},
|
|
8
|
-
"
|
|
9
|
-
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsup src/index.ts --dts --format esm,cjs --splitting",
|
|
10
|
+
"dev": "tsup src/index.ts --watch --dts --format esm,cjs --out-dir dist",
|
|
11
|
+
"test": "vitest"
|
|
12
|
+
},
|
|
13
|
+
"react-native": "dist/index.js",
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"module": "dist/index.mjs",
|
|
10
16
|
"devDependencies": {
|
|
11
17
|
"react": "^18.2.0",
|
|
12
18
|
"@types/react": "^18",
|
|
@@ -36,22 +42,26 @@
|
|
|
36
42
|
"@tanstack/react-query": "^5.54.1"
|
|
37
43
|
},
|
|
38
44
|
"exports": {
|
|
39
|
-
".":
|
|
45
|
+
".": {
|
|
46
|
+
"react-native": "./dist/index.js",
|
|
47
|
+
"require": "./dist/index.js",
|
|
48
|
+
"import": "./dist/index.mjs"
|
|
49
|
+
}
|
|
40
50
|
},
|
|
41
51
|
"bugs": {
|
|
42
52
|
"url": "https://github.com/Speakable-io/speakable-core/issues"
|
|
43
53
|
},
|
|
44
54
|
"description": "Shared data layer for Speakable web & mobile",
|
|
45
|
-
"files": [
|
|
55
|
+
"files": [
|
|
56
|
+
"dist"
|
|
57
|
+
],
|
|
46
58
|
"homepage": "https://github.com/Speakable-io/speakable-core#readme",
|
|
47
59
|
"license": "UNLICENSED",
|
|
48
60
|
"publishConfig": {
|
|
49
61
|
"access": "restricted"
|
|
50
62
|
},
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
},
|
|
56
|
-
"types": "dist/index.d.ts"
|
|
63
|
+
"types": "dist/index.d.ts",
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"dayjs": "^1.11.13"
|
|
66
|
+
}
|
|
57
67
|
}
|