Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
94db4af
Rename "registry" file to "registries"
aedart May 11, 2026
8e9328a
Cleanup
aedart May 11, 2026
294666f
Add interfaces for Owner Context and Member Address
aedart May 11, 2026
7c1e534
Fix formatting and minor lint issue
aedart May 11, 2026
7efd6aa
Add OwnerContext and MemberAddress
aedart May 11, 2026
d9bab56
Refactor, allow ctx to be set
aedart May 11, 2026
b94ee1f
Cleanup
aedart May 11, 2026
cbb9636
Add test for using symbol on instance member / method
aedart May 11, 2026
a51bd55
Fix formatting
aedart May 11, 2026
14fa2da
Fix formatting
aedart May 11, 2026
82f1529
Fix unable to use symbol as key for instance method
aedart May 11, 2026
eecafc2
Add test for storing symbol on static method
aedart May 11, 2026
ca80cd7
Update dependencies
aedart May 13, 2026
80d3439
Add context cache (registry)
aedart May 13, 2026
7539991
Add getOrCreateOwnerContext() util
aedart May 13, 2026
e2fc24b
Add "member address" registry
aedart May 13, 2026
b7f0d18
Add register address util
aedart May 13, 2026
33cae73
Register member address when member is a method
aedart May 13, 2026
5b2b4f6
Optimise Owner Context, avoid using toWeakRef
aedart May 13, 2026
1408df3
Optimise "base path" and path() method
aedart May 13, 2026
832d692
Add "find or create" member address util
aedart May 13, 2026
9efe67a
Reuse existing member address, if one already exists
aedart May 13, 2026
57b7209
Fix lint and formatting
aedart May 13, 2026
c9afcd1
Set allow builds
aedart May 13, 2026
e80313d
Refactor Metadata, allow target to be a class member
aedart May 13, 2026
2e97716
WIP
aedart May 13, 2026
9ee69c3
Cleanup
aedart May 13, 2026
ff040f4
Extract security tests into own file
aedart May 14, 2026
ed24166
Extract "member" related tests into own file
aedart May 14, 2026
84bf3a1
Handle accessor
aedart May 14, 2026
15f779d
Resolve meta inheritance, for address registration
aedart May 14, 2026
a673c98
Add tests for "instance" members
aedart May 14, 2026
382fc48
Fix lint and formatting
aedart May 14, 2026
719a40b
Rename test file
aedart May 15, 2026
9d5bce3
Improve test names / descriptions
aedart May 15, 2026
87610b4
Cleanup
aedart May 15, 2026
fb0b575
Cleanup
aedart May 15, 2026
d05e252
Rename "proto" argument to "target"
aedart May 15, 2026
73b733b
Refactor, add support for returning descriptors of static defined props
aedart May 15, 2026
f541aa5
Change release notes
aedart May 15, 2026
a143ad3
Add support for inheriting meta for static members
aedart May 15, 2026
fb5f267
Add tests for getting meta via static members references (incomplete)
aedart May 15, 2026
96233cf
Disable dummy class
aedart May 15, 2026
b014287
Fix formatting
aedart May 15, 2026
c93e266
Fix formatting
aedart May 15, 2026
a380fb8
Refactor / cleanup
aedart May 17, 2026
84285f8
Fix typo
aedart May 17, 2026
0b57e82
Add inheritMeta decorator
aedart May 17, 2026
5b6c652
Add "fail" tests for inheritMeta decorator
aedart May 17, 2026
ad962f2
Add tests for overridden static members and use of @inheritMeta()
aedart May 17, 2026
3de53f7
Fix lint and formatting
aedart May 17, 2026
5d909e7
Change release notes
aedart May 17, 2026
c490458
Fix multiple pnpm versions specified
aedart May 17, 2026
fe6b2b2
(Re)fix multiple pnpm versions specified
aedart May 17, 2026
9733cf6
Upgrade actions from version 4 to 6
aedart May 17, 2026
8fd0a8f
Fix unknown option --prefer-latest
aedart May 17, 2026
e116058
Change pipeline order
aedart May 17, 2026
f47c88a
Surpress type errors in mixin tests
aedart May 17, 2026
f17007c
Move type-check into QC section
aedart May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,31 @@ jobs:

