@dereekb/firebase 13.42.0 → 13.43.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.
- package/eslint/package.json +3 -3
- package/index.esm.js +5127 -3231
- package/package.json +5 -5
- package/src/lib/model/formspace/formspace.access.d.ts +139 -0
- package/src/lib/model/formspace/formspace.action.d.ts +34 -0
- package/src/lib/model/formspace/formspace.api.d.ts +216 -0
- package/src/lib/model/formspace/formspace.api.error.d.ts +71 -0
- package/src/lib/model/formspace/formspace.d.ts +390 -0
- package/src/lib/model/formspace/formspace.id.d.ts +60 -0
- package/src/lib/model/formspace/formspace.permission.d.ts +47 -0
- package/src/lib/model/formspace/formspace.processing.d.ts +86 -0
- package/src/lib/model/formspace/formspace.query.d.ts +110 -0
- package/src/lib/model/formspace/formspace.task.d.ts +75 -0
- package/src/lib/model/formspace/formspace.type.d.ts +267 -0
- package/src/lib/model/formspace/formspace.upload.d.ts +204 -0
- package/src/lib/model/formspace/formspace.util.d.ts +348 -0
- package/src/lib/model/formspace/index.d.ts +13 -0
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/storagefile/storagefile.api.d.ts +19 -0
- package/src/lib/model/storagefile/storagefile.file.d.ts +42 -2
- package/src/lib/model/storagefile/storagefile.upload.d.ts +30 -0
- package/test/package.json +6 -6
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { type ContentTypeMimeType, type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type FirebaseAuthOwnershipKey, type FirebaseAuthUserId } from '../../common/auth/auth';
|
|
3
|
+
import { type FirestoreModelKey } from '../../common/firestore/collection/collection';
|
|
4
|
+
import { type StorageFileGroupId } from '../storagefile/storagefile.id';
|
|
5
|
+
import { type FormSpace, type FormSpaceData, type FormSpaceFile } from './formspace';
|
|
6
|
+
import { type FormSpaceFileSlot, type FormSpaceKey, type FormSpaceType } from './formspace.id';
|
|
7
|
+
import { type FormSpaceFileSlotConfig, type FormSpaceTypeConfig } from './formspace.type';
|
|
8
|
+
/**
|
|
9
|
+
* @module formspace.util
|
|
10
|
+
*
|
|
11
|
+
* Pure helpers shared by the client and the server: the write templates for each lifecycle transition, and
|
|
12
|
+
* the upload predicate.
|
|
13
|
+
*
|
|
14
|
+
* {@link assertFormSpaceUploadAllowed} in particular is deliberately PURE and lives here rather than in
|
|
15
|
+
* `firebase-server`: the client pre-checks a file with it before asking for a signed URL, and the server's
|
|
16
|
+
* upload initializer enforces the very same function afterwards. One rule, two callers — a client-side copy
|
|
17
|
+
* that drifted would show the user an accept for a file the server then silently discards.
|
|
18
|
+
*
|
|
19
|
+
* A file's NAME is not one of the rules. It used to be: two files of one name in a slot resolved to the
|
|
20
|
+
* same destination object, so the second silently overwrote the first. The destination is now keyed by the
|
|
21
|
+
* space's `fi` index instead, so two files of one name are two objects, and the name is free to be
|
|
22
|
+
* whatever the user uploaded.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Returns the {@link StorageFileGroupId} that owns every file uploaded into a FormSpace.
|
|
26
|
+
*
|
|
27
|
+
* The group is keyed by the FormSpace's own model key, so the existing sync machinery creates it on the
|
|
28
|
+
* first upload and the existing zip / cleanup machinery applies with no FormSpace-specific code.
|
|
29
|
+
*
|
|
30
|
+
* @param formSpaceKey - The FormSpace's model key.
|
|
31
|
+
* @returns The group id.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const groupId = formSpaceStorageFileGroupId('fsp/abc123'); // 'fsp_abc123'
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function formSpaceStorageFileGroupId(formSpaceKey: FormSpaceKey): StorageFileGroupId;
|
|
39
|
+
/**
|
|
40
|
+
* Input for {@link resolveFormSpaceExpiresAt}.
|
|
41
|
+
*/
|
|
42
|
+
export interface ResolveFormSpaceExpiresAtInput {
|
|
43
|
+
readonly config: FormSpaceTypeConfig;
|
|
44
|
+
/**
|
|
45
|
+
* The instant the space is being created at. Defaults to now.
|
|
46
|
+
*/
|
|
47
|
+
readonly now?: Maybe<Date>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns the instant a newly created FormSpace of the given type expires at, or null when its type never
|
|
51
|
+
* expires.
|
|
52
|
+
*
|
|
53
|
+
* Null is meaningful rather than merely absent: it is what leaves `eat` unwritten, and an unwritten `eat`
|
|
54
|
+
* is what excludes the space from the sweep's inequality query.
|
|
55
|
+
*
|
|
56
|
+
* @param input - The type config and creation instant.
|
|
57
|
+
* @returns The expiration instant, or null when the type does not expire.
|
|
58
|
+
*
|
|
59
|
+
* @__NO_SIDE_EFFECTS__
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveFormSpaceExpiresAt(input: ResolveFormSpaceExpiresAtInput): Maybe<Date>;
|
|
62
|
+
/**
|
|
63
|
+
* Input for {@link formSpaceTemplate}.
|
|
64
|
+
*/
|
|
65
|
+
export interface FormSpaceTemplateInput<T extends FormSpaceData = FormSpaceData> {
|
|
66
|
+
readonly formSpaceType: FormSpaceType;
|
|
67
|
+
readonly uid: FirebaseAuthUserId;
|
|
68
|
+
readonly ownerKey?: Maybe<FirebaseAuthOwnershipKey>;
|
|
69
|
+
readonly targetModelKey?: Maybe<FirestoreModelKey>;
|
|
70
|
+
readonly displayName?: Maybe<string>;
|
|
71
|
+
readonly data?: Maybe<T>;
|
|
72
|
+
readonly expiresAt?: Maybe<Date>;
|
|
73
|
+
/**
|
|
74
|
+
* The creation instant. Defaults to now.
|
|
75
|
+
*/
|
|
76
|
+
readonly now?: Maybe<Date>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Builds the complete document template for a newly created FormSpace.
|
|
80
|
+
*
|
|
81
|
+
* @param input - The type, owner, and initial content of the space.
|
|
82
|
+
* @returns The FormSpace template.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* const template = formSpaceTemplate({ formSpaceType: 'demo_example', uid: 'user123' });
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* @__NO_SIDE_EFFECTS__
|
|
90
|
+
*/
|
|
91
|
+
export declare function formSpaceTemplate<T extends FormSpaceData = FormSpaceData>(input: FormSpaceTemplateInput<T>): FormSpace<T>;
|
|
92
|
+
/**
|
|
93
|
+
* Builds the update template that submits a FormSpace.
|
|
94
|
+
*
|
|
95
|
+
* Clearing `eat` is not tidiness: a submitted space that kept its expiration instant would still match the
|
|
96
|
+
* expiration sweep and be retired out from under the processing task.
|
|
97
|
+
*
|
|
98
|
+
* @param now - The submission instant. Defaults to now.
|
|
99
|
+
* @returns The update template.
|
|
100
|
+
*
|
|
101
|
+
* @__NO_SIDE_EFFECTS__
|
|
102
|
+
*/
|
|
103
|
+
export declare function submitFormSpaceTemplate(now?: Maybe<Date>): Partial<FormSpace>;
|
|
104
|
+
/**
|
|
105
|
+
* Builds the update template that expires a FormSpace.
|
|
106
|
+
*
|
|
107
|
+
* @param now - The expiration instant. Defaults to now.
|
|
108
|
+
* @returns The update template.
|
|
109
|
+
*
|
|
110
|
+
* @__NO_SIDE_EFFECTS__
|
|
111
|
+
*/
|
|
112
|
+
export declare function expireFormSpaceTemplate(now?: Maybe<Date>): Partial<FormSpace>;
|
|
113
|
+
/**
|
|
114
|
+
* Input for {@link isFormSpaceEditable}.
|
|
115
|
+
*/
|
|
116
|
+
export interface IsFormSpaceEditableInput {
|
|
117
|
+
readonly formSpace: Pick<FormSpace, 's' | 'sat' | 'eat'>;
|
|
118
|
+
/**
|
|
119
|
+
* The instant to judge against. Defaults to now.
|
|
120
|
+
*/
|
|
121
|
+
readonly now?: Maybe<Date>;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Returns true when a FormSpace may still be edited or uploaded into.
|
|
125
|
+
*
|
|
126
|
+
* Checks the expiration instant as well as the state, so a space whose sweep has not run yet is already
|
|
127
|
+
* closed. The sweep is what RETIRES the document; it is not what makes it un-editable.
|
|
128
|
+
*
|
|
129
|
+
* @param input - The space and the instant to judge against.
|
|
130
|
+
* @returns True when the space is editable.
|
|
131
|
+
*
|
|
132
|
+
* @__NO_SIDE_EFFECTS__
|
|
133
|
+
*/
|
|
134
|
+
export declare function isFormSpaceEditable(input: IsFormSpaceEditableInput): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Returns the slot config a type declares for the given slot, or null when it declares none.
|
|
137
|
+
*
|
|
138
|
+
* @param config - The type config.
|
|
139
|
+
* @param slot - The slot to look up.
|
|
140
|
+
* @returns The slot config, or null.
|
|
141
|
+
*
|
|
142
|
+
* @__NO_SIDE_EFFECTS__
|
|
143
|
+
*/
|
|
144
|
+
export declare function formSpaceFileSlotConfig(config: FormSpaceTypeConfig, slot: FormSpaceFileSlot): Maybe<FormSpaceFileSlotConfig>;
|
|
145
|
+
/**
|
|
146
|
+
* Returns the human-readable name of a slot, falling back to the slot key itself.
|
|
147
|
+
*
|
|
148
|
+
* The key is a reasonable fallback rather than a placeholder: a slot is named `resume` or `cover` precisely
|
|
149
|
+
* because that is what it holds, so a type that declared no `name` still reads as something.
|
|
150
|
+
*
|
|
151
|
+
* @param config - The type config.
|
|
152
|
+
* @param slot - The slot to name.
|
|
153
|
+
* @returns The slot's name.
|
|
154
|
+
*
|
|
155
|
+
* @__NO_SIDE_EFFECTS__
|
|
156
|
+
*/
|
|
157
|
+
export declare function formSpaceFileSlotName(config: FormSpaceTypeConfig, slot: FormSpaceFileSlot): string;
|
|
158
|
+
/**
|
|
159
|
+
* Returns how many files a slot may hold at once.
|
|
160
|
+
*
|
|
161
|
+
* @param slotConfig - The slot config, or null for an undeclared slot.
|
|
162
|
+
* @returns The slot's file capacity.
|
|
163
|
+
*
|
|
164
|
+
* @__NO_SIDE_EFFECTS__
|
|
165
|
+
*/
|
|
166
|
+
export declare function formSpaceSlotMaxFiles(slotConfig: Maybe<FormSpaceFileSlotConfig>): number;
|
|
167
|
+
/**
|
|
168
|
+
* Returns how many files a slot must hold before the space may be submitted.
|
|
169
|
+
*
|
|
170
|
+
* `required` is the older, coarser spelling of the same idea, so it resolves to 1 when `minFiles` is absent.
|
|
171
|
+
*
|
|
172
|
+
* @param slotConfig - The slot config, or null for an undeclared slot.
|
|
173
|
+
* @returns The slot's minimum file count.
|
|
174
|
+
*
|
|
175
|
+
* @__NO_SIDE_EFFECTS__
|
|
176
|
+
*/
|
|
177
|
+
export declare function formSpaceSlotMinFiles(slotConfig: Maybe<FormSpaceFileSlotConfig>): number;
|
|
178
|
+
/**
|
|
179
|
+
* Returns the files a FormSpace currently holds in one slot.
|
|
180
|
+
*
|
|
181
|
+
* @param formSpace - The space to read.
|
|
182
|
+
* @param slot - The slot to filter by.
|
|
183
|
+
* @returns The slot's files, in the order the space stores them.
|
|
184
|
+
*
|
|
185
|
+
* @__NO_SIDE_EFFECTS__
|
|
186
|
+
*/
|
|
187
|
+
export declare function formSpaceFilesInSlot(formSpace: Pick<FormSpace, 'f'>, slot: FormSpaceFileSlot): FormSpaceFile[];
|
|
188
|
+
/**
|
|
189
|
+
* Every slot a type requires be filled before its spaces may be submitted.
|
|
190
|
+
*
|
|
191
|
+
* A CLIENT-side convenience for labelling a form's required slots. The submit gate itself uses
|
|
192
|
+
* {@link formSpaceSubmitBlockers}, which also understands `minFiles` and validation state.
|
|
193
|
+
*
|
|
194
|
+
* @param config - The type config.
|
|
195
|
+
* @returns The required slots.
|
|
196
|
+
*
|
|
197
|
+
* @__NO_SIDE_EFFECTS__
|
|
198
|
+
*/
|
|
199
|
+
export declare function requiredFormSpaceFileSlots(config: FormSpaceTypeConfig): FormSpaceFileSlot[];
|
|
200
|
+
/**
|
|
201
|
+
* Why {@link assertFormSpaceUploadAllowed} rejected an upload.
|
|
202
|
+
*
|
|
203
|
+
* A discriminated reason rather than a bare false: the caller turns it into an error code, and a client
|
|
204
|
+
* pre-check turns it into a message the user can act on.
|
|
205
|
+
*/
|
|
206
|
+
export type FormSpaceUploadRejectionReason = 'not_editable' | 'unknown_slot' | 'max_uploads_reached' | 'slot_full' | 'invalid_mime_type' | 'file_too_large';
|
|
207
|
+
/**
|
|
208
|
+
* Result of {@link assertFormSpaceUploadAllowed}.
|
|
209
|
+
*/
|
|
210
|
+
export interface FormSpaceUploadAllowedResult {
|
|
211
|
+
readonly allowed: boolean;
|
|
212
|
+
readonly reason?: Maybe<FormSpaceUploadRejectionReason>;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Input for {@link assertFormSpaceUploadAllowed}.
|
|
216
|
+
*/
|
|
217
|
+
export interface AssertFormSpaceUploadAllowedInput {
|
|
218
|
+
readonly formSpace: Pick<FormSpace, 's' | 'sat' | 'eat' | 'uc' | 'f'>;
|
|
219
|
+
readonly config: FormSpaceTypeConfig;
|
|
220
|
+
readonly slot: FormSpaceFileSlot;
|
|
221
|
+
readonly mimeType: ContentTypeMimeType;
|
|
222
|
+
readonly sizeBytes: number;
|
|
223
|
+
/**
|
|
224
|
+
* The instant to judge editability against. Defaults to now.
|
|
225
|
+
*/
|
|
226
|
+
readonly now?: Maybe<Date>;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Decides whether one file may be uploaded into one slot of one FormSpace.
|
|
230
|
+
*
|
|
231
|
+
* THE single upload rule. The client calls it to pre-check before requesting a signed URL, and the server's
|
|
232
|
+
* upload initializer calls it again — authoritatively, after loading the space — before creating any
|
|
233
|
+
* StorageFile. The client call is a courtesy; only the server call is a control.
|
|
234
|
+
*
|
|
235
|
+
* @param input - The space, its type config, and the candidate file.
|
|
236
|
+
* @returns Whether the upload is allowed, and why not when it is not.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* const result = assertFormSpaceUploadAllowed({ formSpace, config, slot: 'resume', mimeType: 'application/pdf', sizeBytes: 4096 });
|
|
241
|
+
* ```
|
|
242
|
+
*
|
|
243
|
+
* @__NO_SIDE_EFFECTS__
|
|
244
|
+
*/
|
|
245
|
+
export declare function assertFormSpaceUploadAllowed(input: AssertFormSpaceUploadAllowedInput): FormSpaceUploadAllowedResult;
|
|
246
|
+
/**
|
|
247
|
+
* Why a FormSpace cannot be submitted yet.
|
|
248
|
+
*
|
|
249
|
+
* Per-slot rather than a bare list of slot names, because "you have not uploaded a second document" and "the
|
|
250
|
+
* document you uploaded was rejected" want different words in front of the user.
|
|
251
|
+
*/
|
|
252
|
+
export interface FormSpaceSubmitBlocker {
|
|
253
|
+
readonly slot: FormSpaceFileSlot;
|
|
254
|
+
/**
|
|
255
|
+
* `missing_files` — the slot holds fewer than its `minFiles`.
|
|
256
|
+
* `invalid_file` — the slot holds a file validation judged INVALID.
|
|
257
|
+
* `pending_validation` — the slot holds a file whose validation has not concluded.
|
|
258
|
+
*/
|
|
259
|
+
readonly reason: 'missing_files' | 'invalid_file' | 'pending_validation';
|
|
260
|
+
/**
|
|
261
|
+
* The offending files, for `invalid_file` and `pending_validation`.
|
|
262
|
+
*/
|
|
263
|
+
readonly files?: Maybe<FormSpaceFile[]>;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Returns every reason a FormSpace may not be submitted yet, or an empty array when it may.
|
|
267
|
+
*
|
|
268
|
+
* Reads the space's own `f` array rather than querying its StorageFiles. That array is written in the
|
|
269
|
+
* accept transaction, so unlike a query it is correct immediately after an upload — and unlike a query it
|
|
270
|
+
* can be read inside the transaction that takes the submit lock.
|
|
271
|
+
*
|
|
272
|
+
* @param formSpace - The space to check.
|
|
273
|
+
* @param config - Its type config.
|
|
274
|
+
* @returns The blockers, empty when the space may be submitted.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* const blockers = formSpaceSubmitBlockers(formSpace, config);
|
|
279
|
+
*
|
|
280
|
+
* if (blockers.length > 0) {
|
|
281
|
+
* throw formSpaceRequiredSlotMissingError(blockers.map((x) => x.slot));
|
|
282
|
+
* }
|
|
283
|
+
* ```
|
|
284
|
+
*
|
|
285
|
+
* @__NO_SIDE_EFFECTS__
|
|
286
|
+
*/
|
|
287
|
+
export declare function formSpaceSubmitBlockers(formSpace: Pick<FormSpace, 'f'>, config: FormSpaceTypeConfig): FormSpaceSubmitBlocker[];
|
|
288
|
+
/**
|
|
289
|
+
* What one slot of a FormSpace currently holds, and whether that satisfies the slot's own requirement.
|
|
290
|
+
*
|
|
291
|
+
* The per-slot view of {@link formSpaceSubmitBlockers}, for a UI that labels each slot individually rather
|
|
292
|
+
* than reporting one verdict for the whole space.
|
|
293
|
+
*/
|
|
294
|
+
export interface FormSpaceSlotStatus {
|
|
295
|
+
readonly slot: FormSpaceFileSlot;
|
|
296
|
+
/**
|
|
297
|
+
* The files the slot currently holds.
|
|
298
|
+
*/
|
|
299
|
+
readonly files: FormSpaceFile[];
|
|
300
|
+
readonly minFiles: number;
|
|
301
|
+
readonly maxFiles: number;
|
|
302
|
+
/**
|
|
303
|
+
* Whether the space cannot be submitted while this slot is empty, i.e. {@link minFiles} is above zero.
|
|
304
|
+
*/
|
|
305
|
+
readonly required: boolean;
|
|
306
|
+
/**
|
|
307
|
+
* Every reason this slot blocks submission. Empty when it does not.
|
|
308
|
+
*/
|
|
309
|
+
readonly blockers: FormSpaceSubmitBlocker[];
|
|
310
|
+
/**
|
|
311
|
+
* Whether this slot blocks submission. An OPTIONAL EMPTY slot is satisfied — it is holding up nothing.
|
|
312
|
+
*/
|
|
313
|
+
readonly satisfied: boolean;
|
|
314
|
+
/**
|
|
315
|
+
* Whether the slot is satisfied AND holds something.
|
|
316
|
+
*
|
|
317
|
+
* The distinction from {@link satisfied} is what an optional slot needs: an empty one blocks nothing, but
|
|
318
|
+
* marking it DONE claims the user dealt with it when they have not touched it. So this is the narrower
|
|
319
|
+
* predicate — "there is something here and it is fine" — and it is what a checkmark belongs next to.
|
|
320
|
+
*/
|
|
321
|
+
readonly complete: boolean;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Input for {@link formSpaceSlotStatus}.
|
|
325
|
+
*/
|
|
326
|
+
export interface FormSpaceSlotStatusInput {
|
|
327
|
+
readonly formSpace: Pick<FormSpace, 'f'>;
|
|
328
|
+
readonly config: FormSpaceTypeConfig;
|
|
329
|
+
readonly slot: FormSpaceFileSlot;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Returns what one slot holds and whether that satisfies the slot's requirement.
|
|
333
|
+
*
|
|
334
|
+
* Derived from {@link formSpaceSubmitBlockers} rather than re-deriving the rule, so a slot a UI marks done is
|
|
335
|
+
* exactly a slot the server's submit gate would not object to.
|
|
336
|
+
*
|
|
337
|
+
* @param input - The space, its type config, and the slot to report on.
|
|
338
|
+
* @returns The slot's status.
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```ts
|
|
342
|
+
* const status = formSpaceSlotStatus({ formSpace, config, slot: 'resume' });
|
|
343
|
+
* const showCheck = status.complete;
|
|
344
|
+
* ```
|
|
345
|
+
*
|
|
346
|
+
* @__NO_SIDE_EFFECTS__
|
|
347
|
+
*/
|
|
348
|
+
export declare function formSpaceSlotStatus(input: FormSpaceSlotStatusInput): FormSpaceSlotStatus;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './formspace.access';
|
|
2
|
+
export * from './formspace.action';
|
|
3
|
+
export * from './formspace.api.error';
|
|
4
|
+
export * from './formspace.api';
|
|
5
|
+
export * from './formspace.id';
|
|
6
|
+
export * from './formspace.permission';
|
|
7
|
+
export * from './formspace.processing';
|
|
8
|
+
export * from './formspace.query';
|
|
9
|
+
export * from './formspace.task';
|
|
10
|
+
export * from './formspace.type';
|
|
11
|
+
export * from './formspace.upload';
|
|
12
|
+
export * from './formspace.util';
|
|
13
|
+
export * from './formspace';
|
package/src/lib/model/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type Type } from 'arktype';
|
|
|
2
2
|
import { type TargetModelParams, type OnCallCreateModelResult, type FirestoreModelKey } from '../../common';
|
|
3
3
|
import { type ModelFirebaseCrudFunction, type FirebaseFunctionTypeConfigMap, type ModelFirebaseCrudFunctionConfigMap, type ModelFirebaseFunctionMap, type ModelFirebaseCreateFunction } from '../../client';
|
|
4
4
|
import { type StorageFileSignedDownloadUrl, type StorageFileTypes } from './storagefile';
|
|
5
|
+
import { type StorageFileUploadScope } from './storagefile.upload';
|
|
5
6
|
import { type StorageFileKey, type StorageFileId, type StorageFilePurpose } from './storagefile.id';
|
|
6
7
|
import { type StorageBucketId, type StorageMetadata, type StoragePath, type StorageSlashPath } from '../../common/storage';
|
|
7
8
|
import { type ContentDispositionString, type ContentTypeMimeType, type Maybe, type Milliseconds, type SlashPath, type SlashPathFile, type UnixDateTimeMillisecondsNumber, type UnixDateTimeSecondsNumber } from '@dereekb/util';
|
|
@@ -26,6 +27,14 @@ export interface InitializeAllStorageFilesFromUploadsParams {
|
|
|
26
27
|
readonly maxFilesToInitialize?: Maybe<number>;
|
|
27
28
|
readonly folderPath?: Maybe<StorageSlashPath>;
|
|
28
29
|
readonly overrideUploadsFolderPath?: Maybe<StorageSlashPath>;
|
|
30
|
+
/**
|
|
31
|
+
* Whether to expedite processing of each initialized file that ends up queued for it.
|
|
32
|
+
*
|
|
33
|
+
* The same option {@link InitializeStorageFileFromUploadParams} already offers for a single file. Without
|
|
34
|
+
* it a file initialized by this sweep waits for the next `processAllQueuedStorageFiles` pass, which is a
|
|
35
|
+
* whole scheduling tick of latency for a purpose whose processing the uploader is waiting on.
|
|
36
|
+
*/
|
|
37
|
+
readonly expediteProcessing?: Maybe<boolean>;
|
|
29
38
|
}
|
|
30
39
|
export declare const initializeAllStorageFilesFromUploadsParamsType: Type<InitializeAllStorageFilesFromUploadsParams>;
|
|
31
40
|
/**
|
|
@@ -343,7 +352,17 @@ export interface CreateStorageFileSignedUploadUrlParams {
|
|
|
343
352
|
* when omitted.
|
|
344
353
|
*/
|
|
345
354
|
readonly expiresInMs?: Maybe<Milliseconds>;
|
|
355
|
+
/**
|
|
356
|
+
* The model, and optionally the slot within it, this upload belongs to. Required when the resolved
|
|
357
|
+
* policy sets `requiresScopeInput: true` (e.g. a FormSpace upload, which is keyed by space and slot
|
|
358
|
+
* rather than by the uid alone).
|
|
359
|
+
*/
|
|
360
|
+
readonly scope?: Maybe<StorageFileUploadScope>;
|
|
346
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Arktype for a {@link StorageFileUploadScope}.
|
|
364
|
+
*/
|
|
365
|
+
export declare const storageFileUploadScopeType: Type<StorageFileUploadScope>;
|
|
347
366
|
export declare const createStorageFileSignedUploadUrlParamsType: Type<CreateStorageFileSignedUploadUrlParams>;
|
|
348
367
|
/**
|
|
349
368
|
* Result of creating a signed upload URL.
|
|
@@ -1,11 +1,51 @@
|
|
|
1
|
-
import { type Factory, type FactoryWithRequiredInput, type Maybe, type SlashPathDetails } from '@dereekb/util';
|
|
2
|
-
import { type StoragePath } from '../../common/storage/storage';
|
|
1
|
+
import { type ContentTypeMimeType, type Factory, type FactoryWithRequiredInput, type Maybe, type SlashPathDetails, type SlashPathFile } from '@dereekb/util';
|
|
2
|
+
import { type StoragePath, type StorageSlashPath } from '../../common/storage/storage';
|
|
3
|
+
import { type StorageFileDisplayName } from './storagefile.id';
|
|
3
4
|
import { type StorageCustomMetadata } from '../../common/storage/types';
|
|
4
5
|
import { type FirebaseStorageAccessorFile } from '../../common/storage/driver/accessor';
|
|
5
6
|
/**
|
|
6
7
|
* Input for a {@link StoredFileReader}, carrying the storage bucket and path of the file.
|
|
7
8
|
*/
|
|
8
9
|
export type StoredFileReaderInput = StoragePath;
|
|
10
|
+
/**
|
|
11
|
+
* Input for {@link storageFileDisplayFileName}.
|
|
12
|
+
*/
|
|
13
|
+
export interface StorageFileDisplayFileNameInput {
|
|
14
|
+
/**
|
|
15
|
+
* The StorageFile's own `n`, which is UNTYPED by contract (see {@link StorageFileDisplayName}).
|
|
16
|
+
*/
|
|
17
|
+
readonly displayName?: Maybe<StorageFileDisplayName>;
|
|
18
|
+
/**
|
|
19
|
+
* The object's path — a StorageFile's `pathString`, or the `name` off its storage metadata.
|
|
20
|
+
*/
|
|
21
|
+
readonly pathString?: Maybe<StorageSlashPath>;
|
|
22
|
+
/**
|
|
23
|
+
* The object's content type, used when the path names no extension.
|
|
24
|
+
*/
|
|
25
|
+
readonly contentType?: Maybe<ContentTypeMimeType>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Composes the name a stored file should be presented to a user under.
|
|
29
|
+
*
|
|
30
|
+
* THE one place that answers "what is this file called". A StorageFile's `n` is untyped by contract, so
|
|
31
|
+
* the extension always comes from the object's own path — which is why a purpose whose destination is not
|
|
32
|
+
* name-keyed (a FormSpace file lives at `.../{index}.{ext}`) still downloads and zips under the name its
|
|
33
|
+
* uploader gave it.
|
|
34
|
+
*
|
|
35
|
+
* Falls back to the path's own leaf when there is no display name, so a StorageFile that never had one
|
|
36
|
+
* behaves exactly as it did before.
|
|
37
|
+
*
|
|
38
|
+
* @param input - The display name, the object path, and the content type.
|
|
39
|
+
* @returns The composed file name, or null when the input names neither.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* storageFileDisplayFileName({ displayName: 'resume', pathString: '/fsp/f1/resume/0.pdf' }); // 'resume.pdf'
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @__NO_SIDE_EFFECTS__
|
|
47
|
+
*/
|
|
48
|
+
export declare function storageFileDisplayFileName(input: StorageFileDisplayFileNameInput): Maybe<SlashPathFile>;
|
|
9
49
|
/**
|
|
10
50
|
* Factory that creates a {@link StoredFileReader} from a {@link FirebaseStorageAccessorFile}.
|
|
11
51
|
*
|
|
@@ -105,6 +105,28 @@ export type StorageFileInitializeFromUploadResultType = 'success' | 'no_determin
|
|
|
105
105
|
export interface StorageFilePurposeUploadPolicyBuildPathInput {
|
|
106
106
|
readonly uid: FirebaseAuthUserId;
|
|
107
107
|
readonly filename?: Maybe<SlashPathFile>;
|
|
108
|
+
/**
|
|
109
|
+
* The model the upload is scoped to, for a purpose whose destination is not derivable from the uid alone.
|
|
110
|
+
*
|
|
111
|
+
* Required when the policy sets {@link StorageFilePurposeUploadPolicy.requiresScopeInput}.
|
|
112
|
+
*/
|
|
113
|
+
readonly scope?: Maybe<StorageFileUploadScope>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Names the specific model, and optionally the slot within it, that an upload belongs to.
|
|
117
|
+
*
|
|
118
|
+
* This is what lets ONE purpose serve many destinations: a FormSpace upload is `{ id: formSpaceId,
|
|
119
|
+
* subgroup: slot }` under a single `form_space` purpose, rather than a purpose per form type.
|
|
120
|
+
*/
|
|
121
|
+
export interface StorageFileUploadScope {
|
|
122
|
+
/**
|
|
123
|
+
* Id of the model the upload is scoped to.
|
|
124
|
+
*/
|
|
125
|
+
readonly id: string;
|
|
126
|
+
/**
|
|
127
|
+
* Slot/subgroup within the scoped model, when the model has more than one.
|
|
128
|
+
*/
|
|
129
|
+
readonly subgroup?: Maybe<string>;
|
|
108
130
|
}
|
|
109
131
|
/**
|
|
110
132
|
* Per-purpose constraints for generating short-lived signed upload URLs.
|
|
@@ -128,4 +150,12 @@ export interface StorageFilePurposeUploadPolicy {
|
|
|
128
150
|
* When false (e.g. avatar), the path is derived solely from the uid.
|
|
129
151
|
*/
|
|
130
152
|
readonly requiresFilenameInput: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* When true, the caller MUST provide a {@link StorageFileUploadScope} for `buildUploadPath`.
|
|
155
|
+
*
|
|
156
|
+
* Set by a purpose whose destination folder is keyed by a model rather than by the uid alone. The scope
|
|
157
|
+
* is only a PATH input — it is not itself authorization; the purpose's initializer is what loads the
|
|
158
|
+
* scoped model and decides whether this uploader may write into it.
|
|
159
|
+
*/
|
|
160
|
+
readonly requiresScopeInput?: boolean;
|
|
131
161
|
}
|
package/test/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/firebase/test",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.43.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"peerDependencies": {
|
|
6
|
-
"@dereekb/date": "13.
|
|
7
|
-
"@dereekb/firebase": "13.
|
|
8
|
-
"@dereekb/model": "13.
|
|
9
|
-
"@dereekb/rxjs": "13.
|
|
10
|
-
"@dereekb/util": "13.
|
|
6
|
+
"@dereekb/date": "13.43.0",
|
|
7
|
+
"@dereekb/firebase": "13.43.0",
|
|
8
|
+
"@dereekb/model": "13.43.0",
|
|
9
|
+
"@dereekb/rxjs": "13.43.0",
|
|
10
|
+
"@dereekb/util": "13.43.0",
|
|
11
11
|
"@firebase/rules-unit-testing": "5.0.0",
|
|
12
12
|
"date-fns": "^4.1.0",
|
|
13
13
|
"firebase": "^12.12.1",
|