@praxisjs/core 1.8.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/__tests__/component.test.js +59 -26
  3. package/dist/__tests__/component.test.js.map +1 -1
  4. package/dist/__tests__/resource-server-pass.test.d.ts +2 -0
  5. package/dist/__tests__/resource-server-pass.test.d.ts.map +1 -0
  6. package/dist/__tests__/resource-server-pass.test.js +78 -0
  7. package/dist/__tests__/resource-server-pass.test.js.map +1 -0
  8. package/dist/__tests__/synced.test.js +6 -7
  9. package/dist/__tests__/synced.test.js.map +1 -1
  10. package/dist/async/resource-cache.d.ts +7 -0
  11. package/dist/async/resource-cache.d.ts.map +1 -1
  12. package/dist/async/resource-cache.js +18 -0
  13. package/dist/async/resource-cache.js.map +1 -1
  14. package/dist/async/resource.d.ts +7 -0
  15. package/dist/async/resource.d.ts.map +1 -1
  16. package/dist/async/resource.js +16 -1
  17. package/dist/async/resource.js.map +1 -1
  18. package/dist/async/server-mode.d.ts +10 -0
  19. package/dist/async/server-mode.d.ts.map +1 -0
  20. package/dist/async/server-mode.js +15 -0
  21. package/dist/async/server-mode.js.map +1 -0
  22. package/dist/component/base.d.ts +2 -26
  23. package/dist/component/base.d.ts.map +1 -1
  24. package/dist/component/base.js +2 -29
  25. package/dist/component/base.js.map +1 -1
  26. package/dist/component/internals.d.ts +16 -0
  27. package/dist/component/internals.d.ts.map +1 -0
  28. package/dist/component/internals.js +70 -0
  29. package/dist/component/internals.js.map +1 -0
  30. package/dist/component/stateful.d.ts +0 -15
  31. package/dist/component/stateful.d.ts.map +1 -1
  32. package/dist/component/stateful.js +0 -20
  33. package/dist/component/stateful.js.map +1 -1
  34. package/dist/component/stateless.d.ts +1 -1
  35. package/dist/component/stateless.d.ts.map +1 -1
  36. package/dist/component/stateless.js +3 -2
  37. package/dist/component/stateless.js.map +1 -1
  38. package/dist/internal.d.ts +4 -2
  39. package/dist/internal.d.ts.map +1 -1
  40. package/dist/internal.js +4 -2
  41. package/dist/internal.js.map +1 -1
  42. package/package.json +3 -3
  43. package/src/__tests__/component.test.ts +83 -26
  44. package/src/__tests__/resource-server-pass.test.ts +90 -0
  45. package/src/async/resource-cache.ts +20 -0
  46. package/src/async/resource.ts +17 -0
  47. package/src/async/server-mode.ts +16 -0
  48. package/src/component/base.ts +4 -38
  49. package/src/component/internals.ts +119 -0
  50. package/src/component/stateful.ts +1 -21
  51. package/src/component/stateless.ts +4 -3
  52. package/src/internal.ts +19 -1
