@12ui/design 0.2.15 → 0.2.16

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 (66) hide show
  1. package/README.md +21 -1
  2. package/dist/branch-execution.d.ts +5 -0
  3. package/dist/branch-execution.d.ts.map +1 -1
  4. package/dist/branch-execution.js +26 -8
  5. package/dist/branch-execution.js.map +1 -1
  6. package/dist/branch-page-conversion.d.ts +18 -2
  7. package/dist/branch-page-conversion.d.ts.map +1 -1
  8. package/dist/branch-page-conversion.js +36 -16
  9. package/dist/branch-page-conversion.js.map +1 -1
  10. package/dist/branch-run-record.d.ts +3 -0
  11. package/dist/branch-run-record.d.ts.map +1 -1
  12. package/dist/branch-run-record.js.map +1 -1
  13. package/dist/cli-capabilities.d.ts +15 -5
  14. package/dist/cli-capabilities.d.ts.map +1 -1
  15. package/dist/cli-capabilities.js +19 -5
  16. package/dist/cli-capabilities.js.map +1 -1
  17. package/dist/cli-draft-command.d.ts +2 -0
  18. package/dist/cli-draft-command.d.ts.map +1 -1
  19. package/dist/cli-draft-command.js +22 -4
  20. package/dist/cli-draft-command.js.map +1 -1
  21. package/dist/cli-package-command.d.ts +2 -0
  22. package/dist/cli-package-command.d.ts.map +1 -1
  23. package/dist/cli-package-command.js +11 -0
  24. package/dist/cli-package-command.js.map +1 -1
  25. package/dist/cli-skill-command.d.ts.map +1 -1
  26. package/dist/cli-skill-command.js +27 -12
  27. package/dist/cli-skill-command.js.map +1 -1
  28. package/dist/cli.js +7 -7
  29. package/dist/cli.js.map +1 -1
  30. package/dist/conversion-receipt-store.d.ts +75 -0
  31. package/dist/conversion-receipt-store.d.ts.map +1 -0
  32. package/dist/conversion-receipt-store.js +143 -0
  33. package/dist/conversion-receipt-store.js.map +1 -0
  34. package/dist/conversion-receipt.d.ts +139 -43
  35. package/dist/conversion-receipt.d.ts.map +1 -1
  36. package/dist/conversion-receipt.js +241 -73
  37. package/dist/conversion-receipt.js.map +1 -1
  38. package/dist/draft-adoption.d.ts +48 -0
  39. package/dist/draft-adoption.d.ts.map +1 -0
  40. package/dist/draft-adoption.js +237 -0
  41. package/dist/draft-adoption.js.map +1 -0
  42. package/dist/draft-run.d.ts +10 -0
  43. package/dist/draft-run.d.ts.map +1 -1
  44. package/dist/draft-run.js +16 -4
  45. package/dist/draft-run.js.map +1 -1
  46. package/dist/legacy-skill-catalog.d.ts.map +1 -1
  47. package/dist/legacy-skill-catalog.js +40 -0
  48. package/dist/legacy-skill-catalog.js.map +1 -1
  49. package/dist/package-submission.d.ts +12 -1
  50. package/dist/package-submission.d.ts.map +1 -1
  51. package/dist/package-submission.js +2 -0
  52. package/dist/package-submission.js.map +1 -1
  53. package/dist/skill-clients.d.ts +39 -0
  54. package/dist/skill-clients.d.ts.map +1 -0
  55. package/dist/skill-clients.js +156 -0
  56. package/dist/skill-clients.js.map +1 -0
  57. package/dist/skill-installer.d.ts +2 -3
  58. package/dist/skill-installer.d.ts.map +1 -1
  59. package/dist/skill-installer.js +9 -101
  60. package/dist/skill-installer.js.map +1 -1
  61. package/package.json +1 -1
  62. package/skills/design/SKILL.md +2 -2
  63. package/skills/design-branch/SKILL.md +3 -3
  64. package/skills/design-convert/SKILL.md +3 -3
  65. package/skills/design-draft/SKILL.md +3 -3
  66. package/skills/design-search/SKILL.md +3 -3
