Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ jobs:
- name: Run type check
run: pnpm run type-check

- name: Check core exports
run: pnpm run check-core-exports

build-and-exports:
name: Build and Check Exports
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
{
"files": [
"scripts/**",
"packages/core/src/headlessForm.ts",
"packages/core/src/form/headlessForm.ts",
"src/common/createHeadlessForm.tsx",
"src/common/headlessForm.ts",
"src/common/useHeadlessForm.ts",
Expand Down
14 changes: 14 additions & 0 deletions knip.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/knip@6/schema.json",
"workspaces": {
".": {
"entry": ["src/index.tsx"],
"project": ["src/**/*.{ts,tsx}"],
"ignore": ["src/client/**"]
},
"packages/core": {
"project": ["src/**/*.ts"],
"includeEntryExports": true
}
}
}
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"scripts": {
"audit": "pnpm audit --audit-level=high",
"build": "NODE_ENV=production tsdown --config-loader unrun",
"ci": "pnpm run build && pnpm run check-format && pnpm run check-exports && pnpm run lint && pnpm run type-check && pnpm run test",
"ci": "pnpm run build && pnpm run check-format && pnpm run check-exports && pnpm run check-core-exports && pnpm run lint && pnpm run type-check && pnpm run test",
"check-core-exports": "knip --workspace packages/core --include exports",
"check-exports": "attw --pack . --ignore-rules=cjs-resolves-to-esm --exclude-entrypoints index.css styles.css --profile node16 && pnpm run check-flow-subpaths && pnpm run check-single-schema-form",
"check-flow-subpaths": "tsx scripts/check-flow-subpaths.ts",
"check-single-schema-form": "tsx scripts/check-single-schema-form.ts",
Expand Down Expand Up @@ -139,10 +140,10 @@
"zod": "4.6.6"
},
"devDependencies": {
"@remoteoss/remote-flows-core": "workspace:*",
"@arethetypeswrong/cli": "0.18.5",
"@faker-js/faker": "^10.6.0",
"@hey-api/openapi-ts": "0.0.0-next-20260824173136",
"@remoteoss/remote-flows-core": "workspace:*",
"@testing-library/jest-dom": "7.0.1",
"@testing-library/react": "16.3.3",
"@testing-library/user-event": "14.6.8",
Expand All @@ -164,6 +165,7 @@
"glob": "13.0.6",
"gzip-size": "7.0.0",
"jsdom": "30.1.3",
"knip": "6.44.1",
"lodash": "4.18.1",
"msw": "3.0.3",
"oxfmt": "0.74.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
buildRecurrence,
toOrdinal,
} from './utils';
import { createHeadlessForm } from '../headlessForm';
import type { $TSFixMe } from '../types';
import { createHeadlessForm } from '../../form/headlessForm';
import type { $TSFixMe } from '../../types';

const baseValues = {
currency: 'USD',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,9 +526,7 @@ export function buildCreateInvoiceScheduleSchema({
{
const: PLACEHOLDER_OPTION,
// An empty list is an answer — this contractor has no currencies to be
// invoiced in — where no list at all means the fetch has not resolved. Both
// states exist: the flows build the schema once currencies arrive, but the
// static `createInvoiceScheduleSchema` fallback is built without any.
// invoiced in — where no list at all means the fetch has not resolved.
// Labelling the empty case "loading" left it looking stuck forever.
title: currencies
? 'No currencies available'
Expand Down Expand Up @@ -743,9 +741,3 @@ export function buildCreateInvoiceScheduleSchema({
],
};
}

/**
* The schema as the in-flow onboarding step renders it.
* Currencies are substituted at runtime — see `useGetCreateInvoiceScheduleSchema`.
*/
export const createInvoiceScheduleSchema = buildCreateInvoiceScheduleSchema();
File renamed without changes.
43 changes: 36 additions & 7 deletions packages/core/src/form.ts → packages/core/src/form/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,40 @@ import {
isWeekend,
nextMonday,
} from 'date-fns';
import { supportedTypes } from './fieldTypes';
import { isForceValue, isMultiSelect } from './fieldHelpers';
import { convertFromCents, convertToCents } from './money';
import { getNestedValue } from './object';
import type { $TSFixMe, Field, JSFFields } from './types';
import { supportedTypes } from '../fieldTypes';
import { isForceValue, isMultiSelect } from '../fieldHelpers';
import { convertFromCents, convertToCents } from '../money';
import type { $TSFixMe, Field, JSFFields } from '../types';

/**
* Gets a nested value from an object using a dot-notation path string.
* Replacement for lodash.get with support for nested property access.
* @param obj - The object to query
* @param path - The path to the property (e.g., 'user.address.city')
* @param defaultValue - The value to return if the resolved value is undefined
* @returns The resolved value or defaultValue
*/
export function getNestedValue<T = unknown>(
obj: unknown,
path: string,
defaultValue?: T,
): T | undefined {
if (obj == null || typeof path !== 'string') {
return defaultValue;
}

const keys = path.split('.');
let result: unknown = obj;

for (const key of keys) {
if (result == null || typeof result !== 'object') {
return defaultValue;
}
result = (result as Record<string, unknown>)[key];
}

return (result === undefined ? defaultValue : result) as T | undefined;
}

const trimStringValues = (values: Record<string, $TSFixMe>) =>
Object.entries(values || {}).reduce<Record<string, $TSFixMe>>(
Expand Down Expand Up @@ -212,7 +241,7 @@ function getValueToAPITransformer(type: string, convertFiles: FileConverter) {
return baseFieldTypesTransformations[type]?.transformValueToAPI;
}

export async function parseFormValuesToAPI(
async function parseFormValuesToAPI(
formValues: Record<string, $TSFixMe> = {},
fields: $TSFixMe[],
convertFiles: FileConverter,
Expand Down Expand Up @@ -487,7 +516,7 @@ function cleanUnderscoreFields(
return obj;
}

export async function parseSubmitValues(
async function parseSubmitValues(
formValues: Record<string, $TSFixMe>,
fields: $TSFixMe[],
config: { keepInvisibleValues?: boolean; convertFiles: FileConverter },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { parseValuesForValidation } from './headlessForm';
import type { JSONSchemaFormResultWithFieldsets } from './types';
import type { JSONSchemaFormResultWithFieldsets } from '../types';

describe('parseValuesForValidation', () => {
it('converts selected files with the converter it is given', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
createHeadlessForm as baseCreateHeadlessForm,
modify,
} from '@remoteoss/remote-json-schema-form-kit';
import { convertToCents } from './money';
import { convertToCents } from '../money';
import { parseJSFToValidate } from './form';
import type { FileConverter } from './form';
import type {
Expand All @@ -11,12 +11,9 @@ import type {
JSFModify,
JSONSchemaFormResultWithFieldsets,
$TSFixMe,
} from './types';
} from '../types';

export function findFieldsByType(
fields: Record<string, $TSFixMe>,
type: string,
) {
function findFieldsByType(fields: Record<string, $TSFixMe>, type: string) {
const fieldsNames = [];
for (const [key, value] of Object.entries(fields)) {
// old schemas use type, new schemas use inputType
Expand Down
35 changes: 16 additions & 19 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
export { convertFromCents, convertToCents, round } from './money';
export { supportedTypes } from './fieldTypes';
export {
checkFieldHasForcedValue,
fieldsetHasVisibleContent,
getBlankRowValues,
getFieldRenderType,
isFieldSkipped,
isForceValue,
isMultiSelect,
resolveFieldDynamicProperties,
} from './fieldHelpers';
export {
enableAckFields,
getFieldsWithFlatFieldsets,
getInitialValues,
getMinStartDate,
getNestedValue,
isDateDisabled,
parseFormValuesToAPI,
parseJSFToValidate,
parseSubmitValues,
} from './form';
export type { FileConverter } from './form';
export { getNestedValue } from './object';
} from './form/form';
export {
buildHeadlessForm,
createHeadlessForm,
parseValuesForValidation,
withFieldsetObjects,
} from './form/headlessForm';
export type {
HeadlessFormOptions,
HeadlessFormStrategy,
} from './form/headlessForm';
export type {
$TSFixMe,
Field,
Expand All @@ -32,19 +37,11 @@ export type {
JSONSchemaFormResultWithFieldsets,
UploadedFileReference,
} from './types';
export {
buildHeadlessForm,
createHeadlessForm,
findFieldsByType,
parseValuesForValidation,
withFieldsetObjects,
} from './headlessForm';
export type { HeadlessFormOptions, HeadlessFormStrategy } from './headlessForm';
export { INVOICE_SCHEDULE_STATUS } from './invoiceSchedules/constants';
export { buildCreateInvoiceScheduleSchema } from './invoiceSchedules/jsonSchema';
export { INVOICE_SCHEDULE_STATUS } from './domains/invoice-schedule/constants';
export { buildCreateInvoiceScheduleSchema } from './domains/invoice-schedule/jsonSchema';
export {
buildInvoicePreviewPayload,
buildInvoiceSchedulePayload,
buildInvoiceScheduleFormValues,
} from './invoiceSchedules/utils';
export type { ContractorInvoicePreview } from './invoiceSchedules/types';
} from './domains/invoice-schedule/utils';
export type { ContractorInvoicePreview } from './domains/invoice-schedule/types';
29 changes: 0 additions & 29 deletions packages/core/src/object.ts

This file was deleted.

Loading
Loading