@@ -0,0 +1,16 @@
1
+ export declare const componentPropsType: unique symbol;
2
+ export declare function initComponentInternals(instance: object, props: object): void;
3
+ export declare function getComponentProps(instance: object): object;
4
+ export declare function setComponentProps(instance: object, props: Record<string, unknown>): void;
5
+ export declare function getComponentRawProp(instance: object, name: string): unknown;
6
+ export declare function getComponentDefault(instance: object, name: string): unknown;
7
+ export declare function setComponentDefault(instance: object, name: string, value: unknown): void;
8
+ export declare function getComponentDefaults(instance: object): Record<string, unknown>;
9
+ export declare function getComponentAnchor(instance: object): Comment | undefined;
10
+ export declare function setComponentAnchor(instance: object, anchor: Comment | undefined): void;
11
+ export declare function isComponentMounted(instance: object): boolean;
12
+ export declare function setComponentMounted(instance: object, mounted: boolean): void;
13
+ export declare function markStateDirty(instance: object): void;
14
+ export declare function isStateDirty(instance: object): boolean;
15
+ export declare function setStateDirty(instance: object, stateDirty: boolean): void;
16
+ //# sourceMappingURL=internals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internals.d.ts","sourceRoot":"","sources":["../../src/component/internals.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,EAAE,OAAO,MAA4C,CAAC;AAoBrF,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,IAAI,CAON;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CAMN;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAET;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAET;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,GACb,IAAI,CAEN;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAExE;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,GAAG,SAAS,GAC1B,IAAI,CAEN;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,GACf,IAAI,CAEN;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAKrD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAItD;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,OAAO,GAClB,IAAI,CAKN"}
@@ -0,0 +1,70 @@
1
+ export const componentPropsType = Symbol("praxis.componentPropsType");
2
+ const componentInternals = new WeakMap();
3
+ function getInternals(instance) {
4
+ const internals = componentInternals.get(instance);
5
+ if (!internals) {
6
+ throw new TypeError("Expected a PraxisJS component instance.");
7
+ }
8
+ return internals;
9
+ }
10
+ export function initComponentInternals(instance, props) {
11
+ componentInternals.set(instance, {
12
+ rawProps: { ...props },
13
+ mounted: false,
14
+ defaults: {},
15
+ stateDirty: false,
16
+ });
17
+ }
18
+ export function getComponentProps(instance) {
19
+ return getInternals(instance).rawProps;
20
+ }
21
+ export function setComponentProps(instance, props) {
22
+ const rawProps = getInternals(instance).rawProps;
23
+ Object.keys(rawProps).forEach((key) => {
24
+ Reflect.deleteProperty(rawProps, key);
25
+ });
26
+ Object.assign(rawProps, props);
27
+ }
28
+ export function getComponentRawProp(instance, name) {
29
+ return getInternals(instance).rawProps[name];
30
+ }
31
+ export function getComponentDefault(instance, name) {
32
+ return getInternals(instance).defaults[name];
33
+ }
34
+ export function setComponentDefault(instance, name, value) {
35
+ getInternals(instance).defaults[name] = value;
36
+ }
37
+ export function getComponentDefaults(instance) {
38
+ return getInternals(instance).defaults;
39
+ }
40
+ export function getComponentAnchor(instance) {
41
+ return getInternals(instance).anchor;
42
+ }
43
+ export function setComponentAnchor(instance, anchor) {
44
+ getInternals(instance).anchor = anchor;
45
+ }
46
+ export function isComponentMounted(instance) {
47
+ return getInternals(instance).mounted;
48
+ }
49
+ export function setComponentMounted(instance, mounted) {
50
+ getInternals(instance).mounted = mounted;
51
+ }
52
+ export function markStateDirty(instance) {
53
+ const internals = componentInternals.get(instance);
54
+ if (internals) {
55
+ internals.stateDirty = true;
56
+ }
57
+ }
58
+ export function isStateDirty(instance) {
59
+ const internals = componentInternals.get(instance);
60
+ if (internals)
61
+ return internals.stateDirty;
62
+ return false;
63
+ }
64
+ export function setStateDirty(instance, stateDirty) {
65
+ const internals = componentInternals.get(instance);
66
+ if (internals) {
67
+ internals.stateDirty = stateDirty;
68
+ }
69
+ }
70
+ //# sourceMappingURL=internals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internals.js","sourceRoot":"","sources":["../../src/component/internals.ts"],"names":[],"mappings":"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"}
@@ -1,19 +1,4 @@
1
1
  import { RootComponent } from "./base";
2
2
  export declare abstract class StatefulComponent extends RootComponent<Record<string, unknown>> {
3
- /**
4
- * Default field values declared in the class, used to reset props on update.
5
- * @internal
6
- */
7
- readonly _defaults: Record<string, unknown>;
8
- /**
9
- * Set to `true` by `@State` on any write; cleared by the renderer after each re-render.
10
- * @internal
11
- */
12
- _stateDirty: boolean;
13
- /**
14
- * Raw props. Use `@Prop()` fields instead of reading this directly in user code.
15
- * @internal
16
- */
17
- get props(): Record<string, unknown>;
18
3
  }
19
4
  //# sourceMappingURL=stateful.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stateful.d.ts","sourceRoot":"","sources":["../../src/component/stateful.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,8BAAsB,iBAAkB,SAAQ,aAAa,CAC3D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACxB;IACC;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM;IAEjD;;;OAGG;IACH,WAAW,UAAS;IAEpB;;;OAGG;IACH,IAAa,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE5C;CACF"}
