@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
|
@@ -1,33 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* v2 —
|
|
2
|
+
* v2 — move to UID-based identity and collapse every workspace into the
|
|
3
|
+
* local team.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Server-side slugs (both team and workspace) can change at any time and
|
|
6
|
+
* there is no reliable way for the client to map a stale slug back to its
|
|
7
|
+
* canonical record. Slugs are still meaningful — they drive the URL — but
|
|
8
|
+
* the source of truth for "which workspace is this" must be a stable
|
|
9
|
+
* identifier that lives independently of any human-editable name.
|
|
10
|
+
*
|
|
11
|
+
* Before this migration, the workspace store was keyed by `[namespace, slug]`
|
|
12
|
+
* (with a separate `teamUid` index that was never used for lookups). All
|
|
13
|
+
* chunk records (meta, documents, ...) were keyed by `${namespace}/${slug}`.
|
|
7
14
|
*
|
|
8
15
|
* After this migration:
|
|
9
|
-
* - The workspace object store is re-created with
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* - Every workspace is
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
16
|
+
* - The workspace object store is re-created with `workspaceUid` as its
|
|
17
|
+
* primary key. A fresh UUID is generated for every existing record so
|
|
18
|
+
* the new identifier is guaranteed to be stable across slug renames.
|
|
19
|
+
* - Every workspace is relocated to the local team. Both `teamUid` and
|
|
20
|
+
* `teamSlug` are set to `'local'`, regardless of where the workspace
|
|
21
|
+
* came from. The client only supports a single team workspace going
|
|
22
|
+
* forward, so any pre-existing team association is intentionally
|
|
23
|
+
* dropped — the app will rebuild the user's team workspace from the
|
|
24
|
+
* server on first sign-in.
|
|
25
|
+
* - Workspace slugs are preserved when possible. If two legacy records
|
|
26
|
+
* would collide on the same `[local, <slug>]` pair after collapse, the
|
|
27
|
+
* later record gets a unique suffix (`-2`, `-3`, ...). Records that
|
|
28
|
+
* were already under the local team keep their slug as-is; team
|
|
29
|
+
* workspaces yield first because their slug came from a namespace the
|
|
30
|
+
* user no longer controls.
|
|
31
|
+
* - Two indexes are added to the workspace store:
|
|
32
|
+
* - `teamSlug_slug` on `['teamSlug', 'slug']` with `unique: true`,
|
|
33
|
+
* so the URL `/@<teamSlug>/<workspaceSlug>` resolves to a single
|
|
34
|
+
* workspace and the app can rely on slug-pair uniqueness.
|
|
35
|
+
* - `teamUid` on `['teamUid']`, so we can fetch every workspace for a
|
|
36
|
+
* team without scanning the store.
|
|
37
|
+
* - Every chunk store (meta, documents, originalDocuments,
|
|
38
|
+
* intermediateDocuments, overrides, history, auth) is recreated with
|
|
39
|
+
* `workspaceUid` as its key path (replacing the old `workspaceId`
|
|
40
|
+
* field). All chunk records are re-keyed from the legacy
|
|
41
|
+
* `${namespace}/${slug}` value to the new `workspaceUid`.
|
|
42
|
+
* - Saved tabs (`x-scalar-tabs`) and the active tab index
|
|
43
|
+
* (`x-scalar-active-tab`) are stripped from every workspace's meta
|
|
44
|
+
* chunk. Tab paths embed the old `@<namespace>/<slug>` URL and slugs
|
|
45
|
+
* may have been rewritten to resolve collisions, so keeping them would
|
|
46
|
+
* route the client to stale paths on next load.
|
|
23
47
|
*
|
|
24
|
-
* All work happens inside the upgrade transaction. The migration awaits
|
|
25
|
-
* IDB request it queues so the database is fully migrated before
|
|
26
|
-
* — that guarantee is what lets later migrations safely
|
|
48
|
+
* All work happens inside the upgrade transaction. The migration awaits
|
|
49
|
+
* every IDB request it queues so the database is fully migrated before
|
|
50
|
+
* `up` resolves — that guarantee is what lets later migrations safely
|
|
51
|
+
* build on this state.
|
|
27
52
|
*/
|
|
28
|
-
/** Tables that store per-workspace chunks keyed by `
|
|
53
|
+
/** Tables that store per-workspace chunks keyed by `workspaceUid` (single key). */
|
|
29
54
|
const SINGLE_KEY_CHUNK_TABLES = ['meta'];
|
|
30
|
-
/** Tables that store per-document chunks keyed by `[
|
|
55
|
+
/** Tables that store per-document chunks keyed by `[workspaceUid, documentName]`. */
|
|
31
56
|
const COMPOSITE_KEY_CHUNK_TABLES = [
|
|
32
57
|
'documents',
|
|
33
58
|
'originalDocuments',
|
|
@@ -39,6 +64,12 @@ const COMPOSITE_KEY_CHUNK_TABLES = [
|
|
|
39
64
|
/**
|
|
40
65
|
* Picks a slug that does not collide with anything in `taken`.
|
|
41
66
|
* Falls back to `<slug>-2`, `<slug>-3`, ... when the desired slug is already used.
|
|
67
|
+
*
|
|
68
|
+
* Collapsing every legacy workspace into the local team can produce
|
|
69
|
+
* `[local, <slug>]` collisions whenever a team workspace shared a slug
|
|
70
|
+
* with an existing local workspace (or with another team workspace). The
|
|
71
|
+
* unique `[teamSlug, slug]` index would otherwise reject the upgrade, so
|
|
72
|
+
* this helper resolves collisions deterministically.
|
|
42
73
|
*/
|
|
43
74
|
export const pickUniqueSlug = (desired, taken) => {
|
|
44
75
|
if (!taken.has(desired)) {
|
|
@@ -51,51 +82,67 @@ export const pickUniqueSlug = (desired, taken) => {
|
|
|
51
82
|
return `${desired}-${counter}`;
|
|
52
83
|
};
|
|
53
84
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
85
|
+
* Generates a UUID. Wrapped in a function so tests can stub it and so the
|
|
86
|
+
* migration does not silently break in environments where `crypto.randomUUID`
|
|
87
|
+
* is unavailable.
|
|
88
|
+
*/
|
|
89
|
+
const generateUid = () => {
|
|
90
|
+
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
|
91
|
+
return globalThis.crypto.randomUUID();
|
|
92
|
+
}
|
|
93
|
+
throw new Error('crypto.randomUUID is not available in this environment; cannot run v2 migration');
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Computes the new shape for every workspace.
|
|
97
|
+
*
|
|
98
|
+
* Every record is collapsed into the local team: `teamUid` and `teamSlug`
|
|
99
|
+
* are both forced to `'local'`. Slug uniqueness is enforced by reserving
|
|
100
|
+
* the legacy local-team slugs first (they keep their slug verbatim), then
|
|
101
|
+
* placing every team workspace on top with a `-2`, `-3`, ... suffix when
|
|
102
|
+
* the desired slug is already taken.
|
|
103
|
+
*
|
|
104
|
+
* A fresh `workspaceUid` is generated for every record so the new
|
|
105
|
+
* identifier is stable across future slug renames.
|
|
57
106
|
*/
|
|
58
107
|
export const planWorkspaceMigration = (workspaces) => {
|
|
59
|
-
//
|
|
108
|
+
// Reserve every legacy local slug up front so genuine local workspaces
|
|
109
|
+
// never lose their slug to a colliding team workspace. Without this the
|
|
110
|
+
// suffixing would depend on iteration order and could rename the user's
|
|
111
|
+
// local workspace just because a team workspace happened to come first.
|
|
60
112
|
const reservedSlugs = new Set(workspaces.filter((workspace) => workspace.namespace === 'local').map((workspace) => workspace.slug));
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
teamSlug: 'local',
|
|
69
|
-
slug: workspace.slug,
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
continue;
|
|
113
|
+
return workspaces.map((workspace) => {
|
|
114
|
+
const isLocal = workspace.namespace === 'local';
|
|
115
|
+
// Local workspaces keep their slug because it was already reserved
|
|
116
|
+
// above. Team workspaces pick a unique slug, suffixing on collision.
|
|
117
|
+
const slug = isLocal ? workspace.slug : pickUniqueSlug(workspace.slug, reservedSlugs);
|
|
118
|
+
if (!isLocal) {
|
|
119
|
+
reservedSlugs.add(slug);
|
|
73
120
|
}
|
|
74
|
-
|
|
75
|
-
reservedSlugs.add(newSlug);
|
|
76
|
-
plan.push({
|
|
121
|
+
return {
|
|
77
122
|
before: { namespace: workspace.namespace, slug: workspace.slug },
|
|
78
123
|
after: {
|
|
79
|
-
|
|
80
|
-
// Team
|
|
81
|
-
//
|
|
124
|
+
workspaceUid: generateUid(),
|
|
125
|
+
// Team membership is intentionally dropped: the client now ships
|
|
126
|
+
// with a "single team workspace" UX, so any pre-existing team
|
|
127
|
+
// association is rebuilt from the server on next sign-in.
|
|
128
|
+
teamUid: 'local',
|
|
82
129
|
teamSlug: 'local',
|
|
83
|
-
slug
|
|
130
|
+
slug,
|
|
131
|
+
name: workspace.name,
|
|
84
132
|
},
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return plan;
|
|
133
|
+
};
|
|
134
|
+
});
|
|
88
135
|
};
|
|
89
|
-
const buildWorkspaceId = (prefix, slug) => `${prefix}/${slug}`;
|
|
90
136
|
/**
|
|
91
137
|
* Keys on the workspace meta record that embed URL paths tied to the old
|
|
92
|
-
* `@<namespace>/<slug>` routing scheme. We strip them during the migration
|
|
93
|
-
* the client can rebuild them from the current route on next load.
|
|
138
|
+
* `@<namespace>/<slug>` routing scheme. We strip them during the migration
|
|
139
|
+
* so the client can rebuild them from the current route on next load.
|
|
94
140
|
*/
|
|
95
141
|
const STALE_META_KEYS = ['x-scalar-tabs', 'x-scalar-active-tab'];
|
|
96
142
|
/**
|
|
97
|
-
* Returns a new meta object with stale, URL-bound fields removed. Leaves
|
|
98
|
-
* other key untouched so color mode, theme, active document, etc.
|
|
143
|
+
* Returns a new meta object with stale, URL-bound fields removed. Leaves
|
|
144
|
+
* every other key untouched so color mode, theme, active document, etc.
|
|
145
|
+
* survive.
|
|
99
146
|
*/
|
|
100
147
|
const stripStaleMetaFields = (meta) => {
|
|
101
148
|
if (!meta || typeof meta !== 'object') {
|
|
@@ -113,101 +160,112 @@ const requestAsPromise = (req) => new Promise((resolve, reject) => {
|
|
|
113
160
|
req.onerror = () => reject(req.error);
|
|
114
161
|
});
|
|
115
162
|
/**
|
|
116
|
-
*
|
|
117
|
-
* `
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* Returns a Promise that resolves once every queued read/write has completed
|
|
121
|
-
* — this is what lets the migration runner guarantee subsequent migrations
|
|
122
|
-
* see the fully re-keyed state.
|
|
163
|
+
* Reads every record from a chunk store, returning them tagged with the
|
|
164
|
+
* legacy `workspaceId` so the caller can remap them to the new
|
|
165
|
+
* `workspaceUid` once the store has been recreated.
|
|
123
166
|
*/
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
for (const tableName of SINGLE_KEY_CHUNK_TABLES) {
|
|
128
|
-
if (!transaction.db.objectStoreNames.contains(tableName)) {
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
const store = transaction.objectStore(tableName);
|
|
132
|
-
tasks.push(new Promise((resolve, reject) => {
|
|
133
|
-
const getRequest = store.get(oldWorkspaceId);
|
|
134
|
-
getRequest.onerror = () => reject(getRequest.error);
|
|
135
|
-
getRequest.onsuccess = () => {
|
|
136
|
-
const record = getRequest.result;
|
|
137
|
-
if (!record) {
|
|
138
|
-
resolve();
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
// The meta chunk holds x-scalar-tabs with full URL paths built from the
|
|
142
|
-
// pre-migration namespace/slug. Those paths are no longer routable after
|
|
143
|
-
// this migration (namespace is dropped and slugs may have been renamed),
|
|
144
|
-
// so drop them here and let the client rebuild tabs from the live route.
|
|
145
|
-
const nextData = tableName === 'meta' ? stripStaleMetaFields(record.data) : record.data;
|
|
146
|
-
if (idChanged) {
|
|
147
|
-
store.delete(oldWorkspaceId);
|
|
148
|
-
}
|
|
149
|
-
const putRequest = store.put({ ...record, workspaceId: newWorkspaceId, data: nextData });
|
|
150
|
-
putRequest.onerror = () => reject(putRequest.error);
|
|
151
|
-
putRequest.onsuccess = () => resolve();
|
|
152
|
-
};
|
|
153
|
-
}));
|
|
154
|
-
}
|
|
155
|
-
if (idChanged) {
|
|
156
|
-
for (const tableName of COMPOSITE_KEY_CHUNK_TABLES) {
|
|
157
|
-
if (!transaction.db.objectStoreNames.contains(tableName)) {
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
const store = transaction.objectStore(tableName);
|
|
161
|
-
// Range covering every `[oldWorkspaceId, *]` key.
|
|
162
|
-
const range = IDBKeyRange.bound([oldWorkspaceId], [oldWorkspaceId, []], false, true);
|
|
163
|
-
tasks.push(new Promise((resolve, reject) => {
|
|
164
|
-
const cursorRequest = store.openCursor(range);
|
|
165
|
-
cursorRequest.onerror = () => reject(cursorRequest.error);
|
|
166
|
-
cursorRequest.onsuccess = (event) => {
|
|
167
|
-
const cursor = event.target.result;
|
|
168
|
-
if (!cursor) {
|
|
169
|
-
resolve();
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
const value = cursor.value;
|
|
173
|
-
cursor.delete();
|
|
174
|
-
store.put({ ...value, workspaceId: newWorkspaceId });
|
|
175
|
-
cursor.continue();
|
|
176
|
-
};
|
|
177
|
-
}));
|
|
178
|
-
}
|
|
167
|
+
const readLegacyChunkRecords = async (transaction, tableName) => {
|
|
168
|
+
if (!transaction.db.objectStoreNames.contains(tableName)) {
|
|
169
|
+
return [];
|
|
179
170
|
}
|
|
180
|
-
|
|
171
|
+
const store = transaction.objectStore(tableName);
|
|
172
|
+
const records = (await requestAsPromise(store.getAll())) ?? [];
|
|
173
|
+
return records;
|
|
181
174
|
};
|
|
182
175
|
export const v2TeamToLocalMigration = {
|
|
183
|
-
description: '
|
|
176
|
+
description: 'Switch to UID-based identity: workspaceUid primary key, collapse every workspace into the local team',
|
|
184
177
|
up: async ({ db, transaction }) => {
|
|
185
178
|
if (!db.objectStoreNames.contains('workspace')) {
|
|
186
|
-
// The workspace store must exist after v1; if it does not, something
|
|
187
|
-
// very wrong and we should not silently create a new one here.
|
|
179
|
+
// The workspace store must exist after v1; if it does not, something
|
|
180
|
+
// is very wrong and we should not silently create a new one here.
|
|
188
181
|
return;
|
|
189
182
|
}
|
|
190
|
-
// Read every record
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
183
|
+
// Read every legacy record before we drop the old stores. The upgrade
|
|
184
|
+
// transaction stays alive while these requests are pending, so the
|
|
185
|
+
// schema mutations below still run in versionchange mode — which is
|
|
186
|
+
// required for deleteObjectStore / createObjectStore.
|
|
194
187
|
const oldWorkspaceStore = transaction.objectStore('workspace');
|
|
195
188
|
const workspaces = ((await requestAsPromise(oldWorkspaceStore.getAll())) ?? []);
|
|
189
|
+
// Snapshot every chunk record up front so we can recreate the stores
|
|
190
|
+
// with the new key paths and then rewrite the records below.
|
|
191
|
+
const legacySingleKeyChunks = {};
|
|
192
|
+
for (const tableName of SINGLE_KEY_CHUNK_TABLES) {
|
|
193
|
+
legacySingleKeyChunks[tableName] = await readLegacyChunkRecords(transaction, tableName);
|
|
194
|
+
}
|
|
195
|
+
const legacyCompositeKeyChunks = {};
|
|
196
|
+
for (const tableName of COMPOSITE_KEY_CHUNK_TABLES) {
|
|
197
|
+
const records = await readLegacyChunkRecords(transaction, tableName);
|
|
198
|
+
legacyCompositeKeyChunks[tableName] = records;
|
|
199
|
+
}
|
|
196
200
|
const plan = planWorkspaceMigration(workspaces);
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
|
|
201
|
+
// Map legacy `${namespace}/${slug}` to the new workspaceUid so chunk
|
|
202
|
+
// records can be re-keyed in a single pass below.
|
|
203
|
+
const legacyIdToWorkspaceUid = new Map();
|
|
204
|
+
for (const { before, after } of plan) {
|
|
205
|
+
legacyIdToWorkspaceUid.set(`${before.namespace}/${before.slug}`, after.workspaceUid);
|
|
206
|
+
}
|
|
207
|
+
// Recreate the workspace store with workspaceUid as the primary key,
|
|
208
|
+
// plus the indexes the runtime relies on for slug-based lookups and
|
|
209
|
+
// team-scoped queries. The `[teamSlug, slug]` index is unique so we
|
|
210
|
+
// never end up with two workspaces racing for the same URL.
|
|
200
211
|
db.deleteObjectStore('workspace');
|
|
201
|
-
const newWorkspaceStore = db.createObjectStore('workspace', { keyPath:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
212
|
+
const newWorkspaceStore = db.createObjectStore('workspace', { keyPath: 'workspaceUid' });
|
|
213
|
+
newWorkspaceStore.createIndex('teamSlug_slug', ['teamSlug', 'slug'], { unique: true });
|
|
214
|
+
newWorkspaceStore.createIndex('teamUid', ['teamUid']);
|
|
215
|
+
// Recreate every chunk store with the new `workspaceUid` key path. We
|
|
216
|
+
// drop and recreate (rather than rewriting records in place) because
|
|
217
|
+
// changing an object store's key path is not supported by IndexedDB.
|
|
218
|
+
for (const tableName of SINGLE_KEY_CHUNK_TABLES) {
|
|
219
|
+
if (transaction.db.objectStoreNames.contains(tableName)) {
|
|
220
|
+
db.deleteObjectStore(tableName);
|
|
221
|
+
}
|
|
222
|
+
db.createObjectStore(tableName, { keyPath: 'workspaceUid' });
|
|
223
|
+
}
|
|
224
|
+
for (const tableName of COMPOSITE_KEY_CHUNK_TABLES) {
|
|
225
|
+
if (transaction.db.objectStoreNames.contains(tableName)) {
|
|
226
|
+
db.deleteObjectStore(tableName);
|
|
227
|
+
}
|
|
228
|
+
db.createObjectStore(tableName, { keyPath: ['workspaceUid', 'documentName'] });
|
|
229
|
+
}
|
|
230
|
+
// Write the migrated workspace records. We do this after creating all
|
|
231
|
+
// stores so the upgrade transaction commits a fully-formed schema even
|
|
232
|
+
// if there are zero legacy records to migrate.
|
|
233
|
+
for (const { after } of plan) {
|
|
209
234
|
newWorkspaceStore.put(after);
|
|
210
|
-
|
|
211
|
-
}
|
|
235
|
+
}
|
|
236
|
+
// Re-key every chunk record from the legacy `${namespace}/${slug}` to
|
|
237
|
+
// the new workspaceUid. Records belonging to a workspace that no
|
|
238
|
+
// longer exists (orphans) are dropped — they have no meaning without
|
|
239
|
+
// their parent workspace.
|
|
240
|
+
for (const tableName of SINGLE_KEY_CHUNK_TABLES) {
|
|
241
|
+
const store = transaction.objectStore(tableName);
|
|
242
|
+
for (const record of legacySingleKeyChunks[tableName] ?? []) {
|
|
243
|
+
const workspaceUid = legacyIdToWorkspaceUid.get(record.workspaceId);
|
|
244
|
+
if (!workspaceUid) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const { workspaceId: _legacyId, ...rest } = record;
|
|
248
|
+
// The meta chunk holds x-scalar-tabs with full URL paths built
|
|
249
|
+
// from the pre-migration namespace/slug. Those paths are no
|
|
250
|
+
// longer routable after collapsing into the local team (and slugs
|
|
251
|
+
// may have been suffixed on collision), so drop them here and
|
|
252
|
+
// let the client rebuild tabs from the live route.
|
|
253
|
+
const nextData = tableName === 'meta'
|
|
254
|
+
? stripStaleMetaFields(rest.data)
|
|
255
|
+
: rest.data;
|
|
256
|
+
store.put({ ...rest, data: nextData, workspaceUid });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const tableName of COMPOSITE_KEY_CHUNK_TABLES) {
|
|
260
|
+
const store = transaction.objectStore(tableName);
|
|
261
|
+
for (const record of legacyCompositeKeyChunks[tableName] ?? []) {
|
|
262
|
+
const workspaceUid = legacyIdToWorkspaceUid.get(record.workspaceId);
|
|
263
|
+
if (!workspaceUid) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const { workspaceId: _legacyId, ...rest } = record;
|
|
267
|
+
store.put({ ...rest, workspaceUid });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
212
270
|
},
|
|
213
271
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-request-body.d.ts","sourceRoot":"","sources":["../../../../src/request-example/builder/body/build-request-body.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0DAA0D,CAAA;AAKjG,KAAK,QAAQ,GAAG;IACd,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,CACH;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,MAAM,CAAA;KACd,GACD;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,IAAI,CAAA;QACX,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,GACD;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,IAAI,CAAA;QACX,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CACJ,EAAE,CAAA;CACJ,CAAA;AAED,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,YAAY,CAAA;IAClB,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,MAAM,CAAA;KACd,EAAE,CAAA;CACJ,CAAA;AAED,KAAK,GAAG,GAAG;IACT,IAAI,EAAE,KAAK,CAAA;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,GAAG,CAAA;AAKrD;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAC3B,aAAa,iBAAiB,GAAG,SAAS;AAC1C,qCAAqC;AACrC,oBAAuB;AACvB,sEAAsE;AACtE,kCAAkC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACvD,WAAW,GAAG,
|
|
1
|
+
{"version":3,"file":"build-request-body.d.ts","sourceRoot":"","sources":["../../../../src/request-example/builder/body/build-request-body.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0DAA0D,CAAA;AAKjG,KAAK,QAAQ,GAAG;IACd,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,CACH;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,MAAM,CAAA;KACd,GACD;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,IAAI,CAAA;QACX,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,GACD;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,IAAI,CAAA;QACX,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CACJ,EAAE,CAAA;CACJ,CAAA;AAED,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,YAAY,CAAA;IAClB,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,MAAM,CAAA;KACd,EAAE,CAAA;CACJ,CAAA;AAED,KAAK,GAAG,GAAG;IACT,IAAI,EAAE,KAAK,CAAA;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,GAAG,CAAA;AAKrD;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAC3B,aAAa,iBAAiB,GAAG,SAAS;AAC1C,qCAAqC;AACrC,oBAAuB;AACvB,sEAAsE;AACtE,kCAAkC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACvD,WAAW,GAAG,IAyMhB,CAAA"}
|
|
@@ -97,7 +97,7 @@ requestBodyCompositionSelection) => {
|
|
|
97
97
|
// Convert object properties to form fields
|
|
98
98
|
for (const [key, value] of Object.entries(example.value)) {
|
|
99
99
|
if (key && value !== undefined && value !== null) {
|
|
100
|
-
const stringValue = typeof value === '
|
|
100
|
+
const stringValue = typeof value === 'object' && value !== null ? JSON.stringify(unpackProxyObject(value)) : String(value);
|
|
101
101
|
result.value.push({
|
|
102
102
|
key,
|
|
103
103
|
value: stringValue,
|
|
@@ -1,15 +1,28 @@
|
|
|
1
|
+
import { type Result } from '@scalar/helpers/types/result';
|
|
1
2
|
import type { RequestFactory } from '../../request-example/builder/request-factory.js';
|
|
3
|
+
import { type ResolveRequestFactoryUrlError } from '../../request-example/builder/resolve-request-factory-url.js';
|
|
2
4
|
/**
|
|
3
5
|
* The payload to build a request, useful when bypassing limitations of the browser Request object
|
|
4
6
|
*/
|
|
5
7
|
export type RequestPayload = [string, RequestInit];
|
|
8
|
+
/**
|
|
9
|
+
* Resolved request URL string (path vars, operation query, **security query**
|
|
10
|
+
* params, env substitution, reserved-query rules) without proxy rewriting —
|
|
11
|
+
* aligned with {@link buildRequest} before `redirectToProxy`.
|
|
12
|
+
*
|
|
13
|
+
* By default allows incomplete merged URLs (same as permissive copy / preview); pass
|
|
14
|
+
* `allowMissingRequestServerBase: false` to enforce a complete absolute URL.
|
|
15
|
+
*/
|
|
16
|
+
export declare const resolveExecutableRequestUrl: (request: RequestFactory, envVariables: Record<string, string>, resolveOptions?: {
|
|
17
|
+
allowMissingRequestServerBase?: boolean;
|
|
18
|
+
}) => string;
|
|
6
19
|
/**
|
|
7
20
|
* Built request response
|
|
8
21
|
*
|
|
9
22
|
* We no longer return a Request object, but a tuple of [url, init] that maps directly to the fetch() argument list so
|
|
10
23
|
* we can do things that the browser doesn't allow like GET + body
|
|
11
24
|
* */
|
|
12
|
-
type
|
|
25
|
+
export type BuildRequestData = {
|
|
13
26
|
/** Create a new request payload object with the replaced values ready to be sent to the server */
|
|
14
27
|
requestPayload: RequestPayload;
|
|
15
28
|
/** The abort controller */
|
|
@@ -17,8 +30,16 @@ type BuildRequestResponse = {
|
|
|
17
30
|
/** The flag indicating if the request is being proxied */
|
|
18
31
|
isUsingProxy: boolean;
|
|
19
32
|
};
|
|
33
|
+
/** Catch-all code when an unexpected synchronous error escapes a helper during request construction. */
|
|
34
|
+
export declare const BUILD_REQUEST_FAILED: "BUILD_REQUEST_FAILED";
|
|
35
|
+
export type BuildRequestFailureCode = ResolveRequestFactoryUrlError | typeof BUILD_REQUEST_FAILED;
|
|
36
|
+
export type BuildRequestResult = Result<BuildRequestData, BuildRequestFailureCode>;
|
|
20
37
|
export declare const buildRequest: (request: RequestFactory, options: {
|
|
21
38
|
envVariables: Record<string, string>;
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
/**
|
|
40
|
+
* When true, allows an empty resolved server base URL (embedded modal, API reference callbacks, tests).
|
|
41
|
+
* @default false
|
|
42
|
+
*/
|
|
43
|
+
allowMissingRequestServerBase?: boolean;
|
|
44
|
+
}) => BuildRequestResult;
|
|
24
45
|
//# sourceMappingURL=build-request.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-request.d.ts","sourceRoot":"","sources":["../../../src/request-example/builder/build-request.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"build-request.d.ts","sourceRoot":"","sources":["../../../src/request-example/builder/build-request.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAW,MAAM,8BAA8B,CAAA;AAOnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAA;AAC/E,OAAO,EACL,KAAK,6BAA6B,EAEnC,MAAM,uDAAuD,CAAA;AAK9D;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;AAuClD;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GACtC,SAAS,cAAc,EACvB,cAAc,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,iBAAiB;IAAE,6BAA6B,CAAC,EAAE,OAAO,CAAA;CAAE,KAC3D,MAyBF,CAAA;AAED;;;;;KAKK;AACL,MAAM,MAAM,gBAAgB,GAAG;IAC7B,kGAAkG;IAClG,cAAc,EAAE,cAAc,CAAA;IAC9B,2BAA2B;IAC3B,UAAU,EAAE,eAAe,CAAA;IAC3B,0DAA0D;IAC1D,YAAY,EAAE,OAAO,CAAA;CACtB,CAAA;AAED,wGAAwG;AACxG,eAAO,MAAM,oBAAoB,EAAG,sBAA+B,CAAA;AAEnE,MAAM,MAAM,uBAAuB,GAAG,6BAA6B,GAAG,OAAO,oBAAoB,CAAA;AAEjG,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,gBAAgB,EAAE,uBAAuB,CAAC,CAAA;AAElF,eAAO,MAAM,YAAY,GACvB,SAAS,cAAc,EACvB,SAAS;IACP,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC;;;OAGG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAA;CACxC,KACA,kBAMF,CAAA"}
|
|
@@ -1,18 +1,79 @@
|
|
|
1
|
+
import { X_SCALAR_DATE, X_SCALAR_DNT, X_SCALAR_REFERER, X_SCALAR_USER_AGENT } from '@scalar/helpers/http/scalar-headers';
|
|
1
2
|
import { replaceEnvVariables } from '@scalar/helpers/regex/replace-variables';
|
|
3
|
+
import { err, ok } from '@scalar/helpers/types/result';
|
|
4
|
+
import { safeRun } from '@scalar/helpers/types/safe-run';
|
|
2
5
|
import { redirectToProxy, shouldUseProxy } from '@scalar/helpers/url/redirect-to-proxy';
|
|
3
6
|
import { encode as encodeBase64 } from 'js-base64';
|
|
4
7
|
import { buildRequestCookieHeader } from '../../request-example/builder/header/build-request-cookie-header.js';
|
|
5
8
|
import { applyAllowReservedToUrl } from '../../request-example/builder/helpers/apply-allow-reserved-to-url.js';
|
|
6
|
-
import { resolveRequestFactoryUrl } from '../../request-example/builder/resolve-request-factory-url.js';
|
|
9
|
+
import { resolveRequestFactoryUrl, } from '../../request-example/builder/resolve-request-factory-url.js';
|
|
7
10
|
import { contextFunctions, isContextFunctionName } from '../../request-example/functions.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
const FORBIDDEN_HEADERS = [
|
|
12
|
+
{ header: 'date', scalarHeader: X_SCALAR_DATE },
|
|
13
|
+
{ header: 'dnt', scalarHeader: X_SCALAR_DNT },
|
|
14
|
+
{ header: 'referer', scalarHeader: X_SCALAR_REFERER },
|
|
15
|
+
{ header: 'user-agent', scalarHeader: X_SCALAR_USER_AGENT },
|
|
16
|
+
];
|
|
17
|
+
const formatSecurityValue = (security, replace) => {
|
|
18
|
+
const substitutedValue = replaceEnvVariables(security.value, replace);
|
|
19
|
+
if (security.format === 'basic') {
|
|
20
|
+
return `Basic ${encodeBase64(substitutedValue)}`;
|
|
21
|
+
}
|
|
22
|
+
if (security.format === 'bearer') {
|
|
23
|
+
return `Bearer ${substitutedValue}`;
|
|
24
|
+
}
|
|
25
|
+
return substitutedValue;
|
|
26
|
+
};
|
|
27
|
+
const createEnvReplaceFn = (envVariables) => {
|
|
28
|
+
return (value) => {
|
|
11
29
|
if (isContextFunctionName(value)) {
|
|
12
30
|
return contextFunctions[value].fn() ?? null;
|
|
13
31
|
}
|
|
14
|
-
return
|
|
32
|
+
return envVariables[value] ?? null;
|
|
15
33
|
};
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Resolved request URL string (path vars, operation query, **security query**
|
|
37
|
+
* params, env substitution, reserved-query rules) without proxy rewriting —
|
|
38
|
+
* aligned with {@link buildRequest} before `redirectToProxy`.
|
|
39
|
+
*
|
|
40
|
+
* By default allows incomplete merged URLs (same as permissive copy / preview); pass
|
|
41
|
+
* `allowMissingRequestServerBase: false` to enforce a complete absolute URL.
|
|
42
|
+
*/
|
|
43
|
+
export const resolveExecutableRequestUrl = (request, envVariables, resolveOptions) => {
|
|
44
|
+
const replace = createEnvReplaceFn(envVariables);
|
|
45
|
+
const securityQueryParams = new URLSearchParams();
|
|
46
|
+
if (!request.options?.disableSecurity) {
|
|
47
|
+
request.security.forEach((security) => {
|
|
48
|
+
if (security.in !== 'query') {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const name = replaceEnvVariables(security.name, replace);
|
|
52
|
+
securityQueryParams.append(name, formatSecurityValue(security, replace));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const requestUrl = resolveRequestFactoryUrl(request, {
|
|
56
|
+
envVariables: replace,
|
|
57
|
+
securityQueryParams,
|
|
58
|
+
allowMissingRequestServerBase: resolveOptions?.allowMissingRequestServerBase ?? true,
|
|
59
|
+
});
|
|
60
|
+
if (!requestUrl.ok) {
|
|
61
|
+
throw new Error(requestUrl.message ?? requestUrl.error);
|
|
62
|
+
}
|
|
63
|
+
return applyAllowReservedToUrl(requestUrl.data, request.allowedReservedQueryParameters ?? new Set());
|
|
64
|
+
};
|
|
65
|
+
/** Catch-all code when an unexpected synchronous error escapes a helper during request construction. */
|
|
66
|
+
export const BUILD_REQUEST_FAILED = 'BUILD_REQUEST_FAILED';
|
|
67
|
+
export const buildRequest = (request, options) => {
|
|
68
|
+
const guarded = safeRun(() => buildRequestInner(request, options));
|
|
69
|
+
if (!guarded.ok) {
|
|
70
|
+
return err(BUILD_REQUEST_FAILED, guarded.error);
|
|
71
|
+
}
|
|
72
|
+
return guarded.data;
|
|
73
|
+
};
|
|
74
|
+
const buildRequestInner = (request, options) => {
|
|
75
|
+
/** Replace the value with the environment variable or context function */
|
|
76
|
+
const replace = createEnvReplaceFn(options.envVariables);
|
|
16
77
|
/** Create a new abort controller */
|
|
17
78
|
const controller = new AbortController();
|
|
18
79
|
/** Create a new headers object with the replaced values */
|
|
@@ -60,16 +121,7 @@ export const buildRequest = (request, options) => {
|
|
|
60
121
|
// - For 'basic': prefix with 'Basic' and base64-encode the value (username:password).
|
|
61
122
|
// - For 'bearer': prefix with 'Bearer'.
|
|
62
123
|
// - Otherwise: use the substituted value as is (for API keys, etc).
|
|
63
|
-
const securityValue = (
|
|
64
|
-
const substitutedValue = replaceEnvVariables(security.value, replace);
|
|
65
|
-
if (security.format === 'basic') {
|
|
66
|
-
return `Basic ${encodeBase64(substitutedValue)}`;
|
|
67
|
-
}
|
|
68
|
-
if (security.format === 'bearer') {
|
|
69
|
-
return `Bearer ${substitutedValue}`;
|
|
70
|
-
}
|
|
71
|
-
return substitutedValue;
|
|
72
|
-
})();
|
|
124
|
+
const securityValue = formatSecurityValue(security, replace);
|
|
73
125
|
if (security.in === 'header') {
|
|
74
126
|
// Set the header (use replaced header name so {{ env }} placeholders work)
|
|
75
127
|
headers.append(name, securityValue);
|
|
@@ -89,10 +141,15 @@ export const buildRequest = (request, options) => {
|
|
|
89
141
|
});
|
|
90
142
|
}
|
|
91
143
|
/** Resolve the request URL with the replaced values */
|
|
92
|
-
const
|
|
144
|
+
const requestUrlResult = resolveRequestFactoryUrl(request, {
|
|
93
145
|
envVariables: replace,
|
|
94
146
|
securityQueryParams: securityQueryParams,
|
|
147
|
+
allowMissingRequestServerBase: options.allowMissingRequestServerBase,
|
|
95
148
|
});
|
|
149
|
+
if (!requestUrlResult.ok) {
|
|
150
|
+
return err(requestUrlResult.error, requestUrlResult.message);
|
|
151
|
+
}
|
|
152
|
+
const requestUrl = requestUrlResult.data;
|
|
96
153
|
/** Check if the request should be proxied */
|
|
97
154
|
const isUsingProxy = shouldUseProxy(request.proxyUrl, requestUrl);
|
|
98
155
|
/** Create a new cookies array with the replaced values */
|
|
@@ -112,10 +169,24 @@ export const buildRequest = (request, options) => {
|
|
|
112
169
|
if (cookieHeader) {
|
|
113
170
|
headers.set(cookieHeader.name, cookieHeader.value);
|
|
114
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Browsers strip some forbidden headers from outgoing requests.
|
|
174
|
+
* For a small, explicit allowlist we mirror those values to `X-Scalar-*`
|
|
175
|
+
* so proxy/Electron can apply them server-side.
|
|
176
|
+
*/
|
|
177
|
+
if (isUsingProxy || request.options?.isElectron) {
|
|
178
|
+
FORBIDDEN_HEADERS.forEach(({ header, scalarHeader }) => {
|
|
179
|
+
const headerValue = headers.get(header);
|
|
180
|
+
if (headerValue) {
|
|
181
|
+
headers.set(scalarHeader, headerValue);
|
|
182
|
+
headers.delete(header);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
115
186
|
/** Encode the URL with the allowed reserved query parameters */
|
|
116
187
|
const encodedUrl = applyAllowReservedToUrl(requestUrl, request.allowedReservedQueryParameters ?? new Set());
|
|
117
188
|
const finalUrl = isUsingProxy ? redirectToProxy(request.proxyUrl, encodedUrl) : encodedUrl;
|
|
118
|
-
return {
|
|
189
|
+
return ok({
|
|
119
190
|
requestPayload: [
|
|
120
191
|
finalUrl,
|
|
121
192
|
{
|
|
@@ -133,5 +204,5 @@ export const buildRequest = (request, options) => {
|
|
|
133
204
|
],
|
|
134
205
|
controller,
|
|
135
206
|
isUsingProxy,
|
|
136
|
-
};
|
|
207
|
+
});
|
|
137
208
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { getExampleFromBody } from './body/get-request-body-example.js';
|
|
2
2
|
export { getSelectedBodyContentType } from './body/get-selected-body-content-type.js';
|
|
3
|
-
export { type RequestPayload, buildRequest } from './build-request.js';
|
|
3
|
+
export { BUILD_REQUEST_FAILED, type BuildRequestData, type BuildRequestFailureCode, type BuildRequestResult, type RequestPayload, buildRequest, resolveExecutableRequestUrl, } from './build-request.js';
|
|
4
4
|
export { deSerializeParameter } from './header/de-serialize-parameter.js';
|
|
5
5
|
export { filterGlobalCookie } from './header/filter-global-cookies.js';
|
|
6
6
|
export { serializeContentValue, serializeDeepObjectStyle, serializeFormStyle, serializeFormStyleForCookies, serializePipeDelimitedStyle, serializeSimpleStyle, serializeSpaceDelimitedStyle, } from './header/serialize-parameter.js';
|
|
@@ -11,6 +11,7 @@ export { getResolvedUrl } from './helpers/get-resolved-url.js';
|
|
|
11
11
|
export { getServerVariables } from './helpers/get-server-variables.js';
|
|
12
12
|
export type { RequestFactory } from './request-factory.js';
|
|
13
13
|
export { requestFactory } from './request-factory.js';
|
|
14
|
+
export { INVALID_REQUEST_FACTORY_URL, MISSING_REQUEST_SERVER_BASE, type ResolveRequestFactoryUrlError, type ResolveRequestFactoryUrlResult, resolveRequestFactoryUrl, } from './resolve-request-factory-url.js';
|
|
14
15
|
export { buildRequestSecurity } from './security/build-request-security.js';
|
|
15
16
|
export type { ApiKeyObjectSecret, HttpObjectSecret, OAuth2ObjectSecret, OAuthFlowAuthorizationCodeSecret, OAuthFlowClientCredentialsSecret, OAuthFlowImplicitSecret, OAuthFlowPasswordSecret, OAuthFlowsObjectSecret, OpenIdConnectObjectSecret, SecuritySchemeObjectSecret, } from './security/secret-types.js';
|
|
16
17
|
//# sourceMappingURL=index.d.ts.map
|