@@ -0,0 +1,143 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, readdir, readFile, rename, rm, writeFile, } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ /**
6
+ * Where a user's conversion receipts live, and the only code that touches those
7
+ * files.
8
+ *
9
+ * The records are per USER, not per image: the same approved design converted
10
+ * from a downloads folder, a run directory, and a project checkout is the same
11
+ * bytes and the same paid conversion, and a receipt written beside each copy
12
+ * both litters the user's directories and misses every copy it was not written
13
+ * beside. So the store is keyed by what actually identifies the work — the
14
+ * digest of the submitted bytes and the model that converted them — and lives
15
+ * under the 12ui home.
16
+ *
17
+ * `~/.12ui` is that home. Credentials sit in the config root
18
+ * (`~/.config/12ui/credentials.json`) because they are configuration, and the
19
+ * managed CLI installs itself into the data root (`~/.local/share/12ui`)
20
+ * because it is installed software; a receipt is neither. It is per-user
21
+ * runtime evidence the CLI can always re-buy, and `~/.12ui` is where this
22
+ * codebase already keeps per-user 12ui runtime state (`legacy-codex-cleanup.ts`
23
+ * reads `~/.12ui/codex-design`). `TWELVE_UI_HOME` overrides it wholesale, which
24
+ * is what a test — or a user with a shared home — points somewhere else.
25
+ *
26
+ * Shape: one small JSON file per key, never one appended log. A point lookup
27
+ * reads exactly the record it wants instead of the whole history; a withdrawal
28
+ * unlinks one file instead of rewriting the log; and two CLI invocations
29
+ * running at once — the common case, since branch runs are launched in
30
+ * parallel — never contend for a single file, so no lock is needed. Every write
31
+ * lands through a temp file and a rename, so a reader either sees the previous
32
+ * record or the new one and never a half-written one.
33
+ */
34
+ export const TWELVE_UI_HOME_ENV = 'TWELVE_UI_HOME';
35
+ export const TWELVE_UI_HOME_DIRECTORY_NAME = '.12ui';
36
+ export const CONVERSION_RECEIPT_STORE_DIRECTORY_NAME = 'conversions';
37
+ /** How many records one invocation is willing to inspect while pruning. */
38
+ export const CONVERSION_RECEIPT_PRUNE_LIMIT = 64;
39
+ export const resolveTwelveUiHome = (environment = process.env, homeDirectory = os.homedir()) => {
40
+ const explicit = environment[TWELVE_UI_HOME_ENV]?.trim();
41
+ return explicit
42
+ ? path.resolve(explicit)
43
+ : path.join(homeDirectory, TWELVE_UI_HOME_DIRECTORY_NAME);
44
+ };
45
+ export const resolveConversionReceiptStore = (environment = process.env, homeDirectory = os.homedir()) => path.join(resolveTwelveUiHome(environment, homeDirectory), CONVERSION_RECEIPT_STORE_DIRECTORY_NAME);
46
+ /**
47
+ * The key, as a filename. The digest is already filename-safe and is checked
48
+ * before it is used; the model is whatever the server called itself, so it is
49
+ * reduced to the same alphabet rather than trusted to be one path segment.
50
+ */
51
+ export const conversionReceiptRecordName = (key) => {
52
+ if (!/^[a-f0-9]{64}$/u.test(key.sha256)) {
53
+ throw new Error('A conversion receipt key needs the sha256 of the bytes that were submitted');
54
+ }
55
+ const model = key.model.replace(/[^A-Za-z0-9._-]/gu, '_');
56
+ if (!model)
57
+ throw new Error('A conversion receipt key needs the model that converted the bytes');
58
+ return `${key.sha256}.${model}.json`;
59
+ };
60
+ export const conversionReceiptRecordPath = (store, key) => path.join(store, conversionReceiptRecordName(key));
61
+ /** A missing, unreadable, or unparseable record is simply no record. */
62
+ export const readConversionReceiptRecord = async (store, key) => {
63
+ try {
64
+ return JSON.parse(await readFile(conversionReceiptRecordPath(store, key), 'utf8'));
65
+ }
66
+ catch {
67
+ return undefined;
68
+ }
69
+ };
70
+ /**
71
+ * Atomic replace. The temp file is created exclusively under a unique name so
72
+ * two invocations writing the same key at the same time cannot land in each
73
+ * other's buffer, and the rename is what any concurrent reader observes.
74
+ */
75
+ export const writeConversionReceiptRecord = async (store, key, record) => {
76
+ const target = conversionReceiptRecordPath(store, key);
77
+ await mkdir(store, { recursive: true, mode: 0o700 });
78
+ const temporary = path.join(store, `.${randomUUID()}.tmp`);
79
+ try {
80
+ await writeFile(temporary, `${JSON.stringify(record, null, 2)}\n`, {
81
+ encoding: 'utf8',
82
+ flag: 'wx',
83
+ mode: 0o600,
84
+ });
85
+ await rename(temporary, target);
86
+ return target;
87
+ }
88
+ catch (error) {
89
+ await rm(temporary, { force: true });
90
+ throw error;
91
+ }
92
+ };
93
+ /** Null when there was nothing to remove; removal is never an error. */
94
+ export const removeConversionReceiptRecord = async (store, key) => {
95
+ const target = conversionReceiptRecordPath(store, key);
96
+ try {
97
+ await rm(target);
98
+ return target;
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ };
104
+ /**
105
+ * Bounded expiry hygiene, run on the invocations that already touch the store.
106
+ *
107
+ * A conversion lives about a day and a store record outlives it by however long
108
+ * the user goes without converting, so without this the store only grows. It
109
+ * inspects at most `limit` records per call, starting at a rotating offset, so
110
+ * a large store costs the same as a small one per invocation and is still swept
111
+ * completely over several. Only records the caller's predicate calls expired
112
+ * are removed — anything this version cannot read is left alone, because an
113
+ * unreadable record is as likely to belong to a newer CLI as to be junk.
114
+ */
115
+ export const pruneConversionReceiptRecords = async (args) => {
116
+ let names;
117
+ try {
118
+ names = (await readdir(args.store)).filter((name) => name.endsWith('.json'));
119
+ }
120
+ catch {
121
+ return [];
122
+ }
123
+ if (names.length === 0)
124
+ return [];
125
+ const limit = Math.max(1, args.limit ?? CONVERSION_RECEIPT_PRUNE_LIMIT);
126
+ const start = names.length <= limit ? 0 : Math.floor(Math.random() * names.length);
127
+ const sampled = Array.from({ length: Math.min(limit, names.length) }, (_unused, index) => names[(start + index) % names.length]);
128
+ const removed = await Promise.all(sampled.map(async (name) => {
129
+ const file = path.join(args.store, name);
130
+ try {
131
+ const record = JSON.parse(await readFile(file, 'utf8'));
132
+ if (!args.expired(record))
133
+ return null;
134
+ await rm(file, { force: true });
135
+ return file;
136
+ }
137
+ catch {
138
+ return null;
139
+ }
140
+ }));
141
+ return removed.filter((entry) => entry !== null);
142
+ };
143
+ //# sourceMappingURL=conversion-receipt-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversion-receipt-store.js","sourceRoot":"","sources":["../src/conversion-receipt-store.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  import type { ConversionOperation } from './conversion-client.js';
2
2
  import type { ConversionOutput } from './conversion-outputs.js';
3
+ import { type ConversionReceiptKey } from './conversion-receipt-store.js';
4
+ import type { PackageSubmissionResult } from './package-submission.js';
3
5
  /**
4
6
  * The single owner of "an image's conversion was already bought, and here is
5
7
  * the proof".
@@ -14,14 +16,30 @@ import type { ConversionOutput } from './conversion-outputs.js';
14
16
  * the upload — so a false claim is a failed page, not a cheap win.
15
17
  *
16
18
  * This is therefore a receipt of a proved fact, not a cache: it records the
17
- * digest of the exact bytes a succeeded conversion was admitted with, next to
18
- * the image those bytes came from. A later run reuses it only when the bytes it
19
- * is about to submit hash identically. Absent proof it converts fresh.
19
+ * digest of the exact bytes a succeeded conversion was admitted with. A later
20
+ * run reuses it only when the bytes it is about to submit hash identically.
21
+ * Absent proof it converts fresh.
20
22
  *
21
- * Both `12ui convert` and `12ui branch execute --convert` write it, and only
22
- * this module decides what counts as proof.
23
+ * The records live in the per-user store (`conversion-receipt-store.ts`), keyed
24
+ * by that digest and the model, because the fact is about bytes and not about
25
+ * where a copy of them happens to sit. `12ui convert`, `12ui convert package`,
26
+ * and `12ui branch execute --convert` write them, `12ui branch execute
27
+ * --convert` reads them, and only this module decides what counts as proof.
28
+ *
29
+ * SUNSET — legacy sidecars. Receipts shipped in 0.2.15 as
30
+ * `<image>.conversion.json` written beside the input image. Those files are
31
+ * still READ beside a winner image for one release cycle and migrated into the
32
+ * store the first time they are seen, so an upgrade never re-buys a conversion
33
+ * a 0.2.15 convert already paid for. Nothing writes them any more. Delete the
34
+ * legacy read path — `LEGACY_CONVERSION_RECEIPT_SUFFIX`,
35
+ * `legacyConversionReceiptPath`, `readLegacyConversionReceipts`, and the
36
+ * `legacyImagePaths` argument of `invalidateConversionReceipts` — one release
37
+ * after 0.2.16; by then every receipt a 0.2.15 convert wrote has long outlived
38
+ * the ~24h conversion it names.
23
39
  */
24
- export declare const CONVERSION_RECEIPT_SUFFIX = ".conversion.json";
40
+ export declare const LEGACY_CONVERSION_RECEIPT_SUFFIX = ".conversion.json";
41
+ export type { ConversionReceiptKey } from './conversion-receipt-store.js';
42
+ export { resolveConversionReceiptStore } from './conversion-receipt-store.js';
25
43
  /**
26
44
  * The output an attachable conversion must have produced. A package converts
27
45
  * every viewport to a LayerDoc and buys the page export separately, so the
@@ -34,8 +52,9 @@ export declare const REUSABLE_CONVERSION_OUTPUT: ConversionOutput;
34
52
  * attachment expiring within this window (`PACKAGE_SOURCE_MINIMUM_REMAINING_MS`)
35
53
  * because composition reads the source run at the END of a package drive, and
36
54
  * that refusal fails the page after its screens are already paid for.
37
- * Conversions live 24h while a receipt sits beside a user's image indefinitely,
38
- * so an expiry that is not checked here is a page failure waiting for day two.
55
+ * Conversions live 24h while a stored receipt sits in the user's home
56
+ * indefinitely, so an expiry that is not checked here is a page failure waiting
57
+ * for day two.
39
58
  */
40
59
  export declare const CONVERSION_REUSE_MINIMUM_REMAINING_MS: number;
41
60
  export type ConversionReceipt = {
@@ -46,11 +65,12 @@ export type ConversionReceipt = {
46
65
  /** A reused conversion must have run the model the package declares. */
47
66
  model: string;
48
67
  /**
49
- * The operation that bought it: a branch run id, or for `12ui convert` the
50
- * conversion's own id. An operation never reuses its own receipt — a branch
51
- * package key is derived per run and page, so attaching a reuse on a resume
52
- * would change the request body under a key the server already settled, which
53
- * is `409 idempotency_conflict` on a page that was already paid for.
68
+ * The operation that bought it: a branch run id, a package id, or for
69
+ * `12ui convert` the conversion's own id. An operation never reuses its own
70
+ * receipt — a branch package key is derived per run and page, so attaching a
71
+ * reuse on a resume would change the request body under a key the server
72
+ * already settled, which is `409 idempotency_conflict` on a page that was
73
+ * already paid for.
54
74
  */
55
75
  boughtBy: string;
56
76
  /** When the referenced conversion stops being attachable at all. */
@@ -58,41 +78,50 @@ export type ConversionReceipt = {
58
78
  createdAt: string;
59
79
  };
60
80
  export type ConversionReceiptWrite = {
61
- written: string[];
62
- failures: {
81
+ /** Where the record landed, or null when nothing was written. */
82
+ path: string | null;
83
+ failure?: {
63
84
  path: string;
64
85
  detail: string;
65
- }[];
86
+ };
66
87
  };
67
- export declare const conversionReceiptPath: (imagePath: string) => string;
68
- /** A missing or unreadable receipt is simply no proof, never an error. */
69
- export declare const readConversionReceipt: (imagePath: string) => Promise<ConversionReceipt | undefined>;
70
- export declare const readConversionReceipts: (imagePaths: readonly string[]) => Promise<ConversionReceipt[]>;
88
+ /** Where 0.2.15 wrote a receipt. Read for one release cycle, never written. */
89
+ export declare const legacyConversionReceiptPath: (imagePath: string) => string;
90
+ export declare const conversionReceiptKey: (receipt: ConversionReceipt) => ConversionReceiptKey;
71
91
  /**
72
- * Written next to every local copy of the bytes it describes, so the next run
73
- * that starts from the same approved design finds it wherever it reads that
74
- * design from. A write that fails is not a command failure: the receipt is an
75
- * optimisation, and its absence only costs one conversion. Callers that can say
76
- * so report the failures; the rest ignore them.
92
+ * The stored record for exactly these bytes and this model, or nothing.
93
+ *
94
+ * An expired record is removed as it is read: it is the one expired record this
95
+ * invocation is certain about, and leaving it would have the next run read it
96
+ * again to reach the same conclusion.
77
97
  */
78
- export declare const writeConversionReceipts: (imagePaths: readonly string[], receipt: ConversionReceipt) => Promise<ConversionReceiptWrite>;
98
+ export declare const readStoredConversionReceipt: (key: ConversionReceiptKey, options?: {
99
+ store?: string;
100
+ now?: Date;
101
+ }) => Promise<ConversionReceipt | undefined>;
79
102
  /**
80
- * Withdraw a receipt the SERVER disproved, so the next run does not repeat the
81
- * dance the last one already paid a retry for.
82
- *
83
- * The bar is deliberately narrow: only an explicit refusal of the attachment at
84
- * admission the server having read the referenced conversion's durable
85
- * evidence and rejected the claim — is proof that this receipt is no longer
86
- * proof of anything. A network error, a timeout, or any other transient failure
87
- * says nothing about the referenced conversion, and deleting on one of those
88
- * would throw away a live receipt and buy its conversion again for nothing.
103
+ * Record a proved conversion. A write that fails is not a command failure: the
104
+ * receipt is an optimisation, and its absence only costs one conversion.
105
+ * Callers that can say so report the failure; the rest ignore it.
106
+ */
107
+ export declare const recordConversionReceipt: (receipt: ConversionReceipt, options?: {
108
+ store?: string;
109
+ now?: Date;
110
+ }) => Promise<ConversionReceiptWrite>;
111
+ /**
112
+ * The 0.2.15 sidecars beside these images, carried forward.
89
113
  *
90
- * Only receipts naming the disproven conversion are removed: a newer receipt
91
- * beside the same image describes different work and is untouched. Removal is
92
- * best-effort, because a receipt that cannot be deleted only costs the next run
93
- * one refused attachment and one retry the same price this run paid.
114
+ * Every one that is still live is copied into the store the moment it is seen,
115
+ * so the upgrade costs nothing and the next run finds the proof by digest no
116
+ * matter which copy of the image it starts from. An existing store record wins:
117
+ * it was written by a later command about the same bytes, and overwriting it
118
+ * with the sidecar would replace a fresher expiry with an older one. Migration
119
+ * is best-effort — the receipt is still honoured this run either way.
94
120
  */
95
- export declare const invalidateConversionReceipts: (imagePaths: readonly string[], conversionId: string) => Promise<string[]>;
121
+ export declare const readLegacyConversionReceipts: (imagePaths: readonly string[], options?: {
122
+ store?: string;
123
+ now?: Date;
124
+ }) => Promise<ConversionReceipt[]>;
96
125
  /**
97
126
  * The proof gate. Every condition is a fact about the bytes in hand, never an
98
127
  * assumption about what an earlier command probably did.
@@ -106,22 +135,89 @@ export declare const reusableConversionId: (args: {
106
135
  boughtBy: string;
107
136
  now?: Date;
108
137
  }) => string | undefined;
138
+ /**
139
+ * The gate, over the store and over whatever the caller already holds.
140
+ *
141
+ * The store is consulted first because it is the record every command writes;
142
+ * the in-hand receipts are the 0.2.15 sidecars this run read beside the winner,
143
+ * which stay proof for one release cycle even if their migration failed.
144
+ */
145
+ export declare const findReusableConversionId: (args: {
146
+ sha256: string;
147
+ model: string;
148
+ boughtBy: string;
149
+ receipts?: readonly ConversionReceipt[];
150
+ store?: string;
151
+ now?: Date;
152
+ }) => Promise<string | undefined>;
153
+ /**
154
+ * Withdraw a receipt the SERVER disproved, so the next run does not repeat the
155
+ * dance the last one already paid a retry for.
156
+ *
157
+ * The bar is deliberately narrow: only an explicit refusal of the attachment at
158
+ * admission — the server having read the referenced conversion's durable
159
+ * evidence and rejected the claim — is proof that this receipt is no longer
160
+ * proof of anything. A network error, a timeout, or any other transient failure
161
+ * says nothing about the referenced conversion, and deleting on one of those
162
+ * would throw away a live receipt and buy its conversion again for nothing.
163
+ *
164
+ * Only records naming the disproven conversion are withdrawn: a newer record
165
+ * under the same key describes different work and is untouched. Removal is
166
+ * best-effort, because a receipt that cannot be deleted only costs the next run
167
+ * one refused attachment and one retry — the same price this run paid.
168
+ */
169
+ export declare const invalidateConversionReceipts: (args: {
170
+ conversionId: string;
171
+ /** The keys the disproven claim could have come from. */
172
+ keys?: readonly ConversionReceiptKey[];
173
+ /** Sunset: 0.2.15 sidecars beside these images, if they name it. */
174
+ legacyImagePaths?: readonly string[];
175
+ store?: string;
176
+ }) => Promise<string[]>;
109
177
  /**
110
178
  * What `12ui convert` leaves behind for the branch run that follows it.
111
179
  *
112
180
  * Only a succeeded image-to-LayerDoc conversion can stand in for a package
113
- * viewport, so anything else writes nothing at all: a receipt for an `html`
181
+ * viewport, so anything else records nothing at all: a receipt for an `html`
114
182
  * conversion would be a claim the server refuses, and the refusal costs a whole
115
183
  * page. The digest is of the bytes actually submitted, which is what `--width`
116
184
  * normalization changes and what the server admitted the run with.
117
185
  */
118
186
  export declare const recordConvertedImageReceipt: (args: {
119
- /** The file the caller converted; the branch run looks for the receipt here. */
120
- imagePath: string;
121
187
  /** The EXACT bytes submitted, after any `--width` normalization. */
122
188
  bytes: Uint8Array;
123
189
  operation: Pick<ConversionOperation, "id" | "status" | "output" | "model" | "expires_at" | "input_kind">;
190
+ store?: string;
124
191
  warn?: (message: string) => void;
125
192
  now?: Date;
126
193
  }) => Promise<ConversionReceipt | undefined>;
194
+ /**
195
+ * What `12ui convert package` leaves behind: one receipt per viewport whose own
196
+ * child conversion the package bought.
197
+ *
198
+ * A package converts each viewport to a LayerDoc and composes the page from
199
+ * those children, so every viewport of a submitted page is exactly the artifact
200
+ * a later package can attach instead of buying — the same fact the branch path
201
+ * records for the winner, for every screenshot the user named.
202
+ *
203
+ * A viewport that ATTACHED a conversion bought nothing, so it records nothing.
204
+ * The expiry reported is the package's own, which its children cannot outlive
205
+ * backwards, and the model is the one the server says it ran; without either
206
+ * fact nothing here proves a conversion is still attachable, so nothing is
207
+ * recorded and the caller is told once.
208
+ */
209
+ export declare const recordPackageConversionReceipts: (args: {
210
+ pages: readonly {
211
+ id: string;
212
+ viewports: readonly {
213
+ id: string;
214
+ bytes: Uint8Array;
215
+ sourceConversionId?: string;
216
+ }[];
217
+ }[];
218
+ result: Pick<PackageSubmissionResult, "packageId" | "model" | "expiresAt" | "pages">;
219
+ store?: string;
220
+ warn?: (message: string) => void;
221
+ now?: Date;
222
+ }) => Promise<ConversionReceipt[]>;
127
223
  //# sourceMappingURL=conversion-receipt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"conversion-receipt.d.ts","sourceRoot":"","sources":["../src/conversion-receipt.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAE5D;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,EAAE,gBAA6B,CAAC;AAEvE;;;;;;;GAOG;AACH,eAAO,MAAM,qCAAqC,QAAc,CAAC;AAEjE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,CAAC,CAAC;IACX,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC9C,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,KAAG,MAEzD,CAAC;AAiBF,0EAA0E;AAC1E,eAAO,MAAM,qBAAqB,GAChC,WAAW,MAAM,KAChB,OAAO,CAAC,iBAAiB,GAAG,SAAS,CASvC,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,YAAY,SAAS,MAAM,EAAE,KAC5B,OAAO,CAAC,iBAAiB,EAAE,CAG7B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAClC,YAAY,SAAS,MAAM,EAAE,EAC7B,SAAS,iBAAiB,KACzB,OAAO,CAAC,sBAAsB,CA+BhC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GACvC,YAAY,SAAS,MAAM,EAAE,EAC7B,cAAc,MAAM,KACnB,OAAO,CAAC,MAAM,EAAE,CAalB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,MAAM;IACzC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACvC,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KAAG,MAAM,GAAG,SAQZ,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,GAAU,MAAM;IACtD,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,IAAI,CACb,mBAAmB,EACnB,IAAI,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,YAAY,GAAG,YAAY,CACnE,CAAC;IACF,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CA6BxC,CAAC"}
1
+ {"version":3,"file":"conversion-receipt.d.ts","sourceRoot":"","sources":["../src/conversion-receipt.ts"],"names":[],"mappings":"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"}