npm package: @schematize/instance.js
Description: An extensible javascript library for creating instances
Author: Benjamin Bytheway
Latest version: 0.2.10

# Instance.js

An extensible base class for instances. 

## Features

- **Small** - The Instance library is < 4.5KB minified
- **Event-driven architecture** - Built-in EventTarget interface with custom events
- **Instance caching** - Automatic caching with unique IDs and reference management
- **Proxy support** - Optional Proxy-based property interception for "magic" capabilities
- **Collection class** - Extended Array with event-driven methods and proxied operations
- **Serialization** - JSON serialization with reference preservation
- **Memory management** - Proper cleanup and garbage collection support

## Installation

```bash
npm install @schematize/instance.js
```

```js
import Instance from '@schematize/instance.js/src/Instance.mjs';
import Collection from '@schematize/instance.js/src/Collection.mjs';
// import utilities, etc.=
import findType from '@schematize/instance.js/src/utils/findType.mjs';
import set from '@schematize/instance.js/src/Instance/set.mjs';
import EventTarget from '@schematize/instance.js/src/EventTarget.mjs';
```

# Instance

To create an instance simply use the new javascript keyword syntax:

```javascript
let instance = new Instance({});
```

You can also call Instance without the "new" keywork:

```javascript
let instance = Instance({});
```

When initializing a new instance you can pass initial properties to be set on the instance: 

```javascript
let instance = new Instance({
  property1: `value`,
});
```

## `__type__`

