dynamodb-reactive 0.3.0 → 0.5.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.
Files changed (48) hide show
  1. package/README.md +121 -43
  2. package/dist/{client-Zb67giI5.d.ts → client-wZQ9XC9S.d.ts} +11 -0
  3. package/dist/client.cjs +53 -55
  4. package/dist/client.cjs.map +1 -1
  5. package/dist/client.d.cts +1 -1
  6. package/dist/client.d.ts +1 -1
  7. package/dist/client.js +53 -55
  8. package/dist/client.js.map +1 -1
  9. package/dist/cloudflare.cjs +461 -0
  10. package/dist/cloudflare.cjs.map +1 -0
  11. package/dist/cloudflare.d.cts +129 -0
  12. package/dist/cloudflare.d.ts +129 -0
  13. package/dist/cloudflare.js +456 -0
  14. package/dist/cloudflare.js.map +1 -0
  15. package/dist/cluster.cjs +66 -66
  16. package/dist/cluster.cjs.map +1 -1
  17. package/dist/cluster.d.cts +2 -78
  18. package/dist/cluster.d.ts +2 -78
  19. package/dist/cluster.js +66 -66
  20. package/dist/cluster.js.map +1 -1
  21. package/dist/core.cjs +1 -3
  22. package/dist/core.cjs.map +1 -1
  23. package/dist/core.js +1 -3
  24. package/dist/core.js.map +1 -1
  25. package/dist/hub.cjs +164 -0
  26. package/dist/hub.cjs.map +1 -0
  27. package/dist/hub.d.cts +91 -0
  28. package/dist/hub.d.ts +91 -0
  29. package/dist/hub.js +157 -0
  30. package/dist/hub.js.map +1 -0
  31. package/dist/index.cjs +1 -3
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.js +1 -3
  34. package/dist/index.js.map +1 -1
  35. package/dist/infra.cjs +19 -33
  36. package/dist/infra.cjs.map +1 -1
  37. package/dist/infra.js +19 -33
  38. package/dist/infra.js.map +1 -1
  39. package/dist/protocol-C4Syt_Bl.d.ts +78 -0
  40. package/dist/react.cjs +56 -65
  41. package/dist/react.cjs.map +1 -1
  42. package/dist/react.js +56 -65
  43. package/dist/react.js.map +1 -1
  44. package/dist/server.cjs +46 -139
  45. package/dist/server.cjs.map +1 -1
  46. package/dist/server.js +46 -139
  47. package/dist/server.js.map +1 -1
  48. package/package.json +42 -24
