@lupinum/better-convex-nuxt 0.8.0-beta.40
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/LICENSE +21 -0
- package/LICENSES/Apache-2.0.txt +201 -0
- package/README.md +151 -0
- package/THIRD_PARTY_NOTICES.md +38 -0
- package/dist/module.d.mts +55 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +309 -0
- package/dist/runtime/auth/auth-identity.d.ts +31 -0
- package/dist/runtime/auth/auth-identity.js +21 -0
- package/dist/runtime/auth/better-auth-browser-adapter.d.ts +33 -0
- package/dist/runtime/auth/better-auth-browser-adapter.js +215 -0
- package/dist/runtime/auth/client-engine-types.d.ts +4 -0
- package/dist/runtime/auth/client-engine-types.js +0 -0
- package/dist/runtime/auth/integrated-client.d.ts +25 -0
- package/dist/runtime/auth/integrated-client.js +132 -0
- package/dist/runtime/auth/operation-tracker.d.ts +7 -0
- package/dist/runtime/auth/operation-tracker.js +14 -0
- package/dist/runtime/auth/session-synchronization.d.ts +23 -0
- package/dist/runtime/auth/session-synchronization.js +156 -0
- package/dist/runtime/auth/token-fetcher.d.ts +66 -0
- package/dist/runtime/auth/token-fetcher.js +131 -0
- package/dist/runtime/auth/validate-auth-client-definition.d.ts +12 -0
- package/dist/runtime/auth/validate-auth-client-definition.js +47 -0
- package/dist/runtime/auth-client/convex-client-plugin.d.ts +31 -0
- package/dist/runtime/auth-client/convex-client-plugin.js +14 -0
- package/dist/runtime/auth-client/definition-types.d.ts +6 -0
- package/dist/runtime/auth-client/definition-types.js +0 -0
- package/dist/runtime/auth-client/empty-definition.d.ts +2 -0
- package/dist/runtime/auth-client/empty-definition.js +2 -0
- package/dist/runtime/auth-client/index.d.ts +71 -0
- package/dist/runtime/auth-client/index.js +3 -0
- package/dist/runtime/cli/auth-schema.d.ts +2 -0
- package/dist/runtime/cli/auth-schema.js +169 -0
- package/dist/runtime/cli/convex.d.ts +11 -0
- package/dist/runtime/cli/convex.js +274 -0
- package/dist/runtime/composables/useConvex.d.ts +4 -0
- package/dist/runtime/composables/useConvex.js +9 -0
- package/dist/runtime/composables/useConvexAction.d.ts +4 -0
- package/dist/runtime/composables/useConvexAction.js +18 -0
- package/dist/runtime/composables/useConvexAttachment.d.ts +9 -0
- package/dist/runtime/composables/useConvexAttachment.js +14 -0
- package/dist/runtime/composables/useConvexAuth.d.ts +10 -0
- package/dist/runtime/composables/useConvexAuth.js +37 -0
- package/dist/runtime/composables/useConvexConfig.d.ts +12 -0
- package/dist/runtime/composables/useConvexConfig.js +7 -0
- package/dist/runtime/composables/useConvexConnectionState.d.ts +33 -0
- package/dist/runtime/composables/useConvexConnectionState.js +24 -0
- package/dist/runtime/composables/useConvexFileUpload.d.ts +169 -0
- package/dist/runtime/composables/useConvexFileUpload.js +198 -0
- package/dist/runtime/composables/useConvexMutation.d.ts +13 -0
- package/dist/runtime/composables/useConvexMutation.js +24 -0
- package/dist/runtime/composables/useConvexPaginatedQuery.d.ts +13 -0
- package/dist/runtime/composables/useConvexPaginatedQuery.js +256 -0
- package/dist/runtime/composables/useConvexQuery.d.ts +19 -0
- package/dist/runtime/composables/useConvexQuery.js +308 -0
- package/dist/runtime/convex-auth/adapter/create-adapter.d.ts +33 -0
- package/dist/runtime/convex-auth/adapter/create-adapter.js +308 -0
- package/dist/runtime/convex-auth/adapter/define-functions.d.ts +160 -0
- package/dist/runtime/convex-auth/adapter/define-functions.js +497 -0
- package/dist/runtime/convex-auth/adapter/generate-schema.d.ts +16 -0
- package/dist/runtime/convex-auth/adapter/generate-schema.js +332 -0
- package/dist/runtime/convex-auth/adapter/metadata.d.ts +44 -0
- package/dist/runtime/convex-auth/adapter/metadata.js +91 -0
- package/dist/runtime/convex-auth/adapter/query.d.ts +32 -0
- package/dist/runtime/convex-auth/adapter/query.js +275 -0
- package/dist/runtime/convex-auth/adapter/relationships.d.ts +20 -0
- package/dist/runtime/convex-auth/adapter/relationships.js +128 -0
- package/dist/runtime/convex-auth/component/_generated/api.js +5 -0
- package/dist/runtime/convex-auth/component/_generated/component.d.ts +116 -0
- package/dist/runtime/convex-auth/component/_generated/component.js +0 -0
- package/dist/runtime/convex-auth/component/_generated/dataModel.js +0 -0
- package/dist/runtime/convex-auth/component/_generated/server.js +16 -0
- package/dist/runtime/convex-auth/component/adapter.d.ts +142 -0
- package/dist/runtime/convex-auth/component/adapter.js +16 -0
- package/dist/runtime/convex-auth/component/convex.config.d.ts +2 -0
- package/dist/runtime/convex-auth/component/convex.config.js +3 -0
- package/dist/runtime/convex-auth/component/schema.d.ts +799 -0
- package/dist/runtime/convex-auth/component/schema.js +180 -0
- package/dist/runtime/convex-auth/component/schemaMetadata.d.ts +2094 -0
- package/dist/runtime/convex-auth/component/schemaMetadata.js +2161 -0
- package/dist/runtime/convex-auth/context.d.ts +5 -0
- package/dist/runtime/convex-auth/context.js +8 -0
- package/dist/runtime/convex-auth/create-auth-component.d.ts +41 -0
- package/dist/runtime/convex-auth/create-auth-component.js +157 -0
- package/dist/runtime/convex-auth/index.d.ts +11 -0
- package/dist/runtime/convex-auth/index.js +7 -0
- package/dist/runtime/convex-auth/internal-session.d.ts +2 -0
- package/dist/runtime/convex-auth/internal-session.js +1 -0
- package/dist/runtime/convex-auth/jwks-rotation.d.ts +58 -0
- package/dist/runtime/convex-auth/jwks-rotation.js +163 -0
- package/dist/runtime/convex-auth/oauth-live-access.d.ts +17 -0
- package/dist/runtime/convex-auth/oauth-live-access.js +78 -0
- package/dist/runtime/convex-auth/oauth-provider-compat.d.ts +10 -0
- package/dist/runtime/convex-auth/oauth-provider-compat.js +170 -0
- package/dist/runtime/convex-auth/oauth-resource.d.ts +39 -0
- package/dist/runtime/convex-auth/oauth-resource.js +118 -0
- package/dist/runtime/convex-auth/oauth-security.d.ts +82 -0
- package/dist/runtime/convex-auth/oauth-security.js +454 -0
- package/dist/runtime/convex-auth/origin.d.ts +4 -0
- package/dist/runtime/convex-auth/origin.js +9 -0
- package/dist/runtime/convex-auth/plugin.d.ts +18 -0
- package/dist/runtime/convex-auth/plugin.js +605 -0
- package/dist/runtime/convex-auth/provider.d.ts +2 -0
- package/dist/runtime/convex-auth/provider.js +11 -0
- package/dist/runtime/convex-auth/test.d.ts +410 -0
- package/dist/runtime/convex-auth/test.js +9 -0
- package/dist/runtime/convex-auth/types.d.ts +18 -0
- package/dist/runtime/convex-auth/types.js +0 -0
- package/dist/runtime/convex-auth/user-projection.d.ts +106 -0
- package/dist/runtime/convex-auth/user-projection.js +128 -0
- package/dist/runtime/devtools/auth-proxy-registry.d.ts +4 -0
- package/dist/runtime/devtools/auth-proxy-registry.js +47 -0
- package/dist/runtime/devtools/bridge-setup.d.ts +21 -0
- package/dist/runtime/devtools/bridge-setup.js +165 -0
- package/dist/runtime/devtools/path-utils.d.ts +2 -0
- package/dist/runtime/devtools/path-utils.js +12 -0
- package/dist/runtime/devtools/server.d.ts +2 -0
- package/dist/runtime/devtools/server.js +108 -0
- package/dist/runtime/devtools/setup-client.d.ts +16 -0
- package/dist/runtime/devtools/setup-client.js +43 -0
- package/dist/runtime/devtools/sink.d.ts +18 -0
- package/dist/runtime/devtools/sink.js +110 -0
- package/dist/runtime/devtools/transport.d.ts +16 -0
- package/dist/runtime/devtools/transport.js +149 -0
- package/dist/runtime/devtools/types.d.ts +167 -0
- package/dist/runtime/devtools/types.js +1 -0
- package/dist/runtime/devtools/ui/dist/200.html +1 -0
- package/dist/runtime/devtools/ui/dist/404.html +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/C4MASuCh.js +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/DJHkqtZq.js +3 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/DWwrCltl.js +2 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/DZ_UiYoH.js +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/_m9ujb-i.js +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/builds/latest.json +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/builds/meta/better-convex-nuxt-devtools.json +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/entry.DpFDKYZ5.css +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/error-404.DYUJo5xt.css +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/error-500.DQdoJJXg.css +1 -0
- package/dist/runtime/devtools/ui/dist/convex-logo.svg +5 -0
- package/dist/runtime/devtools/ui/dist/index.html +1 -0
- package/dist/runtime/errors/index.d.ts +7 -0
- package/dist/runtime/errors/index.js +5 -0
- package/dist/runtime/middleware/convex-auth.global.d.ts +2 -0
- package/dist/runtime/middleware/convex-auth.global.js +31 -0
- package/dist/runtime/plugin.auth.client.d.ts +3 -0
- package/dist/runtime/plugin.auth.client.js +217 -0
- package/dist/runtime/plugin.client.d.ts +3 -0
- package/dist/runtime/plugin.client.js +43 -0
- package/dist/runtime/plugin.server.d.ts +12 -0
- package/dist/runtime/plugin.server.js +98 -0
- package/dist/runtime/plugins/convex-call-error-payload.d.ts +14 -0
- package/dist/runtime/plugins/convex-call-error-payload.js +12 -0
- package/dist/runtime/runtime-context.d.ts +26 -0
- package/dist/runtime/runtime-context.js +48 -0
- package/dist/runtime/server/api/auth/[...].d.ts +10 -0
- package/dist/runtime/server/api/auth/[...].js +509 -0
- package/dist/runtime/server/api/auth/authorization-server-metadata.d.ts +2 -0
- package/dist/runtime/server/api/auth/authorization-server-metadata.js +6 -0
- package/dist/runtime/server/api/auth/body-size.d.ts +15 -0
- package/dist/runtime/server/api/auth/body-size.js +179 -0
- package/dist/runtime/server/api/auth/headers.d.ts +6 -0
- package/dist/runtime/server/api/auth/headers.js +98 -0
- package/dist/runtime/server/api/auth/jwks.d.ts +2 -0
- package/dist/runtime/server/api/auth/jwks.js +6 -0
- package/dist/runtime/server/api/auth/security.d.ts +14 -0
- package/dist/runtime/server/api/auth/security.js +85 -0
- package/dist/runtime/server/api/release-fingerprint.d.ts +2 -0
- package/dist/runtime/server/api/release-fingerprint.js +36 -0
- package/dist/runtime/server/index.d.ts +1 -0
- package/dist/runtime/server/index.js +3 -0
- package/dist/runtime/server/plugins/runtime-config.d.ts +7 -0
- package/dist/runtime/server/plugins/runtime-config.js +7 -0
- package/dist/runtime/server/utils/auth-snapshot.d.ts +26 -0
- package/dist/runtime/server/utils/auth-snapshot.js +165 -0
- package/dist/runtime/server/utils/http.d.ts +16 -0
- package/dist/runtime/server/utils/http.js +133 -0
- package/dist/runtime/server/utils/server-convex-caller.d.ts +31 -0
- package/dist/runtime/server/utils/server-convex-caller.js +151 -0
- package/dist/runtime/server/utils/server-convex-options.d.ts +83 -0
- package/dist/runtime/server/utils/server-convex-options.js +69 -0
- package/dist/runtime/server/utils/signed-client-ip.d.ts +6 -0
- package/dist/runtime/server/utils/signed-client-ip.js +29 -0
- package/dist/runtime/server/utils/ssr-auth-headers.d.ts +26 -0
- package/dist/runtime/server/utils/ssr-auth-headers.js +49 -0
- package/dist/runtime/server/utils/token-exchange.d.ts +27 -0
- package/dist/runtime/server/utils/token-exchange.js +96 -0
- package/dist/runtime/shared/auth-cookie.d.ts +4 -0
- package/dist/runtime/shared/auth-cookie.js +25 -0
- package/dist/runtime/shared/auth-origin.d.ts +15 -0
- package/dist/runtime/shared/auth-origin.js +42 -0
- package/dist/runtime/shared/bounded-stream.d.ts +2 -0
- package/dist/runtime/shared/bounded-stream.js +50 -0
- package/dist/runtime/shared/client-ip.d.ts +21 -0
- package/dist/runtime/shared/client-ip.js +123 -0
- package/dist/runtime/shared/release-fingerprint.d.ts +5 -0
- package/dist/runtime/shared/release-fingerprint.js +17 -0
- package/dist/runtime/types.d.ts +19 -0
- package/dist/runtime/utils/auth-config.d.ts +24 -0
- package/dist/runtime/utils/auth-config.js +45 -0
- package/dist/runtime/utils/auth-contract.d.ts +22 -0
- package/dist/runtime/utils/auth-contract.js +0 -0
- package/dist/runtime/utils/auth-errors.d.ts +5 -0
- package/dist/runtime/utils/auth-errors.js +22 -0
- package/dist/runtime/utils/auth-identity-state.d.ts +4 -0
- package/dist/runtime/utils/auth-identity-state.js +8 -0
- package/dist/runtime/utils/auth-pending-state.d.ts +13 -0
- package/dist/runtime/utils/auth-pending-state.js +4 -0
- package/dist/runtime/utils/auth-pending.d.ts +10 -0
- package/dist/runtime/utils/auth-pending.js +30 -0
- package/dist/runtime/utils/auth-route-protection.d.ts +16 -0
- package/dist/runtime/utils/auth-route-protection.js +58 -0
- package/dist/runtime/utils/auth-status.d.ts +47 -0
- package/dist/runtime/utils/auth-status.js +8 -0
- package/dist/runtime/utils/bounded-convex-fetch.d.ts +18 -0
- package/dist/runtime/utils/bounded-convex-fetch.js +152 -0
- package/dist/runtime/utils/callable-devtools.d.ts +17 -0
- package/dist/runtime/utils/callable-devtools.js +49 -0
- package/dist/runtime/utils/callable-observer.d.ts +2 -0
- package/dist/runtime/utils/callable-observer.js +1 -0
- package/dist/runtime/utils/composable-scope.d.ts +2 -0
- package/dist/runtime/utils/composable-scope.js +8 -0
- package/dist/runtime/utils/config-defaults.d.ts +8 -0
- package/dist/runtime/utils/config-defaults.js +8 -0
- package/dist/runtime/utils/convex-cache.d.ts +68 -0
- package/dist/runtime/utils/convex-cache.js +47 -0
- package/dist/runtime/utils/convex-config.d.ts +16 -0
- package/dist/runtime/utils/convex-config.js +50 -0
- package/dist/runtime/utils/convex-shared.d.ts +67 -0
- package/dist/runtime/utils/convex-shared.js +134 -0
- package/dist/runtime/utils/identity-changed-error.d.ts +4 -0
- package/dist/runtime/utils/identity-changed-error.js +12 -0
- package/dist/runtime/utils/identity-key.d.ts +19 -0
- package/dist/runtime/utils/identity-key.js +10 -0
- package/dist/runtime/utils/integrated-auth-client.d.ts +5 -0
- package/dist/runtime/utils/integrated-auth-client.js +0 -0
- package/dist/runtime/utils/logger.d.ts +42 -0
- package/dist/runtime/utils/logger.js +190 -0
- package/dist/runtime/utils/mime-type.d.ts +33 -0
- package/dist/runtime/utils/mime-type.js +10 -0
- package/dist/runtime/utils/query-execution-gate.d.ts +47 -0
- package/dist/runtime/utils/query-execution-gate.js +63 -0
- package/dist/runtime/utils/query-execution.d.ts +8 -0
- package/dist/runtime/utils/query-execution.js +19 -0
- package/dist/runtime/utils/query-foundation.d.ts +20 -0
- package/dist/runtime/utils/query-foundation.js +39 -0
- package/dist/runtime/utils/query-state.d.ts +12 -0
- package/dist/runtime/utils/query-state.js +12 -0
- package/dist/runtime/utils/reactive-args.d.ts +5 -0
- package/dist/runtime/utils/reactive-args.js +42 -0
- package/dist/runtime/utils/runtime-config-normalize.d.ts +22 -0
- package/dist/runtime/utils/runtime-config-normalize.js +28 -0
- package/dist/runtime/utils/runtime-config.d.ts +4 -0
- package/dist/runtime/utils/runtime-config.js +8 -0
- package/dist/runtime/utils/sanitize-diagnostic.d.ts +6 -0
- package/dist/runtime/utils/sanitize-diagnostic.js +81 -0
- package/dist/runtime/utils/shared-helpers.d.ts +46 -0
- package/dist/runtime/utils/shared-helpers.js +139 -0
- package/dist/runtime/utils/site-url.d.ts +4 -0
- package/dist/runtime/utils/site-url.js +15 -0
- package/dist/runtime/utils/ssr-pagination-state.d.ts +3 -0
- package/dist/runtime/utils/ssr-pagination-state.js +3 -0
- package/dist/runtime/utils/types.d.ts +29 -0
- package/dist/runtime/utils/types.js +0 -0
- package/dist/runtime/utils/upload-core.d.ts +16 -0
- package/dist/runtime/utils/upload-core.js +116 -0
- package/dist/types.d.mts +21 -0
- package/package.json +253 -0
- package/security/upstream-convex-better-auth.json +477 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
function resolveAuthIdField(field) {
|
|
2
|
+
const resolved = field ?? "authId";
|
|
3
|
+
const startsWithReservedPrefix = resolved.startsWith("$") || resolved.startsWith("_");
|
|
4
|
+
let containsInvalidCharacter = false;
|
|
5
|
+
for (let index = 0; index < resolved.length && index <= 1024; index += 1) {
|
|
6
|
+
const code = resolved.charCodeAt(index);
|
|
7
|
+
if (code < 32 || code >= 127) {
|
|
8
|
+
containsInvalidCharacter = true;
|
|
9
|
+
break;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if (resolved.length === 0 || resolved.length > 1024 || startsWithReservedPrefix || containsInvalidCharacter || resolved.includes(".") || resolved === "prototype" || resolved === "constructor") {
|
|
13
|
+
throw new TypeError(
|
|
14
|
+
"[better-convex-nuxt] authIdField must be a valid top-level Convex application field"
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return resolved;
|
|
18
|
+
}
|
|
19
|
+
function withCanonicalAuthId(value, authIdField, authUserId) {
|
|
20
|
+
return { ...value, [authIdField]: authUserId };
|
|
21
|
+
}
|
|
22
|
+
async function findExistingByAuthId(ctx, lookup, authUserId) {
|
|
23
|
+
const query = ctx.db.query(lookup.table);
|
|
24
|
+
return await query.withIndex(
|
|
25
|
+
lookup.index,
|
|
26
|
+
(q) => q.eq(lookup.authIdField, authUserId)
|
|
27
|
+
).collect();
|
|
28
|
+
}
|
|
29
|
+
async function deleteDuplicateRows(ctx, existing) {
|
|
30
|
+
for (const duplicate of existing.slice(1)) {
|
|
31
|
+
await ctx.db.delete(duplicate._id);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function createUserProjectionTriggers(options) {
|
|
35
|
+
const lookup = Object.freeze({
|
|
36
|
+
table: options.table,
|
|
37
|
+
index: options.index,
|
|
38
|
+
authIdField: resolveAuthIdField(options.authIdField)
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
user: {
|
|
42
|
+
onCreate: async (ctx, user) => {
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
const existing = await findExistingByAuthId(ctx, lookup, user.id);
|
|
45
|
+
if (existing.length > 0) {
|
|
46
|
+
await deleteDuplicateRows(ctx, existing);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const doc = await options.createDoc({ ctx, user, now });
|
|
50
|
+
await ctx.db.insert(options.table, withCanonicalAuthId(doc, lookup.authIdField, user.id));
|
|
51
|
+
},
|
|
52
|
+
/**
|
|
53
|
+
* Patches the projected row for a Better Auth user update.
|
|
54
|
+
*
|
|
55
|
+
* If `onUpdate` fires before the corresponding `onCreate` has run
|
|
56
|
+
* for this user (e.g. out-of-order trigger delivery, or a webhook race),
|
|
57
|
+
* `findExistingByAuthId` finds nothing and this silently no-ops — the
|
|
58
|
+
* update is dropped rather than queued or retried. The row is created
|
|
59
|
+
* later by `onCreate`, but using `createDoc`'s snapshot at that later
|
|
60
|
+
* time, not the fields this update carried. If your projection needs
|
|
61
|
+
* updates to survive arriving before creation, either make `createDoc`
|
|
62
|
+
* derive from the latest known user state (not just the `onCreate`
|
|
63
|
+
* event's payload) or run `rebuild()` periodically to reconcile drift.
|
|
64
|
+
*/
|
|
65
|
+
onUpdate: async (ctx, user, previousUser) => {
|
|
66
|
+
const existing = await findExistingByAuthId(ctx, lookup, user.id);
|
|
67
|
+
const [retained] = existing;
|
|
68
|
+
if (!retained) return;
|
|
69
|
+
await deleteDuplicateRows(ctx, existing);
|
|
70
|
+
if (!options.patchDoc) return;
|
|
71
|
+
const patch = await options.patchDoc({
|
|
72
|
+
ctx,
|
|
73
|
+
user,
|
|
74
|
+
previousUser,
|
|
75
|
+
existing: retained,
|
|
76
|
+
now: Date.now()
|
|
77
|
+
});
|
|
78
|
+
if (!patch || Object.keys(patch).length === 0) return;
|
|
79
|
+
await ctx.db.patch(retained._id, withCanonicalAuthId(patch, lookup.authIdField, user.id));
|
|
80
|
+
},
|
|
81
|
+
onDelete: async (ctx, user) => {
|
|
82
|
+
const existing = await findExistingByAuthId(ctx, lookup, user.id);
|
|
83
|
+
for (const row of existing) {
|
|
84
|
+
await ctx.db.delete(row._id);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
rebuild: async (ctx, users) => {
|
|
88
|
+
const result = {
|
|
89
|
+
inserted: 0,
|
|
90
|
+
patched: 0,
|
|
91
|
+
skipped: 0
|
|
92
|
+
};
|
|
93
|
+
for (const user of users) {
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
const existing = await findExistingByAuthId(ctx, lookup, user.id);
|
|
96
|
+
const [retained] = existing;
|
|
97
|
+
if (!retained) {
|
|
98
|
+
const doc = await options.createDoc({ ctx, user, now });
|
|
99
|
+
await ctx.db.insert(
|
|
100
|
+
options.table,
|
|
101
|
+
withCanonicalAuthId(doc, lookup.authIdField, user.id)
|
|
102
|
+
);
|
|
103
|
+
result.inserted += 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
await deleteDuplicateRows(ctx, existing);
|
|
107
|
+
if (!options.rebuildDoc) {
|
|
108
|
+
result.skipped += 1;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const patch = await options.rebuildDoc({
|
|
112
|
+
ctx,
|
|
113
|
+
user,
|
|
114
|
+
existing: retained,
|
|
115
|
+
now
|
|
116
|
+
});
|
|
117
|
+
if (!patch || Object.keys(patch).length === 0) {
|
|
118
|
+
result.skipped += 1;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
await ctx.db.patch(retained._id, withCanonicalAuthId(patch, lookup.authIdField, user.id));
|
|
122
|
+
result.patched += 1;
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AuthProxyRequest, AuthProxyStats } from './types.js';
|
|
2
|
+
export declare function recordAuthProxyRequest(request: AuthProxyRequest): Promise<void>;
|
|
3
|
+
export declare function getAuthProxyStats(): Promise<AuthProxyStats>;
|
|
4
|
+
export declare function clearAuthProxyStats(): Promise<void>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const MAX_REQUESTS = 20;
|
|
2
|
+
const STORAGE_NAMESPACE = "devtools:convex:auth-proxy";
|
|
3
|
+
const STORAGE_KEY = "requests";
|
|
4
|
+
async function getStorage() {
|
|
5
|
+
const { useStorage } = await import("nitropack/runtime");
|
|
6
|
+
return useStorage(STORAGE_NAMESPACE);
|
|
7
|
+
}
|
|
8
|
+
async function getRequests() {
|
|
9
|
+
const storage = await getStorage();
|
|
10
|
+
const requests = await storage.getItem(STORAGE_KEY);
|
|
11
|
+
return Array.isArray(requests) ? requests : [];
|
|
12
|
+
}
|
|
13
|
+
async function setRequests(requests) {
|
|
14
|
+
const storage = await getStorage();
|
|
15
|
+
await storage.setItem(STORAGE_KEY, requests);
|
|
16
|
+
}
|
|
17
|
+
export async function recordAuthProxyRequest(request) {
|
|
18
|
+
const requests = await getRequests();
|
|
19
|
+
requests.unshift({
|
|
20
|
+
id: request.id,
|
|
21
|
+
path: request.path,
|
|
22
|
+
method: request.method,
|
|
23
|
+
timestamp: request.timestamp,
|
|
24
|
+
...request.status === void 0 ? {} : { status: request.status },
|
|
25
|
+
...request.duration === void 0 ? {} : { duration: request.duration },
|
|
26
|
+
...request.success === void 0 ? {} : { success: request.success }
|
|
27
|
+
});
|
|
28
|
+
if (requests.length > MAX_REQUESTS) {
|
|
29
|
+
requests.length = MAX_REQUESTS;
|
|
30
|
+
}
|
|
31
|
+
await setRequests(requests);
|
|
32
|
+
}
|
|
33
|
+
export async function getAuthProxyStats() {
|
|
34
|
+
const requests = await getRequests();
|
|
35
|
+
const successful = requests.filter((r) => r.success);
|
|
36
|
+
const durations = successful.filter((r) => r.duration !== void 0).map((r) => r.duration);
|
|
37
|
+
return {
|
|
38
|
+
totalRequests: requests.length,
|
|
39
|
+
successCount: successful.length,
|
|
40
|
+
errorCount: requests.filter((r) => !r.success).length,
|
|
41
|
+
avgDuration: durations.length > 0 ? Math.round(durations.reduce((a, b) => a + b, 0) / durations.length) : 0,
|
|
42
|
+
recentRequests: [...requests]
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export async function clearAuthProxyStats() {
|
|
46
|
+
await setRequests([]);
|
|
47
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevTools bridge setup for the Nuxt DevTools integration.
|
|
3
|
+
*
|
|
4
|
+
* This module sets up the communication bridge between the main window
|
|
5
|
+
* and the DevTools iframe using BroadcastChannel.
|
|
6
|
+
*/
|
|
7
|
+
import type { Ref } from 'vue';
|
|
8
|
+
import type { DevtoolsSink } from './sink.js';
|
|
9
|
+
import type { AuthWaterfall, ConnectionState } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Setup the DevTools bridge on the window object.
|
|
12
|
+
* Only called in dev mode from plugin.client.ts.
|
|
13
|
+
*
|
|
14
|
+
* @param sink - Per-app bounded diagnostics store
|
|
15
|
+
* @param convexToken - Ref to the current auth token
|
|
16
|
+
* @param convexUser - Ref to the current user data
|
|
17
|
+
* @param convexAuthWaterfall - Ref to the SSR auth waterfall timing data
|
|
18
|
+
* @param readConnectionState - Safe projection of the active Vue runtime connection
|
|
19
|
+
* @param providedInstanceId - Stable application identifier used for explicit UI selection
|
|
20
|
+
*/
|
|
21
|
+
export declare function setupDevToolsBridge(sink: DevtoolsSink, convexToken: Ref<string | null>, convexUser: Ref<unknown>, convexAuthWaterfall: Ref<AuthWaterfall | null>, readConnectionState: () => ConnectionState, providedInstanceId?: string): Promise<() => void>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { toRaw } from "vue";
|
|
2
|
+
import { decodeJwtPayload } from "../utils/convex-shared.js";
|
|
3
|
+
import { createAppDevtoolsTransport, cloneDevtoolsPayload } from "./transport.js";
|
|
4
|
+
export async function setupDevToolsBridge(sink, convexToken, convexUser, convexAuthWaterfall, readConnectionState, providedInstanceId) {
|
|
5
|
+
const decodeJWT = (token) => {
|
|
6
|
+
return decodeJwtPayload(token);
|
|
7
|
+
};
|
|
8
|
+
const bridge = {
|
|
9
|
+
version: "1.1.0",
|
|
10
|
+
getQueries: () => sink.getQueries(),
|
|
11
|
+
getQueryDetail: (id) => sink.getQuery(id),
|
|
12
|
+
subscribeToQueries: (callback) => sink.subscribeToQueries(callback),
|
|
13
|
+
getMutations: () => sink.getMutations(),
|
|
14
|
+
subscribeToMutations: (callback) => sink.subscribeToMutations(callback),
|
|
15
|
+
getAuthState: () => {
|
|
16
|
+
const rawUser = toRaw(convexUser.value);
|
|
17
|
+
const hasToken = !!convexToken.value;
|
|
18
|
+
const hasUser = !!(rawUser && typeof rawUser === "object" && (rawUser.id || rawUser.email));
|
|
19
|
+
const plainUser = hasUser ? cloneDevtoolsPayload(rawUser) : null;
|
|
20
|
+
return {
|
|
21
|
+
isAuthenticated: !!(hasToken && hasUser),
|
|
22
|
+
isPending: false,
|
|
23
|
+
// Could be enhanced to track pending state
|
|
24
|
+
user: plainUser,
|
|
25
|
+
tokenStatus: hasToken ? "valid" : "none"
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
getEnhancedAuthState: () => {
|
|
29
|
+
const baseState = bridge.getAuthState();
|
|
30
|
+
const token = convexToken.value;
|
|
31
|
+
if (!token) {
|
|
32
|
+
return {
|
|
33
|
+
...baseState,
|
|
34
|
+
claims: void 0,
|
|
35
|
+
issuedAt: void 0,
|
|
36
|
+
expiresAt: void 0,
|
|
37
|
+
expiresInSeconds: void 0
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const claims = decodeJWT(token);
|
|
41
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
42
|
+
const expiresAt = claims?.exp;
|
|
43
|
+
const expiresInSeconds = expiresAt ? Math.max(0, expiresAt - now) : void 0;
|
|
44
|
+
return {
|
|
45
|
+
...baseState,
|
|
46
|
+
claims: claims ?? void 0,
|
|
47
|
+
issuedAt: claims?.iat ? claims.iat * 1e3 : void 0,
|
|
48
|
+
expiresAt: expiresAt ? expiresAt * 1e3 : void 0,
|
|
49
|
+
expiresInSeconds
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
getConnectionState: readConnectionState,
|
|
53
|
+
getAuthWaterfall: () => {
|
|
54
|
+
const waterfall = convexAuthWaterfall.value;
|
|
55
|
+
if (!waterfall) return null;
|
|
56
|
+
return cloneDevtoolsPayload(toRaw(waterfall));
|
|
57
|
+
},
|
|
58
|
+
getAuthProxyStats: async () => {
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch("/__convex_devtools__/proxy-stats");
|
|
61
|
+
if (!response.ok) return null;
|
|
62
|
+
return await response.json();
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const instanceId = providedInstanceId ?? Math.random().toString(36).slice(2, 10);
|
|
69
|
+
const transport = createAppDevtoolsTransport("convex-devtools");
|
|
70
|
+
const onMessage = (event) => {
|
|
71
|
+
const data = event.data;
|
|
72
|
+
if (!data || typeof data !== "object") return;
|
|
73
|
+
const message = data;
|
|
74
|
+
if (message.type === "CONVEX_DEVTOOLS_INIT") {
|
|
75
|
+
transport.postMessage({
|
|
76
|
+
type: "CONVEX_DEVTOOLS_READY",
|
|
77
|
+
instanceId,
|
|
78
|
+
transport: transport.kind
|
|
79
|
+
});
|
|
80
|
+
} else if (message.type === "CONVEX_DEVTOOLS_REQUEST") {
|
|
81
|
+
if (message.instanceId && message.instanceId !== instanceId) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const { id, method, args } = message;
|
|
85
|
+
if (typeof id !== "number" || typeof method !== "string") {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const bridgeMethod = bridge[method];
|
|
90
|
+
if (typeof bridgeMethod === "function") {
|
|
91
|
+
Promise.resolve(bridgeMethod(...args || [])).then((result) => {
|
|
92
|
+
transport.postMessage({
|
|
93
|
+
type: "CONVEX_DEVTOOLS_RESPONSE",
|
|
94
|
+
id,
|
|
95
|
+
result,
|
|
96
|
+
instanceId,
|
|
97
|
+
transport: transport.kind
|
|
98
|
+
});
|
|
99
|
+
}).catch((err) => {
|
|
100
|
+
transport.postMessage({
|
|
101
|
+
type: "CONVEX_DEVTOOLS_RESPONSE",
|
|
102
|
+
id,
|
|
103
|
+
error: err instanceof Error ? err.message : String(err),
|
|
104
|
+
instanceId,
|
|
105
|
+
transport: transport.kind
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
} else if (bridgeMethod !== void 0) {
|
|
109
|
+
transport.postMessage({
|
|
110
|
+
type: "CONVEX_DEVTOOLS_RESPONSE",
|
|
111
|
+
id,
|
|
112
|
+
result: bridgeMethod,
|
|
113
|
+
instanceId,
|
|
114
|
+
transport: transport.kind
|
|
115
|
+
});
|
|
116
|
+
} else {
|
|
117
|
+
transport.postMessage({
|
|
118
|
+
type: "CONVEX_DEVTOOLS_RESPONSE",
|
|
119
|
+
id,
|
|
120
|
+
error: `Unknown method: ${method}`,
|
|
121
|
+
instanceId,
|
|
122
|
+
transport: transport.kind
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
} catch (err) {
|
|
126
|
+
transport.postMessage({
|
|
127
|
+
type: "CONVEX_DEVTOOLS_RESPONSE",
|
|
128
|
+
id,
|
|
129
|
+
error: err instanceof Error ? err.message : String(err),
|
|
130
|
+
instanceId,
|
|
131
|
+
transport: transport.kind
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
transport.addEventListener("message", onMessage);
|
|
137
|
+
const unsubscribeMutations = sink.subscribeToMutations((mutations) => {
|
|
138
|
+
transport.postMessage({
|
|
139
|
+
type: "CONVEX_DEVTOOLS_MUTATIONS",
|
|
140
|
+
mutations,
|
|
141
|
+
instanceId,
|
|
142
|
+
transport: transport.kind
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
const unsubscribeQueries = sink.subscribeToQueries((queries) => {
|
|
146
|
+
transport.postMessage({
|
|
147
|
+
type: "CONVEX_DEVTOOLS_QUERIES",
|
|
148
|
+
queries,
|
|
149
|
+
instanceId,
|
|
150
|
+
transport: transport.kind
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
let disposed = false;
|
|
154
|
+
const dispose = () => {
|
|
155
|
+
if (disposed) return;
|
|
156
|
+
disposed = true;
|
|
157
|
+
unsubscribeMutations();
|
|
158
|
+
unsubscribeQueries();
|
|
159
|
+
transport.removeEventListener("message", onMessage);
|
|
160
|
+
transport.close();
|
|
161
|
+
};
|
|
162
|
+
const hot = import.meta.hot;
|
|
163
|
+
hot?.dispose(dispose);
|
|
164
|
+
return dispose;
|
|
165
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
2
|
+
export function resolveDevtoolsFilePath(outputDir, pathname) {
|
|
3
|
+
const outputDirResolved = resolve(outputDir);
|
|
4
|
+
return resolve(join(outputDirResolved, pathname));
|
|
5
|
+
}
|
|
6
|
+
export function isPathInsideDirectory(rootDir, filePath) {
|
|
7
|
+
const rootResolved = resolve(rootDir);
|
|
8
|
+
const fileResolved = resolve(filePath);
|
|
9
|
+
const rel = relative(rootResolved, fileResolved);
|
|
10
|
+
if (!rel) return false;
|
|
11
|
+
return !rel.startsWith("..") && !rel.includes(`${sep}..`);
|
|
12
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { defineEventHandler, setHeader, getRequestURL, createError } from "h3";
|
|
3
|
+
import { useRuntimeConfig } from "#imports";
|
|
4
|
+
import { getAuthProxyStats, clearAuthProxyStats } from "./auth-proxy-registry.js";
|
|
5
|
+
import { isPathInsideDirectory, resolveDevtoolsFilePath } from "./path-utils.js";
|
|
6
|
+
const mimeTypes = {
|
|
7
|
+
".html": "text/html",
|
|
8
|
+
".js": "application/javascript",
|
|
9
|
+
".mjs": "application/javascript",
|
|
10
|
+
".css": "text/css",
|
|
11
|
+
".json": "application/json",
|
|
12
|
+
".png": "image/png",
|
|
13
|
+
".svg": "image/svg+xml",
|
|
14
|
+
".ico": "image/x-icon",
|
|
15
|
+
".woff": "font/woff",
|
|
16
|
+
".woff2": "font/woff2"
|
|
17
|
+
};
|
|
18
|
+
function getMimeType(path) {
|
|
19
|
+
const ext = path.substring(path.lastIndexOf("."));
|
|
20
|
+
return mimeTypes[ext] || "application/octet-stream";
|
|
21
|
+
}
|
|
22
|
+
export default defineEventHandler(async (event) => {
|
|
23
|
+
if (!import.meta.dev) {
|
|
24
|
+
throw createError({ statusCode: 404, statusMessage: "Not Found" });
|
|
25
|
+
}
|
|
26
|
+
const config = useRuntimeConfig();
|
|
27
|
+
const outputDir = typeof config.convexDevtoolsPath === "string" ? config.convexDevtoolsPath : void 0;
|
|
28
|
+
const url = getRequestURL(event);
|
|
29
|
+
let pathname = url.pathname.replace("/__convex_devtools__", "") || "/";
|
|
30
|
+
if (pathname === "/proxy-stats") {
|
|
31
|
+
setHeader(event, "Content-Type", "application/json");
|
|
32
|
+
setHeader(event, "Cache-Control", "no-cache");
|
|
33
|
+
return JSON.stringify(await getAuthProxyStats());
|
|
34
|
+
}
|
|
35
|
+
if (pathname === "/proxy-stats/clear" && event.method === "POST") {
|
|
36
|
+
await clearAuthProxyStats();
|
|
37
|
+
setHeader(event, "Content-Type", "application/json");
|
|
38
|
+
return JSON.stringify({ success: true });
|
|
39
|
+
}
|
|
40
|
+
if (pathname === "/" || pathname === "") {
|
|
41
|
+
pathname = "/index.html";
|
|
42
|
+
}
|
|
43
|
+
if (!outputDir || !existsSync(outputDir)) {
|
|
44
|
+
if (pathname === "/index.html") {
|
|
45
|
+
setHeader(event, "Content-Type", "text/html");
|
|
46
|
+
return getFallbackHtml();
|
|
47
|
+
}
|
|
48
|
+
throw createError({ statusCode: 404, statusMessage: "Not Found" });
|
|
49
|
+
}
|
|
50
|
+
const filePath = resolveDevtoolsFilePath(outputDir, pathname);
|
|
51
|
+
if (!isPathInsideDirectory(outputDir, filePath)) {
|
|
52
|
+
throw createError({ statusCode: 403, statusMessage: "Forbidden" });
|
|
53
|
+
}
|
|
54
|
+
if (!existsSync(filePath)) {
|
|
55
|
+
if (pathname === "/index.html") {
|
|
56
|
+
setHeader(event, "Content-Type", "text/html");
|
|
57
|
+
return getFallbackHtml();
|
|
58
|
+
}
|
|
59
|
+
throw createError({ statusCode: 404, statusMessage: "Not Found" });
|
|
60
|
+
}
|
|
61
|
+
const content = readFileSync(filePath);
|
|
62
|
+
setHeader(event, "Content-Type", getMimeType(pathname));
|
|
63
|
+
setHeader(event, "Cache-Control", "no-cache");
|
|
64
|
+
return content;
|
|
65
|
+
});
|
|
66
|
+
function getFallbackHtml() {
|
|
67
|
+
return `<!DOCTYPE html>
|
|
68
|
+
<html lang="en">
|
|
69
|
+
<head>
|
|
70
|
+
<meta charset="UTF-8">
|
|
71
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
72
|
+
<title>Convex DevTools</title>
|
|
73
|
+
<style>
|
|
74
|
+
body {
|
|
75
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
76
|
+
background: #1e1e1e;
|
|
77
|
+
color: #cccccc;
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
justify-content: center;
|
|
81
|
+
height: 100vh;
|
|
82
|
+
margin: 0;
|
|
83
|
+
}
|
|
84
|
+
.message {
|
|
85
|
+
text-align: center;
|
|
86
|
+
padding: 40px;
|
|
87
|
+
}
|
|
88
|
+
.message h1 {
|
|
89
|
+
color: #ee8944;
|
|
90
|
+
margin-bottom: 16px;
|
|
91
|
+
}
|
|
92
|
+
code {
|
|
93
|
+
background: #252526;
|
|
94
|
+
padding: 4px 8px;
|
|
95
|
+
border-radius: 4px;
|
|
96
|
+
font-size: 13px;
|
|
97
|
+
}
|
|
98
|
+
</style>
|
|
99
|
+
</head>
|
|
100
|
+
<body>
|
|
101
|
+
<div class="message">
|
|
102
|
+
<h1>DevTools Not Built</h1>
|
|
103
|
+
<p>The DevTools UI needs to be built first.</p>
|
|
104
|
+
<p style="margin-top: 16px;">Run: <code>pnpm run build:devtools</code></p>
|
|
105
|
+
</div>
|
|
106
|
+
</body>
|
|
107
|
+
</html>`;
|
|
108
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
|
+
import type { ConvexRuntimeContext } from '../runtime-context.js';
|
|
3
|
+
import type { Logger } from '../utils/logger.js';
|
|
4
|
+
import type { AuthWaterfall } from './types.js';
|
|
5
|
+
interface NuxtDevtoolsClientInput {
|
|
6
|
+
runtime: ConvexRuntimeContext;
|
|
7
|
+
token: Ref<string | null>;
|
|
8
|
+
user: Ref<unknown>;
|
|
9
|
+
waterfall: Ref<AuthWaterfall | null>;
|
|
10
|
+
instanceId: string;
|
|
11
|
+
logger: Logger;
|
|
12
|
+
onDispose(dispose: () => void): void;
|
|
13
|
+
}
|
|
14
|
+
/** Install Nuxt's diagnostics adapter without exposing the Vue runtime's raw client or credentials. */
|
|
15
|
+
export declare function setupNuxtDevtoolsClient(input: NuxtDevtoolsClientInput): void;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function readConnection(runtime) {
|
|
2
|
+
const connection = runtime.attachment.connection?.snapshot();
|
|
3
|
+
if (!connection) {
|
|
4
|
+
return {
|
|
5
|
+
isConnected: false,
|
|
6
|
+
hasEverConnected: false,
|
|
7
|
+
connectionRetries: 0,
|
|
8
|
+
inflightRequests: 0
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
const explicitInflight = connection.inflightMutations + connection.inflightActions;
|
|
12
|
+
return {
|
|
13
|
+
isConnected: connection.isWebSocketConnected,
|
|
14
|
+
hasEverConnected: connection.hasEverConnected,
|
|
15
|
+
connectionRetries: connection.connectionRetries,
|
|
16
|
+
inflightRequests: explicitInflight > 0 ? explicitInflight : Number(connection.hasInflightRequests)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function setupNuxtDevtoolsClient(input) {
|
|
20
|
+
void Promise.all([import("./bridge-setup"), import("./sink")]).then(async ([{ setupDevToolsBridge }, { createDevtoolsSink }]) => {
|
|
21
|
+
const sink = createDevtoolsSink();
|
|
22
|
+
const detachSink = input.runtime.attachDevtoolsSink(sink);
|
|
23
|
+
if (!detachSink) return;
|
|
24
|
+
let disposeBridge;
|
|
25
|
+
try {
|
|
26
|
+
disposeBridge = await setupDevToolsBridge(
|
|
27
|
+
sink,
|
|
28
|
+
input.token,
|
|
29
|
+
input.user,
|
|
30
|
+
input.waterfall,
|
|
31
|
+
() => readConnection(input.runtime),
|
|
32
|
+
input.instanceId
|
|
33
|
+
);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
detachSink();
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
input.onDispose(() => {
|
|
39
|
+
disposeBridge();
|
|
40
|
+
detachSink();
|
|
41
|
+
});
|
|
42
|
+
}).catch((error) => input.logger.debug("DevTools bridge setup failed", error));
|
|
43
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MutationEntry, QueryRegistryEntry } from './types.js';
|
|
2
|
+
type QuerySubscriber = (entries: QueryRegistryEntry[]) => void;
|
|
3
|
+
type MutationSubscriber = (entries: MutationEntry[]) => void;
|
|
4
|
+
export interface DevtoolsSink {
|
|
5
|
+
getQueries(): QueryRegistryEntry[];
|
|
6
|
+
getQuery(id: string): QueryRegistryEntry | undefined;
|
|
7
|
+
upsertQuery(entry: Omit<QueryRegistryEntry, 'lastUpdated'>): void;
|
|
8
|
+
removeQuery(id: string): void;
|
|
9
|
+
subscribeToQueries(callback: QuerySubscriber): () => void;
|
|
10
|
+
registerMutation(entry: Omit<MutationEntry, 'id'>): string;
|
|
11
|
+
updateMutation(id: string, update: Partial<Pick<MutationEntry, 'state' | 'result' | 'error' | 'settledAt' | 'duration'>>): void;
|
|
12
|
+
getMutations(): MutationEntry[];
|
|
13
|
+
subscribeToMutations(callback: MutationSubscriber): () => void;
|
|
14
|
+
clearIdentityOwned(): void;
|
|
15
|
+
dispose(): void;
|
|
16
|
+
}
|
|
17
|
+
export declare function createDevtoolsSink(): DevtoolsSink;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { sanitizeDiagnosticValue } from "../utils/sanitize-diagnostic.js";
|
|
2
|
+
const MAX_MUTATIONS = 50;
|
|
3
|
+
const MAX_QUERIES = 200;
|
|
4
|
+
function createId() {
|
|
5
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
6
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
7
|
+
}
|
|
8
|
+
export function createDevtoolsSink() {
|
|
9
|
+
const queries = /* @__PURE__ */ new Map();
|
|
10
|
+
const mutations = /* @__PURE__ */ new Map();
|
|
11
|
+
const querySubscribers = /* @__PURE__ */ new Set();
|
|
12
|
+
const mutationSubscribers = /* @__PURE__ */ new Set();
|
|
13
|
+
let disposed = false;
|
|
14
|
+
const cloneEntry = (entry) => sanitizeDiagnosticValue(entry);
|
|
15
|
+
const getQueries = () => [...queries.values()].map(cloneEntry);
|
|
16
|
+
const getMutations = () => [...mutations.values()].sort((left, right) => right.startedAt - left.startedAt).map(cloneEntry);
|
|
17
|
+
const notifyQueries = () => {
|
|
18
|
+
for (const subscriber of querySubscribers) {
|
|
19
|
+
try {
|
|
20
|
+
subscriber(getQueries());
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const notifyMutations = () => {
|
|
26
|
+
for (const subscriber of mutationSubscribers) {
|
|
27
|
+
try {
|
|
28
|
+
subscriber(getMutations());
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
getQueries,
|
|
35
|
+
getQuery: (id) => {
|
|
36
|
+
const entry = queries.get(id);
|
|
37
|
+
return entry ? cloneEntry(entry) : void 0;
|
|
38
|
+
},
|
|
39
|
+
upsertQuery(entry) {
|
|
40
|
+
if (disposed) return;
|
|
41
|
+
queries.delete(entry.id);
|
|
42
|
+
queries.set(entry.id, {
|
|
43
|
+
...entry,
|
|
44
|
+
args: sanitizeDiagnosticValue(entry.args),
|
|
45
|
+
data: sanitizeDiagnosticValue(entry.data),
|
|
46
|
+
error: entry.error === void 0 ? void 0 : String(sanitizeDiagnosticValue(entry.error)),
|
|
47
|
+
lastUpdated: Date.now()
|
|
48
|
+
});
|
|
49
|
+
while (queries.size > MAX_QUERIES) queries.delete(queries.keys().next().value);
|
|
50
|
+
notifyQueries();
|
|
51
|
+
},
|
|
52
|
+
removeQuery(id) {
|
|
53
|
+
if (queries.delete(id)) notifyQueries();
|
|
54
|
+
},
|
|
55
|
+
subscribeToQueries(callback) {
|
|
56
|
+
if (disposed) return () => {
|
|
57
|
+
};
|
|
58
|
+
querySubscribers.add(callback);
|
|
59
|
+
callback(getQueries());
|
|
60
|
+
return () => querySubscribers.delete(callback);
|
|
61
|
+
},
|
|
62
|
+
registerMutation(entry) {
|
|
63
|
+
if (disposed) return "";
|
|
64
|
+
const id = createId();
|
|
65
|
+
mutations.set(id, {
|
|
66
|
+
...entry,
|
|
67
|
+
id,
|
|
68
|
+
args: sanitizeDiagnosticValue(entry.args)
|
|
69
|
+
});
|
|
70
|
+
while (mutations.size > MAX_MUTATIONS) mutations.delete(mutations.keys().next().value);
|
|
71
|
+
notifyMutations();
|
|
72
|
+
return id;
|
|
73
|
+
},
|
|
74
|
+
updateMutation(id, update) {
|
|
75
|
+
if (disposed) return;
|
|
76
|
+
const existing = mutations.get(id);
|
|
77
|
+
if (!existing) return;
|
|
78
|
+
mutations.set(id, {
|
|
79
|
+
...existing,
|
|
80
|
+
...update,
|
|
81
|
+
...update.result === void 0 ? {} : { result: sanitizeDiagnosticValue(update.result) },
|
|
82
|
+
...update.error === void 0 ? {} : { error: String(sanitizeDiagnosticValue(update.error)) }
|
|
83
|
+
});
|
|
84
|
+
notifyMutations();
|
|
85
|
+
},
|
|
86
|
+
getMutations,
|
|
87
|
+
subscribeToMutations(callback) {
|
|
88
|
+
if (disposed) return () => {
|
|
89
|
+
};
|
|
90
|
+
mutationSubscribers.add(callback);
|
|
91
|
+
callback(getMutations());
|
|
92
|
+
return () => mutationSubscribers.delete(callback);
|
|
93
|
+
},
|
|
94
|
+
clearIdentityOwned() {
|
|
95
|
+
if (disposed) return;
|
|
96
|
+
queries.clear();
|
|
97
|
+
mutations.clear();
|
|
98
|
+
notifyQueries();
|
|
99
|
+
notifyMutations();
|
|
100
|
+
},
|
|
101
|
+
dispose() {
|
|
102
|
+
if (disposed) return;
|
|
103
|
+
disposed = true;
|
|
104
|
+
queries.clear();
|
|
105
|
+
mutations.clear();
|
|
106
|
+
querySubscribers.clear();
|
|
107
|
+
mutationSubscribers.clear();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|