chore: update subproject commit and add blank lines for consistency

- Updated the subproject commit reference to indicate a dirty state.
- Added blank lines to various files for improved readability and consistency across the codebase.
This commit is contained in:
Jamie Pine 2025-11-20 04:38:22 -08:00
parent 8d751b0713
commit d31ba54080
17 changed files with 181 additions and 2 deletions

View File

@ -18,3 +18,4 @@
../../test_snapshots/**

View File

@ -68,3 +68,4 @@ enum VideoMediaData {
Blurhash,
}

View File

@ -8,3 +8,4 @@ pub use action::LibraryOpenAction;
pub use input::LibraryOpenInput;
pub use output::LibraryOpenOutput;

View File

@ -122,3 +122,4 @@ mod tests {
}
}

View File

@ -14,3 +14,4 @@ pub struct VolumeTrackInput {
}

View File

@ -11,3 +11,4 @@ pub struct VolumeUntrackInput {
}

View File

@ -5,3 +5,4 @@ analysis.md

View File

@ -39,3 +39,4 @@ required-features = ["cli"]

View File

@ -50,3 +50,4 @@ fn main() -> Result<()> {
}

View File

@ -17,3 +17,4 @@ pub fn export_json(templates: &[Template], groups: &[LogGroup]) -> Result<String
Ok(serde_json::to_string_pretty(&export)?)
}

View File

@ -239,3 +239,4 @@ mod tests {
}

View File

@ -104,3 +104,4 @@ mod tests {
}

View File

@ -21,6 +21,7 @@
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-tooltip": "^1.0.7",
"@sd/assets": "workspace:*",
"@sd/ts-client": "workspace:*",
"@sd/ui": "workspace:*",
"@tanstack/react-query": "^5.90.7",
"@tanstack/react-query-devtools": "^5.90.2",
@ -35,12 +36,12 @@
"react-dom": "^19.0.0",
"react-hook-form": "^7.53.2",
"react-router-dom": "^6.20.1",
"react-scan": "^0.4.3",
"rooks": "^9.3.0",
"sonner": "^1.0.3",
"tailwind-merge": "^1.14.0",
"zod": "^3.23",
"zustand": "^5.0.8",
"@sd/ts-client": "workspace:*"
"zustand": "^5.0.8"
},
"devDependencies": {
"@types/react": "npm:types-react@rc",

View File

@ -57,3 +57,4 @@ export function useJobDispatch() {
}

View File

@ -44,6 +44,7 @@
export { SpacedriveClient } from "./client";
export type { Transport } from "./transport";
export { UnixSocketTransport, TauriTransport } from "./transport";
export { SubscriptionManager } from "./subscriptionManager";
// Event filtering utilities
export {

View File

@ -0,0 +1,163 @@
/**
* Subscription Manager - Multiplexes event subscriptions
*
* Problem: Each useNormalizedQuery creates its own subscription, causing:
* - Hundreds of subscriptions during render cycles
* - Rapid subscribe/unsubscribe churn
* - Multiple subscriptions with identical filters
*
* Solution: Pool subscriptions by filter signature:
* - One backend subscription serves many hooks
* - Reference counting prevents premature cleanup
* - Automatic cleanup when last listener unsubscribes
*/
import type { Transport } from "./transport";
import type { Event } from "./generated/types";
interface EventFilter {
library_id?: string;
resource_type?: string;
path_scope?: any;
include_descendants?: boolean;
}
interface SubscriptionEntry {
/** Cleanup function from transport */
unsubscribe: () => void;
/** All listeners for this subscription */
listeners: Set<(event: Event) => void>;
/** Reference count (number of hooks using this) */
refCount: number;
}
export class SubscriptionManager {
private subscriptions = new Map<string, SubscriptionEntry>();
private transport: Transport;
constructor(transport: Transport) {
this.transport = transport;
}
/**
* Generate stable key for filter
* Same filters = same key = shared subscription
*/
private getFilterKey(filter: EventFilter): string {
return JSON.stringify({
library_id: filter.library_id ?? null,
resource_type: filter.resource_type ?? null,
path_scope: filter.path_scope ?? null,
include_descendants: filter.include_descendants ?? false,
});
}
/**
* Subscribe to filtered events
* Reuses existing subscription if filter matches
*/
async subscribe(
filter: EventFilter,
callback: (event: Event) => void,
): Promise<() => void> {
const key = this.getFilterKey(filter);
let entry = this.subscriptions.get(key);
// Create new subscription if needed
if (!entry) {
console.log(
`[SubscriptionManager] Creating new subscription for key: ${key}`,
);
const unsubscribe = await this.transport.subscribe(
(event) => {
// Broadcast event to all listeners
const currentEntry = this.subscriptions.get(key);
if (currentEntry) {
currentEntry.listeners.forEach((listener) => listener(event));
}
},
{
event_types: [
"ResourceChanged",
"ResourceChangedBatch",
"ResourceDeleted",
"Refresh",
],
filter: {
resource_type: filter.resource_type,
path_scope: filter.path_scope,
library_id: filter.library_id,
include_descendants: filter.include_descendants,
},
},
);
entry = {
unsubscribe,
listeners: new Set(),
refCount: 0,
};
this.subscriptions.set(key, entry);
} else {
console.log(
`[SubscriptionManager] Reusing existing subscription for key: ${key} (refCount: ${entry.refCount})`,
);
}
// Add listener and increment ref count
entry.listeners.add(callback);
entry.refCount++;
// Return cleanup function
return () => {
const currentEntry = this.subscriptions.get(key);
if (!currentEntry) return;
// Remove listener and decrement ref count
currentEntry.listeners.delete(callback);
currentEntry.refCount--;
console.log(
`[SubscriptionManager] Listener removed from ${key} (refCount: ${currentEntry.refCount})`,
);
// Cleanup subscription if no more listeners
if (currentEntry.refCount === 0) {
console.log(
`[SubscriptionManager] Destroying subscription for key: ${key}`,
);
currentEntry.unsubscribe();
this.subscriptions.delete(key);
}
};
}
/**
* Get stats for debugging
*/
getStats() {
return {
activeSubscriptions: this.subscriptions.size,
subscriptions: Array.from(this.subscriptions.entries()).map(
([key, entry]) => ({
key,
refCount: entry.refCount,
listenerCount: entry.listeners.size,
}),
),
};
}
/**
* Force cleanup all subscriptions (for testing/cleanup)
*/
destroy() {
console.log(
`[SubscriptionManager] Destroying ${this.subscriptions.size} subscriptions`,
);
this.subscriptions.forEach((entry) => entry.unsubscribe());
this.subscriptions.clear();
}
}