1
+ {"version":3,"file":"stateful.d.ts","sourceRoot":"","sources":["../../src/component/stateful.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,8BAAsB,iBAAkB,SAAQ,aAAa,CAC3D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACxB;CAAG"}
@@ -1,24 +1,4 @@
1
1
  import { RootComponent } from "./base";
2
2
  export class StatefulComponent extends RootComponent {
3
- constructor() {
4
- super(...arguments);
5
- /**
6
- * Default field values declared in the class, used to reset props on update.
7
- * @internal
8
- */
9
- this._defaults = {};
10
- /**
11
- * Set to `true` by `@State` on any write; cleared by the renderer after each re-render.
12
- * @internal
13
- */
14
- this._stateDirty = false;
15
- }
16
- /**
17
- * Raw props. Use `@Prop()` fields instead of reading this directly in user code.
18
- * @internal
19
- */
20
- get props() {
21
- return this._rawProps;
22
- }
23
3
  }
24
4
  //# sourceMappingURL=stateful.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stateful.js","sourceRoot":"","sources":["../../src/component/stateful.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,MAAM,OAAgB,iBAAkB,SAAQ,aAE/C;IAFD;;QAGE;;;WAGG;QACM,cAAS,GAA4B,EAAE,CAAC;QAEjD;;;WAGG;QACH,gBAAW,GAAG,KAAK,CAAC;IAStB,CAAC;IAPC;;;OAGG;IACH,IAAa,KAAK;QAChB,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;CACF"}
1
+ {"version":3,"file":"stateful.js","sourceRoot":"","sources":["../../src/component/stateful.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,MAAM,OAAgB,iBAAkB,SAAQ,aAE/C;CAAG"}
@@ -4,7 +4,7 @@ type StatelessComponentType<T> = T & {
4
4
  children?: Children;
5
5
  };
6
6
  export declare abstract class StatelessComponent<T extends object = Record<never, never>> extends RootComponent<StatelessComponentType<T>> {
7
- /** Public props API for presentational components — see `RootComponent.props`. */
7
+ /** Public props API for presentational components. */
8
8
  get props(): StatelessComponentType<T>;
9
9
  }
10
10
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"stateless.d.ts","sourceRoot":"","sources":["../../src/component/stateless.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,KAAK,sBAAsB,CAAC,CAAC,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAE,CAAC;AAE7D,8BAAsB,kBAAkB,CACtC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CACvC,SAAQ,aAAa,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;IAChD,kFAAkF;IAClF,IAAa,KAAK,IAAI,sBAAsB,CAAC,CAAC,CAAC,CAE9C;CACF"}
1
+ {"version":3,"file":"stateless.d.ts","sourceRoot":"","sources":["../../src/component/stateless.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAGvC,KAAK,sBAAsB,CAAC,CAAC,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAE,CAAC;AAE7D,8BAAsB,kBAAkB,CACtC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CACvC,SAAQ,aAAa,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;IAChD,sDAAsD;IACtD,IAAI,KAAK,IAAI,sBAAsB,CAAC,CAAC,CAAC,CAErC;CACF"}
@@ -1,8 +1,9 @@
1
1
  import { RootComponent } from "./base";
2
+ import { getComponentProps } from "./internals";
2
3
  export class StatelessComponent extends RootComponent {
3
- /** Public props API for presentational components — see `RootComponent.props`. */
4
+ /** Public props API for presentational components. */
4
5
  get props() {
5
- return this._rawProps;
6
+ return getComponentProps(this);
6
7
  }
7
8
  }
8
9
  //# sourceMappingURL=stateless.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stateless.js","sourceRoot":"","sources":["../../src/component/stateless.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAIvC,MAAM,OAAgB,kBAEpB,SAAQ,aAAwC;IAChD,kFAAkF;IAClF,IAAa,KAAK;QAChB,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;CACF"}
1
+ {"version":3,"file":"stateless.js","sourceRoot":"","sources":["../../src/component/stateless.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIhD,MAAM,OAAgB,kBAEpB,SAAQ,aAAwC;IAChD,sDAAsD;IACtD,IAAI,KAAK;QACP,OAAO,iBAAiB,CAAC,IAAI,CAA8B,CAAC;IAC9D,CAAC;CACF"}
@@ -1,6 +1,8 @@
1
1
  export { debounced, until, when, history, type HistoryElement, } from "./reactive";
2
2
  export { signal, persistedSignal, syncedSignal, peek, computed, writableComputed, batch, effect, untrack, type PersistedSignalOptions, type SyncedSignal, } from "./signal";
3
3
  export { RootComponent } from "./component";
4
- export { resource, createResource, type ResourceStatus, type Resource, type ResourceOptions, } from "./async/resource";
5
- export { invalidateResource, _clearCache } from "./async/resource-cache";
4
+ export { componentPropsType, getComponentAnchor, getComponentDefault, getComponentDefaults, getComponentProps, getComponentRawProp, isComponentMounted, isStateDirty, markStateDirty, setComponentAnchor, setComponentDefault, setComponentMounted, setComponentProps, setStateDirty, } from "./component/internals";
5
+ export { resource, createResource, flushPendingResources, type ResourceStatus, type Resource, type ResourceOptions, } from "./async/resource";
6
+ export { invalidateResource, trackPendingResource, _clearCache } from "./async/resource-cache";
7
+ export { setServerRenderPass, isServerRenderPass } from "./async/server-mode";
6
8
  //# sourceMappingURL=internal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,KAAK,EACL,IAAI,EACJ,OAAO,EACP,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,MAAM,EACN,eAAe,EACf,YAAY,EACZ,IAAI,EACJ,QAAQ,EACR,gBAAgB,EAChB,KAAK,EACL,MAAM,EACN,OAAO,EACP,KAAK,sBAAsB,EAC3B,KAAK,YAAY,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EACL,QAAQ,EACR,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,eAAe,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,KAAK,EACL,IAAI,EACJ,OAAO,EACP,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,MAAM,EACN,eAAe,EACf,YAAY,EACZ,IAAI,EACJ,QAAQ,EACR,gBAAgB,EAChB,KAAK,EACL,MAAM,EACN,OAAO,EACP,KAAK,sBAAsB,EAC3B,KAAK,YAAY,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,GACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,QAAQ,EACR,cAAc,EACd,qBAAqB,EACrB,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,eAAe,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC/F,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/internal.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export { debounced, until, when, history, } from "./reactive";
2
2
  export { signal, persistedSignal, syncedSignal, peek, computed, writableComputed, batch, effect, untrack, } from "./signal";
3
3
  export { RootComponent } from "./component";
4
- export { resource, createResource, } from "./async/resource";
5
- export { invalidateResource, _clearCache } from "./async/resource-cache";
4
+ export { componentPropsType, getComponentAnchor, getComponentDefault, getComponentDefaults, getComponentProps, getComponentRawProp, isComponentMounted, isStateDirty, markStateDirty, setComponentAnchor, setComponentDefault, setComponentMounted, setComponentProps, setStateDirty, } from "./component/internals";
5
+ export { resource, createResource, flushPendingResources, } from "./async/resource";
6
+ export { invalidateResource, trackPendingResource, _clearCache } from "./async/resource-cache";
7
+ export { setServerRenderPass, isServerRenderPass } from "./async/server-mode";
6
8
  //# sourceMappingURL=internal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal.js","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,KAAK,EACL,IAAI,EACJ,OAAO,GAER,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,MAAM,EACN,eAAe,EACf,YAAY,EACZ,IAAI,EACJ,QAAQ,EACR,gBAAgB,EAChB,KAAK,EACL,MAAM,EACN,OAAO,GAGR,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EACL,QAAQ,EACR,cAAc,GAIf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"internal.js","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,KAAK,EACL,IAAI,EACJ,OAAO,GAER,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,MAAM,EACN,eAAe,EACf,YAAY,EACZ,IAAI,EACJ,QAAQ,EACR,gBAAgB,EAChB,KAAK,EACL,MAAM,EACN,OAAO,GAGR,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,GACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,QAAQ,EACR,cAAc,EACd,qBAAqB,GAItB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC/F,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisjs/core",
3
- "version": "1.8.3",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -15,10 +15,10 @@
15
15
  }
16
16
  },
17
17
  "devDependencies": {
18
- "typescript": "^5.9.3"
18
+ "typescript": "^6.0.3"
19
19
  },
20
20
  "dependencies": {
21
- "@praxisjs/shared": "0.3.0"
21
+ "@praxisjs/shared": "0.3.1"
22
22
  },
23
23
  "scripts": {
24
24
  "build": "tsc",
@@ -1,6 +1,21 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
 
3
3
  import { RootComponent } from "../component/base";
4
+ import {
5
+ getComponentAnchor,
6
+ getComponentDefault,
7
+ getComponentDefaults,
8
+ getComponentProps,
9
+ getComponentRawProp,
10
+ isComponentMounted,
11
+ isStateDirty,
12
+ markStateDirty,
13
+ setComponentAnchor,
14
+ setComponentDefault,
15
+ setComponentMounted,
16
+ setComponentProps,
17
+ setStateDirty,
18
+ } from "../component/internals";
4
19
  import { StatefulComponent } from "../component/stateful";
5
20
  import { StatelessComponent } from "../component/stateless";
6
21
 
@@ -17,48 +32,90 @@ class ConcreteStateful extends StatefulComponent {
17
32
  }
18
33
 
19
34
  describe("RootComponent", () => {
20
- it("stores props on _rawProps", () => {
35
+ it("stores props in component internals", () => {
21
36
  const c = new ConcreteRoot({ name: "Alice" });
22
- expect(c._rawProps.name).toBe("Alice");
37
+ expect((getComponentProps(c) as { name: string }).name).toBe("Alice");
23
38
  });
24
39
 
25
- it("exposes props via .props getter", () => {
26
- const c = new ConcreteRoot({ name: "Bob" });
27
- expect(c.props.name).toBe("Bob");
40
+ it("does not expose old underscore internals as own properties", () => {
41
+ const c = new ConcreteRoot({ name: "Alice" });
42
+ expect(Object.prototype.hasOwnProperty.call(c, "_rawProps")).toBe(false);
43
+ expect(Object.prototype.hasOwnProperty.call(c, "_mounted")).toBe(false);
44
+ expect(Object.prototype.hasOwnProperty.call(c, "_anchor")).toBe(false);
45
+ expect(Object.prototype.hasOwnProperty.call(c, "_setProps")).toBe(false);
28
46
  });
29
47
 
30
- it("_mounted starts as false", () => {
48
+ it("mounted starts as false", () => {
31
49
  const c = new ConcreteRoot({ name: "" });
32
- expect(c._mounted).toBe(false);
50
+ expect(isComponentMounted(c)).toBe(false);
33
51
  });
34
52
 
35
- it("_anchor starts as undefined", () => {
53
+ it("anchor starts as undefined", () => {
36
54
  const c = new ConcreteRoot({ name: "" });
37
- expect(c._anchor).toBeUndefined();
55
+ expect(getComponentAnchor(c)).toBeUndefined();
38
56
  });
39
57
 
40
58
  it("defaults to empty object when no props given", () => {
41
59
  const c = new ConcreteRoot();
42
- expect(c._rawProps).toEqual({});
60
+ expect(getComponentProps(c)).toEqual({});
43
61
  });
44
62
  });
45
63
 
46
64
  describe("StatefulComponent", () => {
47
- it("_defaults starts as empty object", () => {
65
+ it("defaults start as empty object", () => {
48
66
  const c = new ConcreteStateful();
49
- expect(c._defaults).toEqual({});
67
+ expect(getComponentDefaults(c)).toEqual({});
50
68
  });
51
69
 
52
- it("_stateDirty starts as false", () => {
70
+ it("stateDirty starts as false", () => {
53
71
  const c = new ConcreteStateful();
54
- expect(c._stateDirty).toBe(false);
72
+ expect(isStateDirty(c)).toBe(false);
55
73
  });
56
74
 
57
- it("_setProps replaces _rawProps contents", () => {
75
+ it("setComponentProps replaces raw props contents", () => {
58
76
  const c = new ConcreteStateful({ x: 1 });
59
- c._setProps({ y: 2 });
60
- expect(c._rawProps.x).toBeUndefined();
61
- expect(c._rawProps.y).toBe(2);
77
+ setComponentProps(c, { y: 2 });
78
+ const props = getComponentProps(c) as Record<string, unknown>;
79
+ expect(props.x).toBeUndefined();
80
+ expect(props.y).toBe(2);
81
+ });
82
+
83
+ it("updates anchor, mounted, defaults, and dirty state through helpers", () => {
84
+ const c = new ConcreteStateful();
85
+ const anchor = {} as Comment;
86
+
87
+ setComponentAnchor(c, anchor);
88
+ setComponentMounted(c, true);
89
+ setComponentDefault(c, "label", "Save");
90
+ setStateDirty(c, true);
91
+
92
+ expect(getComponentAnchor(c)).toBe(anchor);
93
+ expect(isComponentMounted(c)).toBe(true);
94
+ expect(getComponentDefaults(c).label).toBe("Save");
95
+ expect(isStateDirty(c)).toBe(true);
96
+ });
97
+
98
+ it("reads raw props and defaults through single-value helpers", () => {
99
+ const c = new ConcreteStateful({ label: "From parent" });
100
+ setComponentDefault(c, "label", "Default");
101
+
102
+ expect(getComponentRawProp(c, "label")).toBe("From parent");
103
+ expect(getComponentDefault(c, "label")).toBe("Default");
104
+ });
105
+
106
+ it("throws when component internals are read from a non-component", () => {
107
+ expect(() => getComponentProps({})).toThrow(TypeError);
108
+ expect(() => getComponentProps({})).toThrow(
109
+ "Expected a PraxisJS component instance.",
110
+ );
111
+ });
112
+
113
+ it("ignores dirty-state writes on non-component hosts", () => {
114
+ const host = {};
115
+
116
+ expect(() => markStateDirty(host)).not.toThrow();
117
+ expect(() => setStateDirty(host, true)).not.toThrow();
118
+ expect(isStateDirty(host)).toBe(false);
62
119
  });
63
120
  });
64
121
 
@@ -69,21 +126,21 @@ class ConcreteStateless extends StatelessComponent<{ name: string }> {
69
126
  }
70
127
 
71
128
  describe("StatelessComponent", () => {
72
- it("stores props on _rawProps", () => {
129
+ it("stores props in component internals", () => {
73
130
  const c = new ConcreteStateless({ name: "Alice" });
74
- expect(c._rawProps.name).toBe("Alice");
131
+ expect((getComponentProps(c) as { name: string }).name).toBe("Alice");
75
132
  });
76
133
 
77
- it("_setProps replaces _rawProps contents", () => {
134
+ it("setComponentProps replaces raw props contents", () => {
78
135
  const c = new ConcreteStateless({ name: "Alice" });
79
- c._setProps({ name: "Bob" });
80
- expect(c._rawProps.name).toBe("Bob");
136
+ setComponentProps(c, { name: "Bob" });
137
+ expect((getComponentProps(c) as { name: string }).name).toBe("Bob");
81
138
  });
82
139
 
83
- it("_setProps removes old keys", () => {
140
+ it("setComponentProps removes old keys", () => {
84
141
  const c = new ConcreteStateless({ name: "Alice" });
85
- c._setProps({});
86
- expect((c._rawProps as Record<string, unknown>).name).toBeUndefined();
142
+ setComponentProps(c, {});
143
+ expect((getComponentProps(c) as Record<string, unknown>).name).toBeUndefined();
87
144
  });
88
145
 
89
146
  it("accepts and exposes children via props.children", () => {
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+
3
+ import { flushPendingResources, resource } from "../async/resource";
4
+ import { _clearCache, getPendingResources } from "../async/resource-cache";
5
+ import { setServerRenderPass, isServerRenderPass } from "../async/server-mode";
6
+
7
+ describe("server render pass — flushPendingResources", () => {
8
+ beforeEach(() => { _clearCache(); });
9
+ afterEach(() => { setServerRenderPass(false); });
10
+
11
+ it("is a no-op when no server pass is active", async () => {
12
+ setServerRenderPass(false);
13
+ const r = resource(() => new Promise<string>(() => {}));
14
+ expect(getPendingResources().size).toBe(0);
15
+ await flushPendingResources();
16
+ // Untouched — resource() never settled and flush didn't wait for it.
17
+ expect(r.status()).toBe("pending");
18
+ });
19
+
20
+ it("resolves immediately when nothing is pending", async () => {
21
+ setServerRenderPass(true);
22
+ await expect(flushPendingResources()).resolves.toBeUndefined();
23
+ });
24
+
25
+ it("tracks isServerRenderPass() state via the setter", () => {
26
+ expect(isServerRenderPass()).toBe(false);
27
+ setServerRenderPass(true);
28
+ expect(isServerRenderPass()).toBe(true);
29
+ });
30
+
31
+ it("awaits an unkeyed resource() (e.g. @Collection) before returning", async () => {
32
+ setServerRenderPass(true);
33
+ const r = resource(() => new Promise<string>((res) => { setTimeout(() => { res("content"); }, 5); }));
34
+ await flushPendingResources();
35
+ expect(r.status()).toBe("success");
36
+ expect(r.data()).toBe("content");
37
+ });
38
+
39
+ it("awaits a keyed resource() too", async () => {
40
+ setServerRenderPass(true);
41
+ const r = resource(() => Promise.resolve("keyed-value"), { key: "ssg-key" });
42
+ await flushPendingResources();
43
+ expect(r.status()).toBe("success");
44
+ expect(r.data()).toBe("keyed-value");
45
+ });
46
+
47
+ it("awaits multiple concurrently pending resources", async () => {
48
+ setServerRenderPass(true);
49
+ const r1 = resource(() => new Promise<number>((res) => { setTimeout(() => { res(1); }, 5); }));
50
+ const r2 = resource(() => new Promise<number>((res) => { setTimeout(() => { res(2); }, 10); }));
51
+ await flushPendingResources();
52
+ expect(r1.data()).toBe(1);
53
+ expect(r2.data()).toBe(2);
54
+ });
55
+
56
+ it("fixed-point loop: awaits a resource created while an earlier one settles", async () => {
57
+ setServerRenderPass(true);
58
+ let second: ReturnType<typeof resource> | undefined;
59
+
60
+ // Simulates a component that, upon its first resource resolving, creates a
61
+ // second resource (e.g. a detail page fetching related content) — the new
62
+ // promise must still be picked up even though it didn't exist yet when
63
+ // flushPendingResources() started its first pass.
64
+ const first = resource(() => new Promise<string>((res) => {
65
+ setTimeout(() => {
66
+ res("first");
67
+ second = resource(() => new Promise<string>((res2) => { setTimeout(() => { res2("second"); }, 5); }));
68
+ }, 5);
69
+ }));
70
+
71
+ await flushPendingResources();
72
+ expect(first.status()).toBe("success");
73
+ expect(second?.status()).toBe("success");
74
+ expect(second?.data()).toBe("second");
75
+ });
76
+
77
+ it("does not leave stale entries after resources settle", async () => {
78
+ setServerRenderPass(true);
79
+ resource(() => Promise.resolve("done"));
80
+ await flushPendingResources();
81
+ expect(getPendingResources().size).toBe(0);
82
+ });
83
+
84
+ it("a rejected resource does not hang the flush", async () => {
85
+ setServerRenderPass(true);
86
+ const r = resource(() => Promise.reject(new Error("boom")));
87
+ await expect(flushPendingResources()).resolves.toBeUndefined();
88
+ expect(r.status()).toBe("error");
89
+ });
90
+ });
@@ -1,3 +1,5 @@
1
+ import { isServerRenderPass } from "./server-mode";
2
+
1
3
  interface CacheEntry {
2
4
  data: unknown;
3
5
  timestamp: number;
@@ -6,6 +8,7 @@ interface CacheEntry {
6
8
  const _cache = new Map<string, CacheEntry>();
7
9
  const _inflight = new Map<string, Promise<unknown>>();
8
10
  const _registry = new Map<string, Set<() => void>>();
11
+ const _pendingResources = new Set<Promise<unknown>>();
9
12
 
10
13
  export function getCacheEntry(key: string): CacheEntry | undefined {
11
14
  return _cache.get(key);
@@ -54,9 +57,26 @@ export function invalidateResource(key: string): void {
54
57
  _registry.get(key)?.forEach((fn) => { fn(); });
55
58
  }
56
59
 
60
+ /**
61
+ * Registers `promise` as pending while a server render pass is active (no-op otherwise,
62
+ * so client-side resource() calls never pay this cost). Self-removes on settle.
63
+ */
64
+ export function trackPendingResource(promise: Promise<unknown>): void {
65
+ if (!isServerRenderPass()) return;
66
+ _pendingResources.add(promise);
67
+ const cleanup = (): void => { _pendingResources.delete(promise); };
68
+ promise.then(cleanup, cleanup);
69
+ }
70
+
71
+ /** Snapshot of currently in-flight resource promises tracked during a server render pass. */
72
+ export function getPendingResources(): ReadonlySet<Promise<unknown>> {
73
+ return _pendingResources;
74
+ }
75
+
57
76
  /** Resets all cache state. Intended for use in tests only. */
58
77
  export function _clearCache(): void {
59
78
  _cache.clear();
60
79
  _inflight.clear();
61
80
  _registry.clear();
81
+ _pendingResources.clear();
62
82
  }
@@ -9,6 +9,8 @@ import {
9
9
  setInflight,
10
10
  registerResource,
11
11
  unregisterResource,
12
+ trackPendingResource,
13
+ getPendingResources,
12
14
  } from "./resource-cache";
13
15
  import { effect, untrack } from "../signal/effect";
14
16
 
@@ -58,6 +60,7 @@ export function resource<T>(
58
60
  let _runId = 0;
59
61
 
60
62
  function _execute(fn: Promise<T>): void {
63
+ trackPendingResource(fn);
61
64
  const currentRunId = ++_runId;
62
65
 
63
66
  function clearInflight(): void {
@@ -194,3 +197,17 @@ export function createResource<P, T>(
194
197
  ): Resource<T> {
195
198
  return resource(() => fetcher(param()), options);
196
199
  }
200
+
201
+ /**
202
+ * Awaits every resource fetch tracked during a server render pass (see
203
+ * `setServerRenderPass`), including ones triggered by resources that themselves
204
+ * settle while waiting (fixed-point loop). No-op — resolves immediately — when
205
+ * nothing is pending or tracking isn't active.
206
+ */
207
+ export async function flushPendingResources(): Promise<void> {
208
+ for (;;) {
209
+ const pending = Array.from(getPendingResources());
210
+ if (pending.length === 0) return;
211
+ await Promise.allSettled(pending);
212
+ }
213
+ }
@@ -0,0 +1,16 @@
1
+ let _serverRenderPass = false;
2
+
3
+ /**
4
+ * Marks whether the current process is doing a build-time server/SSG render pass
5
+ * (as opposed to a normal browser render). Consumed by `resource-cache` (gates
6
+ * pending-promise tracking for `flushPendingResources`) and by `@praxisjs/runtime`'s
7
+ * `Portal`, which skips mounting entirely during a server pass so its content
8
+ * never gets baked into static HTML (it mounts normally once on the client).
9
+ */
10
+ export function setServerRenderPass(active: boolean): void {
11
+ _serverRenderPass = active;
12
+ }
13
+
14
+ export function isServerRenderPass(): boolean {
15
+ return _serverRenderPass;
16
+ }
@@ -1,44 +1,10 @@
1
- export abstract class RootComponent<T extends object = Record<string, never>> {
2
- /**
3
- * Props passed by the parent — filled by the renderer when instantiating/updating.
4
- * @internal
5
- */
6
- readonly _rawProps: T = {} as T;
7
-
8
- /**
9
- * Becomes `true` after `onMount` fires.
10
- * @internal
11
- */
12
- _mounted = false;
1
+ import { componentPropsType, initComponentInternals } from "./internals";
13
2
 
14
- /**
15
- * End comment anchor set by the runtime; used by decorators to locate the parent element.
16
- * @internal
17
- */
18
- _anchor?: Comment;
3
+ export abstract class RootComponent<T extends object = Record<string, never>> {
4
+ declare readonly [componentPropsType]: T;
19
5
 
20
6
  constructor(props: T = {} as T) {
21
- Object.assign(this._rawProps, props);
22
- }
23
-
24
- /**
25
- * Replaces the current props with a new set; called by the renderer on every parent re-render.
26
- * @internal
27
- */
28
- _setProps(props: Record<string, unknown>) {
29
- Object.keys(this._rawProps).forEach((k) => {
30
- Reflect.deleteProperty(this._rawProps, k);
31
- });
32
- Object.assign(this._rawProps, props);
33
- }
34
-
35
- /**
36
- * Raw props, typed. `StatelessComponent` re-exposes this as its public
37
- * props API; on `StatefulComponent` it's internal — use `@Prop()` instead.
38
- * @internal
39
- */
40
- get props(): T {
41
- return this._rawProps;
7
+ initComponentInternals(this, props);
42
8
  }
43
9
 
44
10
  onBeforeMount?(): void;