You can also pass the special `__type__` property to indicate the "__type__" of instance this should be (or what constructor function's prototype this instance should inherit from):

```javascript
class AType {}
let instance = new Instance({
  __type__: AType,
});
instance instanceof AType; // true
```

This woks for any "constructor function":

```javascript
function AnotherType () {}
let instance = new Instance({
  __type__: AnotherType,
});
instance instanceof AnotherType; // true
```

`__type__` is a special property that essentially equates to `instance.__proto__.constructor` (or `Object.getPrototypeOf(instance).constructor`).  It is settable just like any other property, but when changed it will trigger a change in the `__proto__` as well.

Whenever you provide a `__type__` in the initial properties when calling `Instance({ __type__: AType })`, internally Instance will ensure that the instance parameter is truely an `instanceof` the `__type__`.  This happens by using `Object.create` and in some cases `Object.setPrototypeOf`.  

### "extending" Instance

Passing the `__type__` as an initial property is fine, but it's not always ideal.  Using Instance as a "base" class can be a powerful pattern.

To extend Instance as a base class using ES6 class syntax:

```javascript
class CoolType extends Instance {
  constructor(properties = {}) {
    properties.__type__ = properties.__type__ || CoolType;
    return super(properties);
  }
}
let instance = new CoolType();
instance instanceof CoolType; // true
instance instanceof Instance; // true
```

To extend Instance as a base class using ES5 function constructor syntax: 

```javascript
NeatType = function NeatType (properties = {}) {
  properties.__type__ = properties.__type__ || NeatType;
  return Instance(properties, this);
  // or 
  // return Instance.call(this, properties);
};
NeatType.prototype = Object.create(Instance.prototype);
NeatType.prototype.constructor = NeatType;
let instance = new NeatType();
instance instanceof NeatType; // true
instance instanceof Instance; // true
```

## `__id__`

Every instance is given an automatic `__id__` property when created.  

```javascript
let instance = new Instance();
console.log(instance.__id__); // 5nhpc2ufqkfuj588nhjauipc
```

The value of the `__id__` property is up to 128 bytes.  It is generated using the `sid` function (described later) and should be universally unique.  The `__id__` identifies the instance and can be used for multiple use cases including: 
- Object equality
- references to instances in cache
- Serialization and deserialization and maintaining referencial integrity
- identification in data stores
- and many more...

You can initialize an instance with an `__id__`: 

```javascript
let instance = new Instance({
  __id__: `ABC123`,
});
```

If an instance with that same `__id__` has already been instantiated, you will receive back an exact reference to the instance that was previously created.  In other words, the two instances will have object equality.

```javascript
let instance1 = new Instance({
  __id__: `ABC123`,
});
// ...later
let instance2 = new Instance({
  __id__: `ABC123`,
});
instance1 === instance2; // true - object equality
```

## EventTarget

Each instance is build with methods that adhere to the EventTarget Interface. SEE: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.  More specifically Instance.prototype gets the following instance methods: 

- `addEventListener` (alias "on")
- `removeEventListener` (alias "off")
- `dispatchEvent` (alias "dispatch" and "trigger")

Instance.prototype is an EventTarget, meaning all of these methods are available on the Instance.prototype object and therefore available to any instance that inherits from Instance.prototype.

### EventTarget function

Calling `EventTarget` directly on an object will set the event target methods on that object. 

```javascript
let obj = {};
EventTarget(obj);
// obj.addEventListener is now defined
// obj.removeEventListener
// obj.dispatEvent
// etc...
```

### Instance.prototype.addEventListener (on)

To add an event listener to an instance, you should use the `EventTarget.prototype.addEventListener` function.  Since Instance.prototype implements EventTarget you can additionally use `Instance.prototype.addEventListener`.

#### Parameters 

- `name` (string): The event name to listen and handle.
- `fn` (Function): The "handler" function or "callback" to call when the event occurs.
- `instance` (Object): The instance or object on which events would be dispatched.

#### Usage

If the instance is an "instanceof" Instance, you can call it from the instance method: 

```javascript
let instance = new Instance({
  //...
});
instance.addEventListener(`change`, (ev) => {
  // ev
});
// or
// instance.on(`change`, (ev) => {});
```

If the object is __not__ an "instanceof" Instance, you can use the prototype method and pass the object as a third parameter (the `this` parameter): 

```javascript
let obj = {};
Instance.prototype.addEventListener(`change`, (ev) => (
 // ev
), obj);
// or
// Instance.prototype.addEventListener.call(obj, (ev) => {});
// or
// Instance.prototype.on(`change`, (ev) => {}, obj);
// or
// Instance.prototype.on.call(obj, (ev) => {});
// or 
// EventTarget.prototype.addEventListener(`change`, (ev) => {}, obj);
// or 
// EventTarget.prototype.addEventListener.call(obj, (ev) => {});
// or 
// EventTarget.prototype.on(`change`, (ev) => {}, obj);
// or
// EventTarget.prototype.on.call(obj, (ev) => {});
```

Similarly, you can simply import the `on` function directly and use it without working through prototype methods: 

```javascript
import on from '@schematize/instance.js/src/EventTarget/on.mjs';
//...
let obj = {};
on('change', (ev) => {}, obj);
```

### Instance.prototype.removeEventListener (off)

To remove an event listener from an instance, you should use the `EventTarget.prototype.removeEventListener` function.  Since Instance.prototype implements EventTarget you can additionally use `Instance.prototype.removeEventListener`.

#### Parameters 

- `name` (string): The event name to stop listening or handling.
- `fn` (Function): The "handler" function or "callback" to remove.  Object equality is used to determine the function to remove.
- `instance` (Object): The instance or object on which holds the "handler" or "callback" function.

#### Usage

If the instance is an "instanceof" Instance, you can call it from the instance method: 

```javascript
// ...handler function created and addEventListener/on was called previously
let instance = new Instance({
  //...
});
instance.removeEventListener(`change`, handler);
// or
// instance.off(`change`, handler);
```

If the object is __not__ an "instanceof" Instance, you can use the prototype method and pass the object as a third parameter (the `this` parameter): 

```javascript
// ...handler function created and addEventListener/on was called previously
let obj = {};
Instance.prototype.removeEventListener(`change`, handler, obj);
// or
// Instance.prototype.removeEventListener.call(obj, handler);
// or
// Instance.prototype.off(`change`, handler, obj);
// or
// Instance.prototype.off.call(obj, handler);
// or 
// EventTarget.prototype.removeEventListener(`change`, handler, obj);
// or 
// EventTarget.prototype.removeEventListener.call(obj, handler);
// or 
// EventTarget.prototype.off(`change`, handler, obj);
// or
// EventTarget.prototype.off.call(obj, handler);
```

Similarly, you can simply import the `off` function directly and use it without working through prototype methods: 

```javascript
import off from '@schematize/instance.js/src/EventTarget/off.mjs';
// ...handler function created and addEventListener/on was called previously
let obj = {};
off('change', handler, obj);
```

### Instance.prototype.dispatchEvent (dispatch) (trigger)

To dispatch an event for an instance, you should use the `EventTarget.prototype.dispatchEvent` function.  Since Instance.prototype implements EventTarget you can additionally use `Instance.prototype.dispatchEvent`.

#### Parameters 

- `evt` (Object): The event to dispatch.
    - `evt.type` (string): The event type or name.  This corresponds to the "name" on addEventListener and removeEventListener.
    - `evt.detail` (Object): Optional. An object with the additional details about the event, context, etc.
    - `evt.returnValue` (mixed): Optional. A standard for returning something from an event handler back to the code that dispatched the event.
- `instance` (Object): The instance or object on which to find event handlers and dispatch the event.

As you can see, the first parameter is the event object which contains a "type" property and a "detail" property.  This was modeled after the browser `CustomEvent` interface.  (SEE: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent).  However, be aware that you do not have to use `CustomEvent` for building the `evt` parameter.  While you can use `CustomEvent`, all that dispatchEvent requires from evt is that it contains a "type" property.  In my testing, `CustomEvent` is extremely performance intensive so I avoid it.  Please also not that `CustomEvent` inherits from the `Event` interface which has multiple other properties and methods like "cancelable", "bubbles", "currentTarget", "stopPropogation()", "preventDefault()", etc. which are not supported at this point. 

On that note, one of the important choices I've made with support for these capabilities relates to peformance. `dispatchEvent` is the backbone through which message passing and reactivity is achieved in multiple dependent packages.  It is the single biggest bottleneck across all platforms. For this reason performance in dispatchEvent is absolutely critical.  In order to maintain the best performance possible, I have intentionally kept the dispatchEvent function as lean as possible and used as many tricks as possible to squeeze out performance.  This means sometimes not supporting some features or functionality. 

#### Catpure/Bubbling

Browsers implement the W3C specification dispatching events.  (SEE: https://www.quirksmode.org/js/events_order.html).  When an event is dispatched, it first goes from the root element "window" and/or "document" and makes it's way to the top most event target. This is called the "capture" phase.  Then after dispatching on the "target" element, it makes it's way back down to the "document".  This is called the bubbling phase.

For dispatching events on instances, I have made the choice to support a slightly different model, but one that gives you just as much flexibility while also ensuring that dispatchEvent is performant.

First, let's establish what "capturing" or "bubbling" would look like in an "instance" world.  Mainly what it looks like is looking through the instance's prototype chain to deteremine whether to capture all events for a particular instance, or it's `__type__` or all Instances.  Each of these can be valuable.

Traversing prototype chains for every instance even when there may not be listeners attached to that prototype in the chain can get expensive when it comes to performance.  The less we have to loop the better.  

#### `__dispatch__`

For this reason in order to decide what order (bubble, capture, other) and which prototypes in the chain on which to dispatch the events (`instance`, `instance.__proto__`, `Instance.prototype`, etc.), you have the `__dispatch__` special property.  This property should be an array.

By default `Instance.prototype.__dispatch__` is set to `[ Instance.prototype ]`.  This means that by default the event will be dispatched on any listeners on the instance as well as the Instance.prototype.  For example, if you fire a "change" event on the instance, any "change" listeners on instance would be handled as well as any "change" listener that were added to the Instance.prototype object.  This means you can listen to events for ALL instances of `Instance.prototype` by attaching listeners to `Instance.prototype`: 

```javascript
Instance.prototype.on(`change`, (ev) => {
  // fires for all "change" events across all instances that have Instance.prototype in their prototype chain...
});
```

In order to switch around the order and which prototypes you want to dispatch, you can overwrite the `Instance.__dispatch__` array or hide the `__dispatch__` instance by defining a property on `Instance.__dispatch__` or any other prototype in the prototype chain.  You can also define a getter/setter property the dynamically adjusts based on the instance or that simply gets the full prototype chain.

#### Usage

If the instance is an "instanceof" Instance, you can call it from the instance method: 

```javascript
// ...handler function created and addEventListener/on was called previously
let instance = new Instance({
  //...
});
instance.dispatchEvent({
  type: `change`,
  detail: {
    instance: instance,
    property: `property1`,
    // ...
  },
});
// or
// instance.dispatch({...});
// or
// instance.trigger({...});
```

If the object is __not__ an "instanceof" Instance, you can use the prototype method and pass the object as a third parameter (the `this` parameter): 

```javascript
// ...handler function created and addEventListener/on was called previously
let obj = {};
Instance.prototype.dispatchEvent({
  type: `change`,
  detail: {
    instance: instance,
    property: `property1`,
    // ...
  },
}, obj);
// or
// Instance.prototype.dispatchEvent.call(obj, handler);
// or
// Instance.prototype.dispatch(`change`, handler, obj);
// or
// Instance.prototype.dispatch.call(obj, handler);
// or
// Instance.prototype.trigger(`change`, handler, obj);
// or
// Instance.prototype.trigger.call(obj, handler);
// or 
// EventTarget.prototype.dispatchEvent(`change`, handler, obj);
// or 
// EventTarget.prototype.dispatchEvent.call(obj, handler);
// or 
// EventTarget.prototype.dispatch(`change`, handler, obj);
// or
// EventTarget.prototype.dispatch.call(obj, handler);
// or 
// EventTarget.prototype.trigger(`change`, handler, obj);
// or
// EventTarget.prototype.trigger.call(obj, handler);
```

Similarly, you can simply import the `dispatch` function directly and use it without working through prototype methods: 

```javascript
import dispatch from '@schematize/instance.js/src/EventTarget/dispatch.mjs';
// ...handler function created and addEventListener/on was called previously
let obj = {};
dispatch({
  type: `change`,
  detail: {
    instance: instance,
    property: `property1`,
    // ...
  },
}, obj);
```

## Instance events

With that in mind, let's go back and talk about what events are fired when you create a new Instance or call the Instance function constructor as a result of constructing an instance of a Type that extends Instance in some way.

#### Events

##### "beforeInstance" event

The `beforeInstance` event is fired at the very beginning of the Instance constructor, before any instance creation logic occurs. This allows listeners to intercept and potentially modify the instance creation process.

_Event Detail Properties_:
- `properties` - The properties object passed to the Instance constructor
- `instance` - The cached instance if one was found by `__id__`, otherwise `undefined`
- `this` - The `this` context passed to the Instance constructor

_Return Value_:
- `returnValue.instance` - Can be set to provide a custom instance to use instead of creating a new one.  This is especially helpful when wanting to implement your own cache or instance provider.
- `returnValue.__type__` - Can be set to override the type determination logic
- `returnValue.initialized` - Can be set to `true` to skip the rest of the initialization process
- `returnValue.noAssign` - Can be set to `true` to prevent property assignment

```javascript
Instance.prototype.on(`beforeInstance`, (evt) => {
  const { properties, instance } = evt.detail;
  
  // Custom logic to modify instance creation
  if (properties?.customType) {
    evt.returnValue.__type__ = CustomClass;
  }
  
  // Skip initialization if already handled
  if (instance && instance.fullyInitialized) {
    evt.returnValue.initialized = true;
  }
});
```

##### "instance" event

The `instance` event is fired after the instance has been created, cached, and all internal properties (`__type__`, `__id__`) have been set, but before properties are assigned.

_Event Detail Properties_:
- `__type__` - The type/constructor that was used to create the instance
- `properties` - The properties object passed to the Instance constructor
- `instance` - The newly created instance

```javascript
Instance.prototype.on(`instance`, (evt) => {
  const { __type__, instance, properties } = evt.detail;
  
  // Perform any post-creation setup
  if (__type__ === CustomClass) {
    instance.setupCustomFeatures();
  }
});
```

##### "change" event

The `change` event is fired whenever properties on an instance are modified. This includes special handling for `__type__` and `__id__` properties.

_Event Detail Properties_:
- `instance` - The instance that was modified
- `changes` - A Map containing the property changes with `from` and `to` values

```javascript
Instance.prototype.on(`change`, (evt) => {
  const { instance, changes } = evt.detail;
  
  for (const [propertyName, change] of changes) {
    console.log(`${propertyName} changed from ${change.from} to ${change.to}`);
    
    // Special handling for __type__ changes
    if (propertyName === `__type__`) {
      // Instance prototype chain is automatically updated
    }
    
    // Special handling for __id__ changes  
    if (propertyName === `__id__`) {
      // Cache and backrefs are automatically updated
    }
  }
});
```

## Instance.prototype methods

### Instance.prototype.get

To get a value and also trigger any listeners who might want to modify the value returned from a "get", you should use the `Instance.prototype.get` function.

#### Parameters 

- `name` (string): The property name.
- `instance` (Object): The instance on which to get the value of the property.

#### Usage

If the instance is an "instanceof" Instance, you can call it from the instance method: 

```javascript
let instance = new Instance({
  //...
});
instance.get(`property`);
```

If the object is __not__ an "instanceof" Instance, you can use the prototype method and pass the object as a second parameter (the `this` parameter): 

```javascript
let obj = {};
Instance.prototype.get(`property`, obj);
// or 
// Instance.prototype.get.call(obj, `property`);
```

Similarly, you can simply import the `get` function directly and use it without working through the Instance.prototype method: 

```javascript
import get from '@schematize/instance.js/src/Instance/get.mjs';
//...
let obj = {};
get('property', obj);
```

#### Events

##### "get" event

The `get` function fires a `"get"` event before returning the property value. This allows listeners to intercept and potentially modify what gets returned.

_Event Detail Properties_:

- `__type__` - The constructor function of the instance
- `instance` - The instance being accessed
- `property` - The property name being retrieved

_Return Value_:

Listeners can set `event.returnValue` to override what gets returned from the `get` function. If no `returnValue` is set, the function returns the actual property value from the instance.

```javascript
instance.on('get', (event) => {
  if (event.detail.property === 'sensitive') {
    event.returnValue = '[REDACTED]';
  }
});

let value = instance.get('sensitive'); // Returns '[REDACTED]' instead of actual value
```

### Instance.prototype.set

To set a value and also trigger any listeners who might want to know when a value was changed on an instance, you should use the `Instance.prototype.set` function.

#### Parameters 

- `name` (string): The property name.
- `value` (mixed): The value to set.
- `instance` (Object): The instance on which to set the value of the property.

#### Usage

If the instance is an "instanceof" Instance, you can call it from the instance method: 

```javascript
let instance = new Instance({
  //...
});
instance.set(`property`, `value`);
```

If the object is __not__ an "instanceof" Instance, you can use the prototype method and pass the object as the third parameter (the `this` parameter): 

```javascript
let obj = {};
Instance.prototype.set(`property`, `value`, obj);
// or 
// Instance.prototype.set.call(obj, `property`, `value`);
```

Similarly, you can simply import the `set` function directly and use it without working through the Instance.prototype method: 

```javascript
import set from '@schematize/instance.js/src/Instance/set.mjs';
//...
let obj = {};
set(`property`, obj, `value`);
```

#### Events

##### "set" event

The `set` function fires a `"set"` event before setting the property value. This allows listeners to intercept and potentially modify the value being set.

_Event Detail Properties_:
- `__type__` - The constructor function of the instance
- `instance` - The instance being modified
- `property` - The property name being set
- `previousValue` - The previous value of the property
- `value` - The new value being set

_Return Value_:
Listeners can set `event.returnValue.value` to override what value gets set on the property. If no `returnValue.value` is set, the function uses the original value.

```javascript
instance.on('set', (event) => {
  if (event.detail.property === 'password') {
    event.returnValue = { value: '[ENCRYPTED]' };
  }
});

instance.set('password', 'plaintext'); // Actually sets '[ENCRYPTED]'
```

##### "modify" event

The `set` function fires a `"modify"` event after the property has been set. This allows listeners to react to completed property changes.

_Event Detail Properties_:
- `__type__` - The constructor function of the instance
- `instance` - The instance that was modified
- `property` - The property name that was set
- `previousValue` - The previous value of the property
- `value` - The new value that was set

##### "change" event

The `set` function fires a `"change"` event when the property value actually changes (previousValue !== value). This provides a higher-level notification of changes.

_Event Detail Properties_:
- `__type__` - The constructor function of the instance
- `instance` - The instance that was modified
- `changes` - A Map containing the property changes
- `added` - Array of new values added
- `removed` - Array of previous values removed

### Instance.prototype.deleteProperty

To delete a property and notify any listeners who might want to know when a property was deleted from an instance, you should use the `Instance.prototype.deleteProperty` function.

#### Parameters 

- `name` (string): The property name.
- `instance` (Object): The instance on which to delete the property.

#### Usage

If the instance is an "instanceof" Instance, you can call it from the instance method: 

```javascript
let instance = new Instance({
  //...
});
instance.deleteProperty(`property`);
```

If the object is __not__ an "instanceof" Instance, you can use the prototype method and pass the object as the second parameter (the `this` parameter): 

```javascript
let obj = {};
Instance.prototype.deleteProperty(`property`, obj);
// or 
// Instance.prototype.deleteProperty.call(obj, `property`);
```

Similarly, you can simply import the `deleteProperty` function directly and use it without working through the Instance.prototype method: 

```javascript
import deleteProperty from '@schematize/instance.js/src/Instance/deleteProperty.mjs';
//...
let obj = {};
deleteProperty(`property`, obj);
```

#### Events

##### "deleteProperty" event

The `deleteProperty` function fires a `"deleteProperty"` event before deleting the property. This allows listeners to intercept and potentially modify the deletion behavior.

_Event Detail Properties_:
- `__type__` - The constructor function of the instance
- `instance` - The instance being modified
- `property` - The property name being deleted
- `previousValue` - The value that will be deleted

_Return Value_:
Listeners can set `event.returnValue.instance` to change which instance the property is deleted from, or `event.returnValue.property` to change which property gets deleted.

```javascript
instance.on('deleteProperty', (event) => {
  if (event.detail.property === 'important') {
    event.returnValue = { property: 'backup_' + event.detail.property };
  }
});

instance.deleteProperty('important'); // Actually deletes 'backup_important'
```

##### "modify" event

The `deleteProperty` function fires a `"modify"` event after the property has been deleted. This allows listeners to react to completed property deletions.

_Event Detail Properties_:
- `__type__` - The constructor function of the instance
- `instance` - The instance that was modified
- `property` - The property name that was deleted
- `previousValue` - The value that was deleted

##### "change" event

The `deleteProperty` function fires a `"change"` event after the property deletion. This provides a higher-level notification of the change.

_Event Detail Properties_:
- `__type__` - The constructor function of the instance
- `instance` - The instance that was modified
- `changes` - A Map containing the property changes
- `added` - Array of new values added (empty for deletions)
- `removed` - Array of previous values removed

### Instance.prototype.defineProperty

To define a property with a descriptor and trigger event listeners, you should use the `Instance.prototype.defineProperty` function. This is similar to `Object.defineProperty` but with event support.

#### Parameters

- `name` (string): The property name to define.
- `descriptor` (Object): The property descriptor object (see [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)).
- `instance` (Object): The instance on which to define the property.

#### Usage

If the instance is an "instanceof" Instance, you can call it from the instance method:

```javascript
let instance = new Instance({
  //...
});
instance.defineProperty('computed', {
  get() { return this.value * 2; },
  enumerable: true
});
```

If the object is __not__ an "instanceof" Instance, you can use the prototype method and pass the object as the third parameter (the `this` parameter):

```javascript
let obj = {};
Instance.prototype.defineProperty('computed', {
  get() { return this.value * 2; },
  enumerable: true
}, obj);
// or 
// Instance.prototype.defineProperty.call(obj, 'computed', descriptor);
```

Similarly, you can simply import the `defineProperty` function directly and use it without working through the Instance.prototype method:

```javascript
import defineProperty from '@schematize/instance.js/src/Instance/defineProperty.mjs';
//...
let obj = {};
defineProperty('computed', descriptor, obj);
```

#### Events

##### "defineProperty" event

The `defineProperty` function fires a `"defineProperty"` event before defining the property. This allows listeners to intercept and potentially modify the property definition.

_Event Detail Properties_:
- `__type__` - The constructor function of the instance
- `instance` - The instance being modified
- `property` - The property name being defined
- `descriptor` - The property descriptor object

_Return Value_:
Listeners can set `event.returnValue.instance` to change which instance the property is defined on, `event.returnValue.property` to change which property gets defined, or `event.returnValue.descriptor` to modify the property descriptor.

```javascript
instance.on('defineProperty', (event) => {
  if (event.detail.property === 'sensitive') {
    event.returnValue = { 
      descriptor: { 
        get() { return '[PROTECTED]'; },
        enumerable: false 
      } 
    };
  }
});

instance.defineProperty('sensitive', { value: 'secret' }); // Actually creates a getter that returns '[PROTECTED]'
```

##### "modify" event

The `defineProperty` function fires a `"modify"` event after the property has been defined. This allows listeners to react to completed property definitions.

_Event Detail Properties_:
- `__type__` - The constructor function of the instance
- `instance` - The instance that was modified
- `property` - The property name that was defined
- `descriptor` - The property descriptor that was applied
- `previousValue` - The previous value of the property
- `value` - The new value of the property

##### "change" event

The `defineProperty` function fires a `"change"` event when the property value actually changes (previousValue !== value). This provides a higher-level notification of changes.

_Event Detail Properties_:
- `__type__` - The constructor function of the instance
- `instance` - The instance that was modified
- `changes` - A Map containing the property changes
- `added` - Array of new values added
- `removed` - Array of previous values removed

### Instance.prototype.assign

To assign multiple properties at once and trigger event listeners, you should use the `Instance.prototype.assign` function. This is similar to `Object.assign` but with event support and the ability to skip individual properties.

#### Parameters

- `properties` (Object): The properties object to assign to the instance.
- `instance` (Object): The instance on which to assign the properties.
- `options` (Object): Optional configuration object.
  - `options.s` (Object): Properties to skip during assignment (skipped properties).
  - `options.c` (boolean): Skip the "change" event (skipChange).

#### Usage

If the instance is an "instanceof" Instance, you can call it from the instance method:

```javascript
let instance = new Instance({
  //...
});
instance.assign({
  name: 'John',
  age: 30,
  city: 'New York'
});
```

If the object is __not__ an "instanceof" Instance, you can use the prototype method and pass the object as the second parameter (the `this` parameter):

```javascript
let obj = {};
Instance.prototype.assign({
  name: 'Jane',
  age: 25
}, obj);
// or 
// Instance.prototype.assign.call(obj, properties);
```

Similarly, you can simply import the `assign` function directly and use it without working through the Instance.prototype method:

```javascript
import assign from '@schematize/instance.js/src/Instance/assign.mjs';
//...
let obj = {};
assign(properties, obj);
```

#### Options

You can skip specific properties during assignment:

```javascript
instance.assign({
  name: 'John',
  age: 30,
  __type__: SomeOtherType  // This will be skipped
}, {
  s: { __type__: 1 }  // Skip the __type__ property
});
```

You can also skip the "change" event:

```javascript
instance.assign(properties, instance, { c: 1 }); // Skip change event
```

#### Events

##### "change" event

The `assign` function fires a single `"change"` event after all properties have been assigned (if any values actually changed). This provides a higher-level notification of multiple changes at once.

**Note**: Individual `"set"`, `"modify"` events are still fired for each property during assignment, but only one `"change"` event is fired at the end if any values changed.

_Event Detail Properties_:
- `__type__` - The constructor function of the instance
- `instance` - The instance that was modified
- `changes` - A Map containing all the property changes
- `added` - Array of new values added (empty for assign)
- `removed` - Array of previous values removed (empty for assign)

```javascript
instance.on('change', (event) => {
  console.log('Multiple properties changed:', event.detail.changes);
  // Logs: Map { 'name' => { to: 'John', from: 'Jane' }, 'age' => { to: 30, from: 25 } }
});

instance.assign({
  name: 'John',
  age: 30
});
```

### Instance.prototype.destroy

To properly destroy an instance and clean up memory, you should use the `Instance.prototype.destroy` function. This removes all properties from the instance and allows the garbage collector to free memory.

**Note**: The `destroy` function removes all properties from the instance, including `__type__` and `__id__`. The instance object itself remains but is completely empty.  This in combination with event listeners can remove references that keep the instance from being garbage collected.

**Note**: Internally Instance will listen to events fired out of this function and remove the Instance from Cache's and other Backref lists.

#### Parameters

- `instance` (Object): The instance to destroy.

#### Usage

If the instance is an "instanceof" Instance, you can call it from the instance method:

```javascript
let instance = new Instance({
  name: 'John',
  age: 30
});
instance.destroy();
// All properties are now removed from the instance
```

If the object is __not__ an "instanceof" Instance, you can use the prototype method and pass the object as the first parameter (the `this` parameter):

```javascript
let obj = { name: 'Jane', age: 25 };
Instance.prototype.destroy(obj);
// or 
// Instance.prototype.destroy.call(obj);
```

Similarly, you can simply import the `destroy` function directly and use it without working through the Instance.prototype method:

```javascript
import destroy from '@schematize/instance.js/src/Instance/destroy.mjs';
//...
let obj = { name: 'Jane', age: 25 };
destroy(obj);
```

#### Events

##### "destroy" event

The `destroy` function fires a `"destroy"` event before destroying the instance. This allows listeners to perform cleanup operations before the instance is destroyed.

_Event Detail Properties_:
- `__type__` - The constructor function of the instance
- `instance` - The instance being destroyed

##### "change" event

The `destroy` function fires a `"change"` event after all properties have been removed. This provides a higher-level notification of the destruction.

_Event Detail Properties_:
- `__type__` - The constructor function of the instance
- `instance` - The instance that was destroyed
- `changes` - A Map containing all the property changes (all properties removed)
- `added` - Array of new values added (empty for destroy)
- `removed` - Array of all previous values that were removed

```javascript
instance.on('destroy', (event) => {
  console.log('Instance is being destroyed:', event.detail.instance);
  // Perform cleanup operations here
});

instance.on('change', (event) => {
  console.log('All properties removed:', event.detail.removed);
  // Logs: ['John', 30, 'someId', Instance]
});

instance.destroy();
```

## Instance Cache

The Instance system maintains a global cache of all instances using their `__id__` as the key. This cache is accessible through `Instance.C` and serves several important purposes:

- **Prevents duplicate instances**: If you try to create an instance with an existing `__id__`, the cached instance is returned instead of creating a new one
- **Enables reference resolution**: When deserializing objects with `__ref__` properties (described later), the cache allows the system to resolve these references to actual instances
- **Performance optimization**: Avoids recreating expensive objects that already exist

```javascript
// Access the cache directly
console.log(Instance.C); // Shows all cached instances by __id__

// Check if an instance exists in cache
const existingInstance = Instance.C['some-id'];
if (existingInstance) {
  console.log('Instance already exists:', existingInstance);
}

// Create an instance with a specific __id__ to retrieve from cache
const instance1 = Instance({ __id__: 'my-unique-id', name: 'First' });
const instance2 = Instance({ __id__: 'my-unique-id', name: 'Second' }); // Returns instance1, not a new instance

console.log(instance1 === instance2); // true
console.log(instance1.name); // 'First' (not 'Second')
```

The cache is automatically managed - instances are added when created and can be removed when destroyed (though the destroy cleanup is currently commented out in the code).

## Backrefs

The backref system handles deferred reference resolution when instances are not yet available in the cache. This is particularly useful during deserialization or when instances are loaded asynchronously.

### How Backrefs Work

When you set a property to an object with a `__ref__` property (instead of `__id__`), the system:

1. **Checks the cache first**: If an instance with that `__id__` exists in `Instance.C`, it immediately replaces the `__ref__` object with the actual instance
2. **Creates a backref if not found**: If the instance doesn't exist yet, it creates a backref entry in `Instance.R` that will be resolved later
3. **Resolves backrefs when instance appears**: When an instance with the matching `__id__` is eventually created or loaded, all backrefs are automatically resolved

### Accessing the Backref Cache

The global backref cache is accessible through `Instance.R`:

```javascript
// Access the backref cache directly
console.log(Instance.R); // Shows all pending backrefs by __id__

// Check if there are pending backrefs for an ID
const pendingBackrefs = Instance.R['some-id'];
if (pendingBackrefs) {
  console.log('Pending backrefs:', pendingBackrefs);
  // Each backref is [object, propertyName] that needs to be resolved
}
```

### Using __ref__ for Deferred References

```javascript
// Create a reference object with __ref__ instead of __id__
const userRef = { __ref__: 'user-123' };

// Set it as a property - this creates a backref if user-123 doesn't exist yet
instance.set('owner', userRef);

// Later, when the actual user instance is created/loaded
const actualUser = Instance({ __id__: 'user-123', name: 'John' });

// The backref is automatically resolved - instance.owner now points to actualUser
console.log(instance.owner === actualUser); // true
console.log(instance.owner.name); // 'John'
```

### Backref Resolution During __id__ Changes

When an instance's `__id__` changes, the system automatically:

1. **Removes old cache entries**: Deletes the instance from the old `__id__` in both `Instance.C` and `Instance.R`
2. **Resolves pending backrefs**: If there were backrefs waiting for the new `__id__`, they are immediately resolved
3. **Updates cache**: Adds the instance to the new `__id__` in `Instance.C`

```javascript
// Change an instance's __id__
instance.set('__id__', 'new-id');

// Any objects with { __ref__: 'new-id' } will now be resolved to this instance
// The old 'old-id' cache entries are cleaned up
```

This system ensures that references are maintained correctly even when instances are loaded out of order or when IDs change.

## Proxy

When using Instance.js you can optionally wrap instances in JavaScript Proxies to provide transparent property access with automatic event dispatching. This creates a "magic" experience where you work with instances using normal object syntax, but all operations trigger events and can be intercepted.

### Enabling Proxy Support

Proxy support is disabled by default for performance reasons. To enable it, set the global variable before loading the library:

```javascript
// Enable proxy support before importing the library
globalThis.__proxy__ = true;

// Now import and use the library
import Instance from '@schematize/instance.js';

// All instances will now be wrapped in proxies
const instance = Instance({ name: 'John' });
```

### The "Magic" Experience

With proxies enabled, you can use normal object syntax and everything "just works":

```javascript
// Normal property access - triggers "get" events
console.log(instance.name); // 'John'

// Normal property assignment - triggers "set" events  
instance.name = 'Jane';

// Normal property deletion - triggers "deleteProperty" events
delete instance.age;

// Normal property definition - triggers "defineProperty" events
Object.defineProperty(instance, 'id', { value: 123 });
```

Under the hood, these operations automatically dispatch events and can be intercepted, allowing for:
- **Change detection**: Know exactly when and how properties change
- **Behavior modification**: Intercept and modify property operations like get, set, deleteProperty, etc.
- **Validation**: Add custom validation logic
- **Logging**: Track all property access and modifications

### Proxy Target References

When an instance is wrapped in a proxy, two special properties are created:

- **`__proxyTarget__`**: References the original instance (the proxy target)
- **`__proxy__`**: References the proxy wrapper

```javascript
const instance = Instance({ name: 'John' });

// Access the original instance (bypasses proxy traps)
const original = instance.__proxyTarget__;
original.name = 'Jane'; // No events fired

// Access the proxy (triggers events)
const proxy = instance.__proxy__;
proxy.name = 'Bob'; // Fires "set" event

// Object equality should use the proxy
console.log(instance === instance.__proxy__); // true
console.log(instance === instance.__proxyTarget__); // false
```

### When to Use Proxy vs Direct Access

**Use the proxy** (normal property access) when you want:
- Event dispatching
- Change detection
- Behavior interception
- Normal application logic

**Use `__proxyTarget__`** when you need:
- Performance-critical operations
- Bypassing event dispatching
- Direct property manipulation
- Internal library operations

### Performance Considerations

Proxies add overhead to every property access, assignment, and deletion. This is why they're disabled by default (`globalThis.__proxy__` is `undefined` by default):

The performance impact is most noticeable in:
- High-frequency property access
- Large-scale data processing
- Performance-critical applications

You must make the judgement call based on your coding style and your specific needs.

## Collection

Collection extends the primitive JavaScript Array with event-driven functionality. It provides all standard Array methods plus custom methods for array manipulation, all with event support.

### Creating Collections

```javascript
import Collection from '@schematize/instance.js/src/Collection.mjs';
```

Create empty collection:

```javascript
let collection = new Collection();
```

Without new keyword:

```javascript
let collection = Collection();
```

Create with initial data:

```javascript
let collection2 = new Collection(['item1', 'item2']);
```

// Create empty collection with properties:

```javascript
let collection3 = new Collection([], { name: 'My Collection' });
```

### Collection EventTarget

One of the biggest goals of the "Collection" constructor is to essentially make an "observable" array.  In order to achieve this, Collections must be able to dispatch events when items in the array are modified or after an operation occurs.

To achieve this, Collection also implements the EventTarget Interface, and utilizes the same conventions like `__dispatch__`, etc.  For more information about how EventTarget works, please see EventTarget above.  By default `Collection.prototype.__dispatch__` is set to `[ Collection.prototype ]` which means you can listen to events for ALL instances of `Collection.prototype` by attaching listeners to `Collection.prototype`: 

```javascript
Collection.prototype.on(`change`, (ev) => {
  // fires for all "change" events across all Collection instances...
});
```

### Standard Array Methods

Because Collection inherits from Array, it has all of the same native methods as Array.  `collection.forEach`, `collection.map`, etc.  SEE: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array.

### Overwritten Array Methods

Arrays natively (as of this writing) only have 9 methods that modify the array in place (or mutate the array rather than simply return a copy of the array). In order to preserve backwards compatibility with these methods while still dispatching events when these arrays change, I have overwritten these mutuating methods so that they dispatch the "change" event after performing the operation.  These are the methods that are overwritten:
- `push`
- `fill`
- `pop`
- `shift`
- `unshift`
- `splice`
- `reverse`
- `sort`
- `copyWithin`

```javascript
collection.push(`new item`);    // Fires `change` event
collection.fill(1, 2, `item`);  // Fires `change` event
collection.pop();               // Fires `change` event
collection.shift();             // Fires `change` event
collection.unshift(`item`);     // Fires `change` event
collection.splice(0, 1, `new`); // Fires `change` event
collection.reverse();           // Fires `change` event
collection.sort();              // Fires `change` event
collection.copyWithin(0, 1, 2); // Fires `change` event
```

### Custom Collection Methods

In order to precisely control when and how collections are updated and be able to interject in that behavior, I've introduce equivilent methods that fire even more events and capture more metadata about what operation has taken place.

### Collection.prototype.append

Append items to the end of the collection. This is equivalent to `Array.prototype.push` but with event support.

#### Usage

If an "instanceof" Collection, you can call the method directly:

```javascript
let collection = new Collection([]);
collection.append(`item`);
```

If not __not__ an "instanceof" Collection, you can use the prototype method.  Unlike the instance methods, you must call the function with `.call` or `.apply` in order to properly set the `this` value to the array-like object. This is because many of the Collection methods have "rest" parameters:

```javascript
let arr = [];
Collection.prototype.append.call(arr, `item`);
```

As with every method, you can simply import the function and use it directly (again making sure to call it with `.call` or `.apply`):

```javascript
import append from '@schematize/instance.js/src/Collection/append.mjs';
//...
let arr = [];
append.call(arr, 'item');
```

#### Parameters

- `...items` (any): Items to append to the collection.

#### Returns

- `number`: The new length of the collection.

#### Events

**Note**: During append operations, individual `"set"` and `"modify"` events may fire for each index being set and for the `"length"` property. A single `"change"` event fires after the complete operation.

##### "change" event

The `append` function fires a `"change"` event after items have been appended.

_Event Detail Properties_:
- `instance` - The collection that was modified
- `changes` - A Map containing the property changes
- `added` - Array of items that were added
- `removed` - Array of items that were removed (empty for append)

```javascript
collection.append('item1', 'item2'); // Returns new length
```

### Collection.prototype.prepend

Prepend items to the beginning of the collection. This is equivalent to `Array.prototype.unshift` but with event support.

#### Usage

If an "instanceof" Collection, you can call the method directly:

```javascript
let collection = new Collection([]);
collection.prepend(`item`);
```

If not __not__ an "instanceof" Collection, you can use the prototype method.  Unlike the instance methods, you must call the function with `.call` or `.apply` in order to properly set the `this` value to the array-like object. This is because many of the Collection methods have "rest" parameters:

```javascript
let arr = [];
Collection.prototype.prepend.call(arr, `item`);
```

As with every method, you can simply import the function and use it directly (again making sure to call it with `.call` or `.apply`):

```javascript
import prepend from '@schematize/instance.js/src/Collection/prepend.mjs';
//...
let arr = [];
prepend.call(arr, 'item');
```

#### Parameters

- `...items` (any): Items to prepend to the collection.

#### Returns

- `number`: The new length of the collection.

#### Events

**Note**: During prepend operations, individual `"set"`, `"modify"`, and `"deleteProperty"` events may fire for each index being modified and for the `"length"` property. A single `"change"` event fires after the complete operation.

##### "change" event

The `prepend` function fires a `"change"` event after items have been prepended.

_Event Detail Properties_:
- `instance` - The collection that was modified
- `changes` - A Map containing the property changes
- `added` - Array of items that were added
- `removed` - Array of items that were removed (empty for prepend)

```javascript
collection.prepend('item1', 'item2'); // Returns new length
```

### Collection.prototype.removeLast

Remove the last item from the collection. This is equivalent to `Array.prototype.pop` but with event support.

#### Usage

If an "instanceof" Collection, you can call the method directly:

```javascript
let collection = new Collection([]);
collection.removeLast();
```

If not __not__ an "instanceof" Collection, you can use the prototype method.  Unlike the instance methods, you must call the function with `.call` or `.apply` in order to properly set the `this` value to the array-like object. This is because many of the Collection methods have "rest" parameters:

```javascript
let arr = [];
Collection.prototype.removeLast.call(arr);
```

As with every method, you can simply import the function and use it directly (again making sure to call it with `.call` or `.apply`):

```javascript
import removeLast from '@schematize/instance.js/src/Collection/removeLast.mjs';
//...
let arr = [];
removeLast.call(arr);
```

#### Parameters

None.

#### Returns

- `any`: The removed item, or `undefined` if the collection was empty.

#### Events

**Note**: During removeLast operations, individual `"deleteProperty"` and `"modify"` events may fire for the index being removed and for the `"length"` property. A single `"change"` event fires after the complete operation.

##### "change" event

The `removeLast` function fires a `"change"` event after the last item has been removed.

_Event Detail Properties_:
- `instance` - The collection that was modified
- `changes` - A Map containing the property changes
- `added` - Array of items that were added (empty for removeLast)
- `removed` - Array containing the removed item

```javascript
let item = collection.removeLast(); // Returns removed item
```

### Collection.prototype.removeFirst

Remove the first item from the collection. This is equivalent to `Array.prototype.shift` but with event support.

#### Usage

If an "instanceof" Collection, you can call the method directly:

```javascript
let collection = new Collection([]);
collection.removeFirst();
```

If not __not__ an "instanceof" Collection, you can use the prototype method.  Unlike the instance methods, you must call the function with `.call` or `.apply` in order to properly set the `this` value to the array-like object. This is because many of the Collection methods have "rest" parameters:

```javascript
let arr = [];
Collection.prototype.removeFirst.call(arr);
```

As with every method, you can simply import the function and use it directly (again making sure to call it with `.call` or `.apply`):

```javascript
import removeFirst from '@schematize/instance.js/src/Collection/removeFirst.mjs';
//...
let arr = [];
removeFirst.call(arr);
```

#### Parameters

None.

#### Returns

- `any`: The removed item, or `undefined` if the collection was empty.

#### Events

**Note**: During removeFirst operations, individual `"set"`, `"modify"`, and `"deleteProperty"` events may fire for each index being shifted and for the `"length"` property. A single `"change"` event fires after the complete operation.

##### "change" event

The `removeFirst` function fires a `"change"` event after the first item has been removed.

_Event Detail Properties_:
- `instance` - The collection that was modified
- `changes` - A Map containing the property changes
- `added` - Array of items that were added (empty for removeFirst)
- `removed` - Array containing the removed item

```javascript
let item = collection.removeFirst(); // Returns removed item
```

### Collection.prototype.replace

Replace items in the collection. This is equivalent to `Array.prototype.splice` but with event support.

#### Usage

If an "instanceof" Collection, you can call the method directly:

```javascript
let collection = new Collection([]);
collection.replace(0, 1, `newItem`);
```

If not __not__ an "instanceof" Collection, you can use the prototype method.  Unlike the instance methods, you must call the function with `.call` or `.apply` in order to properly set the `this` value to the array-like object. This is because many of the Collection methods have "rest" parameters:

```javascript
let arr = [];
Collection.prototype.replace.call(arr, 0, 1, `newItem`);
```

As with every method, you can simply import the function and use it directly (again making sure to call it with `.call` or `.apply`):

```javascript
import replace from '@schematize/instance.js/src/Collection/replace.mjs';
//...
let arr = [];
replace.call(arr, 0, 1, 'newItem');
```

#### Parameters

- `start` (number): The index at which to start replacing.
- `deleteCount` (number): The number of items to remove.
- `...items` (any): Items to insert.

#### Returns

- `Array`: An array containing the removed items.

#### Events

**Note**: During replace operations, individual `"set"`, `"modify"`, and `"deleteProperty"` events may fire for each index being modified and for the `"length"` property. A single `"change"` event fires after the complete operation.

##### "change" event

The `replace` function fires a `"change"` event after items have been replaced.

_Event Detail Properties_:
- `instance` - The collection that was modified
- `changes` - A Map containing the property changes
- `added` - Array of items that were added
- `removed` - Array of items that were removed

```javascript
let removed = collection.replace(0, 2, 'new1', 'new2'); // Returns removed items
```

### Collection.prototype.flip

Reverse the order of items in the collection. This is equivalent to `Array.prototype.reverse` but with event support.

#### Usage

If an "instanceof" Collection, you can call the method directly:

```javascript
let collection = new Collection([]);
collection.flip();
```

If not __not__ an "instanceof" Collection, you can use the prototype method.  Unlike the instance methods, you must call the function with `.call` or `.apply` in order to properly set the `this` value to the array-like object. This is because many of the Collection methods have "rest" parameters:

```javascript
let arr = [];
Collection.prototype.flip.call(arr);
```

As with every method, you can simply import the function and use it directly (again making sure to call it with `.call` or `.apply`):

```javascript
import flip from '@schematize/instance.js/src/Collection/flip.mjs';
//...
let arr = [];
flip.call(arr);
```

#### Parameters

None.

#### Returns

- `Collection`: The collection itself (for chaining).

#### Events

**Note**: During flip operations, individual `"set"` and `"modify"` events may fire for each index being swapped. A single `"change"` event fires after the complete operation.

##### "change" event

The `flip` function fires a `"change"` event after the collection has been reversed.

_Event Detail Properties_:
- `instance` - The collection that was modified
- `changes` - A Map containing the property changes
- `added` - Array of items that were added (empty for flip)
- `removed` - Array of items that were removed (empty for flip)

```javascript
collection.flip(); // Returns the collection
```

### Collection.prototype.order

Sort the items in the collection. This is equivalent to `Array.prototype.sort` but with event support.

#### Usage

If an "instanceof" Collection, you can call the method directly:

```javascript
let collection = new Collection([]);
collection.order((a, b) => a - b);
```

If not __not__ an "instanceof" Collection, you can use the prototype method.  Unlike the instance methods, you must call the function with `.call` or `.apply` in order to properly set the `this` value to the array-like object. This is because many of the Collection methods have "rest" parameters:

```javascript
let arr = [];
Collection.prototype.order.call(arr, (a, b) => a - b);
```

As with every method, you can simply import the function and use it directly (again making sure to call it with `.call` or `.apply`):

```javascript
import order from '@schematize/instance.js/src/Collection/order.mjs';
//...
let arr = [];
order.call(obj, (a, b) => a - b);
```

#### Parameters

- `comparefn` (function): Optional comparison function for sorting.

#### Returns

- `Collection`: The collection itself (for chaining).

#### Events

**Note**: During order operations, individual `"set"`, `"modify"`, and `"deleteProperty"` events may fire for each index being reordered. A single `"change"` event fires after the complete operation.

##### "change" event

The `order` function fires a `"change"` event after the collection has been sorted (only if changes were made).

_Event Detail Properties_:
- `instance` - The collection that was modified
- `changes` - A Map containing the property changes
- `added` - Array of items that were added (empty for order)
- `removed` - Array of items that were removed (empty for order)

```javascript
collection.order((a, b) => a - b); // Returns the collection
```

### Collection as a Proxy

When `globalThis.__proxy__` is enabled, Collection instances are also wrapped in proxies, providing the same "magic" behavior for array operations.

#### Magic Array Operations

With proxy support enabled, you can use normal array syntax and all operations trigger events:

```javascript
// Enable proxy support
globalThis.__proxy__ = true;
import { Collection } from '@schematize/instance.js';

const collection = new Collection(['a', 'b', 'c']);

// Normal index access - triggers "get" events
console.log(collection[0]); // 'a'

// Normal index assignment - triggers "set" events
collection[1] = 'x'; // Fires "set" and "change" events

// Normal index deletion - triggers "deleteProperty" events
delete collection[2]; // Fires "deleteProperty" and "change" events

// Normal array methods work with automatic event dispatching
collection.push('d'); // Fires "change" event
collection.pop(); // Fires "change" event
```

#### Standard Array Method Integration

The proxy system captures JavaScript's internal index access, assignment, and deletion operations that occur within standard array methods:

```javascript
// These operations internally trigger proxy traps:
collection.splice(1, 2, 'new1', 'new2'); // Internal index access/assignment triggers events
collection.sort(); // Internal index swapping triggers events
collection.reverse(); // Internal index swapping triggers events
collection.fill('x', 1, 3); // Internal index assignment triggers events
```

This means that even when using standard array methods, you get full event coverage for all the underlying property operations, providing complete observability into array changes.

#### Proxy Target Access

Collections also have the same proxy target references:

```javascript
// Access the original array (bypasses proxy traps)
const original = collection.__proxyTarget__;
original[0] = 'direct'; // No events fired

// Access the proxy (triggers events)
const proxy = collection.__proxy__;
proxy[0] = 'magic'; // Fires "set" event
```

This gives you the flexibility to choose between performance (direct access) and features (event dispatching) based on your needs.

## Utility Functions

### sid - Super ID

A sid is composed of: 

1. the current timestamp in milliseconds, 
2. a crypographically generated random string, 
3. and an incrementing id within the millisecond.
4. Math.random.

In theory it is universally unique and has been tested, but more tests could be done to be sure.

### findType

Find the `__type__` of an instance. Returns the instance's `__type__` property if it exists, otherwise returns the constructor from the instance's prototype.

```javascript
import findType from '@schematize/instance.js/src/utils/findType.mjs';

let instance = new Instance();
let type = findType(instance); // Returns Instance constructor

let AType = {};
let obj = {
  __type__: AType,
};
findType(obj); // returns AType
```

### getConstructor

Get the constructor function from an instance's prototype chain.

```javascript
import getConstructor from '@schematize/instance.js/src/utils/getConstructor.mjs';

let instance = new Instance();
let constructor = getConstructor(instance); // Returns Instance constructor

class SweetClass {}
let instance = new SweetClass();
let constructor = getConstructor(SweetClass); // Returns SweetClass class constructor
```

### isClassOrFunctionConstructor

Check if a value is a class or function constructor (has a `prototype` property).

```javascript
import isClassOrFunctionConstructor from '@schematize/instance.js/src/utils/isClassOrFunctionConstructor.mjs';

isClassOrFunctionConstructor(Instance); // true
isClassOrFunctionConstructor(class {}); // true
isClassOrFunctionConstructor(function () {}); // true
isClassOrFunctionConstructor(() => {}); // false (arrow function)
isClassOrFunctionConstructor({}); // false (object)
```

## Browser Support

Instance.js works in all modern browsers and Node.js environments that support:
- ES6 Classes
- Proxy (optional)
- Map/Set
- Symbol

## License

MIT