Skip to content

Commit

Permalink
Major upgrade (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
zoe-codez authored Dec 8, 2024
1 parent 5bd068d commit 85cd878
Show file tree
Hide file tree
Showing 49 changed files with 2,562 additions and 2,882 deletions.
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export default [
"unicorn/prefer-module": "off",
"@typescript-eslint/no-magic-numbers": "warn",
"unicorn/no-object-as-default-parameter": "off",
"@typescript-eslint/no-floating-promises": "error",
"unicorn/no-null": "off",
"sonarjs/no-empty-function": "off",
"sonarjs/no-unused-expressions": "off",
Expand Down
39 changes: 21 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "@digital-alchemy/synapse",
"repository": "https://github.com/Digital-Alchemy-TS/synapse",
"homepage": "https://docs.digital-alchemy.app/Synapse",
"version": "24.11.4",
"version": "24.12.1",
"scripts": {
"start:mock": "tsx src/mock/main.mts",
"build": "rm -rf dist; tsc",
Expand All @@ -28,7 +28,7 @@
},
"license": "MIT",
"dependencies": {
"better-sqlite3": "^11.5.0"
"better-sqlite3": "^11.7.0"
},
"peerDependencies": {
"@digital-alchemy/core": "*",
Expand All @@ -38,40 +38,43 @@
"uuid": "*"
},
"devDependencies": {
"@cspell/eslint-plugin": "^8.16.0",
"@digital-alchemy/core": "^24.11.3",
"@digital-alchemy/hass": "^24.11.3",
"@eslint/compat": "^1.2.3",
"@cspell/eslint-plugin": "^8.16.1",
"@digital-alchemy/core": "^24.11.4",
"@digital-alchemy/hass": "^24.11.4",
"@eslint/compat": "^1.2.4",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.15.0",
"@eslint/js": "^9.16.0",
"@types/better-sqlite3": "^7.6.12",
"@types/node": "^22.9.1",
"@types/bun": "^1.1.14",
"@types/node": "^22.10.1",
"@types/node-cron": "^3.0.11",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.5.13",
"@typescript-eslint/eslint-plugin": "8.15.0",
"@typescript-eslint/parser": "8.15.0",
"@typescript-eslint/eslint-plugin": "8.17.0",
"@typescript-eslint/parser": "8.17.0",
"bun": "^1.1.38",
"bun-types": "^1.1.38",
"dayjs": "^1.11.13",
"dotenv": "^16.4.5",
"eslint": "9.15.0",
"dotenv": "^16.4.7",
"eslint": "9.16.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsonc": "^2.18.2",
"eslint-plugin-no-unsanitized": "^4.1.2",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-security": "^3.0.1",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-sonarjs": "^2.0.4",
"eslint-plugin-sonarjs": "^3.0.1",
"eslint-plugin-sort-keys-fix": "^1.1.2",
"eslint-plugin-unicorn": "^56.0.1",
"node-cron": "^3.0.3",
"prettier": "^3.3.3",
"prettier": "^3.4.2",
"tsx": "^4.19.2",
"type-fest": "^4.27.0",
"typescript": "^5.7.0-beta",
"type-fest": "^4.30.0",
"typescript": "^5.7.2",
"uuid": "^11.0.3",
"vitest": "^2.1.5",
"vitest": "^2.1.8",
"ws": "^8.18.0"
},
"packageManager": "yarn@4.5.1"
"packageManager": "yarn@4.5.3"
}
46 changes: 29 additions & 17 deletions src/helpers/base-domain.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,30 @@ import { TRawDomains } from "@digital-alchemy/hass";
import { createHash } from "crypto";
import { EmptyObject } from "type-fest";

import { EntityConfigCommon } from "./common-config.mts";
import { EntityConfigCommon, NonReactive } from "./common-config.mts";
import { TSynapseId } from "./utility.mts";

export type RemovableCallback<DATA extends unknown = unknown> = (
data: DATA,
remove: () => void,
) => TBlackHole;

export type TSerialize<
CONFIGURATION extends object = object,
SERIALIZE_TYPES extends unknown = unknown,
> = {
serialize: (
property: keyof CONFIGURATION,
data: SERIALIZE_TYPES,
options: CONFIGURATION,
) => string;
unserialize: (
property: keyof CONFIGURATION,
data: string,
options: CONFIGURATION,
) => SERIALIZE_TYPES;
};

export type CreateRemovableCallback<DATA extends unknown = unknown> = (
callback: RemovableCallback<DATA>,
) => { remove: () => void };
Expand Down Expand Up @@ -46,21 +62,7 @@ export type DomainGeneratorOptions<
* ensure that data is valid before handing off to internals
*/
validate?: (current: CONFIGURATION, key: keyof CONFIGURATION, value: unknown) => void | never;
} & (
| {
serialize: (
property: keyof CONFIGURATION,
data: SERIALIZE_TYPES,
options: CONFIGURATION,
) => string;
unserialize: (
property: keyof CONFIGURATION,
data: string,
options: CONFIGURATION,
) => SERIALIZE_TYPES;
}
| EmptyObject
);
} & (TSerialize<CONFIGURATION, SERIALIZE_TYPES> | EmptyObject);

