@purveyors/cli 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/catalog.d.ts +4 -56
- package/dist/commands/catalog.d.ts.map +1 -1
- package/dist/commands/catalog.js +14 -81
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +82 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/inventory.d.ts +2 -25
- package/dist/commands/inventory.d.ts.map +1 -1
- package/dist/commands/inventory.js +109 -149
- package/dist/commands/inventory.js.map +1 -1
- package/dist/commands/roast.d.ts +2 -41
- package/dist/commands/roast.d.ts.map +1 -1
- package/dist/commands/roast.js +445 -126
- package/dist/commands/roast.js.map +1 -1
- package/dist/commands/sales.d.ts +2 -10
- package/dist/commands/sales.d.ts.map +1 -1
- package/dist/commands/sales.js +100 -112
- package/dist/commands/sales.js.map +1 -1
- package/dist/commands/tasting.d.ts +4 -38
- package/dist/commands/tasting.d.ts.map +1 -1
- package/dist/commands/tasting.js +93 -116
- package/dist/commands/tasting.js.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/ai.d.ts +36 -0
- package/dist/lib/ai.d.ts.map +1 -0
- package/dist/lib/ai.js +38 -0
- package/dist/lib/ai.js.map +1 -0
- package/dist/lib/artisan/db.d.ts +37 -0
- package/dist/lib/artisan/db.d.ts.map +1 -0
- package/dist/lib/artisan/db.js +51 -0
- package/dist/lib/artisan/db.js.map +1 -0
- package/dist/lib/artisan/import.d.ts +16 -0
- package/dist/lib/artisan/import.d.ts.map +1 -0
- package/dist/lib/artisan/import.js +447 -0
- package/dist/lib/artisan/import.js.map +1 -0
- package/dist/lib/artisan/index.d.ts +9 -0
- package/dist/lib/artisan/index.d.ts.map +1 -0
- package/dist/lib/artisan/index.js +7 -0
- package/dist/lib/artisan/index.js.map +1 -0
- package/dist/lib/artisan/parser.d.ts +19 -0
- package/dist/lib/artisan/parser.d.ts.map +1 -0
- package/dist/lib/artisan/parser.js +376 -0
- package/dist/lib/artisan/parser.js.map +1 -0
- package/dist/lib/artisan/temperature.d.ts +52 -0
- package/dist/lib/artisan/temperature.d.ts.map +1 -0
- package/dist/lib/artisan/temperature.js +101 -0
- package/dist/lib/artisan/temperature.js.map +1 -0
- package/dist/lib/artisan/types.d.ts +195 -0
- package/dist/lib/artisan/types.d.ts.map +1 -0
- package/dist/lib/artisan/types.js +35 -0
- package/dist/lib/artisan/types.js.map +1 -0
- package/dist/lib/artisan/validator.d.ts +14 -0
- package/dist/lib/artisan/validator.d.ts.map +1 -0
- package/dist/lib/artisan/validator.js +228 -0
- package/dist/lib/artisan/validator.js.map +1 -0
- package/dist/lib/catalog.d.ts +87 -0
- package/dist/lib/catalog.d.ts.map +1 -0
- package/dist/lib/catalog.js +111 -0
- package/dist/lib/catalog.js.map +1 -0
- package/dist/lib/config.d.ts +26 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +59 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +11 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/interactive/forms.d.ts +33 -0
- package/dist/lib/interactive/forms.d.ts.map +1 -0
- package/dist/lib/interactive/forms.js +139 -0
- package/dist/lib/interactive/forms.js.map +1 -0
- package/dist/lib/interactive/watch.d.ts +66 -0
- package/dist/lib/interactive/watch.d.ts.map +1 -0
- package/dist/lib/interactive/watch.js +494 -0
- package/dist/lib/interactive/watch.js.map +1 -0
- package/dist/lib/inventory.d.ts +80 -0
- package/dist/lib/inventory.d.ts.map +1 -0
- package/dist/lib/inventory.js +205 -0
- package/dist/lib/inventory.js.map +1 -0
- package/dist/lib/roast.d.ts +127 -0
- package/dist/lib/roast.d.ts.map +1 -0
- package/dist/lib/roast.js +284 -0
- package/dist/lib/roast.js.map +1 -0
- package/dist/lib/sales.d.ts +53 -0
- package/dist/lib/sales.d.ts.map +1 -0
- package/dist/lib/sales.js +155 -0
- package/dist/lib/sales.js.map +1 -0
- package/dist/lib/tasting.d.ts +76 -0
- package/dist/lib/tasting.d.ts.map +1 -0
- package/dist/lib/tasting.js +136 -0
- package/dist/lib/tasting.js.map +1 -0
- package/package.json +15 -2
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AuthError, PrvrsError } from './errors.js';
|
|
3
|
+
// ─── Shared select columns ────────────────────────────────────────────────────
|
|
4
|
+
export const INVENTORY_LIST_SELECT = [
|
|
5
|
+
'id',
|
|
6
|
+
'rank',
|
|
7
|
+
'notes',
|
|
8
|
+
'cupping_notes',
|
|
9
|
+
'purchase_date',
|
|
10
|
+
'purchased_qty_lbs',
|
|
11
|
+
'bean_cost',
|
|
12
|
+
'tax_ship_cost',
|
|
13
|
+
'last_updated',
|
|
14
|
+
'user',
|
|
15
|
+
'catalog_id',
|
|
16
|
+
'stocked',
|
|
17
|
+
'coffee_catalog!catalog_id (id, name, source, country, region, processing, cost_lb, description_short, stocked)',
|
|
18
|
+
].join(', ');
|
|
19
|
+
export const INVENTORY_DETAIL_SELECT = [
|
|
20
|
+
'id',
|
|
21
|
+
'rank',
|
|
22
|
+
'notes',
|
|
23
|
+
'cupping_notes',
|
|
24
|
+
'purchase_date',
|
|
25
|
+
'purchased_qty_lbs',
|
|
26
|
+
'bean_cost',
|
|
27
|
+
'tax_ship_cost',
|
|
28
|
+
'last_updated',
|
|
29
|
+
'user',
|
|
30
|
+
'catalog_id',
|
|
31
|
+
'stocked',
|
|
32
|
+
'coffee_catalog!catalog_id (id, name, source, country, region, processing, cost_lb, description_short, description_long, farm_notes, cupping_notes, stocked)',
|
|
33
|
+
].join(', ');
|
|
34
|
+
// ─── Zod schemas ──────────────────────────────────────────────────────────────
|
|
35
|
+
export const listInventorySchema = z.object({
|
|
36
|
+
stocked: z.boolean().optional(),
|
|
37
|
+
limit: z.number().int().min(1).default(20),
|
|
38
|
+
});
|
|
39
|
+
export const getInventorySchema = z.object({
|
|
40
|
+
id: z.number().int().positive(),
|
|
41
|
+
});
|
|
42
|
+
export const addInventorySchema = z.object({
|
|
43
|
+
catalogId: z.number().int().positive(),
|
|
44
|
+
qty: z.number().positive(),
|
|
45
|
+
cost: z.number().optional(),
|
|
46
|
+
taxShip: z.number().optional(),
|
|
47
|
+
notes: z.string().optional(),
|
|
48
|
+
purchaseDate: z.string().optional(),
|
|
49
|
+
});
|
|
50
|
+
export const updateInventorySchema = z
|
|
51
|
+
.object({
|
|
52
|
+
qty: z.number().positive().optional(),
|
|
53
|
+
cost: z.number().optional(),
|
|
54
|
+
taxShip: z.number().optional(),
|
|
55
|
+
notes: z.string().optional(),
|
|
56
|
+
stocked: z.boolean().optional(),
|
|
57
|
+
})
|
|
58
|
+
.refine((v) => Object.keys(v).some((k) => v[k] !== undefined), {
|
|
59
|
+
message: 'No update fields provided. Pass at least one of: qty, cost, taxShip, notes, stocked.',
|
|
60
|
+
});
|
|
61
|
+
export const deleteInventorySchema = z.object({
|
|
62
|
+
id: z.number().int().positive(),
|
|
63
|
+
});
|
|
64
|
+
// ─── Pure lib functions ───────────────────────────────────────────────────────
|
|
65
|
+
/**
|
|
66
|
+
* List green coffee inventory for a user.
|
|
67
|
+
*/
|
|
68
|
+
export async function listInventory(supabase, userId, opts) {
|
|
69
|
+
const parsed = listInventorySchema.parse(opts);
|
|
70
|
+
let query = supabase.from('green_coffee_inv').select(INVENTORY_LIST_SELECT).eq('user', userId);
|
|
71
|
+
if (parsed.stocked) {
|
|
72
|
+
query = query.eq('stocked', true);
|
|
73
|
+
}
|
|
74
|
+
const { data, error } = await query
|
|
75
|
+
.order('last_updated', { ascending: false })
|
|
76
|
+
.limit(parsed.limit);
|
|
77
|
+
if (error)
|
|
78
|
+
throw error;
|
|
79
|
+
return (data ?? []);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Fetch a single inventory item by ID (must belong to userId).
|
|
83
|
+
*/
|
|
84
|
+
export async function getInventory(supabase, userId, id) {
|
|
85
|
+
getInventorySchema.parse({ id });
|
|
86
|
+
const { data, error } = await supabase
|
|
87
|
+
.from('green_coffee_inv')
|
|
88
|
+
.select(INVENTORY_DETAIL_SELECT)
|
|
89
|
+
.eq('id', id)
|
|
90
|
+
.eq('user', userId)
|
|
91
|
+
.single();
|
|
92
|
+
if (error) {
|
|
93
|
+
if (error.code === 'PGRST116') {
|
|
94
|
+
throw new AuthError(`Inventory item ${id} not found or does not belong to you.`);
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
return data;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Add a new inventory item for a user.
|
|
102
|
+
*/
|
|
103
|
+
export async function addInventory(supabase, userId, input) {
|
|
104
|
+
const parsed = addInventorySchema.parse(input);
|
|
105
|
+
const todayIso = () => new Date().toISOString().slice(0, 10);
|
|
106
|
+
const insertPayload = {
|
|
107
|
+
user: userId,
|
|
108
|
+
catalog_id: parsed.catalogId,
|
|
109
|
+
purchased_qty_lbs: parsed.qty,
|
|
110
|
+
purchase_date: parsed.purchaseDate ?? todayIso(),
|
|
111
|
+
};
|
|
112
|
+
if (parsed.cost !== undefined)
|
|
113
|
+
insertPayload.bean_cost = parsed.cost;
|
|
114
|
+
if (parsed.taxShip !== undefined)
|
|
115
|
+
insertPayload.tax_ship_cost = parsed.taxShip;
|
|
116
|
+
if (parsed.notes !== undefined)
|
|
117
|
+
insertPayload.notes = parsed.notes;
|
|
118
|
+
const { data: inserted, error: insertError } = await supabase
|
|
119
|
+
.from('green_coffee_inv')
|
|
120
|
+
.insert(insertPayload)
|
|
121
|
+
.select('id')
|
|
122
|
+
.single();
|
|
123
|
+
if (insertError)
|
|
124
|
+
throw insertError;
|
|
125
|
+
// Re-fetch with catalog join
|
|
126
|
+
const { data, error } = await supabase
|
|
127
|
+
.from('green_coffee_inv')
|
|
128
|
+
.select(INVENTORY_LIST_SELECT)
|
|
129
|
+
.eq('id', inserted.id)
|
|
130
|
+
.single();
|
|
131
|
+
if (error)
|
|
132
|
+
throw error;
|
|
133
|
+
return data;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Update an existing inventory item (must belong to userId).
|
|
137
|
+
*/
|
|
138
|
+
export async function updateInventory(supabase, userId, id, input) {
|
|
139
|
+
getInventorySchema.parse({ id });
|
|
140
|
+
const parsed = updateInventorySchema.parse(input);
|
|
141
|
+
// Verify ownership
|
|
142
|
+
const { data: existing, error: fetchError } = await supabase
|
|
143
|
+
.from('green_coffee_inv')
|
|
144
|
+
.select('id')
|
|
145
|
+
.eq('id', id)
|
|
146
|
+
.eq('user', userId)
|
|
147
|
+
.single();
|
|
148
|
+
if (fetchError || !existing) {
|
|
149
|
+
throw new AuthError(`Inventory item ${id} not found or does not belong to you.`);
|
|
150
|
+
}
|
|
151
|
+
const updates = {};
|
|
152
|
+
if (parsed.qty !== undefined)
|
|
153
|
+
updates.purchased_qty_lbs = parsed.qty;
|
|
154
|
+
if (parsed.cost !== undefined)
|
|
155
|
+
updates.bean_cost = parsed.cost;
|
|
156
|
+
if (parsed.taxShip !== undefined)
|
|
157
|
+
updates.tax_ship_cost = parsed.taxShip;
|
|
158
|
+
if (parsed.notes !== undefined)
|
|
159
|
+
updates.notes = parsed.notes;
|
|
160
|
+
if (parsed.stocked !== undefined)
|
|
161
|
+
updates.stocked = parsed.stocked;
|
|
162
|
+
if (Object.keys(updates).length === 0) {
|
|
163
|
+
throw new PrvrsError('INVALID_ARGUMENT', 'No update fields provided. Pass at least one of: qty, cost, taxShip, notes, stocked.');
|
|
164
|
+
}
|
|
165
|
+
const { error: updateError } = await supabase
|
|
166
|
+
.from('green_coffee_inv')
|
|
167
|
+
.update(updates)
|
|
168
|
+
.eq('id', id)
|
|
169
|
+
.eq('user', userId);
|
|
170
|
+
if (updateError)
|
|
171
|
+
throw updateError;
|
|
172
|
+
// Re-fetch the updated row
|
|
173
|
+
const { data, error } = await supabase
|
|
174
|
+
.from('green_coffee_inv')
|
|
175
|
+
.select(INVENTORY_LIST_SELECT)
|
|
176
|
+
.eq('id', id)
|
|
177
|
+
.single();
|
|
178
|
+
if (error)
|
|
179
|
+
throw error;
|
|
180
|
+
return data;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Delete an inventory item (must belong to userId).
|
|
184
|
+
*/
|
|
185
|
+
export async function deleteInventory(supabase, userId, id) {
|
|
186
|
+
deleteInventorySchema.parse({ id });
|
|
187
|
+
// Verify ownership
|
|
188
|
+
const { data: existing, error: fetchError } = await supabase
|
|
189
|
+
.from('green_coffee_inv')
|
|
190
|
+
.select('id')
|
|
191
|
+
.eq('id', id)
|
|
192
|
+
.eq('user', userId)
|
|
193
|
+
.single();
|
|
194
|
+
if (fetchError || !existing) {
|
|
195
|
+
throw new AuthError(`Inventory item ${id} not found or does not belong to you.`);
|
|
196
|
+
}
|
|
197
|
+
const { error: deleteError } = await supabase
|
|
198
|
+
.from('green_coffee_inv')
|
|
199
|
+
.delete()
|
|
200
|
+
.eq('id', id)
|
|
201
|
+
.eq('user', userId);
|
|
202
|
+
if (deleteError)
|
|
203
|
+
throw deleteError;
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=inventory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory.js","sourceRoot":"","sources":["../../src/lib/inventory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
import type { ArtisanImportResult } from './artisan/import.js';
|
|
4
|
+
export interface RoastProfile {
|
|
5
|
+
roast_id: number;
|
|
6
|
+
batch_name: string | null;
|
|
7
|
+
coffee_id: number | null;
|
|
8
|
+
coffee_name: string | null;
|
|
9
|
+
roast_date: string | null;
|
|
10
|
+
oz_in: number | null;
|
|
11
|
+
oz_out: number | null;
|
|
12
|
+
weight_loss_percent: number | null;
|
|
13
|
+
roast_notes: string | null;
|
|
14
|
+
user: string;
|
|
15
|
+
last_updated: string;
|
|
16
|
+
roaster_type: string | null;
|
|
17
|
+
roaster_size: string | null;
|
|
18
|
+
temperature_unit: string | null;
|
|
19
|
+
charge_time: number | null;
|
|
20
|
+
drop_time: number | null;
|
|
21
|
+
fc_start_time: number | null;
|
|
22
|
+
fc_end_time: number | null;
|
|
23
|
+
fc_start_temp: number | null;
|
|
24
|
+
drop_temp: number | null;
|
|
25
|
+
charge_temp: number | null;
|
|
26
|
+
dry_percent: number | null;
|
|
27
|
+
maillard_percent: number | null;
|
|
28
|
+
development_percent: number | null;
|
|
29
|
+
total_roast_time: number | null;
|
|
30
|
+
data_source: string | null;
|
|
31
|
+
roast_uuid: string | null;
|
|
32
|
+
temperatures?: TemperatureEntry[];
|
|
33
|
+
events?: RoastEventEntry[];
|
|
34
|
+
}
|
|
35
|
+
export interface TemperatureEntry {
|
|
36
|
+
roast_id: number;
|
|
37
|
+
time_seconds: number;
|
|
38
|
+
bean_temp: number | null;
|
|
39
|
+
environmental_temp: number | null;
|
|
40
|
+
}
|
|
41
|
+
export interface RoastEventEntry {
|
|
42
|
+
roast_id: number;
|
|
43
|
+
time_seconds: number;
|
|
44
|
+
event_type: number | null;
|
|
45
|
+
event_value: string | null;
|
|
46
|
+
}
|
|
47
|
+
export declare const listRoastsSchema: z.ZodObject<{
|
|
48
|
+
coffeeId: z.ZodOptional<z.ZodNumber>;
|
|
49
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
export type ListRoastsInput = z.input<typeof listRoastsSchema>;
|
|
52
|
+
export declare const getRoastSchema: z.ZodObject<{
|
|
53
|
+
id: z.ZodNumber;
|
|
54
|
+
includeTemps: z.ZodOptional<z.ZodBoolean>;
|
|
55
|
+
includeEvents: z.ZodOptional<z.ZodBoolean>;
|
|
56
|
+
}, z.core.$strip>;
|
|
57
|
+
export type GetRoastInput = z.input<typeof getRoastSchema>;
|
|
58
|
+
export declare const createRoastSchema: z.ZodObject<{
|
|
59
|
+
coffeeId: z.ZodNumber;
|
|
60
|
+
batchName: z.ZodOptional<z.ZodString>;
|
|
61
|
+
ozIn: z.ZodOptional<z.ZodNumber>;
|
|
62
|
+
ozOut: z.ZodOptional<z.ZodNumber>;
|
|
63
|
+
roastDate: z.ZodOptional<z.ZodString>;
|
|
64
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
65
|
+
}, z.core.$strip>;
|
|
66
|
+
export type CreateRoastInput = z.input<typeof createRoastSchema>;
|
|
67
|
+
export declare const deleteRoastSchema: z.ZodObject<{
|
|
68
|
+
id: z.ZodNumber;
|
|
69
|
+
}, z.core.$strip>;
|
|
70
|
+
export type DeleteRoastInput = z.input<typeof deleteRoastSchema>;
|
|
71
|
+
/**
|
|
72
|
+
* List roast profiles for a user.
|
|
73
|
+
*/
|
|
74
|
+
export declare function listRoasts(supabase: SupabaseClient, userId: string, opts: ListRoastsInput): Promise<RoastProfile[]>;
|
|
75
|
+
/**
|
|
76
|
+
* Fetch a single roast profile by ID (must belong to userId).
|
|
77
|
+
* Optionally includes temperature curve and roast events.
|
|
78
|
+
*/
|
|
79
|
+
export declare function getRoast(supabase: SupabaseClient, userId: string, id: number, opts?: {
|
|
80
|
+
includeTemps?: boolean;
|
|
81
|
+
includeEvents?: boolean;
|
|
82
|
+
}): Promise<RoastProfile>;
|
|
83
|
+
/**
|
|
84
|
+
* Create a new roast profile (coffeeId must be an inventory item belonging to userId).
|
|
85
|
+
*/
|
|
86
|
+
export declare function createRoast(supabase: SupabaseClient, userId: string, input: CreateRoastInput): Promise<RoastProfile>;
|
|
87
|
+
/**
|
|
88
|
+
* Delete a roast profile (must belong to userId).
|
|
89
|
+
*/
|
|
90
|
+
export declare function deleteRoast(supabase: SupabaseClient, userId: string, id: number): Promise<void>;
|
|
91
|
+
export declare const importRoastSchema: z.ZodObject<{
|
|
92
|
+
fileContent: z.ZodString;
|
|
93
|
+
fileName: z.ZodString;
|
|
94
|
+
coffeeId: z.ZodNumber;
|
|
95
|
+
batchName: z.ZodOptional<z.ZodString>;
|
|
96
|
+
ozIn: z.ZodOptional<z.ZodNumber>;
|
|
97
|
+
roastNotes: z.ZodOptional<z.ZodString>;
|
|
98
|
+
}, z.core.$strip>;
|
|
99
|
+
export type ImportRoastInput = z.input<typeof importRoastSchema>;
|
|
100
|
+
export interface ImportRoastResult extends ArtisanImportResult {
|
|
101
|
+
roast_id: number;
|
|
102
|
+
batch_name: string;
|
|
103
|
+
coffee_name: string;
|
|
104
|
+
coffee_id: number;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Extract the input weight in ounces from an Artisan .alog weight array.
|
|
108
|
+
* Falls back to undefined if weight data is absent or unparseable.
|
|
109
|
+
*
|
|
110
|
+
* @param weight - The `weight` field from ArtisanRoastData: [input, output, unit]
|
|
111
|
+
*/
|
|
112
|
+
export declare function extractOzFromAlog(weight: [number, number, string] | undefined): number | undefined;
|
|
113
|
+
/**
|
|
114
|
+
* Generate a default batch name: "{coffee_name} {YYYY-MM-DD}".
|
|
115
|
+
*/
|
|
116
|
+
export declare function defaultBatchName(coffeeName: string, dateIso: string): string;
|
|
117
|
+
/**
|
|
118
|
+
* Import a roast from an Artisan .alog file in one step:
|
|
119
|
+
* 1. Verify inventory ownership + get coffee name
|
|
120
|
+
* 2. Create a new roast_profile row
|
|
121
|
+
* 3. Run the full Artisan import (temperatures, events, profile metadata)
|
|
122
|
+
* 4. Return the combined result
|
|
123
|
+
*
|
|
124
|
+
* The supabase client must already be authenticated as userId.
|
|
125
|
+
*/
|
|
126
|
+
export declare function importRoastFromFile(supabase: SupabaseClient, userId: string, input: ImportRoastInput): Promise<ImportRoastResult>;
|
|
127
|
+
//# sourceMappingURL=roast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roast.d.ts","sourceRoot":"","sources":["../../src/lib/roast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAI/D,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAClC,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAYD,eAAO,MAAM,gBAAgB;;;iBAG3B,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE/D,eAAO,MAAM,cAAc;;;;iBAIzB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE3D,eAAO,MAAM,iBAAiB;;;;;;;iBAO5B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEjE,eAAO,MAAM,iBAAiB;;iBAE5B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAIjE;;GAEG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,YAAY,EAAE,CAAC,CAczB;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,IAAI,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7D,OAAO,CAAC,YAAY,CAAC,CA0CvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,YAAY,CAAC,CA2DvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAID,eAAO,MAAM,iBAAiB;;;;;;;iBAO5B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEjE,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAC3C,MAAM,GAAG,SAAS,CA0BpB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,iBAAiB,CAAC,CAkF5B"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AuthError, PrvrsError } from './errors.js';
|
|
3
|
+
import { importArtisanData } from './artisan/import.js';
|
|
4
|
+
// ─── Shared select columns ────────────────────────────────────────────────────
|
|
5
|
+
const ROAST_LIST_SELECT = 'roast_id, batch_name, coffee_id, coffee_name, roast_date, oz_in, oz_out, weight_loss_percent, roast_notes, roaster_type, roaster_size, temperature_unit, total_roast_time, development_percent, data_source, last_updated';
|
|
6
|
+
const ROAST_DETAIL_SELECT = 'roast_id, batch_name, coffee_id, coffee_name, roast_date, oz_in, oz_out, weight_loss_percent, roast_notes, roaster_type, total_roast_time, data_source, last_updated';
|
|
7
|
+
// ─── Zod schemas ──────────────────────────────────────────────────────────────
|
|
8
|
+
export const listRoastsSchema = z.object({
|
|
9
|
+
coffeeId: z.number().int().positive().optional(),
|
|
10
|
+
limit: z.number().int().min(1).default(20),
|
|
11
|
+
});
|
|
12
|
+
export const getRoastSchema = z.object({
|
|
13
|
+
id: z.number().int().positive(),
|
|
14
|
+
includeTemps: z.boolean().optional(),
|
|
15
|
+
includeEvents: z.boolean().optional(),
|
|
16
|
+
});
|
|
17
|
+
export const createRoastSchema = z.object({
|
|
18
|
+
coffeeId: z.number().int().positive(),
|
|
19
|
+
batchName: z.string().optional(),
|
|
20
|
+
ozIn: z.number().positive().optional(),
|
|
21
|
+
ozOut: z.number().positive().optional(),
|
|
22
|
+
roastDate: z.string().optional(),
|
|
23
|
+
notes: z.string().optional(),
|
|
24
|
+
});
|
|
25
|
+
export const deleteRoastSchema = z.object({
|
|
26
|
+
id: z.number().int().positive(),
|
|
27
|
+
});
|
|
28
|
+
// ─── Pure lib functions ───────────────────────────────────────────────────────
|
|
29
|
+
/**
|
|
30
|
+
* List roast profiles for a user.
|
|
31
|
+
*/
|
|
32
|
+
export async function listRoasts(supabase, userId, opts) {
|
|
33
|
+
const parsed = listRoastsSchema.parse(opts);
|
|
34
|
+
let query = supabase.from('roast_profiles').select(ROAST_LIST_SELECT).eq('user', userId);
|
|
35
|
+
if (parsed.coffeeId !== undefined) {
|
|
36
|
+
query = query.eq('coffee_id', parsed.coffeeId);
|
|
37
|
+
}
|
|
38
|
+
const { data, error } = await query.order('roast_date', { ascending: false }).limit(parsed.limit);
|
|
39
|
+
if (error)
|
|
40
|
+
throw error;
|
|
41
|
+
return (data ?? []);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Fetch a single roast profile by ID (must belong to userId).
|
|
45
|
+
* Optionally includes temperature curve and roast events.
|
|
46
|
+
*/
|
|
47
|
+
export async function getRoast(supabase, userId, id, opts = {}) {
|
|
48
|
+
getRoastSchema.parse({ id, ...opts });
|
|
49
|
+
const { data: profile, error: profileError } = await supabase
|
|
50
|
+
.from('roast_profiles')
|
|
51
|
+
.select('*')
|
|
52
|
+
.eq('roast_id', id)
|
|
53
|
+
.eq('user', userId)
|
|
54
|
+
.single();
|
|
55
|
+
if (profileError) {
|
|
56
|
+
if (profileError.code === 'PGRST116') {
|
|
57
|
+
throw new AuthError(`Roast profile ${id} not found or does not belong to you.`);
|
|
58
|
+
}
|
|
59
|
+
throw profileError;
|
|
60
|
+
}
|
|
61
|
+
const result = { ...profile };
|
|
62
|
+
if (opts.includeTemps) {
|
|
63
|
+
const { data: temps, error: tempError } = await supabase
|
|
64
|
+
.from('roast_temperatures')
|
|
65
|
+
.select('roast_id, time_seconds, bean_temp, environmental_temp')
|
|
66
|
+
.eq('roast_id', id)
|
|
67
|
+
.order('time_seconds', { ascending: true });
|
|
68
|
+
if (tempError)
|
|
69
|
+
throw tempError;
|
|
70
|
+
result.temperatures = (temps ?? []);
|
|
71
|
+
}
|
|
72
|
+
if (opts.includeEvents) {
|
|
73
|
+
const { data: events, error: eventsError } = await supabase
|
|
74
|
+
.from('roast_events')
|
|
75
|
+
.select('roast_id, time_seconds, event_type, event_value')
|
|
76
|
+
.eq('roast_id', id)
|
|
77
|
+
.order('time_seconds', { ascending: true });
|
|
78
|
+
if (eventsError)
|
|
79
|
+
throw eventsError;
|
|
80
|
+
result.events = (events ?? []);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Create a new roast profile (coffeeId must be an inventory item belonging to userId).
|
|
86
|
+
*/
|
|
87
|
+
export async function createRoast(supabase, userId, input) {
|
|
88
|
+
const parsed = createRoastSchema.parse(input);
|
|
89
|
+
const todayIso = () => new Date().toISOString().slice(0, 10);
|
|
90
|
+
// Verify ownership of the inventory item and get coffee name for default batch name
|
|
91
|
+
const { data: invItem, error: invError } = await supabase
|
|
92
|
+
.from('green_coffee_inv')
|
|
93
|
+
.select('id, coffee_catalog!catalog_id (name)')
|
|
94
|
+
.eq('id', parsed.coffeeId)
|
|
95
|
+
.eq('user', userId)
|
|
96
|
+
.single();
|
|
97
|
+
if (invError || !invItem) {
|
|
98
|
+
throw new AuthError(`Inventory item ${parsed.coffeeId} not found or does not belong to you.`);
|
|
99
|
+
}
|
|
100
|
+
const roastDate = parsed.roastDate ?? todayIso();
|
|
101
|
+
let batchName = parsed.batchName;
|
|
102
|
+
if (!batchName) {
|
|
103
|
+
const catalogRaw = invItem.coffee_catalog;
|
|
104
|
+
const catalog = Array.isArray(catalogRaw) ? (catalogRaw[0] ?? null) : catalogRaw;
|
|
105
|
+
const coffeeName = catalog?.name ?? `Coffee #${parsed.coffeeId}`;
|
|
106
|
+
batchName = `${coffeeName} — ${roastDate}`;
|
|
107
|
+
}
|
|
108
|
+
const insertPayload = {
|
|
109
|
+
user: userId,
|
|
110
|
+
coffee_id: parsed.coffeeId,
|
|
111
|
+
batch_name: batchName,
|
|
112
|
+
roast_date: roastDate,
|
|
113
|
+
};
|
|
114
|
+
if (parsed.ozIn !== undefined)
|
|
115
|
+
insertPayload.oz_in = parsed.ozIn;
|
|
116
|
+
if (parsed.ozOut !== undefined)
|
|
117
|
+
insertPayload.oz_out = parsed.ozOut;
|
|
118
|
+
if (parsed.notes !== undefined)
|
|
119
|
+
insertPayload.roast_notes = parsed.notes;
|
|
120
|
+
const { data: inserted, error: insertError } = await supabase
|
|
121
|
+
.from('roast_profiles')
|
|
122
|
+
.insert(insertPayload)
|
|
123
|
+
.select('roast_id')
|
|
124
|
+
.single();
|
|
125
|
+
if (insertError)
|
|
126
|
+
throw insertError;
|
|
127
|
+
// Re-fetch the full row
|
|
128
|
+
const { data, error } = await supabase
|
|
129
|
+
.from('roast_profiles')
|
|
130
|
+
.select(ROAST_DETAIL_SELECT)
|
|
131
|
+
.eq('roast_id', inserted.roast_id)
|
|
132
|
+
.single();
|
|
133
|
+
if (error)
|
|
134
|
+
throw error;
|
|
135
|
+
return data;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Delete a roast profile (must belong to userId).
|
|
139
|
+
*/
|
|
140
|
+
export async function deleteRoast(supabase, userId, id) {
|
|
141
|
+
deleteRoastSchema.parse({ id });
|
|
142
|
+
if (isNaN(id)) {
|
|
143
|
+
throw new PrvrsError('INVALID_ARGUMENT', `Invalid roast ID: "${id}".`);
|
|
144
|
+
}
|
|
145
|
+
// Verify ownership
|
|
146
|
+
const { data: existing, error: fetchError } = await supabase
|
|
147
|
+
.from('roast_profiles')
|
|
148
|
+
.select('roast_id')
|
|
149
|
+
.eq('roast_id', id)
|
|
150
|
+
.eq('user', userId)
|
|
151
|
+
.single();
|
|
152
|
+
if (fetchError || !existing) {
|
|
153
|
+
throw new AuthError(`Roast profile ${id} not found or does not belong to you.`);
|
|
154
|
+
}
|
|
155
|
+
const { error: deleteError } = await supabase
|
|
156
|
+
.from('roast_profiles')
|
|
157
|
+
.delete()
|
|
158
|
+
.eq('roast_id', id)
|
|
159
|
+
.eq('user', userId);
|
|
160
|
+
if (deleteError)
|
|
161
|
+
throw deleteError;
|
|
162
|
+
}
|
|
163
|
+
// ─── Roast import from .alog file ─────────────────────────────────────────────
|
|
164
|
+
export const importRoastSchema = z.object({
|
|
165
|
+
fileContent: z.string().min(1),
|
|
166
|
+
fileName: z.string().min(1),
|
|
167
|
+
coffeeId: z.number().int().positive(),
|
|
168
|
+
batchName: z.string().optional(),
|
|
169
|
+
ozIn: z.number().positive().optional(),
|
|
170
|
+
roastNotes: z.string().optional(),
|
|
171
|
+
});
|
|
172
|
+
/**
|
|
173
|
+
* Extract the input weight in ounces from an Artisan .alog weight array.
|
|
174
|
+
* Falls back to undefined if weight data is absent or unparseable.
|
|
175
|
+
*
|
|
176
|
+
* @param weight - The `weight` field from ArtisanRoastData: [input, output, unit]
|
|
177
|
+
*/
|
|
178
|
+
export function extractOzFromAlog(weight) {
|
|
179
|
+
if (!weight || !Array.isArray(weight) || weight.length < 3)
|
|
180
|
+
return undefined;
|
|
181
|
+
const [inputWeight, , unit] = weight;
|
|
182
|
+
if (typeof inputWeight !== 'number' || inputWeight <= 0)
|
|
183
|
+
return undefined;
|
|
184
|
+
if (typeof unit !== 'string')
|
|
185
|
+
return undefined;
|
|
186
|
+
const unitLower = unit.toLowerCase();
|
|
187
|
+
if (unitLower === 'g' || unitLower === 'gr' || unitLower === 'gram' || unitLower === 'grams') {
|
|
188
|
+
return inputWeight / 28.3495;
|
|
189
|
+
}
|
|
190
|
+
if (unitLower === 'oz' || unitLower === 'ounce' || unitLower === 'ounces') {
|
|
191
|
+
return inputWeight;
|
|
192
|
+
}
|
|
193
|
+
if (unitLower === 'kg' || unitLower === 'kilogram' || unitLower === 'kilograms') {
|
|
194
|
+
return inputWeight * 35.274;
|
|
195
|
+
}
|
|
196
|
+
if (unitLower === 'lb' ||
|
|
197
|
+
unitLower === 'lbs' ||
|
|
198
|
+
unitLower === 'pound' ||
|
|
199
|
+
unitLower === 'pounds') {
|
|
200
|
+
return inputWeight * 16;
|
|
201
|
+
}
|
|
202
|
+
// Unknown unit — return undefined rather than a wrong number
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Generate a default batch name: "{coffee_name} {YYYY-MM-DD}".
|
|
207
|
+
*/
|
|
208
|
+
export function defaultBatchName(coffeeName, dateIso) {
|
|
209
|
+
return `${coffeeName} ${dateIso}`;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Import a roast from an Artisan .alog file in one step:
|
|
213
|
+
* 1. Verify inventory ownership + get coffee name
|
|
214
|
+
* 2. Create a new roast_profile row
|
|
215
|
+
* 3. Run the full Artisan import (temperatures, events, profile metadata)
|
|
216
|
+
* 4. Return the combined result
|
|
217
|
+
*
|
|
218
|
+
* The supabase client must already be authenticated as userId.
|
|
219
|
+
*/
|
|
220
|
+
export async function importRoastFromFile(supabase, userId, input) {
|
|
221
|
+
const parsed = importRoastSchema.parse(input);
|
|
222
|
+
// 1. Verify ownership and get coffee name
|
|
223
|
+
const { data: invItem, error: invError } = await supabase
|
|
224
|
+
.from('green_coffee_inv')
|
|
225
|
+
.select('id, coffee_catalog!catalog_id (name)')
|
|
226
|
+
.eq('id', parsed.coffeeId)
|
|
227
|
+
.eq('user', userId)
|
|
228
|
+
.single();
|
|
229
|
+
if (invError || !invItem) {
|
|
230
|
+
throw new AuthError(`Inventory item ${parsed.coffeeId} not found or does not belong to you.`);
|
|
231
|
+
}
|
|
232
|
+
const catalogRaw = invItem.coffee_catalog;
|
|
233
|
+
const catalog = Array.isArray(catalogRaw) ? (catalogRaw[0] ?? null) : catalogRaw;
|
|
234
|
+
const coffeeName = catalog?.name ?? `Coffee #${parsed.coffeeId}`;
|
|
235
|
+
// 2. Extract roast date + weight from the .alog if not provided by caller
|
|
236
|
+
let artisanRaw;
|
|
237
|
+
try {
|
|
238
|
+
// Light pre-parse to grab roastdate and weight without a full import cycle.
|
|
239
|
+
// We import parseArtisanFile lazily here to keep imports tidy.
|
|
240
|
+
const { parseArtisanFile } = await import('./artisan/import.js');
|
|
241
|
+
const artisan = await parseArtisanFile(parsed.fileContent, parsed.fileName);
|
|
242
|
+
artisanRaw = artisan;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// Pre-parse failure is non-fatal here; the real import will surface the error
|
|
246
|
+
}
|
|
247
|
+
const roastDate = artisanRaw?.roastdate ?? new Date().toISOString().slice(0, 10);
|
|
248
|
+
let ozIn = parsed.ozIn;
|
|
249
|
+
if (ozIn === undefined && artisanRaw?.weight) {
|
|
250
|
+
ozIn = extractOzFromAlog(artisanRaw.weight);
|
|
251
|
+
}
|
|
252
|
+
const batchName = parsed.batchName ?? defaultBatchName(coffeeName, roastDate);
|
|
253
|
+
// 3. Create roast_profile row
|
|
254
|
+
const insertPayload = {
|
|
255
|
+
user: userId,
|
|
256
|
+
coffee_id: parsed.coffeeId,
|
|
257
|
+
coffee_name: coffeeName,
|
|
258
|
+
batch_name: batchName,
|
|
259
|
+
roast_date: roastDate,
|
|
260
|
+
data_source: 'artisan_import',
|
|
261
|
+
};
|
|
262
|
+
if (ozIn !== undefined)
|
|
263
|
+
insertPayload.oz_in = ozIn;
|
|
264
|
+
if (parsed.roastNotes !== undefined)
|
|
265
|
+
insertPayload.roast_notes = parsed.roastNotes;
|
|
266
|
+
const { data: inserted, error: insertError } = await supabase
|
|
267
|
+
.from('roast_profiles')
|
|
268
|
+
.insert(insertPayload)
|
|
269
|
+
.select('roast_id')
|
|
270
|
+
.single();
|
|
271
|
+
if (insertError)
|
|
272
|
+
throw insertError;
|
|
273
|
+
const roastId = inserted.roast_id;
|
|
274
|
+
// 4. Run the full Artisan import (updates profile metadata + writes temps/events)
|
|
275
|
+
const importResult = await importArtisanData(supabase, roastId, userId, parsed.fileContent, parsed.fileName);
|
|
276
|
+
return {
|
|
277
|
+
...importResult,
|
|
278
|
+
roast_id: roastId,
|
|
279
|
+
batch_name: batchName,
|
|
280
|
+
coffee_name: coffeeName,
|
|
281
|
+
coffee_id: parsed.coffeeId,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=roast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roast.js","sourceRoot":"","sources":["../../src/lib/roast.ts"],"names":[],"mappings":"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"}
|