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.
Files changed (72) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +21 -0
  3. package/bin/gitsheets +5 -0
  4. package/dist/cli/index.d.ts +2 -0
  5. package/dist/cli/index.d.ts.map +1 -0
  6. package/dist/cli/index.js +256 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/errors.d.ts +72 -0
  9. package/dist/errors.d.ts.map +1 -0
  10. package/dist/errors.js +74 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/index.d.ts +17 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +12 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/patch.d.ts +2 -0
  17. package/dist/patch.d.ts.map +1 -0
  18. package/dist/patch.js +39 -0
  19. package/dist/patch.js.map +1 -0
  20. package/dist/path-template/index.d.ts +42 -0
  21. package/dist/path-template/index.d.ts.map +1 -0
  22. package/dist/path-template/index.js +288 -0
  23. package/dist/path-template/index.js.map +1 -0
  24. package/dist/push-daemon.d.ts +53 -0
  25. package/dist/push-daemon.d.ts.map +1 -0
  26. package/dist/push-daemon.js +148 -0
  27. package/dist/push-daemon.js.map +1 -0
  28. package/dist/repository.d.ts +67 -0
  29. package/dist/repository.d.ts.map +1 -0
  30. package/dist/repository.js +322 -0
  31. package/dist/repository.js.map +1 -0
  32. package/dist/sheet.d.ts +107 -0
  33. package/dist/sheet.d.ts.map +1 -0
  34. package/dist/sheet.js +605 -0
  35. package/dist/sheet.js.map +1 -0
  36. package/dist/store.d.ts +41 -0
  37. package/dist/store.d.ts.map +1 -0
  38. package/dist/store.js +49 -0
  39. package/dist/store.js.map +1 -0
  40. package/dist/toml.d.ts +11 -0
  41. package/dist/toml.d.ts.map +1 -0
  42. package/dist/toml.js +28 -0
  43. package/dist/toml.js.map +1 -0
  44. package/dist/transaction.d.ts +96 -0
  45. package/dist/transaction.d.ts.map +1 -0
  46. package/dist/transaction.js +227 -0
  47. package/dist/transaction.js.map +1 -0
  48. package/dist/validation.d.ts +37 -0
  49. package/dist/validation.d.ts.map +1 -0
  50. package/dist/validation.js +105 -0
  51. package/dist/validation.js.map +1 -0
  52. package/package.json +41 -35
  53. package/bin/cli.js +0 -61
  54. package/commands/edit.js +0 -90
  55. package/commands/normalize.js +0 -81
  56. package/commands/query.js +0 -206
  57. package/commands/read.js +0 -64
  58. package/commands/singer-target.js +0 -214
  59. package/commands/upsert.js +0 -260
  60. package/lib/GitSheets.js +0 -464
  61. package/lib/Repository.js +0 -88
  62. package/lib/Sheet.js +0 -625
  63. package/lib/errors.js +0 -21
  64. package/lib/hologit.js +0 -1
  65. package/lib/logger.js +0 -18
  66. package/lib/path/BaseComponent.js +0 -24
  67. package/lib/path/ExpressionComponent.js +0 -26
  68. package/lib/path/FieldComponent.js +0 -13
  69. package/lib/path/LiteralComponent.js +0 -12
  70. package/lib/path/Query.js +0 -18
  71. package/lib/path/Template.js +0 -214
  72. 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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"}
@@ -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"}
@@ -0,0 +1,2 @@
1
+ export declare function mergePatch(target: unknown, patch: unknown): unknown;
2
+ //# sourceMappingURL=patch.d.ts.map
@@ -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