@furo/open-models 1.14.0 → 1.15.1

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.
@@ -0,0 +1,89 @@
1
+ import { Registry } from "../Registry.js";
2
+ const primitivesMap = new Map([
3
+ ["primitives.BOOLEAN", "boolean"],
4
+ ["primitives.BYTES", "string"],
5
+ ["primitives.DOUBLE", "number"],
6
+ ["primitives.ENUM", "string"],
7
+ ["primitives.FLOAT", "number"],
8
+ ["primitives.INT32", "integer"],
9
+ ["primitives.INT64", "integer"],
10
+ ["primitives.SINT32", "integer"],
11
+ ["primitives.SINT64", "integer"],
12
+ ["primitives.STRING", "string"],
13
+ ["primitives.UINT32", "integer"],
14
+ ["primitives.UINT64", "integer"],
15
+ ]);
16
+ export class SchemaBuilder {
17
+ static generate(model) {
18
+ const schema = {
19
+ $schema: "http://json-schema.org/draft-07/schema#",
20
+ $id: model.__meta.typeName,
21
+ type: "object",
22
+ description: model.__meta.description,
23
+ };
24
+ schema.properties = SchemaBuilder.getProps(model);
25
+ // required fields
26
+ const rq = SchemaBuilder.getRequiredFields(model.__meta.nodeFields);
27
+ if (rq.length > 0) {
28
+ schema.required = rq;
29
+ }
30
+ return schema;
31
+ }
32
+ static getProps(model) {
33
+ return Object.fromEntries(model.__meta.nodeFields.map(fieldDescriptor => {
34
+ const field = model.__getFieldNodeByPath(fieldDescriptor.fieldName);
35
+ if (field.__isPrimitive && field.__meta.typeName !== "primitives.ENUM") {
36
+ const spec = {
37
+ type: primitivesMap.get(field.__meta.typeName),
38
+ description: [fieldDescriptor.description, field.__meta.description].join(""),
39
+ ...SchemaBuilder.getConstraints(fieldDescriptor.constraints),
40
+ };
41
+ return [fieldDescriptor.fieldName, spec];
42
+ }
43
+ // ENUM
44
+ if (field.__meta.typeName === "primitives.ENUM") {
45
+ const eargs = model.__getFieldNodeByPath("material").enumArg;
46
+ return [
47
+ fieldDescriptor.fieldName,
48
+ {
49
+ type: primitivesMap.get(field.__meta.typeName),
50
+ description: [fieldDescriptor.description, field.__meta.description].join(""),
51
+ enum: Array.from(Object.keys(eargs)),
52
+ },
53
+ ];
54
+ }
55
+ const schema = {
56
+ type: "object",
57
+ description: [fieldDescriptor.description, field.__meta.description].join(""),
58
+ };
59
+ schema.properties = SchemaBuilder.getProps(field);
60
+ // required fields
61
+ const rq = SchemaBuilder.getRequiredFields(model.__meta.nodeFields);
62
+ if (rq.length > 0) {
63
+ schema.required = rq;
64
+ }
65
+ return [fieldDescriptor.fieldName, schema];
66
+ }));
67
+ }
68
+ static getRequiredFields(descriptors) {
69
+ const req = [];
70
+ descriptors.forEach(descriptor => {
71
+ if (descriptor.constraints?.required) {
72
+ req.push(descriptor.fieldName);
73
+ }
74
+ });
75
+ return req;
76
+ }
77
+ static getConstraints(constraints) {
78
+ if (constraints === undefined) {
79
+ return {};
80
+ }
81
+ const co = { ...constraints };
82
+ delete co.required;
83
+ return co;
84
+ }
85
+ static createFieldNodeFromSchema(schema) {
86
+ return Registry.createInstanceByTypeName(schema.$id, schema);
87
+ }
88
+ }
89
+ //# sourceMappingURL=SchemaBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SchemaBuilder.js","sourceRoot":"","sources":["../../src/decorators/SchemaBuilder.ts"],"names":[],"mappings":"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","sourcesContent":["import { ENUM } from \"../primitives/ENUM\";\nimport { FieldConstraints } from \"../FieldConstraints\";\nimport { FieldNode } from \"../FieldNode\";\nimport type { FieldDescriptor } from \"../FieldNode\";\nimport { Registry } from \"../Registry\";\nimport type { JSONSchema7, JSONSchema7Definition } from \"json-schema\";\n\nconst primitivesMap: Map<string, string> = new Map([\n [\"primitives.BOOLEAN\", \"boolean\"],\n [\"primitives.BYTES\", \"string\"],\n [\"primitives.DOUBLE\", \"number\"],\n [\"primitives.ENUM\", \"string\"],\n [\"primitives.FLOAT\", \"number\"],\n [\"primitives.INT32\", \"integer\"],\n [\"primitives.INT64\", \"integer\"],\n [\"primitives.SINT32\", \"integer\"],\n [\"primitives.SINT64\", \"integer\"],\n [\"primitives.STRING\", \"string\"],\n [\"primitives.UINT32\", \"integer\"],\n [\"primitives.UINT64\", \"integer\"],\n]);\n\ninterface FieldNodeSchema {\n $schema: string;\n $id: string;\n [key: string]: unknown;\n}\n\nexport class SchemaBuilder {\n public static generate(model: FieldNode): JSONSchema7 {\n const schema: JSONSchema7 = {\n $schema: \"http://json-schema.org/draft-07/schema#\",\n $id: model.__meta.typeName,\n type: \"object\",\n description: model.__meta.description,\n };\n\n schema.properties = SchemaBuilder.getProps(model);\n\n // required fields\n const rq = SchemaBuilder.getRequiredFields(model.__meta.nodeFields);\n if (rq.length > 0) {\n schema.required = rq;\n }\n\n return schema;\n }\n\n private static getProps(model: FieldNode): Record<string, JSONSchema7Definition> {\n return Object.fromEntries(\n model.__meta.nodeFields.map(fieldDescriptor => {\n const field = model.__getFieldNodeByPath(fieldDescriptor.fieldName) as FieldNode;\n\n if (field.__isPrimitive && field.__meta.typeName !== \"primitives.ENUM\") {\n const spec = {\n type: primitivesMap.get(field.__meta.typeName!)!,\n description: [fieldDescriptor.description, field.__meta.description].join(\"\"),\n ...SchemaBuilder.getConstraints(fieldDescriptor.constraints),\n };\n\n return [fieldDescriptor.fieldName, spec];\n }\n // ENUM\n if (field.__meta.typeName === \"primitives.ENUM\") {\n const eargs = (model.__getFieldNodeByPath(\"material\") as ENUM<unknown>).enumArg;\n\n return [\n fieldDescriptor.fieldName,\n {\n type: primitivesMap.get(field.__meta.typeName!),\n description: [fieldDescriptor.description, field.__meta.description].join(\"\"),\n enum: Array.from(Object.keys(eargs)),\n },\n ];\n }\n\n const schema: JSONSchema7 = {\n type: \"object\",\n description: [fieldDescriptor.description, field.__meta.description].join(\"\"),\n };\n schema.properties = SchemaBuilder.getProps(field);\n // required fields\n const rq = SchemaBuilder.getRequiredFields(model.__meta.nodeFields);\n if (rq.length > 0) {\n schema.required = rq;\n }\n\n return [fieldDescriptor.fieldName, schema];\n })\n );\n }\n\n private static getRequiredFields(descriptors: FieldDescriptor[]): string[] {\n const req: string[] = [];\n descriptors.forEach(descriptor => {\n if (descriptor.constraints?.required) {\n req.push(descriptor.fieldName);\n }\n });\n return req;\n }\n\n private static getConstraints(constraints: FieldConstraints | undefined) {\n if (constraints === undefined) {\n return {};\n }\n const co = { ...constraints };\n delete co.required;\n return co;\n }\n\n public static createFieldNodeFromSchema(schema: FieldNodeSchema): FieldNode {\n return Registry.createInstanceByTypeName(schema.$id, schema);\n }\n}\n"]}
@@ -0,0 +1,79 @@
1
+ import { EntityServiceEventMap } from "./EntityServiceTypes.js";
2
+ /**
3
+ * ### serviceBindings Factory
4
+ *
5
+ * Creates type-safe decorators bound to a specific service instance.
6
+ * Use this to bind component properties and methods to service events.
7
+ *
8
+ * The factory is generic and works with any `EventTarget`.
9
+ * Event types and their detail payloads are type-checked at compile time
10
+ * via the `TEventMap` type parameter.
11
+ *
12
+ * Usage:
13
+ * ```typescript
14
+ * import { cubeEntityService } from "./CubeEntityService";
15
+ * import { serviceBindings } from "./ServiceDecorators.js";
16
+ *
17
+ * const cube = serviceBindings(cubeEntityService);
18
+ *
19
+ * class MyComponent extends LitElement {
20
+ * // Property binding - type-safe event name, auto-extracts from detail
21
+ * @cube.bindToEvent("busy-changed")
22
+ * @state()
23
+ * private busy: boolean = false;
24
+ *
25
+ * // Event binding - type-safe event name and detail type
26
+ * @cube.onEvent("response-received")
27
+ * private onResponseReceived() {
28
+ * console.log("Data received!");
29
+ * }
30
+ *
31
+ * @cube.onEvent("error-5xx")
32
+ * private onError(detail: { serverResponse: Response }) {
33
+ * console.error("Server error:", detail.serverResponse);
34
+ * }
35
+ *
36
+ * // Compile error! "typo-event" is not a valid event type
37
+ * // @cube.onEvent("typo-event")
38
+ * }
39
+ * ```
40
+ *
41
+ * ### Custom Event Maps
42
+ *
43
+ * To add custom events, extend the `EntityServiceEventMap`:
44
+ * ```typescript
45
+ * interface MyServiceEventMap extends EntityServiceEventMap {
46
+ * "custom-event": { data: string };
47
+ * }
48
+ *
49
+ * const myBindings = serviceBindings<MyServiceEventMap>(myService);
50
+ * ```
51
+ *
52
+ * @typeParam TEventMap - The event map type (defaults to EntityServiceEventMap)
53
+ * @param service - The EventTarget service to bind to
54
+ * @returns Object with `bindToEvent` and `onEvent` decorator factories
55
+ */
56
+ export declare function ServiceBindings<TEventMap extends EntityServiceEventMap = EntityServiceEventMap>(service: EventTarget): {
57
+ /**
58
+ * Binds a property to a service event.
59
+ * When the event fires, the property is automatically updated from event.detail.
60
+ *
61
+ * @typeParam K - The event type (constrained to valid event names)
62
+ * @param eventType - The event name to listen for
63
+ * @param detailKey - Optional key to extract from event.detail (defaults to inferring from event type)
64
+ */
65
+ bindToEvent<K extends keyof TEventMap & string>(eventType: K, detailKey?: string): (target: object, propertyKey: string) => void;
66
+ /**
67
+ * Binds a method to a service event.
68
+ * When the event fires, the method is called with event.detail as argument.
69
+ *
70
+ * The detail type is inferred from the event map:
71
+ * - `@cube.onEvent("busy-changed")` → method receives `{ busy: boolean }`
72
+ * - `@cube.onEvent("error-5xx")` → method receives `{ serverResponse: Response }`
73
+ * - `@cube.onEvent("response-received")` → method receives `{ response, serverResponse }`
74
+ *
75
+ * @typeParam K - The event type (constrained to valid event names)
76
+ * @param eventType - The event name to listen for
77
+ */
78
+ onEvent<K extends keyof TEventMap & string>(eventType: K): (target: object, propertyKey: string, descriptor: PropertyDescriptor) => void;
79
+ };
@@ -0,0 +1,203 @@
1
+ const propertyBindingsMetadata = new WeakMap();
2
+ // Symbol keys for instance storage
3
+ const PROPERTY_LISTENERS = Symbol.for("__servicePropertyListeners__");
4
+ const EVENT_LISTENERS = Symbol.for("__serviceEventListeners__");
5
+ const PROPERTY_PATCHED = Symbol.for("__servicePropertyPatched__");
6
+ const EVENT_PATCHED = Symbol.for("__serviceEventPatched__");
7
+ const EVENT_METHODS = Symbol.for("__serviceEventMethods__");
8
+ /**
9
+ * ### serviceBindings Factory
10
+ *
11
+ * Creates type-safe decorators bound to a specific service instance.
12
+ * Use this to bind component properties and methods to service events.
13
+ *
14
+ * The factory is generic and works with any `EventTarget`.
15
+ * Event types and their detail payloads are type-checked at compile time
16
+ * via the `TEventMap` type parameter.
17
+ *
18
+ * Usage:
19
+ * ```typescript
20
+ * import { cubeEntityService } from "./CubeEntityService";
21
+ * import { serviceBindings } from "./ServiceDecorators.js";
22
+ *
23
+ * const cube = serviceBindings(cubeEntityService);
24
+ *
25
+ * class MyComponent extends LitElement {
26
+ * // Property binding - type-safe event name, auto-extracts from detail
27
+ * @cube.bindToEvent("busy-changed")
28
+ * @state()
29
+ * private busy: boolean = false;
30
+ *
31
+ * // Event binding - type-safe event name and detail type
32
+ * @cube.onEvent("response-received")
33
+ * private onResponseReceived() {
34
+ * console.log("Data received!");
35
+ * }
36
+ *
37
+ * @cube.onEvent("error-5xx")
38
+ * private onError(detail: { serverResponse: Response }) {
39
+ * console.error("Server error:", detail.serverResponse);
40
+ * }
41
+ *
42
+ * // Compile error! "typo-event" is not a valid event type
43
+ * // @cube.onEvent("typo-event")
44
+ * }
45
+ * ```
46
+ *
47
+ * ### Custom Event Maps
48
+ *
49
+ * To add custom events, extend the `EntityServiceEventMap`:
50
+ * ```typescript
51
+ * interface MyServiceEventMap extends EntityServiceEventMap {
52
+ * "custom-event": { data: string };
53
+ * }
54
+ *
55
+ * const myBindings = serviceBindings<MyServiceEventMap>(myService);
56
+ * ```
57
+ *
58
+ * @typeParam TEventMap - The event map type (defaults to EntityServiceEventMap)
59
+ * @param service - The EventTarget service to bind to
60
+ * @returns Object with `bindToEvent` and `onEvent` decorator factories
61
+ */
62
+ export function ServiceBindings(service) {
63
+ return {
64
+ /**
65
+ * Binds a property to a service event.
66
+ * When the event fires, the property is automatically updated from event.detail.
67
+ *
68
+ * @typeParam K - The event type (constrained to valid event names)
69
+ * @param eventType - The event name to listen for
70
+ * @param detailKey - Optional key to extract from event.detail (defaults to inferring from event type)
71
+ */
72
+ bindToEvent(eventType, detailKey) {
73
+ return function bindToEventDecorator(target, propertyKey) {
74
+ // Infer detailKey from event type if not provided
75
+ // e.g., "busy-changed" -> "busy"
76
+ const key = detailKey ?? inferDetailKey(eventType, propertyKey);
77
+ // Get or create metadata map for this class
78
+ let metadata = propertyBindingsMetadata.get(target);
79
+ if (!metadata) {
80
+ metadata = new Map();
81
+ propertyBindingsMetadata.set(target, metadata);
82
+ }
83
+ metadata.set(propertyKey, { service, eventType, detailKey: key });
84
+ // Patch lifecycle methods (only once per class)
85
+ patchPropertyLifecycle(target.constructor);
86
+ };
87
+ },
88
+ /**
89
+ * Binds a method to a service event.
90
+ * When the event fires, the method is called with event.detail as argument.
91
+ *
92
+ * The detail type is inferred from the event map:
93
+ * - `@cube.onEvent("busy-changed")` → method receives `{ busy: boolean }`
94
+ * - `@cube.onEvent("error-5xx")` → method receives `{ serverResponse: Response }`
95
+ * - `@cube.onEvent("response-received")` → method receives `{ response, serverResponse }`
96
+ *
97
+ * @typeParam K - The event type (constrained to valid event names)
98
+ * @param eventType - The event name to listen for
99
+ */
100
+ onEvent(eventType) {
101
+ return function onEventDecorator(target, propertyKey, descriptor) {
102
+ const originalMethod = descriptor.value;
103
+ const ctor = target.constructor;
104
+ // Store method metadata on the constructor
105
+ let methods = ctor[EVENT_METHODS];
106
+ if (!methods) {
107
+ methods = [];
108
+ ctor[EVENT_METHODS] = methods;
109
+ }
110
+ methods.push({ propertyKey, service, eventType, method: originalMethod });
111
+ // Patch lifecycle methods (only once per class)
112
+ patchEventLifecycle(ctor);
113
+ };
114
+ },
115
+ };
116
+ }
117
+ /**
118
+ * Infer the detail key from the event type or property name
119
+ */
120
+ function inferDetailKey(eventType, propertyKey) {
121
+ // Common patterns: "busy-changed" -> "busy", "validity-changed" -> check property
122
+ const match = eventType.match(/^(.+)-changed$/);
123
+ if (match) {
124
+ return match[1];
125
+ }
126
+ // Fall back to property name
127
+ return propertyKey;
128
+ }
129
+ /**
130
+ * Patch connectedCallback/disconnectedCallback for property bindings
131
+ */
132
+ function patchPropertyLifecycle(ctor) {
133
+ if (ctor[PROPERTY_PATCHED]) {
134
+ return;
135
+ }
136
+ ctor[PROPERTY_PATCHED] = true;
137
+ const originalConnected = ctor.prototype.connectedCallback;
138
+ const originalDisconnected = ctor.prototype.disconnectedCallback;
139
+ ctor.prototype.connectedCallback = function connectedCallback() {
140
+ originalConnected?.call(this);
141
+ const metadata = propertyBindingsMetadata.get(Object.getPrototypeOf(this));
142
+ if (!metadata)
143
+ return;
144
+ const listeners = new Map();
145
+ this[PROPERTY_LISTENERS] = listeners;
146
+ metadata.forEach(({ service, eventType, detailKey }, propKey) => {
147
+ const listener = ((e) => {
148
+ if (e.detail && detailKey in e.detail) {
149
+ this[propKey] = e.detail[detailKey];
150
+ }
151
+ });
152
+ listeners.set(propKey, { listener, service, eventType });
153
+ service.addEventListener(eventType, listener);
154
+ });
155
+ };
156
+ ctor.prototype.disconnectedCallback = function disconnectedCallback() {
157
+ const listeners = this[PROPERTY_LISTENERS];
158
+ if (listeners) {
159
+ listeners.forEach(({ listener, service, eventType }) => {
160
+ service.removeEventListener(eventType, listener);
161
+ });
162
+ listeners.clear();
163
+ }
164
+ originalDisconnected?.call(this);
165
+ };
166
+ }
167
+ /**
168
+ * Patch connectedCallback/disconnectedCallback for event method bindings
169
+ */
170
+ function patchEventLifecycle(ctor) {
171
+ if (ctor[EVENT_PATCHED]) {
172
+ return;
173
+ }
174
+ ctor[EVENT_PATCHED] = true;
175
+ const originalConnected = ctor.prototype.connectedCallback;
176
+ const originalDisconnected = ctor.prototype.disconnectedCallback;
177
+ ctor.prototype.connectedCallback = function connectedCallback() {
178
+ originalConnected?.call(this);
179
+ const methods = this.constructor[EVENT_METHODS];
180
+ if (!methods)
181
+ return;
182
+ const listeners = new Map();
183
+ this[EVENT_LISTENERS] = listeners;
184
+ methods.forEach(({ propertyKey, service, eventType, method }) => {
185
+ const listener = ((e) => {
186
+ method.call(this, e.detail);
187
+ });
188
+ listeners.set(propertyKey, { listener, service, eventType });
189
+ service.addEventListener(eventType, listener);
190
+ });
191
+ };
192
+ ctor.prototype.disconnectedCallback = function disconnectedCallback() {
193
+ const listeners = this[EVENT_LISTENERS];
194
+ if (listeners) {
195
+ listeners.forEach(({ listener, service, eventType }) => {
196
+ service.removeEventListener(eventType, listener);
197
+ });
198
+ listeners.clear();
199
+ }
200
+ originalDisconnected?.call(this);
201
+ };
202
+ }
203
+ //# sourceMappingURL=ServiceDecorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ServiceDecorators.js","sourceRoot":"","sources":["../../src/decorators/ServiceDecorators.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable no-param-reassign */\nimport { LitElement, ReactiveElement } from \"lit\";\n\nimport { EntityServiceEventMap } from \"./EntityServiceTypes\";\n\n/**\n * Metadata storage for property bindings\n * Maps: class prototype -> Map<propertyKey, { service, eventType, detailKey }>\n */\ninterface PropertyBindingMeta {\n service: EventTarget;\n eventType: string;\n detailKey: string;\n}\nconst propertyBindingsMetadata = new WeakMap<object, Map<string, PropertyBindingMeta>>();\n\n/**\n * Metadata storage for event method bindings\n * Maps: class constructor -> Array<{ propertyKey, service, eventType, method }>\n */\ninterface EventBindingMeta {\n propertyKey: string;\n service: EventTarget;\n eventType: string;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n method: Function;\n}\n\n// Symbol keys for instance storage\nconst PROPERTY_LISTENERS = Symbol.for(\"__servicePropertyListeners__\");\nconst EVENT_LISTENERS = Symbol.for(\"__serviceEventListeners__\");\nconst PROPERTY_PATCHED = Symbol.for(\"__servicePropertyPatched__\");\nconst EVENT_PATCHED = Symbol.for(\"__serviceEventPatched__\");\nconst EVENT_METHODS = Symbol.for(\"__serviceEventMethods__\");\n\n/**\n * ### serviceBindings Factory\n *\n * Creates type-safe decorators bound to a specific service instance.\n * Use this to bind component properties and methods to service events.\n *\n * The factory is generic and works with any `EventTarget`.\n * Event types and their detail payloads are type-checked at compile time\n * via the `TEventMap` type parameter.\n *\n * Usage:\n * ```typescript\n * import { cubeEntityService } from \"./CubeEntityService\";\n * import { serviceBindings } from \"./ServiceDecorators\";\n *\n * const cube = serviceBindings(cubeEntityService);\n *\n * class MyComponent extends LitElement {\n * // Property binding - type-safe event name, auto-extracts from detail\n * @cube.bindToEvent(\"busy-changed\")\n * @state()\n * private busy: boolean = false;\n *\n * // Event binding - type-safe event name and detail type\n * @cube.onEvent(\"response-received\")\n * private onResponseReceived() {\n * console.log(\"Data received!\");\n * }\n *\n * @cube.onEvent(\"error-5xx\")\n * private onError(detail: { serverResponse: Response }) {\n * console.error(\"Server error:\", detail.serverResponse);\n * }\n *\n * // Compile error! \"typo-event\" is not a valid event type\n * // @cube.onEvent(\"typo-event\")\n * }\n * ```\n *\n * ### Custom Event Maps\n *\n * To add custom events, extend the `EntityServiceEventMap`:\n * ```typescript\n * interface MyServiceEventMap extends EntityServiceEventMap {\n * \"custom-event\": { data: string };\n * }\n *\n * const myBindings = serviceBindings<MyServiceEventMap>(myService);\n * ```\n *\n * @typeParam TEventMap - The event map type (defaults to EntityServiceEventMap)\n * @param service - The EventTarget service to bind to\n * @returns Object with `bindToEvent` and `onEvent` decorator factories\n */\nexport function ServiceBindings<TEventMap extends EntityServiceEventMap = EntityServiceEventMap>(service: EventTarget) {\n return {\n /**\n * Binds a property to a service event.\n * When the event fires, the property is automatically updated from event.detail.\n *\n * @typeParam K - The event type (constrained to valid event names)\n * @param eventType - The event name to listen for\n * @param detailKey - Optional key to extract from event.detail (defaults to inferring from event type)\n */\n bindToEvent<K extends keyof TEventMap & string>(eventType: K, detailKey?: string) {\n return function bindToEventDecorator(target: object, propertyKey: string) {\n // Infer detailKey from event type if not provided\n // e.g., \"busy-changed\" -> \"busy\"\n const key = detailKey ?? inferDetailKey(eventType, propertyKey);\n\n // Get or create metadata map for this class\n let metadata = propertyBindingsMetadata.get(target);\n if (!metadata) {\n metadata = new Map();\n propertyBindingsMetadata.set(target, metadata);\n }\n metadata.set(propertyKey, { service, eventType, detailKey: key });\n\n // Patch lifecycle methods (only once per class)\n patchPropertyLifecycle(target.constructor as typeof ReactiveElement);\n };\n },\n\n /**\n * Binds a method to a service event.\n * When the event fires, the method is called with event.detail as argument.\n *\n * The detail type is inferred from the event map:\n * - `@cube.onEvent(\"busy-changed\")` → method receives `{ busy: boolean }`\n * - `@cube.onEvent(\"error-5xx\")` → method receives `{ serverResponse: Response }`\n * - `@cube.onEvent(\"response-received\")` → method receives `{ response, serverResponse }`\n *\n * @typeParam K - The event type (constrained to valid event names)\n * @param eventType - The event name to listen for\n */\n onEvent<K extends keyof TEventMap & string>(eventType: K) {\n return function onEventDecorator(target: object, propertyKey: string, descriptor: PropertyDescriptor) {\n const originalMethod = descriptor.value;\n const ctor = target.constructor as typeof ReactiveElement;\n\n // Store method metadata on the constructor\n let methods = (ctor as unknown as Record<symbol, EventBindingMeta[]>)[EVENT_METHODS];\n if (!methods) {\n methods = [];\n (ctor as unknown as Record<symbol, EventBindingMeta[]>)[EVENT_METHODS] = methods;\n }\n methods.push({ propertyKey, service, eventType, method: originalMethod });\n\n // Patch lifecycle methods (only once per class)\n patchEventLifecycle(ctor);\n };\n },\n };\n}\n\n/**\n * Infer the detail key from the event type or property name\n */\nfunction inferDetailKey(eventType: string, propertyKey: string): string {\n // Common patterns: \"busy-changed\" -> \"busy\", \"validity-changed\" -> check property\n const match = eventType.match(/^(.+)-changed$/);\n if (match) {\n return match[1];\n }\n // Fall back to property name\n return propertyKey;\n}\n\n/**\n * Patch connectedCallback/disconnectedCallback for property bindings\n */\nfunction patchPropertyLifecycle(ctor: typeof ReactiveElement): void {\n if ((ctor as unknown as Record<symbol, boolean>)[PROPERTY_PATCHED]) {\n return;\n }\n (ctor as unknown as Record<symbol, boolean>)[PROPERTY_PATCHED] = true;\n\n const originalConnected = ctor.prototype.connectedCallback;\n const originalDisconnected = ctor.prototype.disconnectedCallback;\n\n ctor.prototype.connectedCallback = function connectedCallback(\n this: LitElement & Record<symbol, Map<string, { listener: EventListener; service: EventTarget; eventType: string }>>\n ) {\n originalConnected?.call(this);\n\n const metadata = propertyBindingsMetadata.get(Object.getPrototypeOf(this));\n if (!metadata) return;\n\n const listeners = new Map<string, { listener: EventListener; service: EventTarget; eventType: string }>();\n this[PROPERTY_LISTENERS] = listeners;\n\n metadata.forEach(({ service, eventType, detailKey }, propKey) => {\n const listener = ((e: CustomEvent) => {\n if (e.detail && detailKey in e.detail) {\n (this as unknown as Record<string, unknown>)[propKey] = e.detail[detailKey];\n }\n }) as EventListener;\n\n listeners.set(propKey, { listener, service, eventType });\n service.addEventListener(eventType, listener);\n });\n };\n\n ctor.prototype.disconnectedCallback = function disconnectedCallback(\n this: LitElement & Record<symbol, Map<string, { listener: EventListener; service: EventTarget; eventType: string }>>\n ) {\n const listeners = this[PROPERTY_LISTENERS];\n if (listeners) {\n listeners.forEach(({ listener, service, eventType }) => {\n service.removeEventListener(eventType, listener);\n });\n listeners.clear();\n }\n\n originalDisconnected?.call(this);\n };\n}\n\n/**\n * Patch connectedCallback/disconnectedCallback for event method bindings\n */\nfunction patchEventLifecycle(ctor: typeof ReactiveElement): void {\n if ((ctor as unknown as Record<symbol, boolean>)[EVENT_PATCHED]) {\n return;\n }\n (ctor as unknown as Record<symbol, boolean>)[EVENT_PATCHED] = true;\n\n const originalConnected = ctor.prototype.connectedCallback;\n const originalDisconnected = ctor.prototype.disconnectedCallback;\n\n ctor.prototype.connectedCallback = function connectedCallback(\n this: LitElement & Record<symbol, Map<string, { listener: EventListener; service: EventTarget; eventType: string }>>\n ) {\n originalConnected?.call(this);\n\n const methods = (this.constructor as unknown as Record<symbol, EventBindingMeta[]>)[EVENT_METHODS];\n if (!methods) return;\n\n const listeners = new Map<string, { listener: EventListener; service: EventTarget; eventType: string }>();\n this[EVENT_LISTENERS] = listeners;\n\n methods.forEach(({ propertyKey, service, eventType, method }) => {\n const listener = ((e: CustomEvent) => {\n method.call(this, e.detail);\n }) as EventListener;\n\n listeners.set(propertyKey, { listener, service, eventType });\n service.addEventListener(eventType, listener);\n });\n };\n\n ctor.prototype.disconnectedCallback = function disconnectedCallback(\n this: LitElement & Record<symbol, Map<string, { listener: EventListener; service: EventTarget; eventType: string }>>\n ) {\n const listeners = this[EVENT_LISTENERS];\n if (listeners) {\n listeners.forEach(({ listener, service, eventType }) => {\n service.removeEventListener(eventType, listener);\n });\n listeners.clear();\n }\n\n originalDisconnected?.call(this);\n };\n}\n"]}
@@ -0,0 +1,89 @@
1
+ import { EntityServiceEventMap, EntityServiceEventType } from "./EntityServiceTypes.js";
2
+ /**
3
+ * Dispatch function type for entity service events.
4
+ * Accepts any function that can dispatch the standard EntityServiceEventType events.
5
+ * This allows extended event maps (like CubeServiceEventMap) to be used.
6
+ */
7
+ type DispatchFn = (type: EntityServiceEventType, detail: EntityServiceEventMap[EntityServiceEventType]) => void;
8
+ /**
9
+ * Options for creating default service event handlers.
10
+ */
11
+ export interface DefaultServiceEventHandlersOptions {
12
+ /**
13
+ * Function to check if the service is still loading.
14
+ * Used by `onRequestFinished` to determine the busy state.
15
+ * If not provided, busy is set to false on request finish.
16
+ */
17
+ isLoading?: () => boolean;
18
+ }
19
+ /**
20
+ * ### DefaultServiceEventHandlers
21
+ *
22
+ * Creates default service handlers that dispatch standard events.
23
+ * Use this to reduce boilerplate when setting up service handlers.
24
+ *
25
+ * The `onResponse` handler is intentionally NOT included - you must provide your own
26
+ * implementation since response handling is typically service-specific.
27
+ *
28
+ * Usage:
29
+ * ```typescript
30
+ * class MyEntityService extends EventTarget {
31
+ * private dispatch = createDispatch(this);
32
+ *
33
+ * setupHandlers() {
34
+ * this.service.Get.setHandlers({
35
+ * ...DefaultServiceEventHandlers(this.dispatch),
36
+ * onResponse: (response, serverResponse) => {
37
+ * // Your custom response handling
38
+ * this.entity.fromLiteral(response.entity);
39
+ * this.dispatch("response-received", { response, serverResponse });
40
+ * },
41
+ * });
42
+ * }
43
+ * }
44
+ * ```
45
+ *
46
+ * With loading check:
47
+ * ```typescript
48
+ * this.service.Get.setHandlers({
49
+ * ...DefaultServiceEventHandlers(this.dispatch, {
50
+ * isLoading: () => this.service.Get.isLoading || this.service.Update.isLoading,
51
+ * }),
52
+ * onResponse: (response, serverResponse) => { ... },
53
+ * });
54
+ * ```
55
+ *
56
+ * @param dispatch - Function to dispatch events (typically bound to the service's dispatchEvent)
57
+ * @param options - Optional configuration
58
+ * @returns Object with all standard handlers except onResponse
59
+ */
60
+ export declare function DefaultServiceEventHandlers(dispatch: DispatchFn, options?: DefaultServiceEventHandlersOptions): {
61
+ onRequestStarted(request: unknown): void;
62
+ onRequestFinished(request: unknown): void;
63
+ onResponseError(parsedResponse: unknown, serverResponse: Response): void;
64
+ onResponseErrorRaw(serverResponse: Response): void;
65
+ onResponseRaw(serverResponse: Response): void;
66
+ onRequestAborted(reason: unknown): void;
67
+ onFatalError(error: unknown): void;
68
+ };
69
+ /**
70
+ * ### createDispatch
71
+ *
72
+ * Helper to create a typed dispatch function for an EventTarget.
73
+ *
74
+ * Usage:
75
+ * ```typescript
76
+ * class MyService extends EventTarget {
77
+ * private dispatch = createDispatch(this);
78
+ *
79
+ * doSomething() {
80
+ * this.dispatch("busy-changed", { busy: true });
81
+ * }
82
+ * }
83
+ * ```
84
+ *
85
+ * @param target - The EventTarget to dispatch events on
86
+ * @returns A typed dispatch function
87
+ */
88
+ export declare function CreateDispatch(target: EventTarget): DispatchFn;
89
+ export {};
@@ -0,0 +1,100 @@
1
+ /**
2
+ * ### DefaultServiceEventHandlers
3
+ *
4
+ * Creates default service handlers that dispatch standard events.
5
+ * Use this to reduce boilerplate when setting up service handlers.
6
+ *
7
+ * The `onResponse` handler is intentionally NOT included - you must provide your own
8
+ * implementation since response handling is typically service-specific.
9
+ *
10
+ * Usage:
11
+ * ```typescript
12
+ * class MyEntityService extends EventTarget {
13
+ * private dispatch = createDispatch(this);
14
+ *
15
+ * setupHandlers() {
16
+ * this.service.Get.setHandlers({
17
+ * ...DefaultServiceEventHandlers(this.dispatch),
18
+ * onResponse: (response, serverResponse) => {
19
+ * // Your custom response handling
20
+ * this.entity.fromLiteral(response.entity);
21
+ * this.dispatch("response-received", { response, serverResponse });
22
+ * },
23
+ * });
24
+ * }
25
+ * }
26
+ * ```
27
+ *
28
+ * With loading check:
29
+ * ```typescript
30
+ * this.service.Get.setHandlers({
31
+ * ...DefaultServiceEventHandlers(this.dispatch, {
32
+ * isLoading: () => this.service.Get.isLoading || this.service.Update.isLoading,
33
+ * }),
34
+ * onResponse: (response, serverResponse) => { ... },
35
+ * });
36
+ * ```
37
+ *
38
+ * @param dispatch - Function to dispatch events (typically bound to the service's dispatchEvent)
39
+ * @param options - Optional configuration
40
+ * @returns Object with all standard handlers except onResponse
41
+ */
42
+ export function DefaultServiceEventHandlers(dispatch, options = {}) {
43
+ const { isLoading } = options;
44
+ return {
45
+ onRequestStarted(request) {
46
+ dispatch("request-started", { request });
47
+ dispatch("busy-changed", { busy: true });
48
+ },
49
+ onRequestFinished(request) {
50
+ dispatch("request-finished", { request });
51
+ dispatch("busy-changed", { busy: isLoading ? isLoading() : false });
52
+ },
53
+ onResponseError(parsedResponse, serverResponse) {
54
+ dispatch("response-error", { parsedResponse, serverResponse });
55
+ if (serverResponse.status === 404) {
56
+ dispatch("error-404", { serverResponse });
57
+ }
58
+ if (serverResponse.status >= 500) {
59
+ dispatch("error-5xx", { serverResponse });
60
+ }
61
+ },
62
+ onResponseErrorRaw(serverResponse) {
63
+ dispatch("raw-error", { serverResponse });
64
+ },
65
+ onResponseRaw(serverResponse) {
66
+ dispatch("raw-response", { serverResponse });
67
+ },
68
+ onRequestAborted(reason) {
69
+ dispatch("request-aborted", { reason: String(reason) });
70
+ },
71
+ onFatalError(error) {
72
+ dispatch("fatal-error", { error });
73
+ },
74
+ };
75
+ }
76
+ /**
77
+ * ### createDispatch
78
+ *
79
+ * Helper to create a typed dispatch function for an EventTarget.
80
+ *
81
+ * Usage:
82
+ * ```typescript
83
+ * class MyService extends EventTarget {
84
+ * private dispatch = createDispatch(this);
85
+ *
86
+ * doSomething() {
87
+ * this.dispatch("busy-changed", { busy: true });
88
+ * }
89
+ * }
90
+ * ```
91
+ *
92
+ * @param target - The EventTarget to dispatch events on
93
+ * @returns A typed dispatch function
94
+ */
95
+ export function CreateDispatch(target) {
96
+ return function dispatchEvent(type, detail) {
97
+ target.dispatchEvent(new CustomEvent(type, { detail }));
98
+ };
99
+ }
100
+ //# sourceMappingURL=DefaultServiceEventHandlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DefaultServiceEventHandlers.js","sourceRoot":"","sources":["../../src/decorators/DefaultServiceEventHandlers.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,UAAU,2BAA2B,CAAC,QAAoB,EAAE,UAA8C,EAAE;IAChH,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAE9B,OAAO;QACL,gBAAgB,CAAC,OAAgB;YAC/B,QAAQ,CAAC,iBAAiB,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;YACzC,QAAQ,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC;QAED,iBAAiB,CAAC,OAAgB;YAChC,QAAQ,CAAC,kBAAkB,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;YAC1C,QAAQ,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QACtE,CAAC;QAED,eAAe,CAAC,cAAuB,EAAE,cAAwB;YAC/D,QAAQ,CAAC,gBAAgB,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC,CAAC;YAC/D,IAAI,cAAc,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAClC,QAAQ,CAAC,WAAW,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;YAC5C,CAAC;YACD,IAAI,cAAc,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;gBACjC,QAAQ,CAAC,WAAW,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;QAED,kBAAkB,CAAC,cAAwB;YACzC,QAAQ,CAAC,WAAW,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;QAC5C,CAAC;QAED,aAAa,CAAC,cAAwB;YACpC,QAAQ,CAAC,cAAc,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;QAC/C,CAAC;QAED,gBAAgB,CAAC,MAAe;YAC9B,QAAQ,CAAC,iBAAiB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1D,CAAC;QAED,YAAY,CAAC,KAAc;YACzB,QAAQ,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACrC,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,cAAc,CAAC,MAAmB;IAChD,OAAO,SAAS,aAAa,CAAmC,IAAO,EAAE,MAAgC;QACvG,MAAM,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { EntityServiceEventMap, EntityServiceEventType } from \"./EntityServiceTypes\";\n\n/**\n * Dispatch function type for entity service events.\n * Accepts any function that can dispatch the standard EntityServiceEventType events.\n * This allows extended event maps (like CubeServiceEventMap) to be used.\n */\ntype DispatchFn = (type: EntityServiceEventType, detail: EntityServiceEventMap[EntityServiceEventType]) => void;\n\n/**\n * Options for creating default service event handlers.\n */\nexport interface DefaultServiceEventHandlersOptions {\n /**\n * Function to check if the service is still loading.\n * Used by `onRequestFinished` to determine the busy state.\n * If not provided, busy is set to false on request finish.\n */\n isLoading?: () => boolean;\n}\n\n/**\n * ### DefaultServiceEventHandlers\n *\n * Creates default service handlers that dispatch standard events.\n * Use this to reduce boilerplate when setting up service handlers.\n *\n * The `onResponse` handler is intentionally NOT included - you must provide your own\n * implementation since response handling is typically service-specific.\n *\n * Usage:\n * ```typescript\n * class MyEntityService extends EventTarget {\n * private dispatch = createDispatch(this);\n *\n * setupHandlers() {\n * this.service.Get.setHandlers({\n * ...DefaultServiceEventHandlers(this.dispatch),\n * onResponse: (response, serverResponse) => {\n * // Your custom response handling\n * this.entity.fromLiteral(response.entity);\n * this.dispatch(\"response-received\", { response, serverResponse });\n * },\n * });\n * }\n * }\n * ```\n *\n * With loading check:\n * ```typescript\n * this.service.Get.setHandlers({\n * ...DefaultServiceEventHandlers(this.dispatch, {\n * isLoading: () => this.service.Get.isLoading || this.service.Update.isLoading,\n * }),\n * onResponse: (response, serverResponse) => { ... },\n * });\n * ```\n *\n * @param dispatch - Function to dispatch events (typically bound to the service's dispatchEvent)\n * @param options - Optional configuration\n * @returns Object with all standard handlers except onResponse\n */\nexport function DefaultServiceEventHandlers(dispatch: DispatchFn, options: DefaultServiceEventHandlersOptions = {}) {\n const { isLoading } = options;\n\n return {\n onRequestStarted(request: unknown) {\n dispatch(\"request-started\", { request });\n dispatch(\"busy-changed\", { busy: true });\n },\n\n onRequestFinished(request: unknown) {\n dispatch(\"request-finished\", { request });\n dispatch(\"busy-changed\", { busy: isLoading ? isLoading() : false });\n },\n\n onResponseError(parsedResponse: unknown, serverResponse: Response) {\n dispatch(\"response-error\", { parsedResponse, serverResponse });\n if (serverResponse.status === 404) {\n dispatch(\"error-404\", { serverResponse });\n }\n if (serverResponse.status >= 500) {\n dispatch(\"error-5xx\", { serverResponse });\n }\n },\n\n onResponseErrorRaw(serverResponse: Response) {\n dispatch(\"raw-error\", { serverResponse });\n },\n\n onResponseRaw(serverResponse: Response) {\n dispatch(\"raw-response\", { serverResponse });\n },\n\n onRequestAborted(reason: unknown) {\n dispatch(\"request-aborted\", { reason: String(reason) });\n },\n\n onFatalError(error: unknown) {\n dispatch(\"fatal-error\", { error });\n },\n };\n}\n\n/**\n * ### createDispatch\n *\n * Helper to create a typed dispatch function for an EventTarget.\n *\n * Usage:\n * ```typescript\n * class MyService extends EventTarget {\n * private dispatch = createDispatch(this);\n *\n * doSomething() {\n * this.dispatch(\"busy-changed\", { busy: true });\n * }\n * }\n * ```\n *\n * @param target - The EventTarget to dispatch events on\n * @returns A typed dispatch function\n */\nexport function CreateDispatch(target: EventTarget): DispatchFn {\n return function dispatchEvent<K extends EntityServiceEventType>(type: K, detail: EntityServiceEventMap[K]) {\n target.dispatchEvent(new CustomEvent(type, { detail }));\n };\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -36,6 +36,15 @@ export { FieldMask } from './well_known/FieldMask.js';
36
36
  export { ARRAY } from './proxies/ARRAY.js';