export type TEventMap = Record<string, object>;

Expand All @@ -69,9 +71,10 @@ export type AddEntityOptions<
EVENT_MAP extends Record<string, object>,
ATTRIBUTES extends object,
LOCALS extends object,
DATA extends object,
> = {
context: TContext;
} & EntityConfigCommon<ATTRIBUTES, LOCALS> &
} & EntityConfigCommon<ATTRIBUTES, LOCALS, DATA> &
CONFIGURATION &
Partial<{
[EVENT in keyof EVENT_MAP]: RemovableCallback<EVENT_MAP[EVENT]>;
Expand Down Expand Up @@ -100,3 +103,12 @@ export const formatObjectId = (input: string) =>
.replaceAll(/_+/g, "_");

export const LATE_READY = -1;

export type CallbackData<
LOCALS extends object,
ATTRIBUTES extends object,
EXTRA extends object = {},
> = {
locals: LOCALS;
attributes: ATTRIBUTES;
} & NonReactive<Omit<EXTRA, "managed">>;
95 changes: 77 additions & 18 deletions src/helpers/common-config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import { CreateRemovableCallback, TEventMap } from "./base-domain.mts";
import { TSynapseEntityStorage } from "./storage.mts";
import { TSynapseDeviceId } from "./utility.mts";

export type EntityConfigCommon<ATTRIBUTES extends object, LOCALS extends object> = {
export type EntityConfigCommon<
ATTRIBUTES extends object,
LOCALS extends object,
DATA extends object,
> = {
/**
* Use a different device to register this entity
*/
Expand All @@ -33,8 +37,8 @@ export type EntityConfigCommon<ATTRIBUTES extends object, LOCALS extends object>
* This ID uniquely identifies the entity, through `entity_id` renames
*/
unique_id?: string;
disabled?: SettableConfiguration<boolean>;
icon?: SettableConfiguration<string>;
disabled?: SettableConfiguration<boolean, DATA>;
icon?: SettableConfiguration<string, DATA>;
/**
* An entity with a category will:
* - Not be exposed to cloud, Alexa, or Google Assistant components
Expand Down Expand Up @@ -62,31 +66,73 @@ export type EntityConfigCommon<ATTRIBUTES extends object, LOCALS extends object>
* can be used as a sqlite backed cache for entity specific data
*/
locals?: LOCALS;
/**
* Automatically trigger reactive config updates in response to updates from these entities
*
* List gets merged with `onUpdate` array in the configs, is convenient shorthand
*/
bind?: Updatable<DATA>[];
};

export const isCommonConfigKey = <ATTRIBUTES extends object, LOCALS extends object>(
key: string,
): key is keyof EntityConfigCommon<ATTRIBUTES, LOCALS> => COMMON_CONFIG_KEYS.has(key);
): key is keyof EntityConfigCommon<ATTRIBUTES, LOCALS, object> => COMMON_CONFIG_KEYS.has(key);

export type SettableConfiguration<TYPE extends unknown, DATA extends object> =
/**
* Straight provide the value.
* If this changes in the definition (hard coded value usually), then the entity config will be reset
*
* This option can be used with assignments
*
* ```typescript
* entity.field = new_value;
* ```
*/
| TYPE
// Verbose form
| ReactiveConfig<TYPE, DATA>
// Equiv of the `current` for the verbose reactive config
// If you don't need the other options (or prefer bind), this works great 👍
| ((data: DATA) => TYPE);

export type SettableConfiguration<TYPE extends unknown> = TYPE | ReactiveConfig<TYPE>;
export type Updatable<DATA extends object> = {
onUpdate: (callback: (data: DATA) => TBlackHole) => void;
};

export type ReactiveConfig<TYPE extends unknown = unknown> = {
/**
* > **NOTE**: `onUpdate` list is merged with the `bind` array that is provided to the entity
* ```typescript
* {
* icon: {
* current() {
* return someLogic ? "mdi:cookie-clock" : "mdi:cookie-alert-outline";
* },
* onUpdate: [hassEntityReference, synapseEntityReference],
* // every 30 seconds by default
* schedule: CronExpression.EVERY_SECOND,
* },
* }
* ```
*/
export type ReactiveConfig<TYPE extends unknown = unknown, DATA extends object = object> = {
/**
* Update immediately in response to entity updates
*/
onUpdate?: { onUpdate: (callback: () => TBlackHole) => void }[];

onUpdate?: Updatable<DATA>[];
/**
* Every 30s by default
*/
schedule?: CronExpression | string;

/**
* Calculate current value
*/
current(): TYPE;
current(data: DATA): TYPE;
};

export const isShortReactiveConfig = (key: string, value: unknown): value is ReactiveConfig =>
is.function(value) && key !== "attributes" && !NO_LIVE_UPDATE.has(key);

export const isReactiveConfig = (key: string, value: unknown): value is ReactiveConfig =>
is.object(value) &&
is.function((value as { current: () => void }).current) &&
Expand Down Expand Up @@ -127,13 +173,18 @@ export type NON_SETTABLE =

export type NonReactive<CONFIGURATION extends object> = {
[KEY in Extract<keyof CONFIGURATION, string>]: CONFIGURATION[KEY] extends SettableConfiguration<
infer TYPE
infer TYPE,
object
>
? TYPE
: CONFIGURATION[KEY];
};

export type CommonMethods<CONFIGURATION extends object, LOCALS extends object> = {
export type CommonMethods<
CONFIGURATION extends object,
LOCALS extends object,
DATA extends object,
> = {
/**
* Look up the actual entity_id that is mapped to this entity by unique_id
*/
Expand Down Expand Up @@ -173,7 +224,7 @@ export type CommonMethods<CONFIGURATION extends object, LOCALS extends object> =
/**
* @internal
*/
storage: TSynapseEntityStorage<CONFIGURATION & EntityConfigCommon<object, LOCALS>>;
storage: TSynapseEntityStorage<CONFIGURATION & EntityConfigCommon<object, LOCALS, DATA>>;
/**
* add a listener that can be removed with the removeAllListeners call
*
Expand Down Expand Up @@ -202,16 +253,17 @@ type ProxyBase<
EVENT_MAP extends TEventMap,
ATTRIBUTES extends object,
LOCALS extends object,
> = CommonMethods<CONFIGURATION, LOCALS> &
DATA extends object,
> = CommonMethods<CONFIGURATION, LOCALS, DATA> &
NonReactive<CONFIGURATION> &
BuildCallbacks<EVENT_MAP> &
EntityConfigCommon<ATTRIBUTES, LOCALS> & {
EntityConfigCommon<ATTRIBUTES, LOCALS, DATA> & {
/**
* @internal
*
* duplicate the entity proxy, used for management of listeners
*/
child: (context: TContext) => ProxyBase<CONFIGURATION, EVENT_MAP, ATTRIBUTES, LOCALS>;
child: (context: TContext) => ProxyBase<CONFIGURATION, EVENT_MAP, ATTRIBUTES, LOCALS, DATA>;
};

/**
Expand All @@ -224,7 +276,8 @@ export type SynapseEntityProxy<
EVENT_MAP extends TEventMap,
ATTRIBUTES extends object,
LOCALS extends object,
PROXY = ProxyBase<CONFIGURATION, EVENT_MAP, ATTRIBUTES, LOCALS>,
DATA extends object,
PROXY = ProxyBase<CONFIGURATION, EVENT_MAP, ATTRIBUTES, LOCALS, DATA>,
> = Omit<PROXY, Extract<keyof PROXY, NON_SETTABLE>>;

export type BuildCallbacks<EVENT_MAP extends TEventMap> = {
Expand All @@ -233,4 +286,10 @@ export type BuildCallbacks<EVENT_MAP extends TEventMap> = {
string
> as CamelCase<`on-${EVENT_NAME}`>]: CreateRemovableCallback<EVENT_MAP[EVENT_NAME]>;
};
export type GenericSynapseEntity = SynapseEntityProxy<object, TEventMap, object, object>;
export type GenericSynapseEntity<DATA extends object = object> = SynapseEntityProxy<
object,
TEventMap,
object,
object,
DATA
>;
7 changes: 4 additions & 3 deletions src/helpers/sensor.mts
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ type NumberSensors = (
*
* Note that the `datetime.datetime` returned by the `last_reset` property will be converted to an ISO 8601-formatted string when the entity's state attributes are updated. When changing `last_reset`, the `state` must be a valid number.
*/
last_reset?: SettableConfiguration<Dayjs>;
last_reset?: SettableConfiguration<Dayjs, object>;

/**
* Type of state.
Expand All @@ -359,7 +359,8 @@ export type SensorConfiguration<
ATTRIBUTES extends object,
LOCALS extends object,
STATE_TYPE extends string | number | Date | Dayjs,
> = EntityConfigCommon<ATTRIBUTES, LOCALS> &
DATA extends object,
> = EntityConfigCommon<ATTRIBUTES, LOCALS, DATA> &
SensorDeviceClasses<STATE_TYPE> & {
state?: SettableConfiguration<STATE_TYPE>;
state?: SettableConfiguration<STATE_TYPE, DATA>;
};
Loading

0 comments on commit 85cd878

Please sign in to comment.