@scalar/workspace-store 0.49.3 → 0.51.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/CHANGELOG.md +108 -0
- package/dist/client.d.ts +2 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +38 -15
- package/dist/entities/auth/schema.d.ts +10 -5
- package/dist/entities/auth/schema.d.ts.map +1 -1
- package/dist/events/bus.d.ts +70 -0
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/events/bus.js +48 -11
- package/dist/events/definitions/analytics.d.ts +0 -12
- package/dist/events/definitions/analytics.d.ts.map +1 -1
- package/dist/events/definitions/auth.d.ts +44 -6
- package/dist/events/definitions/auth.d.ts.map +1 -1
- package/dist/events/definitions/index.d.ts +3 -2
- package/dist/events/definitions/index.d.ts.map +1 -1
- package/dist/events/definitions/log.d.ts +18 -0
- package/dist/events/definitions/log.d.ts.map +1 -0
- package/dist/events/definitions/log.js +1 -0
- package/dist/events/definitions/operation.d.ts +1 -1
- package/dist/events/definitions/operation.d.ts.map +1 -1
- package/dist/events/definitions/ui.d.ts +18 -1
- package/dist/events/definitions/ui.d.ts.map +1 -1
- package/dist/events/index.d.ts +1 -1
- package/dist/events/index.d.ts.map +1 -1
- package/dist/helpers/get-resolved-ref.d.ts +6 -0
- package/dist/helpers/get-resolved-ref.d.ts.map +1 -1
- package/dist/helpers/get-resolved-ref.js +9 -0
- package/dist/helpers/is-hidden.d.ts +8 -0
- package/dist/helpers/is-hidden.d.ts.map +1 -0
- package/dist/helpers/is-hidden.js +5 -0
- package/dist/mutators/auth.d.ts +22 -3
- package/dist/mutators/auth.d.ts.map +1 -1
- package/dist/mutators/auth.js +213 -37
- package/dist/mutators/cookie.d.ts.map +1 -1
- package/dist/mutators/cookie.js +3 -2
- package/dist/mutators/document.d.ts.map +1 -1
- package/dist/mutators/document.js +5 -4
- package/dist/mutators/environment.d.ts.map +1 -1
- package/dist/mutators/environment.js +12 -5
- package/dist/mutators/index.d.ts +4 -0
- package/dist/mutators/index.d.ts.map +1 -1
- package/dist/mutators/operation/body.d.ts.map +1 -1
- package/dist/mutators/operation/body.js +6 -2
- package/dist/mutators/operation/extensions.d.ts.map +1 -1
- package/dist/mutators/operation/extensions.js +5 -1
- package/dist/mutators/operation/history.d.ts.map +1 -1
- package/dist/mutators/operation/history.js +7 -3
- package/dist/mutators/operation/operation.d.ts.map +1 -1
- package/dist/mutators/operation/operation.js +15 -10
- package/dist/mutators/operation/parameters.d.ts.map +1 -1
- package/dist/mutators/operation/parameters.js +12 -5
- package/dist/mutators/server.d.ts.map +1 -1
- package/dist/mutators/server.js +2 -1
- package/dist/mutators/tag.d.ts.map +1 -1
- package/dist/mutators/tag.js +9 -4
- package/dist/navigation/helpers/get-openapi-object.d.ts.map +1 -1
- package/dist/navigation/helpers/get-openapi-object.js +5 -0
- package/dist/navigation/helpers/traverse-paths.d.ts.map +1 -1
- package/dist/navigation/helpers/traverse-paths.js +4 -3
- package/dist/navigation/helpers/traverse-schemas.d.ts.map +1 -1
- package/dist/navigation/helpers/traverse-schemas.js +9 -4
- package/dist/navigation/helpers/traverse-tags.d.ts.map +1 -1
- package/dist/navigation/helpers/traverse-tags.js +2 -1
- package/dist/navigation/helpers/traverse-webhooks.d.ts.map +1 -1
- package/dist/navigation/helpers/traverse-webhooks.js +4 -3
- package/dist/navigation/helpers/update-order-ids.d.ts.map +1 -1
- package/dist/navigation/helpers/update-order-ids.js +4 -1
- package/dist/persistence/index.d.ts +123 -80
- package/dist/persistence/index.d.ts.map +1 -1
- package/dist/persistence/index.js +233 -167
- package/dist/persistence/migrations/v2-team-to-local.d.ts +22 -5
- package/dist/persistence/migrations/v2-team-to-local.d.ts.map +1 -1
- package/dist/persistence/migrations/v2-team-to-local.js +195 -137
- package/dist/request-example/builder/body/build-request-body.d.ts.map +1 -1
- package/dist/request-example/builder/body/build-request-body.js +1 -1
- package/dist/request-example/builder/build-request.d.ts +24 -3
- package/dist/request-example/builder/build-request.d.ts.map +1 -1
- package/dist/request-example/builder/build-request.js +89 -18
- package/dist/request-example/builder/index.d.ts +2 -1
- package/dist/request-example/builder/index.d.ts.map +1 -1
- package/dist/request-example/builder/index.js +2 -1
- package/dist/request-example/builder/request-factory.d.ts.map +1 -1
- package/dist/request-example/builder/request-factory.js +5 -8
- package/dist/request-example/builder/resolve-request-factory-url.d.ts +18 -1
- package/dist/request-example/builder/resolve-request-factory-url.d.ts.map +1 -1
- package/dist/request-example/builder/resolve-request-factory-url.js +29 -4
- package/dist/request-example/context/environment.d.ts.map +1 -1
- package/dist/request-example/context/environment.js +2 -1
- package/dist/request-example/context/get-request-example-context.d.ts.map +1 -1
- package/dist/request-example/context/get-request-example-context.js +7 -0
- package/dist/request-example/context/headers.d.ts +28 -13
- package/dist/request-example/context/headers.d.ts.map +1 -1
- package/dist/request-example/context/headers.js +84 -19
- package/dist/request-example/context/index.d.ts +1 -0
- package/dist/request-example/context/index.d.ts.map +1 -1
- package/dist/request-example/context/index.js +1 -0
- package/dist/request-example/context/security/get-selected-security.d.ts.map +1 -1
- package/dist/request-example/context/security/get-selected-security.js +3 -6
- package/dist/request-example/context/servers.d.ts.map +1 -1
- package/dist/request-example/context/servers.js +3 -3
- package/dist/request-example/index.d.ts +3 -3
- package/dist/request-example/index.d.ts.map +1 -1
- package/dist/request-example/index.js +2 -2
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +1 -8
- package/dist/schemas/asyncapi/asyncapi-document.d.ts +79 -0
- package/dist/schemas/asyncapi/asyncapi-document.d.ts.map +1 -0
- package/dist/schemas/asyncapi/asyncapi-document.js +58 -0
- package/dist/schemas/extensions/document/workspace-managed-extensions.d.ts +25 -0
- package/dist/schemas/extensions/document/workspace-managed-extensions.d.ts.map +1 -0
- package/dist/schemas/extensions/document/workspace-managed-extensions.js +26 -0
- package/dist/schemas/inmemory-workspace.d.ts +3 -4631
- package/dist/schemas/inmemory-workspace.d.ts.map +1 -1
- package/dist/schemas/inmemory-workspace.js +1 -15
- package/dist/schemas/reference-config/index.d.ts +3 -2
- package/dist/schemas/reference-config/index.d.ts.map +1 -1
- package/dist/schemas/reference-config/settings.d.ts +2 -1
- package/dist/schemas/reference-config/settings.d.ts.map +1 -1
- package/dist/schemas/type-guards.d.ts +24 -0
- package/dist/schemas/type-guards.d.ts.map +1 -0
- package/dist/schemas/type-guards.js +35 -0
- package/dist/schemas/v3.1/openapi/index.d.ts +2 -1
- package/dist/schemas/v3.1/openapi/index.d.ts.map +1 -1
- package/dist/schemas/v3.1/openapi/index.js +3 -3
- package/dist/schemas/v3.1/strict/openapi-document.d.ts +74 -39
- package/dist/schemas/v3.1/strict/openapi-document.d.ts.map +1 -1
- package/dist/schemas/v3.1/strict/openapi-document.js +6 -2
- package/dist/schemas/workspace-specification/index.d.ts +1 -1
- package/dist/schemas/workspace.d.ts +15 -4377
- package/dist/schemas/workspace.d.ts.map +1 -1
- package/dist/schemas/workspace.js +13 -8
- package/dist/schemas.d.ts +3 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +3 -1
- package/package.json +7 -7
|
@@ -3,18 +3,25 @@ import { createIndexDbConnection } from '../persistence/indexdb.js';
|
|
|
3
3
|
import { v1InitialMigration } from '../persistence/migrations/v1-initial.js';
|
|
4
4
|
import { v2TeamToLocalMigration } from '../persistence/migrations/v2-team-to-local.js';
|
|
5
5
|
/**
|
|
6
|
-
* Generates a
|
|
7
|
-
*
|
|
6
|
+
* Generates a fresh `workspaceUid` for new workspaces.
|
|
7
|
+
*
|
|
8
|
+
* Wraps `crypto.randomUUID` so every caller produces UIDs in a consistent
|
|
9
|
+
* shape and we can swap the implementation later without rippling through
|
|
10
|
+
* the codebase.
|
|
8
11
|
*/
|
|
9
|
-
export const
|
|
12
|
+
export const generateWorkspaceUid = () => crypto.randomUUID();
|
|
10
13
|
/**
|
|
11
14
|
* Creates the persistence layer for the workspace store using IndexedDB.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
*
|
|
16
|
+
* Storage model:
|
|
17
|
+
* - `workspace` is the catalog. Its primary key is `workspaceUid`, a
|
|
18
|
+
* stable UUID that does not change when the user (or the server)
|
|
19
|
+
* renames the team or workspace slug. Two indexes back the runtime:
|
|
20
|
+
* - `teamSlug_slug` (unique) for slug-based URL lookups.
|
|
21
|
+
* - `teamUid` for team-scoped queries.
|
|
22
|
+
* - Every chunk table (`meta`, `documents`, ...) is keyed by
|
|
23
|
+
* `workspaceUid` so chunks survive slug renames without ever being
|
|
24
|
+
* re-keyed.
|
|
18
25
|
*/
|
|
19
26
|
export const createWorkspaceStorePersistence = async () => {
|
|
20
27
|
// The `tables` config below only describes the CURRENT shape for TypeScript
|
|
@@ -27,46 +34,49 @@ export const createWorkspaceStorePersistence = async () => {
|
|
|
27
34
|
tables: {
|
|
28
35
|
workspace: {
|
|
29
36
|
schema: Type.Object({
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
/**
|
|
37
|
+
/** Stable UUID for the workspace; never changes after creation. */
|
|
38
|
+
workspaceUid: Type.String(),
|
|
39
|
+
/** UID of the team this workspace belongs to. Use 'local' for personal workspaces. */
|
|
40
|
+
teamUid: Type.String({ default: 'local' }),
|
|
41
|
+
/** Current team slug. Mutable metadata used to build URLs. */
|
|
33
42
|
teamSlug: Type.String({ default: 'local' }),
|
|
34
|
-
/**
|
|
43
|
+
/** Current workspace slug. Mutable metadata used to build URLs. */
|
|
35
44
|
slug: Type.String({ default: 'local' }),
|
|
45
|
+
/** Visual name for a given workspace. */
|
|
46
|
+
name: Type.String(),
|
|
36
47
|
}),
|
|
37
|
-
keyPath: ['
|
|
48
|
+
keyPath: ['workspaceUid'],
|
|
38
49
|
},
|
|
39
50
|
meta: {
|
|
40
|
-
schema: Type.Object({
|
|
41
|
-
keyPath: ['
|
|
51
|
+
schema: Type.Object({ workspaceUid: Type.String(), data: Type.Any() }),
|
|
52
|
+
keyPath: ['workspaceUid'],
|
|
42
53
|
},
|
|
43
54
|
documents: {
|
|
44
|
-
schema: Type.Object({
|
|
45
|
-
keyPath: ['
|
|
55
|
+
schema: Type.Object({ workspaceUid: Type.String(), documentName: Type.String(), data: Type.Any() }),
|
|
56
|
+
keyPath: ['workspaceUid', 'documentName'],
|
|
46
57
|
},
|
|
47
58
|
originalDocuments: {
|
|
48
|
-
schema: Type.Object({
|
|
49
|
-
keyPath: ['
|
|
59
|
+
schema: Type.Object({ workspaceUid: Type.String(), documentName: Type.String(), data: Type.Any() }),
|
|
60
|
+
keyPath: ['workspaceUid', 'documentName'],
|
|
50
61
|
},
|
|
51
62
|
intermediateDocuments: {
|
|
52
|
-
schema: Type.Object({
|
|
53
|
-
keyPath: ['
|
|
63
|
+
schema: Type.Object({ workspaceUid: Type.String(), documentName: Type.String(), data: Type.Any() }),
|
|
64
|
+
keyPath: ['workspaceUid', 'documentName'],
|
|
54
65
|
},
|
|
55
66
|
overrides: {
|
|
56
|
-
schema: Type.Object({
|
|
57
|
-
keyPath: ['
|
|
67
|
+
schema: Type.Object({ workspaceUid: Type.String(), documentName: Type.String(), data: Type.Any() }),
|
|
68
|
+
keyPath: ['workspaceUid', 'documentName'],
|
|
58
69
|
},
|
|
59
70
|
history: {
|
|
60
|
-
schema: Type.Object({
|
|
61
|
-
keyPath: ['
|
|
71
|
+
schema: Type.Object({ workspaceUid: Type.String(), documentName: Type.String(), data: Type.Any() }),
|
|
72
|
+
keyPath: ['workspaceUid', 'documentName'],
|
|
62
73
|
},
|
|
63
74
|
auth: {
|
|
64
|
-
schema: Type.Object({
|
|
65
|
-
keyPath: ['
|
|
75
|
+
schema: Type.Object({ workspaceUid: Type.String(), documentName: Type.String(), data: Type.Any() }),
|
|
76
|
+
keyPath: ['workspaceUid', 'documentName'],
|
|
66
77
|
},
|
|
67
78
|
},
|
|
68
79
|
});
|
|
69
|
-
// Tables wrappers for each logical section.
|
|
70
80
|
const workspaceTable = connection.get('workspace');
|
|
71
81
|
const metaTable = connection.get('meta');
|
|
72
82
|
const documentsTable = connection.get('documents');
|
|
@@ -75,208 +85,264 @@ export const createWorkspaceStorePersistence = async () => {
|
|
|
75
85
|
const overridesTable = connection.get('overrides');
|
|
76
86
|
const historyTable = connection.get('history');
|
|
77
87
|
const authTable = connection.get('auth');
|
|
78
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Resolves a workspace record by its `[teamSlug, slug]` pair using the
|
|
90
|
+
* unique secondary index. Returns `undefined` when no workspace matches.
|
|
91
|
+
*
|
|
92
|
+
* The compound index is unique, so at most one record is ever returned;
|
|
93
|
+
* we still go through `getRange` because the wrapper does not expose a
|
|
94
|
+
* single-key index lookup helper.
|
|
95
|
+
*/
|
|
96
|
+
const findByTeamSlugAndSlug = async (teamSlug, slug) => {
|
|
97
|
+
const matches = await workspaceTable.getRange([teamSlug, slug], 'teamSlug_slug');
|
|
98
|
+
return matches[0];
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Loads every chunk that belongs to a workspace and stitches them back
|
|
102
|
+
* into a single in-memory shape. Shared between `getItem` and
|
|
103
|
+
* `getItemBySlug` so both lookups produce identical output.
|
|
104
|
+
*/
|
|
105
|
+
const assembleWorkspace = async (workspace) => {
|
|
106
|
+
const { workspaceUid } = workspace;
|
|
107
|
+
const [workspaceDocuments, workspaceOriginalDocuments, workspaceIntermediateDocuments, workspaceOverrides, workspaceMeta, workspaceHistory, workspaceAuth,] = await Promise.all([
|
|
108
|
+
documentsTable.getRange([workspaceUid]),
|
|
109
|
+
originalDocumentTable.getRange([workspaceUid]),
|
|
110
|
+
intermediateDocumentTable.getRange([workspaceUid]),
|
|
111
|
+
overridesTable.getRange([workspaceUid]),
|
|
112
|
+
metaTable.getItem({ workspaceUid }),
|
|
113
|
+
historyTable.getRange([workspaceUid]),
|
|
114
|
+
authTable.getRange([workspaceUid]),
|
|
115
|
+
]);
|
|
116
|
+
return {
|
|
117
|
+
workspaceUid: workspace.workspaceUid,
|
|
118
|
+
teamUid: workspace.teamUid,
|
|
119
|
+
teamSlug: workspace.teamSlug,
|
|
120
|
+
slug: workspace.slug,
|
|
121
|
+
name: workspace.name,
|
|
122
|
+
workspace: {
|
|
123
|
+
documents: Object.fromEntries(workspaceDocuments.map((item) => [item.documentName, item.data])),
|
|
124
|
+
originalDocuments: Object.fromEntries(workspaceOriginalDocuments.map((item) => [item.documentName, item.data])),
|
|
125
|
+
intermediateDocuments: Object.fromEntries(workspaceIntermediateDocuments.map((item) => [item.documentName, item.data])),
|
|
126
|
+
overrides: Object.fromEntries(workspaceOverrides.map((item) => [item.documentName, item.data])),
|
|
127
|
+
meta: workspaceMeta?.data ?? {},
|
|
128
|
+
history: Object.fromEntries(workspaceHistory.map((item) => [item.documentName, item.data])),
|
|
129
|
+
auth: Object.fromEntries(workspaceAuth.map((item) => [item.documentName, item.data])),
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
};
|
|
79
133
|
return {
|
|
80
134
|
close: () => {
|
|
81
135
|
connection.closeDatabase();
|
|
82
136
|
},
|
|
83
137
|
meta: {
|
|
84
138
|
/**
|
|
85
|
-
*
|
|
139
|
+
* Loads persisted workspace meta only (no document or other chunk
|
|
140
|
+
* reads). Returns an empty object when no meta row exists yet.
|
|
86
141
|
*/
|
|
87
|
-
|
|
88
|
-
await metaTable.
|
|
142
|
+
getItem: async (workspaceUid) => {
|
|
143
|
+
const row = await metaTable.getItem({ workspaceUid });
|
|
144
|
+
return (row?.data ?? {});
|
|
145
|
+
},
|
|
146
|
+
/** Set meta data for a workspace. */
|
|
147
|
+
setItem: async (workspaceUid, data) => {
|
|
148
|
+
await metaTable.addItem({ workspaceUid }, { data });
|
|
89
149
|
},
|
|
90
150
|
},
|
|
91
151
|
documents: {
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
setItem: async (workspaceId, documentName, data) => {
|
|
96
|
-
await documentsTable.addItem({ workspaceId, documentName }, { data });
|
|
152
|
+
/** Set (persist) a workspace document using workspaceUid and documentName as composite key. */
|
|
153
|
+
setItem: async (workspaceUid, documentName, data) => {
|
|
154
|
+
await documentsTable.addItem({ workspaceUid, documentName }, { data });
|
|
97
155
|
},
|
|
98
156
|
},
|
|
99
157
|
originalDocuments: {
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
setItem: async (workspaceId, documentName, data) => {
|
|
104
|
-
await originalDocumentTable.addItem({ workspaceId, documentName }, { data });
|
|
158
|
+
/** Set an original (raw) document for a workspace/document pair. */
|
|
159
|
+
setItem: async (workspaceUid, documentName, data) => {
|
|
160
|
+
await originalDocumentTable.addItem({ workspaceUid, documentName }, { data });
|
|
105
161
|
},
|
|
106
162
|
},
|
|
107
163
|
intermediateDocuments: {
|
|
108
|
-
/**
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
setItem: async (workspaceId, documentName, data) => {
|
|
112
|
-
await intermediateDocumentTable.addItem({ workspaceId, documentName }, { data });
|
|
164
|
+
/** Set an intermediate (transformed) document for a workspace/document pair. */
|
|
165
|
+
setItem: async (workspaceUid, documentName, data) => {
|
|
166
|
+
await intermediateDocumentTable.addItem({ workspaceUid, documentName }, { data });
|
|
113
167
|
},
|
|
114
168
|
},
|
|
115
169
|
overrides: {
|
|
116
|
-
/**
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
setItem: async (workspaceId, documentName, data) => {
|
|
120
|
-
await overridesTable.addItem({ workspaceId, documentName }, { data });
|
|
170
|
+
/** Set document overrides for a workspace/document pair. */
|
|
171
|
+
setItem: async (workspaceUid, documentName, data) => {
|
|
172
|
+
await overridesTable.addItem({ workspaceUid, documentName }, { data });
|
|
121
173
|
},
|
|
122
174
|
},
|
|
123
175
|
history: {
|
|
124
|
-
/**
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
setItem: async (workspaceId, documentName, data) => {
|
|
128
|
-
await historyTable.addItem({ workspaceId, documentName }, { data });
|
|
176
|
+
/** Set history for a document. */
|
|
177
|
+
setItem: async (workspaceUid, documentName, data) => {
|
|
178
|
+
await historyTable.addItem({ workspaceUid, documentName }, { data });
|
|
129
179
|
},
|
|
130
180
|
},
|
|
131
181
|
auth: {
|
|
132
|
-
/**
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
setItem: async (workspaceId, documentName, data) => {
|
|
136
|
-
await authTable.addItem({ workspaceId, documentName }, { data });
|
|
182
|
+
/** Set auth for a document. */
|
|
183
|
+
setItem: async (workspaceUid, documentName, data) => {
|
|
184
|
+
await authTable.addItem({ workspaceUid, documentName }, { data });
|
|
137
185
|
},
|
|
138
186
|
},
|
|
139
187
|
workspace: {
|
|
140
188
|
/**
|
|
141
|
-
* Retrieves a workspace by its
|
|
142
|
-
*
|
|
143
|
-
*
|
|
189
|
+
* Retrieves a workspace by its stable UID, returning the full
|
|
190
|
+
* assembled state (chunks included). Returns `undefined` when no
|
|
191
|
+
* workspace matches.
|
|
192
|
+
*
|
|
193
|
+
* This is the primary lookup path because the UID never changes,
|
|
194
|
+
* making it safe to cache and reference across slug renames.
|
|
144
195
|
*/
|
|
145
|
-
getItem: async (
|
|
146
|
-
const workspace = await workspaceTable.getItem({
|
|
196
|
+
getItem: async (workspaceUid) => {
|
|
197
|
+
const workspace = (await workspaceTable.getItem({ workspaceUid }));
|
|
147
198
|
if (!workspace) {
|
|
148
199
|
return undefined;
|
|
149
200
|
}
|
|
150
|
-
|
|
151
|
-
const id = getWorkspaceId(teamSlug, slug);
|
|
152
|
-
// Retrieve all chunk records for this workspace.
|
|
153
|
-
const workspaceDocuments = await documentsTable.getRange([id]);
|
|
154
|
-
const workspaceOriginalDocuments = await originalDocumentTable.getRange([id]);
|
|
155
|
-
const workspaceIntermediateDocuments = await intermediateDocumentTable.getRange([id]);
|
|
156
|
-
const workspaceOverrides = await overridesTable.getRange([id]);
|
|
157
|
-
const workspaceMeta = await metaTable.getItem({ workspaceId: id });
|
|
158
|
-
const workspaceHistory = await historyTable.getRange([id]);
|
|
159
|
-
const workspaceAuth = await authTable.getRange([id]);
|
|
160
|
-
// Compose the workspace structure from table records.
|
|
161
|
-
return {
|
|
162
|
-
name: workspace.name,
|
|
163
|
-
teamSlug: workspace.teamSlug,
|
|
164
|
-
slug: workspace.slug,
|
|
165
|
-
workspace: {
|
|
166
|
-
documents: Object.fromEntries(workspaceDocuments.map((item) => [item.documentName, item.data])),
|
|
167
|
-
originalDocuments: Object.fromEntries(workspaceOriginalDocuments.map((item) => [item.documentName, item.data])),
|
|
168
|
-
intermediateDocuments: Object.fromEntries(workspaceIntermediateDocuments.map((item) => [item.documentName, item.data])),
|
|
169
|
-
overrides: Object.fromEntries(workspaceOverrides.map((item) => [item.documentName, item.data])),
|
|
170
|
-
meta: workspaceMeta?.data,
|
|
171
|
-
history: Object.fromEntries(workspaceHistory.map((item) => [item.documentName, item.data])),
|
|
172
|
-
auth: Object.fromEntries(workspaceAuth.map((item) => [item.documentName, item.data])),
|
|
173
|
-
},
|
|
174
|
-
};
|
|
201
|
+
return assembleWorkspace(workspace);
|
|
175
202
|
},
|
|
176
203
|
/**
|
|
177
|
-
* Retrieves
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
204
|
+
* Retrieves a workspace by its mutable `[teamSlug, slug]` pair.
|
|
205
|
+
*
|
|
206
|
+
* Use this when the only thing you have is the URL — for example
|
|
207
|
+
* when the router needs to resolve `/@<teamSlug>/<workspaceSlug>`
|
|
208
|
+
* back to a workspace. For all other cases, prefer `getItem(uid)`
|
|
209
|
+
* because the slugs can change at any time.
|
|
210
|
+
*/
|
|
211
|
+
getItemBySlug: async ({ teamSlug = 'local', slug, }) => {
|
|
212
|
+
const workspace = await findByTeamSlugAndSlug(teamSlug, slug);
|
|
213
|
+
if (!workspace) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
return assembleWorkspace(workspace);
|
|
217
|
+
},
|
|
218
|
+
/**
|
|
219
|
+
* Retrieves all workspace catalog records.
|
|
220
|
+
*
|
|
221
|
+
* Only returns the workspace shell (`workspaceUid`, `teamUid`,
|
|
222
|
+
* `teamSlug`, `slug`, `name`). To get the full workspace data
|
|
223
|
+
* including documents and metadata, use `getItem(workspaceUid)`.
|
|
181
224
|
*/
|
|
182
225
|
getAll: async () => {
|
|
183
|
-
return await workspaceTable.getAll();
|
|
226
|
+
return (await workspaceTable.getAll());
|
|
227
|
+
},
|
|
228
|
+
/**
|
|
229
|
+
* Retrieves all workspaces for a given team UID. Uses the `teamUid`
|
|
230
|
+
* index, so this is O(matches) rather than a full scan.
|
|
231
|
+
*
|
|
232
|
+
* Prefer this over `getAllByTeamSlug` because the team UID is the
|
|
233
|
+
* canonical identifier and survives team-slug renames.
|
|
234
|
+
*/
|
|
235
|
+
getAllByTeamUid: async (teamUid) => {
|
|
236
|
+
return (await workspaceTable.getRange([teamUid], 'teamUid'));
|
|
184
237
|
},
|
|
185
238
|
/**
|
|
186
|
-
* Retrieves all workspaces for a given team slug. Uses the
|
|
187
|
-
*
|
|
239
|
+
* Retrieves all workspaces for a given team slug. Uses the
|
|
240
|
+
* `teamSlug_slug` compound index as a prefix scan. Useful when the
|
|
241
|
+
* only thing on hand is the URL segment; otherwise prefer
|
|
242
|
+
* `getAllByTeamUid`.
|
|
188
243
|
*/
|
|
189
244
|
getAllByTeamSlug: async (teamSlug) => {
|
|
190
|
-
return await workspaceTable.getRange([teamSlug]);
|
|
245
|
+
return (await workspaceTable.getRange([teamSlug], 'teamSlug_slug'));
|
|
191
246
|
},
|
|
192
247
|
/**
|
|
193
|
-
* Saves a workspace
|
|
194
|
-
*
|
|
195
|
-
*
|
|
248
|
+
* Saves a workspace and all of its chunks. The caller is responsible
|
|
249
|
+
* for providing a stable `workspaceUid` (typically `crypto.randomUUID()`
|
|
250
|
+
* for new records, or the existing UID for updates).
|
|
251
|
+
*
|
|
252
|
+
* `teamSlug` and `slug` are validated by the underlying unique index;
|
|
253
|
+
* attempting to persist a duplicate pair will reject the transaction.
|
|
196
254
|
*/
|
|
197
|
-
setItem: async ({ teamSlug = 'local', slug }, value) => {
|
|
198
|
-
const workspace = await workspaceTable.addItem({ teamSlug, slug }
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// Persist all original documents.
|
|
209
|
-
await Promise.all(Object.entries(value.workspace.originalDocuments ?? {}).map(([name, data]) => {
|
|
210
|
-
return originalDocumentTable.addItem({ workspaceId: id, documentName: name }, { data });
|
|
211
|
-
}));
|
|
212
|
-
// Persist all intermediate documents.
|
|
213
|
-
await Promise.all(Object.entries(value.workspace.intermediateDocuments ?? {}).map(([name, data]) => {
|
|
214
|
-
return intermediateDocumentTable.addItem({ workspaceId: id, documentName: name }, { data });
|
|
215
|
-
}));
|
|
216
|
-
// Persist all document overrides.
|
|
217
|
-
await Promise.all(Object.entries(value.workspace.overrides ?? {}).map(([name, data]) => {
|
|
218
|
-
return overridesTable.addItem({ workspaceId: id, documentName: name }, { data });
|
|
219
|
-
}));
|
|
220
|
-
// Persist all history.
|
|
221
|
-
await Promise.all(Object.entries(value.workspace.history ?? {}).map(([name, data]) => {
|
|
222
|
-
return historyTable.addItem({ workspaceId: id, documentName: name }, { data });
|
|
223
|
-
}));
|
|
224
|
-
// Persist all auth.
|
|
225
|
-
await Promise.all(Object.entries(value.workspace.auth ?? {}).map(([name, data]) => {
|
|
226
|
-
return authTable.addItem({ workspaceId: id, documentName: name }, { data });
|
|
227
|
-
}));
|
|
255
|
+
setItem: async ({ workspaceUid, teamUid = 'local', teamSlug = 'local', slug, }, value) => {
|
|
256
|
+
const workspace = (await workspaceTable.addItem({ workspaceUid }, { teamUid, teamSlug, slug, name: value.name }));
|
|
257
|
+
await metaTable.addItem({ workspaceUid }, { data: value.workspace.meta });
|
|
258
|
+
await Promise.all([
|
|
259
|
+
...Object.entries(value.workspace.documents ?? {}).map(([name, data]) => documentsTable.addItem({ workspaceUid, documentName: name }, { data })),
|
|
260
|
+
...Object.entries(value.workspace.originalDocuments ?? {}).map(([name, data]) => originalDocumentTable.addItem({ workspaceUid, documentName: name }, { data })),
|
|
261
|
+
...Object.entries(value.workspace.intermediateDocuments ?? {}).map(([name, data]) => intermediateDocumentTable.addItem({ workspaceUid, documentName: name }, { data })),
|
|
262
|
+
...Object.entries(value.workspace.overrides ?? {}).map(([name, data]) => overridesTable.addItem({ workspaceUid, documentName: name }, { data })),
|
|
263
|
+
...Object.entries(value.workspace.history ?? {}).map(([name, data]) => historyTable.addItem({ workspaceUid, documentName: name }, { data })),
|
|
264
|
+
...Object.entries(value.workspace.auth ?? {}).map(([name, data]) => authTable.addItem({ workspaceUid, documentName: name }, { data })),
|
|
265
|
+
]);
|
|
228
266
|
return workspace;
|
|
229
267
|
},
|
|
230
268
|
/**
|
|
231
|
-
* Deletes an entire workspace and
|
|
269
|
+
* Deletes an entire workspace and every chunk that belongs to it.
|
|
270
|
+
* Safe to call on a workspace that does not exist — the chunk
|
|
271
|
+
* deletions are range scans that simply find nothing to delete.
|
|
232
272
|
*/
|
|
233
|
-
deleteItem: async (
|
|
234
|
-
|
|
235
|
-
await workspaceTable.deleteItem({ teamSlug, slug });
|
|
236
|
-
// Remove all workspace-related records from all chunk tables.
|
|
273
|
+
deleteItem: async (workspaceUid) => {
|
|
274
|
+
await workspaceTable.deleteItem({ workspaceUid });
|
|
237
275
|
await Promise.all([
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
historyTable.deleteRange([id]),
|
|
246
|
-
authTable.deleteRange([id]),
|
|
276
|
+
metaTable.deleteItem({ workspaceUid }),
|
|
277
|
+
documentsTable.deleteRange([workspaceUid]),
|
|
278
|
+
originalDocumentTable.deleteRange([workspaceUid]),
|
|
279
|
+
intermediateDocumentTable.deleteRange([workspaceUid]),
|
|
280
|
+
overridesTable.deleteRange([workspaceUid]),
|
|
281
|
+
historyTable.deleteRange([workspaceUid]),
|
|
282
|
+
authTable.deleteRange([workspaceUid]),
|
|
247
283
|
]);
|
|
248
284
|
},
|
|
249
285
|
/**
|
|
250
|
-
* Deletes a single document and all related
|
|
251
|
-
* for the given workspace
|
|
286
|
+
* Deletes a single document and all related chunks (overrides,
|
|
287
|
+
* history, auth, ...) for the given workspace/document pair.
|
|
252
288
|
*/
|
|
253
|
-
deleteDocument: async (
|
|
289
|
+
deleteDocument: async (workspaceUid, documentName) => {
|
|
254
290
|
await Promise.all([
|
|
255
|
-
documentsTable.deleteItem({
|
|
256
|
-
intermediateDocumentTable.deleteItem({
|
|
257
|
-
originalDocumentTable.deleteItem({
|
|
258
|
-
overridesTable.deleteItem({
|
|
259
|
-
historyTable.deleteItem({
|
|
260
|
-
authTable.deleteItem({
|
|
291
|
+
documentsTable.deleteItem({ workspaceUid, documentName }),
|
|
292
|
+
intermediateDocumentTable.deleteItem({ workspaceUid, documentName }),
|
|
293
|
+
originalDocumentTable.deleteItem({ workspaceUid, documentName }),
|
|
294
|
+
overridesTable.deleteItem({ workspaceUid, documentName }),
|
|
295
|
+
historyTable.deleteItem({ workspaceUid, documentName }),
|
|
296
|
+
authTable.deleteItem({ workspaceUid, documentName }),
|
|
261
297
|
]);
|
|
262
298
|
},
|
|
263
299
|
/**
|
|
264
|
-
* Updates the name of an existing workspace.
|
|
265
|
-
*
|
|
300
|
+
* Updates the name of an existing workspace. Returns the updated
|
|
301
|
+
* record, or `undefined` when the workspace does not exist.
|
|
266
302
|
*/
|
|
267
|
-
updateName: async (
|
|
268
|
-
const workspace = await workspaceTable.getItem({
|
|
303
|
+
updateName: async (workspaceUid, name) => {
|
|
304
|
+
const workspace = (await workspaceTable.getItem({ workspaceUid }));
|
|
269
305
|
if (!workspace) {
|
|
270
306
|
return undefined;
|
|
271
307
|
}
|
|
272
|
-
|
|
273
|
-
return await workspaceTable.addItem({ teamSlug, slug }, { ...workspace, name });
|
|
308
|
+
return (await workspaceTable.addItem({ workspaceUid }, { ...workspace, name }));
|
|
274
309
|
},
|
|
275
310
|
/**
|
|
276
|
-
*
|
|
311
|
+
* Updates the mutable slug metadata for an existing workspace.
|
|
312
|
+
* Returns the updated record, or `undefined` when the workspace
|
|
313
|
+
* does not exist, or when another workspace already owns the target
|
|
314
|
+
* `[teamSlug, slug]` pair (the `teamSlug_slug` index is unique).
|
|
315
|
+
*
|
|
316
|
+
* Use this when the server tells us a team slug or workspace slug
|
|
317
|
+
* has changed. The `workspaceUid` stays the same, so all chunk
|
|
318
|
+
* references continue to resolve.
|
|
277
319
|
*/
|
|
278
|
-
|
|
279
|
-
|
|
320
|
+
updateSlugs: async (workspaceUid, slugs) => {
|
|
321
|
+
const workspace = (await workspaceTable.getItem({ workspaceUid }));
|
|
322
|
+
if (!workspace) {
|
|
323
|
+
return undefined;
|
|
324
|
+
}
|
|
325
|
+
const nextTeamSlug = slugs.teamSlug ?? workspace.teamSlug;
|
|
326
|
+
const nextSlug = slugs.slug ?? workspace.slug;
|
|
327
|
+
if (nextTeamSlug !== workspace.teamSlug || nextSlug !== workspace.slug) {
|
|
328
|
+
const occupant = await findByTeamSlugAndSlug(nextTeamSlug, nextSlug);
|
|
329
|
+
if (occupant && occupant.workspaceUid !== workspaceUid) {
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return (await workspaceTable.addItem({ workspaceUid }, {
|
|
334
|
+
...workspace,
|
|
335
|
+
teamSlug: nextTeamSlug,
|
|
336
|
+
slug: nextSlug,
|
|
337
|
+
}));
|
|
338
|
+
},
|
|
339
|
+
/** Checks if a workspace with the given UID exists. */
|
|
340
|
+
has: async (workspaceUid) => {
|
|
341
|
+
return (await workspaceTable.getItem({ workspaceUid })) !== undefined;
|
|
342
|
+
},
|
|
343
|
+
/** Checks if a workspace with the given `[teamSlug, slug]` pair exists. */
|
|
344
|
+
hasSlug: async ({ teamSlug = 'local', slug }) => {
|
|
345
|
+
return (await findByTeamSlugAndSlug(teamSlug, slug)) !== undefined;
|
|
280
346
|
},
|
|
281
347
|
},
|
|
282
348
|
clear: async () => {
|
|
@@ -1,25 +1,42 @@
|
|
|
1
1
|
import type { Migration } from '../../persistence/indexdb.js';
|
|
2
2
|
type WorkspaceRecordV1 = {
|
|
3
3
|
name: string;
|
|
4
|
-
/**
|
|
4
|
+
/** Team UID at the time of save. Often missing for personal workspaces. */
|
|
5
5
|
teamUid?: string;
|
|
6
|
+
/** Team slug at the time of save. Doubled as the team identifier in v1. */
|
|
6
7
|
namespace: string;
|
|
8
|
+
/** Workspace slug at the time of save. */
|
|
7
9
|
slug: string;
|
|
8
10
|
};
|
|
9
11
|
type WorkspaceRecordV2 = {
|
|
10
|
-
|
|
12
|
+
workspaceUid: string;
|
|
13
|
+
teamUid: string;
|
|
11
14
|
teamSlug: string;
|
|
12
15
|
slug: string;
|
|
16
|
+
name: string;
|
|
13
17
|
};
|
|
14
18
|
/**
|
|
15
19
|
* Picks a slug that does not collide with anything in `taken`.
|
|
16
20
|
* Falls back to `<slug>-2`, `<slug>-3`, ... when the desired slug is already used.
|
|
21
|
+
*
|
|
22
|
+
* Collapsing every legacy workspace into the local team can produce
|
|
23
|
+
* `[local, <slug>]` collisions whenever a team workspace shared a slug
|
|
24
|
+
* with an existing local workspace (or with another team workspace). The
|
|
25
|
+
* unique `[teamSlug, slug]` index would otherwise reject the upgrade, so
|
|
26
|
+
* this helper resolves collisions deterministically.
|
|
17
27
|
*/
|
|
18
28
|
export declare const pickUniqueSlug: (desired: string, taken: ReadonlySet<string>) => string;
|
|
19
29
|
/**
|
|
20
|
-
* Computes the new shape for every workspace
|
|
21
|
-
*
|
|
22
|
-
*
|
|
30
|
+
* Computes the new shape for every workspace.
|
|
31
|
+
*
|
|
32
|
+
* Every record is collapsed into the local team: `teamUid` and `teamSlug`
|
|
33
|
+
* are both forced to `'local'`. Slug uniqueness is enforced by reserving
|
|
34
|
+
* the legacy local-team slugs first (they keep their slug verbatim), then
|
|
35
|
+
* placing every team workspace on top with a `-2`, `-3`, ... suffix when
|
|
36
|
+
* the desired slug is already taken.
|
|
37
|
+
*
|
|
38
|
+
* A fresh `workspaceUid` is generated for every record so the new
|
|
39
|
+
* identifier is stable across future slug renames.
|
|
23
40
|
*/
|
|
24
41
|
export declare const planWorkspaceMigration: (workspaces: readonly WorkspaceRecordV1[]) => Array<{
|
|
25
42
|
before: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v2-team-to-local.d.ts","sourceRoot":"","sources":["../../../src/persistence/migrations/v2-team-to-local.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"v2-team-to-local.d.ts","sourceRoot":"","sources":["../../../src/persistence/migrations/v2-team-to-local.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAoEtD,KAAK,iBAAiB,GAAG;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAA;IACjB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,KAAK,iBAAiB,GAAG;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,EAAE,OAAO,WAAW,CAAC,MAAM,CAAC,KAAG,MAU5E,CAAA;AAcD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GACjC,YAAY,SAAS,iBAAiB,EAAE,KACvC,KAAK,CAAC;IAAE,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,KAAK,EAAE,iBAAiB,CAAA;CAAE,CAkCjF,CAAA;AAiDD,eAAO,MAAM,sBAAsB,EAAE,SA8GpC,CAAA"}
|