37
37
  export { MAP } from './proxies/MAP.js';
38
38
  export { RECURSION } from './proxies/RECURSION.js';
39
+ export { ServiceBindings } from './decorators/ServiceDecorators.js';
40
+ export { ModelBindings } from './decorators/ModelDecorators.js';
41
+ export type { ModelEventType } from './decorators/ModelDecorators.js';
42
+ export { fieldBindings } from './decorators/FieldBindings.js';
43
+ export type { BindableComponent, FieldNodeLike } from './decorators/FieldBindings.js';
44
+ export { SchemaBuilder } from './decorators/SchemaBuilder.js';
45
+ export { DefaultServiceEventHandlers, CreateDispatch } from './decorators/DefaultServiceEventHandlers.js';
46
+ export type { EntityServiceEventType, TypedEntityService } from './decorators/EntityServiceTypes.js';
47
+ export type { EntityServiceEventMap } from './decorators/EntityServiceTypes.js';
39
48
  export interface IAny {
40
49
  '@type': string;
41
50
  [key: string]: unknown;
package/dist/index.js CHANGED
@@ -36,4 +36,10 @@ export { FieldMask } from './well_known/FieldMask.js';
36
36
  export { ARRAY } from './proxies/ARRAY.js';
37
37
  export { MAP } from './proxies/MAP.js';
38
38
  export { RECURSION } from './proxies/RECURSION.js';
39
+ // Decorators
40
+ export { ServiceBindings } from './decorators/ServiceDecorators.js';
41
+ export { ModelBindings } from './decorators/ModelDecorators.js';
42
+ export { fieldBindings } from './decorators/FieldBindings.js';
43
+ export { SchemaBuilder } from './decorators/SchemaBuilder.js';
44
+ export { DefaultServiceEventHandlers, CreateDispatch } from './decorators/DefaultServiceEventHandlers.js';
39
45
  //# sourceMappingURL=index.js.map