@dereekb/firebase 13.42.0 → 14.0.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 +209 -302
- package/eslint/package.json +9 -9
- package/index.esm.js +5774 -3926
- package/package.json +10 -10
- package/src/lib/common/firestore/accessor/document.rxjs.d.ts +0 -4
- package/src/lib/common/firestore/query/accumulator.d.ts +2 -2
- package/src/lib/common/firestore/query/iterator.d.ts +1 -1
- package/src/lib/common/model/function.d.ts +0 -14
- 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 +240 -0
- package/src/lib/model/formspace/formspace.api.error.d.ts +96 -0
- package/src/lib/model/formspace/formspace.d.ts +461 -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 +136 -0
- package/src/lib/model/formspace/formspace.type.d.ts +300 -0
- package/src/lib/model/formspace/formspace.upload.d.ts +204 -0
- package/src/lib/model/formspace/formspace.util.d.ts +514 -0
- package/src/lib/model/formspace/index.d.ts +13 -0
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/notification/notification.task.d.ts +3 -1
- 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/index.esm.js +158 -258
- package/test/package.json +8 -8
- package/test/src/lib/common/firebase.instance.d.ts +0 -4
- package/test/src/lib/common/firestore/firestore.instance.d.ts +0 -4
- package/test/src/lib/common/mock/mock.item.collection.fixture.d.ts +0 -7
- package/test/src/lib/common/storage/storage.instance.d.ts +0 -4
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type FirestoreQueryConstraint } from '../../common/firestore/query/constraint';
|
|
3
|
+
import { type FirebaseAuthOwnershipKey } from '../../common/auth/auth';
|
|
4
|
+
import { type FormSpaceKey } from './formspace.id';
|
|
5
|
+
/**
|
|
6
|
+
* @module formspace.query
|
|
7
|
+
*
|
|
8
|
+
* SINGLE-FIELD BY CONSTRUCTION. `firestore.indexes.json` is generated from downstream `-firebase`
|
|
9
|
+
* components and cannot resolve an identity declared upstream here, so a FormSpace query that needed a
|
|
10
|
+
* composite index would have no way to declare one. Every query below is therefore one field plus an
|
|
11
|
+
* optional sort on that same field, which Firestore serves from its automatic single-field indexes.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Returns query constraints for FormSpaces awaiting a processing task (`ps == QUEUED_FOR_PROCESSING`).
|
|
15
|
+
*
|
|
16
|
+
* This is the backstop sweep: submission normally creates the task inline, so a space that is still sitting
|
|
17
|
+
* here is one whose task creation was lost.
|
|
18
|
+
*
|
|
19
|
+
* @param limitCount - Maximum number of results. Omit for unbounded.
|
|
20
|
+
* @returns Firestore query constraints for FormSpaces queued for processing.
|
|
21
|
+
*
|
|
22
|
+
* @dbxModelFirebaseIndex
|
|
23
|
+
* @dbxModelFirebaseIndexModel FormSpace
|
|
24
|
+
* @dbxModelFirebaseIndexScope COLLECTION
|
|
25
|
+
* @dbxModelFirebaseIndexCategory sweep
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const constraints = formSpacesQueuedForProcessingQuery(100);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function formSpacesQueuedForProcessingQuery(limitCount?: Maybe<number>): FirestoreQueryConstraint[];
|
|
33
|
+
/**
|
|
34
|
+
* Input for {@link formSpacesDueForExpirationQuery}.
|
|
35
|
+
*/
|
|
36
|
+
export interface FormSpacesDueForExpirationQueryInput {
|
|
37
|
+
/**
|
|
38
|
+
* FormSpaces whose `eat` is at or before this instant are due.
|
|
39
|
+
*
|
|
40
|
+
* PIN THIS for the whole sweep rather than re-deriving it per page — a cutoff that advances with the
|
|
41
|
+
* clock lets a space that ages mid-sweep join a page not yet reached, making the pass unbounded.
|
|
42
|
+
*/
|
|
43
|
+
readonly before: Date;
|
|
44
|
+
/**
|
|
45
|
+
* Maximum number of results per page.
|
|
46
|
+
*/
|
|
47
|
+
readonly limit?: Maybe<number>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns query constraints for FormSpaces whose expiration instant has arrived.
|
|
51
|
+
*
|
|
52
|
+
* NOTE: a Firestore inequality skips documents where the field is absent, so a space with no `eat` — one
|
|
53
|
+
* whose type does not expire, or one that has already been submitted or expired and had `eat` cleared — is
|
|
54
|
+
* not matched. That absence IS the exclusion mechanism; there is no second flag to keep in step.
|
|
55
|
+
*
|
|
56
|
+
* @param input - The pinned cutoff and page size.
|
|
57
|
+
* @returns Firestore query constraints for FormSpaces due for expiration.
|
|
58
|
+
*
|
|
59
|
+
* @dbxModelFirebaseIndex
|
|
60
|
+
* @dbxModelFirebaseIndexModel FormSpace
|
|
61
|
+
* @dbxModelFirebaseIndexScope COLLECTION
|
|
62
|
+
* @dbxModelFirebaseIndexCategory cleanup
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const constraints = formSpacesDueForExpirationQuery({ before: new Date(), limit: 50 });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare function formSpacesDueForExpirationQuery(input: FormSpacesDueForExpirationQueryInput): FirestoreQueryConstraint[];
|
|
70
|
+
/**
|
|
71
|
+
* Returns query constraints for every StorageFile that belongs to a FormSpace.
|
|
72
|
+
*
|
|
73
|
+
* A single `array-contains` on the group id, which Firestore serves from its automatic array index — the
|
|
74
|
+
* FormSpace's own group id is the only handle needed, so nothing here requires a composite index.
|
|
75
|
+
*
|
|
76
|
+
* @param formSpaceKey - The FormSpace whose files to select.
|
|
77
|
+
* @returns Firestore query constraints for the space's StorageFiles.
|
|
78
|
+
*
|
|
79
|
+
* @dbxModelFirebaseIndex
|
|
80
|
+
* @dbxModelFirebaseIndexModel StorageFile
|
|
81
|
+
* @dbxModelFirebaseIndexScope COLLECTION
|
|
82
|
+
* @dbxModelFirebaseIndexCategory lookup
|
|
83
|
+
* @dbxModelFirebaseIndexSkip true
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* const constraints = storageFilesForFormSpaceQuery('fsp/abc123');
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export declare function storageFilesForFormSpaceQuery(formSpaceKey: FormSpaceKey): FirestoreQueryConstraint[];
|
|
91
|
+
/**
|
|
92
|
+
* Returns query constraints for every FormSpace carrying the given ownership key.
|
|
93
|
+
*
|
|
94
|
+
* Ordering is deliberately left to the caller/client: a second field here would need a composite index this
|
|
95
|
+
* package has no way to declare.
|
|
96
|
+
*
|
|
97
|
+
* @param ownerKey - The ownership key to filter by.
|
|
98
|
+
* @returns Firestore query constraints for the owner's FormSpaces.
|
|
99
|
+
*
|
|
100
|
+
* @dbxModelFirebaseIndex
|
|
101
|
+
* @dbxModelFirebaseIndexModel FormSpace
|
|
102
|
+
* @dbxModelFirebaseIndexScope COLLECTION
|
|
103
|
+
* @dbxModelFirebaseIndexCategory lookup
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* const constraints = formSpacesForOwnerQuery('pr/abc123');
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare function formSpacesForOwnerQuery(ownerKey: FirebaseAuthOwnershipKey): FirestoreQueryConstraint[];
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type CreateNotificationTaskTemplate } from '../notification/notification.create.task';
|
|
3
|
+
import { type NotificationTaskSubtaskCheckpointString, type NotificationTaskSubtaskData, type NotificationTaskSubtaskMetadata } from '../notification/notification.task.subtask';
|
|
4
|
+
import { type NotificationTaskType, type NotificationTaskUniqueId } from '../notification/notification.id';
|
|
5
|
+
import { type FormSpaceDocument } from './formspace';
|
|
6
|
+
import { type FormSpaceId, type FormSpaceType } from './formspace.id';
|
|
7
|
+
/**
|
|
8
|
+
* @module formspace.task
|
|
9
|
+
*
|
|
10
|
+
* The NotificationTask that processes a submitted FormSpace.
|
|
11
|
+
*
|
|
12
|
+
* ONE task type for every form type. The task's SUBTASK TARGET is the {@link FormSpaceType}, so a new form
|
|
13
|
+
* type registers a processor rather than a task type — the same specialization
|
|
14
|
+
* `StorageFileProcessingNotificationTask` already does by purpose.
|
|
15
|
+
*
|
|
16
|
+
* ONE task per SUBMISSION ATTEMPT, not per space. A unique task's document id is derived and permanent, and
|
|
17
|
+
* a completed task is only marked done (`d`) — it lingers until the cleanup sweep collects it. So a space
|
|
18
|
+
* that is reopened and resubmitted would re-derive the id of its own finished task, and
|
|
19
|
+
* `createOrRunUniqueNotificationDocument` would find it already there and do nothing at all: the space
|
|
20
|
+
* would sit in QUEUED_FOR_PROCESSING pointing at a dead document forever. Keying the id by the space's
|
|
21
|
+
* reopen count is what keeps each attempt a document of its own, and the first attempt keeps the exact id
|
|
22
|
+
* it has always had.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* NotificationTask type identifier for FormSpace submission processing.
|
|
26
|
+
*/
|
|
27
|
+
export declare const FORM_SPACE_SUBMISSION_NOTIFICATION_TASK_TYPE: NotificationTaskType;
|
|
28
|
+
/**
|
|
29
|
+
* Checkpoint string for a FormSpace submission subtask.
|
|
30
|
+
*/
|
|
31
|
+
export type FormSpaceSubmissionSubtask = NotificationTaskSubtaskCheckpointString;
|
|
32
|
+
/**
|
|
33
|
+
* Arbitrary metadata carried between a FormSpace submission's subtasks.
|
|
34
|
+
*/
|
|
35
|
+
export type FormSpaceSubmissionSubtaskMetadata = NotificationTaskSubtaskMetadata;
|
|
36
|
+
/**
|
|
37
|
+
* Data payload for a FormSpace submission NotificationTask.
|
|
38
|
+
*
|
|
39
|
+
* @template M - subtask metadata type
|
|
40
|
+
* @template S - subtask checkpoint string type
|
|
41
|
+
*/
|
|
42
|
+
export interface FormSpaceSubmissionNotificationTaskData<M extends FormSpaceSubmissionSubtaskMetadata = FormSpaceSubmissionSubtaskMetadata, S extends FormSpaceSubmissionSubtask = FormSpaceSubmissionSubtask> extends NotificationTaskSubtaskData<M, S> {
|
|
43
|
+
/**
|
|
44
|
+
* The FormSpaceDocument id.
|
|
45
|
+
*/
|
|
46
|
+
readonly formSpace: FormSpaceId;
|
|
47
|
+
/**
|
|
48
|
+
* The FormSpace's type, which is also the subtask target.
|
|
49
|
+
*
|
|
50
|
+
* Retrieved from the FormSpace the first time the task runs and re-copied onto the metadata afterwards,
|
|
51
|
+
* so subsequent runs do not re-read the document only to learn which processor to dispatch to.
|
|
52
|
+
*/
|
|
53
|
+
readonly t?: Maybe<FormSpaceType>;
|
|
54
|
+
/**
|
|
55
|
+
* The space's reopen count when this task was created — the submission ATTEMPT this task belongs to.
|
|
56
|
+
*
|
|
57
|
+
* The handler compares it against the space's current count and terminates when they differ. Without
|
|
58
|
+
* that fence a task left over from a superseded attempt would process the reopened space's new content
|
|
59
|
+
* and its cleanup would write `ps`/`cpat`/`pn` over the attempt actually in force. Absent on a task
|
|
60
|
+
* created before attempt-keying existed, which is read as "do not fence".
|
|
61
|
+
*/
|
|
62
|
+
readonly rc?: Maybe<number>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Input for {@link formSpaceSubmissionNotificationTaskTemplate}.
|
|
66
|
+
*/
|
|
67
|
+
export interface FormSpaceSubmissionNotificationTaskInput<M extends FormSpaceSubmissionSubtaskMetadata = FormSpaceSubmissionSubtaskMetadata> extends Omit<FormSpaceSubmissionNotificationTaskData<M>, 'formSpace' | 't' | 'sfps' | 'rc'> {
|
|
68
|
+
readonly formSpaceDocument: FormSpaceDocument;
|
|
69
|
+
/**
|
|
70
|
+
* The space's current reopen count — the attempt this task is for. Defaults to 0, the first submission.
|
|
71
|
+
*/
|
|
72
|
+
readonly reopenCount?: Maybe<number>;
|
|
73
|
+
readonly overrideExistingTask?: Maybe<boolean>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Input for {@link formSpaceSubmissionNotificationTaskUniqueId}.
|
|
77
|
+
*/
|
|
78
|
+
export interface FormSpaceSubmissionNotificationTaskUniqueIdInput {
|
|
79
|
+
readonly formSpaceId: FormSpaceId;
|
|
80
|
+
/**
|
|
81
|
+
* The attempt's reopen count. Defaults to 0, the first submission.
|
|
82
|
+
*/
|
|
83
|
+
readonly reopenCount?: Maybe<number>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Returns the unique NotificationTask document id processing one SUBMISSION ATTEMPT of a FormSpace.
|
|
87
|
+
*
|
|
88
|
+
* The first attempt is the bare {@link notificationTaskUniqueId}, unsuffixed, so a space submitted before
|
|
89
|
+
* reopening existed keeps the exact document it already has. Every attempt after a reopen appends its
|
|
90
|
+
* count, which is what stops a resubmit from colliding with the finished task of the attempt before it.
|
|
91
|
+
*
|
|
92
|
+
* The `r` between the separators keeps the suffix from ever being able to produce a `__` run, whatever a
|
|
93
|
+
* FormSpace id happens to contain — Firestore reserves ids wrapped in double underscores, and staying well
|
|
94
|
+
* clear of them costs one character.
|
|
95
|
+
*
|
|
96
|
+
* Exported so a caller that has to LOCATE an attempt's task — a test asserting which document a resubmit
|
|
97
|
+
* created, or tooling inspecting a stuck submission — derives the id the same way the template does. The
|
|
98
|
+
* reopen action deliberately does not need it: the superseded attempt's task is left alone, either already
|
|
99
|
+
* done and awaiting the cleanup sweep or still queued and about to fence itself off.
|
|
100
|
+
*
|
|
101
|
+
* @param input - The FormSpace id and the attempt's reopen count.
|
|
102
|
+
* @returns The unique notification task id.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* formSpaceSubmissionNotificationTaskUniqueId({ formSpaceId: 'abc' }); // 'abc_FSPS'
|
|
107
|
+
* formSpaceSubmissionNotificationTaskUniqueId({ formSpaceId: 'abc', reopenCount: 2 }); // 'abc_FSPS_r2'
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @__NO_SIDE_EFFECTS__
|
|
111
|
+
*/
|
|
112
|
+
export declare function formSpaceSubmissionNotificationTaskUniqueId(input: FormSpaceSubmissionNotificationTaskUniqueIdInput): NotificationTaskUniqueId;
|
|
113
|
+
/**
|
|
114
|
+
* Creates a {@link CreateNotificationTaskTemplate} for a FormSpace submission task.
|
|
115
|
+
*
|
|
116
|
+
* The task is UNIQUE to one submission ATTEMPT of the FormSpace. Within an attempt that uniqueness is what
|
|
117
|
+
* makes a second template resolve to the same document rather than racing a second processor against the
|
|
118
|
+
* first; across attempts, {@link formSpaceSubmissionNotificationTaskUniqueId} keys them apart so a resubmit
|
|
119
|
+
* after a reopen gets a document — and a checkpoint flow — of its own.
|
|
120
|
+
*
|
|
121
|
+
* @param input - The target FormSpaceDocument, its reopen count, and optional subtask data.
|
|
122
|
+
* @returns A CreateNotificationTaskTemplate for the submission task.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* const template = formSpaceSubmissionNotificationTaskTemplate({ formSpaceDocument: doc, reopenCount: formSpace.rc });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export declare function formSpaceSubmissionNotificationTaskTemplate(input: FormSpaceSubmissionNotificationTaskInput): CreateNotificationTaskTemplate;
|
|
130
|
+
/**
|
|
131
|
+
* All NotificationTask types used by the FormSpace system.
|
|
132
|
+
*
|
|
133
|
+
* Register these with the notification task service so an unhandled type is caught at wiring time rather
|
|
134
|
+
* than at the first submission.
|
|
135
|
+
*/
|
|
136
|
+
export declare const ALL_FORM_SPACE_NOTIFICATION_TASK_TYPES: NotificationTaskType[];
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { type ContentTypeMimeType, type Maybe, type Milliseconds } from '@dereekb/util';
|
|
2
|
+
import { type FormSpaceFileSlot, type FormSpaceType } from './formspace.id';
|
|
3
|
+
/**
|
|
4
|
+
* @module formspace.type
|
|
5
|
+
*
|
|
6
|
+
* The {@link FormSpaceType} registry: the per-type upload restrictions, expiration policy, and submission
|
|
7
|
+
* metadata an app declares once and both the client and the server read.
|
|
8
|
+
*
|
|
9
|
+
* The type and the factory live here, in the model folder rather than in `firebase-server`, so a client-side
|
|
10
|
+
* pre-check rejects an oversized file with the SAME rule the server enforces authoritatively. Only the
|
|
11
|
+
* service INSTANCE is constructed server-side, as a NestJS provider — the same split
|
|
12
|
+
* {@link AppCalendarTypeConfigService} uses.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Who may read and remove an individual file, among the users who already reach the FormSpace itself.
|
|
16
|
+
*
|
|
17
|
+
* Narrows access; it never widens it. A caller that cannot read the space at all is refused before this is
|
|
18
|
+
* ever consulted, so `'space'` is not "public" — it is "whoever the space already lets in".
|
|
19
|
+
*
|
|
20
|
+
* - `'space'` — anyone holding the corresponding role on the space. The DEFAULT, and the only sensible
|
|
21
|
+
* answer for a single-user form, where every file was uploaded by the one person who can reach it.
|
|
22
|
+
* - `'uploader'` — only the user who uploaded that file, per {@link FormSpaceFile.ub}. For a SHARED space
|
|
23
|
+
* whose members contribute side by side rather than collaborating on one pile: a member adds their own
|
|
24
|
+
* photos and can take them back, and cannot read or delete anybody else's. The space's own `u` is NOT
|
|
25
|
+
* exempt — on a shared space `u` is whoever the space was opened for, not an administrator of its
|
|
26
|
+
* contents, and exempting them would quietly hand one member the whole album.
|
|
27
|
+
*/
|
|
28
|
+
export type FormSpaceFileAccess = 'space' | 'uploader';
|
|
29
|
+
/**
|
|
30
|
+
* Default for {@link FormSpaceFileSlotConfig.fileAccess} and {@link FormSpaceTypeConfig.fileAccess}.
|
|
31
|
+
*
|
|
32
|
+
* `'space'`, so a type declared before per-file access existed keeps behaving exactly as it did.
|
|
33
|
+
*/
|
|
34
|
+
export declare const DEFAULT_FORM_SPACE_FILE_ACCESS: FormSpaceFileAccess;
|
|
35
|
+
/**
|
|
36
|
+
* Restrictions for a single named upload slot within a {@link FormSpaceTypeConfig}.
|
|
37
|
+
*
|
|
38
|
+
* A slot is a LOGICAL position, not a file: uploading into an occupied slot supersedes what was there, so
|
|
39
|
+
* "one current resume" needs no extra bookkeeping.
|
|
40
|
+
*/
|
|
41
|
+
export interface FormSpaceFileSlotConfig {
|
|
42
|
+
/**
|
|
43
|
+
* The slot this configuration applies to.
|
|
44
|
+
*/
|
|
45
|
+
readonly slot: FormSpaceFileSlot;
|
|
46
|
+
/**
|
|
47
|
+
* Human-readable name of the slot, for tooling and logs.
|
|
48
|
+
*/
|
|
49
|
+
readonly name?: Maybe<string>;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the space may be submitted without a file in this slot. Defaults to false.
|
|
52
|
+
*/
|
|
53
|
+
readonly required?: Maybe<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Mime types accepted in this slot. Defaults to the type's {@link FormSpaceTypeConfig.allowedMimeTypes}.
|
|
56
|
+
*/
|
|
57
|
+
readonly allowedMimeTypes?: Maybe<readonly ContentTypeMimeType[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Size cap for a file in this slot. Defaults to the type's {@link FormSpaceTypeConfig.maxFileSizeBytes}.
|
|
60
|
+
*/
|
|
61
|
+
readonly maxFileSizeBytes?: Maybe<number>;
|
|
62
|
+
/**
|
|
63
|
+
* How many files this slot may hold at once. Defaults to {@link DEFAULT_FORM_SPACE_SLOT_MAX_FILES}.
|
|
64
|
+
*
|
|
65
|
+
* At 1 the slot is a POSITION: a new upload supersedes whatever was there, which is the original "one
|
|
66
|
+
* current resume" behaviour. Above 1 it is a FOLDER: uploads accumulate, and one is refused once the
|
|
67
|
+
* folder is full rather than quietly evicting the oldest file the user put there.
|
|
68
|
+
*/
|
|
69
|
+
readonly maxFiles?: Maybe<number>;
|
|
70
|
+
/**
|
|
71
|
+
* How many files this slot must hold before the space may be submitted.
|
|
72
|
+
*
|
|
73
|
+
* Defaults to 1 when {@link required} is true, and 0 otherwise, so `required` keeps meaning exactly what it
|
|
74
|
+
* meant before folders existed.
|
|
75
|
+
*/
|
|
76
|
+
readonly minFiles?: Maybe<number>;
|
|
77
|
+
/**
|
|
78
|
+
* Who may read and remove an individual file in this slot.
|
|
79
|
+
*
|
|
80
|
+
* Defaults to the type's {@link FormSpaceTypeConfig.fileAccess}, which itself defaults to
|
|
81
|
+
* {@link DEFAULT_FORM_SPACE_FILE_ACCESS}. Narrowing it per slot is what lets one shared space hold a
|
|
82
|
+
* public banner everybody sees alongside a folder of each member's own documents.
|
|
83
|
+
*/
|
|
84
|
+
readonly fileAccess?: Maybe<FormSpaceFileAccess>;
|
|
85
|
+
/**
|
|
86
|
+
* Whether a file here must pass validation before the space may be submitted. Defaults to false.
|
|
87
|
+
*
|
|
88
|
+
* Setting it does two things: an accepted upload enters the StorageFile processing pipeline instead of
|
|
89
|
+
* being stored as-is, and a file left PENDING or judged INVALID blocks submission. A type that sets this
|
|
90
|
+
* MUST register a validator for the slot server-side; the wiring asserts that at boot.
|
|
91
|
+
*/
|
|
92
|
+
readonly validationRequired?: Maybe<boolean>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Upload, expiration, and submission configuration for a single {@link FormSpaceType}.
|
|
96
|
+
*
|
|
97
|
+
* This is the contract a FormSpace of the type is held to. It is pure data and is shared by the client and
|
|
98
|
+
* the server; the server-side PROCESSING of a submission is registered separately, as a
|
|
99
|
+
* `FormSpaceSubmissionProcessorConfig` keyed by the same type string.
|
|
100
|
+
*/
|
|
101
|
+
export interface FormSpaceTypeConfig {
|
|
102
|
+
/**
|
|
103
|
+
* The type this configuration applies to.
|
|
104
|
+
*/
|
|
105
|
+
readonly formSpaceType: FormSpaceType;
|
|
106
|
+
/**
|
|
107
|
+
* Human-readable name of the type, for tooling and logs.
|
|
108
|
+
*/
|
|
109
|
+
readonly name?: Maybe<string>;
|
|
110
|
+
/**
|
|
111
|
+
* Longer description of what the type collects.
|
|
112
|
+
*/
|
|
113
|
+
readonly description?: Maybe<string>;
|
|
114
|
+
/**
|
|
115
|
+
* The upload slots this type declares.
|
|
116
|
+
*
|
|
117
|
+
* When empty, the type accepts no uploads at all — a FormSpace can be a pure JSON container.
|
|
118
|
+
*/
|
|
119
|
+
readonly slots?: Maybe<readonly FormSpaceFileSlotConfig[]>;
|
|
120
|
+
/**
|
|
121
|
+
* Whether a slot NOT declared in {@link slots} may be uploaded into. Defaults to false.
|
|
122
|
+
*
|
|
123
|
+
* False is the safe default: an undeclared slot has no size or mime restriction of its own, so allowing
|
|
124
|
+
* one silently widens the type's contract to the global defaults.
|
|
125
|
+
*/
|
|
126
|
+
readonly allowUndeclaredSlots?: Maybe<boolean>;
|
|
127
|
+
/**
|
|
128
|
+
* Maximum number of uploads a single FormSpace of this type may ACCEPT over its whole lifetime.
|
|
129
|
+
*
|
|
130
|
+
* Counted against the space's monotonic `uc` counter, which is why superseding a slot still consumes one:
|
|
131
|
+
* the cap bounds work done, not files retained. Defaults to {@link DEFAULT_FORM_SPACE_MAX_UPLOADS}.
|
|
132
|
+
*/
|
|
133
|
+
readonly maxUploads?: Maybe<number>;
|
|
134
|
+
/**
|
|
135
|
+
* Mime types accepted by any slot that does not narrow them further.
|
|
136
|
+
* Defaults to {@link DEFAULT_FORM_SPACE_ALLOWED_MIME_TYPES}.
|
|
137
|
+
*/
|
|
138
|
+
readonly allowedMimeTypes?: Maybe<readonly ContentTypeMimeType[]>;
|
|
139
|
+
/**
|
|
140
|
+
* Size cap for any slot that does not narrow it further.
|
|
141
|
+
* Defaults to {@link DEFAULT_FORM_SPACE_MAX_FILE_SIZE_BYTES}.
|
|
142
|
+
*/
|
|
143
|
+
readonly maxFileSizeBytes?: Maybe<number>;
|
|
144
|
+
/**
|
|
145
|
+
* Who may read and remove an individual file in any slot that does not narrow it further.
|
|
146
|
+
* Defaults to {@link DEFAULT_FORM_SPACE_FILE_ACCESS}.
|
|
147
|
+
*/
|
|
148
|
+
readonly fileAccess?: Maybe<FormSpaceFileAccess>;
|
|
149
|
+
/**
|
|
150
|
+
* How long a newly created FormSpace of this type stays editable before the expiration sweep retires it.
|
|
151
|
+
*
|
|
152
|
+
* When absent the space never expires and `eat` is never written, which is exactly what keeps it out of
|
|
153
|
+
* the sweep's inequality query.
|
|
154
|
+
*/
|
|
155
|
+
readonly expiresIn?: Maybe<Milliseconds>;
|
|
156
|
+
/**
|
|
157
|
+
* How long after EACH submission a space of this type may be reopened, returning it to an editable draft.
|
|
158
|
+
*
|
|
159
|
+
* THE MASTER SWITCH. When absent the type is never reopenable and submission stays the one-way door it
|
|
160
|
+
* has always been — which is what makes the whole reopen feature opt-in, and why no existing type can
|
|
161
|
+
* acquire resubmit semantics by accident.
|
|
162
|
+
*
|
|
163
|
+
* Distinct from {@link expiresIn}, which bounds the lifetime of the DRAFT. This bounds the lifetime of
|
|
164
|
+
* the SUBMISSION's mutability.
|
|
165
|
+
*
|
|
166
|
+
* Rolling by default: absent {@link reopenableUntil}, every submission grants a fresh window measured
|
|
167
|
+
* from its own `sat`.
|
|
168
|
+
*/
|
|
169
|
+
readonly reopenableFor?: Maybe<Milliseconds>;
|
|
170
|
+
/**
|
|
171
|
+
* A hard ceiling on reopening, measured from the FIRST submission rather than the current one.
|
|
172
|
+
*
|
|
173
|
+
* Materialized onto the space as `lat` on its first submit and never moved, so repeated reopen/resubmit
|
|
174
|
+
* rounds cannot walk the deadline forward the way a purely rolling {@link reopenableFor} lets them. Use
|
|
175
|
+
* it when "this submission is final N hours after it was first made" has to be a wall-clock guarantee.
|
|
176
|
+
*
|
|
177
|
+
* Absent leaves {@link reopenableFor} rolling. Meaningless on its own: {@link reopenableFor} is what
|
|
178
|
+
* permits a reopen at all, and this only narrows it.
|
|
179
|
+
*/
|
|
180
|
+
readonly reopenableUntil?: Maybe<Milliseconds>;
|
|
181
|
+
/**
|
|
182
|
+
* How many times a space of this type may be reopened. Counted against the space's monotonic `rc`.
|
|
183
|
+
*
|
|
184
|
+
* Absent leaves the count unbounded and the windows above the only limit — which is fine for a
|
|
185
|
+
* {@link reopenableUntil} ceiling, and worth setting deliberately for a purely rolling window, where a
|
|
186
|
+
* resubmit keeps earning a new one.
|
|
187
|
+
*/
|
|
188
|
+
readonly maxReopens?: Maybe<number>;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Default for {@link FormSpaceTypeConfig.maxUploads}.
|
|
192
|
+
*/
|
|
193
|
+
export declare const DEFAULT_FORM_SPACE_MAX_UPLOADS = 20;
|
|
194
|
+
/**
|
|
195
|
+
* Default for {@link FormSpaceFileSlotConfig.maxFiles}.
|
|
196
|
+
*
|
|
197
|
+
* One, so a slot declared before folders existed keeps superseding rather than silently accumulating.
|
|
198
|
+
*/
|
|
199
|
+
export declare const DEFAULT_FORM_SPACE_SLOT_MAX_FILES = 1;
|
|
200
|
+
/**
|
|
201
|
+
* Default for {@link FormSpaceTypeConfig.maxFileSizeBytes}: 10 MiB.
|
|
202
|
+
*/
|
|
203
|
+
export declare const DEFAULT_FORM_SPACE_MAX_FILE_SIZE_BYTES: number;
|
|
204
|
+
/**
|
|
205
|
+
* Default for {@link FormSpaceTypeConfig.allowedMimeTypes}.
|
|
206
|
+
*
|
|
207
|
+
* Deliberately narrow: a form attachment is a document or an image, and an open list on the DEFAULT path is
|
|
208
|
+
* how an upload endpoint becomes a general-purpose file host.
|
|
209
|
+
*/
|
|
210
|
+
export declare const DEFAULT_FORM_SPACE_ALLOWED_MIME_TYPES: readonly ContentTypeMimeType[];
|
|
211
|
+
/**
|
|
212
|
+
* Default expiration applied to {@link DEFAULT_FORM_SPACE_TYPE_CONFIG}-shaped types that opt in: seven days.
|
|
213
|
+
*/
|
|
214
|
+
export declare const DEFAULT_FORM_SPACE_EXPIRES_IN: Milliseconds;
|
|
215
|
+
/**
|
|
216
|
+
* The {@link FormSpaceType} of {@link DEFAULT_FORM_SPACE_TYPE_CONFIG}, used for a type the app never registered.
|
|
217
|
+
*/
|
|
218
|
+
export declare const UNKNOWN_FORM_SPACE_TYPE: FormSpaceType;
|
|
219
|
+
/**
|
|
220
|
+
* The configuration applied to a FormSpace whose type the app did not register.
|
|
221
|
+
*
|
|
222
|
+
* An unregistered type falls back rather than throwing on purpose: a scheduled sweep over every FormSpace in
|
|
223
|
+
* the app must not be taken down by one badly-typed document. CREATION is the opposite — `createFormSpace`
|
|
224
|
+
* rejects an unregistered type outright, so this fallback only ever governs a document that already exists.
|
|
225
|
+
*
|
|
226
|
+
* It declares no slots and does not allow undeclared ones, so an unregistered space accepts no uploads.
|
|
227
|
+
*/
|
|
228
|
+
export declare const DEFAULT_FORM_SPACE_TYPE_CONFIG: FormSpaceTypeConfig;
|
|
229
|
+
/**
|
|
230
|
+
* Record of {@link FormSpaceTypeConfig} keyed by {@link FormSpaceType}.
|
|
231
|
+
*/
|
|
232
|
+
export type FormSpaceTypeConfigRecord = Record<FormSpaceType, FormSpaceTypeConfig>;
|
|
233
|
+
/**
|
|
234
|
+
* Creates a {@link FormSpaceTypeConfigRecord} from an array of configs.
|
|
235
|
+
*
|
|
236
|
+
* @param configs - The configs to index.
|
|
237
|
+
* @returns A record keyed by form space type.
|
|
238
|
+
* @throws {Error} When two configs declare the same {@link FormSpaceType}.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```ts
|
|
242
|
+
* const record = formSpaceTypeConfigRecord([{ formSpaceType: 'demo_example' }]);
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
export declare function formSpaceTypeConfigRecord(configs: FormSpaceTypeConfig[]): FormSpaceTypeConfigRecord;
|
|
246
|
+
/**
|
|
247
|
+
* Runtime service for resolving a {@link FormSpaceTypeConfig} from a {@link FormSpaceType}.
|
|
248
|
+
*
|
|
249
|
+
* Built from a {@link FormSpaceTypeConfigRecord} via {@link appFormSpaceTypeConfigService}.
|
|
250
|
+
*/
|
|
251
|
+
export declare abstract class AppFormSpaceTypeConfigService {
|
|
252
|
+
/**
|
|
253
|
+
* All registered configs for this app.
|
|
254
|
+
*/
|
|
255
|
+
abstract readonly appFormSpaceTypeConfigRecord: FormSpaceTypeConfigRecord;
|
|
256
|
+
/**
|
|
257
|
+
* Returns the config for the given type, falling back to the service's default when it is not registered.
|
|
258
|
+
*
|
|
259
|
+
* @param formSpaceType - The type to look up.
|
|
260
|
+
*/
|
|
261
|
+
abstract configForFormSpaceType(formSpaceType: FormSpaceType): FormSpaceTypeConfig;
|
|
262
|
+
/**
|
|
263
|
+
* Returns the config for the given type, or null when it is not registered.
|
|
264
|
+
*
|
|
265
|
+
* This is what `createFormSpace` gates on: a space may only be CREATED for a type the app declared.
|
|
266
|
+
*
|
|
267
|
+
* @param formSpaceType - The type to look up.
|
|
268
|
+
*/
|
|
269
|
+
abstract registeredConfigForFormSpaceType(formSpaceType: FormSpaceType): Maybe<FormSpaceTypeConfig>;
|
|
270
|
+
/**
|
|
271
|
+
* Returns every registered {@link FormSpaceType}.
|
|
272
|
+
*/
|
|
273
|
+
abstract getAllKnownFormSpaceTypes(): FormSpaceType[];
|
|
274
|
+
/**
|
|
275
|
+
* Returns every registered {@link FormSpaceTypeConfig}.
|
|
276
|
+
*/
|
|
277
|
+
abstract getAllKnownFormSpaceTypeConfigs(): FormSpaceTypeConfig[];
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Reference to an {@link AppFormSpaceTypeConfigService} instance, for dependency injection.
|
|
281
|
+
*/
|
|
282
|
+
export interface AppFormSpaceTypeConfigServiceRef {
|
|
283
|
+
readonly appFormSpaceTypeConfigService: AppFormSpaceTypeConfigService;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Creates an {@link AppFormSpaceTypeConfigService} from the given record.
|
|
287
|
+
*
|
|
288
|
+
* @param appFormSpaceTypeConfigRecord - The complete form space type registry for the application.
|
|
289
|
+
* @param defaultConfig - Config used for an unregistered type. Defaults to {@link DEFAULT_FORM_SPACE_TYPE_CONFIG}.
|
|
290
|
+
* @returns The service.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```ts
|
|
294
|
+
* const service = appFormSpaceTypeConfigService(formSpaceTypeConfigRecord(DEMO_FORM_SPACE_TYPE_CONFIGS));
|
|
295
|
+
* const config = service.configForFormSpaceType('demo_example');
|
|
296
|
+
* ```
|
|
297
|
+
*
|
|
298
|
+
* @__NO_SIDE_EFFECTS__
|
|
299
|
+
*/
|
|
300
|
+
export declare function appFormSpaceTypeConfigService(appFormSpaceTypeConfigRecord: FormSpaceTypeConfigRecord, defaultConfig?: FormSpaceTypeConfig): AppFormSpaceTypeConfigService;
|