@hasna/invoices 0.1.3 → 0.1.5

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/cli/index.js CHANGED
@@ -2088,6 +2088,21 @@ function normalizePagination(options, defaults) {
2088
2088
  const offset = Number.isFinite(options.offset) ? Math.max(Math.trunc(options.offset), 0) : 0;
2089
2089
  return { limit, offset };
2090
2090
  }
2091
+ function assertInvoiceStatus(status) {
2092
+ if (!INVOICE_STATUSES.has(status)) {
2093
+ throw new RangeError(`Invalid invoice status: ${String(status)}`);
2094
+ }
2095
+ }
2096
+ function assertPartyKind(kind) {
2097
+ if (!PARTY_KINDS.has(kind)) {
2098
+ throw new RangeError(`Invalid party kind: ${String(kind)}`);
2099
+ }
2100
+ }
2101
+ function assertNonBlankString(value, field) {
2102
+ if (typeof value !== "string" || value.trim().length === 0) {
2103
+ throw new TypeError(`${field} must not be blank`);
2104
+ }
2105
+ }
2091
2106
  function assertSafeCents(value, field) {
2092
2107
  if (!Number.isSafeInteger(value) || value < 0) {
2093
2108
  throw new RangeError(`${field} must be a safe nonnegative integer`);
@@ -2205,14 +2220,27 @@ function mapInvoiceLineRow(row) {
2205
2220
  };
2206
2221
  }
2207
2222
  function createParty(db, input) {
2208
- const id = input.id ?? randomUUID();
2223
+ const id = input.id;
2224
+ const kind = input.kind;
2225
+ const legalName = input.legalName;
2226
+ const email = input.email;
2227
+ const taxId = input.taxId;
2228
+ const country = input.country;
2229
+ const address = input.address;
2230
+ if (id !== undefined) {
2231
+ assertNonBlankString(id, "id");
2232
+ }
2233
+ assertPartyKind(kind);
2234
+ assertNonBlankString(legalName, "legalName");
2235
+ const partyId = id ?? randomUUID();
2209
2236
  db.query(`
2210
2237
  INSERT INTO parties (id, kind, legal_name, email, tax_id, country, address)
2211
2238
  VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
2212
- `).run(id, input.kind, input.legalName, input.email ?? null, input.taxId ?? null, input.country ?? null, input.address ?? null);
2213
- return getPartyById(db, id);
2239
+ `).run(partyId, kind, legalName, email ?? null, taxId ?? null, country ?? null, address ?? null);
2240
+ return getPartyById(db, partyId);
2214
2241
  }
2215
2242
  function getPartyById(db, id) {
2243
+ assertNonBlankString(id, "id");
2216
2244
  const row = db.query("SELECT * FROM parties WHERE id = ?1").get(id);
2217
2245
  return row ? mapPartyRow(row) : null;
2218
2246
  }
@@ -2223,8 +2251,28 @@ function listParties(db, kind) {
2223
2251
  return db.query("SELECT * FROM parties ORDER BY legal_name ASC").all().map(mapPartyRow);
2224
2252
  }
2225
2253
  function createInvoice(db, input) {
2226
- const preparedLines = prepareInvoiceLines(input.lines);
2227
- const invoiceId = input.id ?? randomUUID();
2254
+ const id = input.id;
2255
+ const number = input.number;
2256
+ const issuerId = input.issuerId;
2257
+ const customerId = input.customerId;
2258
+ const currency = input.currency;
2259
+ const notes = input.notes;
2260
+ const issuedAt = input.issuedAt;
2261
+ const dueAt = input.dueAt;
2262
+ const lines = input.lines;
2263
+ if (id !== undefined) {
2264
+ assertNonBlankString(id, "id");
2265
+ }
2266
+ assertNonBlankString(number, "number");
2267
+ assertNonBlankString(issuerId, "issuerId");
2268
+ assertNonBlankString(customerId, "customerId");
2269
+ assertNonBlankString(currency, "currency");
2270
+ assertNonBlankString(issuedAt, "issuedAt");
2271
+ if (dueAt !== undefined) {
2272
+ assertNonBlankString(dueAt, "dueAt");
2273
+ }
2274
+ const preparedLines = prepareInvoiceLines(lines);
2275
+ const invoiceId = id ?? randomUUID();
2228
2276
  let subtotalCents = 0;
2229
2277
  let taxCents = 0;
2230
2278
  for (const line of preparedLines) {
@@ -2241,7 +2289,7 @@ INSERT INTO invoices (
2241
2289
  id, number, issuer_id, customer_id, status, currency, notes, issued_at, due_at, subtotal_cents, tax_cents, total_cents
2242
2290
  )
2243
2291
  VALUES (?1, ?2, ?3, ?4, 'draft', ?5, ?6, ?7, ?8, ?9, ?10, ?11)
2244
- `).run(invoiceId, input.number, input.issuerId, input.customerId, input.currency, input.notes ?? null, input.issuedAt, input.dueAt ?? null, subtotalCents, taxCents, totalCents);
2292
+ `).run(invoiceId, number, issuerId, customerId, currency, notes ?? null, issuedAt, dueAt ?? null, subtotalCents, taxCents, totalCents);
2245
2293
  const insertLine = db.query(`
2246
2294
  INSERT INTO invoice_lines (
2247
2295
  id, invoice_id, position, description, quantity, unit_price_cents, tax_rate_basis_points, line_total_cents
@@ -2267,12 +2315,14 @@ function getInvoiceById(db, id) {
2267
2315
  }
2268
2316
  function listInvoices(db, options = {}) {
2269
2317
  const { limit, offset } = normalizePagination(options, { limit: 50 });
2270
- if (options.status) {
2318
+ if (options.status !== undefined) {
2319
+ assertInvoiceStatus(options.status);
2271
2320
  return db.query("SELECT * FROM invoices WHERE status = ?1 ORDER BY issued_at DESC, created_at DESC LIMIT ?2 OFFSET ?3").all(options.status, limit, offset).map(mapInvoiceRow);
2272
2321
  }
2273
2322
  return db.query("SELECT * FROM invoices ORDER BY issued_at DESC, created_at DESC LIMIT ?1 OFFSET ?2").all(limit, offset).map(mapInvoiceRow);
2274
2323
  }
2275
2324
  function updateInvoiceStatus(db, id, status) {
2325
+ assertInvoiceStatus(status);
2276
2326
  db.query("UPDATE invoices SET status = ?2, updated_at = datetime('now') WHERE id = ?1").run(id, status);
2277
2327
  const row = db.query("SELECT * FROM invoices WHERE id = ?1").get(id);
2278
2328
  return row ? mapInvoiceRow(row) : null;
@@ -2280,7 +2330,8 @@ function updateInvoiceStatus(db, id, status) {
2280
2330
  function searchInvoices(db, query, options = {}) {
2281
2331
  const { limit, offset } = normalizePagination(options, { limit: 20 });
2282
2332
  const escapedPhrase = `"${query.replace(/"/g, '""')}"`;
2283
- if (options.status) {
2333
+ if (options.status !== undefined) {
2334
+ assertInvoiceStatus(options.status);
2284
2335
  return db.query(`
2285
2336
  SELECT i.*
2286
2337
  FROM invoices_fts f
@@ -2302,7 +2353,11 @@ LIMIT ?2
2302
2353
  OFFSET ?3
2303
2354
  `).all(escapedPhrase, limit, offset).map(mapInvoiceRow);
2304
2355
  }
2305
- var init_invoices = () => {};
2356
+ var INVOICE_STATUSES, PARTY_KINDS;
2357
+ var init_invoices = __esm(() => {
2358
+ INVOICE_STATUSES = new Set(["draft", "sent", "partially_paid", "paid", "void", "overdue"]);
2359
+ PARTY_KINDS = new Set(["issuer", "customer"]);
2360
+ });
2306
2361
 
2307
2362
  // src/db/database.ts
2308
2363
  import { mkdirSync } from "fs";
@@ -2471,7 +2526,7 @@ function listAgents(db) {
2471
2526
  }
2472
2527
 
2473
2528
  // src/lib/version.ts
2474
- var VERSION = "0.0.1";
2529
+ var VERSION = "0.1.5";
2475
2530
 
2476
2531
  // src/cli/index.ts
2477
2532
  init_database();
@@ -1 +1 @@
1
- {"version":3,"file":"invoices.d.ts","sourceRoot":"","sources":["../../src/db/invoices.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"invoices.d.ts","sourceRoot":"","sources":["../../src/db/invoices.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // @bun
2
2
  // src/lib/version.ts
3
- var VERSION = "0.0.1";
3
+ var VERSION = "0.1.5";
4
4
  // src/db/database.ts
5
5
  import { mkdirSync } from "fs";
6
6
  import { dirname, join } from "path";
@@ -205,11 +205,28 @@ function migrateDatabase(options = {}) {
205
205
  }
206
206
  // src/db/invoices.ts
207
207
  import { randomUUID } from "crypto";
208
+ var INVOICE_STATUSES = new Set(["draft", "sent", "partially_paid", "paid", "void", "overdue"]);
209
+ var PARTY_KINDS = new Set(["issuer", "customer"]);
208
210
  function normalizePagination(options, defaults) {
209
211
  const limit = Number.isFinite(options.limit) ? Math.max(Math.trunc(options.limit), 0) : defaults.limit;
210
212
  const offset = Number.isFinite(options.offset) ? Math.max(Math.trunc(options.offset), 0) : 0;
211
213
  return { limit, offset };
212
214
  }
215
+ function assertInvoiceStatus(status) {
216
+ if (!INVOICE_STATUSES.has(status)) {
217
+ throw new RangeError(`Invalid invoice status: ${String(status)}`);
218
+ }
219
+ }
220
+ function assertPartyKind(kind) {
221
+ if (!PARTY_KINDS.has(kind)) {
222
+ throw new RangeError(`Invalid party kind: ${String(kind)}`);
223
+ }
224
+ }
225
+ function assertNonBlankString(value, field) {
226
+ if (typeof value !== "string" || value.trim().length === 0) {
227
+ throw new TypeError(`${field} must not be blank`);
228
+ }
229
+ }
213
230
  function assertSafeCents(value, field) {
214
231
  if (!Number.isSafeInteger(value) || value < 0) {
215
232
  throw new RangeError(`${field} must be a safe nonnegative integer`);
@@ -327,14 +344,35 @@ function mapInvoiceLineRow(row) {
327
344
  };
328
345
  }
329
346
  function createParty(db, input) {
330
- const id = input.id ?? randomUUID();
347
+ const id = input.id;
348
+ const kind = input.kind;
349
+ const legalName = input.legalName;
350
+ const email = input.email;
351
+ const taxId = input.taxId;
352
+ const country = input.country;
353
+ const address = input.address;
354
+ if (id !== undefined) {
355
+ assertNonBlankString(id, "id");
356
+ }
357
+ assertPartyKind(kind);
358
+ assertNonBlankString(legalName, "legalName");
359
+ const partyId = id ?? randomUUID();
331
360
  db.query(`
332
361
  INSERT INTO parties (id, kind, legal_name, email, tax_id, country, address)
333
362
  VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
334
- `).run(id, input.kind, input.legalName, input.email ?? null, input.taxId ?? null, input.country ?? null, input.address ?? null);
335
- return getPartyById(db, id);
363
+ `).run(partyId, kind, legalName, email ?? null, taxId ?? null, country ?? null, address ?? null);
364
+ return getPartyById(db, partyId);
336
365
  }
337
366
  function updateParty(db, id, updates) {
367
+ const legalName = updates.legalName;
368
+ const email = updates.email;
369
+ const taxId = updates.taxId;
370
+ const country = updates.country;
371
+ const address = updates.address;
372
+ assertNonBlankString(id, "id");
373
+ if (legalName !== undefined) {
374
+ assertNonBlankString(legalName, "legalName");
375
+ }
338
376
  db.query(`
339
377
  UPDATE parties
340
378
  SET legal_name = COALESCE(?2, legal_name),
@@ -344,10 +382,11 @@ SET legal_name = COALESCE(?2, legal_name),
344
382
  address = COALESCE(?6, address),
345
383
  updated_at = datetime('now')
346
384
  WHERE id = ?1
347
- `).run(id, updates.legalName ?? null, updates.email ?? null, updates.taxId ?? null, updates.country ?? null, updates.address ?? null);
385
+ `).run(id, legalName ?? null, email ?? null, taxId ?? null, country ?? null, address ?? null);
348
386
  return getPartyById(db, id);
349
387
  }
350
388
  function getPartyById(db, id) {
389
+ assertNonBlankString(id, "id");
351
390
  const row = db.query("SELECT * FROM parties WHERE id = ?1").get(id);
352
391
  return row ? mapPartyRow(row) : null;
353
392
  }
@@ -358,8 +397,28 @@ function listParties(db, kind) {
358
397
  return db.query("SELECT * FROM parties ORDER BY legal_name ASC").all().map(mapPartyRow);
359
398
  }
360
399
  function createInvoice(db, input) {
361
- const preparedLines = prepareInvoiceLines(input.lines);
362
- const invoiceId = input.id ?? randomUUID();
400
+ const id = input.id;
401
+ const number = input.number;
402
+ const issuerId = input.issuerId;
403
+ const customerId = input.customerId;
404
+ const currency = input.currency;
405
+ const notes = input.notes;
406
+ const issuedAt = input.issuedAt;
407
+ const dueAt = input.dueAt;
408
+ const lines = input.lines;
409
+ if (id !== undefined) {
410
+ assertNonBlankString(id, "id");
411
+ }
412
+ assertNonBlankString(number, "number");
413
+ assertNonBlankString(issuerId, "issuerId");
414
+ assertNonBlankString(customerId, "customerId");
415
+ assertNonBlankString(currency, "currency");
416
+ assertNonBlankString(issuedAt, "issuedAt");
417
+ if (dueAt !== undefined) {
418
+ assertNonBlankString(dueAt, "dueAt");
419
+ }
420
+ const preparedLines = prepareInvoiceLines(lines);
421
+ const invoiceId = id ?? randomUUID();
363
422
  let subtotalCents = 0;
364
423
  let taxCents = 0;
365
424
  for (const line of preparedLines) {
@@ -376,7 +435,7 @@ INSERT INTO invoices (
376
435
  id, number, issuer_id, customer_id, status, currency, notes, issued_at, due_at, subtotal_cents, tax_cents, total_cents
377
436
  )
378
437
  VALUES (?1, ?2, ?3, ?4, 'draft', ?5, ?6, ?7, ?8, ?9, ?10, ?11)
379
- `).run(invoiceId, input.number, input.issuerId, input.customerId, input.currency, input.notes ?? null, input.issuedAt, input.dueAt ?? null, subtotalCents, taxCents, totalCents);
438
+ `).run(invoiceId, number, issuerId, customerId, currency, notes ?? null, issuedAt, dueAt ?? null, subtotalCents, taxCents, totalCents);
380
439
  const insertLine = db.query(`
381
440
  INSERT INTO invoice_lines (
382
441
  id, invoice_id, position, description, quantity, unit_price_cents, tax_rate_basis_points, line_total_cents
@@ -402,12 +461,14 @@ function getInvoiceById(db, id) {
402
461
  }
403
462
  function listInvoices(db, options = {}) {
404
463
  const { limit, offset } = normalizePagination(options, { limit: 50 });
405
- if (options.status) {
464
+ if (options.status !== undefined) {
465
+ assertInvoiceStatus(options.status);
406
466
  return db.query("SELECT * FROM invoices WHERE status = ?1 ORDER BY issued_at DESC, created_at DESC LIMIT ?2 OFFSET ?3").all(options.status, limit, offset).map(mapInvoiceRow);
407
467
  }
408
468
  return db.query("SELECT * FROM invoices ORDER BY issued_at DESC, created_at DESC LIMIT ?1 OFFSET ?2").all(limit, offset).map(mapInvoiceRow);
409
469
  }
410
470
  function updateInvoiceStatus(db, id, status) {
471
+ assertInvoiceStatus(status);
411
472
  db.query("UPDATE invoices SET status = ?2, updated_at = datetime('now') WHERE id = ?1").run(id, status);
412
473
  const row = db.query("SELECT * FROM invoices WHERE id = ?1").get(id);
413
474
  return row ? mapInvoiceRow(row) : null;
@@ -432,7 +493,8 @@ FROM invoices i;
432
493
  function searchInvoices(db, query, options = {}) {
433
494
  const { limit, offset } = normalizePagination(options, { limit: 20 });
434
495
  const escapedPhrase = `"${query.replace(/"/g, '""')}"`;
435
- if (options.status) {
496
+ if (options.status !== undefined) {
497
+ assertInvoiceStatus(options.status);
436
498
  return db.query(`
437
499
  SELECT i.*
438
500
  FROM invoices_fts f
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.0.1";
1
+ export declare const VERSION = "0.1.5";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/mcp/index.js CHANGED
@@ -4201,11 +4201,28 @@ function migrateDatabase(options = {}) {
4201
4201
 
4202
4202
  // src/db/invoices.ts
4203
4203
  import { randomUUID } from "crypto";
4204
+ var INVOICE_STATUSES = new Set(["draft", "sent", "partially_paid", "paid", "void", "overdue"]);
4205
+ var PARTY_KINDS = new Set(["issuer", "customer"]);
4204
4206
  function normalizePagination(options, defaults) {
4205
4207
  const limit = Number.isFinite(options.limit) ? Math.max(Math.trunc(options.limit), 0) : defaults.limit;
4206
4208
  const offset = Number.isFinite(options.offset) ? Math.max(Math.trunc(options.offset), 0) : 0;
4207
4209
  return { limit, offset };
4208
4210
  }
4211
+ function assertInvoiceStatus(status) {
4212
+ if (!INVOICE_STATUSES.has(status)) {
4213
+ throw new RangeError(`Invalid invoice status: ${String(status)}`);
4214
+ }
4215
+ }
4216
+ function assertPartyKind(kind) {
4217
+ if (!PARTY_KINDS.has(kind)) {
4218
+ throw new RangeError(`Invalid party kind: ${String(kind)}`);
4219
+ }
4220
+ }
4221
+ function assertNonBlankString(value, field) {
4222
+ if (typeof value !== "string" || value.trim().length === 0) {
4223
+ throw new TypeError(`${field} must not be blank`);
4224
+ }
4225
+ }
4209
4226
  function assertSafeCents(value, field) {
4210
4227
  if (!Number.isSafeInteger(value) || value < 0) {
4211
4228
  throw new RangeError(`${field} must be a safe nonnegative integer`);
@@ -4323,14 +4340,27 @@ function mapInvoiceLineRow(row) {
4323
4340
  };
4324
4341
  }
4325
4342
  function createParty(db, input) {
4326
- const id = input.id ?? randomUUID();
4343
+ const id = input.id;
4344
+ const kind = input.kind;
4345
+ const legalName = input.legalName;
4346
+ const email = input.email;
4347
+ const taxId = input.taxId;
4348
+ const country = input.country;
4349
+ const address = input.address;
4350
+ if (id !== undefined) {
4351
+ assertNonBlankString(id, "id");
4352
+ }
4353
+ assertPartyKind(kind);
4354
+ assertNonBlankString(legalName, "legalName");
4355
+ const partyId = id ?? randomUUID();
4327
4356
  db.query(`
4328
4357
  INSERT INTO parties (id, kind, legal_name, email, tax_id, country, address)
4329
4358
  VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
4330
- `).run(id, input.kind, input.legalName, input.email ?? null, input.taxId ?? null, input.country ?? null, input.address ?? null);
4331
- return getPartyById(db, id);
4359
+ `).run(partyId, kind, legalName, email ?? null, taxId ?? null, country ?? null, address ?? null);
4360
+ return getPartyById(db, partyId);
4332
4361
  }
4333
4362
  function getPartyById(db, id) {
4363
+ assertNonBlankString(id, "id");
4334
4364
  const row = db.query("SELECT * FROM parties WHERE id = ?1").get(id);
4335
4365
  return row ? mapPartyRow(row) : null;
4336
4366
  }
@@ -4341,8 +4371,28 @@ function listParties(db, kind) {
4341
4371
  return db.query("SELECT * FROM parties ORDER BY legal_name ASC").all().map(mapPartyRow);
4342
4372
  }
4343
4373
  function createInvoice(db, input) {
4344
- const preparedLines = prepareInvoiceLines(input.lines);
4345
- const invoiceId = input.id ?? randomUUID();
4374
+ const id = input.id;
4375
+ const number = input.number;
4376
+ const issuerId = input.issuerId;
4377
+ const customerId = input.customerId;
4378
+ const currency = input.currency;
4379
+ const notes = input.notes;
4380
+ const issuedAt = input.issuedAt;
4381
+ const dueAt = input.dueAt;
4382
+ const lines = input.lines;
4383
+ if (id !== undefined) {
4384
+ assertNonBlankString(id, "id");
4385
+ }
4386
+ assertNonBlankString(number, "number");
4387
+ assertNonBlankString(issuerId, "issuerId");
4388
+ assertNonBlankString(customerId, "customerId");
4389
+ assertNonBlankString(currency, "currency");
4390
+ assertNonBlankString(issuedAt, "issuedAt");
4391
+ if (dueAt !== undefined) {
4392
+ assertNonBlankString(dueAt, "dueAt");
4393
+ }
4394
+ const preparedLines = prepareInvoiceLines(lines);
4395
+ const invoiceId = id ?? randomUUID();
4346
4396
  let subtotalCents = 0;
4347
4397
  let taxCents = 0;
4348
4398
  for (const line of preparedLines) {
@@ -4359,7 +4409,7 @@ INSERT INTO invoices (
4359
4409
  id, number, issuer_id, customer_id, status, currency, notes, issued_at, due_at, subtotal_cents, tax_cents, total_cents
4360
4410
  )
4361
4411
  VALUES (?1, ?2, ?3, ?4, 'draft', ?5, ?6, ?7, ?8, ?9, ?10, ?11)
4362
- `).run(invoiceId, input.number, input.issuerId, input.customerId, input.currency, input.notes ?? null, input.issuedAt, input.dueAt ?? null, subtotalCents, taxCents, totalCents);
4412
+ `).run(invoiceId, number, issuerId, customerId, currency, notes ?? null, issuedAt, dueAt ?? null, subtotalCents, taxCents, totalCents);
4363
4413
  const insertLine = db.query(`
4364
4414
  INSERT INTO invoice_lines (
4365
4415
  id, invoice_id, position, description, quantity, unit_price_cents, tax_rate_basis_points, line_total_cents
@@ -4385,12 +4435,14 @@ function getInvoiceById(db, id) {
4385
4435
  }
4386
4436
  function listInvoices(db, options = {}) {
4387
4437
  const { limit, offset } = normalizePagination(options, { limit: 50 });
4388
- if (options.status) {
4438
+ if (options.status !== undefined) {
4439
+ assertInvoiceStatus(options.status);
4389
4440
  return db.query("SELECT * FROM invoices WHERE status = ?1 ORDER BY issued_at DESC, created_at DESC LIMIT ?2 OFFSET ?3").all(options.status, limit, offset).map(mapInvoiceRow);
4390
4441
  }
4391
4442
  return db.query("SELECT * FROM invoices ORDER BY issued_at DESC, created_at DESC LIMIT ?1 OFFSET ?2").all(limit, offset).map(mapInvoiceRow);
4392
4443
  }
4393
4444
  function updateInvoiceStatus(db, id, status) {
4445
+ assertInvoiceStatus(status);
4394
4446
  db.query("UPDATE invoices SET status = ?2, updated_at = datetime('now') WHERE id = ?1").run(id, status);
4395
4447
  const row = db.query("SELECT * FROM invoices WHERE id = ?1").get(id);
4396
4448
  return row ? mapInvoiceRow(row) : null;
@@ -4402,7 +4454,8 @@ function deleteInvoice(db, id) {
4402
4454
  function searchInvoices(db, query, options = {}) {
4403
4455
  const { limit, offset } = normalizePagination(options, { limit: 20 });
4404
4456
  const escapedPhrase = `"${query.replace(/"/g, '""')}"`;
4405
- if (options.status) {
4457
+ if (options.status !== undefined) {
4458
+ assertInvoiceStatus(options.status);
4406
4459
  return db.query(`
4407
4460
  SELECT i.*
4408
4461
  FROM invoices_fts f
@@ -4009,11 +4009,28 @@ function openInvoiceDatabase(options = {}) {
4009
4009
 
4010
4010
  // src/db/invoices.ts
4011
4011
  import { randomUUID } from "crypto";
4012
+ var INVOICE_STATUSES = new Set(["draft", "sent", "partially_paid", "paid", "void", "overdue"]);
4013
+ var PARTY_KINDS = new Set(["issuer", "customer"]);
4012
4014
  function normalizePagination(options, defaults) {
4013
4015
  const limit = Number.isFinite(options.limit) ? Math.max(Math.trunc(options.limit), 0) : defaults.limit;
4014
4016
  const offset = Number.isFinite(options.offset) ? Math.max(Math.trunc(options.offset), 0) : 0;
4015
4017
  return { limit, offset };
4016
4018
  }
4019
+ function assertInvoiceStatus(status) {
4020
+ if (!INVOICE_STATUSES.has(status)) {
4021
+ throw new RangeError(`Invalid invoice status: ${String(status)}`);
4022
+ }
4023
+ }
4024
+ function assertPartyKind(kind) {
4025
+ if (!PARTY_KINDS.has(kind)) {
4026
+ throw new RangeError(`Invalid party kind: ${String(kind)}`);
4027
+ }
4028
+ }
4029
+ function assertNonBlankString(value, field) {
4030
+ if (typeof value !== "string" || value.trim().length === 0) {
4031
+ throw new TypeError(`${field} must not be blank`);
4032
+ }
4033
+ }
4017
4034
  function assertSafeCents(value, field) {
4018
4035
  if (!Number.isSafeInteger(value) || value < 0) {
4019
4036
  throw new RangeError(`${field} must be a safe nonnegative integer`);
@@ -4131,14 +4148,27 @@ function mapInvoiceLineRow(row) {
4131
4148
  };
4132
4149
  }
4133
4150
  function createParty(db, input) {
4134
- const id = input.id ?? randomUUID();
4151
+ const id = input.id;
4152
+ const kind = input.kind;
4153
+ const legalName = input.legalName;
4154
+ const email = input.email;
4155
+ const taxId = input.taxId;
4156
+ const country = input.country;
4157
+ const address = input.address;
4158
+ if (id !== undefined) {
4159
+ assertNonBlankString(id, "id");
4160
+ }
4161
+ assertPartyKind(kind);
4162
+ assertNonBlankString(legalName, "legalName");
4163
+ const partyId = id ?? randomUUID();
4135
4164
  db.query(`
4136
4165
  INSERT INTO parties (id, kind, legal_name, email, tax_id, country, address)
4137
4166
  VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
4138
- `).run(id, input.kind, input.legalName, input.email ?? null, input.taxId ?? null, input.country ?? null, input.address ?? null);
4139
- return getPartyById(db, id);
4167
+ `).run(partyId, kind, legalName, email ?? null, taxId ?? null, country ?? null, address ?? null);
4168
+ return getPartyById(db, partyId);
4140
4169
  }
4141
4170
  function getPartyById(db, id) {
4171
+ assertNonBlankString(id, "id");
4142
4172
  const row = db.query("SELECT * FROM parties WHERE id = ?1").get(id);
4143
4173
  return row ? mapPartyRow(row) : null;
4144
4174
  }
@@ -4149,8 +4179,28 @@ function listParties(db, kind) {
4149
4179
  return db.query("SELECT * FROM parties ORDER BY legal_name ASC").all().map(mapPartyRow);
4150
4180
  }
4151
4181
  function createInvoice(db, input) {
4152
- const preparedLines = prepareInvoiceLines(input.lines);
4153
- const invoiceId = input.id ?? randomUUID();
4182
+ const id = input.id;
4183
+ const number = input.number;
4184
+ const issuerId = input.issuerId;
4185
+ const customerId = input.customerId;
4186
+ const currency = input.currency;
4187
+ const notes = input.notes;
4188
+ const issuedAt = input.issuedAt;
4189
+ const dueAt = input.dueAt;
4190
+ const lines = input.lines;
4191
+ if (id !== undefined) {
4192
+ assertNonBlankString(id, "id");
4193
+ }
4194
+ assertNonBlankString(number, "number");
4195
+ assertNonBlankString(issuerId, "issuerId");
4196
+ assertNonBlankString(customerId, "customerId");
4197
+ assertNonBlankString(currency, "currency");
4198
+ assertNonBlankString(issuedAt, "issuedAt");
4199
+ if (dueAt !== undefined) {
4200
+ assertNonBlankString(dueAt, "dueAt");
4201
+ }
4202
+ const preparedLines = prepareInvoiceLines(lines);
4203
+ const invoiceId = id ?? randomUUID();
4154
4204
  let subtotalCents = 0;
4155
4205
  let taxCents = 0;
4156
4206
  for (const line of preparedLines) {
@@ -4167,7 +4217,7 @@ INSERT INTO invoices (
4167
4217
  id, number, issuer_id, customer_id, status, currency, notes, issued_at, due_at, subtotal_cents, tax_cents, total_cents
4168
4218
  )
4169
4219
  VALUES (?1, ?2, ?3, ?4, 'draft', ?5, ?6, ?7, ?8, ?9, ?10, ?11)
4170
- `).run(invoiceId, input.number, input.issuerId, input.customerId, input.currency, input.notes ?? null, input.issuedAt, input.dueAt ?? null, subtotalCents, taxCents, totalCents);
4220
+ `).run(invoiceId, number, issuerId, customerId, currency, notes ?? null, issuedAt, dueAt ?? null, subtotalCents, taxCents, totalCents);
4171
4221
  const insertLine = db.query(`
4172
4222
  INSERT INTO invoice_lines (
4173
4223
  id, invoice_id, position, description, quantity, unit_price_cents, tax_rate_basis_points, line_total_cents
@@ -4193,12 +4243,14 @@ function getInvoiceById(db, id) {
4193
4243
  }
4194
4244
  function listInvoices(db, options = {}) {
4195
4245
  const { limit, offset } = normalizePagination(options, { limit: 50 });
4196
- if (options.status) {
4246
+ if (options.status !== undefined) {
4247
+ assertInvoiceStatus(options.status);
4197
4248
  return db.query("SELECT * FROM invoices WHERE status = ?1 ORDER BY issued_at DESC, created_at DESC LIMIT ?2 OFFSET ?3").all(options.status, limit, offset).map(mapInvoiceRow);
4198
4249
  }
4199
4250
  return db.query("SELECT * FROM invoices ORDER BY issued_at DESC, created_at DESC LIMIT ?1 OFFSET ?2").all(limit, offset).map(mapInvoiceRow);
4200
4251
  }
4201
4252
  function updateInvoiceStatus(db, id, status) {
4253
+ assertInvoiceStatus(status);
4202
4254
  db.query("UPDATE invoices SET status = ?2, updated_at = datetime('now') WHERE id = ?1").run(id, status);
4203
4255
  const row = db.query("SELECT * FROM invoices WHERE id = ?1").get(id);
4204
4256
  return row ? mapInvoiceRow(row) : null;
@@ -4206,7 +4258,8 @@ function updateInvoiceStatus(db, id, status) {
4206
4258
  function searchInvoices(db, query, options = {}) {
4207
4259
  const { limit, offset } = normalizePagination(options, { limit: 20 });
4208
4260
  const escapedPhrase = `"${query.replace(/"/g, '""')}"`;
4209
- if (options.status) {
4261
+ if (options.status !== undefined) {
4262
+ assertInvoiceStatus(options.status);
4210
4263
  return db.query(`
4211
4264
  SELECT i.*
4212
4265
  FROM invoices_fts f
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/invoices",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Invoice generation and management for AI agents - CLI + MCP server + REST API + dashboard + SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",