package/dist/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../core/src/schema.ts","../../core/src/table.ts"],"names":[],"mappings":"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","file":"core.js","sourcesContent":["import { z } from 'zod';\n\nimport type { FilterCondition } from './types.js';\n\nexport const FilterConditionSchema: z.ZodType<FilterCondition> = z.lazy(() => {\n const comparison = z\n .object({\n type: z.literal('comparison'),\n operator: z.enum(['eq', 'ne', 'gt', 'gte', 'lt', 'lte']),\n field: z.string().min(1),\n value: z.unknown(),\n })\n .strict();\n const queryFunction = z.union([\n z\n .object({\n type: z.literal('function'),\n operator: z.literal('between'),\n field: z.string().min(1),\n value: z.unknown(),\n value2: z.unknown(),\n })\n .strict(),\n z\n .object({\n type: z.literal('function'),\n operator: z.enum(['beginsWith', 'contains']),\n field: z.string().min(1),\n value: z.unknown(),\n })\n .strict(),\n ]);\n const logical = z\n .object({\n type: z.literal('logical'),\n operator: z.enum(['and', 'or', 'not']),\n conditions: z.array(FilterConditionSchema).min(1),\n })\n .strict()\n .superRefine((condition, context) => {\n if (condition.operator === 'not' && condition.conditions.length !== 1) {\n context.addIssue({\n code: 'custom',\n path: ['conditions'],\n message: 'not requires exactly one condition',\n });\n }\n });\n return z.union([comparison, queryFunction, logical]);\n});\n\nexport const QueryMetadataSchema = z\n .object({\n planVersion: z.literal(1),\n tableName: z.string().min(1),\n indexName: z.string().min(1).optional(),\n indexPkField: z.string().min(1).optional(),\n indexSkField: z.string().min(1).optional(),\n filterConditions: z.array(FilterConditionSchema),\n sortField: z.string().min(1).optional(),\n sortOrder: z.enum(['asc', 'desc']).optional(),\n limit: z.number().int().positive().optional(),\n })\n .strict()\n .superRefine((metadata, context) => {\n if (metadata.indexName && !metadata.indexPkField) {\n context.addIssue({\n code: 'custom',\n path: ['indexPkField'],\n message: 'Index metadata requires its partition key field',\n });\n }\n if (!metadata.indexName && (metadata.indexPkField || metadata.indexSkField)) {\n context.addIssue({\n code: 'custom',\n path: ['indexName'],\n message: 'Index key fields require an index name',\n });\n }\n if (Boolean(metadata.sortField) !== Boolean(metadata.sortOrder)) {\n context.addIssue({\n code: 'custom',\n path: ['sortOrder'],\n message: 'Sort field and order must be specified together',\n });\n }\n });\n\nconst DeliveryVersionSchema = z.object({\n generation: z.number().int().nonnegative(),\n fromVersion: z.number().int().nonnegative(),\n toVersion: z.number().int().positive(),\n messageId: z.string().min(1),\n});\n\nexport const PendingDeliverySchema = z.discriminatedUnion('type', [\n DeliveryVersionSchema.extend({\n type: z.literal('patch'),\n patches: z.array(\n z.object({\n op: z.enum(['add', 'remove', 'replace', 'move', 'copy', 'test']),\n path: z.string(),\n value: z.unknown().optional(),\n from: z.string().optional(),\n }),\n ),\n }),\n DeliveryVersionSchema.extend({\n type: z.literal('resync'),\n }).strict(),\n]);\n\n/**\n * System table schemas for the reactive engine\n */\nexport const SystemSchemas = {\n /**\n * ReactiveConnectionsV2 - Tracks active WebSocket connections\n */\n connections: z.object({\n connectionId: z.string(),\n status: z.enum(['open', 'closed']),\n context: z.record(z.string(), z.unknown()).optional(),\n authorizerContext: z.record(z.string(), z.unknown()).optional(),\n requestIdentity: z.record(z.string(), z.unknown()).optional(),\n connectedAt: z.number(),\n ttl: z.number(),\n }),\n\n /**\n * ReactiveDependenciesV2 - The inverted index for O(1) lookups\n */\n dependencies: z.object({\n pk: z.string(), // Versioned encoded dependency key\n sk: z.string(), // ConnectionID#SubscriptionID\n connectionId: z.string(),\n subscriptionId: z.string(),\n tableName: z.string(),\n fieldName: z.string(),\n fieldValue: z.string(),\n generation: z.number().int().nonnegative(),\n registrationId: z.string().min(1),\n ttl: z.number(),\n }),\n\n /**\n * ReactiveConnectionQueriesV2 - Stores subscription state for diffing\n */\n queries: z.object({\n pk: z.string(), // ConnectionID\n sk: z.string(), // SubscriptionID\n connectionId: z.string(),\n subscriptionId: z.string(),\n status: z.enum(['registering', 'active', 'cancelled', 'faulted']),\n generation: z.number().int().nonnegative(),\n registrationId: z.string().min(1),\n queryMetadata: QueryMetadataSchema,\n lastResult: z.array(z.unknown()),\n dependencies: z.array(z.string()),\n version: z.number().int().nonnegative(),\n pendingDelivery: PendingDeliverySchema.optional(),\n createdAt: z.number(),\n updatedAt: z.number(),\n ttl: z.number(),\n }),\n};\n\n/** Storage/protocol generation used by all reactive system tables. */\nexport const SYSTEM_TABLE_PROTOCOL_VERSION = 2 as const;\n\n/**\n * Versioned system table names. The V2 suffix intentionally prevents state\n * written by the incompatible 0.1.x dependency protocol from being reused.\n */\nexport const SystemTableNames = {\n connections: 'ReactiveConnectionsV2',\n dependencies: 'ReactiveDependenciesV2',\n queries: 'ReactiveConnectionQueriesV2',\n} as const;\n","import type { z } from 'zod';\n\nimport type {\n DynamoTableConfig,\n FieldRef,\n IndexDefinition,\n SupportedSchema,\n} from './types.js';\n\n/**\n * Extract all field names from a schema (handles both ZodObject and ZodDiscriminatedUnion)\n */\nfunction getSchemaFields(schema: SupportedSchema): Set<string> {\n const fields = new Set<string>();\n\n // Check if it's a discriminated union by looking for 'options' property\n if ('options' in schema && Array.isArray(schema.options)) {\n // It's a discriminated union - collect fields from all options\n for (const option of schema.options) {\n if ('shape' in option && typeof option.shape === 'object') {\n for (const key of Object.keys(option.shape)) {\n fields.add(key);\n }\n }\n }\n } else if ('shape' in schema && typeof schema.shape === 'object') {\n // It's a regular ZodObject\n for (const key of Object.keys(schema.shape)) {\n fields.add(key);\n }\n }\n\n return fields;\n}\n\n/**\n * Creates field references for type-safe query building\n */\ntype FieldRefs<TSchema extends SupportedSchema> = {\n [K in keyof z.infer<TSchema>]: FieldRef<K & string, z.infer<TSchema>[K]>;\n};\n\n/**\n * DynamoTable - Defines a DynamoDB table with type-safe schema\n *\n * Supports both ZodObject and ZodDiscriminatedUnion schemas for single-table design patterns.\n *\n * @example\n * ```ts\n * // Simple schema\n * const TodoTable = new DynamoTable({\n * tableName: 'prod-todo-table',\n * schema: z.object({\n * PK: z.string(),\n * SK: z.string(),\n * text: z.string(),\n * isDone: z.boolean(),\n * }),\n * pk: 'PK',\n * sk: 'SK',\n * });\n *\n * // Discriminated union for single-table design\n * const MultiEntityTable = new DynamoTable({\n * tableName: 'prod-entities',\n * schema: z.discriminatedUnion('entityType', [\n * z.object({ PK: z.string(), SK: z.string(), entityType: z.literal('USER'), name: z.string() }),\n * z.object({ PK: z.string(), SK: z.string(), entityType: z.literal('ITEM'), title: z.string() }),\n * ]),\n * pk: 'PK',\n * sk: 'SK',\n * });\n * ```\n */\nexport class DynamoTable<\n TSchema extends SupportedSchema,\n TPk extends keyof z.infer<TSchema>,\n TSk extends keyof z.infer<TSchema> | undefined = undefined,\n TIndexes extends Record<string, IndexDefinition> = Record<\n string,\n IndexDefinition\n >,\n> {\n public readonly tableName: string;\n public readonly schema: TSchema;\n public readonly pk: TPk;\n public readonly sk: TSk;\n public readonly indexes: TIndexes;\n public readonly field: FieldRefs<TSchema>;\n\n constructor(config: DynamoTableConfig<TSchema, TPk, TSk, TIndexes>) {\n this.tableName = config.tableName;\n this.schema = config.schema;\n this.pk = config.pk;\n this.sk = config.sk as TSk;\n this.indexes = (config.indexes ?? {}) as TIndexes;\n\n // Create field references for query building\n this.field = this.createFieldRefs();\n }\n\n private createFieldRefs(): FieldRefs<TSchema> {\n const fields = getSchemaFields(this.schema);\n const refs: Record<string, FieldRef<string, unknown>> = {};\n\n for (const key of fields) {\n refs[key] = {\n fieldName: key,\n _type: undefined as unknown,\n };\n }\n\n return refs as FieldRefs<TSchema>;\n }\n\n /**\n * Validate an item against the schema\n */\n validate(item: unknown): z.infer<TSchema> {\n return this.schema.parse(item) as z.infer<TSchema>;\n }\n\n /**\n * Safely validate an item, returning a result object\n */\n safeParse(item: unknown): z.ZodSafeParseResult<z.infer<TSchema>> {\n return this.schema.safeParse(item) as z.ZodSafeParseResult<\n z.infer<TSchema>\n >;\n }\n\n /**\n * Get the list of field names in the schema\n */\n getFieldNames(): (keyof z.infer<TSchema>)[] {\n return Array.from(\n getSchemaFields(this.schema),\n ) as (keyof z.infer<TSchema>)[];\n }\n\n}\n"]}
1
+ {"version":3,"sources":["../../core/src/schema.ts","../../core/src/table.ts"],"names":[],"mappings":"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","file":"core.js","sourcesContent":["import { z } from 'zod';\n\nimport type { FilterCondition } from './types.js';\n\nexport const FilterConditionSchema: z.ZodType<FilterCondition> = z.lazy(() => {\n const comparison = z\n .object({\n type: z.literal('comparison'),\n operator: z.enum(['eq', 'ne', 'gt', 'gte', 'lt', 'lte']),\n field: z.string().min(1),\n value: z.unknown(),\n })\n .strict();\n const queryFunction = z.union([\n z\n .object({\n type: z.literal('function'),\n operator: z.literal('between'),\n field: z.string().min(1),\n value: z.unknown(),\n value2: z.unknown(),\n })\n .strict(),\n z\n .object({\n type: z.literal('function'),\n operator: z.enum(['beginsWith', 'contains']),\n field: z.string().min(1),\n value: z.unknown(),\n })\n .strict(),\n ]);\n const logical = z\n .object({\n type: z.literal('logical'),\n operator: z.enum(['and', 'or', 'not']),\n conditions: z.array(FilterConditionSchema).min(1),\n })\n .strict()\n .superRefine((condition, context) => {\n if (condition.operator === 'not' && condition.conditions.length !== 1) {\n context.addIssue({\n code: 'custom',\n path: ['conditions'],\n message: 'not requires exactly one condition',\n });\n }\n });\n return z.union([comparison, queryFunction, logical]);\n});\n\nexport const QueryMetadataSchema = z\n .object({\n planVersion: z.literal(1),\n tableName: z.string().min(1),\n indexName: z.string().min(1).optional(),\n indexPkField: z.string().min(1).optional(),\n indexSkField: z.string().min(1).optional(),\n filterConditions: z.array(FilterConditionSchema),\n sortField: z.string().min(1).optional(),\n sortOrder: z.enum(['asc', 'desc']).optional(),\n limit: z.number().int().positive().optional(),\n })\n .strict()\n .superRefine((metadata, context) => {\n if (metadata.indexName && !metadata.indexPkField) {\n context.addIssue({\n code: 'custom',\n path: ['indexPkField'],\n message: 'Index metadata requires its partition key field',\n });\n }\n if (!metadata.indexName && (metadata.indexPkField || metadata.indexSkField)) {\n context.addIssue({\n code: 'custom',\n path: ['indexName'],\n message: 'Index key fields require an index name',\n });\n }\n if (Boolean(metadata.sortField) !== Boolean(metadata.sortOrder)) {\n context.addIssue({\n code: 'custom',\n path: ['sortOrder'],\n message: 'Sort field and order must be specified together',\n });\n }\n });\n\nconst DeliveryVersionSchema = z.object({\n generation: z.number().int().nonnegative(),\n fromVersion: z.number().int().nonnegative(),\n toVersion: z.number().int().positive(),\n messageId: z.string().min(1),\n});\n\nexport const PendingDeliverySchema = z.discriminatedUnion('type', [\n DeliveryVersionSchema.extend({\n type: z.literal('patch'),\n patches: z.array(\n z.object({\n op: z.enum(['add', 'remove', 'replace', 'move', 'copy', 'test']),\n path: z.string(),\n value: z.unknown().optional(),\n from: z.string().optional(),\n }),\n ),\n }),\n DeliveryVersionSchema.extend({\n type: z.literal('resync'),\n }).strict(),\n]);\n\n/**\n * System table schemas for the reactive engine\n */\nexport const SystemSchemas = {\n /**\n * ReactiveConnectionsV2 - Tracks active WebSocket connections\n */\n connections: z.object({\n connectionId: z.string(),\n status: z.enum(['open', 'closed']),\n context: z.record(z.string(), z.unknown()).optional(),\n authorizerContext: z.record(z.string(), z.unknown()).optional(),\n requestIdentity: z.record(z.string(), z.unknown()).optional(),\n connectedAt: z.number(),\n ttl: z.number(),\n }),\n\n /**\n * ReactiveDependenciesV2 - The inverted index for O(1) lookups\n */\n dependencies: z.object({\n pk: z.string(), // Versioned encoded dependency key\n sk: z.string(), // ConnectionID#SubscriptionID\n connectionId: z.string(),\n subscriptionId: z.string(),\n tableName: z.string(),\n fieldName: z.string(),\n fieldValue: z.string(),\n generation: z.number().int().nonnegative(),\n registrationId: z.string().min(1),\n ttl: z.number(),\n }),\n\n /**\n * ReactiveConnectionQueriesV2 - Stores subscription state for diffing\n */\n queries: z.object({\n pk: z.string(), // ConnectionID\n sk: z.string(), // SubscriptionID\n connectionId: z.string(),\n subscriptionId: z.string(),\n status: z.enum(['registering', 'active', 'cancelled', 'faulted']),\n generation: z.number().int().nonnegative(),\n registrationId: z.string().min(1),\n queryMetadata: QueryMetadataSchema,\n lastResult: z.array(z.unknown()),\n dependencies: z.array(z.string()),\n version: z.number().int().nonnegative(),\n pendingDelivery: PendingDeliverySchema.optional(),\n createdAt: z.number(),\n updatedAt: z.number(),\n ttl: z.number(),\n }),\n};\n\n/** Storage/protocol generation used by all reactive system tables. */\nexport const SYSTEM_TABLE_PROTOCOL_VERSION = 2 as const;\n\n/**\n * Versioned system table names. The V2 suffix intentionally prevents state\n * written by the incompatible 0.1.x dependency protocol from being reused.\n */\nexport const SystemTableNames = {\n connections: 'ReactiveConnectionsV2',\n dependencies: 'ReactiveDependenciesV2',\n queries: 'ReactiveConnectionQueriesV2',\n} as const;\n","import type { z } from 'zod';\n\nimport type { DynamoTableConfig, FieldRef, IndexDefinition, SupportedSchema } from './types.js';\n\n/**\n * Extract all field names from a schema (handles both ZodObject and ZodDiscriminatedUnion)\n */\nfunction getSchemaFields(schema: SupportedSchema): Set<string> {\n const fields = new Set<string>();\n\n // Check if it's a discriminated union by looking for 'options' property\n if ('options' in schema && Array.isArray(schema.options)) {\n // It's a discriminated union - collect fields from all options\n for (const option of schema.options) {\n if ('shape' in option && typeof option.shape === 'object') {\n for (const key of Object.keys(option.shape)) {\n fields.add(key);\n }\n }\n }\n } else if ('shape' in schema && typeof schema.shape === 'object') {\n // It's a regular ZodObject\n for (const key of Object.keys(schema.shape)) {\n fields.add(key);\n }\n }\n\n return fields;\n}\n\n/**\n * Creates field references for type-safe query building\n */\ntype FieldRefs<TSchema extends SupportedSchema> = {\n [K in keyof z.infer<TSchema>]: FieldRef<K & string, z.infer<TSchema>[K]>;\n};\n\n/**\n * DynamoTable - Defines a DynamoDB table with type-safe schema\n *\n * Supports both ZodObject and ZodDiscriminatedUnion schemas for single-table design patterns.\n *\n * @example\n * ```ts\n * // Simple schema\n * const TodoTable = new DynamoTable({\n * tableName: 'prod-todo-table',\n * schema: z.object({\n * PK: z.string(),\n * SK: z.string(),\n * text: z.string(),\n * isDone: z.boolean(),\n * }),\n * pk: 'PK',\n * sk: 'SK',\n * });\n *\n * // Discriminated union for single-table design\n * const MultiEntityTable = new DynamoTable({\n * tableName: 'prod-entities',\n * schema: z.discriminatedUnion('entityType', [\n * z.object({ PK: z.string(), SK: z.string(), entityType: z.literal('USER'), name: z.string() }),\n * z.object({ PK: z.string(), SK: z.string(), entityType: z.literal('ITEM'), title: z.string() }),\n * ]),\n * pk: 'PK',\n * sk: 'SK',\n * });\n * ```\n */\nexport class DynamoTable<\n TSchema extends SupportedSchema,\n TPk extends keyof z.infer<TSchema>,\n TSk extends keyof z.infer<TSchema> | undefined = undefined,\n TIndexes extends Record<string, IndexDefinition> = Record<string, IndexDefinition>,\n> {\n public readonly tableName: string;\n public readonly schema: TSchema;\n public readonly pk: TPk;\n public readonly sk: TSk;\n public readonly indexes: TIndexes;\n public readonly field: FieldRefs<TSchema>;\n\n constructor(config: DynamoTableConfig<TSchema, TPk, TSk, TIndexes>) {\n this.tableName = config.tableName;\n this.schema = config.schema;\n this.pk = config.pk;\n this.sk = config.sk as TSk;\n this.indexes = (config.indexes ?? {}) as TIndexes;\n\n // Create field references for query building\n this.field = this.createFieldRefs();\n }\n\n private createFieldRefs(): FieldRefs<TSchema> {\n const fields = getSchemaFields(this.schema);\n const refs: Record<string, FieldRef<string, unknown>> = {};\n\n for (const key of fields) {\n refs[key] = {\n fieldName: key,\n _type: undefined as unknown,\n };\n }\n\n return refs as FieldRefs<TSchema>;\n }\n\n /**\n * Validate an item against the schema\n */\n validate(item: unknown): z.infer<TSchema> {\n return this.schema.parse(item) as z.infer<TSchema>;\n }\n\n /**\n * Safely validate an item, returning a result object\n */\n safeParse(item: unknown): z.ZodSafeParseResult<z.infer<TSchema>> {\n return this.schema.safeParse(item) as z.ZodSafeParseResult<z.infer<TSchema>>;\n }\n\n /**\n * Get the list of field names in the schema\n */\n getFieldNames(): (keyof z.infer<TSchema>)[] {\n return Array.from(getSchemaFields(this.schema)) as (keyof z.infer<TSchema>)[];\n }\n}\n"]}
package/dist/hub.cjs ADDED
@@ -0,0 +1,164 @@
1
+ 'use strict';
2
+
3
+ // ../client/src/hub.ts
4
+ var DEFAULT_MAX_ATTACHMENT_BYTES = 2048;
5
+ var ReactiveHubError = class extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "ReactiveHubError";
9
+ }
10
+ };
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null;
13
+ }
14
+ function isHubRequest(value) {
15
+ if (!isRecord(value)) return false;
16
+ return (value.type === "subscribe" || value.type === "unsubscribe") && typeof value.connectionId === "string" && typeof value.subscriptionId === "string" && typeof value.generation === "number" && (value.type === "unsubscribe" || typeof value.path === "string");
17
+ }
18
+ function bearerToken(request) {
19
+ const match = /^Bearer ([^\s]+)$/u.exec(request.headers.get("authorization") ?? "");
20
+ return match?.[1] ?? "";
21
+ }
22
+ function createHubSocketState(connectionId, principal, connectedAt) {
23
+ return {
24
+ connectionId,
25
+ principal,
26
+ subscriptions: [],
27
+ ...connectedAt === void 0 ? {} : { connectedAt }
28
+ };
29
+ }
30
+ function socketState(socket) {
31
+ const state = socket.deserializeAttachment();
32
+ return { ...state, subscriptions: state.subscriptions.filter((entry) => entry.path) };
33
+ }
34
+ function send(socket, event) {
35
+ try {
36
+ socket.send(JSON.stringify(event));
37
+ } catch {
38
+ }
39
+ }
40
+ function createReactiveHub(options) {
41
+ const { sockets, versions } = options;
42
+ const maxAttachmentBytes = options.maxAttachmentBytes ?? DEFAULT_MAX_ATTACHMENT_BYTES;
43
+ const resolve = (path) => typeof options.paths === "function" ? options.paths(path) : Object.hasOwn(options.paths, path) ? options.paths[path] : void 0;
44
+ const ownedSocket = (connectionId, principal) => {
45
+ const socket = sockets.byConnectionId(connectionId);
46
+ return socket && socketState(socket).principal === principal ? socket : void 0;
47
+ };
48
+ const subscribersOf = (path) => sockets.all().flatMap(
49
+ (socket) => socketState(socket).subscriptions.filter((subscription) => subscription.path === path).map((subscription) => ({ socket, subscription }))
50
+ );
51
+ const bump = async (path) => {
52
+ const fromVersion = await versions.get(path);
53
+ const toVersion = fromVersion + 1;
54
+ await versions.set(path, toVersion);
55
+ return { fromVersion, toVersion };
56
+ };
57
+ return {
58
+ /** Answers the client's `init` and `ping` frames. */
59
+ message(socket, raw) {
60
+ let frame;
61
+ try {
62
+ frame = JSON.parse(raw);
63
+ } catch {
64
+ return;
65
+ }
66
+ const type = isRecord(frame) ? frame.type : void 0;
67
+ if (type === "init") {
68
+ send(socket, { type: "connected", connectionId: socketState(socket).connectionId });
69
+ } else if (type === "ping") {
70
+ send(socket, { type: "pong" });
71
+ }
72
+ },
73
+ async subscribe(request, principal) {
74
+ const handler = resolve(request.path);
75
+ if (!handler) throw new ReactiveHubError("Unknown subscription path");
76
+ if (!ownedSocket(request.connectionId, principal)) {
77
+ throw new ReactiveHubError("Unknown connection");
78
+ }
79
+ const context = { path: request.path, principal, input: request.input };
80
+ if (handler.authorize && !await handler.authorize(context)) {
81
+ throw new ReactiveHubError("Subscription not allowed");
82
+ }
83
+ const socket = ownedSocket(request.connectionId, principal);
84
+ if (!socket) throw new ReactiveHubError("Unknown connection");
85
+ const state = socketState(socket);
86
+ const next = {
87
+ ...state,
88
+ subscriptions: [
89
+ ...state.subscriptions.filter((entry) => entry.subscriptionId !== request.subscriptionId),
90
+ {
91
+ subscriptionId: request.subscriptionId,
92
+ generation: request.generation,
93
+ path: request.path
94
+ }
95
+ ]
96
+ };
97
+ if (new TextEncoder().encode(JSON.stringify(next)).length > maxAttachmentBytes) {
98
+ throw new ReactiveHubError("Too many subscriptions on this connection");
99
+ }
100
+ socket.serializeAttachment(next);
101
+ const version = await versions.get(request.path);
102
+ const data = await handler.snapshot(context);
103
+ return {
104
+ type: "snapshot",
105
+ subscriptionId: request.subscriptionId,
106
+ generation: request.generation,
107
+ version,
108
+ data
109
+ };
110
+ },
111
+ unsubscribe(request, principal) {
112
+ const socket = ownedSocket(request.connectionId, principal);
113
+ if (!socket) return;
114
+ const state = socketState(socket);
115
+ socket.serializeAttachment({
116
+ ...state,
117
+ subscriptions: state.subscriptions.filter(
118
+ (entry) => entry.subscriptionId !== request.subscriptionId
119
+ )
120
+ });
121
+ },
122
+ /** Bumps the path's version and asks each of its subscribers to re-fetch. */
123
+ async notify(path) {
124
+ const { fromVersion, toVersion } = await bump(path);
125
+ for (const { socket, subscription } of subscribersOf(path)) {
126
+ send(socket, {
127
+ type: "resync",
128
+ subscriptionId: subscription.subscriptionId,
129
+ generation: subscription.generation,
130
+ fromVersion,
131
+ toVersion,
132
+ messageId: crypto.randomUUID()
133
+ });
134
+ }
135
+ },
136
+ /**
137
+ * Bumps the path's version and sends the same JSON patches to each subscriber.
138
+ * Use it only when every subscriber of the path sees the same data.
139
+ */
140
+ async publish(path, patches) {
141
+ const { fromVersion, toVersion } = await bump(path);
142
+ for (const { socket, subscription } of subscribersOf(path)) {
143
+ send(socket, {
144
+ type: "patch",
145
+ subscriptionId: subscription.subscriptionId,
146
+ generation: subscription.generation,
147
+ fromVersion,
148
+ toVersion,
149
+ messageId: crypto.randomUUID(),
150
+ patches
151
+ });
152
+ }
153
+ }
154
+ };
155
+ }
156
+
157
+ exports.DEFAULT_MAX_ATTACHMENT_BYTES = DEFAULT_MAX_ATTACHMENT_BYTES;
158
+ exports.ReactiveHubError = ReactiveHubError;
159
+ exports.bearerToken = bearerToken;
160
+ exports.createHubSocketState = createHubSocketState;
161
+ exports.createReactiveHub = createReactiveHub;
162
+ exports.isHubRequest = isHubRequest;
163
+ //# sourceMappingURL=hub.cjs.map
164
+ //# sourceMappingURL=hub.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../client/src/hub.ts"],"names":[],"mappings":"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","file":"hub.cjs","sourcesContent":["import type {\n JsonPatch,\n SnapshotResponse,\n SubscribeRequest,\n UnsubscribeRequest,\n WebSocketEvent,\n} from './protocol.js';\n\n/**\n * Protocol core for a reactive hub: the server half of the wire protocol for\n * backends that hold WebSockets themselves (for example a Cloudflare Durable\n * Object). It keeps every piece of per-socket state on the socket's attachment so\n * the state survives hibernation, and it has no runtime imports, so it runs and\n * tests in Node.\n */\n\n/** Hibernation limits a socket attachment to 2,048 bytes of serialized JSON. */\nexport const DEFAULT_MAX_ATTACHMENT_BYTES = 2_048;\n\n/** One subscription held by a socket. */\nexport interface HubSubscription {\n subscriptionId: string;\n generation: number;\n path: string;\n}\n\n/** State kept on each socket's attachment. */\nexport interface HubSocketState {\n connectionId: string;\n principal: string;\n subscriptions: HubSubscription[];\n /** Epoch ms the socket was accepted; the liveness baseline before its first ping. */\n connectedAt?: number;\n}\n\n/** The parts of a hibernatable WebSocket the hub uses; faked in tests. */\nexport interface HubSocket {\n send(message: string): void;\n deserializeAttachment(): unknown;\n serializeAttachment(value: unknown): void;\n}\n\nexport interface HubSockets {\n all(): HubSocket[];\n byConnectionId(connectionId: string): HubSocket | undefined;\n}\n\n/** Monotonic per-path versions. Versions only need to grow while sockets stay open. */\nexport interface HubVersions {\n get(path: string): Promise<number>;\n set(path: string, version: number): Promise<void>;\n}\n\nexport interface HubPathContext {\n path: string;\n principal: string;\n input: unknown;\n}\n\nexport interface HubPath {\n /**\n * Runs before the subscription is stored. Return false to reject it. Every\n * subscriber of a path receives its pushes, so authorise here, not in `snapshot`.\n */\n authorize?(context: HubPathContext): boolean | Promise<boolean>;\n /** Current data for the path, sent as the subscription snapshot. */\n snapshot(context: HubPathContext): unknown;\n}\n\n/** Fixed paths by name, or a resolver for parameterised paths such as `drafts.<id>`. */\nexport type HubPaths = Record<string, HubPath> | ((path: string) => HubPath | undefined);\n\nexport interface ReactiveHubOptions {\n sockets: HubSockets;\n versions: HubVersions;\n paths: HubPaths;\n /** Rejects subscribes that would grow an attachment past this size. */\n maxAttachmentBytes?: number;\n}\n\n/** Thrown for requests the caller is not allowed to make. */\nexport class ReactiveHubError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ReactiveHubError';\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** Validates a reactive HTTP body for the subscribe and unsubscribe requests a hub serves. */\nexport function isHubRequest(value: unknown): value is SubscribeRequest | UnsubscribeRequest {\n if (!isRecord(value)) return false;\n return (\n (value.type === 'subscribe' || value.type === 'unsubscribe') &&\n typeof value.connectionId === 'string' &&\n typeof value.subscriptionId === 'string' &&\n typeof value.generation === 'number' &&\n (value.type === 'unsubscribe' || typeof value.path === 'string')\n );\n}\n\n/** Returns the bearer token from an `Authorization` header, or an empty string. */\nexport function bearerToken(request: Request): string {\n const match = /^Bearer ([^\\s]+)$/u.exec(request.headers.get('authorization') ?? '');\n return match?.[1] ?? '';\n}\n\n/** Initial attachment for a newly accepted socket. */\nexport function createHubSocketState(\n connectionId: string,\n principal: string,\n connectedAt?: number,\n): HubSocketState {\n return {\n connectionId,\n principal,\n subscriptions: [],\n ...(connectedAt === undefined ? {} : { connectedAt }),\n };\n}\n\nfunction socketState(socket: HubSocket): HubSocketState {\n const state = socket.deserializeAttachment() as HubSocketState;\n // Attachments written before subscriptions carried a path have no path; they\n // are dropped rather than fanned out to every notification.\n return { ...state, subscriptions: state.subscriptions.filter((entry) => entry.path) };\n}\n\nfunction send(socket: HubSocket, event: WebSocketEvent): void {\n try {\n socket.send(JSON.stringify(event));\n } catch {\n // A socket closing mid-broadcast is cleaned up by its close handler.\n }\n}\n\n/** Creates the protocol core for one hub instance. */\nexport function createReactiveHub(options: ReactiveHubOptions) {\n const { sockets, versions } = options;\n const maxAttachmentBytes = options.maxAttachmentBytes ?? DEFAULT_MAX_ATTACHMENT_BYTES;\n const resolve = (path: string): HubPath | undefined =>\n typeof options.paths === 'function'\n ? options.paths(path)\n : Object.hasOwn(options.paths, path)\n ? options.paths[path]\n : undefined;\n\n const ownedSocket = (connectionId: string, principal: string): HubSocket | undefined => {\n const socket = sockets.byConnectionId(connectionId);\n // The HTTP caller must own the WebSocket it registers against.\n return socket && socketState(socket).principal === principal ? socket : undefined;\n };\n\n const subscribersOf = (path: string) =>\n sockets.all().flatMap((socket) =>\n socketState(socket)\n .subscriptions.filter((subscription) => subscription.path === path)\n .map((subscription) => ({ socket, subscription })),\n );\n\n const bump = async (path: string) => {\n const fromVersion = await versions.get(path);\n const toVersion = fromVersion + 1;\n await versions.set(path, toVersion);\n return { fromVersion, toVersion };\n };\n\n return {\n /** Answers the client's `init` and `ping` frames. */\n message(socket: HubSocket, raw: string): void {\n let frame: unknown;\n try {\n frame = JSON.parse(raw);\n } catch {\n return;\n }\n const type = isRecord(frame) ? frame.type : undefined;\n if (type === 'init') {\n send(socket, { type: 'connected', connectionId: socketState(socket).connectionId });\n } else if (type === 'ping') {\n send(socket, { type: 'pong' });\n }\n },\n\n async subscribe(request: SubscribeRequest, principal: string): Promise<SnapshotResponse> {\n const handler = resolve(request.path);\n if (!handler) throw new ReactiveHubError('Unknown subscription path');\n if (!ownedSocket(request.connectionId, principal)) {\n throw new ReactiveHubError('Unknown connection');\n }\n const context: HubPathContext = { path: request.path, principal, input: request.input };\n if (handler.authorize && !(await handler.authorize(context))) {\n throw new ReactiveHubError('Subscription not allowed');\n }\n // Re-read after the awaits so a concurrent subscribe on the same socket is kept.\n const socket = ownedSocket(request.connectionId, principal);\n if (!socket) throw new ReactiveHubError('Unknown connection');\n const state = socketState(socket);\n const next: HubSocketState = {\n ...state,\n subscriptions: [\n ...state.subscriptions.filter((entry) => entry.subscriptionId !== request.subscriptionId),\n {\n subscriptionId: request.subscriptionId,\n generation: request.generation,\n path: request.path,\n },\n ],\n };\n if (new TextEncoder().encode(JSON.stringify(next)).length > maxAttachmentBytes) {\n throw new ReactiveHubError('Too many subscriptions on this connection');\n }\n socket.serializeAttachment(next);\n // Read the version before the data so a concurrent notify can only make the\n // client re-fetch, never miss a change.\n const version = await versions.get(request.path);\n const data = await handler.snapshot(context);\n return {\n type: 'snapshot',\n subscriptionId: request.subscriptionId,\n generation: request.generation,\n version,\n data,\n };\n },\n\n unsubscribe(request: UnsubscribeRequest, principal: string): void {\n const socket = ownedSocket(request.connectionId, principal);\n if (!socket) return;\n const state = socketState(socket);\n socket.serializeAttachment({\n ...state,\n subscriptions: state.subscriptions.filter(\n (entry) => entry.subscriptionId !== request.subscriptionId,\n ),\n } satisfies HubSocketState);\n },\n\n /** Bumps the path's version and asks each of its subscribers to re-fetch. */\n async notify(path: string): Promise<void> {\n const { fromVersion, toVersion } = await bump(path);\n for (const { socket, subscription } of subscribersOf(path)) {\n send(socket, {\n type: 'resync',\n subscriptionId: subscription.subscriptionId,\n generation: subscription.generation,\n fromVersion,\n toVersion,\n messageId: crypto.randomUUID(),\n });\n }\n },\n\n /**\n * Bumps the path's version and sends the same JSON patches to each subscriber.\n * Use it only when every subscriber of the path sees the same data.\n */\n async publish(path: string, patches: JsonPatch[]): Promise<void> {\n const { fromVersion, toVersion } = await bump(path);\n for (const { socket, subscription } of subscribersOf(path)) {\n send(socket, {\n type: 'patch',\n subscriptionId: subscription.subscriptionId,\n generation: subscription.generation,\n fromVersion,\n toVersion,\n messageId: crypto.randomUUID(),\n patches,\n });\n }\n },\n };\n}\n\nexport type ReactiveHub = ReturnType<typeof createReactiveHub>;\n"]}
package/dist/hub.d.cts ADDED
@@ -0,0 +1,91 @@
1
+ import { SubscribeRequest, SnapshotResponse, UnsubscribeRequest, JsonPatch } from './cluster.cjs';
2
+
3
+ /**
4
+ * Protocol core for a reactive hub: the server half of the wire protocol for
5
+ * backends that hold WebSockets themselves (for example a Cloudflare Durable
6
+ * Object). It keeps every piece of per-socket state on the socket's attachment so
7
+ * the state survives hibernation, and it has no runtime imports, so it runs and
8
+ * tests in Node.
9
+ */
10
+ /** Hibernation limits a socket attachment to 2,048 bytes of serialized JSON. */
11
+ declare const DEFAULT_MAX_ATTACHMENT_BYTES = 2048;
12
+ /** One subscription held by a socket. */
13
+ interface HubSubscription {
14
+ subscriptionId: string;
15
+ generation: number;
16
+ path: string;
17
+ }
18
+ /** State kept on each socket's attachment. */
19
+ interface HubSocketState {
20
+ connectionId: string;
21
+ principal: string;
22
+ subscriptions: HubSubscription[];
23
+ /** Epoch ms the socket was accepted; the liveness baseline before its first ping. */
24
+ connectedAt?: number;
25
+ }
26
+ /** The parts of a hibernatable WebSocket the hub uses; faked in tests. */
27
+ interface HubSocket {
28
+ send(message: string): void;
29
+ deserializeAttachment(): unknown;
30
+ serializeAttachment(value: unknown): void;
31
+ }
32
+ interface HubSockets {
33
+ all(): HubSocket[];
34
+ byConnectionId(connectionId: string): HubSocket | undefined;
35
+ }
36
+ /** Monotonic per-path versions. Versions only need to grow while sockets stay open. */
37
+ interface HubVersions {
38
+ get(path: string): Promise<number>;
39
+ set(path: string, version: number): Promise<void>;
40
+ }
41
+ interface HubPathContext {
42
+ path: string;
43
+ principal: string;
44
+ input: unknown;
45
+ }
46
+ interface HubPath {
47
+ /**
48
+ * Runs before the subscription is stored. Return false to reject it. Every
49
+ * subscriber of a path receives its pushes, so authorise here, not in `snapshot`.
50
+ */
51
+ authorize?(context: HubPathContext): boolean | Promise<boolean>;
52
+ /** Current data for the path, sent as the subscription snapshot. */
53
+ snapshot(context: HubPathContext): unknown;
54
+ }
55
+ /** Fixed paths by name, or a resolver for parameterised paths such as `drafts.<id>`. */
56
+ type HubPaths = Record<string, HubPath> | ((path: string) => HubPath | undefined);
57
+ interface ReactiveHubOptions {
58
+ sockets: HubSockets;
59
+ versions: HubVersions;
60
+ paths: HubPaths;
61
+ /** Rejects subscribes that would grow an attachment past this size. */
62
+ maxAttachmentBytes?: number;
63
+ }
64
+ /** Thrown for requests the caller is not allowed to make. */
65
+ declare class ReactiveHubError extends Error {
66
+ constructor(message: string);
67
+ }
68
+ /** Validates a reactive HTTP body for the subscribe and unsubscribe requests a hub serves. */
69
+ declare function isHubRequest(value: unknown): value is SubscribeRequest | UnsubscribeRequest;
70
+ /** Returns the bearer token from an `Authorization` header, or an empty string. */
71
+ declare function bearerToken(request: Request): string;
72
+ /** Initial attachment for a newly accepted socket. */
73
+ declare function createHubSocketState(connectionId: string, principal: string, connectedAt?: number): HubSocketState;
74
+ /** Creates the protocol core for one hub instance. */
75
+ declare function createReactiveHub(options: ReactiveHubOptions): {
76
+ /** Answers the client's `init` and `ping` frames. */
77
+ message(socket: HubSocket, raw: string): void;
78
+ subscribe(request: SubscribeRequest, principal: string): Promise<SnapshotResponse>;
79
+ unsubscribe(request: UnsubscribeRequest, principal: string): void;
80
+ /** Bumps the path's version and asks each of its subscribers to re-fetch. */
81
+ notify(path: string): Promise<void>;
82
+ /**
83
+ * Bumps the path's version and sends the same JSON patches to each subscriber.
84
+ * Use it only when every subscriber of the path sees the same data.
85
+ */
86
+ publish(path: string, patches: JsonPatch[]): Promise<void>;
87
+ };
88
+ type ReactiveHub = ReturnType<typeof createReactiveHub>;
89
+
90
+ export { DEFAULT_MAX_ATTACHMENT_BYTES, ReactiveHubError, bearerToken, createHubSocketState, createReactiveHub, isHubRequest };
91
+ export type { HubPath, HubPathContext, HubPaths, HubSocket, HubSocketState, HubSockets, HubSubscription, HubVersions, ReactiveHub, ReactiveHubOptions };
package/dist/hub.d.ts ADDED
@@ -0,0 +1,91 @@
1
+ import { SubscribeRequest, SnapshotResponse, UnsubscribeRequest, JsonPatch } from './cluster.js';
2
+
3
+ /**
4
+ * Protocol core for a reactive hub: the server half of the wire protocol for
5
+ * backends that hold WebSockets themselves (for example a Cloudflare Durable
6
+ * Object). It keeps every piece of per-socket state on the socket's attachment so
7
+ * the state survives hibernation, and it has no runtime imports, so it runs and
8
+ * tests in Node.
9
+ */
10
+ /** Hibernation limits a socket attachment to 2,048 bytes of serialized JSON. */
11
+ declare const DEFAULT_MAX_ATTACHMENT_BYTES = 2048;
12
+ /** One subscription held by a socket. */
13
+ interface HubSubscription {
14
+ subscriptionId: string;
15
+ generation: number;
16
+ path: string;
17
+ }
18
+ /** State kept on each socket's attachment. */
19
+ interface HubSocketState {
20
+ connectionId: string;
21
+ principal: string;
22
+ subscriptions: HubSubscription[];
23
+ /** Epoch ms the socket was accepted; the liveness baseline before its first ping. */
24
+ connectedAt?: number;
25
+ }
26
+ /** The parts of a hibernatable WebSocket the hub uses; faked in tests. */
27
+ interface HubSocket {
28
+ send(message: string): void;
29
+ deserializeAttachment(): unknown;
30
+ serializeAttachment(value: unknown): void;
31
+ }
32
+ interface HubSockets {
33
+ all(): HubSocket[];
34
+ byConnectionId(connectionId: string): HubSocket | undefined;
35
+ }
36
+ /** Monotonic per-path versions. Versions only need to grow while sockets stay open. */
37
+ interface HubVersions {
38
+ get(path: string): Promise<number>;
39
+ set(path: string, version: number): Promise<void>;
40
+ }
41
+ interface HubPathContext {
42
+ path: string;
43
+ principal: string;
44
+ input: unknown;
45
+ }
46
+ interface HubPath {
47
+ /**
48
+ * Runs before the subscription is stored. Return false to reject it. Every
49
+ * subscriber of a path receives its pushes, so authorise here, not in `snapshot`.
50
+ */
51
+ authorize?(context: HubPathContext): boolean | Promise<boolean>;
52
+ /** Current data for the path, sent as the subscription snapshot. */
53
+ snapshot(context: HubPathContext): unknown;
54
+ }
55
+ /** Fixed paths by name, or a resolver for parameterised paths such as `drafts.<id>`. */
56
+ type HubPaths = Record<string, HubPath> | ((path: string) => HubPath | undefined);
57
+ interface ReactiveHubOptions {
58
+ sockets: HubSockets;
59
+ versions: HubVersions;
60
+ paths: HubPaths;
61
+ /** Rejects subscribes that would grow an attachment past this size. */
62
+ maxAttachmentBytes?: number;
63
+ }
64
+ /** Thrown for requests the caller is not allowed to make. */
65
+ declare class ReactiveHubError extends Error {
66
+ constructor(message: string);
67
+ }
68
+ /** Validates a reactive HTTP body for the subscribe and unsubscribe requests a hub serves. */
69
+ declare function isHubRequest(value: unknown): value is SubscribeRequest | UnsubscribeRequest;
70
+ /** Returns the bearer token from an `Authorization` header, or an empty string. */
71
+ declare function bearerToken(request: Request): string;
72
+ /** Initial attachment for a newly accepted socket. */
73
+ declare function createHubSocketState(connectionId: string, principal: string, connectedAt?: number): HubSocketState;
74
+ /** Creates the protocol core for one hub instance. */
75
+ declare function createReactiveHub(options: ReactiveHubOptions): {
76
+ /** Answers the client's `init` and `ping` frames. */
77
+ message(socket: HubSocket, raw: string): void;
78
+ subscribe(request: SubscribeRequest, principal: string): Promise<SnapshotResponse>;
79
+ unsubscribe(request: UnsubscribeRequest, principal: string): void;
80
+ /** Bumps the path's version and asks each of its subscribers to re-fetch. */
81
+ notify(path: string): Promise<void>;
82
+ /**
83
+ * Bumps the path's version and sends the same JSON patches to each subscriber.
84
+ * Use it only when every subscriber of the path sees the same data.
85
+ */
86
+ publish(path: string, patches: JsonPatch[]): Promise<void>;
87
+ };
88
+ type ReactiveHub = ReturnType<typeof createReactiveHub>;
89
+
90
+ export { DEFAULT_MAX_ATTACHMENT_BYTES, ReactiveHubError, bearerToken, createHubSocketState, createReactiveHub, isHubRequest };
91
+ export type { HubPath, HubPathContext, HubPaths, HubSocket, HubSocketState, HubSockets, HubSubscription, HubVersions, ReactiveHub, ReactiveHubOptions };
package/dist/hub.js ADDED
@@ -0,0 +1,157 @@
1
+ // ../client/src/hub.ts
2
+ var DEFAULT_MAX_ATTACHMENT_BYTES = 2048;
3
+ var ReactiveHubError = class extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "ReactiveHubError";
7
+ }
8
+ };
9
+ function isRecord(value) {
10
+ return typeof value === "object" && value !== null;
11
+ }
12
+ function isHubRequest(value) {
13
+ if (!isRecord(value)) return false;
14
+ return (value.type === "subscribe" || value.type === "unsubscribe") && typeof value.connectionId === "string" && typeof value.subscriptionId === "string" && typeof value.generation === "number" && (value.type === "unsubscribe" || typeof value.path === "string");
15
+ }
16
+ function bearerToken(request) {
17
+ const match = /^Bearer ([^\s]+)$/u.exec(request.headers.get("authorization") ?? "");
18
+ return match?.[1] ?? "";
19
+ }
20
+ function createHubSocketState(connectionId, principal, connectedAt) {
21
+ return {
22
+ connectionId,
23
+ principal,
24
+ subscriptions: [],
25
+ ...connectedAt === void 0 ? {} : { connectedAt }
26
+ };
27
+ }
28
+ function socketState(socket) {
29
+ const state = socket.deserializeAttachment();
30
+ return { ...state, subscriptions: state.subscriptions.filter((entry) => entry.path) };
31
+ }
32
+ function send(socket, event) {
33
+ try {
34
+ socket.send(JSON.stringify(event));
35
+ } catch {
36
+ }
37
+ }
38
+ function createReactiveHub(options) {
39
+ const { sockets, versions } = options;
40
+ const maxAttachmentBytes = options.maxAttachmentBytes ?? DEFAULT_MAX_ATTACHMENT_BYTES;
41
+ const resolve = (path) => typeof options.paths === "function" ? options.paths(path) : Object.hasOwn(options.paths, path) ? options.paths[path] : void 0;
42
+ const ownedSocket = (connectionId, principal) => {
43
+ const socket = sockets.byConnectionId(connectionId);
44
+ return socket && socketState(socket).principal === principal ? socket : void 0;
45
+ };
46
+ const subscribersOf = (path) => sockets.all().flatMap(
47
+ (socket) => socketState(socket).subscriptions.filter((subscription) => subscription.path === path).map((subscription) => ({ socket, subscription }))
48
+ );
49
+ const bump = async (path) => {
50
+ const fromVersion = await versions.get(path);
51
+ const toVersion = fromVersion + 1;
52
+ await versions.set(path, toVersion);
53
+ return { fromVersion, toVersion };
54
+ };
55
+ return {
56
+ /** Answers the client's `init` and `ping` frames. */
57
+ message(socket, raw) {
58
+ let frame;
59
+ try {
60
+ frame = JSON.parse(raw);
61
+ } catch {
62
+ return;
63
+ }
64
+ const type = isRecord(frame) ? frame.type : void 0;
65
+ if (type === "init") {
66
+ send(socket, { type: "connected", connectionId: socketState(socket).connectionId });
67
+ } else if (type === "ping") {
68
+ send(socket, { type: "pong" });
69
+ }
70
+ },
71
+ async subscribe(request, principal) {
72
+ const handler = resolve(request.path);
73
+ if (!handler) throw new ReactiveHubError("Unknown subscription path");
74
+ if (!ownedSocket(request.connectionId, principal)) {
75
+ throw new ReactiveHubError("Unknown connection");
76
+ }
77
+ const context = { path: request.path, principal, input: request.input };
78
+ if (handler.authorize && !await handler.authorize(context)) {
79
+ throw new ReactiveHubError("Subscription not allowed");
80
+ }
81
+ const socket = ownedSocket(request.connectionId, principal);
82
+ if (!socket) throw new ReactiveHubError("Unknown connection");
83
+ const state = socketState(socket);
84
+ const next = {
85
+ ...state,
86
+ subscriptions: [
87
+ ...state.subscriptions.filter((entry) => entry.subscriptionId !== request.subscriptionId),
88
+ {
89
+ subscriptionId: request.subscriptionId,
90
+ generation: request.generation,
91
+ path: request.path
92
+ }
93
+ ]
94
+ };
95
+ if (new TextEncoder().encode(JSON.stringify(next)).length > maxAttachmentBytes) {
96
+ throw new ReactiveHubError("Too many subscriptions on this connection");
97
+ }
98
+ socket.serializeAttachment(next);
99
+ const version = await versions.get(request.path);
100
+ const data = await handler.snapshot(context);
101
+ return {
102
+ type: "snapshot",
103
+ subscriptionId: request.subscriptionId,
104
+ generation: request.generation,
105
+ version,
106
+ data
107
+ };
108
+ },
109
+ unsubscribe(request, principal) {
110
+ const socket = ownedSocket(request.connectionId, principal);
111
+ if (!socket) return;
112
+ const state = socketState(socket);
113
+ socket.serializeAttachment({
114
+ ...state,
115
+ subscriptions: state.subscriptions.filter(
116
+ (entry) => entry.subscriptionId !== request.subscriptionId
117
+ )
118
+ });
119
+ },
120
+ /** Bumps the path's version and asks each of its subscribers to re-fetch. */
121
+ async notify(path) {
122
+ const { fromVersion, toVersion } = await bump(path);
123
+ for (const { socket, subscription } of subscribersOf(path)) {
124
+ send(socket, {
125
+ type: "resync",
126
+ subscriptionId: subscription.subscriptionId,
127
+ generation: subscription.generation,
128
+ fromVersion,
129
+ toVersion,
130
+ messageId: crypto.randomUUID()
131
+ });
132
+ }
133
+ },
134
+ /**
135
+ * Bumps the path's version and sends the same JSON patches to each subscriber.
136
+ * Use it only when every subscriber of the path sees the same data.
137
+ */
138
+ async publish(path, patches) {
139
+ const { fromVersion, toVersion } = await bump(path);
140
+ for (const { socket, subscription } of subscribersOf(path)) {
141
+ send(socket, {
142
+ type: "patch",
143
+ subscriptionId: subscription.subscriptionId,
144
+ generation: subscription.generation,
145
+ fromVersion,
146
+ toVersion,
147
+ messageId: crypto.randomUUID(),
148
+ patches
149
+ });
150
+ }
151
+ }
152
+ };
153
+ }
154
+
155
+ export { DEFAULT_MAX_ATTACHMENT_BYTES, ReactiveHubError, bearerToken, createHubSocketState, createReactiveHub, isHubRequest };
156
+ //# sourceMappingURL=hub.js.map
157
+ //# sourceMappingURL=hub.js.map