gitsheets 0.22.5 → 1.0.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/LICENSE +201 -0
- package/README.md +21 -0
- package/bin/gitsheets +5 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +256 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/errors.d.ts +72 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +74 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/patch.d.ts +2 -0
- package/dist/patch.d.ts.map +1 -0
- package/dist/patch.js +39 -0
- package/dist/patch.js.map +1 -0
- package/dist/path-template/index.d.ts +42 -0
- package/dist/path-template/index.d.ts.map +1 -0
- package/dist/path-template/index.js +288 -0
- package/dist/path-template/index.js.map +1 -0
- package/dist/push-daemon.d.ts +53 -0
- package/dist/push-daemon.d.ts.map +1 -0
- package/dist/push-daemon.js +148 -0
- package/dist/push-daemon.js.map +1 -0
- package/dist/repository.d.ts +67 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +322 -0
- package/dist/repository.js.map +1 -0
- package/dist/sheet.d.ts +107 -0
- package/dist/sheet.d.ts.map +1 -0
- package/dist/sheet.js +605 -0
- package/dist/sheet.js.map +1 -0
- package/dist/store.d.ts +41 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +49 -0
- package/dist/store.js.map +1 -0
- package/dist/toml.d.ts +11 -0
- package/dist/toml.d.ts.map +1 -0
- package/dist/toml.js +28 -0
- package/dist/toml.js.map +1 -0
- package/dist/transaction.d.ts +96 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +227 -0
- package/dist/transaction.js.map +1 -0
- package/dist/validation.d.ts +37 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +105 -0
- package/dist/validation.js.map +1 -0
- package/package.json +41 -35
- package/bin/cli.js +0 -61
- package/commands/edit.js +0 -90
- package/commands/normalize.js +0 -81
- package/commands/query.js +0 -206
- package/commands/read.js +0 -64
- package/commands/singer-target.js +0 -214
- package/commands/upsert.js +0 -260
- package/lib/GitSheets.js +0 -464
- package/lib/Repository.js +0 -88
- package/lib/Sheet.js +0 -625
- package/lib/errors.js +0 -21
- package/lib/hologit.js +0 -1
- package/lib/logger.js +0 -18
- package/lib/path/BaseComponent.js +0 -24
- package/lib/path/ExpressionComponent.js +0 -26
- package/lib/path/FieldComponent.js +0 -13
- package/lib/path/LiteralComponent.js +0 -12
- package/lib/path/Query.js +0 -18
- package/lib/path/Template.js +0 -214
- package/server.js +0 -120
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from './errors.js';
|
|
2
|
+
export { Repository, openRepo } from './repository.js';
|
|
3
|
+
export type { OpenRepoOptions, OpenSheetOptions, OpenSheetsOptions, } from './repository.js';
|
|
4
|
+
export { Sheet, RECORD_PATH_KEY, RECORD_SHEET_KEY } from './sheet.js';
|
|
5
|
+
export type { SheetConfig, SheetFieldConfig, SortRule, UpsertResult, SheetConstructorOptions, IndexKeyFn, DefineIndexOptions, QueryFilter, } from './sheet.js';
|
|
6
|
+
export { mergePatch } from './patch.js';
|
|
7
|
+
export { openStore } from './store.js';
|
|
8
|
+
export type { OpenStoreOptions, Store, StoreTx, StoreTransactFn, ValidatorMap, InferRecord, } from './store.js';
|
|
9
|
+
export { PushDaemon } from './push-daemon.js';
|
|
10
|
+
export type { PushDaemonOptions, PushDaemonStatus, BackoffConfig, } from './push-daemon.js';
|
|
11
|
+
export { Transaction } from './transaction.js';
|
|
12
|
+
export type { Author, TransactionOptions, TransactionResult, TransactionHandler, } from './transaction.js';
|
|
13
|
+
export { Template } from './path-template/index.js';
|
|
14
|
+
export type { RecordLike, PathTemplateBlob, PathTemplateTree, PathTemplateQueryResult, } from './path-template/index.js';
|
|
15
|
+
export { validateRecord } from './validation.js';
|
|
16
|
+
export type { JSONSchema, StandardSchemaV1, StandardSchemaIssue, StandardSchemaResult, StandardSchemaFailure, StandardSchemaSuccess, StandardSchemaPathSegment, } from './validation.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACtE,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,QAAQ,EACR,YAAY,EACZ,uBAAuB,EACvB,UAAU,EACV,kBAAkB,EAClB,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,YAAY,EACV,gBAAgB,EAChB,KAAK,EACL,OAAO,EACP,eAAe,EACf,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EACV,MAAM,EACN,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,YAAY,EACV,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,YAAY,EACV,UAAU,EACV,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Public exports for gitsheets.
|
|
2
|
+
// See specs/api/ for the full contract.
|
|
3
|
+
export * from './errors.js';
|
|
4
|
+
export { Repository, openRepo } from './repository.js';
|
|
5
|
+
export { Sheet, RECORD_PATH_KEY, RECORD_SHEET_KEY } from './sheet.js';
|
|
6
|
+
export { mergePatch } from './patch.js';
|
|
7
|
+
export { openStore } from './store.js';
|
|
8
|
+
export { PushDaemon } from './push-daemon.js';
|
|
9
|
+
export { Transaction } from './transaction.js';
|
|
10
|
+
export { Template } from './path-template/index.js';
|
|
11
|
+
export { validateRecord } from './validation.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,wCAAwC;AAExC,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAOvD,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAYtE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAUvC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAO9C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAQ/C,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAQpD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/patch.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../src/patch.ts"],"names":[],"mappings":"AAsBA,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAenE"}
|
package/dist/patch.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// RFC 7396 JSON Merge Patch.
|
|
2
|
+
//
|
|
3
|
+
// Semantics (per RFC):
|
|
4
|
+
// - null in the patch deletes the corresponding member of the target
|
|
5
|
+
// - arrays in the patch replace the target's array entirely (no concat)
|
|
6
|
+
// - objects merge recursively
|
|
7
|
+
// - scalars in the patch replace the target's value
|
|
8
|
+
//
|
|
9
|
+
// Inline implementation (rather than a dependency) so @iarna/toml's custom
|
|
10
|
+
// Date subclasses round-trip cleanly through the merge without classification.
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
if (value === null || typeof value !== 'object')
|
|
13
|
+
return false;
|
|
14
|
+
if (Array.isArray(value))
|
|
15
|
+
return false;
|
|
16
|
+
if (value instanceof Date)
|
|
17
|
+
return false;
|
|
18
|
+
// Treat anything with a non-Object prototype as a class instance (BlobObject, etc.)
|
|
19
|
+
const proto = Object.getPrototypeOf(value);
|
|
20
|
+
return proto === Object.prototype || proto === null;
|
|
21
|
+
}
|
|
22
|
+
export function mergePatch(target, patch) {
|
|
23
|
+
if (!isPlainObject(patch)) {
|
|
24
|
+
// Scalars, arrays, null, Dates, class instances — replace wholesale.
|
|
25
|
+
return patch;
|
|
26
|
+
}
|
|
27
|
+
// Patch is an object — merge into a fresh plain object derived from target.
|
|
28
|
+
const base = isPlainObject(target) ? { ...target } : {};
|
|
29
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
30
|
+
if (value === null) {
|
|
31
|
+
delete base[key];
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
base[key] = mergePatch(base[key], value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return base;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=patch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patch.js","sourceRoot":"","sources":["../src/patch.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,EAAE;AACF,uBAAuB;AACvB,uEAAuE;AACvE,0EAA0E;AAC1E,gCAAgC;AAChC,sDAAsD;AACtD,EAAE;AACF,2EAA2E;AAC3E,+EAA+E;AAI/E,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,KAAK,CAAC;IACxC,oFAAoF;IACpF,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC3C,OAAO,KAAK,KAAK,MAAM,CAAC,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAe,EAAE,KAAc;IACxD,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,qEAAqE;QACrE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,4EAA4E;IAC5E,MAAM,IAAI,GAAa,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAClE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type RecordLike = Record<string, unknown>;
|
|
2
|
+
export interface PathTemplateBlob {
|
|
3
|
+
readonly isBlob: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface PathTemplateTree {
|
|
6
|
+
readonly isTree: boolean;
|
|
7
|
+
getChild(name: string): Promise<PathTemplateTree | PathTemplateBlob | undefined>;
|
|
8
|
+
getChildren(): Promise<Record<string, PathTemplateTree | PathTemplateBlob>>;
|
|
9
|
+
getBlobMap(): Promise<Record<string, PathTemplateBlob>>;
|
|
10
|
+
}
|
|
11
|
+
export interface PathTemplateQueryResult {
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly blob: PathTemplateBlob;
|
|
14
|
+
}
|
|
15
|
+
export declare class Template {
|
|
16
|
+
#private;
|
|
17
|
+
static fromString(templateString: string): Template;
|
|
18
|
+
/** Clear the global parse cache. Test-only escape hatch. */
|
|
19
|
+
static clearCache(): void;
|
|
20
|
+
readonly source: string;
|
|
21
|
+
private constructor();
|
|
22
|
+
get componentCount(): number;
|
|
23
|
+
/**
|
|
24
|
+
* Render the template against a full record.
|
|
25
|
+
*
|
|
26
|
+
* @throws PathTemplateError(`path_render_failed`) when any component is unrenderable.
|
|
27
|
+
* @throws PathTemplateError(`path_invalid_chars`) when a rendered segment contains
|
|
28
|
+
* disallowed characters.
|
|
29
|
+
*/
|
|
30
|
+
render(record: RecordLike): string;
|
|
31
|
+
/**
|
|
32
|
+
* Walk a tree yielding records that may match the query.
|
|
33
|
+
*
|
|
34
|
+
* Pruning: when the query supplies the inputs to render a component, the
|
|
35
|
+
* walk descends into only that subtree. When a component is unrenderable
|
|
36
|
+
* against the partial query, the walk expands across all subtrees at that
|
|
37
|
+
* level. The caller still applies the full equality filter on the yielded
|
|
38
|
+
* record contents.
|
|
39
|
+
*/
|
|
40
|
+
queryTree(tree: PathTemplateTree | undefined, query: RecordLike, pathPrefix?: string, depth?: number): AsyncGenerator<PathTemplateQueryResult>;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/path-template/index.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,gBAAgB,GAAG,SAAS,CAAC,CAAC;IACjF,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAAC,CAAC,CAAC;IAC5E,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;CACjC;AAwPD,qBAAa,QAAQ;;IACnB,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,QAAQ;IAQnD,4DAA4D;IAC5D,MAAM,CAAC,UAAU,IAAI,IAAI;IAIzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAGxB,OAAO;IAKP,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM;IAiBlC;;;;;;;;OAQG;IACI,SAAS,CACd,IAAI,EAAE,gBAAgB,GAAG,SAAS,EAClC,KAAK,EAAE,UAAU,EACjB,UAAU,SAAK,EACf,KAAK,SAAI,GACR,cAAc,CAAC,uBAAuB,CAAC;CAmE3C"}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// Path template parser + renderer + query-tree traversal.
|
|
2
|
+
// See specs/behaviors/path-templates.md for the contract.
|
|
3
|
+
import { runInNewContext } from 'node:vm';
|
|
4
|
+
import { PathTemplateError } from '../errors.js';
|
|
5
|
+
// --- Validation ---
|
|
6
|
+
// Windows-disallowed chars per specs/behaviors/path-templates.md.
|
|
7
|
+
const WINDOWS_INVALID = /[<>:"|?*\x00-\x1f]/;
|
|
8
|
+
// Same set plus `/` — segments rendered for non-recursive components must not
|
|
9
|
+
// contain `/`, since that would silently expand the rendered path's structure.
|
|
10
|
+
const SEGMENT_INVALID = /[<>:"|?*\x00-\x1f/]/;
|
|
11
|
+
function rejectInvalidChars(rendered, pattern, source, componentIndex) {
|
|
12
|
+
const match = pattern.exec(rendered);
|
|
13
|
+
if (match) {
|
|
14
|
+
throw new PathTemplateError('path_invalid_chars', `component ${componentIndex} of "${source}" rendered to ${JSON.stringify(rendered)}, ` +
|
|
15
|
+
`which contains disallowed character ${JSON.stringify(match[0])}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// --- Expression compilation ---
|
|
19
|
+
const NOT_DEFINED_RE = / is not defined$/;
|
|
20
|
+
function compileExpression(source) {
|
|
21
|
+
let compiled;
|
|
22
|
+
try {
|
|
23
|
+
compiled = runInNewContext(`(record) => { with (record) { return (${source}) } }`);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
throw new PathTemplateError('path_render_failed', `expression ${JSON.stringify(source)} failed to compile: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
27
|
+
}
|
|
28
|
+
return (record) => {
|
|
29
|
+
try {
|
|
30
|
+
return compiled(record);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
// Per spec: missing identifiers are "un-renderable", not fatal —
|
|
34
|
+
// that lets queries with partial fields walk the tree fully.
|
|
35
|
+
if (err instanceof ReferenceError && NOT_DEFINED_RE.test(err.message)) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// --- Value rendering ---
|
|
43
|
+
function stringifyValue(value) {
|
|
44
|
+
if (value === undefined || value === null)
|
|
45
|
+
return undefined;
|
|
46
|
+
if (typeof value === 'function')
|
|
47
|
+
return undefined;
|
|
48
|
+
if (typeof value === 'string')
|
|
49
|
+
return value;
|
|
50
|
+
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
|
|
51
|
+
return String(value);
|
|
52
|
+
}
|
|
53
|
+
if (value instanceof Date)
|
|
54
|
+
return value.toString();
|
|
55
|
+
// Plain objects / arrays / Symbols: treat as un-renderable. Consumers
|
|
56
|
+
// wanting a custom serialization use an expression component.
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
function renderPart(part, record) {
|
|
60
|
+
switch (part.kind) {
|
|
61
|
+
case 'literal':
|
|
62
|
+
return part.text;
|
|
63
|
+
case 'field':
|
|
64
|
+
return stringifyValue(record[part.name]);
|
|
65
|
+
case 'expression':
|
|
66
|
+
return stringifyValue(part.evaluate(record));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function renderComponent(c, record) {
|
|
70
|
+
let out = '';
|
|
71
|
+
for (const part of c.parts) {
|
|
72
|
+
const piece = renderPart(part, record);
|
|
73
|
+
if (piece === undefined)
|
|
74
|
+
return undefined;
|
|
75
|
+
out += piece;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
// --- Parser ---
|
|
80
|
+
const FIELD_NAME_RE = /^[a-zA-Z0-9_-]+(\/\*\*)?$/;
|
|
81
|
+
function parseTemplateString(source) {
|
|
82
|
+
const normalized = source.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
83
|
+
if (normalized === '') {
|
|
84
|
+
throw new PathTemplateError('path_render_failed', 'path template is empty');
|
|
85
|
+
}
|
|
86
|
+
// Single-pass: structural `/` separates components, but `/` inside an
|
|
87
|
+
// expression (notably the `/**` recursive suffix) belongs to the expression
|
|
88
|
+
// and must not split segments.
|
|
89
|
+
const segments = [[]];
|
|
90
|
+
let pendingLiteral = '';
|
|
91
|
+
let i = 0;
|
|
92
|
+
const flushLiteral = () => {
|
|
93
|
+
if (pendingLiteral) {
|
|
94
|
+
segments[segments.length - 1].push({ kind: 'literal', text: pendingLiteral });
|
|
95
|
+
pendingLiteral = '';
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
while (i < normalized.length) {
|
|
99
|
+
if (normalized.startsWith('${{', i)) {
|
|
100
|
+
flushLiteral();
|
|
101
|
+
i += 3;
|
|
102
|
+
let exprSource = '';
|
|
103
|
+
while (!normalized.startsWith('}}', i)) {
|
|
104
|
+
if (i >= normalized.length) {
|
|
105
|
+
throw new PathTemplateError('path_render_failed', `expression "\${{${exprSource}" in template "${source}" was not closed with }}`);
|
|
106
|
+
}
|
|
107
|
+
exprSource += normalized[i];
|
|
108
|
+
i++;
|
|
109
|
+
}
|
|
110
|
+
exprSource = exprSource.trim();
|
|
111
|
+
i += 2;
|
|
112
|
+
if (FIELD_NAME_RE.test(exprSource)) {
|
|
113
|
+
const recursive = exprSource.endsWith('/**');
|
|
114
|
+
const name = recursive ? exprSource.slice(0, -3) : exprSource;
|
|
115
|
+
segments[segments.length - 1].push({ kind: 'field', name, recursive });
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
segments[segments.length - 1].push({
|
|
119
|
+
kind: 'expression',
|
|
120
|
+
source: exprSource,
|
|
121
|
+
evaluate: compileExpression(exprSource),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (normalized[i] === '/') {
|
|
126
|
+
flushLiteral();
|
|
127
|
+
segments.push([]);
|
|
128
|
+
i++;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
pendingLiteral += normalized[i];
|
|
132
|
+
i++;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
flushLiteral();
|
|
136
|
+
const components = segments.map((parts, idx) => buildComponent(parts, idx, source));
|
|
137
|
+
// Recursive (`/**`) must be the last component if present.
|
|
138
|
+
for (let i = 0; i < components.length - 1; i++) {
|
|
139
|
+
if (components[i].recursive) {
|
|
140
|
+
throw new PathTemplateError('path_render_failed', `recursive component (\${{ ... /** }}) must be the final component of the template`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return components;
|
|
144
|
+
}
|
|
145
|
+
function buildComponent(parts, index, source) {
|
|
146
|
+
if (parts.length === 0) {
|
|
147
|
+
throw new PathTemplateError('path_render_failed', `empty component at index ${index} of "${source}" — consecutive slashes are not allowed`);
|
|
148
|
+
}
|
|
149
|
+
const onlyPart = parts.length === 1 ? parts[0] : null;
|
|
150
|
+
const componentRecursive = onlyPart !== null && onlyPart.kind === 'field' && onlyPart.recursive;
|
|
151
|
+
if (!componentRecursive) {
|
|
152
|
+
for (const part of parts) {
|
|
153
|
+
if (part.kind === 'field' && part.recursive) {
|
|
154
|
+
throw new PathTemplateError('path_render_failed', `recursive field reference (\${{ ${part.name}/** }}) must be the only part of its component`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return { parts, recursive: componentRecursive };
|
|
159
|
+
}
|
|
160
|
+
// --- Tree helpers ---
|
|
161
|
+
function isBlob(x) {
|
|
162
|
+
return x.isBlob === true;
|
|
163
|
+
}
|
|
164
|
+
function isTree(x) {
|
|
165
|
+
return x.isTree === true;
|
|
166
|
+
}
|
|
167
|
+
function joinPath(prefix, name) {
|
|
168
|
+
return prefix ? `${prefix}/${name}` : name;
|
|
169
|
+
}
|
|
170
|
+
// --- Template class ---
|
|
171
|
+
const TEMPLATE_CACHE = new Map();
|
|
172
|
+
export class Template {
|
|
173
|
+
static fromString(templateString) {
|
|
174
|
+
const cached = TEMPLATE_CACHE.get(templateString);
|
|
175
|
+
if (cached)
|
|
176
|
+
return cached;
|
|
177
|
+
const instance = new Template(templateString, parseTemplateString(templateString));
|
|
178
|
+
TEMPLATE_CACHE.set(templateString, instance);
|
|
179
|
+
return instance;
|
|
180
|
+
}
|
|
181
|
+
/** Clear the global parse cache. Test-only escape hatch. */
|
|
182
|
+
static clearCache() {
|
|
183
|
+
TEMPLATE_CACHE.clear();
|
|
184
|
+
}
|
|
185
|
+
source;
|
|
186
|
+
#components;
|
|
187
|
+
constructor(source, components) {
|
|
188
|
+
this.source = source;
|
|
189
|
+
this.#components = components;
|
|
190
|
+
}
|
|
191
|
+
get componentCount() {
|
|
192
|
+
return this.#components.length;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Render the template against a full record.
|
|
196
|
+
*
|
|
197
|
+
* @throws PathTemplateError(`path_render_failed`) when any component is unrenderable.
|
|
198
|
+
* @throws PathTemplateError(`path_invalid_chars`) when a rendered segment contains
|
|
199
|
+
* disallowed characters.
|
|
200
|
+
*/
|
|
201
|
+
render(record) {
|
|
202
|
+
const segments = [];
|
|
203
|
+
for (let i = 0; i < this.#components.length; i++) {
|
|
204
|
+
const c = this.#components[i];
|
|
205
|
+
const rendered = renderComponent(c, record);
|
|
206
|
+
if (rendered === undefined) {
|
|
207
|
+
throw new PathTemplateError('path_render_failed', `cannot render component ${i} of "${this.source}" — a required field or expression returned undefined`);
|
|
208
|
+
}
|
|
209
|
+
rejectInvalidChars(rendered, c.recursive ? WINDOWS_INVALID : SEGMENT_INVALID, this.source, i);
|
|
210
|
+
segments.push(rendered);
|
|
211
|
+
}
|
|
212
|
+
return segments.join('/');
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Walk a tree yielding records that may match the query.
|
|
216
|
+
*
|
|
217
|
+
* Pruning: when the query supplies the inputs to render a component, the
|
|
218
|
+
* walk descends into only that subtree. When a component is unrenderable
|
|
219
|
+
* against the partial query, the walk expands across all subtrees at that
|
|
220
|
+
* level. The caller still applies the full equality filter on the yielded
|
|
221
|
+
* record contents.
|
|
222
|
+
*/
|
|
223
|
+
async *queryTree(tree, query, pathPrefix = '', depth = 0) {
|
|
224
|
+
if (!tree)
|
|
225
|
+
return;
|
|
226
|
+
const numComponents = this.#components.length;
|
|
227
|
+
let currentTree = tree;
|
|
228
|
+
let currentPrefix = pathPrefix;
|
|
229
|
+
for (let i = depth; i < numComponents; i++) {
|
|
230
|
+
const isLast = i + 1 === numComponents;
|
|
231
|
+
const c = this.#components[i];
|
|
232
|
+
const rendered = renderComponent(c, query);
|
|
233
|
+
if (isLast) {
|
|
234
|
+
if (rendered !== undefined) {
|
|
235
|
+
const child = await currentTree.getChild(`${rendered}.toml`);
|
|
236
|
+
if (child && isBlob(child)) {
|
|
237
|
+
yield { path: joinPath(currentPrefix, rendered), blob: child };
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const children = c.recursive
|
|
242
|
+
? await currentTree.getBlobMap()
|
|
243
|
+
: await currentTree.getChildren();
|
|
244
|
+
let attachmentPrefix;
|
|
245
|
+
// `for...in` walks own + inherited enumerable keys. hologit's
|
|
246
|
+
// getChildren() exposes loaded entries on the object's prototype
|
|
247
|
+
// and only stages local mutations as own properties — Object.keys
|
|
248
|
+
// would miss the loaded entries.
|
|
249
|
+
const allKeys = [];
|
|
250
|
+
for (const k in children)
|
|
251
|
+
allKeys.push(k);
|
|
252
|
+
const sortedKeys = allKeys.sort();
|
|
253
|
+
for (const childPath of sortedKeys) {
|
|
254
|
+
if (!childPath.endsWith('.toml'))
|
|
255
|
+
continue;
|
|
256
|
+
if (attachmentPrefix && childPath.startsWith(attachmentPrefix))
|
|
257
|
+
continue;
|
|
258
|
+
const child = children[childPath];
|
|
259
|
+
if (!child || !isBlob(child))
|
|
260
|
+
continue;
|
|
261
|
+
const childName = childPath.slice(0, -5);
|
|
262
|
+
attachmentPrefix = `${childName}/`;
|
|
263
|
+
yield { path: joinPath(currentPrefix, childName), blob: child };
|
|
264
|
+
}
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (rendered !== undefined) {
|
|
268
|
+
const next = await currentTree.getChild(rendered);
|
|
269
|
+
if (!next || !isTree(next))
|
|
270
|
+
return;
|
|
271
|
+
currentTree = next;
|
|
272
|
+
currentPrefix = joinPath(currentPrefix, rendered);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
// Unrenderable intermediate component: expand across all subtree children.
|
|
276
|
+
const children = await currentTree.getChildren();
|
|
277
|
+
// for...in to include hologit's prototype-loaded entries (see comment above).
|
|
278
|
+
for (const name in children) {
|
|
279
|
+
const child = children[name];
|
|
280
|
+
if (!child || !isTree(child))
|
|
281
|
+
continue;
|
|
282
|
+
yield* this.queryTree(child, query, joinPath(currentPrefix, name), i + 1);
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/path-template/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
export interface BackoffConfig {
|
|
3
|
+
/** Initial delay in ms; default 1000. */
|
|
4
|
+
readonly base: number;
|
|
5
|
+
/** Multiplier each attempt; default 2. */
|
|
6
|
+
readonly multiplier: number;
|
|
7
|
+
/** Max delay in ms; default 3_600_000 (1 hour). */
|
|
8
|
+
readonly cap: number;
|
|
9
|
+
}
|
|
10
|
+
export interface PushDaemonOptions {
|
|
11
|
+
/** Git remote name (e.g., 'origin'). Required. */
|
|
12
|
+
readonly remote: string;
|
|
13
|
+
/** Branch to push; default the repo's current HEAD branch. */
|
|
14
|
+
readonly branch?: string;
|
|
15
|
+
/** Retry backoff configuration; default exponential. */
|
|
16
|
+
readonly backoff?: 'exponential' | BackoffConfig;
|
|
17
|
+
/** Max retry attempts per commit batch; default Infinity. */
|
|
18
|
+
readonly maxRetries?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface PushDaemonStatus {
|
|
21
|
+
readonly running: boolean;
|
|
22
|
+
readonly lastPushAt: string | null;
|
|
23
|
+
readonly lastError: {
|
|
24
|
+
message: string;
|
|
25
|
+
at: string;
|
|
26
|
+
attempt: number;
|
|
27
|
+
} | null;
|
|
28
|
+
readonly pendingCommits: number;
|
|
29
|
+
readonly currentBackoffMs: number | null;
|
|
30
|
+
readonly currentAttempt: number | null;
|
|
31
|
+
}
|
|
32
|
+
export declare class PushDaemon extends EventEmitter {
|
|
33
|
+
#private;
|
|
34
|
+
constructor(opts: {
|
|
35
|
+
gitDir: string;
|
|
36
|
+
remote: string;
|
|
37
|
+
branch: string;
|
|
38
|
+
backoff: BackoffConfig;
|
|
39
|
+
maxRetries: number;
|
|
40
|
+
});
|
|
41
|
+
/** Repository invokes this after each successful transact commit. */
|
|
42
|
+
notifyCommit(commitHash: string): void;
|
|
43
|
+
status(): PushDaemonStatus;
|
|
44
|
+
/**
|
|
45
|
+
* Stop accepting new commits, drain in-flight retries up to `timeoutMs`,
|
|
46
|
+
* resolve once idle. Idempotent.
|
|
47
|
+
*/
|
|
48
|
+
stop(opts?: {
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
}): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
export declare function resolveBackoff(opt: PushDaemonOptions['backoff']): BackoffConfig;
|
|
53
|
+
//# sourceMappingURL=push-daemon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push-daemon.d.ts","sourceRoot":"","sources":["../src/push-daemon.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,wDAAwD;IACxD,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,aAAa,CAAC;IACjD,6DAA6D;IAC7D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED,qBAAa,UAAW,SAAQ,YAAY;;gBAmB9B,IAAI,EAAE;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,aAAa,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC;KACpB;IASD,qEAAqE;IACrE,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAOtC,MAAM,IAAI,gBAAgB;IAW1B;;;OAGG;IACG,IAAI,CAAC,IAAI,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAuF7D;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,iBAAiB,CAAC,SAAS,CAAC,GAAG,aAAa,CAK/E"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Push daemon — async push-to-remote with retry/backoff.
|
|
2
|
+
// See specs/behaviors/push-sync.md and specs/api/repository.md.
|
|
3
|
+
import { execFile } from 'node:child_process';
|
|
4
|
+
import { EventEmitter } from 'node:events';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
const exec = promisify(execFile);
|
|
7
|
+
export class PushDaemon extends EventEmitter {
|
|
8
|
+
#gitDir;
|
|
9
|
+
#remote;
|
|
10
|
+
#branch;
|
|
11
|
+
#backoff;
|
|
12
|
+
#maxRetries;
|
|
13
|
+
#running = true;
|
|
14
|
+
#stopResolve = null;
|
|
15
|
+
#pendingCounter = 0;
|
|
16
|
+
#lastPushedCounter = 0;
|
|
17
|
+
#lastCommit = null;
|
|
18
|
+
#lastPushAt = null;
|
|
19
|
+
#lastError = null;
|
|
20
|
+
#currentAttempt = null;
|
|
21
|
+
#currentBackoffMs = null;
|
|
22
|
+
#inFlight = false;
|
|
23
|
+
#pendingTimer = null;
|
|
24
|
+
constructor(opts) {
|
|
25
|
+
super();
|
|
26
|
+
this.#gitDir = opts.gitDir;
|
|
27
|
+
this.#remote = opts.remote;
|
|
28
|
+
this.#branch = opts.branch;
|
|
29
|
+
this.#backoff = opts.backoff;
|
|
30
|
+
this.#maxRetries = opts.maxRetries;
|
|
31
|
+
}
|
|
32
|
+
/** Repository invokes this after each successful transact commit. */
|
|
33
|
+
notifyCommit(commitHash) {
|
|
34
|
+
if (!this.#running)
|
|
35
|
+
return;
|
|
36
|
+
this.#pendingCounter++;
|
|
37
|
+
this.#lastCommit = commitHash;
|
|
38
|
+
void this.#drain();
|
|
39
|
+
}
|
|
40
|
+
status() {
|
|
41
|
+
return {
|
|
42
|
+
running: this.#running,
|
|
43
|
+
lastPushAt: this.#lastPushAt,
|
|
44
|
+
lastError: this.#lastError,
|
|
45
|
+
pendingCommits: Math.max(0, this.#pendingCounter - this.#lastPushedCounter),
|
|
46
|
+
currentBackoffMs: this.#currentBackoffMs,
|
|
47
|
+
currentAttempt: this.#currentAttempt,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Stop accepting new commits, drain in-flight retries up to `timeoutMs`,
|
|
52
|
+
* resolve once idle. Idempotent.
|
|
53
|
+
*/
|
|
54
|
+
async stop(opts = {}) {
|
|
55
|
+
const timeoutMs = opts.timeoutMs ?? 30_000;
|
|
56
|
+
if (!this.#running)
|
|
57
|
+
return;
|
|
58
|
+
this.#running = false;
|
|
59
|
+
if (this.#pendingTimer) {
|
|
60
|
+
clearTimeout(this.#pendingTimer);
|
|
61
|
+
this.#pendingTimer = null;
|
|
62
|
+
}
|
|
63
|
+
if (this.#inFlight) {
|
|
64
|
+
await new Promise((resolve) => {
|
|
65
|
+
this.#stopResolve = resolve;
|
|
66
|
+
const t = setTimeout(() => {
|
|
67
|
+
this.#stopResolve = null;
|
|
68
|
+
resolve();
|
|
69
|
+
}, timeoutMs);
|
|
70
|
+
// Ensure we don't keep the process alive
|
|
71
|
+
t.unref?.();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
this.emit('stopped');
|
|
75
|
+
}
|
|
76
|
+
async #drain() {
|
|
77
|
+
if (this.#inFlight || !this.#running)
|
|
78
|
+
return;
|
|
79
|
+
if (this.#pendingCounter <= this.#lastPushedCounter)
|
|
80
|
+
return;
|
|
81
|
+
this.#inFlight = true;
|
|
82
|
+
try {
|
|
83
|
+
await this.#pushWithBackoff();
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
this.#inFlight = false;
|
|
87
|
+
this.#currentAttempt = null;
|
|
88
|
+
this.#currentBackoffMs = null;
|
|
89
|
+
const stopResolve = this.#stopResolve;
|
|
90
|
+
if (stopResolve) {
|
|
91
|
+
this.#stopResolve = null;
|
|
92
|
+
stopResolve();
|
|
93
|
+
}
|
|
94
|
+
else if (this.#running && this.#pendingCounter > this.#lastPushedCounter) {
|
|
95
|
+
// More commits arrived during the push; chain another drain.
|
|
96
|
+
void this.#drain();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async #pushWithBackoff() {
|
|
101
|
+
let attempt = 0;
|
|
102
|
+
let delay = this.#backoff.base;
|
|
103
|
+
const counterAtStart = this.#pendingCounter;
|
|
104
|
+
const commit = this.#lastCommit;
|
|
105
|
+
while (this.#running) {
|
|
106
|
+
attempt++;
|
|
107
|
+
this.#currentAttempt = attempt;
|
|
108
|
+
this.#currentBackoffMs = null;
|
|
109
|
+
const started = Date.now();
|
|
110
|
+
try {
|
|
111
|
+
await exec('git', ['push', this.#remote, this.#branch], { cwd: this.#gitDir });
|
|
112
|
+
this.#lastPushedCounter = counterAtStart;
|
|
113
|
+
this.#lastPushAt = new Date().toISOString();
|
|
114
|
+
this.emit('push', { commit, durationMs: Date.now() - started });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
119
|
+
this.#lastError = { message, at: new Date().toISOString(), attempt };
|
|
120
|
+
this.emit('error', { commit, err, attempt });
|
|
121
|
+
if (attempt >= this.#maxRetries) {
|
|
122
|
+
// Give up on this batch; drop the counter forward so we don't loop forever.
|
|
123
|
+
this.#lastPushedCounter = counterAtStart;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const next = Math.min(delay, this.#backoff.cap);
|
|
127
|
+
this.#currentBackoffMs = next;
|
|
128
|
+
this.emit('retry', { commit, attempt, nextDelayMs: next });
|
|
129
|
+
await this.#sleep(next);
|
|
130
|
+
delay = Math.min(delay * this.#backoff.multiplier, this.#backoff.cap);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
#sleep(ms) {
|
|
135
|
+
return new Promise((resolve) => {
|
|
136
|
+
const t = setTimeout(resolve, ms);
|
|
137
|
+
this.#pendingTimer = t;
|
|
138
|
+
t.unref?.();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export function resolveBackoff(opt) {
|
|
143
|
+
if (!opt || opt === 'exponential') {
|
|
144
|
+
return { base: 1000, multiplier: 2, cap: 3_600_000 };
|
|
145
|
+
}
|
|
146
|
+
return opt;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=push-daemon.js.map
|