@stithy/pod 0.1.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.
@@ -0,0 +1,84 @@
1
+ import { ProviderUnsupportedError } from "./types.js";
2
+ const PRINTFUL_BASE = "https://api.printful.com";
3
+ /**
4
+ * Printful adapter — read paths only in v0.1 (list_products, list_orders, ping).
5
+ * Pricing updates and pause/unpause require Printful's per-variant sync flow that
6
+ * needs more careful handling. Returns ProviderUnsupportedError for those ops so
7
+ * the caller can fall back gracefully.
8
+ */
9
+ export class PrintfulProvider {
10
+ apiKey;
11
+ id = "printful";
12
+ displayName = "Printful";
13
+ capabilities = {
14
+ list_products: true,
15
+ update_pricing: false,
16
+ list_orders: true,
17
+ pause_listing: false,
18
+ stock_query: false,
19
+ create_product: false,
20
+ };
21
+ constructor(apiKey) {
22
+ this.apiKey = apiKey;
23
+ }
24
+ async req(path, init = {}) {
25
+ const res = await fetch(`${PRINTFUL_BASE}${path}`, {
26
+ ...init,
27
+ headers: {
28
+ Authorization: `Bearer ${this.apiKey}`,
29
+ "Content-Type": "application/json",
30
+ ...(init.headers ?? {}),
31
+ },
32
+ });
33
+ if (!res.ok) {
34
+ const body = await res.text().catch(() => "");
35
+ throw new Error(`Printful ${res.status} on ${path}: ${body.slice(0, 300)}`);
36
+ }
37
+ return (await res.json());
38
+ }
39
+ async ping() {
40
+ try {
41
+ await this.req(`/stores`);
42
+ return { ok: true };
43
+ }
44
+ catch (e) {
45
+ return { ok: false, reason: e instanceof Error ? e.message : String(e) };
46
+ }
47
+ }
48
+ async listProducts(opts) {
49
+ const limit = Math.min(opts?.limit ?? 50, 100);
50
+ const data = await this.req(`/store/products?limit=${limit}`);
51
+ return data.result.map((p) => ({
52
+ external_id: String(p.id),
53
+ title: p.name,
54
+ price_cents: null,
55
+ cost_cents: null,
56
+ stock: null,
57
+ status: p.is_ignored ? "ignored" : "active",
58
+ }));
59
+ }
60
+ updatePricing() {
61
+ throw new ProviderUnsupportedError(this.id, "update_pricing");
62
+ }
63
+ pauseListing() {
64
+ throw new ProviderUnsupportedError(this.id, "pause_listing");
65
+ }
66
+ unpauseListing() {
67
+ throw new ProviderUnsupportedError(this.id, "pause_listing");
68
+ }
69
+ async listOrders(opts) {
70
+ const limit = Math.min(opts?.limit ?? 50, 100);
71
+ const data = await this.req(`/orders?limit=${limit}`);
72
+ return data.result.map((o) => {
73
+ const totalNum = o.retail_costs?.total ? Math.round(parseFloat(o.retail_costs.total) * 100) : null;
74
+ return {
75
+ external_id: String(o.id),
76
+ status: o.status,
77
+ total_cents: Number.isFinite(totalNum) ? totalNum : null,
78
+ currency: o.retail_costs?.currency ?? "USD",
79
+ placed_at: o.created ? o.created * 1000 : null,
80
+ };
81
+ });
82
+ }
83
+ }
84
+ //# sourceMappingURL=printful.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"printful.js","sourceRoot":"","sources":["../../src/providers/printful.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { Provider, ProviderCapabilities, ProductSummary, OrderSummary } from "./types.js";
2
+ export declare class PrintifyProvider implements Provider {
3
+ private readonly apiKey;
4
+ private readonly shopId;
5
+ readonly id = "printify";
6
+ readonly displayName = "Printify";
7
+ readonly capabilities: ProviderCapabilities;
8
+ constructor(apiKey: string, shopId: string);
9
+ private req;
10
+ ping(): Promise<{
11
+ ok: boolean;
12
+ reason?: string;
13
+ }>;
14
+ listProducts(opts?: {
15
+ limit?: number;
16
+ }): Promise<ProductSummary[]>;
17
+ updatePricing(externalId: string, priceCents: number): Promise<void>;
18
+ pauseListing(externalId: string): Promise<void>;
19
+ unpauseListing(externalId: string): Promise<void>;
20
+ listOrders(opts?: {
21
+ limit?: number;
22
+ }): Promise<OrderSummary[]>;
23
+ }
@@ -0,0 +1,100 @@
1
+ const PRINTIFY_BASE = "https://api.printify.com/v1";
2
+ export class PrintifyProvider {
3
+ apiKey;
4
+ shopId;
5
+ id = "printify";
6
+ displayName = "Printify";
7
+ capabilities = {
8
+ list_products: true,
9
+ update_pricing: true,
10
+ list_orders: true,
11
+ pause_listing: true,
12
+ stock_query: true,
13
+ create_product: false, // deferred — needs upload + variants flow
14
+ };
15
+ constructor(apiKey, shopId) {
16
+ this.apiKey = apiKey;
17
+ this.shopId = shopId;
18
+ }
19
+ async req(path, init = {}) {
20
+ const res = await fetch(`${PRINTIFY_BASE}${path}`, {
21
+ ...init,
22
+ headers: {
23
+ Authorization: `Bearer ${this.apiKey}`,
24
+ "Content-Type": "application/json",
25
+ ...(init.headers ?? {}),
26
+ },
27
+ });
28
+ if (!res.ok) {
29
+ const body = await res.text().catch(() => "");
30
+ throw new Error(`Printify ${res.status} on ${path}: ${body.slice(0, 300)}`);
31
+ }
32
+ return (await res.json());
33
+ }
34
+ async ping() {
35
+ try {
36
+ await this.req(`/shops.json`);
37
+ return { ok: true };
38
+ }
39
+ catch (e) {
40
+ return { ok: false, reason: e instanceof Error ? e.message : String(e) };
41
+ }
42
+ }
43
+ async listProducts(opts) {
44
+ const limit = Math.min(opts?.limit ?? 50, 100);
45
+ const data = await this.req(`/shops/${this.shopId}/products.json?limit=${limit}`);
46
+ return data.data.map((p) => {
47
+ const enabled = p.variants.filter((v) => v.is_enabled);
48
+ const minPrice = enabled.length
49
+ ? Math.min(...enabled.map((v) => v.price))
50
+ : null;
51
+ const minCost = enabled.length && enabled[0].cost !== undefined
52
+ ? Math.min(...enabled.map((v) => v.cost ?? 0))
53
+ : null;
54
+ const totalStock = enabled.reduce((acc, v) => (v.quantity !== undefined ? acc + v.quantity : acc), 0);
55
+ return {
56
+ external_id: p.id,
57
+ title: p.title,
58
+ price_cents: minPrice,
59
+ cost_cents: minCost,
60
+ stock: totalStock || null,
61
+ status: p.visible ? "active" : "hidden",
62
+ };
63
+ });
64
+ }
65
+ async updatePricing(externalId, priceCents) {
66
+ const product = await this.req(`/shops/${this.shopId}/products/${externalId}.json`);
67
+ const variants = product.variants.map((v) => ({
68
+ id: v.id,
69
+ price: priceCents,
70
+ is_enabled: v.is_enabled,
71
+ }));
72
+ await this.req(`/shops/${this.shopId}/products/${externalId}.json`, {
73
+ method: "PUT",
74
+ body: JSON.stringify({ variants }),
75
+ });
76
+ }
77
+ async pauseListing(externalId) {
78
+ await this.req(`/shops/${this.shopId}/products/${externalId}/unpublish.json`, {
79
+ method: "POST",
80
+ });
81
+ }
82
+ async unpauseListing(externalId) {
83
+ await this.req(`/shops/${this.shopId}/products/${externalId}/publish.json`, {
84
+ method: "POST",
85
+ body: JSON.stringify({ title: true, description: true, images: true, variants: true, tags: true }),
86
+ });
87
+ }
88
+ async listOrders(opts) {
89
+ const limit = Math.min(opts?.limit ?? 50, 100);
90
+ const data = await this.req(`/shops/${this.shopId}/orders.json?limit=${limit}`);
91
+ return data.data.map((o) => ({
92
+ external_id: o.id,
93
+ status: o.status,
94
+ total_cents: o.total_price,
95
+ currency: o.currency ?? "USD",
96
+ placed_at: Date.parse(o.created_at) || null,
97
+ }));
98
+ }
99
+ }
100
+ //# sourceMappingURL=printify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"printify.js","sourceRoot":"","sources":["../../src/providers/printify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type Database from "better-sqlite3";
2
+ import { type ProviderId } from "../db.js";
3
+ import { type Provider } from "./types.js";
4
+ export interface ProviderConfig {
5
+ id: ProviderId;
6
+ enabled: boolean;
7
+ shop_id: string | null;
8
+ configured_at: number;
9
+ }
10
+ export declare class ProviderRegistry {
11
+ private readonly db;
12
+ private readonly masterKey;
13
+ constructor(db: Database.Database, masterKey: Buffer);
14
+ configure(input: {
15
+ id: ProviderId;
16
+ api_key: string;
17
+ shop_id?: string;
18
+ }): ProviderConfig;
19
+ remove(id: ProviderId): boolean;
20
+ setEnabled(id: ProviderId, enabled: boolean): void;
21
+ list(): ProviderConfig[];
22
+ /** Returns an instantiated Provider for the given id, decrypting credentials. */
23
+ load(id: ProviderId): Provider;
24
+ /** Load all enabled providers; ignore configuration errors for absent ones. */
25
+ loadAll(): Provider[];
26
+ }
@@ -0,0 +1,94 @@
1
+ import { decryptString, encryptString } from "../crypto.js";
2
+ import { PROVIDER_IDS } from "../db.js";
3
+ import { ProviderNotConfiguredError, } from "./types.js";
4
+ import { PrintifyProvider } from "./printify.js";
5
+ import { PrintfulProvider } from "./printful.js";
6
+ import { GelatoProvider } from "./gelato.js";
7
+ export class ProviderRegistry {
8
+ db;
9
+ masterKey;
10
+ constructor(db, masterKey) {
11
+ this.db = db;
12
+ this.masterKey = masterKey;
13
+ }
14
+ configure(input) {
15
+ if (!PROVIDER_IDS.includes(input.id)) {
16
+ throw new Error(`unknown provider '${input.id}'. Valid: ${PROVIDER_IDS.join(", ")}`);
17
+ }
18
+ if (!input.api_key || input.api_key.length < 10) {
19
+ throw new Error("api_key looks invalid (too short)");
20
+ }
21
+ const enc = encryptString(input.api_key, this.masterKey);
22
+ const now = Date.now();
23
+ this.db
24
+ .prepare(`INSERT INTO providers (id, enabled, api_key_enc, shop_id, meta, created_at, updated_at)
25
+ VALUES (?, 1, ?, ?, '{}', ?, ?)
26
+ ON CONFLICT(id) DO UPDATE SET api_key_enc = excluded.api_key_enc, shop_id = excluded.shop_id, updated_at = excluded.updated_at, enabled = 1`)
27
+ .run(input.id, enc, input.shop_id ?? null, now, now);
28
+ return {
29
+ id: input.id,
30
+ enabled: true,
31
+ shop_id: input.shop_id ?? null,
32
+ configured_at: now,
33
+ };
34
+ }
35
+ remove(id) {
36
+ const r = this.db.prepare(`DELETE FROM providers WHERE id = ?`).run(id);
37
+ return r.changes > 0;
38
+ }
39
+ setEnabled(id, enabled) {
40
+ this.db
41
+ .prepare(`UPDATE providers SET enabled = ?, updated_at = ? WHERE id = ?`)
42
+ .run(enabled ? 1 : 0, Date.now(), id);
43
+ }
44
+ list() {
45
+ const rows = this.db.prepare(`SELECT * FROM providers`).all();
46
+ return rows.map((r) => ({
47
+ id: r.id,
48
+ enabled: !!r.enabled,
49
+ shop_id: r.shop_id,
50
+ configured_at: r.created_at,
51
+ }));
52
+ }
53
+ /** Returns an instantiated Provider for the given id, decrypting credentials. */
54
+ load(id) {
55
+ const row = this.db
56
+ .prepare(`SELECT * FROM providers WHERE id = ? AND enabled = 1`)
57
+ .get(id);
58
+ if (!row)
59
+ throw new ProviderNotConfiguredError(id);
60
+ const apiKey = decryptString(row.api_key_enc, this.masterKey);
61
+ switch (id) {
62
+ case "printify": {
63
+ const shopId = row.shop_id;
64
+ if (!shopId)
65
+ throw new Error("printify requires shop_id at configure time");
66
+ return new PrintifyProvider(apiKey, shopId);
67
+ }
68
+ case "printful":
69
+ return new PrintfulProvider(apiKey);
70
+ case "gelato": {
71
+ const storeId = row.shop_id;
72
+ if (!storeId)
73
+ throw new Error("gelato requires shop_id (store id) at configure time");
74
+ return new GelatoProvider(apiKey, storeId);
75
+ }
76
+ }
77
+ }
78
+ /** Load all enabled providers; ignore configuration errors for absent ones. */
79
+ loadAll() {
80
+ const out = [];
81
+ for (const cfg of this.list()) {
82
+ if (!cfg.enabled)
83
+ continue;
84
+ try {
85
+ out.push(this.load(cfg.id));
86
+ }
87
+ catch {
88
+ // skip
89
+ }
90
+ }
91
+ return out;
92
+ }
93
+ }
94
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Provider adapter interface.
3
+ *
4
+ * Each POD platform (Printify, Printful, Gelato) implements this interface.
5
+ * The server depends only on this contract — providers are swappable, and one
6
+ * being yanked (Printify ToS, API terminations) doesn't break the others.
7
+ *
8
+ * Tools that fan out across providers iterate the registry and call these
9
+ * methods; per-provider failures are reported but don't abort the whole call.
10
+ */
11
+ export interface ProductSummary {
12
+ external_id: string;
13
+ title: string;
14
+ price_cents: number | null;
15
+ cost_cents: number | null;
16
+ stock: number | null;
17
+ status: string | null;
18
+ }
19
+ export interface OrderSummary {
20
+ external_id: string;
21
+ status: string | null;
22
+ total_cents: number | null;
23
+ currency: string | null;
24
+ placed_at: number | null;
25
+ }
26
+ export interface ProviderCapabilities {
27
+ list_products: boolean;
28
+ update_pricing: boolean;
29
+ list_orders: boolean;
30
+ pause_listing: boolean;
31
+ stock_query: boolean;
32
+ create_product: boolean;
33
+ }
34
+ export interface Provider {
35
+ readonly id: string;
36
+ readonly displayName: string;
37
+ readonly capabilities: ProviderCapabilities;
38
+ /** Validates the provider is reachable and credentials are accepted. */
39
+ ping(): Promise<{
40
+ ok: boolean;
41
+ reason?: string;
42
+ }>;
43
+ listProducts(opts?: {
44
+ limit?: number;
45
+ }): Promise<ProductSummary[]>;
46
+ updatePricing(externalId: string, priceCents: number): Promise<void>;
47
+ pauseListing(externalId: string): Promise<void>;
48
+ unpauseListing(externalId: string): Promise<void>;
49
+ listOrders(opts?: {
50
+ limit?: number;
51
+ }): Promise<OrderSummary[]>;
52
+ }
53
+ export declare class ProviderNotConfiguredError extends Error {
54
+ constructor(providerId: string);
55
+ }
56
+ export declare class ProviderUnsupportedError extends Error {
57
+ constructor(providerId: string, op: string);
58
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Provider adapter interface.
3
+ *
4
+ * Each POD platform (Printify, Printful, Gelato) implements this interface.
5
+ * The server depends only on this contract — providers are swappable, and one
6
+ * being yanked (Printify ToS, API terminations) doesn't break the others.
7
+ *
8
+ * Tools that fan out across providers iterate the registry and call these
9
+ * methods; per-provider failures are reported but don't abort the whole call.
10
+ */
11
+ export class ProviderNotConfiguredError extends Error {
12
+ constructor(providerId) {
13
+ super(`Provider '${providerId}' is not configured. Run configure_provider first.`);
14
+ }
15
+ }
16
+ export class ProviderUnsupportedError extends Error {
17
+ constructor(providerId, op) {
18
+ super(`Provider '${providerId}' does not support '${op}' in this version.`);
19
+ }
20
+ }
21
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAyCH,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IACnD,YAAY,UAAkB;QAC5B,KAAK,CAAC,aAAa,UAAU,oDAAoD,CAAC,CAAC;IACrF,CAAC;CACF;AAED,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACjD,YAAY,UAAkB,EAAE,EAAU;QACxC,KAAK,CAAC,aAAa,UAAU,uBAAuB,EAAE,oBAAoB,CAAC,CAAC;IAC9E,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};