steps:
- name: 'Checkout'
uses: actions/checkout@v4
uses: actions/checkout@v6

# 1. Setup pnpm
- name: 'Install pnpm'
uses: pnpm/action-setup@v4
with:
version: 9
uses: pnpm/action-setup@v6

# 2. Setup Node 24 (No pnpm cache here to ensure a fresh fetch)
- name: 'Use Node ${{ matrix.node }}'
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}

# 3. Force Fresh Dependency Resolution
# We ignore the lockfile and prefer the latest versions in range.
- name: 'Install latest dependencies'
run: pnpm install --no-frozen-lockfile --prefer-latest
run: pnpm install --no-frozen-lockfile

# 4. Install Playwright Browsers (Required for Vitest)
- name: 'Install Playwright Browsers'
run: pnpm exec playwright install --with-deps chromium firefox

# 5. Run the Pipeline
- name: 'Build'
run: pnpm build

- name: 'Check Formatting'
run: pnpm format

Expand All @@ -54,8 +55,5 @@ jobs:
- name: 'Type Check'
run: pnpm type-check

- name: 'Build'
run: pnpm build

- name: 'Test (Node & Headless Browsers)'
run: pnpm test
4 changes: 3 additions & 1 deletion CHANGELOG_CLEAN_SLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Temporary changelog for feature branch.
* `scripts/fix-esm-imports.js` util for fixing ESM imports.
* `toParts()` util function (_support package_).
* `isException()` util (_support package_).
* `@inheritMeta()` decorator (_support package_).

### Changed

Expand All @@ -41,6 +42,7 @@ Temporary changelog for feature branch.
* `useCloneable` option has been renamed to `clone` and set to `false` by default, in `MergeOptions` (_contracts and support packages_)
* Object `merge()` behaviour changed to correctly deal with concat-spreadable objects and arrays (_support package_).
* `set()`, `get()`, `has()`, `forget()` and `isArrayLike()` no longer use lodash (_support package_).
* `ObjectId` properties are now set to be private.

**Non-breaking Changes**

Expand All @@ -49,7 +51,7 @@ Temporary changelog for feature branch.
* Optimized performance of reflections utilities (_support package_).
* Optimized performance of objects' `hasAll()`, `hasAny()`, `forgetAll()`, `ObjectId`, `isset()` and `populate()` (_support package_).
* Improved typing of array `merge()` to work with any amount of arrays (_support package_).
* `ObjectId` properties are now set to be private.
* `getClassPropertyDescriptors()` can now also return property descriptors for static defined properties (_support package_).
* The TypeScript source files are now also published, along with appropriate source maps.
* dprint is now used for formatting.

