@genrojs/bag 0.10.1 → 0.10.3

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.
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@genrojs/bag",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "JavaScript port of genro-bag - hierarchical data container",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
+ "types": "./src/index.d.ts",
7
8
  "exports": {
8
- ".": "./src/index.js",
9
+ ".": {
10
+ "types": "./src/index.d.ts",
11
+ "default": "./src/index.js"
12
+ },
9
13
  "./devtools": "./src/devtools/index.js",
10
14
  "./devtools/html-repr.css": "./src/devtools/html-repr.css"
11
15
  },
package/src/bag.js CHANGED
@@ -19,6 +19,22 @@ function* serializationNodes(bag, prefix = '') {
19
19
  }
20
20
  }
21
21
 
22
+ function xmlDateText(value) {
23
+ const iso = value.toISOString();
24
+ const isEpochDate = value.getUTCFullYear() === 1970 &&
25
+ value.getUTCMonth() === 0 && value.getUTCDate() === 1;
26
+ const isMidnight = value.getUTCHours() === 0 &&
27
+ value.getUTCMinutes() === 0 && value.getUTCSeconds() === 0 &&
28
+ value.getUTCMilliseconds() === 0;
29
+
30
+ // Date is also used for date-only and time-only values. Keep their XML
31
+ // lexical forms distinct while using UTC for every representation.
32
+ if (isMidnight && !isEpochDate) return iso.slice(0, iso.indexOf('T'));
33
+ const timestamp = iso.replace(/\.000Z$/, 'Z');
34
+ if (isEpochDate && !isMidnight) return timestamp.slice(timestamp.indexOf('T') + 1);
35
+ return timestamp;
36
+ }
37
+
22
38
  /**
23
39
  * Bag - Hierarchical data container with path-based access.
24
40
  *
@@ -1930,6 +1946,7 @@ export class Bag {
1930
1946
  * Serialize Bag to XML format.
1931
1947
  *
1932
1948
  * All values are converted to strings without type information.
1949
+ * Date values use ISO 8601 text in UTC (date, time, or datetime).
1933
1950
  * For type-preserving serialization, use toTytx() instead.
1934
1951
  *
1935
1952
  * @param {Object} [options={}] - Serialization options.
@@ -2024,7 +2041,10 @@ export class Bag {
2024
2041
  return `<${tag}${attrsStr}></${tag}>`;
2025
2042
  }
2026
2043
 
2027
- const text = this._escapeXml(String(value));
2044
+ const scalar = value instanceof Date && !Number.isNaN(value.getTime())
2045
+ ? xmlDateText(value)
2046
+ : String(value);
2047
+ const text = this._escapeXml(scalar);
2028
2048
  return `<${tag}${attrsStr}>${text}</${tag}>`;
2029
2049
  }
2030
2050
 
package/src/index.d.ts ADDED
@@ -0,0 +1,301 @@
1
+ // Copyright 2025 Softwell S.r.l. - SPDX-License-Identifier: Apache-2.0
2
+
3
+ /** Retry policy applied by a resolver when `load()` fails. */
4
+ export interface RetryPolicy {
5
+ maxAttempts: number;
6
+ /** Initial delay in seconds. */
7
+ delay: number;
8
+ backoff: number;
9
+ jitter: boolean;
10
+ /** Error names that trigger a retry. */
11
+ on: string[];
12
+ }
13
+
14
+ /** Predefined retry policies, referenced by name in `retryPolicy`. */
15
+ export const RETRY_POLICIES: {
16
+ network: RetryPolicy;
17
+ aggressive: RetryPolicy;
18
+ gentle: RetryPolicy;
19
+ };
20
+
21
+ /** Options shared by every resolver. */
22
+ export interface BagResolverOptions {
23
+ /** Cache duration in ms. 0 disables the cache, a negative value never expires. */
24
+ cacheTime?: number;
25
+ /** If true, the resolved value is not stored in the node. */
26
+ readOnly?: boolean;
27
+ /** If true, the result is converted to a Bag. */
28
+ asBag?: boolean | null;
29
+ retryPolicy?: RetryPolicy | keyof typeof RETRY_POLICIES | null;
30
+ [key: string]: any;
31
+ }
32
+
33
+ /** Position of a node in a Bag: '>', '<', '#n', '<label', '>label' or an index. */
34
+ export type NodePosition = string | number | null;
35
+
36
+ /** Callback of `Bag.forEach`: receives the node, the context and the sibling index. */
37
+ export type ForEachCallback = (node: BagNode, kwargs: Record<string, any>, index: number) => any;
38
+
39
+ /** Callbacks accepted by `Bag.subscribe`. */
40
+ export interface BagSubscriptionCallbacks {
41
+ update?: ((...args: any[]) => void) | null;
42
+ insert?: ((...args: any[]) => void) | null;
43
+ delete?: ((...args: any[]) => void) | null;
44
+ any?: ((...args: any[]) => void) | null;
45
+ }
46
+
47
+ /** Options of `Bag.toXml`. */
48
+ export interface ToXmlOptions {
49
+ pretty?: boolean;
50
+ encoding?: string;
51
+ docHeader?: boolean | string | null;
52
+ selfClosedTags?: string[] | null;
53
+ }
54
+
55
+ /** Options of `Bag.fromXml`. */
56
+ export interface FromXmlOptions {
57
+ tagAttribute?: string | null;
58
+ }
59
+
60
+ /** Options of `Bag.fromUrl`. */
61
+ export interface FromUrlOptions {
62
+ /** Timeout in seconds. */
63
+ timeout?: number;
64
+ [key: string]: any;
65
+ }
66
+
67
+ /** Options of `Bag.forEach`. */
68
+ export interface ForEachOptions {
69
+ static?: boolean;
70
+ deep?: boolean;
71
+ kwargs?: Record<string, any>;
72
+ }
73
+
74
+ /** Error raised for Bag-specific failures. */
75
+ export class BagException extends Error {
76
+ constructor(message: string);
77
+ }
78
+
79
+ /** Error raised when a resolver cannot be serialized or executed locally. */
80
+ export class BagSerializationError extends Error {
81
+ constructor(message: string);
82
+ }
83
+
84
+ /** Ordered container of BagNodes, addressed by label, index or dotted path. */
85
+ export class Bag implements Iterable<BagNode> {
86
+ /** Wire suffix of the base class in the TYTX registry. */
87
+ static tytxSuffix: string;
88
+ constructor(source?: Record<string, any> | Bag | null);
89
+
90
+ parent: Bag | null;
91
+ parentNode: BagNode | null;
92
+ readonly backref: boolean;
93
+ readonly nodeClass: typeof BagNode;
94
+ readonly fullpath: string | null;
95
+ readonly length: number;
96
+ readonly root: Bag;
97
+ readonly attributes: Record<string, any>;
98
+ rootAttributes: Record<string, any> | null;
99
+ readonly nodes: BagNode[];
100
+
101
+ createChildBag(): Bag;
102
+ relativePath(node: BagNode): string | null;
103
+
104
+ get(label: string | number, defaultValue?: any, isStatic?: boolean, kwargs?: Record<string, any>): any;
105
+ getItem(path: string, defaultValue?: any, isStatic?: boolean, kwargs?: Record<string, any>): any;
106
+ setItem(path: string, value: any, attr?: Record<string, any> | null, nodePosition?: NodePosition,
107
+ updattr?: boolean, removeNullAttributes?: boolean, reason?: string | null, fired?: boolean,
108
+ doTrigger?: boolean, resolver?: BagResolver | false | null, nodeTag?: string | null): BagNode;
109
+ pop(path: string, defaultValue?: any, reason?: string | null): any;
110
+ delItem(path: string, defaultValue?: any, reason?: string | null): any;
111
+ popNode(path: string, reason?: string | null): BagNode | null;
112
+ clear(): void;
113
+ getNode(path: string | number, isStatic?: boolean, autocreate?: boolean, defaultValue?: any): BagNode | null;
114
+ node(key: string | number): BagNode | null;
115
+ setdefault(path: string, defaultVal?: any): any;
116
+ has(what: string): boolean;
117
+
118
+ setBackref(node?: BagNode | null, parent?: Bag | null): void;
119
+ delParentRef(): void;
120
+ clearBackref(): void;
121
+ subscribe(subscriberId: string, callbacks?: BagSubscriptionCallbacks): void;
122
+ unsubscribe(subscriberId: string, options?: { update?: boolean; insert?: boolean; delete?: boolean; any?: boolean }): void;
123
+
124
+ keys(): string[];
125
+ values(): any[];
126
+ items(): Array<[string, any]>;
127
+ [Symbol.iterator](): Iterator<BagNode>;
128
+
129
+ setAttr(path?: string | null, attr?: Record<string, any> | null, removeNullAttributes?: boolean): void;
130
+ getAttr(path?: string | null, attr?: string | null, defaultVal?: any): any;
131
+ delAttr(path?: string | null, ...attrs: string[]): void;
132
+ getInheritedAttributes(): Record<string, any>;
133
+
134
+ getNodes(condition?: ((node: BagNode) => boolean) | null): BagNode[];
135
+ getNodeByValue(key: string, value: any): BagNode | null;
136
+ getNodeByAttr(attr: string, value: any, caseInsensitive?: boolean, deep_first?: boolean): BagNode | null;
137
+ isEmpty(zeroIsNone?: boolean, blankIsNone?: boolean): boolean;
138
+ query(what?: string | ((node: BagNode) => any) | null, condition?: ((node: BagNode) => boolean) | boolean | null,
139
+ iter?: boolean, deep?: boolean, leaf?: boolean, branch?: boolean, limit?: number | null,
140
+ isStatic?: boolean): any[] | Generator<any>;
141
+ getLeaves(): Array<[string, any]>;
142
+ digest(what?: string | null, condition?: ((node: BagNode) => boolean) | null, asColumns?: boolean): any[];
143
+ columns(cols: string | string[], attrMode?: boolean): any[][];
144
+ sum(what?: string, strict?: boolean, condition?: ((node: BagNode) => boolean) | null): number | null;
145
+ sort(key?: string | ((node: BagNode) => any)): this;
146
+ equalTo(other: any): boolean;
147
+
148
+ getResolver(path: string): BagResolver | null;
149
+ setResolver(path: string, resolver: BagResolver | null): void;
150
+ setCallbackItem(path: string, callback: (kwargs: Record<string, any>) => any, options?: BagResolverOptions): void;
151
+
152
+ move(what: number | number[], position: number, trigger?: boolean): void;
153
+ asDict(ascii?: boolean, lower?: boolean, recursive?: boolean, excludeNullValues?: boolean): Record<string, any>;
154
+ deepcopy(resolve?: boolean): Bag;
155
+ update(source: Bag | Record<string, any>, mode?: string | null, reason?: string | null, ignoreNone?: boolean): void;
156
+ replace(other: Bag): void;
157
+ forEach(callback: ForEachCallback, options?: ForEachOptions): any;
158
+ traverse(): Generator<BagNode>;
159
+
160
+ toTytx(transport?: 'json' | 'msgpack', compact?: boolean): string | Uint8Array;
161
+ static fromTytx(data: string | Uint8Array, transport?: 'json' | 'msgpack'): Bag;
162
+ toXml(options?: ToXmlOptions): string;
163
+ static fromXml(source: string, options?: FromXmlOptions): Bag;
164
+ static fromUrl(url: string, options?: FromUrlOptions): Promise<Bag>;
165
+ toJson(typed?: boolean): string;
166
+ static fromJson(source: string | Record<string, any> | any[], listJoiner?: string | null): Bag;
167
+ toStringTree(isStatic?: boolean): string;
168
+ toString(isStatic?: boolean): string;
169
+ }
170
+
171
+ /** A labelled element of a Bag, holding a value, attributes and an optional resolver. */
172
+ export class BagNode {
173
+ constructor(parentBag: Bag | null, label: string, value?: any, attr?: Record<string, any> | null,
174
+ resolver?: BagResolver | null, nodeTag?: string | null, xmlTag?: string | null,
175
+ removeNullAttributes?: boolean);
176
+ label: string;
177
+ nodeTag: string | null;
178
+ xmlTag: string | null;
179
+ parentBag: Bag | null;
180
+ value: any;
181
+ staticValue: any;
182
+ resolver: BagResolver | null;
183
+ readonly attr: Record<string, any>;
184
+ readonly compiled: any;
185
+ readonly isBranch: boolean;
186
+ readonly position: number;
187
+ readonly fullpath: string | null;
188
+ readonly parentNode: BagNode | null;
189
+
190
+ getValue(isStatic?: boolean, queryString?: string | null, kwargs?: Record<string, any>): any;
191
+ replace(other: BagNode): void;
192
+ resetResolver(): void;
193
+ getAttr(label?: string | null, defaultValue?: any): any;
194
+ setAttr(attr?: Record<string, any> | null, trigger?: boolean, updattr?: boolean, removeNullAttributes?: boolean): void;
195
+ delAttr(...attrsToDelete: string[]): void;
196
+ hasAttr(label: string, value?: any): boolean;
197
+ getInheritedAttributes(): Record<string, any>;
198
+ subscribe(subscriberId: string, callback: (...args: any[]) => void): void;
199
+ unsubscribe(subscriberId: string): void;
200
+ attributeOwnerNode(attrname: string, attrvalue?: any): BagNode | null;
201
+ asTuple(): [string, any, Record<string, any>, BagResolver | null];
202
+ isEqual(other: any): boolean;
203
+ diff(other: BagNode): string | null;
204
+ toJson(typed?: boolean): string;
205
+ toString(): string;
206
+ }
207
+
208
+ /** Ordered storage of the nodes of a Bag, addressable by label, index or position. */
209
+ export class BagNodeContainer implements Iterable<BagNode> {
210
+ constructor();
211
+ readonly length: number;
212
+ index(label: string): number;
213
+ get(key: string | number): BagNode | null;
214
+ pop(key: string | number): BagNode | null;
215
+ has(key: string | number): boolean;
216
+ clear(): void;
217
+ keys(): string[];
218
+ values(): any[];
219
+ items(): Array<[string, any]>;
220
+ move(what: number | number[], position: number, trigger?: boolean): void;
221
+ isEqual(other: any): boolean;
222
+ [Symbol.iterator](): Iterator<BagNode>;
223
+ }
224
+
225
+ /** Base class of lazy and cached value providers attached to a BagNode. */
226
+ export class BagResolver {
227
+ static classKwargs: Record<string, any>;
228
+ static classArgs: string[];
229
+ static internalParams: Set<string>;
230
+ /** Registers the Bag class used for `asBag` conversion. */
231
+ static registerBagClass(BagClass: typeof Bag): void;
232
+
233
+ constructor(kwargs?: BagResolverOptions);
234
+ cacheTime: number;
235
+ readonly readOnly: boolean;
236
+ readonly expired: boolean;
237
+ cachedValue: any;
238
+ readonly node: BagNode | null;
239
+
240
+ init(): void;
241
+ setNode(node: BagNode | null): void;
242
+ onSetResolver(node: BagNode): void;
243
+ reset(): void;
244
+ resolve(options?: { static?: boolean; [key: string]: any }): any;
245
+ load(kwargs?: Record<string, any>): any;
246
+ }
247
+
248
+ /** Resolver that computes its value with a callback. */
249
+ export class BagCbResolver extends BagResolver {
250
+ constructor(kwargs?: BagResolverOptions & { callback?: ((kwargs: Record<string, any>) => any) | null } | ((kwargs: Record<string, any>) => any));
251
+ load(kwargs?: Record<string, any>): any;
252
+ }
253
+
254
+ /** Resolver that fetches a URL. */
255
+ export class UrlResolver extends BagResolver {
256
+ constructor(urlOrKwargs?: string | (BagResolverOptions & {
257
+ url?: string | null;
258
+ method?: string;
259
+ qs?: Record<string, any> | null;
260
+ body?: any;
261
+ /** Timeout in seconds. */
262
+ timeout?: number;
263
+ transport?: string;
264
+ }), kwargs?: BagResolverOptions);
265
+ load(kwargs?: Record<string, any>): Promise<any>;
266
+ }
267
+
268
+ /** Resolver that reads a value from localStorage or sessionStorage. */
269
+ export class StorageResolver extends BagResolver {
270
+ constructor(keyOrKwargs?: string | (BagResolverOptions & {
271
+ key?: string | null;
272
+ storageType?: 'local' | 'session';
273
+ defaultValue?: any;
274
+ dtype?: string | null;
275
+ }), kwargs?: BagResolverOptions);
276
+ load(kwargs?: Record<string, any>): any;
277
+ }
278
+
279
+ /** Resolver that generates a UUID. */
280
+ export class UuidResolver extends BagResolver {
281
+ constructor(kwargs?: BagResolverOptions & { version?: string });
282
+ load(): string;
283
+ }
284
+
285
+ /** A server resolver description that can travel on the wire but cannot execute locally. */
286
+ export class OpaqueResolver extends BagResolver {
287
+ constructor(payload: any);
288
+ readonly payload: any;
289
+ load(): never;
290
+ }
291
+
292
+ /** Wire adapters of a resolver class registered for a Python module/class pair. */
293
+ export interface ResolverRegistration {
294
+ module: string;
295
+ name: string;
296
+ encode: (resolver: any) => { args: any[]; kwargs: Record<string, any> };
297
+ decode: (data: { args: any[]; kwargs: Record<string, any> }) => BagResolver;
298
+ }
299
+
300
+ /** Register a JS counterpart for a Python module/class pair. */
301
+ export function registerResolver(cls: new (...args: any[]) => BagResolver, registration: ResolverRegistration): void;
package/src/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ /* @ts-self-types="./index.d.ts" */
1
2
  // Copyright 2025 Softwell S.r.l. - SPDX-License-Identifier: Apache-2.0
2
3
 
3
4
  export { Bag, BagException } from './bag.js';