Skip to content

Commit

Permalink
DX: Nicer backtracking errors (#8660)
Browse files Browse the repository at this point in the history
* dx: better debugging for backtracking errors

* fixes

* fix prod test
  • Loading branch information
runspired authored Jul 1, 2023
1 parent e8ba078 commit f1b5c2e
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 17 deletions.
10 changes: 9 additions & 1 deletion packages/model/src/-private/record-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,15 @@ class Tag {
declare isDirty: boolean;
declare value: any;
declare t: boolean;
declare _debug_base: string;
declare _debug_prop: string;

constructor() {
if (DEBUG) {
const [base, prop] = arguments as unknown as [string, string];
this._debug_base = base;
this._debug_prop = prop;
}
this.rev = 1;
this.isDirty = true;
this.value = undefined;
Expand Down Expand Up @@ -68,7 +75,8 @@ function getTag(record, key) {
tags = Object.create(null);
Tags.set(record, tags);
}
return (tags[key] = tags[key] || new Tag());
// @ts-expect-error
return (tags[key] = tags[key] || (DEBUG ? new Tag(record.constructor.modelName, key) : new Tag()));
}

export function peekTag(record, key) {
Expand Down
2 changes: 2 additions & 0 deletions packages/store/src/-private/cache-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ function maybeUpdateUiObjects<T>(
return document as T;
}
const data = recordArrayManager.createArray({
type: request.url,
identifiers: document.data,
doc: document as CollectionResourceDataDocument,
query: request,
Expand All @@ -95,6 +96,7 @@ function maybeUpdateUiObjects<T>(

if (!managed) {
managed = recordArrayManager.createArray({
type: identifier.lid,
identifiers: document.data,
doc: document as CollectionResourceDataDocument,
});
Expand Down
14 changes: 12 additions & 2 deletions packages/store/src/-private/record-arrays/identifier-array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const NOTIFY = Symbol('#notify');
const IS_COLLECTION = Symbol.for('Collection');

export function notifyArray(arr: IdentifierArray) {
arr[IDENTIFIER_ARRAY_TAG].ref = null;
addToTransaction(arr[IDENTIFIER_ARRAY_TAG]);

if (DEPRECATE_COMPUTED_CHAINS) {
// eslint-disable-next-line
Expand All @@ -101,8 +101,16 @@ class Tag {
* whether this was part of a transaction when last mutated
*/
declare t: boolean;
declare _debug_base: string;
declare _debug_prop: string;

constructor() {
if (DEBUG) {
const [arr, prop] = arguments as unknown as [IdentifierArray, string];

this._debug_base = arr.constructor.name + ':' + String(arr.modelName);
this._debug_prop = prop;
}
this.shouldReset = false;
this.t = false;
}
Expand Down Expand Up @@ -207,7 +215,7 @@ class IdentifierArray {
_updatingPromise: PromiseArray<RecordInstance, IdentifierArray> | Promise<IdentifierArray> | null = null;

[IS_COLLECTION] = true;
[IDENTIFIER_ARRAY_TAG] = new Tag();
declare [IDENTIFIER_ARRAY_TAG]: Tag;
[SOURCE]: StableRecordIdentifier[];
[NOTIFY]() {
notifyArray(this);
Expand Down Expand Up @@ -268,6 +276,8 @@ class IdentifierArray {
this.store = options.store;
this._manager = options.manager;
this[SOURCE] = options.identifiers;
// @ts-expect-error
this[IDENTIFIER_ARRAY_TAG] = DEBUG ? new Tag(this, 'length') : new Tag();
const store = options.store;
const boundFns = new Map<KeyType, ProxiedMethod>();
const _TAG = this[IDENTIFIER_ARRAY_TAG];
Expand Down
76 changes: 75 additions & 1 deletion packages/tracking/addon-main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,79 @@
const requireModule = require('@ember-data/private-build-infra/src/utilities/require-module');
const getEnv = require('@ember-data/private-build-infra/src/utilities/get-env');
const detectModule = require('@ember-data/private-build-infra/src/utilities/detect-module');

const pkg = require('./package.json');

module.exports = {
name: require('./package.json').name,
name: pkg.name,

options: {
'@embroider/macros': {
setOwnConfig: {},
},
},

_emberDataConfig: null,
configureEmberData() {
if (this._emberDataConfig) {
return this._emberDataConfig;
}
const app = this._findHost();
const isProd = /production/.test(process.env.EMBER_ENV);
const hostOptions = app.options?.emberData || {};
const debugOptions = Object.assign(
{
LOG_PAYLOADS: false,
LOG_OPERATIONS: false,
LOG_MUTATIONS: false,
LOG_NOTIFICATIONS: false,
LOG_REQUESTS: false,
LOG_REQUEST_STATUS: false,
LOG_IDENTIFIERS: false,
LOG_GRAPH: false,
LOG_INSTANCE_CACHE: false,
},
hostOptions.debug || {}
);

const HAS_DEBUG_PACKAGE = detectModule(require, '@ember-data/debug', __dirname, pkg);
const HAS_META_PACKAGE = detectModule(require, 'ember-data', __dirname, pkg);

const includeDataAdapterInProduction =
typeof hostOptions.includeDataAdapterInProduction === 'boolean'
? hostOptions.includeDataAdapterInProduction
: HAS_META_PACKAGE;

const includeDataAdapter = HAS_DEBUG_PACKAGE ? (isProd ? includeDataAdapterInProduction : true) : false;
const DEPRECATIONS = require('@ember-data/private-build-infra/src/deprecations')(hostOptions.compatWith || null);
const FEATURES = require('@ember-data/private-build-infra/src/features')(isProd);

const ALL_PACKAGES = requireModule('@ember-data/private-build-infra/virtual-packages/packages.js');
const MACRO_PACKAGE_FLAGS = Object.assign({}, ALL_PACKAGES.default);
delete MACRO_PACKAGE_FLAGS['HAS_DEBUG_PACKAGE'];

Object.keys(MACRO_PACKAGE_FLAGS).forEach((key) => {
MACRO_PACKAGE_FLAGS[key] = detectModule(require, MACRO_PACKAGE_FLAGS[key], __dirname, pkg);
});

// copy configs forward
const ownConfig = this.options['@embroider/macros'].setOwnConfig;
ownConfig.compatWith = hostOptions.compatWith || null;
ownConfig.debug = debugOptions;
ownConfig.deprecations = Object.assign(DEPRECATIONS, ownConfig.deprecations || {}, hostOptions.deprecations || {});
ownConfig.features = Object.assign({}, FEATURES, ownConfig.features || {}, hostOptions.features || {});
ownConfig.includeDataAdapter = includeDataAdapter;
ownConfig.packages = MACRO_PACKAGE_FLAGS;
ownConfig.env = getEnv(ownConfig);

this._emberDataConfig = ownConfig;
return ownConfig;
},

included() {
this.configureEmberData();
return this._super.included.call(this, ...arguments);
},

treeForVendor() {
return;
Expand Down
13 changes: 13 additions & 0 deletions packages/tracking/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const macros = require('@ember-data/private-build-infra/src/v2-babel-build-pack');

module.exports = {
plugins: [
...macros,
// '@embroider/macros/src/babel/macros-babel-plugin.js',
['@babel/plugin-transform-runtime', { loose: true }],
['@babel/plugin-transform-typescript', { allowDeclareFields: true }],
['@babel/plugin-proposal-decorators', { legacy: true, loose: true }],
['@babel/plugin-proposal-private-methods', { loose: true }],
['@babel/plugin-proposal-class-properties', { loose: true }],
],
};
8 changes: 0 additions & 8 deletions packages/tracking/babel.config.json

This file was deleted.

10 changes: 9 additions & 1 deletion packages/tracking/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,15 @@
"volta": {
"extends": "../../package.json"
},
"dependenciesMeta": {
"@ember-data/private-build-infra": {
"injected": true
}
},
"dependencies": {
"ember-cli-babel": "^7.26.11"
"ember-cli-babel": "^7.26.11",
"@ember-data/private-build-infra": "workspace:4.12.1",
"@embroider/macros": "^1.10.0"
},
"files": [
"addon-main.js",
Expand All @@ -47,6 +54,7 @@
"@babel/core": "^7.21.4",
"@babel/cli": "^7.21.0",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.21.0",
"@babel/plugin-transform-runtime": "^7.21.4",
"@babel/plugin-transform-typescript": "^7.21.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/tracking/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default {
// You can augment this if you need to.
output: addon.output(),

external: [],
external: ['@embroider/macros'],

plugins: [
// These are the modules that users should be able to import from your
Expand Down
64 changes: 61 additions & 3 deletions packages/tracking/src/-private.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DEBUG } from '@ember-data/env';

/**
* This package provides primitives that allow powerful low-level
* adjustments to change tracking notification behaviors.
Expand Down Expand Up @@ -43,6 +45,62 @@ export function subscribe(obj: Tag): void {
}
}

function updateRef(obj: Tag): void {
if (DEBUG) {
try {
obj.ref = null;
} catch (e: unknown) {
if (e instanceof Error) {
if (e.message.includes('You attempted to update `ref` on `Tag`')) {
e.message = e.message.replace(
'You attempted to update `ref` on `Tag`',
// @ts-expect-error
`You attempted to update <${obj._debug_base}>.${obj._debug_prop}` // eslint-disable-line
);
e.stack = e.stack?.replace(
'You attempted to update `ref` on `Tag`',
// @ts-expect-error
`You attempted to update <${obj._debug_base}>.${obj._debug_prop}` // eslint-disable-line
);

const lines = e.stack?.split(`\n`);
const finalLines: string[] = [];
let lastFile: string | null = null;

lines?.forEach((line) => {
if (line.trim().startsWith('at ')) {
// get the last string in the line which contains the code source location
const location = line.split(' ').at(-1)!;
// remove the line and char offset info

if (location.includes(':')) {
const parts = location.split(':');
parts.pop();
parts.pop();
const file = parts.join(':');
if (file !== lastFile) {
lastFile = file;
finalLines.push('');
}
}
finalLines.push(line);
}
});

const splitstr = '`ref` was first used:';
const parts = e.message.split(splitstr);
parts.splice(1, 0, `Original Stack\n=============\n${finalLines.join(`\n`)}\n\n${splitstr}`);

e.message = parts.join('');
}
}
throw e;
}
} else {
obj.ref = null;
}
}

function flushTransaction() {
let transaction = TRANSACTION!;
TRANSACTION = transaction.parent;
Expand All @@ -52,7 +110,7 @@ function flushTransaction() {
transaction.props.forEach((obj: Tag) => {
// mark this mutation as part of a transaction
obj.t = true;
obj.ref = null;
updateRef(obj);
});
transaction.sub.forEach((obj: Tag) => {
obj.ref;
Expand All @@ -70,15 +128,15 @@ async function untrack() {
transaction.props.forEach((obj: Tag) => {
// mark this mutation as part of a transaction
obj.t = true;
obj.ref = null;
updateRef(obj);
});
}

export function addToTransaction(obj: Tag): void {
if (TRANSACTION) {
TRANSACTION.props.add(obj);
} else {
obj.ref = null;
updateRef(obj);
}
}
export function addTransactionCB(method: OpaqueFn): void {
Expand Down
14 changes: 14 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit f1b5c2e

Please sign in to comment.