Expand Down
22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@
"@babel/plugin-proposal-decorators": "^7.29.0",
"@eslint/js": "^10.0.1",
"@types/luxon": "^3.7.1",
"@types/node": "^25.6.2",
"@vitest/browser": "^4.1.5",
"@vitest/browser-playwright": "^4.1.5",
"@vitest/coverage-istanbul": "^4.1.5",
"@vitest/coverage-v8": "^4.1.5",
"@types/node": "^25.7.0",
"@vitest/browser": "^4.1.6",
"@vitest/browser-playwright": "^4.1.6",
"@vitest/coverage-istanbul": "^4.1.6",
"@vitest/coverage-v8": "^4.1.6",
"@vuepress/bundler-vite": "2.0.0-rc.29",
"@vuepress/client": "2.0.0-rc.29",
"@vuepress/plugin-back-to-top": "2.0.0-rc.128",
Expand All @@ -47,14 +47,14 @@
"eslint": "^10.3.0",
"globals": "^17.6.0",
"luxon": "^3.7.2",
"playwright": "^1.59.1",
"playwright": "^1.60.0",
"sass": "^1.99.0",
"tslib": "^2.8.1",
"turbo": "^2.9.12",
"typescript": "^6.0.3",
"typescript-eslint": "^8.59.2",
"vite": "^8.0.11",
"vitest": "^4.1.5",
"typescript-eslint": "^8.59.3",
"vite": "^8.0.12",
"vitest": "^4.1.6",
"vue": "^3.5.34",
"vuepress": "2.0.0-rc.29"
},
Expand All @@ -67,13 +67,13 @@
"--- [ Development & Docs ] ---": "echo",
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs",
"type-check": "turbo run type-check && tsc -p tsconfig.json --noEmit",
"--- [ Quality Control (Lint/Format/Test) ] ---": "echo",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "dprint check",
"format:fix": "dprint fmt",
"fix:all": "npm run format:fix && npm run lint:fix && npm run fix:imports",
"type-check": "turbo run type-check && tsc -p tsconfig.json --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"--- [ Build & Release ] ---": "echo",
Expand Down Expand Up @@ -104,5 +104,5 @@
"publish-check": "Simulates a workspace-wide npm publish (dry-run).",
"publish": "Full release workflow: sync, build, tag, and publish."
},
"packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8"
"packageManager": "pnpm@11.1.1"
}
51 changes: 51 additions & 0 deletions packages/contracts/src/support/meta/MemberAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Key } from '../types.js';
import OwnerContext from './OwnerContext.js';

/**
* Member Address
*/
export default interface MemberAddress {
/**
* Owner Context
*
* @type {OwnerContext | undefined}
*/
ctx: OwnerContext | undefined;

/**
* Indicates whether the member is static (`true`) or otherwise (`false`)
*
* @type {boolean}
*/
readonly static: boolean;

/**
* The kind of member
*
* @type {string}
*/
readonly kind: string;

/**
* Name of the member
*
* @type {string|symbol}
*/
readonly name: string | symbol;

/**
* The "base" path to where metadata is stored inside a Meta Repository
*
* @type {Key}
*/
readonly basePath: Key;

/**
* Returns the full path to the given key in a Meta Repository, for the member
*
* @param {Key} [key]
*
* @returns {Key} {@link basePath} if no key provided
*/
path(key?: Key): Key;
}
11 changes: 11 additions & 0 deletions packages/contracts/src/support/meta/OwnerContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Owner Context
*/
export default interface OwnerContext {
/**
* Reference to the owner of a Meta Repository
*
* @type {WeakRef<object>}
*/
readonly ownerRef: WeakRef<object>;
}
3 changes: 1 addition & 2 deletions packages/contracts/src/support/meta/Repository.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// @aedart/contracts/support/meta/Repository.js
import { Key } from '../types.js';

