UNPKG

@swoft/gtd-domain

Version:

Getting Things Done (GTD) productivity system - consolidated domain implementation

526 lines (522 loc) 16.7 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/client/index.ts var client_exports = {}; __export(client_exports, { GTDClient: () => GTDClient }); module.exports = __toCommonJS(client_exports); // src/contracts/api/backend-response-types.ts var import_zod = require("zod"); var BackendInboxItemSchema = import_zod.z.object({ id: import_zod.z.string(), content: import_zod.z.string(), text: import_zod.z.string().optional(), title: import_zod.z.string().optional(), status: import_zod.z.enum([ "unprocessed", "processed", "clarified" ]), capturedAt: import_zod.z.string(), createdAt: import_zod.z.string(), capturedBy: import_zod.z.string(), capturedByPerson: import_zod.z.string().nullable().optional(), clarification: import_zod.z.string().nullable().optional(), description: import_zod.z.string().nullable().optional(), isActionable: import_zod.z.boolean().nullable().optional(), tags: import_zod.z.array(import_zod.z.string()) }); var BackendProjectSchema = import_zod.z.object({ id: import_zod.z.string(), name: import_zod.z.string().optional(), title: import_zod.z.string().optional(), description: import_zod.z.string(), status: import_zod.z.enum([ "active", "completed", "someday", "cancelled" ]), createdAt: import_zod.z.string(), ownerId: import_zod.z.string().optional(), createdBy: import_zod.z.string().optional(), priority: import_zod.z.enum([ "low", "medium", "high" ]).optional(), progress: import_zod.z.object({ totalActions: import_zod.z.number(), completedActions: import_zod.z.number(), completionPercentage: import_zod.z.number() }) }); var BackendSuccessResponseSchema = /* @__PURE__ */ __name((dataSchema) => import_zod.z.object({ success: import_zod.z.literal(true), data: dataSchema }), "BackendSuccessResponseSchema"); var BackendErrorResponseSchema = import_zod.z.object({ success: import_zod.z.literal(false), error: import_zod.z.object({ message: import_zod.z.string(), code: import_zod.z.string().optional(), details: import_zod.z.record(import_zod.z.any()).optional() }) }); var BackendInboxListResponseSchema = import_zod.z.union([ BackendSuccessResponseSchema(import_zod.z.object({ items: import_zod.z.array(BackendInboxItemSchema), total: import_zod.z.number() })), BackendErrorResponseSchema ]); var BackendProjectsListResponseSchema = import_zod.z.union([ BackendSuccessResponseSchema(import_zod.z.object({ projects: import_zod.z.array(BackendProjectSchema), total: import_zod.z.number().optional() })), BackendErrorResponseSchema ]); function isBackendSuccessResponse(response) { return response.success === true; } __name(isBackendSuccessResponse, "isBackendSuccessResponse"); var BackendApiError = class _BackendApiError extends Error { static { __name(this, "BackendApiError"); } code; details; constructor(code, message, details) { super(message), this.code = code, this.details = details; this.name = "BackendApiError"; } static fromBackendError(errorResponse) { return new _BackendApiError(errorResponse.error.code || "UNKNOWN_ERROR", errorResponse.error.message, errorResponse.error.details); } }; var ApiValidationError = class extends Error { static { __name(this, "ApiValidationError"); } validationErrors; constructor(message, validationErrors) { super(message), this.validationErrors = validationErrors; this.name = "ApiValidationError"; } }; // src/contracts/api/error-handling.ts function classifyError(error) { if (error instanceof TypeError && error.message.includes("fetch")) { return { type: "NETWORK_ERROR", message: "Network connection failed. Please check your internet connection.", originalError: error }; } if (error instanceof BackendApiError) { return { type: getErrorTypeFromCode(error.code), message: error.message, code: error.code, details: error.details, originalError: error }; } if (error instanceof ApiValidationError) { return { type: "VALIDATION_ERROR", message: error.message, details: error.validationErrors, originalError: error }; } if (error.status) { return classifyHttpError(error.status, error.message || "HTTP Error"); } return { type: "UNKNOWN_ERROR", message: error.message || "An unexpected error occurred", originalError: error }; } __name(classifyError, "classifyError"); function getErrorTypeFromCode(code) { switch (code) { case "UNAUTHORIZED": case "INVALID_TOKEN": return "AUTHENTICATION_ERROR"; case "FORBIDDEN": case "INSUFFICIENT_PERMISSIONS": return "AUTHORIZATION_ERROR"; case "NOT_FOUND": case "RESOURCE_NOT_FOUND": return "NOT_FOUND_ERROR"; case "VALIDATION_FAILED": case "INVALID_REQUEST": return "VALIDATION_ERROR"; case "SERVER_ERROR": case "INTERNAL_ERROR": return "SERVER_ERROR"; case "TIMEOUT": return "TIMEOUT_ERROR"; default: return "API_ERROR"; } } __name(getErrorTypeFromCode, "getErrorTypeFromCode"); function classifyHttpError(status, message) { if (status === 401) { return { type: "AUTHENTICATION_ERROR", message: "Authentication required. Please log in.", code: "UNAUTHORIZED" }; } if (status === 403) { return { type: "AUTHORIZATION_ERROR", message: "You do not have permission to perform this action.", code: "FORBIDDEN" }; } if (status === 404) { return { type: "NOT_FOUND_ERROR", message: "The requested resource was not found.", code: "NOT_FOUND" }; } if (status >= 400 && status < 500) { return { type: "VALIDATION_ERROR", message: message || "Invalid request", code: `HTTP_${status}` }; } if (status >= 500) { return { type: "SERVER_ERROR", message: "Server error occurred. Please try again later.", code: `HTTP_${status}` }; } return { type: "UNKNOWN_ERROR", message: message || `HTTP error ${status}`, code: `HTTP_${status}` }; } __name(classifyHttpError, "classifyHttpError"); var DEFAULT_RETRY_CONFIG = { maxRetries: 3, retryDelay: 1e3, retryableErrors: [ "NETWORK_ERROR", "TIMEOUT_ERROR", "SERVER_ERROR" ] }; function shouldRetry(error, config = DEFAULT_RETRY_CONFIG) { return config.retryableErrors.includes(error.type); } __name(shouldRetry, "shouldRetry"); async function withRetry(operation, config = DEFAULT_RETRY_CONFIG) { let lastError; for (let attempt = 0; attempt <= config.maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = classifyError(error); if (attempt === config.maxRetries || !shouldRetry(lastError, config)) { throw lastError; } await new Promise((resolve) => setTimeout(resolve, config.retryDelay * Math.pow(2, attempt))); } } throw lastError; } __name(withRetry, "withRetry"); var ConsoleErrorLogger = class { static { __name(this, "ConsoleErrorLogger"); } logError(error, context) { console.error("API Error:", { type: error.type, message: error.message, code: error.code, details: error.details, context, stack: error.originalError?.stack }); } }; var defaultErrorLogger = new ConsoleErrorLogger(); // src/client/gtd-client.ts var GTDClient = class { static { __name(this, "GTDClient"); } http; basePath; config; constructor(http, basePath = "/api/productivity/gtd", config = {}) { this.http = http; this.basePath = basePath; this.config = { enableValidation: true, enableRetry: true, errorLogger: defaultErrorLogger, ...config }; } async executeRequest(operation) { try { if (this.config.enableRetry) { return await withRetry(operation); } return await operation(); } catch (error) { const standardError = classifyError(error); this.config.errorLogger?.logError(standardError); throw standardError; } } validateResponse(data, schema, operationName) { if (!this.config.enableValidation) { return data; } const result = schema.safeParse(data); if (!result.success) { throw new ApiValidationError(`Invalid response format for ${operationName}`, result.error.issues); } return result.data; } // Inbox Operations async getInboxItems(filters) { return this.executeRequest(async () => { const queryParams = filters ? new URLSearchParams(Object.entries(filters).filter(([_, value]) => value !== void 0).reduce((acc, [key, value]) => ({ ...acc, [key]: String(value) }), {})).toString() : ""; const url = queryParams ? `${this.basePath}/inbox?${queryParams}` : `${this.basePath}/inbox`; const rawResponse = await this.http.get(url, { headers: { "Content-Type": "application/json" } }); const validatedResponse = this.validateResponse(rawResponse, BackendInboxListResponseSchema, "getInboxItems"); if (!isBackendSuccessResponse(validatedResponse)) { throw BackendApiError.fromBackendError(validatedResponse); } const { data } = validatedResponse; return { items: data.items.map((item) => ({ id: item.id, originalContent: item.content || item.text || item.title || "", capturedAt: item.capturedAt, capturedByPersonId: item.capturedBy || "unknown", processingStatus: item.status === "processed" ? "processed" : "unprocessed", clarification: item.clarification || void 0, isActionable: item.isActionable || void 0, // These fields are not available in backend response yet - use undefined for now lastRefinedAt: void 0, refinedByPersonId: void 0 })), pagination: { totalCount: data.total }, totalCount: data.total }; }); } async getInboxItem(id) { const response = await this.http.get(`${this.basePath}/inbox/${id}`); return response.item || null; } async captureInboxItem(request) { const response = await this.http.post(`${this.basePath}/inbox`, request); return response.item || response; } async refineInboxItem(id, request) { const response = await this.http.post(`${this.basePath}/inbox/${id}/refine`, request); return response.item || response; } async clarifyInboxItem(id, request) { const response = await this.http.post(`${this.basePath}/inbox/${id}/clarify`, request); return response.item || response; } async processInboxItem(id, request) { return this.http.post(`${this.basePath}/inbox/${id}/process`, request); } async deleteInboxItem(id) { await this.http.delete(`${this.basePath}/inbox/${id}`); } // Project Operations async getProjects(filters) { const queryParams = filters ? new URLSearchParams(Object.entries(filters).filter(([_, value]) => value !== void 0).reduce((acc, [key, value]) => ({ ...acc, [key]: String(value) }), {})).toString() : ""; const url = queryParams ? `${this.basePath}/projects?${queryParams}` : `${this.basePath}/projects`; const response = await this.http.get(url); if (response.items && response.total !== void 0) { return { items: response.items, pagination: { totalCount: response.total, page: response.meta?.page, pageSize: response.meta?.limit }, totalCount: response.total }; } const items = Array.isArray(response) ? response : []; return { items, pagination: { totalCount: items.length }, totalCount: items.length }; } async getProject(id) { const response = await this.http.get(`${this.basePath}/projects/${id}`); return response.item || null; } async createProject(request) { const response = await this.http.post(`${this.basePath}/projects`, request); return response.item || response; } async completeProject(id, completedBy, completionNotes) { const response = await this.http.post(`${this.basePath}/projects/${id}/complete`, { completedBy, completionNotes }); return response.item || response; } async deferProject(id) { const response = await this.http.post(`${this.basePath}/projects/${id}/defer`); return response.item || response; } async activateProject(id) { const response = await this.http.post(`${this.basePath}/projects/${id}/activate`); return response.item || response; } async cancelProject(id) { const response = await this.http.post(`${this.basePath}/projects/${id}/cancel`); return response.item || response; } async deleteProject(id) { await this.http.delete(`${this.basePath}/projects/${id}`); } // Next Action Operations async getNextActions(filters) { const queryParams = filters ? new URLSearchParams(Object.entries(filters).filter(([_, value]) => value !== void 0).reduce((acc, [key, value]) => ({ ...acc, [key]: String(value) }), {})).toString() : ""; const url = queryParams ? `${this.basePath}/actions?${queryParams}` : `${this.basePath}/actions`; const response = await this.http.get(url); if (response.items && response.total !== void 0) { return { items: response.items, pagination: { totalCount: response.total, page: response.meta?.page, pageSize: response.meta?.limit }, totalCount: response.total }; } const items = Array.isArray(response) ? response : []; return { items, pagination: { totalCount: items.length }, totalCount: items.length }; } async getNextAction(id) { const response = await this.http.get(`${this.basePath}/actions/${id}`); return response.item || null; } async createNextAction(request) { const response = await this.http.post(`${this.basePath}/actions`, request); return response.item || response; } async assignNextAction(id, request) { const response = await this.http.post(`${this.basePath}/actions/${id}/assign`, request); return response.item || response; } async unassignNextAction(id) { const response = await this.http.post(`${this.basePath}/actions/${id}/unassign`); return response.item || response; } async completeNextAction(id) { const response = await this.http.post(`${this.basePath}/actions/${id}/complete`); return response.item || response; } async deleteNextAction(id) { await this.http.delete(`${this.basePath}/actions/${id}`); } // Convenience methods that match the existing frontend API async getActionsByProject(projectId) { return this.getNextActions({ projectId }); } async getActionsByContext(context) { return this.getNextActions({ context }); } async getContexts() { const response = await this.http.get(`${this.basePath}/contexts`); const contexts = Array.isArray(response) ? response : []; return { items: contexts, pagination: { totalCount: contexts.length }, totalCount: contexts.length }; } // Weekly Review async getWeeklyReview(reviewedBy) { const queryParams = new URLSearchParams({ reviewedBy }).toString(); const response = await this.http.get(`${this.basePath}/weekly-review?${queryParams}`, { headers: { "Content-Type": "application/json" } }); return response.review || response; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { GTDClient }); //# sourceMappingURL=index.js.map