{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/utils/test/index.ts"],"sourcesContent":["import { isNestedObject } from \"#/runtime/field-parse\";\nimport type { output } from \"#/configure/index\";\nimport type { TailorDBType } from \"#/configure/services/tailordb/schema\";\nimport type { TailorField } from \"#/configure/types/type\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n// Not `record[key] = value`: assigning to `__proto__` goes through the inherited\n// setter, which mutates the prototype instead of recording the field and leaves\n// no own property behind for the value to be read from.\nfunction setField(record: Record<string, unknown>, key: string, value: unknown): void {\n  Object.defineProperty(record, key, {\n    value,\n    enumerable: true,\n    writable: true,\n    configurable: true,\n  });\n}\n\n/**\n * Creates a hook function that processes TailorDB table fields\n * - Uses existing id from data if provided, otherwise generates UUID for id fields\n * - Recursively processes nested types\n * - Executes hooks.create for fields with create hooks\n * - Takes each field from the data's own properties, so a field named after a\n *   member of `Object` such as `toString` is read from the record rather than\n *   from the prototype\n * @template T - The output type of the hook function\n * @param type - TailorDB table definition\n * @returns A function that transforms input data according to field hooks\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function createTailorDBHook<T extends TailorDBType<any, any>>(type: T) {\n  return (data: unknown, now: Date = new Date()) => {\n    const obj = data && typeof data === \"object\" ? (data as Record<string, unknown>) : undefined;\n    const hooked = Object.entries(type.fields).reduce(\n      (hooked, [key, value]) => {\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        const field = value as TailorField<any, any, any>;\n        // `Object.hasOwn`, not `obj?.[key]`: a field named after an Object member\n        // such as `toString` would otherwise read the inherited value.\n        const input = obj && Object.hasOwn(obj, key) ? obj[key] : undefined;\n        let hookedValue: unknown;\n        if (key === \"id\") {\n          hookedValue = input ?? crypto.randomUUID();\n        } else if (field.type === \"nested\") {\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          const nestedHook = createTailorDBHook({ fields: field.fields } as any);\n          if (field.metadata.array) {\n            hookedValue = Array.isArray(input)\n              ? input.map((item) => (isNestedObject(item) ? nestedHook(item, now) : item))\n              : input;\n          } else {\n            hookedValue = isNestedObject(input) ? nestedHook(input, now) : input;\n          }\n        } else if (field.metadata.hooks?.create) {\n          hookedValue = field.metadata.hooks.create({ input, invoker: null, now });\n          if (hookedValue instanceof Date) {\n            hookedValue = hookedValue.toISOString();\n          }\n        } else {\n          hookedValue = input;\n        }\n        if (hookedValue == null && field.metadata.default !== undefined) {\n          const isTimeType =\n            field.type === \"datetime\" || field.type === \"date\" || field.type === \"time\";\n          hookedValue =\n            field.metadata.default === \"now\" && isTimeType\n              ? now.toISOString()\n              : field.metadata.default;\n        }\n        // Set even when there is no value: the key carrying `undefined` is what\n        // tells a schema inferred from the record that the column is nullable,\n        // and it shadows a same-named member of `Object.prototype`.\n        setField(hooked, key, hookedValue);\n        return hooked;\n      },\n      {} as Record<string, unknown>,\n    );\n\n    // oxlint-disable-next-line typescript/no-unnecessary-condition -- metadata absent in recursive nested calls\n    if (type.metadata?.typeHook?.create) {\n      const { id: _id, ...typeHookInput } = hooked;\n      // oxlint-disable-next-line typescript/no-unsafe-function-type\n      const overrides = type.metadata.typeHook.create({\n        input: typeHookInput,\n        invoker: null,\n        now,\n      });\n      if (overrides && typeof overrides === \"object\") {\n        for (const [key, value] of Object.entries(overrides as Record<string, unknown>)) {\n          setField(hooked, key, value instanceof Date ? value.toISOString() : value);\n        }\n      }\n    }\n\n    return hooked as Partial<output<T>>;\n  };\n}\n\n// Collect the issues the table's own `validate` reports for a record, so they\n// surface the same way a field's do instead of ending the run.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction typeLevelIssues(type: TailorDBType<any, any> | undefined, hooked: unknown) {\n  // oxlint-disable-next-line typescript/no-unnecessary-condition -- absent on a nested type\n  const typeValidate = type?.metadata?.typeValidate;\n  if (!typeValidate) {\n    return [];\n  }\n  const { id: _id, ...newRecord } = hooked as Record<string, unknown>;\n  const issues: StandardSchemaV1.Issue[] = [];\n  // oxlint-disable-next-line typescript/no-unsafe-function-type\n  typeValidate({ newRecord, oldRecord: null, invoker: null }, (field: string, message: string) => {\n    issues.push({ message, path: [field] });\n  });\n  return issues;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype DeclaredFields = Record<string, TailorField<any, any, any>>;\n\nfunction undeclaredFieldMessage(key: string): string {\n  return `Field \"${key}\" is not declared by the table. Remove it from the row, or add it to the table definition and run \\`tailor generate\\`.`;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n// Reads the raw row, not the hooked one: the hook only copies the declared fields,\n// so an undeclared key is gone by the time the field schema runs.\nfunction collectUndeclaredFieldIssues(\n  value: unknown,\n  fields: DeclaredFields,\n  path: string[],\n  issues: StandardSchemaV1.Issue[],\n): void {\n  if (!isRecord(value)) {\n    return;\n  }\n  for (const key of Object.keys(value)) {\n    // `Object.hasOwn`, not `key in fields`: a key such as `constructor` would\n    // otherwise resolve to a member of `Object.prototype`.\n    if (!Object.hasOwn(fields, key)) {\n      issues.push({ message: undeclaredFieldMessage(key), path: path.concat(key) });\n      continue;\n    }\n    const field = fields[key];\n    if (field?.type !== \"nested\") {\n      continue;\n    }\n    const nested = value[key];\n    const nestedFields = field.fields as DeclaredFields;\n    if (field.metadata.array) {\n      if (Array.isArray(nested)) {\n        nested.forEach((item, index) => {\n          collectUndeclaredFieldIssues(item, nestedFields, path.concat(key, `[${index}]`), issues);\n        });\n      }\n    } else {\n      collectUndeclaredFieldIssues(nested, nestedFields, path.concat(key), issues);\n    }\n  }\n}\n\n/**\n * Creates the standard schema definition used to validate seed rows.\n * Runs the hook, then the table's own `validate`, and the field schema only when\n * that reported nothing, so both levels of validation report as issues rather\n * than by throwing. When the table is given, a key the row carries that the table\n * does not declare is reported as an issue as well, including keys inside nested\n * objects, so a row that no longer matches the table fails here instead of when\n * it is applied.\n * @template T - The output type after validation\n * @param schemaType - TailorDB field schema for validation\n * @param hook - Hook function to transform data before validation\n * @param type - TailorDB table definition; runs its table-level `validate` and\n *   rejects fields it does not declare\n * @returns Schema object with ~standard section for defineSchema\n */\nexport function createStandardSchema<T = Record<string, unknown>>(\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  schemaType: TailorField<any, T>,\n  hook: (data: unknown) => Partial<T>,\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  type?: TailorDBType<any, any>,\n) {\n  const validateHooked = (hooked: Partial<T>): StandardSchemaV1.Result<T> => {\n    const issues = typeLevelIssues(type, hooked);\n    if (issues.length > 0) {\n      return { issues };\n    }\n    const result = schemaType.parse({\n      value: hooked,\n      data: hooked,\n      invoker: null,\n    });\n    if (result.issues) {\n      return result;\n    }\n    return { value: hooked as T };\n  };\n\n  return {\n    \"~standard\": {\n      version: 1,\n      vendor: \"@tailor-platform/sdk\",\n      validate: (value: unknown) => {\n        const hooked = hook(value);\n        const undeclared: StandardSchemaV1.Issue[] = [];\n        if (type) {\n          collectUndeclaredFieldIssues(value, type.fields as DeclaredFields, [], undeclared);\n        }\n        const result = validateHooked(hooked);\n        const issues = [...undeclared, ...(result.issues ?? [])];\n        return issues.length > 0 ? { issues } : result;\n      },\n    },\n  } as const satisfies StandardSchemaV1<T>;\n}\n"],"mappings":"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"}