/**
* Metadata Repository Contract
* Metadata Repository
*/
export default interface Repository {
/**
Expand Down
4 changes: 3 additions & 1 deletion packages/contracts/src/support/meta/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
*/
export const SUPPORT_META: unique symbol = Symbol('@aedart/contracts/support/meta');

import MemberAddress from './MemberAddress.js';
import MetaEntry from './MetaEntry.js';
import OwnerContext from './OwnerContext.js';
import Repository from './Repository.js';
export { type MetaEntry, type Repository };
export { type MemberAddress, type MetaEntry, type OwnerContext, type Repository };

export type * from './types.js';
109 changes: 109 additions & 0 deletions packages/support/src/meta/Address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { type Key } from '@aedart/contracts/support';
import { type MemberAddress, type OwnerContext } from '@aedart/contracts/support/meta';
import { toParts } from '../objects/toParts.js';

/**
* Member Address
*/
export default class Address implements MemberAddress
{
/**
* @inheritdoc
*/
ctx: OwnerContext | undefined;

/**
* @inheritdoc
*/
readonly static: boolean;

/**
* @inheritdoc
*/
readonly kind: string;

/**
* @inheritdoc
*/
readonly name: string | symbol;

/**
* @inheritdoc
*/
readonly basePath: Key;

/**
* Create a new member address instance
*
* @param {OwnerContext | undefined} ctx
* @param {boolean} isStatic
* @param {string} kind
* @param {string | symbol} name
*/
constructor(
ctx: OwnerContext | undefined,
isStatic: boolean,
kind: string,
name: string | symbol,
)
{
this.ctx = ctx;
this.static = isStatic;
this.kind = kind;
this.name = name;
this.basePath = this.resolveBasePath();
}

/**
* @inheritdoc
*/
path(key?: Key): Key
{
if (key === undefined || key === null) {
return this.basePath;
}

const base = this.basePath as PropertyKey[];
const parts = toParts(key);

if (parts.length === 0) {
return this.basePath;
}

// Pre-allocate the exact array size needed.
// This avoids V8 inner-loop array resizing and memory reallocation steps.
const output = new Array<PropertyKey>(base.length + parts.length);

// Linear copy operations (highly optimized by V8 runtime JIT)
for (let i = 0; i < base.length; ++i) {
output[i] = base[i];
}

for (let i = 0; i < parts.length; ++i) {
output[base.length + i] = parts[i];
}

return output;
}

/**
* Resolves the base path
*
* @returns {Key}
*
* @protected
*/
protected resolveBasePath(): Key
{
const kind = this.kind === 'method' ? 'methods' : 'fields';

// Direct layout construction avoids slow unshift structural re-indexes
// E.g. [ 'static', 'methods', 'playSound' ], or [ 'methods', 'playSound' ]
const path = this.static
? ['static', kind, this.name]
: [kind, this.name];

// Freezing stops V8 from keeping hidden "growth memory buffers"
return Object.freeze(path) as unknown as Key;
}
}
41 changes: 37 additions & 4 deletions packages/support/src/meta/Metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Key } from '@aedart/contracts/support';
import { toParts } from '../objects/toParts.js';
import { isConstructor } from '../reflections/isConstructor.js';
import { getOrCreateRepository } from './getOrCreateRepository.js';
import { addressRegistry } from './registries.js';
import type Resolved from './Resolved.js';

/**
* Metadata Helper
Expand All @@ -23,9 +25,9 @@ export default class Metadata
*/
static get<T>(target: object, key: Key, defaultValue?: T): T | undefined
{
const resolved = this.resolveTarget(target, key);
const { resolvedTarget, resolvedKey } = this.resolveTargetAndKey(target, key);

return getOrCreateRepository(resolved).get<T>(key, defaultValue);
return getOrCreateRepository(resolvedTarget).get<T>(resolvedKey, defaultValue);
}

/**
Expand All @@ -38,9 +40,9 @@ export default class Metadata
*/
static has(target: object, key: Key): boolean
{
const resolved = this.resolveTarget(target, key);
const { resolvedTarget, resolvedKey } = this.resolveTargetAndKey(target, key);

return getOrCreateRepository(resolved).has(key);
return getOrCreateRepository(resolvedTarget).has(resolvedKey);
}

/**
Expand All @@ -55,6 +57,37 @@ export default class Metadata
return getOrCreateRepository(target).all();
}

/**
* Resolve the actual target and key for metadata lookup
*
* @param {object} target
* @param {Key} key
*
* @returns {Resolved}
*
* @protected
*/
protected static resolveTargetAndKey(target: object, key: Key): Resolved
{
let owner = target;
let resolvedKey = key;

const address = addressRegistry.get(target);
if (address !== undefined) {
const resolvedOwner = address.ctx?.ownerRef?.deref();
if (resolvedOwner !== undefined) {
owner = resolvedOwner;
}

resolvedKey = address.path(key);
}

return {
resolvedTarget: this.resolveTarget(owner, resolvedKey),
resolvedKey,
};
}

/**
* Resolves the actual target for metadata lookup
*
Expand Down
Loading