@dereekb/firebase 13.41.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/index.esm.js +145 -170
- package/eslint/package.json +3 -3
- package/index.esm.js +7207 -2997
- package/package.json +5 -5
- package/src/lib/common/firestore/snapshot/snapshot.field.d.ts +4 -0
- package/src/lib/common/storage/context.d.ts +9 -1
- package/src/lib/common/storage/driver/accessor.d.ts +12 -0
- package/src/lib/common/storage/index.d.ts +1 -0
- package/src/lib/common/storage/storage.url.d.ts +69 -0
- package/src/lib/model/calendar/calendar.action.d.ts +34 -0
- package/src/lib/model/calendar/calendar.api.d.ts +147 -0
- package/src/lib/model/calendar/calendar.api.error.d.ts +24 -0
- package/src/lib/model/calendar/calendar.d.ts +460 -0
- package/src/lib/model/calendar/calendar.expand.d.ts +95 -0
- package/src/lib/model/calendar/calendar.ics.d.ts +322 -0
- package/src/lib/model/calendar/calendar.id.d.ts +110 -0
- package/src/lib/model/calendar/calendar.processing.d.ts +77 -0
- package/src/lib/model/calendar/calendar.query.d.ts +76 -0
- package/src/lib/model/calendar/calendar.schedule.d.ts +82 -0
- package/src/lib/model/calendar/calendar.type.d.ts +188 -0
- package/src/lib/model/calendar/calendar.util.d.ts +485 -0
- package/src/lib/model/calendar/index.d.ts +12 -0
- 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 +2 -0
- package/src/lib/model/notification/notification.message.d.ts +83 -0
- package/src/lib/model/notification/notification.query.d.ts +41 -0
- package/src/lib/model/oidcmodel/oidcmodel.query.d.ts +36 -0
- package/src/lib/model/storagefile/storagefile.api.d.ts +42 -2
- package/src/lib/model/storagefile/storagefile.create.d.ts +14 -3
- package/src/lib/model/storagefile/storagefile.file.d.ts +42 -2
- package/src/lib/model/storagefile/storagefile.query.d.ts +36 -0
- package/src/lib/model/storagefile/storagefile.upload.d.ts +30 -0
- package/src/lib/model/system/index.d.ts +1 -0
- package/src/lib/model/system/system.scheduler.d.ts +235 -0
- package/test/index.esm.js +24 -1
- package/test/package.json +6 -6
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { type Maybe, type SlashPathFile } from '@dereekb/util';
|
|
2
|
+
import { type GrantedReadRole, type GrantedUpdateRole, type GrantedDeleteRole } from '@dereekb/model';
|
|
3
|
+
import { AbstractFirestoreDocument, type CollectionReference, type FirestoreCollection, type FirestoreContext, type FirebaseAuthOwnershipKey, type FirebaseAuthUserId, type FirestoreModelKey } from '../../common';
|
|
4
|
+
import { type NotificationKey } from '../notification/notification.id';
|
|
5
|
+
import { type StorageFileId } from '../storagefile/storagefile.id';
|
|
6
|
+
import { type FormSpaceFileSlot, type FormSpaceType } from './formspace.id';
|
|
7
|
+
/**
|
|
8
|
+
* @module formspace
|
|
9
|
+
*
|
|
10
|
+
* Defines the FormSpace Firestore model: a generic, type-registered container that parks a client-side
|
|
11
|
+
* form's JSON while the user fills it out, accepts a bounded set of file uploads against it, and hands the
|
|
12
|
+
* finished result to server code that knows what to do with it.
|
|
13
|
+
*
|
|
14
|
+
* **Why one document.** The form's own values live embedded in `d` as pass-through JSON. Files are the
|
|
15
|
+
* thing that is actually large, and they live in GCS as ordinary {@link StorageFile}s inside a
|
|
16
|
+
* {@link StorageFileGroup} keyed by the FormSpace — so every existing sync / zip / delete behaviour applies
|
|
17
|
+
* unchanged and the document itself stays far under Firestore's 1 MiB ceiling.
|
|
18
|
+
*
|
|
19
|
+
* **Submission.** Submitting stamps `sat`, locks the space out of further edits, and moves `ps` to
|
|
20
|
+
* QUEUED_FOR_PROCESSING. A NotificationTask keyed by the space then dispatches to the registered
|
|
21
|
+
* server-side handler for its type, inheriting the checkpoint / retry / delay semantics every other task in
|
|
22
|
+
* the system already uses. `ps` mirrors {@link StorageFileProcessingState} field-for-field for exactly that
|
|
23
|
+
* reason: it is the same lifecycle.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Model identity for the FormSpace collection (collection name: `formSpace`, prefix: `fsp`).
|
|
27
|
+
*
|
|
28
|
+
* Deliberately not `fs`: too easy to misread against `sf` (StorageFile) in `firestore.rules`.
|
|
29
|
+
*/
|
|
30
|
+
export declare const formSpaceIdentity: import("../..").RootFirestoreModelIdentity<"formSpace", "fsp">;
|
|
31
|
+
/**
|
|
32
|
+
* Lifecycle state of a {@link FormSpace}.
|
|
33
|
+
*
|
|
34
|
+
* Only DRAFT is editable. The other three are terminal for the owner: SUBMITTED is awaiting or undergoing
|
|
35
|
+
* processing, EXPIRED was retired by the sweep before it was ever submitted, and ARCHIVED is a space kept
|
|
36
|
+
* for the record after its processing concluded.
|
|
37
|
+
*/
|
|
38
|
+
export declare enum FormSpaceState {
|
|
39
|
+
DRAFT = 0,
|
|
40
|
+
SUBMITTED = 1,
|
|
41
|
+
EXPIRED = 2,
|
|
42
|
+
ARCHIVED = 3
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Processing state of a submitted {@link FormSpace}.
|
|
46
|
+
*
|
|
47
|
+
* Mirrors {@link StorageFileProcessingState} value-for-value, because it drives the same NotificationTask
|
|
48
|
+
* machinery and a reader that already knows one should not have to learn a second vocabulary.
|
|
49
|
+
*/
|
|
50
|
+
export declare enum FormSpaceProcessingState {
|
|
51
|
+
INIT_OR_NONE = 0,
|
|
52
|
+
QUEUED_FOR_PROCESSING = 1,
|
|
53
|
+
PROCESSING = 2,
|
|
54
|
+
FAILED = 3,
|
|
55
|
+
SUCCESS = 4,
|
|
56
|
+
DO_NOT_PROCESS = 5
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Validation state of one {@link FormSpaceFile}.
|
|
60
|
+
*
|
|
61
|
+
* A slot that declares no validator leaves every file at NONE — "nothing to check" and "checked and fine"
|
|
62
|
+
* are deliberately distinct, so a type that gains a validator later does not silently inherit a pass.
|
|
63
|
+
*/
|
|
64
|
+
export declare enum FormSpaceFileValidationState {
|
|
65
|
+
NONE = 0,
|
|
66
|
+
PENDING = 1,
|
|
67
|
+
VALID = 2,
|
|
68
|
+
INVALID = 3
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Why validation could not reach a verdict about a file's CONTENT.
|
|
72
|
+
*
|
|
73
|
+
* Closed, unlike {@link FormSpaceFile.r}, because every member here is infrastructural: the object was gone,
|
|
74
|
+
* the file was superseded mid-check, no validator was registered, the validator threw. A content rejection's
|
|
75
|
+
* reason is written by the validator for a human to read and so cannot be enumerated.
|
|
76
|
+
*/
|
|
77
|
+
export type FormSpaceFileValidationFailureReason = 'replaced' | 'file_unavailable' | 'no_validator' | 'error';
|
|
78
|
+
/**
|
|
79
|
+
* One file currently held in a {@link FormSpace} slot.
|
|
80
|
+
*
|
|
81
|
+
* The FormSpace's `f` array is the ONLY authority on what a space currently holds. The StorageFiles
|
|
82
|
+
* themselves are not queryable by the owner (`firestore.rules` grants `get` but not `list` on `/sf`) and the
|
|
83
|
+
* StorageFileGroup's own `f[]` is populated lazily by the group-sync sweep, so neither can answer "what is in
|
|
84
|
+
* this folder" at the moment an upload is accepted. This array is written in the same transaction that
|
|
85
|
+
* accepts the upload, so it always can.
|
|
86
|
+
*
|
|
87
|
+
* @dbxModelSubObject
|
|
88
|
+
*/
|
|
89
|
+
export interface FormSpaceFile {
|
|
90
|
+
/**
|
|
91
|
+
* The slot this file fills.
|
|
92
|
+
*
|
|
93
|
+
* @dbxModelVariable slot
|
|
94
|
+
*/
|
|
95
|
+
sl: FormSpaceFileSlot;
|
|
96
|
+
/**
|
|
97
|
+
* The id of the StorageFile holding the bytes.
|
|
98
|
+
*
|
|
99
|
+
* @dbxModelVariable storageFileId
|
|
100
|
+
*/
|
|
101
|
+
sf: StorageFileId;
|
|
102
|
+
/**
|
|
103
|
+
* The user who put the file here.
|
|
104
|
+
*
|
|
105
|
+
* On a single-user space this always equals the space's `u`; on a SHARED one it is whichever member
|
|
106
|
+
* actually uploaded, which is the only thing that can answer "is this MY file". Absent on an entry written
|
|
107
|
+
* before the field existed, where the space's `u` was necessarily the uploader — which is exactly what
|
|
108
|
+
* {@link formSpaceFileUploaderId} falls back to.
|
|
109
|
+
*
|
|
110
|
+
* @dbxModelVariable uploadedBy
|
|
111
|
+
*/
|
|
112
|
+
ub?: Maybe<FirebaseAuthUserId>;
|
|
113
|
+
/**
|
|
114
|
+
* The file's name, as it was uploaded.
|
|
115
|
+
*
|
|
116
|
+
* @dbxModelVariable fileName
|
|
117
|
+
*/
|
|
118
|
+
n: SlashPathFile;
|
|
119
|
+
/**
|
|
120
|
+
* Validation state.
|
|
121
|
+
*
|
|
122
|
+
* @dbxModelVariable validationState
|
|
123
|
+
*/
|
|
124
|
+
v: FormSpaceFileValidationState;
|
|
125
|
+
/**
|
|
126
|
+
* Free-text reason the file was judged INVALID, written for the owner to act on.
|
|
127
|
+
*
|
|
128
|
+
* @dbxModelVariable invalidReason
|
|
129
|
+
*/
|
|
130
|
+
r?: Maybe<string>;
|
|
131
|
+
/**
|
|
132
|
+
* Reason validation never reached a content verdict, if it did not.
|
|
133
|
+
*
|
|
134
|
+
* @dbxModelVariable failureReason
|
|
135
|
+
*/
|
|
136
|
+
fr?: Maybe<FormSpaceFileValidationFailureReason>;
|
|
137
|
+
/**
|
|
138
|
+
* The date the upload was accepted.
|
|
139
|
+
*
|
|
140
|
+
* @dbxModelVariable uploadedAt
|
|
141
|
+
*/
|
|
142
|
+
at: Date;
|
|
143
|
+
/**
|
|
144
|
+
* The date validation concluded, if it has.
|
|
145
|
+
*
|
|
146
|
+
* @dbxModelVariable validatedAt
|
|
147
|
+
*/
|
|
148
|
+
vat?: Maybe<Date>;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Firestore sub-object converter for {@link FormSpaceFile}.
|
|
152
|
+
*
|
|
153
|
+
* Dates are stored as Unix seconds rather than timestamps, matching {@link storageFileGroupEmbeddedFile}:
|
|
154
|
+
* these are embedded in an array that is rewritten on every upload, and the compact form keeps the document
|
|
155
|
+
* small enough that the array is never the reason a space approaches Firestore's ceiling.
|
|
156
|
+
*/
|
|
157
|
+
export declare const formSpaceFileSubObject: import("../..").FirestoreSubObjectFieldMapFunctionsConfig<FormSpaceFile, Partial<import("@dereekb/util").ReplaceType<FormSpaceFile, import("@dereekb/util").MaybeMap<object>, any>>>;
|
|
158
|
+
/**
|
|
159
|
+
* The arbitrary JSON a FormSpace parks while the user fills the form out.
|
|
160
|
+
*
|
|
161
|
+
* PASS-THROUGH: the framework never interprets it. The type's handler is what gives it meaning, and an app
|
|
162
|
+
* narrows this generic to its own interface at the point it reads the space.
|
|
163
|
+
*/
|
|
164
|
+
export type FormSpaceData = Record<string, unknown>;
|
|
165
|
+
/**
|
|
166
|
+
* A type-registered container for a client-side form: its in-progress JSON, its uploads, and its
|
|
167
|
+
* submission state.
|
|
168
|
+
*
|
|
169
|
+
* `o` drives `resourceIsOwnedByAuthOwnershipKey()` in the security rules identically to `sf` / `sfg` / `cal`,
|
|
170
|
+
* and `ps` / `pn` / `pat` mirror {@link StorageFile}'s processing triple exactly.
|
|
171
|
+
*
|
|
172
|
+
* @template T - the shape of the embedded form data
|
|
173
|
+
* @dbxModel
|
|
174
|
+
* @dbxModelRead owner
|
|
175
|
+
* @dbxModelArchetype root-entity
|
|
176
|
+
* @dbxModelArchetype state-machine-item
|
|
177
|
+
*/
|
|
178
|
+
export interface FormSpace<T extends FormSpaceData = FormSpaceData> {
|
|
179
|
+
/**
|
|
180
|
+
* The kind of form this space holds, resolving its upload restrictions, expiration policy, and the
|
|
181
|
+
* server-side handler its submission is dispatched to.
|
|
182
|
+
*
|
|
183
|
+
* @dbxModelVariable formSpaceType
|
|
184
|
+
*/
|
|
185
|
+
t: FormSpaceType;
|
|
186
|
+
/**
|
|
187
|
+
* Display name of the space, for the owner's own list of in-progress forms.
|
|
188
|
+
*
|
|
189
|
+
* @dbxModelVariable displayName
|
|
190
|
+
*/
|
|
191
|
+
n?: Maybe<string>;
|
|
192
|
+
/**
|
|
193
|
+
* Lifecycle state. Only DRAFT is editable.
|
|
194
|
+
*
|
|
195
|
+
* @dbxModelVariable state
|
|
196
|
+
*/
|
|
197
|
+
s: FormSpaceState;
|
|
198
|
+
/**
|
|
199
|
+
* Processing state of the submission.
|
|
200
|
+
*
|
|
201
|
+
* @dbxModelVariable processingState
|
|
202
|
+
*/
|
|
203
|
+
ps: FormSpaceProcessingState;
|
|
204
|
+
/**
|
|
205
|
+
* The form's own values, stored as pass-through JSON.
|
|
206
|
+
*
|
|
207
|
+
* @dbxModelVariable data
|
|
208
|
+
*/
|
|
209
|
+
d?: Maybe<T>;
|
|
210
|
+
/**
|
|
211
|
+
* The user the space belongs to. Set at creation and never changed.
|
|
212
|
+
*
|
|
213
|
+
* @dbxModelVariable userId
|
|
214
|
+
*/
|
|
215
|
+
u: FirebaseAuthUserId;
|
|
216
|
+
/**
|
|
217
|
+
* Ownership key, if applicable. Drives read access in the security rules.
|
|
218
|
+
*
|
|
219
|
+
* @dbxModelVariable ownerKey
|
|
220
|
+
*/
|
|
221
|
+
o?: Maybe<FirebaseAuthOwnershipKey>;
|
|
222
|
+
/**
|
|
223
|
+
* Key of the model this space was opened against, when it was opened against one.
|
|
224
|
+
*
|
|
225
|
+
* A TARGETING HANDLE, not an identity — several concurrent spaces may share one target, which is exactly
|
|
226
|
+
* why the FormSpace does not derive its own id from it.
|
|
227
|
+
*
|
|
228
|
+
* @dbxModelVariable targetModelKey
|
|
229
|
+
*/
|
|
230
|
+
m?: Maybe<FirestoreModelKey>;
|
|
231
|
+
/**
|
|
232
|
+
* Monotonic count of uploads this space has ACCEPTED over its whole lifetime.
|
|
233
|
+
*
|
|
234
|
+
* NOT a live file count: superseding a slot still increments it. It exists so `maxUploads` can be
|
|
235
|
+
* enforced inside the same transaction that creates the StorageFile, which a query-based count could not
|
|
236
|
+
* do without a read of the whole collection.
|
|
237
|
+
*
|
|
238
|
+
* @dbxModelVariable uploadCount
|
|
239
|
+
*/
|
|
240
|
+
uc: number;
|
|
241
|
+
/**
|
|
242
|
+
* The next index a file's permanent storage path is keyed by.
|
|
243
|
+
*
|
|
244
|
+
* NOT an index into `f`, and not a count of anything. It advances when a path is CLAIMED — before the
|
|
245
|
+
* bytes are copied and before the upload is accepted — so a claim that never becomes a file leaves a
|
|
246
|
+
* gap, which is the point. A gap costs nothing; a reused index puts two StorageFiles on one object, and
|
|
247
|
+
* deleting either then destroys the other's bytes.
|
|
248
|
+
*
|
|
249
|
+
* Deliberately separate from `uc`. `uc` is the upload BUDGET and must not move for a refused upload;
|
|
250
|
+
* this must move for every path handed out, refused or not.
|
|
251
|
+
*
|
|
252
|
+
* @dbxModelVariable nextFileIndex
|
|
253
|
+
*/
|
|
254
|
+
fi: number;
|
|
255
|
+
/**
|
|
256
|
+
* Every file the space currently holds, across every slot.
|
|
257
|
+
*
|
|
258
|
+
* THE authority on the space's files. It is written in the same transaction that increments `uc`, so it is
|
|
259
|
+
* correct the instant an upload is accepted — which neither a StorageFile query (the owner cannot `list`
|
|
260
|
+
* `/sf`) nor the StorageFileGroup's lazily-synced `f[]` is.
|
|
261
|
+
*
|
|
262
|
+
* Flat rather than a map of slot to files: one array converts with one {@link firestoreObjectArray}, and
|
|
263
|
+
* the per-slot views callers actually want are a filter away. Bounded by the type's `maxUploads`.
|
|
264
|
+
*
|
|
265
|
+
* Distinct from `uc`: superseding a slot drops the old entry and appends a new one, leaving the length
|
|
266
|
+
* unchanged while `uc` still advances.
|
|
267
|
+
*
|
|
268
|
+
* @dbxModelVariable files
|
|
269
|
+
*/
|
|
270
|
+
f: FormSpaceFile[];
|
|
271
|
+
/**
|
|
272
|
+
* The NotificationTask key processing this space's submission.
|
|
273
|
+
*
|
|
274
|
+
* Set when the submission is queued; cleared once processing is no longer PROCESSING.
|
|
275
|
+
*
|
|
276
|
+
* @dbxModelVariable processingNotificationKey
|
|
277
|
+
*/
|
|
278
|
+
pn?: Maybe<NotificationKey>;
|
|
279
|
+
/**
|
|
280
|
+
* The date `ps` was last moved to PROCESSING. Used to detect a stuck task.
|
|
281
|
+
*
|
|
282
|
+
* @dbxModelVariable processingAt
|
|
283
|
+
*/
|
|
284
|
+
pat?: Maybe<Date>;
|
|
285
|
+
/**
|
|
286
|
+
* Created at date.
|
|
287
|
+
*
|
|
288
|
+
* @dbxModelVariable createdAt
|
|
289
|
+
*/
|
|
290
|
+
cat: Date;
|
|
291
|
+
/**
|
|
292
|
+
* Updated at date. Moves on every content change.
|
|
293
|
+
*
|
|
294
|
+
* @dbxModelVariable updatedAt
|
|
295
|
+
*/
|
|
296
|
+
uat: Date;
|
|
297
|
+
/**
|
|
298
|
+
* The date the space was submitted, if it was. Its presence IS the lock.
|
|
299
|
+
*
|
|
300
|
+
* @dbxModelVariable submittedAt
|
|
301
|
+
*/
|
|
302
|
+
sat?: Maybe<Date>;
|
|
303
|
+
/**
|
|
304
|
+
* The date processing of the submission concluded, if it has.
|
|
305
|
+
*
|
|
306
|
+
* @dbxModelVariable completedAt
|
|
307
|
+
*/
|
|
308
|
+
cpat?: Maybe<Date>;
|
|
309
|
+
/**
|
|
310
|
+
* The date this space becomes eligible for the expiration sweep, if it expires at all.
|
|
311
|
+
*
|
|
312
|
+
* CLEARED whenever the space leaves the expirable window — on submit, and on expiry itself. That is what
|
|
313
|
+
* keeps {@link formSpacesDueForExpirationQuery} on a single-field inequality: Firestore skips a document
|
|
314
|
+
* where the field is absent, so a cleared `eat` removes the space from the sweep entirely.
|
|
315
|
+
*
|
|
316
|
+
* @dbxModelVariable expiresAt
|
|
317
|
+
*/
|
|
318
|
+
eat?: Maybe<Date>;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Permission roles for FormSpace operations.
|
|
322
|
+
*
|
|
323
|
+
* `submit` is separate from `update` because it is the one-way door: an owner who may edit a draft is not
|
|
324
|
+
* necessarily the party allowed to finalize it.
|
|
325
|
+
*
|
|
326
|
+
* `uploadFile` and `removeFile` are separate from `update` for the mirror-image reason. On a SHARED space a
|
|
327
|
+
* member contributes files to a form whose `d` belongs to everyone; letting them add or take back their own
|
|
328
|
+
* file must not also let them rewrite the form. WHICH files a `removeFile` holder may remove is a second,
|
|
329
|
+
* per-file question the type's {@link FormSpaceFileAccess} answers — the role only opens the door.
|
|
330
|
+
*
|
|
331
|
+
* The two are enforced in DIFFERENT places, because an upload and a removal arrive by different routes.
|
|
332
|
+
* `removeFile` gates a callable, so a role map answers it directly. An upload has no callable at all — the
|
|
333
|
+
* client writes bytes into its own storage namespace and a storage trigger picks them up with no auth
|
|
334
|
+
* context to build a role map from — so `uploadFile` is the DECLARATION of who may contribute, and
|
|
335
|
+
* `FormSpaceUploadAuthorizationDelegate` is where the same app policy is actually applied. Grant them
|
|
336
|
+
* together, to the same people.
|
|
337
|
+
*/
|
|
338
|
+
export type FormSpaceRoles = GrantedReadRole | GrantedUpdateRole | GrantedDeleteRole | 'submit' | 'uploadFile' | 'removeFile';
|
|
339
|
+
/**
|
|
340
|
+
* Firestore document wrapper for a {@link FormSpace}.
|
|
341
|
+
*
|
|
342
|
+
* Deliberately NOT generic over the form data. The converter and the collection are both typed to the base
|
|
343
|
+
* {@link FormSpace}, so a generic here would be a type-level claim nothing downstream could honour — a
|
|
344
|
+
* caller that knows its type's shape narrows `d` at the read site instead.
|
|
345
|
+
*/
|
|
346
|
+
export declare class FormSpaceDocument extends AbstractFirestoreDocument<FormSpace, FormSpaceDocument, typeof formSpaceIdentity> {
|
|
347
|
+
get modelIdentity(): import("../..").RootFirestoreModelIdentity<"formSpace", "fsp">;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Snapshot converter for {@link FormSpace} documents.
|
|
351
|
+
*/
|
|
352
|
+
export declare const formSpaceConverter: import("../..").SnapshotConverterFunctions<FormSpace<FormSpaceData>, Partial<import("@dereekb/util").ReplaceType<FormSpace<FormSpaceData>, import("@dereekb/util").MaybeMap<object>, any>>>;
|
|
353
|
+
/**
|
|
354
|
+
* Returns the raw Firestore CollectionReference for the FormSpace collection.
|
|
355
|
+
*
|
|
356
|
+
* @param context - The Firestore context to use.
|
|
357
|
+
* @returns The CollectionReference for FormSpace documents.
|
|
358
|
+
*/
|
|
359
|
+
export declare function formSpaceCollectionReference(context: FirestoreContext): CollectionReference<FormSpace>;
|
|
360
|
+
/**
|
|
361
|
+
* Typed FirestoreCollection for {@link FormSpace} documents.
|
|
362
|
+
*/
|
|
363
|
+
export type FormSpaceFirestoreCollection = FirestoreCollection<FormSpace, FormSpaceDocument>;
|
|
364
|
+
/**
|
|
365
|
+
* Creates a fully configured {@link FormSpaceFirestoreCollection} with snapshot conversion and document factory.
|
|
366
|
+
*
|
|
367
|
+
* @param firestoreContext - The Firestore context to use.
|
|
368
|
+
* @returns A configured FormSpaceFirestoreCollection.
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* ```ts
|
|
372
|
+
* const collection = formSpaceFirestoreCollection(firestoreContext);
|
|
373
|
+
* const doc = collection.documentAccessor().loadDocumentForId(formSpaceId);
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
export declare function formSpaceFirestoreCollection(firestoreContext: FirestoreContext): FormSpaceFirestoreCollection;
|
|
377
|
+
/**
|
|
378
|
+
* Abstract base providing access to the FormSpace Firestore collection.
|
|
379
|
+
*
|
|
380
|
+
* Implement this in your app module to wire up the collection for dependency injection.
|
|
381
|
+
*
|
|
382
|
+
* @dbxModelGroup FormSpace
|
|
383
|
+
*/
|
|
384
|
+
export declare abstract class FormSpaceFirestoreCollections {
|
|
385
|
+
abstract readonly formSpaceCollection: FormSpaceFirestoreCollection;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Union of all FormSpace-related model identity types.
|
|
389
|
+
*/
|
|
390
|
+
export type FormSpaceTypes = typeof formSpaceIdentity;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type FirestoreModelId, type FirestoreModelKey, twoWayFlatFirestoreModelKey } from '../../common';
|
|
2
|
+
/**
|
|
3
|
+
* @module formspace.id
|
|
4
|
+
*
|
|
5
|
+
* Identity types for the FormSpace model.
|
|
6
|
+
*
|
|
7
|
+
* A FormSpace has an ARBITRARY, auto-generated id rather than an id derived from another model's key: a
|
|
8
|
+
* user may hold several spaces of the same type against the same target at once (a resubmission, a second
|
|
9
|
+
* application), so a derived id would make two concurrent drafts collide. The association to another model
|
|
10
|
+
* is carried by the optional `m` (targetModelKey) field instead.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Firestore document id for a FormSpace. Auto-generated.
|
|
14
|
+
*/
|
|
15
|
+
export type FormSpaceId = FirestoreModelId;
|
|
16
|
+
/**
|
|
17
|
+
* Full Firestore document key (collection path + id) for a FormSpace.
|
|
18
|
+
*/
|
|
19
|
+
export type FormSpaceKey = FirestoreModelKey;
|
|
20
|
+
/**
|
|
21
|
+
* The {@link FormSpaceId} of a space whose identity IS the model it was opened against.
|
|
22
|
+
*
|
|
23
|
+
* `formSpaceIdForModel('gb/abc123') // 'gb_abc123'`
|
|
24
|
+
*
|
|
25
|
+
* The DEFAULT shape is the one this module's own doc describes: an arbitrary id, because several concurrent
|
|
26
|
+
* drafts may target one model. This is the other shape — a space SHARED by everyone who reaches the target,
|
|
27
|
+
* where "one space per target" is the identity itself. There, a generated id is a bug rather than a
|
|
28
|
+
* convenience: two callers bootstrapping at once each mint a "shared" space, and neither sees the other's
|
|
29
|
+
* files. Deriving the id makes the create idempotent and lets a client read the space with a plain `get`
|
|
30
|
+
* before it has ever called create.
|
|
31
|
+
*
|
|
32
|
+
* Same construction as {@link calendarIdForModel}, and for the same reason.
|
|
33
|
+
*/
|
|
34
|
+
export declare const formSpaceIdForModel: typeof twoWayFlatFirestoreModelKey;
|
|
35
|
+
/**
|
|
36
|
+
* Arbitrary string describing the kind of form a FormSpace holds, driving its upload restrictions, its
|
|
37
|
+
* expiration policy, and the server-side handler its submission is dispatched to through the app's
|
|
38
|
+
* {@link FormSpaceTypeConfig} registry.
|
|
39
|
+
*
|
|
40
|
+
* Open by design, exactly like {@link StorageFilePurpose} and {@link CalendarType}: a downstream app
|
|
41
|
+
* registers its own types without a library change.
|
|
42
|
+
*
|
|
43
|
+
* @semanticType
|
|
44
|
+
* @semanticTopic identifier
|
|
45
|
+
* @semanticTopic string
|
|
46
|
+
* @semanticTopic dereekb-firebase:form-space
|
|
47
|
+
*/
|
|
48
|
+
export type FormSpaceType = string;
|
|
49
|
+
/**
|
|
50
|
+
* Names one upload "slot" within a FormSpace — the resume, the cover letter, the photo of the ID.
|
|
51
|
+
*
|
|
52
|
+
* A slot is stored on the uploaded {@link StorageFile} as its `pg` (purposeSubgroup), which is what makes
|
|
53
|
+
* "replace the file in this slot" the existing `flagPreviousForDelete` behaviour rather than new machinery.
|
|
54
|
+
*
|
|
55
|
+
* @semanticType
|
|
56
|
+
* @semanticTopic identifier
|
|
57
|
+
* @semanticTopic string
|
|
58
|
+
* @semanticTopic dereekb-firebase:form-space
|
|
59
|
+
*/
|
|
60
|
+
export type FormSpaceFileSlot = string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type Getter, type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type FirebaseModelContext, type FirebasePermissionServiceModel, type FirestoreModelKey, type GrantRolesOtherwiseFunction, type GrantedRolesOtherwiseFunctionResult } from '../../common';
|
|
3
|
+
import { type FormSpace, type FormSpaceDocument, type FormSpaceRoles } from './formspace';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for {@link grantFormSpaceRolesForUserAuthFunction}, providing the permission
|
|
6
|
+
* service output, auth context, and target FormSpace document.
|
|
7
|
+
*/
|
|
8
|
+
export interface GrantFormSpaceRolesForUserAuthFunctionConfig<T extends FirebaseModelContext> {
|
|
9
|
+
readonly output: FirebasePermissionServiceModel<FormSpace, FormSpaceDocument>;
|
|
10
|
+
readonly context: T;
|
|
11
|
+
readonly model: FormSpaceDocument;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Input for the role granting function, specifying which roles to grant based on
|
|
15
|
+
* user ownership and/or ownership key matching.
|
|
16
|
+
*/
|
|
17
|
+
export interface GrantFormSpaceRolesForUserAuthInput {
|
|
18
|
+
/**
|
|
19
|
+
* Roles to grant if the user matches the FormSpace's `u` value.
|
|
20
|
+
*/
|
|
21
|
+
readonly rolesForFormSpaceUser?: Maybe<Getter<GrantedRolesOtherwiseFunctionResult<FormSpaceRoles>>>;
|
|
22
|
+
/**
|
|
23
|
+
* Roles to grant if the FormSpace carries an ownership key.
|
|
24
|
+
*/
|
|
25
|
+
readonly rolesForFormSpaceOwnershipKey?: Maybe<(ownershipKey: FirestoreModelKey) => GrantedRolesOtherwiseFunctionResult<FormSpaceRoles>>;
|
|
26
|
+
}
|
|
27
|
+
export type GrantFormSpaceRolesForUserAuthFunction = (input: GrantFormSpaceRolesForUserAuthInput) => GrantRolesOtherwiseFunction<FormSpaceRoles>;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a function that grants {@link FormSpaceRoles} based on the authentication context.
|
|
30
|
+
*
|
|
31
|
+
* Mirrors {@link grantStorageFileRolesForUserAuthFunction}: the two conditions — the caller IS the space's
|
|
32
|
+
* user, and the space carries an ownership key the caller satisfies — are evaluated in parallel and merged.
|
|
33
|
+
*
|
|
34
|
+
* @param config - Permission output, auth context, and target document for the grant.
|
|
35
|
+
* @returns Builder that takes role configuration and yields a GrantRolesOtherwiseFunction.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* const grantRoles = grantFormSpaceRolesForUserAuthFunction({ output, context, model });
|
|
40
|
+
* const otherwise = grantRoles({
|
|
41
|
+
* rolesForFormSpaceUser: () => ({ read: true, update: true, submit: true, delete: true })
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @__NO_SIDE_EFFECTS__
|
|
46
|
+
*/
|
|
47
|
+
export declare function grantFormSpaceRolesForUserAuthFunction<T extends FirebaseModelContext>(config: GrantFormSpaceRolesForUserAuthFunctionConfig<T>): GrantFormSpaceRolesForUserAuthFunction;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type StorageFileMetadata } from '../storagefile/storagefile.id';
|
|
3
|
+
import { type StorageFileProcessingSubtask, type StorageFileProcessingSubtaskMetadata } from '../storagefile/storagefile.task';
|
|
4
|
+
import { type FormSpaceFileValidationFailureReason } from './formspace';
|
|
5
|
+
import { type FormSpaceFileSlot } from './formspace.id';
|
|
6
|
+
/**
|
|
7
|
+
* @module formspace.processing
|
|
8
|
+
*
|
|
9
|
+
* The subtask vocabulary and StorageFile metadata for validating a file uploaded into a FormSpace.
|
|
10
|
+
*
|
|
11
|
+
* FormSpace validation rides the EXISTING `SFP` storage-file processing task rather than a task type of its
|
|
12
|
+
* own: a validated attachment is a StorageFile being processed, and the retry, stuck-detection, delay, and
|
|
13
|
+
* cleanup behaviour it inherits is the whole reason the purpose-processor mechanism exists. The processor
|
|
14
|
+
* that consumes these lives server-side, in `@dereekb/firebase-server/model`.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The first checkpoint: make sure the FormSpace knows this file exists.
|
|
18
|
+
*
|
|
19
|
+
* The upload initializer already writes the entry, in the same transaction that increments `uc` — that is
|
|
20
|
+
* what makes `maxFiles` enforceable and what gives the owner an entry the moment the upload is accepted.
|
|
21
|
+
* This step RECONCILES: it re-adds an entry for a StorageFile that carries this purpose but is missing from
|
|
22
|
+
* its space, which is the case for a file created by any path other than that initializer.
|
|
23
|
+
*
|
|
24
|
+
* It runs before validation so a reconciled file is validated in the same task rather than waiting for the
|
|
25
|
+
* next sweep to notice it.
|
|
26
|
+
*/
|
|
27
|
+
export declare const FORM_SPACE_PURPOSE_REGISTER_SUBTASK: StorageFileProcessingSubtask;
|
|
28
|
+
/**
|
|
29
|
+
* The second checkpoint: run the slot's registered validator.
|
|
30
|
+
*
|
|
31
|
+
* One checkpoint rather than a `send`/`retrieve` pair like the resume check: a validator that needs to wait
|
|
32
|
+
* on something returns a `pending` verdict with a retry delay, which re-enters this same checkpoint. A
|
|
33
|
+
* second checkpoint would only add a state the validator cannot see.
|
|
34
|
+
*/
|
|
35
|
+
export declare const FORM_SPACE_PURPOSE_VALIDATE_SUBTASK: StorageFileProcessingSubtask;
|
|
36
|
+
/**
|
|
37
|
+
* Type alias for the FormSpace file processing checkpoints.
|
|
38
|
+
*/
|
|
39
|
+
export type FormSpaceFileValidationSubtask = typeof FORM_SPACE_PURPOSE_REGISTER_SUBTASK | typeof FORM_SPACE_PURPOSE_VALIDATE_SUBTASK;
|
|
40
|
+
/**
|
|
41
|
+
* Metadata carried between runs of the FormSpace file validation subtask.
|
|
42
|
+
*
|
|
43
|
+
* The verdict is recorded here as well as on the FormSpace because the cleanup step — which is what writes
|
|
44
|
+
* the StorageFile's final processing state — runs after the flow and can only see the persisted metadata.
|
|
45
|
+
*/
|
|
46
|
+
export interface FormSpaceFileValidationSubtaskMetadata extends StorageFileProcessingSubtaskMetadata {
|
|
47
|
+
/**
|
|
48
|
+
* The slot the file fills, copied from the StorageFile on the first run.
|
|
49
|
+
*/
|
|
50
|
+
readonly slot?: Maybe<FormSpaceFileSlot>;
|
|
51
|
+
/**
|
|
52
|
+
* Whether the concluded verdict judged the file valid.
|
|
53
|
+
*/
|
|
54
|
+
readonly valid?: Maybe<boolean>;
|
|
55
|
+
/**
|
|
56
|
+
* Free-text reason the file was judged invalid.
|
|
57
|
+
*/
|
|
58
|
+
readonly reason?: Maybe<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Reason no content verdict was reached.
|
|
61
|
+
*/
|
|
62
|
+
readonly failureReason?: Maybe<FormSpaceFileValidationFailureReason>;
|
|
63
|
+
/**
|
|
64
|
+
* How many times the validator has been asked for a verdict.
|
|
65
|
+
*/
|
|
66
|
+
readonly attempts?: Maybe<number>;
|
|
67
|
+
/**
|
|
68
|
+
* Whether the register step had to add the file back to its FormSpace.
|
|
69
|
+
*
|
|
70
|
+
* Normally false — the upload initializer already registered it. A true here means the file reached
|
|
71
|
+
* processing without its space knowing about it, which is worth being able to see after the fact.
|
|
72
|
+
*/
|
|
73
|
+
readonly reconciled?: Maybe<boolean>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Metadata written onto a validated FormSpace file's StorageFile.
|
|
77
|
+
*
|
|
78
|
+
* Mirrors what lands on the FormSpace, so a StorageFile inspected on its own still explains itself. A
|
|
79
|
+
* validator that wants to record more (extracted dates, a page count) returns its own metadata, which
|
|
80
|
+
* REPLACES this rather than merging — the validator owns the shape it needs downstream.
|
|
81
|
+
*/
|
|
82
|
+
export interface FormSpaceFileValidationStorageFileMetadata extends StorageFileMetadata {
|
|
83
|
+
readonly valid: boolean;
|
|
84
|
+
readonly reason?: Maybe<string>;
|
|
85
|
+
readonly checkedAt: Date;
|
|
86
|
+
}
|